Skip to content

feat(map): draw nodes at one spot as one counted chip on both maps - #7762

Merged
jamesarich merged 4 commits into
mainfrom
feat/map-colocated-nodes
Oct 25, 2026
Merged

jamesarich merged 4 commits into
mainfrom
feat/map-colocated-nodes

Conversation

@jamesarich

@jamesarich jamesarich commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Nodes sharing a reduced-precision position, or radios a few meters apart at one site, drew as chips piled on one another. Nothing showed there were several, only the top one could be tapped, and on MapLibre the "Nodes at this location" list never opened for a cluster of them because supercluster reports clusterMaxZoom + 1 for points that never split.

🌟 New Features

  • Nodes within 5 m form one stack, the threshold the Apple map uses (MapColocation.spreadMeters). Grouping lives in feature/map (stackCoincident) so both flavors draw the same stacks.
  • A stack draws its top node's chip (this node or a favorite, then the lowest node number, so it does not flip as packets arrive) with a Material 3 count badge in the secondary color. Google uses BadgedBox and Badge; MapLibre rasterizes the same large badge since a map layer cannot host a composable.
  • Tapping a stack lists its nodes, favorites first in natural name order (S6 before S10) by local display name where set, on both maps. A single chip opens the node's details on both, so Google drops its info window.
  • One precision circle per stack at 0.15 alpha on both maps, drawn inside cluster bubbles too.
  • A Google cluster tap zooms to fit its members with a 140 m floor instead of one zoom level.

🐛 Bug Fixes

  • MapLibre lists a cluster that cannot split instead of doing nothing on tap.

🧹 Chores

  • map-and-waypoints.md describes stacks, the badge and the cluster tap.
  • Removes unclusteredNodes, which only the per-node precision circles used.

Testing Performed

  • NodeStackTest: 5 m grouping, separation past the spread, a stack spanning two latitude bands, top-node rule, badge label cap.
  • StackFeaturesTest: stack count and members on MapLibre features.
  • ClusterCannotSplitTest: expansion zoom past the ceiling and failed-query sentinels list the cluster.
  • ClusterMemberOrderTest: natural order, favorites first, long name over the heard-ago title, a non-ASCII digit sorting as text (fails under the old isDigit check), and a local display name deciding the order over the broadcast long name (fails when sorting on user.long_name).
  • MapNodeClusterItemsTest: co-located nodes become one Google item carrying every member.
  • Full baseline with -PwarningsAsErrors=true and kmpSmokeCompile.
  • On an emulator against a simulated radio, both flavors: a 5-node and a 15-node stack at one precision-13 point, and three full-precision radios 2 m apart.
Before After
Google before Google after
MapLibre before MapLibre after

Summary by CodeRabbit

  • New Features

    • Nodes within 5 meters now appear as a single map marker with a count. Tapping the marker opens a list of nodes at that location, with favorites shown first.
    • Cluster taps zoom to fit the nodes when possible; clusters that cannot split display their members instead.
    • Precision circles remain visible for stacked nodes, including within clusters.
    • Cluster members are sorted by favorite status, then by name.
  • Bug Fixes

    • Map markers prioritize your node or a favorite when choosing which node to display.
    • Cluster zooming fits the map around the cluster with a minimum visible span.

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Walkthrough

Map markers now group nodes within 5 meters into stacks, display stack counts, and retain member data for lists. Stack taps open member lists in both map implementations. Cluster handling, precision-circle rendering, tests, and map documentation are also updated.

Changes: Node stacking

