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

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

bool

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

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.

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>

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.