The Parking Lot
Give <arcgis-map> an item-id and it does something reasonable and expensive: it loads the
WebMap, creates the view, and then creates a layer view for every layer in the map. Creating a
layer view loads the layer: its service metadata, and for the fifty-odd layers referenced by
portal item, a portal lookup first. Visibility does not matter; an invisible layer's view is
merely suspended after it has loaded. On 2026-09-03 we measured it: 110 of 113 layers loaded at
startup for a map on which the operator typically has a handful turned on.
The fix is a parking lot. Layers the operator has not asked for are kept out of the map's collections entirely, and put back, at their original position, when a checkbox turns them on.
The catalog
util/map/webMapLayerCatalog.ts is a snapshot of the WebMap's full tree, taken by
usePreparedWebMap right after WebMap.load() and before any view exists. It remembers every
layer, its parent (group or map) and its index among its siblings, and it is registered against
the WebMap in a WeakMap so a discarded map takes its catalog with it.
- Camera Arrowsours
- Cursor Tracking LOSours
- Line of Sightours
- Field of Viewours
- basemap
Press reload to boot the map in the selected mode.
Three things are never parked: the topo and imagery layers (TOPO_LABELS, IMAGERY_LABELS),
group layers themselves (their children park individually), and the camera feature service.
Everything else leaves the map at prepare time. restore() puts a layer back after every
original sibling that precedes it and is currently on the map, which keeps WebMap order intact
no matter which subset is on.
A parked layer is still a real layer object. It can be found, configured and queried, and a query loads it on demand. That is what lets the Alerts panel query the "Alerts" layer whether or not it is drawn, and what lets a saved area scope resolve against a boundary layer the operator has switched off.
map.allLayersmap.allLayers lists only what is on the map, so a toggled-off layer silently vanishes from any
code that walks it. Every finder goes through listWebMapLayers, findWebMapLayer or
getLayerCatalog. The Layers Menu tree, the alert modals, area-filter discovery, Velocity
discovery and stream-token injection all do. Where the point is to draw or hit-test,
map.allLayers is still correct, and isWebMapLayerOnMap answers "is this one parked right now?"
One pipeline for every visibility change
Nothing sets layer.visible directly. Visibility lives in one place, the layersOnMap array in
mapCameraInteractiveStore, and every change flows through the same four steps:
onLayerVisibilityChange in useFeatureMapAGOLLayers is the only place that touches the
collections. For a parkable layer turning on it restores and calls load() with the layerLoadingStore set to
loading, then loaded or failed. Turning off parks and resets the status to idle. For the
topo and imagery layers it flips visible, re-adding them to the root collection if a group had
swallowed them, imagery above topo. For a StreamLayer without a catalog it removes and re-adds
the layer, since hiding one leaves its WebSocket open.
The sync effect in usePageUpdates also decides what to persist: every layer except the ignore
list and the Velocity set is written to layerVisibilityStore, and the Layers Menu's optimistic
toggle calls useUpdateLayerVisibilityMutation so the operator's layer setup follows them across
devices. Basemap state is not persisted; the basemap button owns it, and on load processLayers
forces topo on and imagery off.
Basemaps are layers wearing a trench coat
In this WebMap the "basemap" an operator sees is the portal's hillshade basemap plus an
operational vector tile layer titled ALERTCalifornia World Topographic Map, with an imagery
alternative in a group. data/basemaps.ts defines two options, Topographic and Imagery, each
naming the layer titles it toggles. The basemap button in the map's FAB tray cycles through them
and flips those titles through the exact pipeline above. There is no basemap-specific machinery,
which is why it is listed here rather than on its own page.
What the menu is allowed to show
processLayers builds the tree from the catalog's roots (parked layers included, at their WebMap
positions) and hands it to the store, which also flattens it into layersOnMap. Before anything
reaches the screen, three filters apply:
- The ignore list.
LAYERS_TO_IGNOREis the topo and imagery titles plusCLIENT_LAYERS. Operators cannot switch off the arrows, and the basemap pair is the basemap button's job. - The legacy camera service.
LEGACY_CAMERA_LAYER_TITLESkeeps the parked camera feature service out of the menu; it is always on. - Roles.
hooks/useCanAccessLayer.tsreads two maps indata/layers.ts:layersToAccessRequirements(Alerts needsNotification.Alert; Raw AI, AI Hits and AI Ray Lines needAI.Access) andlayersRequiringAuth(CAL FIRE Battalion Boundaries needs any signed-in session). An inaccessible node renders nothing and is excluded from the accordion counts, so an operator without the role never learns the layer exists.
The Velocity layers have one more rule, covered on their own page: while the real-time source is active their checkboxes toggle plain visibility; while it is inactive they are off the map and therefore out of the menu.
Registering a new client-side layer
If you create a layer in layerFactories.ts and forget to add its title to CLIENT_LAYERS, three
things go wrong at once: it appears in the Layers Menu as a toggle, the catalog refuses to park it
(it is not in the snapshot, which is correct) but the visibility handler will still flip its
visible, and its state gets persisted to the server as an operator preference. Add the title
before you add the layer.
Wrapping up
The WebMap is loaded once, before the view, and every toggleable layer is parked out of the
collections so the view only creates views for what the operator has on. A restore puts a layer
back in WebMap order, loads it then and there, and reports the load on the menu row. Every
visibility change, basemaps included, flows through layersOnMap and one handler. Finders use the
catalog; drawers use map.allLayers.
Next: Icons Without Image Files, how the layers we own decide what they look like.