Table of Contents

Class ClusterHierarchy

Namespace
LansMap.Core.Markers

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
object
ClusterHierarchy

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

ClusterBuildState

IsBuilt

True once a Build(MarkerStore, int, int, int, double) has completed.

public bool IsBuilt { get; }

Property Value

bool

LastAdvanceSeedsProcessed

Seeds consumed by the most recent AdvanceBuild(int) call.

public int LastAdvanceSeedsProcessed { get; }

Property Value

int

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

int

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

int

LastQueryLevel

public int LastQueryLevel { get; }

Property Value

int

MaxLevel

Finest (most zoomed in) integer level built.

public int MaxLevel { get; }

Property Value

int

MinLevel

Coarsest (most zoomed out) integer level built.

public int MinLevel { get; }

Property Value

int

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

int

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

seedBudget int

Maximum 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

store MarkerStore
minLevel int
maxLevel int
tileSizePx int
clusterRadiusPx double

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

store MarkerStore

Markers to cluster. Only read.

minLevel int

Coarsest integer zoom level to build.

maxLevel int

Finest integer zoom level to build; at least minLevel.

tileSizePx int

Tile edge length in screen px.

clusterRadiusPx double

Merge 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

level int

Returns

IReadOnlyList<MarkerCluster>

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

seedBudget int

Returns

bool

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

level int

Returns

IReadOnlyList<int>

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

level int

Returns

IReadOnlyList<int>

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

zoom double

Map zoom; floored to a level and clamped into the built range.

bounds MercatorBounds

Viewport in normalized mercator; may extend past 0 or 1 to cover a wrapped view.

results List<MarkerCluster>

Caller-owned output list; cleared first.

Remarks

A returned cluster's MemberStart/MemberCount address MembersForLevel(int) at LastQueryLevel.