Every Layer Has an Owner
Open the Layers Menu and you will count somewhere around a hundred entries. Open map.allLayers
in the console and you will count a few more that the menu never shows. Each of those layers was
created by one of six mechanisms, is filtered by one of two properties, and sits at a position in
the draw stack that something, somewhere, deliberately chose. When a change to one layer makes
another misbehave, it is nearly always because two pieces of code believed they owned the same
property. This page is the ownership map.
Six kinds of layer
| Kind | Examples | Created by | Class |
|---|---|---|---|
| Portal operational layers | Fire perimeters, battalions, aircraft, weather, basemap-style VT | The WebMap, authored in ArcGIS Online | Whatever the portal says |
| The legacy camera service | ALERTCalifornia Cameras (LEGACY_CAMERA_LAYER_TITLES) | The WebMap; parked at init, never loaded | FeatureLayer (hosted) |
| Client graphics layers | Cursor Tracking LOS, area highlight, alert drawing, what3words | Hooks and on-demand features | GraphicsLayer |
| The view's own graphics | Recenter marker, coordinate crosshair, sightings, companion marks | view.graphics.add(...) | Graphics on MapView |
| Velocity set | Locations, Viewshed, Proboscis, Camera Feed + persisted companions | The WebMap; discovered by service name | StreamLayer or FeatureLayer |
The first two kinds arrive with the WebMap. The next three we construct. The last is a hybrid:
the layers come from the WebMap, but the app decides which of them are on the map at any moment,
because a StreamLayer only disconnects its WebSocket when it is removed, not when it is hidden.
The camera feature service
The legacy camera service deserves a paragraph only to say what it no longer does. Until
October 2026 it drew the site circles, answered the extent query that fed the tile grid, was
hit-tested by hover and click, carried clustering, and was refreshed every 60 seconds — and it was
the single largest source of ArcGIS 429s. Cameras now come from the Velocity set, so the catalog
parks this layer at startup like any other toggled-off layer, usePageUpdates ignores its stored
preference, and the Layers menu and the area-filter picker hide it. Nothing reads
NEXT_PUBLIC_CAMERA_LAYER_NAME any more.
The one client layer
initializeLayers adds a single GraphicsLayer, "Cursor Tracking LOS", holding one graphic whose
geometry is moved in place as the operator's cursor crosses the camera image. Its title is listed
in CLIENT_LAYERS so the Layers Menu never offers it as a toggle and the catalog never parks it.
Everything else a camera needs — point, heading arrow, field-of-view wedge, line-of-sight beam —
is a Velocity layer; see Streams, Beams, and the Handoff.
Where things draw
From the top down: the view's own graphics (markers, sightings, companion marks) sit above everything; graphics layers added later by features (area highlight, alert drawing, what3words) come next; then the cursor line, added at init; then every WebMap operational layer in WebMap order, which is also where a restored layer lands — the Velocity camera layers among them, each persisted companion ordered just beneath its stream; and the basemap underneath it all.
Two forces decide this stack. The WebMap order comes first, as authored; webmap.add appends the
cursor line above it. Then ensureVelocityLayersOnMap keeps each companion below its stream so a
live beam covers the last-known one. If a portal layer must draw above the camera beams, that is
a WebMap ordering decision, authored in the portal. Graphics layers that features add later use
map.add(layer), which appends to the top, and view.graphics is above all layers by
construction. A claimed Velocity layer inside a WebMap group that the GIS team left hidden gets
its ancestors made visible so it can draw at all.
Who owns which property
This is the part that prevents the next regression. Each property on a camera layer has exactly one writer.
| Property | Writer | Meaning |
|---|---|---|
definitionExpression | applyVelocityLayerFilters (feature layers) | The Type and spotlight filters (and the beam gate), as deviceId IN (…) |
layerView.filter (features) | useFeatureMapAreaFilter | The area scope, ANDed client-side, evaluated after definitionExpression |
layerView.filter (streams) | applyVelocityLayerFilters only | Camera filter AND beam set AND handoff AND area scope, composed into one clause |
layerView.featureEffect | pulseAiDetectionHit | The AI pulse bloom on the point layers |
visible | The Layers Menu checkbox (usePageUpdates) | Plain visibility; never persisted for the Velocity layers |
renderer | The portal; borrowStreamRenderer for Locations only | Appearance; never a symbol on a feature |
Membership in map.layers | The catalog (park / restore) via ensureVelocityLayersOnMap | Loaded-or-not, connected-or-not |
The split between the two filter properties is not cosmetic. A layer-view filter applies with no
network request and composes with the definition expression instead of competing for it. A StreamLayer in SDK 5 has no filter of its own (the 4.x connection filter became
definitionExpression and geometryDefinition, which go to the Velocity service), which is why
the area hook publishes its clause on the store and lets the stream's single writer compose it.
Writing layer.filter on a stream is a silent no-op; it was one for several months.
Legacy you will find in the tree
data/layers.ts exports a layers array describing WFIGS incident and perimeter services with
custom renderers. Nothing on the current map reads it; those layers come from the WebMap now.
The file is still imported for layersToAccessRequirements and layersRequiringAuth, which gate
menu rows by role. Likewise data/basemaps.ts and util/map/getBasemapByKey.ts describe
basemaps the map no longer constructs; only basemapOptions (the Topographic/Imagery pair and the
layer titles each one toggles) is live. Treat both as inventory, not as entry points.
Wrapping up
Six kinds of layer, one owner each. Portal layers are curated, not authored; the Velocity set is
discovered by service name and is where every camera comes from; the legacy camera service is
parked; graphics layers and view.graphics are for small, mutable things. Draw order is WebMap,
then ours. And every filtering property has exactly one writer, which is the rule to check before
adding a fifth.
Next: The Parking Lot, how a hundred portal layers load only when asked.