Skip to main content

Why FeatureMap replaced MapComponent

The map renders thousands of camera points that update every few seconds. The original MapComponent did that with an ArcGIS GraphicsLayer and a scale watcher. At fleet size it dropped to 15–30fps while panning and burned 5–10% CPU sitting idle.

FeatureMap replaced it with a FeatureLayer plus declarative renderers. Same visual result, roughly an order of magnitude less work per frame.

You mostly need this page for one reason: so you don't reintroduce the old pattern. The rules at the bottom are the part that matters day to day.

What actually changed

Three decisions carry nearly all the gain.

Declarative visual variables instead of scale watchers. The old code subscribed to zoom changes and recomputed every symbol in JavaScript on the main thread. A FeatureLayer's renderer expresses "size varies with scale" as configuration, and ArcGIS applies it on the GPU without a round trip through our code.

One shared symbol instead of one per graphic. A GraphicsLayer allocates a symbol object, a JS geometry object, and a JS attribute object per point. A FeatureLayer stores geometry in a binary buffer, attributes in typed arrays, and points every feature at a single shared symbol — with an R-tree spatial index on top.

Diff-based updates instead of full redraws. The old layer cleared and rebuilt on every poll. FeatureMap compares incoming camera state against current state and applies only the delta, so a poll that changed 3 cameras touches 3 features rather than 5,000.

The numbers

Measured at 5,000 camera points:

Legacy MapComponentFeatureMap
Initial render~3000ms~800ms
Zoom frame time50–100ms~16ms
Pan30–45fps60fps
Idle CPU5–10%<1%
Memory~150MB~80MB
Features touched per pollall 5,0001–10 changed

The idle-CPU line is the one that mattered most operationally. Dispatchers leave this app open for an entire shift, often on modest hardware, sometimes on battery in a vehicle.

The rules this leaves you with

These are the ones people break when adding to the map:

  • Never add/remove to redraw. Update the attribute and let the renderer react. A delete-then-add produces a visible blink and defeats the diff.
  • Never subscribe to zoom to resize things. Use the renderer's visual variables.
  • Never set a symbol on an individual feature. That's what put the old implementation on the main thread.
  • Don't duplicate camera state into a second store. One source of truth is what makes the diff correct; a second copy is what made the old version race.

If you find yourself writing a loop over every camera on every poll, stop — that's the shape of the problem this migration removed.

Next: FeatureMap internals for how the diff and hook lifecycle actually work.