Class ClusterHierarchy
Every integer zoom level's clusters, built once from a marker store and then queried per view, so panning or crossing a zoom level is a lookup instead of a fresh clustering pass.
public sealed class ClusterHierarchy
- Inheritance
-
objectClusterHierarchy
Remarks
Build(MarkerStore, int, int, int, double) walks from the most zoomed-in level down to the most zoomed-out one. The first level clusters the markers themselves; every level below it clusters the level above's own clusters, so a marker is read once no matter how many levels exist. Marker counts are carried through that fold, so a cluster's Count is markers at every level, never clusters-of-clusters.
Query(double, in MercatorBounds, List<MarkerCluster>) clips one already-built level to a viewport. It never rebuilds, so membership does not depend on where the camera was when the build happened: two views of the same ground at the same zoom return the identical clusters. Rebuild only when the markers themselves change.
Not thread safe, and a build is synchronous: the call that rebuilds pays the whole cost. Cluster lists and member buffers handed out by ClustersForLevel(int) / MembersForLevel(int) stay valid until this instance's next Build(MarkerStore, int, int, int, double).
Properties
IsBuilt
True once a Build(MarkerStore, int, int, int, double) has completed.
public bool IsBuilt { get; }
Property Value
LastQueryCandidatesVisited
Clusters the most recent Query(double, in MercatorBounds, List<MarkerCluster>) pulled out of the level's grid. Far below that level's total cluster count is the whole point of the grid; a test asserts it.
public int LastQueryCandidatesVisited { get; }
Property Value
LastQueryCellsVisited
Grid cells the most recent Query(double, in MercatorBounds, List<MarkerCluster>) stepped over, occupied or not, summed across the world-wrap copies it needed.
public int LastQueryCellsVisited { get; }
Property Value
LastQueryLevel
The level the most recent Query(double, in MercatorBounds, List<MarkerCluster>) read.
public int LastQueryLevel { get; }
Property Value
MaxLevel
Finest (most zoomed in) integer level built.
public int MaxLevel { get; }
Property Value
MinLevel
Coarsest (most zoomed out) integer level built.
public int MinLevel { get; }
Property Value
SourceVersion
The source store's Version as of the last completed build, for a caller deciding whether to rebuild. This type never rebuilds itself.
public int SourceVersion { get; }
Property Value
Methods
Build(MarkerStore, int, int, int, double)
Rebuilds every level from store's current
contents. Synchronous and complete: nothing is reused from a
previous build, and no level is queryable until all of them are
finished.
public void Build(MarkerStore store, int minLevel, int maxLevel, int tileSizePx, double clusterRadiusPx)
Parameters
storeMarkerStoreMarkers to cluster. Only read.
minLevelintCoarsest integer zoom level to build.
maxLevelintFinest integer zoom level to build; at least
minLevel.tileSizePxintTile edge length in screen px.
clusterRadiusPxdoubleMerge radius in screen px, applied at each level's own zoom.
Remarks
A marker enters the build when MarkerInstanceData.IsDrawn is
true, which is the authoring toggles (size and enabled) only. A
marker hidden merely by the current zoom window still belongs in
the hierarchy, because the same hierarchy answers for every level.
Markers are visited in store slot order and each level's clusters feed the next in the order they were produced, so two builds from identical store contents produce identical levels. Nothing here iterates a hash container, which would break that.
When two clusters are exactly equally near a seed, which one wins is decided inside MarkerClusterer by the order its own transient centroid grid hands back candidates, not by a rule this type imposes. That choice is stable for a given input, which is what the paragraph above depends on, but it is not "lowest id wins" and callers must not assume it is.
ClustersForLevel(int)
The clusters built for level, in build order.
A cluster's MemberStart/MemberCount slice
MembersForLevel(int) for the same level.
public IReadOnlyList<MarkerCluster> ClustersForLevel(int level)
Parameters
levelint
Returns
MembersForLevel(int)
Marker store slots for level, grouped
contiguously by cluster. Every level resolves all the way down to
markers, so this list is the same length at every level: one entry
per clustered marker.
public IReadOnlyList<int> MembersForLevel(int level)
Parameters
levelint
Returns
ParentsForLevel(int)
For each cluster at level, the index of the
cluster one level coarser that contains it, or -1 at
MinLevel.
public IReadOnlyList<int> ParentsForLevel(int level)
Parameters
levelint
Returns
Query(double, in MercatorBounds, List<MarkerCluster>)
Fills results (cleared first) with the clusters
of the level zoom falls in that overlap
bounds. Reads an already-built level; never
clusters anything.
public void Query(double zoom, in MercatorBounds bounds, List<MarkerCluster> results)
Parameters
zoomdoubleMap zoom; floored to a level and clamped into the built range.
boundsMercatorBoundsViewport in normalized mercator; may extend past 0 or 1 to cover a wrapped view.
resultsList<MarkerCluster>Caller-owned output list; cleared first.
Remarks
A returned cluster's MemberStart/MemberCount address
MembersForLevel(int) at LastQueryLevel.