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
Builds finest to coarsest. Queries read only the complete front. After
BeginBuild(MarkerStore, int, int, int, double), one worker may own AdvanceBuild(int) while the publisher only
calls Query(double, in MercatorBounds, List<MarkerCluster>), ClustersForLevel(int),
MembersForLevel(int) or ParentsForLevel(int), or reads
IsBuilt, MinLevel, MaxLevel,
SourceVersion or the LastQuery* properties.
BuildState and LastAdvanceSeedsProcessed
are worker-owned then, not completion signals. Join before committing,
cancelling or beginning again. Level lists remain valid until commit.
Properties
BuildState
State of the current rebuild.
public ClusterBuildState BuildState { get; }
Property Value
IsBuilt
True once a Build(MarkerStore, int, int, int, double) has completed.
public bool IsBuilt { get; }
Property Value
LastAdvanceSeedsProcessed
Seeds consumed by the most recent AdvanceBuild(int) call.
public int LastAdvanceSeedsProcessed { 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
AdvanceBuild(int)
Advances the current build without publishing it. A true result means the complete build is ready for CommitBuild().
public bool AdvanceBuild(int seedBudget)
Parameters
seedBudgetintMaximum seeds to process in this call.
Returns
- bool
True when the complete build is ready to publish.
BeginBuild(MarkerStore, int, int, int, double)
Starts a resumable rebuild of every level from
store's current contents: snapshots the leaf
seeds, then returns before sorting or doing greedy merge work.
Follow with
ContinueBuild(int) calls until one returns true.
Callable again before a previous BeginBuild(MarkerStore, int, int, int, double)/
ContinueBuild(int) sequence on this instance has
finished - that abandons the in-flight build and starts a fresh
one from a new snapshot, which is the only supported way to
react to the store changing again mid-build (never patch a
snapshot in place). Query(double, in MercatorBounds, List<MarkerCluster>) keeps answering from the
previous complete build for as long as either kind of build is
in flight, so there is no half-built state a caller can observe.
Same parameters, validation and leaf-seed snapshot as
Build(MarkerStore, int, int, int, double) (reuses its seed-collection pass).
public void BeginBuild(MarkerStore store, int minLevel, int maxLevel, int tileSizePx, double clusterRadiusPx)
Parameters
storeMarkerStoreminLevelintmaxLevelinttileSizePxintclusterRadiusPxdouble
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.
CancelBuild()
Drops an unpublished build while preserving the published hierarchy.
public void CancelBuild()
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
CommitBuild()
Publishes a complete build that reached Ready.
public void CommitBuild()
ContinueBuild(int)
Processes up to seedBudget seeds of the
current level's seed pass (a seed count, not a time budget - Core
has no notion of a frame, so a caller with a frame-time budget
converts it to a seed count itself, from its own measured or
estimated per-seed cost). Resumes exactly where the previous call
left off. Sorting and each level finish occupy their own call and
process zero seeds. Returns true once
every level down to minLevel is done and the front/back
swap has happened - at that point the new build is what
Query(double, in MercatorBounds, List<MarkerCluster>) answers from. Must not be called without a
matching BeginBuild(MarkerStore, int, int, int, double) first.
public bool ContinueBuild(int seedBudget)
Parameters
seedBudgetint
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.