Layer / File(s) Summary
Stack model and member ordering
feature/map/src/commonMain/..., feature/map/src/commonTest/..., feature/map/src/commonMain/.../component/*, feature/map/src/commonTest/.../component/*
Adds proximity-based node stacks with a selected top node and count labels. Cluster member lists sort favorites first, then use natural name ordering.
MapLibre stack feature data
feature/map-maplibre/src/commonMain/.../geojson/*, feature/map-maplibre/src/commonMain/.../MapGeometry.kt, feature/map-maplibre/src/commonTest/...
GeoJSON features carry stack counts and member data. Member extraction reads the encoded list. The former unclustered-node helper and its tests are removed.
MapLibre markers and cluster interactions
feature/map-maplibre/src/commonMain/.../layers/*, feature/map-maplibre/src/commonTest/.../layers/*
Map layers use stack tops, show badges, and route stack taps to member lists. Cluster taps list members when the cluster cannot split at its expansion zoom.
Google Maps stacks and cluster fitting
androidApp/src/google/.../map/*, androidApp/src/testGoogle/..., docs/en/user/map-and-waypoints.md
Google Maps groups colocated items, shows stack badges and precision circles, and opens member lists on stack taps. Other cluster taps fit expanded bounds. Tests and documentation cover the updated behavior.

Priority: Normal

Merge Risk: Moderate · up to d5615

Some closely spaced Google map nodes may remain inaccessible through cluster taps. Resolve that interaction before merging unless the limitation is explicitly accepted.

Pre-merge checks: Passed 6 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Tests Prove The Path, Not The End State Warning The prior failure remains unresolved. feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/NodeStackTest.kt still checks only stack counts for key grouping cases: identical coordinates (line… Strengthen NodeStackTest with exact member-number assertions. Assert [1, 2, 3, 4, 5] for identical coordinates, [1, 2] for the near pair, [listOf(1, 2), listOf(3)] for a near pair plus a distant node, and [1, 2, ..., 10] for the l…
Regression Coverage For Changed Behavior Warning Regression coverage remains incomplete. (1) MapView.MainMapContent and LatLngBounds.atLeastSpan change Google cluster camera fitting, including the 140 m minimum span, latitude-adjusted longitude,… Add a testable Google camera-fit model or callback harness for the 140 m floor, high-latitude longitude adjustment, 64 dp padding, same-location listing, and separated-cluster fit. Add Google interaction tests for singleton navigation, stac…
Passed Passed checks: 6 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title "feat(map): draw nodes at one spot as one counted chip on both maps" matches the main change. The PR groups nodes within 5 m into stacks on both the Google and MapLibre maps. Each stack draw…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Sibling Call Sites And Presence Semantics Passed PASS. The PR diff contains no changes to ambient temperature, humidity, RSSI, current, voltage, particulate concentration, or SNR fields. It adds map-only stack metadata: stackMembers defaults to `e…
Moved Code Diffed Against Its Original Passed No qualifying silent move was introduced. The old nodesToFeatureCollection implementation was extracted to private Node.toFeature in the same file; its public signature, default myNodeNum = null…
Full details: Title check

Explanation

The title "feat(map): draw nodes at one spot as one counted chip on both maps" matches the main change. The PR groups nodes within 5 m into stacks on both the Google and MapLibre maps. Each stack draws as one chip with a count badge, and tapping a stack lists its members. The title is specific and concise, and it follows the conventional commit style. It omits secondary changes such as the 140 m cluster fit, the MapLibre unsplittable-cluster fix, and the removal of the unclusteredNodes function, but the instructions allow that.

Full details: Sibling Call Sites And Presence Semantics

Explanation

PASS. The PR diff contains no changes to ambient temperature, humidity, RSSI, current, voltage, particulate concentration, or SNR fields. It adds map-only stack metadata: stackMembers defaults to emptyList(), while STACK_COUNT and STACK_MEMBERS are omitted for single-node features. These are not physical measurements, and no new field defaults to numeric 0. The existing precision presence handling remains precisionRadiusMetersOrNull(...)? .let { put(...) }, including the shared nodesToFeatureCollection callers and GeoCircle; no sibling call site was left with a changed presence contract.

Full details: Moved Code Diffed Against Its Original

Explanation

No qualifying silent move was introduced. The old nodesToFeatureCollection implementation was extracted to private Node.toFeature in the same file; its public signature, default myNodeNum = null, validation, and emitted node properties remain equivalent. unclusteredNodes was intentionally removed, and its sole production caller in NodeLayers was updated to the stack-based flow; no stale callers remain. No moved annotation, override, validation, numeric nullability, or coroutine-policy change was found.

Full details: Tests Prove The Path, Not The End State

Explanation

The prior failure remains unresolved. feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/NodeStackTest.kt still checks only stack counts for key grouping cases: identical coordinates (lines 40–43), the near pair (49–52), separated nodes (57–61), and the latitude-band case (67–70). These assertions do not verify which node numbers are members, so incorrect grouping or membership can still pass. The other added tests assert exact members or values, and no added test uses a fake backing store or Dispatchers.Unconfined emission-order assertion.

Resolution

Strengthen NodeStackTest with exact member-number assertions. Assert [1, 2, 3, 4, 5] for identical coordinates, [1, 2] for the near pair, [listOf(1, 2), listOf(3)] for a near pair plus a distant node, and [1, 2, ..., 10] for the latitude-band case. Keep both separation checks tied to the exact surviving node members.

Full details: Regression Coverage For Changed Behavior

Explanation

Regression coverage remains incomplete. (1) MapView.MainMapContent and LatLngBounds.atLeastSpan change Google cluster camera fitting, including the 140 m minimum span, latitude-adjusted longitude, and 64 dp padding. MapNodeClusterItemsTest only checks stack data and has no camera-fit test. (2) NodeClusterMarkers changes Google singleton navigation, stack listing, badge rendering, and precision circles inside clusters. No Google interaction or rendering test covers these paths. (3) NodeLayers, NodeChipLayer, and StackBadgeLayer change MapLibre stack and singleton taps, top-feature selection, badge and precision-circle filters, and split-cluster versus cannot-split callbacks. ChipRankTest and ClusterCannotSplitTest test only pure helpers; no layer-level interaction test invokes these callbacks. (4) ClusterMember.toClusterMembers now flattens stack metadata and parses fallback cases. Existing tests cover ordinary leaves and one valid stack, but not mixed leaves, malformed JSON, empty arrays, or stack entries without node numbers. (5) ClusterMembersDialog now deduplicates and orders rows at the UI boundary. ClusterMemberOrderTest covers only the helper; no Compose test covers displayed order, duplicates, missing-node rows, row navigation, or dismissal.

Resolution

Add a testable Google camera-fit model or callback harness for the 140 m floor, high-latitude longitude adjustment, 64 dp padding, same-location listing, and separated-cluster fit. Add Google interaction tests for singleton navigation, stack listing, badge visibility, and precision circles inside and outside clusters. Add MapLibre layer-level tests with stacked and singleton features for chip and dot taps, stack-top selection, badge and circle filters, split-cluster zoom, and cannot-split leaf listing. Extend ClusterMembersTest for mixed leaves, malformed JSON, empty arrays, and entries without node numbers. Add a Compose ClusterMembersDialog test for deduplication, favorite and natural-name ordering, missing-node rows, row navigation, and dismissal.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the enhancement New feature or request label Oct 25, 2026
@jamesarich
jamesarich marked this pull request as ready for review October 25, 2026 13:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 1bedc5d and f7c566d.

Files selected for processing: 20
  • androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt
  • androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt
  • androidApp/src/google/kotlin/org/meshtastic/app/map/model/NodeClusterItem.kt
  • androidApp/src/testGoogle/kotlin/org/meshtastic/app/map/MapNodeClusterItemsTest.kt
  • docs/en/user/map-and-waypoints.md
  • feature/map-maplibre/src/commonMain/kotlin/org/meshtastic/feature/map/maplibre/MapGeometry.kt
  • feature/map-maplibre/src/commonMain/kotlin/org/meshtastic/feature/map/maplibre/geojson/ClusterMember.kt
  • feature/map-maplibre/src/commonMain/kotlin/org/meshtastic/feature/map/maplibre/geojson/NodeFeatureKeys.kt
  • feature/map-maplibre/src/commonMain/kotlin/org/meshtastic/feature/map/maplibre/geojson/NodeFeatures.kt
  • feature/map-maplibre/src/commonMain/kotlin/org/meshtastic/feature/map/maplibre/layers/NodeChipLayer.kt
  • feature/map-maplibre/src/commonMain/kotlin/org/meshtastic/feature/map/maplibre/layers/NodeLayers.kt
  • feature/map-maplibre/src/commonMain/kotlin/org/meshtastic/feature/map/maplibre/layers/StackBadgeLayer.kt
  • feature/map-maplibre/src/commonTest/kotlin/org/meshtastic/feature/map/maplibre/UnclusteredNodesTest.kt
  • feature/map-maplibre/src/commonTest/kotlin/org/meshtastic/feature/map/maplibre/geojson/StackFeaturesTest.kt
  • feature/map-maplibre/src/commonTest/kotlin/org/meshtastic/feature/map/maplibre/layers/ClusterCannotSplitTest.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/NodeStack.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/ClusterMemberEntry.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/ClusterMembersDialog.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/NodeStackTest.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/component/ClusterMemberOrderTest.kt
Files with no reviewable changes: 1
  • feature/map-maplibre/src/commonTest/kotlin/org/meshtastic/feature/map/maplibre/UnclusteredNodesTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between f7c566d and 49fba23.

Files selected for processing: 2
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/ClusterMemberEntry.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/component/ClusterMemberOrderTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

Nodes sharing a reduced-precision position, or radios a few meters
apart at one site, drew as chips piled on one another: there was no
sign there were several, only the top one could be tapped, and on
MapLibre the "Nodes at this location" list never opened for a cluster
of them.

Nodes within 5 m now form one stack, the threshold the Apple map uses.
Each stack draws its top node's chip (this node or a favorite, then the
lowest node number) with a Material 3 count badge, and one precision
circle, which stays visible inside a cluster bubble. Tapping a stack
lists its nodes, favorites first in natural name order; a single chip
opens the node's details on both maps. A Google cluster tap zooms to
fit its members, and a MapLibre cluster that cannot split lists them.
…rder

Char.isDigit accepts other scripts' digits while the split only takes
ASCII ones, so a name with them could break the comparator's ordering.
Both now use ASCII digits only.
Every earlier assertion started its run with N, so none reached the
digit test. A bare non-ASCII digit now has to sort as text.
main added local display names; stack members and the list order still
read the broadcast long name.
@jamesarich
jamesarich force-pushed the feat/map-colocated-nodes branch from 7d86fee to d56154b Compare October 25, 2026 21:35
@jamesarich
jamesarich added this pull request to the merge queue Oct 25, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


Review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 7d86fee and d56154b.

Files selected for processing: 4
  • androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt
  • feature/map-maplibre/src/commonMain/kotlin/org/meshtastic/feature/map/maplibre/geojson/NodeFeatures.kt
  • feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/component/ClusterMemberEntry.kt
  • feature/map/src/commonTest/kotlin/org/meshtastic/feature/map/component/ClusterMemberOrderTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

cluster.items.forEach { bounds.include(it.position) }
// Fit the members, as the Apple map does, rather than step one zoom level and leave a tight cluster
// needing several taps to break.
val fitted = bounds.build().atLeastSpan(CLUSTER_FIT_MIN_SPAN_METERS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Major · Functional Correctness · Quick win

🔎 Supported by static analysis

Script executed:

set -o pipefail
printf '%s\n' '--- diff ---'
git diff --unified=80 bf7e1155a69585b4190d774b3e93eba6d374ea8c d56154bf1079cadb79e44a38308f0d75202a73d1 -- androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt
printf '%s\n' '--- relevant symbols ---'
rg -n -F -- 'CLUSTER_FIT_MIN_SPAN_METERS' androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt
rg -n -F -e 'ClusterManager' -e 'ClusterItem' -e 'clusterClick' -e 'onClusterClick' -e 'atLeastSpan' -e 'allSameLocation' -e 'setOnCameraIdleListener' androidApp/src/google/kotlin/org/meshtastic/app/map/MapView.kt
printf '%s\n' '--- dependency/config references ---'
rg -n -F -e 'maps-compose-utils' -e 'maps-utils' -e 'cluster' gradle* androidApp build-logic gradle 2>/dev/null || true

Repository: meshtastic/Meshtastic-Android

Length of output: 38972


Script executed:

set -o pipefail
printf '%s\n' '--- NodeClusterMarkers.kt ---'
nl -ba androidApp/src/google/kotlin/org/meshtastic/app/map/component/NodeClusterMarkers.kt
printf '%s\n' '--- NodeClusterItem.kt ---'
nl -ba androidApp/src/google/kotlin/org/meshtastic/app/map/model/NodeClusterItem.kt
printf '%s\n' '--- relevant dependency declarations ---'
nl -ba gradle/libs.versions.toml | sed -n '60,80p;210,245p'
nl -ba androidApp/build.gradle.kts | sed -n '260,295p'
printf '%s\n' '--- clustering tests and related helpers ---'
rg -n -F -e 'NonHierarchicalDistanceBasedAlgorithm' -e 'NonHierarchicalViewBasedAlgorithm' -e 'NodeClusterMarkers' -e 'CLUSTER_FIT_MIN_SPAN_METERS' -e '140' androidApp/src/testGoogle androidApp/src/google feature 2>/dev/null || true

Repository: meshtastic/Meshtastic-Android

Length of output: 42967


Web query:

official GitHub source com.google.maps.android android-maps-utils 6.1.0 NonHierarchicalDistanceBasedAlgorithm max distance clustering

Result:

<source_evidence>
<source kind="search">
<title>NonHierarchicalDistanceBasedAlgorithm (library API)</title>
<location>https://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.html</location>
<excerpt>citeturn0search0 [wordlim: 200] Published: 6.7 years ago; Crawled: 3 weeks ago;     *       * com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm&lt;T&gt; ... `protected java.util.Collection&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt;`  | `getClusteringItems(PointQuadTree&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt; quadTree, float zoom)` ... `int`  | `getMaxDistanceBetweenClusteredItems()`

com.google.maps.android.clustering.algo

## Class NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt;

  * java.lang.Object
  *     * com.google.maps.android.clustering.algo.AbstractAlgorithm&lt;T&gt;
    *       * com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm&lt;T&gt;

  * All Implemented Interfaces:
    Algorithm&lt;T&gt;

Direct Known Subclasses:
    NonHierarchicalViewBasedAlgorithm

* * *
    
    public class NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt;
    extends AbstractAlgorithm&lt;T&gt;

A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not hierarchical.

High level algorithm:
1. Iterate over items in the order they were added (candidate clusters).
2. Create a cluster with the center of the item.
3. Add all items that are within a certain distance to the cluster.
4. Move any items out of an existing cluster if they are closer to another cluster.
5. Remove those items from the list of candidate clusters.

Clusters have the center of the first element (not the centroid of the items within it).

  *     * ### Nested Class Summary

Nested Classes 
Modifier and Type  | Class and Description
--- | ---
`protected static class `  | `NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T extends ClusterItem&gt;` 
    * ### Constructor Summary

Constructors 
Constructor and Description
---
`NonHierarchicalDistanceBasedAlgorithm()` 
    * ### Method Summary

All Methods Instance Methods Concrete Methods 
Modifier and Type  | Method and Description
--- | ---
`boolean`  | `addItem(T item)` Adds an item to the algorithm
`boolean`  | `addItems(java.util.Collection&lt;T&gt; items)` Adds a collection of items to the algorithm
`void`  | `clearItems()` 
`protected java.util.Collection&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt;`  | `getClusteringItems(PointQuadTree&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt; quadTree, float zoom)` 
`java.util.Set&lt;? extends Cluster&lt;T&gt;&gt;`  | `getClusters(float zoom)` 
`java.util.Collection&lt;T&gt;`  | `getItems()` 
`int`  | `getMaxDistanceBetweenClusteredItems()` 
`boolean`  | `removeItem(T item)` Removes an item from the algorithm
`boolean`  | `removeItems(java.util.Collection&lt;T&gt; items)` Removes a collection of items from the algorithm
`void`  | `setMaxDistanceBetweenClusteredItems(int maxDistance)` 
`boolean`  | `updateItem(T item)` Updates the provided item in the algorithm

  *     * ### Constructor Detail

      * #### NonHierarchicalDistanceBasedAlgorithm
            
            public NonHierarchicalDistanceBasedAlgorithm()

    * ### Method Detail

      * #### addItem
            
            public boolean addItem(T item)

Adds an item to the algorithm

Parameters:
    `item` - the item to be added
Returns:
    true if the algorithm contents changed as a result of the call
      * #### addItems
            
            public boolean addItems(java.util.Collection&lt;T&gt; items)

Adds a collection of items to the algorithm

Parameters:
    `items` - the items to be added
Returns:
    true if the algorithm contents changed as a result of the call
      * #### clearItems
            
            public void clearItems()

      * #### removeItem
            
            public boolean removeItem(T item)

Removes an item from the algorithm

Parameters:
    `item` - the item to be removed
Returns:
    true if this algorithm contained the specified element (or equivalently, if this al…[truncated]</excerpt>
</source>
<source kind="search">
<title>GitHub - googlemaps/android-maps-utils: Maps SDK for Android Utility Library · GitHub</title>
<location>https://github.com/googlemaps/android-maps-utils</location>
<excerpt>citeturn0search5 [wordlim: 200] Crawled: 3 weeks ago; Required for demos using Advanced Markers or Cloud-based Map Styling. ...   1. `secrets.properties`: Add a line to your `secrets.properties` file with your Map ID: ...             android:name=&quot;com.google.maps.android.utils.attribution.AttributionIdInitializer&quot; ... Accordingly, while the library is in version 0.x, backwards-incompatible changes may be introduced at any time.If you find a bug, or have a feature request, please file an issue on GitHub.
You can learn more about map IDs in the official documentation. You can set the Map ID in one of the following ways:

  1. `secrets.properties`: Add a line to your `secrets.properties` file with your Map ID:
    
    MAP_ID=YOUR_MAP_ID

  2. `strings.xml`: Alternatively, you can set the Map ID in the `demo/src/main/res/values/strings.xml` file:
    
    &lt;string name=&quot;map_id&quot;&gt;YOUR_MAP_ID&lt;/string&gt;

  3. XML Layout Files: You can also hardcode the Map ID directly in the XML layout files where a map is defined, by setting the `map:mapId` attribute:
    
    &lt;androidx.fragment.app.FragmentContainerView
        map:mapId=&quot;YOUR_MAP_ID&quot; 
        /&gt;

## Documentation

See the documentation for a full list of classes and their methods.

Full guides for using the utilities are published in Google Maps Platform documentation.

## Usage

Marker utilities

### Marker utilities

  * Marker animation source, sample code
  * Marker clustering source, guide
  * Advanced Markers clustering source, sample code
  * Marker icons source, sample code
Data visualization utilities

### Data visualization utilities

  * Display heat maps source, guide
  * Import GeoJSON source, guide
  * Import KML source, guide
Polyline and spherical geometry utilities

### Additional utilities

  * Polyline encoding and decoding source, encoding sample, decoding sample
  * Spherical geometry source, compute distance sample
Street View metadata utility

### Street View metadata utility

The StreetViewUtil class provides functionality to check whether a location is supported in StreetView. You can avoid errors when adding a Street View panorama to an Android app by calling this metadata utility and only adding a Street View panorama if the response is `OK`.
    
    StreetViewUtils.fetchStreetViewData(LatLng(8.1425918, 11.5386121), BuildConfig.MAPS_API_KEY,Source.DEFAULT)

`fetchStreetViewData` will return `NOT_FOUND`, `OK`, `ZERO_RESULTS` or `REQUEST_DENIED`, depending on the response.

By default, the `Source` is set to `Source.DEFAULT`, but you can also specify `Source.OUTDOOR` to request outdoor Street View panoramas.

## Internal usage attribution ID

This library calls the `addInternalUsageAttributionId` method, which helps Google understand which libraries and samples are helpful to developers and is optional. Instructions for opting out of the identifier are provided below.

If you wish to disable this, you can do so by removing the initializer in your `AndroidManifest.xml` using the `tools:node=&quot;remove&quot;` attribute:
    
    &lt;provider
        android:name=&quot;androidx.startup.InitializationProvider&quot;
        android:authorities=&quot;${applicationId}.androidx-startup&quot;
        android:exported=&quot;false&quot;
        tools:node=&quot;merge&quot;&gt;
        &lt;meta-data
            android:name=&quot;com.google.maps.android.utils.attribution.AttributionIdInitializer&quot;
            tools:node=&quot;remove&quot; /&gt;
    &lt;/provider&gt;

## Contributing

Contributions are welcome and encouraged! If you&#39;d like to contribute, send us a pull request and refer to our code of conduct and contributing guide.

### Using AI

This repository provides an official Gemini Skill and an `llm-integration-prompt.md` to help AI agents navigate the codebase and provide assistance. Refer to the contributing guide for more details on AI usage.

## Terms of Service

This library uses Goog…[truncated]</excerpt>
</source>
<source kind="search">
<title>NonHierarchicalDistanceBasedAlgorithm (library API)</title>
<location>https://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.html</location>
<excerpt>citeturn2search0 [wordlim: 200] Published: 6.7 years ago; Crawled: 3 weeks ago; `protected java.util.Collection&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt;`  | `getClusteringItems(PointQuadTree&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt; quadTree, float zoom)` ... `void`  | `setMaxDistanceBetweenClusteredItems(int maxDistance)`

com.google.maps.android.clustering.algo

## Class NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt;

  * java.lang.Object
  *     * com.google.maps.android.clustering.algo.AbstractAlgorithm&lt;T&gt;
    *       * com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm&lt;T&gt;

  * All Implemented Interfaces:
    Algorithm&lt;T&gt;

Direct Known Subclasses:
    NonHierarchicalViewBasedAlgorithm

* * *
    
    public class NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt;
    extends AbstractAlgorithm&lt;T&gt;

A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not hierarchical.

High level algorithm:
1. Iterate over items in the order they were added (candidate clusters).
2. Create a cluster with the center of the item.
3. Add all items that are within a certain distance to the cluster.
4. Move any items out of an existing cluster if they are closer to another cluster.
5. Remove those items from the list of candidate clusters.

Clusters have the center of the first element (not the centroid of the items within it).

  *     * ### Nested Class Summary

Nested Classes 
Modifier and Type  | Class and Description
--- | ---
`protected static class `  | `NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T extends ClusterItem&gt;` 
    * ### Constructor Summary

Constructors 
Constructor and Description
---
`NonHierarchicalDistanceBasedAlgorithm()` 
    * ### Method Summary

All Methods Instance Methods Concrete Methods 
Modifier and Type  | Method and Description
--- | ---
`boolean`  | `addItem(T item)` Adds an item to the algorithm
`boolean`  | `addItems(java.util.Collection&lt;T&gt; items)` Adds a collection of items to the algorithm
`void`  | `clearItems()` 
`protected java.util.Collection&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt;`  | `getClusteringItems(PointQuadTree&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt; quadTree, float zoom)` 
`java.util.Set&lt;? extends Cluster&lt;T&gt;&gt;`  | `getClusters(float zoom)` 
`java.util.Collection&lt;T&gt;`  | `getItems()` 
`int`  | `getMaxDistanceBetweenClusteredItems()` 
`boolean`  | `removeItem(T item)` Removes an item from the algorithm
`boolean`  | `removeItems(java.util.Collection&lt;T&gt; items)` Removes a collection of items from the algorithm
`void`  | `setMaxDistanceBetweenClusteredItems(int maxDistance)` 
`boolean`  | `updateItem(T item)` Updates the provided item in the algorithm

  *     * ### Constructor Detail

      * #### NonHierarchicalDistanceBasedAlgorithm
            
            public NonHierarchicalDistanceBasedAlgorithm()

    * ### Method Detail

      * #### addItem
            
            public boolean addItem(T item)

Adds an item to the algorithm

Parameters:
    `item` - the item to be added
Returns:
    true if the algorithm contents changed as a result of the call
      * #### addItems
            
            public boolean addItems(java.util.Collection&lt;T&gt; items)

Adds a collection of items to the algorithm

Parameters:
    `items` - the items to be added
Returns:
    true if the algorithm contents changed as a result of the call
      * #### clearItems
            
            public void clearItems()

      * #### removeItem
            
            public boolean removeItem(T item)

Removes an item from the algorithm

Parameters:
    `item` - the item to be removed
Returns:
    true if this algorithm contained the specified element (or equivalently, if this algorithm changed as a result of the call).
      * #### removeItems
            
         …[truncated]</excerpt>
</source>
<source kind="search">
<title>GitHub - googlemaps/android-maps-utils: Maps SDK for Android Utility Library · GitHub</title>
<location>https://github.com/googlemaps/android-maps-utils</location>
<excerpt>citeturn2search6 [wordlim: 200] Crawled: 3 weeks ago;   1. `secrets.properties`: Add a line to your `secrets.properties` file with your Map ID: ...     &lt;string name=&quot;map_id&quot;&gt;YOUR_MAP_ID&lt;/string&gt; ...             android:name=&quot;com.google.maps.android.utils.attribution.AttributionIdInitializer&quot; ... If you find a bug, or have a feature request, please file an issue on GitHub.
  * Marker clustering source, guide
  * Advanced Markers clustering source, sample code
  * Marker icons source, sample code
Data visualization utilities

### Data visualization utilities

  * Display heat maps source, guide
  * Import GeoJSON source, guide
  * Import KML source, guide
Polyline and spherical geometry utilities

### Additional utilities

  * Polyline encoding and decoding source, encoding sample, decoding sample
  * Spherical geometry source, compute distance sample
Street View metadata utility

### Street View metadata utility

The StreetViewUtil class provides functionality to check whether a location is supported in StreetView. You can avoid errors when adding a Street View panorama to an Android app by calling this metadata utility and only adding a Street View panorama if the response is `OK`.
    
    StreetViewUtils.fetchStreetViewData(LatLng(8.1425918, 11.5386121), BuildConfig.MAPS_API_KEY,Source.DEFAULT)

`fetchStreetViewData` will return `NOT_FOUND`, `OK`, `ZERO_RESULTS` or `REQUEST_DENIED`, depending on the response.

By default, the `Source` is set to `Source.DEFAULT`, but you can also specify `Source.OUTDOOR` to request outdoor Street View panoramas.

Refer to the contributing guide for more details on AI usage.

## Terms of Service

This library uses Google Maps Platform services. Use of Google Maps Platform services through this library is subject to the Google Maps Platform Terms of Service.

If your billing address is in the European Economic Area, effective on 8 July 2025, the Google Maps Platform EEA Terms of Service will apply to your use of the Services. Functionality varies by region. Learn more.

This library is not a Google Maps Platform Core Service. Therefore, the Google Maps Platform Terms of Service (e.g. Technical Support Services, Service Level Agreements, and Deprecation Policy) do not apply to the code in this library.

## Support

This library is offered via an open source license. It is not governed by the Google Maps Platform Support Technical Support Services Guidelines, the SLA, or the Deprecation Policy. However, any Google Maps Platform services used by the library remain subject to the Google Maps Platform Terms of Service.

This library adheres to semantic versioning to indicate when backwards-incompatible changes are introduced. Accordingly, while the library is in version 0.x, backwards-incompatible changes may be introduced at any time.

If you find a bug, or have a feature request, please file an issue on GitHub. If you would like to get answers to technical questions from other Google Maps Platform developers, ask through one of our developer community channels. If you&#39;d like to contribute, please check the contributing guide.

You can also discuss this library on our Discord server.

</excerpt>
</source>
<source kind="search">
<title>com.google.maps.android.clustering.algo (library API)</title>
<location>https://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/clustering/algo/package-summary.html</location>
<excerpt>citeturn0search1 [wordlim: 200] Published: 6.7 years ago; Crawled: 3 weeks ago; # Package com.google.maps.android.clustering.algo ... NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt;  | A simple clustering algorithm with O(nlog n) performance.

# Package com.google.maps.android.clustering.algo

  * Interface Summary 
Interface  | Description
--- | ---
Algorithm&lt;T extends ClusterItem&gt;  | Logic for computing clusters
ScreenBasedAlgorithm&lt;T extends ClusterItem&gt;  | This algorithm uses map position for clustering, and should be reclustered on map movement
  * Class Summary 
Class  | Description
--- | ---
AbstractAlgorithm&lt;T extends ClusterItem&gt;  | Base Algorithm class that implements lock/unlock functionality.
GridBasedAlgorithm&lt;T extends ClusterItem&gt;  | Groups markers into a grid.
NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt;  | A simple clustering algorithm with O(nlog n) performance.
NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T extends ClusterItem&gt;  |  
NonHierarchicalViewBasedAlgorithm&lt;T extends ClusterItem&gt;  | This algorithm works the same way as `NonHierarchicalDistanceBasedAlgorithm` but works, only in visible area.
PreCachingAlgorithmDecorator&lt;T extends ClusterItem&gt;  | Optimistically fetch clusters for adjacent zoom levels, caching them as necessary.
ScreenBasedAlgorithmAdapter&lt;T extends ClusterItem&gt;  |  
StaticCluster&lt;T extends ClusterItem&gt;  | A cluster whose center is determined upon creation.</excerpt>
</source>
<omitted_sources count="37">Additional source records were omitted from this bounded result.</omitted_sources>
</source_evidence>

Citations:

- 1: https://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.html?utm_source=openai
- 2: https://github.com/googlemaps/android-maps-utils?utm_source=openai

Web query:

official GitHub source com.google.maps.android maps-compose 9.0.1 Clustering default algorithm

Result:

<source_evidence>
<source kind="find_in_page">
<title>android-maps-compose/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt at v9.0.1 · googlemaps/android-maps-compose · GitHub</title>
<location>https://github.com/googlemaps/android-maps-compose/blob/v9.0.1/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt</location>
<excerpt>citeturn3view2 [wordlim: 200] Crawled: today; Content type: text/html; Source: find({&quot;ref_id&quot;:&quot;turn2view0&quot;,&quot;pattern&quot;:&quot;Algorithm&quot;}); Total lines: 2457
L1586: * @param clusterItemContentRotation the rotation of the non-clustered item in degrees clockwise about the marker&#39;s anchor point
L1587: 
L1588: * @param clusterRenderer an optional ClusterRenderer that can be used to specify the algorithm used by the rendering.
L1589: 
L1590: */
L1591: 
L1592: @Composable
L1593: 
L1594: @GoogleMapComposable
L1595: 
L1596: @MapsComposeExperimentalApi
L1597: 
L1598: @Deprecated(
L1599: 
L1600: message = &quot;If clusterRenderer is specified, clusterContent and clusterItemContent are not used; use a function that takes ClusterManager as an argument instead.&quot;,
L1987: * @param items all items to show
L1988: 
L1989: * @param clusterManager a [ClusterManager] that can be used to specify the algorithm used by the rendering.
L1990: 
L1991: */
L1992: 
L1993: @Composable
L1994: 
L1995: @GoogleMapComposable
L1996: 
L1997: @MapsComposeExperimentalApi
L1998: 
L1999: public fun &lt;T : ClusterItem&gt; Clustering(
L2000: 
L2001: items: Collection&lt;T&gt;,
L2002: 
L2003: clusterManager: ClusterManager&lt;T&gt;,
L2004: 
L2005: clusterItemDecoration: @Composable @GoogleMapComposable (T) -&gt; Unit = {},
L2006: 
L2007: ) {
L2008: 
L2009: Clustering(
L2010: 
L2011: items = items,
L2012: 
L2013: clusterManager = clusterManager,
L2014: 
L2015: clusterItemDecoration = clusterItemDecoration,
</excerpt>
</source>
<source kind="open_page">
<title>android-maps-compose/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt at v9.0.1 · googlemaps/android-maps-compose · GitHub</title>
<location>https://github.com/googlemaps/android-maps-compose/blob/v9.0.1/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt</location>
<excerpt>citeturn2view0 [wordlim: 200] Crawled: today; Content type: text/html; Source: open({&quot;ref_id&quot;:&quot;https://github.com/googlemaps/android-maps-compose/blob/v9.0.1/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt&quot;,&quot;lineno&quot;:null}); Total lines: 2457
L0: 【0†Skip to content】
L1: ## Navigation Menu
L2: 
L3: 【1†Sign in】
L4: 
L5: Appearance settings
L6: 
L7:   * Platform
L8: 
L9:     * AI CODE CREATION
L10:       * 【2†GitHub CopilotWrite better code with AI】
L11:       * 【3†GitHub Copilot appDirect agents from issue to merge】
L12:       * 【4†MCP RegistryIntegrate external tools】
L13: 
L14:     * DEVELOPER WORKFLOWS
L15:       * 【5†ActionsAutomate any workflow】
L16:       * 【6†CodespacesInstant dev environments】
L17:       * 【7†IssuesPlan and track work】
L18:       * 【8†Code ReviewManage code changes】
L19:       * 【9†Code QualityEnforce quality at merge】
L20:     * APPLICATION SECURITY
L21:       * 【10†GitHub Advanced SecurityFind and fix vulnerabilities】
L22:       * 【11†Code securitySecure your code as you build】
L23:       * 【12†Secret protectionStop leaks before they start】
L24: 
L25:     * EXPLORE
L26:       * 【13†Why GitHub】
L27:       * 【14†Documentation†docs.github.com】
L28:       * 【15†Blog†github.blog】
L29:       * 【16†Changelog†github.blog】
L30:       * 【17†Marketplace】
L31: 
L32: 【18†View all features】
L33: 
L34:   * Solutions
L35:     * BY COMPANY SIZE
L36:       * 【19†Enterprises】
L37:       * 【20†Small and medium teams】
L38:       * 【21†Startups】
L39:       * 【22†Nonprofits】
L40: 
L41:     * BY USE CASE
L42:       * 【23†App Modernization】
L43:       * 【24†DevSecOps】
L44:       * 【25†DevOps】
L45:       * 【26†CI/CD】
L46:       * 【27†View all use cases】
L47: 
L48:     * BY INDUSTRY
L49:       * 【28†Healthcare】
L50:       * 【29†Financial services】
L51:       * 【30†Manufacturing】
L52:       * 【31†Government】
L53:       * 【32†See all industries】
L54: 
L55: 【33†View all solutions】
L56: 
L57:   * Resources
L58:     * EXPLORE BY TOPIC
L59:       * 【34†AI】
L60:       * 【35†Software Development】
L61:       * 【36†DevOps】
L62:       * 【37†Security】
L63:       * 【38†View all topics】
L64: 
L65:     * EXPLORE BY TYPE
L66:       * 【39†Customer stories】
L67:       * 【40†Events &amp; webinars】
L68:       * 【41†Ebooks &amp; reports】
L69:       * 【42†Business insights】
L70:       * 【43†GitHub Skills†skills.github.com】
L71:     * SUPPORT &amp; SERVICES
L72:       * 【14†Documentation†docs.github.com】
L73:       * 【44†Customer support†support.github.com】
L74:       * 【45†Community forum】
L75:       * 【46†Trust center】
L76:       * 【47†Partners】
L77: 
L78: 【48†View all resources】
L79: 
L80:   * Open Source
L81: 
L82:     * COMMUNITY
L83:       * 【49†GitHub SponsorsFund open source developers】
L84:     * PROGRAMS
L85:       * 【50†Security Lab†securitylab.github.com】
L86:       * 【51†Maintainer Community†maintainers.github.com】
L87:       * 【52†GitHub Stars†stars.github.com】
L88:       * 【53†Archive Program†archiveprogram.github.com】
L89: 
L90:     * REPOSITORIES
L91:       * 【54†Topics】
L92:       * 【55†Trending】
L93:       * 【56†Collections】
L94: 
L95:   * Enterprise
L96: 
L97:     * ENTERPRISE SOLUTIONS
L98:       * 【19†Enterprise platformAI-powered developer platform】
L99:     * AVAILABLE ADD-ONS
L100:       * 【10†GitHub Advanced SecurityEnterprise-grade security features】
L101:       * 【57†Copilot for BusinessEnterprise-grade AI features】
L102:       * 【58†Premium SupportEnterprise-grade 24/7 support】
L103: 
L104:   * 【59†Pricing】
L105: 
L106: Search`/`
L107: 
L108: 【1†Sign in】
L109: 
L110: 【60†Sign up】
L111: 
L112: Appearance settings
L113: You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session. Dismiss alert
L114: 
L115:   1. 【61†googlemaps】
L116:   2. 【62†android-maps-compose】
L117: ## Repos…[truncated]</excerpt>
</source>
<source kind="search">
<title>GitHub - googlemaps/android-maps-compose: Jetpack Compose composables for the Maps SDK for Android · GitHub</title>
<location>https://github.com/googlemaps/android-maps-compose</location>
<excerpt>citeturn0search0 [wordlim: 200] Crawled: yesterday;         implementation &#39;com.google.maps.android:maps-compose:9.0.1&#39; // {x-release-please-version}        // Optionally, you can include the Compose utils library for Clustering, ... This library also provides optional composable widgets in the `maps-compose-widgets` library that you can use alongside the `GoogleMap` composable. ... If you find a bug, or have a feature request, please file an issue on GitHub.

# Maps Compose 🗺

## Description

This repository contains Jetpack Compose components for the Maps SDK for Android.

## Requirements

  * Android API level 21+
  * Kotlin-enabled project
  * Jetpack Compose-enabled project (see releases for the required version of Jetpack Compose)
  * Sign up with Google Maps Platform
  * A Google Maps Platform project with the Maps SDK for Android enabled

  * An API key associated with the project above ... follow the API key instructions if you&#39;re new to the process

## Installation

You no longer need to specify the Maps SDK for Android or its Utility Library as separate dependencies, since `maps-compose` and `maps-compose-utils` pull in the appropriate versions of these respectively.
    
    dependencies {
        implementation &#39;com.google.maps.android:maps-compose:9.0.1&#39; // {x-release-please-version}
        // Optionally, you can include the Compose utils library for Clustering,
        // Street View metadata checks, etc.
        implementation &#39;com.google.maps.android:maps-compose-utils:9.0.1&#39; // {x-release-please-version}
        // Optionally, you can include the widgets library for ScaleBar, etc.
        implementation &#39;com.google.maps.android:maps-compose-widgets:9.0.1&#39; // {x-release-please-version}
    }

## Sample App

This repository includes a sample app.

To run the demo app, ensure you&#39;ve met the requirements above then:

  1. Open the `secrets.properties` file in your top-level directory, and then add the following code. Replace YOUR_API_KEY with your API key. Store your key in this file because secrets.properties is excluded from being checked into a version control system. If the `secrets.properties` file does not exist, create it in the same folder as the `local.default.properties` file.
    
    `MAPS_API_KEY=YOUR_API_KEY
    `

  2. Build and run

## Documentation

See the documentation for a full list of classes and their methods.

## Maps Compose Utility Library

This library provides optional utilities in the `maps-compose-utils` library from the Maps SDK for Android Utility Library.

### Clustering

The marker clustering utility helps you manage multiple markers at different zoom levels. When a user views the map at a high zoom level, the individual markers show on the map. When the user zooms out, the markers gather together into clusters, to make viewing the map easier.

The MarkerClusteringActivity demonstrates usage.
    
    Clustering(
        items = items,
        // Optional: Handle clicks on clusters, cluster items, and cluster item info windows
        onClusterClick = null,
        onClusterItemClick = null,
        onClusterItemInfoWindowClick = null,
        // Optional: Custom rendering for clusters
        clusterContent = null,
        // Optional: Custom rendering for non-clustered items
        clusterItemContent = null,
    )

### Street View metadata utility

The `fetchStreetViewData` method provides functionality to check whether a location is supported in StreetView. </excerpt>
</source>
<source kind="search">
<title>android-maps-compose/CHANGELOG.md at main · googlemaps/android-maps-compose · GitHub</title>
<location>https://github.com/googlemaps/android-maps-compose/blob/main/CHANGELOG.md</location>
<excerpt>citeturn0search1 [wordlim: 200] Crawled: yesterday;   * migrate from android-maps-ktx to android-maps-utils (v9.0.0) (#1002) ...   * maps-compose: default mapColorScheme to FOLLOW_SYSTEM in GoogleMap (#986) (5f1d438) ...   * avoid zero-size crash in rememberComposeBitmapDescriptor when used inside Clustering (#963) (11a2430)

# Changelog

## 9.0.1 (2026-10-08)

### Bug Fixes

  * maps-compose: do not apply mapColorScheme to Lite mode maps (#1029) (114aaa5)

## 9.0.0 (2026-10-01)

### ⚠ BREAKING CHANGES

  * migrate from android-maps-ktx to android-maps-utils (v9.0.0) (#1002)

### Features

  * ci: publish an instrumented coverage badge (#1009) (b0d6420)
  * migrate from android-maps-ktx to android-maps-utils (v9.0.0) (#1002) (0c49e8d)

## 8.6.0 (2026-09-03)

### Features

  * expose map zoom limits (#949) (e0d4572)
  * maps-compose: default mapColorScheme to FOLLOW_SYSTEM in GoogleMap (#986) (5f1d438)

### Bug Fixes

  * guard against null in StreetView panorama change listeners (#970) (6147f75)
  * maps-compose: apply mapColorScheme to GoogleMapOptions during MapView creation (#976) (7dec2b7)
  * pin ViewTreeLifecycleOwner/SavedStateRegistryOwner on MapView to prevent info window crash (#972) (4024515)

## 8.5.0 (2026-08-17)

### Features

  * expose info window customization for advanced markers (#965) (4c65f8c)

### Bug Fixes

  * avoid re-parenting crash for MarkerInfoWindowContent/MarkerInfoWindowComposable (#953) (12f35a4)
  * avoid zero-size crash in rememberComposeBitmapDescriptor when used inside Clustering (#963) (11a2430)
  * rename InvalidatingComposeView getRotation param to avoid View shadowing (#952) (1d971cd)
</excerpt>
</source>
<source kind="search">
<title>Clustering</title>
<location>https://googlemaps.github.io/android-maps-compose/maps-compose-utils/com.google.maps.android.compose.clustering/-clustering.html</location>
<excerpt>citeturn0search2 [wordlim: 200] Crawled: last month; @MapsComposeExperimentalApifun &lt;T : ClusterItem&gt; Clustering(items: Collection&lt;T&gt;, onClusterClick: (Cluster&lt;T&gt;) -&gt; Boolean = { false }, onClusterItemClick: (T) -&gt; Boolean = { false }, onClusterItemInfoWindowClick: (T) -&gt; Unit = { }, onClusterItemInfoWindowLongClick: (T) -&gt; Unit = { }, clusterContent: @Composable (Cluster&lt;T&gt;) -&gt; Unit? ... = null, clusterContentAnchor: Offset = Offset(0.5f, 1.0f), clusterItemContentAnchor: Offset = Offset(0.5f, 1.0f), clusterContentZIndex: Float = 0.0f, clusterItemContentZIndex: Float = 0.0f, clusterContentRotation: Float = 0.0f, clusterItemContentRotation: Float = 0.0f, clusterItemDecoration: @Composable (T) -&gt; Unit = {})Groups many items on a map based on zoom level.
</excerpt>
</source>
<omitted_sources count="33">Additional source records were omitted from this bounded result.</omitted_sources>
</source_evidence>

Citations:

- 1: https://github.com/googlemaps/android-maps-compose/blob/v9.0.1/maps-compose-utils/src/main/java/com/google/maps/android/compose/clustering/Clustering.kt

Web query:

site:github.com/googlemaps/android-maps-utils/blob v6.1.0 NonHierarchicalDistanceBasedAlgorithm DEFAULT_MAX_DISTANCE_BETWEEN_CLUSTERED_ITEMS 100

Result:

<source_evidence>
<source kind="search">
<title>NonHierarchicalDistanceBasedAlgorithm.java example</title>
<location>https://www.javatips.net/api/android-maps-utils-master/library/src/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.java</location>
<excerpt>citeturn2search0 [wordlim: 200] Crawled: 4 months ago;     import java.util.Map; ...     import com.google.android.gms.maps.model.LatLng; ...     public class NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt; implements Algorithm&lt;T&gt; {        public static final int MAX_DISTANCE_AT_ZOOM = 100; // essentially 100 dp.
    import com.google.maps.android.clustering.Cluster;
    import com.google.maps.android.clustering.ClusterItem;
    import com.google.maps.android.geometry.Bounds;
    import com.google.maps.android.geometry.Point;
    import com.google.maps.android.projection.SphericalMercatorProjection;
    import com.google.maps.android.quadtree.PointQuadTree;
    
    /**
     * A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not
     * hierarchical.
     * &lt;p/&gt;
     * High level algorithm:&lt;br&gt;
     * 1. Iterate over items in the order they were added (candidate clusters).&lt;br&gt;
     * 2. Create a cluster with the center of the item. &lt;br&gt;
     * 3. Add all items that are within a certain distance to the cluster. &lt;br&gt;
     * 4. Move any items out of an existing cluster if they are closer to another cluster. &lt;br&gt;
     * 5. Remove those items from the list of candidate clusters.
     * &lt;p/&gt;
     * Clusters have the center of the first element (not the centroid of the items within it).
     */
    public class NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt; implements Algorithm&lt;T&gt; {
        public static final int MAX_DISTANCE_AT_ZOOM = 100; // essentially 100 dp.
    
        /**
         * Any modifications should be synchronized on mQuadTree.
         */
        private final Collection&lt;QuadItem&lt;T&gt;&gt; mItems = new ArrayList&lt;QuadItem&lt;T&gt;&gt;();
    
        /**
         * Any modifications should be synchronized on mQuadTree.
         */
        private final PointQuadTree&lt;QuadItem&lt;T&gt;&gt; mQuadTree = new PointQuadTree&lt;QuadItem&lt;T&gt;&gt;(0, 1, 0, 1);
    
        private static final SphericalMercatorProjection PROJECTION = new SphericalMercatorProjection(1);
    
        @Override
        public void addItem(T item) {
            final QuadItem&lt;T&gt; quadItem = new QuadItem&lt;T&gt;(item);
            synchronized (mQuadTree) {
                mItems.add(quadItem);
                mQuadTree.add(quadItem);
            }
        }
    
        @Override
        public void addItems(Collection&lt;T&gt; items) {
            for (T item : items) {
                addItem(item);
            }
        }
    
        @Override
        public void clearItems() {
            synchronized (mQuadTree) {
                mItems.clear();
                mQuadTree.clear();
            }
        }
    
        @Override
        public void removeItem(T item) {
            // QuadItem delegates hashcode() and equals() to its item so,
            //   removing any QuadItem to that item will remove the item
            final QuadItem&lt;T&gt; quadItem = new QuadItem&lt;T&gt;(item);
            synchronized (mQuadTree) {
                mItems.remove(quadItem);
                mQuadTree.remove(quadItem);
            }
        }
    
        @Override
        public Set&lt;? extends Cluster&lt;T&gt;&gt; getClusters(double zoom) {
            final int discreteZoom = (int) zoom;
    
            final double zoomSpecificSpan = MAX_DISTANCE_AT_ZOOM / Math.pow(2, discreteZoom) / 256;
    
            final Set&lt;QuadItem&lt;T&gt;&gt; visitedCandidates = new HashSet&lt;QuadItem&lt;T&gt;&gt;();
            final Set&lt;Cluster&lt;T&gt;&gt; results = new HashSet&lt;Cluster&lt;T&gt;&gt;();
            final Map&lt;QuadItem&lt;T&gt;, Double&gt; distanceToCluster = new HashMap&lt;QuadItem&lt;T&gt;, Double&gt;();
            final Map&lt;QuadItem&lt;T&gt;, StaticCluster&lt;T&gt;&gt; itemToCluster = new HashMap&lt;QuadItem&lt;T&gt;, StaticCluster&lt;T&gt;&gt;();
    
            synchronized…[truncated]</excerpt>
</source>
<source kind="search">
<title>android-maps-utils/CHANGELOG.md at main · googlemaps/android-maps-utils · GitHub</title>
<location>https://github.com/googlemaps/android-maps-utils/blob/main/CHANGELOG.md</location>
<excerpt>citeturn0search0 [wordlim: 200] Crawled: last month;   * restore isLayerOnMap() as abstract member of Layer (#1748) (ddc283c), closes #1746## 5.1.0 (2026-08-05) ...   * stale QuadItem removal after ClusterItem position updates (#1730) (c39ba07) ...   * rewrite android maps utils (#1658) (eeb0156)

143 lines (68 loc) · 9.11 KB

Raw

Copy raw file

Download raw file

Outline

Edit and raw actions

# Changelog

## 5.2.0 (2026-08-28)

### Features

  * clustering: make ClusterManager and its public API open with JvmSuppressWildcards (#1759) (bbb66c4)
  * collections,clustering: complete Kotlin migration for core modules and apply optimizations (#1766) (ca97025)

### Bug Fixes

  * ci: stop failing external PRs on missing coverage token (#1765) (09030dc)
  * data: prevent SSRF from untrusted KML/GeoJSON icon and GroundOverlay href (#1758) (9078c33)

## 5.1.1 (2026-08-11)

### Bug Fixes

  * restore isLayerOnMap() as abstract member of Layer (#1748) (ddc283c), closes #1746

## 5.1.0 (2026-08-05)

### Features

  * data: add support for parsing GroundOverlay with LatLonQuad bounds (#1739) (c2a165d)

### Bug Fixes

  * apply polygon styling to GeoJSON MultiPolygon features (#1727) (67d7252)
  * bound untrusted GeoJSON input to prevent parser resource exhaustion (#1733) (1e41aa4)
  * remove MultiGeometry features from the map in removeFeature (#1725) (4f796ca)
  * render legacy bridge point markers visible (#1726) (fe802a4)
  * stale QuadItem removal after ClusterItem position updates (#1730) (c39ba07)
  * visual-testing: add UiScrollable chip scroll support for robust chip selection (#1735) (6104d93)

## 5.0.0 (2026-07-15)

### ⚠ BREAKING CHANGES

  * rewrite android maps utils (#1658)

### Features

  * rewrite android maps utils (#1658) (eeb0156)

### Bug Fixes

  * prevent StackOverflowError from deeply nested GeometryCollection in GeoJsonParser (#1699) (f513807)

## 4.5.2 (2026-07-07)

### Bug Fixes

  * prevent StackOverflowError when parsing deeply nested KML containers and multi-geometries (#1710) (1463cc5)

## 4.5.1 (2026-06-17)

### Bug Fixes

  * enable filterTouchesWhenObscured on WebView layouts (#1705) (3e59470)

## 4.5.0 (2026-06-16)

### Features

  * add manual RC release workflow (#1695) (688fc83)

### Bug Fixes

  * change model name from gemini-2.5-flash to gemini-3.5-flash (#1696) (838a2bf)
  * enable filterTouchesWhenObscured on WebView (#1703) (0674d75)

## 4.4.1 (2026-05-26)

### Bug Fixes

  * address lint, compiler, and Error Prone warnings (#1692) (ce809c7)

## 4.4.0 (2026-05-20)

### Features

  * add navigation flavor to demo for SDK 7.x reproduction (#1680) (12ac29a)

## 4.3.0 (2026-04-24)

### Features

  * add constructor parameter for custom Executor in cluster renderers (#1682) (b803dc0)

## 4.2.0 (2026-04-22)

### Features

  * KML URL Sanitizer API (#1678) (43855e9)

### Bug Fixes

  * disable XML external entities in KML parser (#1673) (4516be3)
  * KMZ zip bomb mitigation by adding entry and size limits (#1677) (bae3455)
  * prevent NPE in LatLngBounds.contains by adding null checks for positions (#1675) (6b5a2d3)
  * restrict KML image downloads to http/https schemes (#1674) (898ae8f)

## 4.1.1 (2026-03-11)

### Bug Fixes

  * trigger release for PR #1661 (#1662) (8a96267)

## 4.1.0 (2026-02-20)

### Features

  * Transit Layer Demo &amp; Quality Improvements (#1653) (cb074f9)

## 4.0.0 (2026-01-23)

### ⚠ BREAKING CHANGES

  * update to maps 20.0.0 (#1644)

### Features

  * update to maps 20.0.0 (#1644) (60adcde)</excerpt>
</source>
<source kind="search">
<title>GitHub - googlemaps/android-maps-utils: Maps SDK for Android Utility Library · GitHub</title>
<location>https://github.com/googlemaps/android-maps-utils</location>
<excerpt>citeturn0search1 [wordlim: 200] Crawled: 3 weeks ago; Required for demos using Advanced Markers or Cloud-based Map Styling. ...     StreetViewUtils.fetchStreetViewData(LatLng(8.1425918, 11.5386121), BuildConfig.MAPS_API_KEY,Source.DEFAULT) ... Accordingly, while the library is in version 0.x, backwards-incompatible changes may be introduced at any time.If you find a bug, or have a feature request, please file an issue on GitHub. ... android geojson google-maps heat-maps kml maps marker-clustering utility-library
Refer to the contributing guide for more details on AI usage.

## Terms of Service

This library uses Google Maps Platform services. Use of Google Maps Platform services through this library is subject to the Google Maps Platform Terms of Service.

If your billing address is in the European Economic Area, effective on 8 July 2025, the Google Maps Platform EEA Terms of Service will apply to your use of the Services. Functionality varies by region. Learn more.

This library is not a Google Maps Platform Core Service. Therefore, the Google Maps Platform Terms of Service (e.g. Technical Support Services, Service Level Agreements, and Deprecation Policy) do not apply to the code in this library.

## Support

This library is offered via an open source license. It is not governed by the Google Maps Platform Support Technical Support Services Guidelines, the SLA, or the Deprecation Policy. However, any Google Maps Platform services used by the library remain subject to the Google Maps Platform Terms of Service.

This library adheres to semantic versioning to indicate when backwards-incompatible changes are introduced. Accordingly, while the library is in version 0.x, backwards-incompatible changes may be introduced at any time.

If you find a bug, or have a feature request, please file an issue on GitHub. If you would like to get answers to technical questions from other Google Maps Platform developers, ask through one of our developer community channels. If you&#39;d like to contribute, please check the contributing guide.

You can also discuss this library on our Discord server.

</excerpt>
</source>
<source kind="search">
<title>android-maps-utils/README.md at main · googlemaps/android-maps-utils · GitHub</title>
<location>https://github.com/googlemaps/android-maps-utils/blob/main/README.md</location>
<excerpt>citeturn0search2 [wordlim: 200] Crawled: last month;   * Heatmaps — display a large number of points as a heat map ...         implementation(&quot;com.google.maps.android:android-maps-utils-clustering:5.2.0&quot;) // x-release-please-version

Edit and raw actions

# Maps SDK for Android Utility Library

## Description

This open-source library contains utilities that are useful for a wide range of applications using the Google Maps SDK for Android.

As of version 5.0.0, the library has been rewritten in Kotlin and split into focused submodules so you can depend only on what you need. If you are upgrading from 4.x, see the migration guide.

  * Marker animation - animates a marker from one position to another
  * Marker clustering — handles the display of a large number of points
  * Marker icons — display text on your Markers
  * Heatmaps — display a large number of points as a heat map
  * Import KML — displays KML data on the map
  * Import GeoJSON — displays and styles GeoJSON data on the map
  * Polyline encoding and decoding — compact encoding for paths, interoperability with Maps API web services
  * Spherical geometry — for example: computeDistance, computeHeading, computeArea
  * Street View metadata — checks if a Street View panorama exists at a given location

You can also find Kotlin extensions for this library in Maps Android KTX.

## Requirements

  * Android API level 23+
  * Sign up with Google Maps Platform
  * A Google Maps Platform project with the Maps SDK for Android enabled

  * An API key associated with the project above ... follow the API key instructions if you&#39;re new to the process

## Installation
    
    dependencies {
        // Utilities for Maps SDK for Android (requires Google Play Services)
        // You do not need to add a separate dependency for the Maps SDK for Android
        // since this library builds in the compatible version of the Maps SDK.
        // The aggregator artifact transitively pulls in all submodules below.
        implementation(&quot;com.google.maps.android:android-maps-utils:5.2.0&quot;) // x-release-please-version
    }

Alternatively, depend only on the submodules your app needs:
    
    dependencies {
        // Base utilities: PolyUtil, SphericalUtil, collection managers, Street View metadata
        implementation(&quot;com.google.maps.android:android-maps-utils-core:5.2.0&quot;) // x-release-please-version
        // Marker clustering
        implementation(&quot;com.google.maps.android:android-maps-utils-clustering:5.2.0&quot;) // x-release-please-version
        // KML and GeoJSON import
        implementation(&quot;com.google.maps.android:android-maps-utils-data:5.2.0&quot;) // x-release-please-version
        // Heatmaps
        implementation(&quot;com.google.maps.android:android-maps-utils-heatmaps:5.2.0&quot;) // x-release-please-version
        // Marker icons and animation
        implementation(&quot;com.google.maps.android:android-maps-utils-ui:5.2.0&quot;) // x-release-please-version
    }

### Compatibility with Google Navigation SDK for Android

If your project integrates the Google Navigation SDK for Android, you must exclude the transitive `play-services-maps` dependency to prevent duplicate class conflicts:
    
    configurations.all {
        if (name.contains(&quot;navigation&quot;, ignoreCase = true)) {
            exclude(group = &quot;com.google.android.gms&quot;, module = &quot;play-services-maps&quot;)
        }
    }

</excerpt>
</source>
<source kind="search">
<title>Releases · googlemaps/android-maps-ktx · GitHub</title>
<location>https://github.com/googlemaps/android-maps-ktx/releases</location>
<excerpt>citeturn0search3 [wordlim: 200] Crawled: 2 months ago; # Releases: googlemaps/android-maps-ktx ...   * v6.1.0 ... This commit was created on GitHub.com and signed with GitHub’s verified signature.
</excerpt>
</source>
<omitted_sources count="20">Additional source records were omitted from this bounded result.</omitted_sources>
</source_evidence>

Citations:

- 1: https://www.javatips.net/api/android-maps-utils-master/library/src/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.java?utm_source=openai

Web query:

github googlemaps android-maps-utils NonHierarchicalDistanceBasedAlgorithm "maxDistanceBetweenClusteredItems" source

Result:

<source_evidence>
<source kind="open_page">
<title>NonHierarchicalDistanceBasedAlgorithm (library API)</title>
<location>https://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.html</location>
<excerpt>citeturn1view1 [wordlim: 200] Crawled: last month; Content type: text/html; Source: open({&quot;ref_id&quot;:&quot;turn0search0&quot;,&quot;lineno&quot;:null}); Total lines: 194
L0: JavaScript is disabled on your browser.
L1: 
L2: 【0†Skip navigation links】
L3: 
L4:   * 【1†Overview】
L5:   * 【2†Package】
L6:   * Class
L7:   * 【3†Tree】
L8:   * 【4†Deprecated】
L9:   * 【5†Index】
L10:   * 【6†Help】
L11: 
L12:   * 【7†Prev Class】
L13:   * 【8†Next Class】
L14: 
L15:   * 【9†Frames】
L16:   * 【10†No Frames】
L17: 
L18:   * 【11†All Classes】
L19: 
L20:   * Summary:
L21:   * 【12†Nested】 |
L22:   * Field |
L23:   * 【13†Constr】 |
L24:   * 【14†Method】
L25: 
L26:   * Detail:
L27:   * Field |
L28:   * 【15†Constr】 |
L29:   * 【16†Method】
L30: 
L31: com.google.maps.android.clustering.algo
L32: ## Class NonHierarchicalDistanceBasedAlgorithm&lt;T extends 【17†ClusterItem】&gt;
L33: 
L34:   * java.lang.Object
L35:   *     * 【18†com.google.maps.android.clustering.algo.AbstractAlgorithm】&lt;T&gt;
L36:     *       * com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm&lt;T&gt;
L37: 
L38:   * All Implemented Interfaces:
L39:     【19†Algorithm】&lt;T&gt;
L40: 
L41: Direct Known Subclasses:
L42:     【20†NonHierarchicalViewBasedAlgorithm】
L43: * * *
L44: 
L45:     public class NonHierarchicalDistanceBasedAlgorithm&lt;T extends 【17†ClusterItem】&gt;
L46:     extends 【18†AbstractAlgorithm】&lt;T&gt;
L47: 
L48: A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not hierarchical.
L49: High level algorithm:
L50: 1. Iterate over items in the order they were added (candidate clusters).
L51: 2. Create a cluster with the center of the item.
L52: 3. Add all items that are within a certain distance to the cluster.
L53: 4. Move any items out of an existing cluster if they are closer to another cluster.
L54: 5. Remove those items from the list of candidate clusters.
L55: 
L56: Clusters have the center of the first element (not the centroid of the items within it).
L57: 
L58:   *     * ### Nested Class Summary
L59: Nested Classes
L60: Modifier and Type  | Class and Description
L61: --- | ---
L62: `protected static class `  | `【8†NonHierarchicalDistanceBasedAlgorithm.QuadItem】&lt;【8†T】 extends 【17†ClusterItem】&gt;`
L63:     * ### Constructor Summary
L64: 
L65: Constructors
L66: Constructor and Description
L67: ---
L68: `【21†NonHierarchicalDistanceBasedAlgorithm】()`
L69:     * ### Method Summary
L70: All Methods Instance Methods Concrete Methods
L71: Modifier and Type  | Method and Description
L72: --- | ---
L73: `boolean`  | `【22†addItem】(【10†T】 item)` Adds an item to the algorithm
L74: `boolean`  | `【23†addItems】(java.util.Collection&lt;【10†T】&gt; items)` Adds a collection of items to the algorithm
L75: `void`  | `【24†clearItems】()`
L76: `protected java.util.Collection&lt;【8†NonHierarchicalDistanceBasedAlgorithm.QuadItem】&lt;【10†T】&gt;&gt;`  | `【25†getClusteringItems】(【26†PointQuadTree】&lt;【8†NonHierarchicalDistanceBasedAlgorithm.QuadItem】&lt;【10†T】&gt;&gt; quadTree, float zoom)`
L77: `java.util.Set&lt;? extends 【27†Cluster】&lt;【10†T】&gt;&gt;`  | `【28†getClusters】(float zoom)`
L78: `java.util.Collection&lt;【10†T】&gt;`  | `【29†getItems】()`
L79: `int`  | `【30†getMaxDistanceBetweenClusteredItems】()`
L80: `boolean`  | `【31†removeItem】(【10†T】 item)` Removes an item from the algorithm
L81: `boolean`  | `【32†removeItems】(java.util.Collection&lt;【10†T】&gt; items)` Removes a collection of items from the algorithm
L82: `void`  | `【33†setMaxDistanceBetweenClusteredItems】(int maxDistance)`
L83: `boolean`  | `【34†updateItem】(【10†T】 item)` Updates the provided item in the algorithm
L84:       * ### Methods inherited from class com.google.maps.android.clustering.algo.【18†AbstractAlgorithm】
L85: `【35†lock】, 【36†unlock】`
L86:       * ### Methods inherited from class java.lang.Object
L87: 
L88: `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`
L89: 
L90:   *     * ### Constructor Detail
L9…[truncated]</excerpt>
</source>
<source kind="search">
<title>NonHierarchicalDistanceBasedAlgorithm (library API)</title>
<location>https://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.html</location>
<excerpt>citeturn0search0 [wordlim: 200] Published: 6.7 years ago; Crawled: 3 weeks ago;     *       * com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm&lt;T&gt; ... `protected java.util.Collection&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt;`  | `getClusteringItems(PointQuadTree&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt; quadTree, float zoom)` ... `int`  | `getMaxDistanceBetweenClusteredItems()`

com.google.maps.android.clustering.algo

## Class NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt;

  * java.lang.Object
  *     * com.google.maps.android.clustering.algo.AbstractAlgorithm&lt;T&gt;
    *       * com.google.maps.android.clustering.algo.NonHierarchicalDistanceBasedAlgorithm&lt;T&gt;

  * All Implemented Interfaces:
    Algorithm&lt;T&gt;

Direct Known Subclasses:
    NonHierarchicalViewBasedAlgorithm

* * *
    
    public class NonHierarchicalDistanceBasedAlgorithm&lt;T extends ClusterItem&gt;
    extends AbstractAlgorithm&lt;T&gt;

A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not hierarchical.

High level algorithm:
1. Iterate over items in the order they were added (candidate clusters).
2. Create a cluster with the center of the item.
3. Add all items that are within a certain distance to the cluster.
4. Move any items out of an existing cluster if they are closer to another cluster.
5. Remove those items from the list of candidate clusters.
`java.util.Set&lt;? extends Cluster&lt;T&gt;&gt;`  | `getClusters(float zoom)` 
`java.util.Collection&lt;T&gt;`  | `getItems()` 
`int`  | `getMaxDistanceBetweenClusteredItems()` 
`boolean`  | `removeItem(T item)` Removes an item from the algorithm
`boolean`  | `removeItems(java.util.Collection&lt;T&gt; items)` Removes a collection of items from the algorithm
`void`  | `setMaxDistanceBetweenClusteredItems(int maxDistance)` 
`boolean`  | `updateItem(T item)` Updates the provided item in the algorithm

  *     * ### Constructor Detail

      * #### NonHierarchicalDistanceBasedAlgorithm
            
            public NonHierarchicalDistanceBasedAlgorithm()

    * ### Method Detail

      * #### addItem
            
            public boolean addItem(T item)

Adds an item to the algorithm

Parameters:
    `item` - the item to be added
Returns:
    true if the algorithm contents changed as a result of the call

Updates the provided item in the algorithm

Parameters:
    `item` - the item to be updated
Returns:
    true if the item existed in the algorithm and was updated, or false if the item did not exist in the algorithm and the algorithm contents remain unchanged.
      * #### getClusters
            
            public java.util.Set&lt;? extends Cluster&lt;T&gt;&gt; getClusters(float zoom)

      * #### getClusteringItems
            
            protected java.util.Collection&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt; getClusteringItems(PointQuadTree&lt;NonHierarchicalDistanceBasedAlgorithm.QuadItem&lt;T&gt;&gt; quadTree,
                                                                                                                 float zoom)

      * #### getItems
            
            public java.util.Collection&lt;T&gt; getItems()

      * #### setMaxDistanceBetweenClusteredItems
            
            public void setMaxDistanceBetweenClusteredItems(int maxDistance)

      * #### getMaxDistanceBetweenClusteredItems
            
            public int getMaxDistanceBetweenClusteredItems()</excerpt>
</source>
<source kind="search">
<title>Algorithm (library API)</title>
<location>https://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/clustering/algo/Algorithm.html</location>
<excerpt>citeturn0search1 [wordlim: 200] Published: 6.7 years ago; Crawled: 3 weeks ago; com.google.maps.android.clustering.algo## Interface Algorithm&lt;T extends ClusterItem&gt; ...     AbstractAlgorithm, GridBasedAlgorithm, NonHierarchicalDistanceBasedAlgorithm, NonHierarchicalViewBasedAlgorithm, PreCachingAlgorithmDecorator, ScreenBasedAlgorithmAdapter ... `int`  | `getMaxDistanceBetweenClusteredItems()`
`int`  | `getMaxDistanceBetweenClusteredItems()` 
`void`  | `lock()` 
`boolean`  | `removeItem(T item)` Removes an item from the algorithm
`boolean`  | `removeItems(java.util.Collection&lt;T&gt; items)` Removes a collection of items from the algorithm
`void`  | `setMaxDistanceBetweenClusteredItems(int maxDistance)` 
`void`  | `unlock()` 
`boolean`  | `updateItem(T item)` Updates the provided item in the algorithm

  *     * ### Method Detail

      * #### addItem
            
            boolean addItem(T item)

Adds an item to the algorithm

Parameters:
    `item` - the item to be added
Returns:
    true if the algorithm contents changed as a result of the call
      * #### addItems
            
            boolean addItems(java.util.Collection&lt;T&gt; items)

Adds a collection of items to the algorithm

Parameters:
    `items` - the items to be added
Returns:
    true if the algorithm contents changed as a result of the call
</excerpt>
</source>
<source kind="search">
<title>NonHierarchicalViewBasedAlgorithm</title>
<location>https://googlemaps.github.io/android-maps-utils/android-maps-utils%20/com.google.maps.android.clustering.algo/-non-hierarchical-view-based-algorithm/index.html</location>
<excerpt>citeturn0search2 [wordlim: 200] Crawled: 3 months ago; open class NonHierarchicalViewBasedAlgorithm&lt;T : ClusterItem? ... It requires shouldReclusterOnMapMovement to be true in order to re-render clustering when camera movement changes the visible area. ... open fun getMaxDistanceBetweenClusteredItems(): Int
</excerpt>
</source>
<source kind="search">
<title>GitHub - googlemaps/android-maps-utils: Maps SDK for Android Utility Library · GitHub</title>
<location>https://github.com/googlemaps/android-maps-utils</location>
<excerpt>citeturn0search3 [wordlim: 200] Crawled: 3 weeks ago; Required for demos using Advanced Markers or Cloud-based Map Styling. ...   * Spherical geometry source, compute distance sample ...             android:name=&quot;com.google.maps.android.utils.attribution.AttributionIdInitializer&quot; ... If you find a bug, or have a feature request, please file an issue on GitHub. ... android geojson google-maps heat-maps kml maps marker-clustering utility-library
</excerpt>
</source>
<omitted_sources count="15">Additional source records were omitted from this bounded result.</omitted_sources>
</source_evidence>

Citations:

- 1: https://googlemaps.github.io/android-maps-utils/javadoc/com/google/maps/android/clustering/algo/NonHierarchicalDistanceBasedAlgorithm.html

Script executed:

set -o pipefail
base="${HOME}/.gradle/caches/modules-2/files-2.1/com.google.maps.android"
printf '%s\n' '--- cached maps-utils artifacts ---'
find "$base" -maxdepth 4 -type f \( -name '*android-maps-utils*6.1.0*.jar' -o -name '*android-maps-utils*6.1.0*.aar' -o -name '*android-maps-utils*6.1.0*.pom' -o -name '*android-maps-utils*6.1.0*.module' \) -print 2>/dev/null
printf '%s\n' '--- cached clustering source/class candidates ---'
find "$base" -type f \( -name '*sources.jar' -o -name '*.jar' -o -name '*.aar' \) -print 2>/dev/null | grep -E 'android-maps-utils|maps-compose' | head -80

Repository: meshtastic/Meshtastic-Android

Length of output: 253


Script executed:

set -o pipefail
printf '%s\n' '--- stackCoincident definition and callers ---'
rg -n -F -e 'fun stackCoincident' -e 'stackCoincident(' feature androidApp
printf '%s\n' '--- implementation ---'
file=$(rg -l -F 'fun stackCoincident' feature androidApp | head -1)
if [ -n "$file" ]; then
  nl -ba "$file"
fi
printf '%s\n' '--- stack tests ---'
rg -n -F -e 'stackCoincident' -e '5 m' -e '5m' feature/*/src/*Test androidApp/src 2>/dev/null || true

Repository: meshtastic/Meshtastic-Android

Length of output: 44003


Ensure a cluster tap can split distinct stack tops.

A cluster containing at least 10 distinct stack tops can remain clustered after the 140 m minimum fit. The distance-based algorithm groups items by a zoom-dependent screen distance, and a 140 m geographic floor does not guarantee that a 6 m separation exceeds that distance. A second tap then recomputes the same bounds because allSameLocation is false, so the cluster does not open its member list. Fit closer, or open the member list when a cluster remains unchanged after fitting.

Merged via the queue into main with commit 0e0b15c Oct 25, 2026
15 checks passed
@jamesarich
jamesarich deleted the feat/map-colocated-nodes branch October 25, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant