Table of Contents

Class MarkerStore

Namespace
LansMap.Core.Markers

The bulk-tier marker collection: a flat array of MarkerInstanceData plus a free list, sized for tens of thousands of markers uploaded to the GPU as one buffer.

public sealed class MarkerStore
Inheritance
object
MarkerStore

Remarks

This is the data-only marker tier. There is no GameObject and no component per marker; a marker is an index into an array, and the whole array is handed to a StructuredBuffer for a single instanced draw. Use the prefab tier instead when you need a few hundred markers that carry their own scene objects.

Markers are stored in mercator and go through the same per-frame transform as tiles, so they stay pinned to the map rather than by a follow-up correction.

Zero-GC by contract: mutating a marker allocates nothing, and the backing array only ever grows, by doubling, when Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) runs out of slots. Pass a realistic initialCapacity to the constructor to avoid the copy. This is also why Instances hands out the live array rather than a copy.

Not thread safe, and not safe against concurrent enumeration: any mutation may resize or reuse slots.

Constructors

MarkerStore(int)

Creates an empty store with room for initialCapacity markers before the first grow.

public MarkerStore(int initialCapacity)

Parameters

initialCapacity int

Slots to preallocate. Values below 4 are raised to 4. Growing later costs one array copy, so size this for the expected marker count.

Fields

DefaultCapacityHint

Pre-allocation hint: how many markers a fresh store expects before growing.

public const int DefaultCapacityHint = 256

Field Value

int

MaxGeneration

Times one slot can be reused before it is retired. A slot whose generation space is spent is never returned to the free list, so a generation can never wrap back onto an id already handed out.

public const int MaxGeneration = 4095

Field Value

int

MaxSlots

Slots this store can ever hand out. Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) throws past it rather than let a slot index collide with the generation field.

public const int MaxSlots = 524288

Field Value

int

MinGpuBufferCapacity

Smallest GPU instance buffer a marker layer allocates.

public const int MinGpuBufferCapacity = 64

Field Value

int

Properties

ActiveRange

Exclusive upper bound on slot indices ever handed out, and the range the GPU uploads. Slots below this may be live or free; hidden and removed markers are uploaded too and collapsed by the vertex shader. Never decreases, not even when every marker is removed.

public int ActiveRange { get; }

Property Value

int

Capacity

Slots currently allocated, including free ones.

public int Capacity { get; }

Property Value

int

Count

Number of live markers, which is not an upper bound on slot indices because removed slots leave gaps. Iterate to ActiveRange, not to this.

public int Count { get; }

Property Value

int

CurrentSampler

Ground-height source Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) and UpdatePosition(int, LatLon, double) resolve altitude against right now. Not a per-call parameter: the owning LansMap.Markers.Bulk.MarkerLayer (Runtime assembly) keeps this in sync with the map's own elevation sampler, the same way SpatialIndex is set once and read by every later mutation. Null, the default, means flat (0 m) - the same "no sampler means flat map" convention ResolveAltitudeMercator(MarkerAltitudeMode, double, LatLon, IElevationSampler) and MapViewTransform already use.

public IElevationSampler CurrentSampler { get; set; }

Property Value

IElevationSampler

Dirty

True when a marker changed since the last ClearDirty() and the GPU buffer needs re-uploading. The renderer polls this so that a static set of markers costs nothing per frame.

public bool Dirty { get; }

Property Value

bool

DirtyRangeCount

Number of contiguous slots from DirtyRangeStart that need re-uploading. This is a bounding range, not a sparse set: a slot between the first and last touch that did not itself change is still included, so the renderer can do one contiguous SetData call instead of one per touched slot. Zero when Dirty is false.

public int DirtyRangeCount { get; }

Property Value

int

DirtyRangeStart

Start of the slot range touched since the last ClearDirty(), inclusive. Only meaningful when Dirty is true; the renderer should check Dirty first.

public int DirtyRangeStart { get; }

Property Value

int

Instances

The live marker data, read-only to callers outside this assembly. Only indices below ActiveRange hold meaningful data, and the span is invalidated by any Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) that grows the backing array (re-read this property after such a call rather than keeping an old span around).

public ReadOnlySpan<MarkerInstanceData> Instances { get; }

Property Value

ReadOnlySpan<MarkerInstanceData>

Remarks

Indexed by slot, not by marker id: pass ids through SlotOf(int) first. A marker id also carries a generation, so using one as an index reads the wrong slot or throws.

A ReadOnlySpan<T> indexer returns by value, the same reason transform.position.x = 5 does not compile on Vector3: Store.Instances[id].Field = x no longer compiles for an outside caller, closing off the raw-write bug this property used to allow (writing AltitudeMercator here does not raise MaxAltitudeMercator, so a direct write left the re-anchor predicate believing the content was lower than it is - the unsafe direction). Raise a marker through UpdatePosition(int, LatLon, double), or restyle it through MarkerHandle or this type's own Set* methods.

The renderer's own zero-copy GPU upload still needs the raw array: see LansMap.Core.Markers.MarkerStore.RawInstances, internal to the Runtime assembly.

MaxAltitudeMercator

The largest altitude any marker in this store has ever carried, in the same normalized mercator units MarkerInstanceData.AltitudeMercator uses, for MapViewTransform.NeedsReanchor.

public double MaxAltitudeMercator { get; }

Property Value

double

Remarks

A running maximum, so it never decreases, and nothing in this type lowers it again: removing the tallest marker or moving it down to the ground both leave this at the old value for the lifetime of the store. Too large is the safe direction for the only consumer, whose job is to re-anchor no later than necessary, and it keeps the cost at one compare per write instead of a scan of every slot.

What that costs, concretely: add one marker 100 km up, delete it, and the map re-anchors on every frame for the rest of the session, which at the 10,000 marker target means re-uploading the whole instance buffer every frame. Nothing here decays, and no rebuild API exists to reset it. If that becomes a problem, recompute the recompute the maximum on Clear or on a compaction pass, where the scan is already being paid for.

A non-finite altitude does not raise this. It is still stored on the instance and still reaches the GPU: only the maximum refuses it, so that one bad marker cannot make the predicate useless for every other marker in the store.

SpatialIndex

Optional spatial index this store keeps in sync by forwarding every Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode), Remove(int) and UpdatePosition(int, LatLon, double) into it, keyed by slot - the same convention MarkerPicker and MarkerClusterer already use for spatial-index candidates (MarkerViewportTracker, in the Runtime assembly, matches it too). Set this before adding markers: existing live markers are not backfilled into an index attached later, so constructing both together (MarkerLayer, in the Runtime assembly) is the intended usage.

public MarkerSpatialIndex SpatialIndex { get; set; }

Property Value

MarkerSpatialIndex

Version

Monotonic change counter: increments on every mutation that marks a slot dirty. Exists for SECOND consumers of a shared store: the dirty range is a single-consumer protocol (whoever calls ClearDirty() first eats it), so a layer sharing this store with the owning layer re-uploads when this counter moves instead of touching the dirty machinery. Wraps harmlessly after 2^31 mutations - consumers compare for inequality, not order.

public int Version { get; }

Property Value

int

ZoomMax

public float[] ZoomMax { get; }

Property Value

float[]

ZoomMin

Per-slot lower zoom bound, indexed like Instances. NegativeInfinity (the Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) default) means no lower bound. MarkerLayer uploads this into a StructuredBuffer the vertex shader compares against the current view zoom with the same inclusive test IsZoomVisible(double, float, float) runs on the CPU.

public float[] ZoomMin { get; }

Property Value

float[]

Methods

Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode)

Adds a marker and returns its slot index, which is the id every other method on this type takes.

public MarkerHandle Add(LatLon position, double altitudeMeters, float sizeParam, MarkerSizeMode mode, uint colorRgba, MarkerShape shape = MarkerShape.Circle, bool flat = false, float minZoom = -Infinity, float maxZoom = Infinity, MarkerAltitudeMode altitudeMode = MarkerAltitudeMode.RelativeToGround)

Parameters

position LatLon

Where the marker sits, in degrees WGS84. Projected to mercator once, here, so the per-frame path does no trigonometry. Latitude is clamped into the Web Mercator range for the position but not for altitudeMeters.

altitudeMeters double

Authored altitude in meters; meaning depends on altitudeMode. Converted to mercator units at this latitude via ResolveAltitudeMercator(MarkerAltitudeMode, double, LatLon, IElevationSampler). Unreliable above about 85 degrees of latitude, where the conversion divides by a cosine approaching zero.

sizeParam float

Marker size, in screen px when mode is SceneSize or in ground meters when it is RealSize. Zero or negative is legal and means hidden; the marker is still live and still removable, because slot lifetime is tracked separately from visibility. Only NaN and infinity are refused.

mode MarkerSizeMode

Whether sizeParam is screen px or ground meters, and therefore whether the marker keeps its size on screen or on the ground as the map zooms.

colorRgba uint

Fill color packed as 0xRRGGBBAA, one byte per channel, red in the most significant byte.

shape MarkerShape

Shape to draw. Pass a value of 16 or above, cast from the id returned by MarkerLayer.RegisterShape, to use a custom sprite.

flat bool

True to lay the marker flat on the map plane so it rotates and tilts with the world. False, the default, billboards it toward the screen so it stays upright at any bearing or pitch.

minZoom float

Zoom below which the marker is invisible, inclusive at the bound itself. NegativeInfinity, the default, means no lower bound. See IsZoomVisible(double, float, float) for the exact test.

maxZoom float

Zoom above which the marker is invisible, inclusive at the bound itself. PositiveInfinity, the default, means no upper bound. A maxZoom below minZoom is legal and simply means the marker is never visible at any zoom, the same tolerance sizeParam extends to zero and negative sizes.

altitudeMode MarkerAltitudeMode

How altitudeMeters is resolved against sampled ground. Default RelativeToGround. With elevation off (no sampler; CurrentSampler null, the shipped default) every mode resolves to the same flat value, so an existing call site that omits this argument renders exactly as it always did.

Returns

MarkerHandle

A handle carrying this store and the new marker's id (a slot index plus the generation that slot is on). Slots are reused, ids are not, so an id from a removed marker is refused by every method here instead of addressing whoever holds the slot now. Index Instances with SlotOf(int) of the id, never with the id itself. MarkerHandle converts implicitly to int, so every existing int id = store.Add(...) call site keeps compiling unchanged.

Exceptions

ArgumentOutOfRangeException

sizeParam is NaN or infinity.

InvalidOperationException

The store has handed out MaxSlots slots.

AddTag(int, string)

Tags markerId with tag, so it can later be found with MarkersWithTag(string) or bulk-removed with RemoveByTag(string) without the caller holding this id.

public void AddTag(int markerId, string tag)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

tag string

Caller-defined label. A marker may carry any number of distinct tags; adding one it already carries is a no-op.

Remarks

A stale id is ignored, like every other mutator here. Tags are a setup/query-time concern on the CPU only - not on the GPU upload path, and never read by the shader.

Exceptions

ArgumentException

tag is null or empty.

ClearDirty()

Clears Dirty and the tracked slot range. Called by the renderer once it has uploaded DirtyRangeStart through DirtyRangeCount; callers who mutate markers should not call this.

public void ClearDirty()

GenerationOf(int)

The reuse counter a marker id carries. Zero for a slot's first occupant, which is why an id equals its slot until that slot is removed and handed out again.

public static int GenerationOf(int markerId)

Parameters

markerId int

Returns

int

GetAuthoredAltitude(int)

The altitude mode and authored meters currently stored for markerId - the same two values Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode)/UpdatePosition(int, LatLon, double) accepted, not the derived AltitudeMercator, which for a non-Absolute mode already has ground height baked in. A caller that needs to re-author a marker's position (a drag controller, for example) must read this rather than convert the resolved mercator value back to meters: re-deriving from the resolved value double-counts ground height on every subsequent write for RelativeToGround/ClampToGround markers.

public (MarkerAltitudeMode Mode, double AltitudeMeters) GetAuthoredAltitude(int markerId)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

Returns

(MarkerAltitudeMode Mode, double AltitudeMeters)

The stored mode and meters, or RelativeToGround and 0 for a stale id - the same defaults Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) uses, since there is nothing authored to report.

HandleOf(int)

The current id of an allocated slot, or -1 when the slot holds no marker. This is the bridge from an index - a loop counter, MarkerPicker output, a cluster member - to an id the mutators accept.

public int HandleOf(int slot)

Parameters

slot int

Returns

int

HasTag(int, string)

Whether markerId currently carries tag.

public bool HasTag(int markerId, string tag)

Parameters

markerId int
tag string

Returns

bool

IsEnabled(int)

The authoring flag for markerId. False for a stale id. This is not "is it on screen": zoom can hide an enabled marker.

public bool IsEnabled(int markerId)

Parameters

markerId int

Returns

bool

IsLive(int)

Whether markerId still owns the marker it was handed for. False for an id whose slot was removed, and false for one whose slot has since been handed to a different marker: that second case is what a bare slot check cannot answer.

public bool IsLive(int markerId)

Parameters

markerId int

Returns

bool

Remarks

A different question from "is it drawn": a live marker with size 0 is hidden, and the drawing, picking and clustering paths rightly test SizeParam for that.

IsSlotLive(int)

Whether a slot index holds a marker, whoever owns it. For loops over ActiveRange; ownership questions want IsLive(int).

public bool IsSlotLive(int slot)

Parameters

slot int

Returns

bool

IsZoomVisible(double, float, float)

Whether a marker whose visibility window is [minZoom, maxZoom] should be shown at zoom. Both edges are inclusive, and this is the exact comparison MarkerQuad.shader runs per instance against ZoomMin / ZoomMax - the CPU-testable mirror of that one line, not a separate policy that could drift from it.

public static bool IsZoomVisible(double zoom, float minZoom, float maxZoom)

Parameters

zoom double
minZoom float
maxZoom float

Returns

bool

Remarks

Passing NegativeInfinity / PositiveInfinity (the Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode) defaults) makes the corresponding side of the comparison always true, which is how "unset" needs no separate branch here or on the GPU. A NaN zoom, which no caller should ever pass, makes both sides false and so reports not visible: the same fail-closed direction float comparison already takes with NaN everywhere else in this file.

MarkersWithTag(string)

Live ids currently tagged tag, or an empty collection for a tag nothing carries.

public IReadOnlyCollection<int> MarkersWithTag(string tag)

Parameters

tag string

Returns

IReadOnlyCollection<int>

Remarks

The returned collection is this store's own live set, not a copy, the same convention Instances documents: do not mutate the store (in particular, do not call Remove(int) or RemoveByTag(string)) while enumerating it. Use RemoveByTag(string) itself for that, which copies first.

Remove(int)

Removes the marker markerId names and returns its slot to the free list for reuse.

public void Remove(int markerId)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode). An id whose slot lies outside the store throws IndexOutOfRangeException; an id whose marker is gone does nothing.

Remarks

Removal is recorded in a separate liveness flag, not by zeroing the size. Calling this twice on one id is safe and does nothing the second time. Hidden and removed are distinct states: size zero still collapses the quad in the vertex shader, and IsLive(int) answers whether a stored id is stale.

A stale id is ignored rather than obeyed. Before ids carried a generation this call removed whichever marker had inherited the slot, so a component that removed a marker twice with an Add in between deleted a stranger's.

RemoveByTag(string)

Removes every marker currently tagged tag, the same as calling Remove(int) on each of its ids, and returns how many were removed.

public int RemoveByTag(string tag)

Parameters

tag string

Returns

int

Remarks

Copies the tag's id set before removing, because Remove(int) strips the tag from that same set as it goes; iterating it directly while removing would skip members.

SetApplyZoomRange(int, bool)

Opts one marker in or out of its stored min/max zoom window.

public void SetApplyZoomRange(int markerId, bool apply)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

apply bool

True, the default on Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode), to honour the window. False keeps the stored min/max but never hides by zoom.

Remarks

A stale id is ignored. Does set Dirty: the GPU packs this into instance mode bit 19, unlike SetDraggable(int, bool).

SetDraggable(int, bool)

Opts one marker in or out of dragging by MarkerDragController.

public void SetDraggable(int markerId, bool draggable)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

draggable bool

True to allow the user to drag it.

Remarks

Deliberately does not set Dirty. This flag is used by CPU-side hit testing only and is not part of the GPU instance layout, so changing it cannot require a re-upload. A stale id is ignored.

SetEnabled(int, bool)

Turns one marker on or off without deleting it or zeroing size.

public void SetEnabled(int markerId, bool enabled)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

enabled bool

True, the default on Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode), to keep it in play. False hides draw and pick; zoom can still hide an enabled marker.

Remarks

A stale id is ignored. Does set Dirty: the GPU packs this into instance mode bit 18.

SetFlat(int, bool)

Switches one marker between lying flat on the map plane and billboarding toward the screen.

public void SetFlat(int markerId, bool flat)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

flat bool

True to lie flat and rotate and tilt with the world, false to stay upright facing the screen.

Remarks

A stale id is ignored.

SetOutlineColor(int, uint)

Overrides the outline color of one marker.

public void SetOutlineColor(int markerId, uint outlineRgba)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

outlineRgba uint

Outline color packed as 0xRRGGBBAA. Zero restores the layer-wide outline color, so transparent black cannot be requested here.

Remarks

A stale id is ignored.

SetRotation(int, float)

Rotates one marker's shape, e.g. to point a directional icon along its heading. See RotationDeg for the rotation direction and its billboard/flat/tier caveats.

public void SetRotation(int markerId, float rotationDeg)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

rotationDeg float

Degrees from the shape's unrotated authoring orientation. Not normalized; any finite value is accepted.

Remarks

A stale id is ignored.

SetShowStem(int, bool)

Draws or hides a vertical stem from one floating marker to ground.

public void SetShowStem(int markerId, bool showStem)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

showStem bool

True to draw a stem if the marker has altitude, false to hide it.

Remarks

A stale id is ignored.

SetStemColor(int, uint)

Sets the stem color for one marker, overriding the layer-wide stem color.

public void SetStemColor(int markerId, uint stemColorRgba)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

stemColorRgba uint

Color packed as 0xRRGGBBAA. Pass 0 to inherit the layer-wide color.

Remarks

A stale id is ignored.

SetStemWidth(int, float)

Sets the stem width for one marker, overriding the layer-wide stem width.

public void SetStemWidth(int markerId, float stemWidthPx)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

stemWidthPx float

Width in screen px. Pass 0 to inherit the layer-wide width.

Remarks

A stale id is ignored.

ShouldApplyZoomRange(bool, bool)

Whether a marker's stored min/max window should hide it. Both the layer flag and the per-marker flag must be true; either false keeps the numbers and still draws and picks.

public static bool ShouldApplyZoomRange(bool layerApply, bool markerApply)

Parameters

layerApply bool
markerApply bool

Returns

bool

SlotOf(int)

The slot a marker id addresses, which is how to index Instances. Idempotent on a value that is already a slot, so a loop counter may be passed through it harmlessly. Negative ids stay negative.

public static int SlotOf(int markerId)

Parameters

markerId int

Returns

int

UpdatePosition(int, LatLon, double)

Moves an existing marker, reprojecting it to mercator.

public void UpdatePosition(int markerId, LatLon position, double altitudeMeters)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

position LatLon

New position in degrees WGS84. Latitude is clamped into the Web Mercator range for the position but not for altitudeMeters.

altitudeMeters double

New authored altitude in meters; meaning depends on the marker's altitude mode. Unreliable above about 85 degrees of latitude.

Remarks

A stale id is ignored, not an error: MarkerDragController holds an id across frames where a concurrent Remove would turn a throw into a mid-drag crash. This also holds after the freed slot has been assigned to another marker, so a stale id cannot move the new occupant. Reuses the marker's currently stored altitude mode - see the overload below to also change the mode.

UpdatePosition(int, LatLon, double, MarkerAltitudeMode)

Moves an existing marker and changes how its altitude is resolved.

public void UpdatePosition(int markerId, LatLon position, double altitudeMeters, MarkerAltitudeMode altitudeMode)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

position LatLon

New position in degrees WGS84.

altitudeMeters double

New authored altitude in meters; meaning depends on altitudeMode.

altitudeMode MarkerAltitudeMode

How altitudeMeters is resolved against sampled ground.

Remarks

A stale id is ignored, same as the mode-less overload.

UpdateStyle(int, float, MarkerSizeMode, uint, MarkerShape)

Restyles an existing marker, leaving its position alone.

public void UpdateStyle(int markerId, float sizeParam, MarkerSizeMode mode, uint colorRgba, MarkerShape shape = MarkerShape.Circle)

Parameters

markerId int

Id returned by Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).

sizeParam float

New size, in screen px or ground meters according to mode. Zero or negative hides the marker without removing it. Only NaN and infinity are refused.

mode MarkerSizeMode

Whether sizeParam is screen px or ground meters.

colorRgba uint

Fill color packed as 0xRRGGBBAA.

shape MarkerShape

Shape to draw.

Remarks

Throws rather than restyle a marker the caller does not own. The measured failure this closes: with plain slot ids, Remove(a) followed by an Add that reused a's slot left id a passing the liveness check, and a stale call rewrote the new marker's size, mode, color and shape.

Exceptions

ArgumentOutOfRangeException

sizeParam is NaN or infinity.

InvalidOperationException

markerId names no live marker, either because it was removed or because its slot now belongs to a different marker. This is the one mutator that writes the size, so on a freed slot it would resurrect a marker the free list has already promised to the next Add(LatLon, double, float, MarkerSizeMode, uint, MarkerShape, bool, float, float, MarkerAltitudeMode).