Skip to main content

Drawing on Top

Not everything on the map is a fleet. A single alert pin with its radius ring, a located smoke point with its sight line, a 3 m what3words square, a crosshair where someone right-clicked: these are one-offs, they change geometry rather than attributes, and they are owned by features that do not own the map. ArcGIS gives two homes for such graphics, and the choice between them, plus the clean-up discipline each demands, is most of what this page is about. The rest is the handful of Esri services and widgets we use that are not layers at all.

view.graphics or a GraphicsLayer?​

Both hold plain Graphic objects with their own symbol. The difference is scope and order. view.graphics is one collection owned by the view, drawn above every layer, and shared by everyone who uses it. A GraphicsLayer is a layer: it has a position in the stack, a listMode, can be hidden as a unit, and is removed as a unit. The convention that fell out of the features below:

Use view.graphics when…Use a GraphicsLayer when…
A few graphics, short-lived, always on topThe graphics belong together and may be hidden or removed as one
The owner tags its graphics with an attribute to find them againThe owner needs an id to find the layer again after a remount
Examples: recenter marker, coordinates crosshair, sightings, companion marksExamples: cursor LOS, area highlight, alert drawing, what3words

Whichever you choose, the owner removes exactly what it added. SightingsMapLayer tags every graphic with a sighting attribute and removes by that tag; W3WMapLayer keeps the graphics it added in a ref; useFeatureMapAreaFilter and useFeatureMapPolygons look up their layer by id so a remount reuses it instead of stacking a duplicate. A graphics layer added with map.add(layer) lands on top of the stack; one that must stay out of the Layers Menu sets listMode: 'hide'.

The alert pin, the ring, and the picture in the email​

Creating an alert draws on the operator's live map through useFeatureMapPolygons: a GraphicsLayer with id alert-drawing-layer, a PictureMarkerSymbol built from the beacon SVG in util/map/alertBeaconSvg.ts, and a radius ring. The ring is a Circle with geodesic: true, and that flag is not optional. Without it the circle is buffered in the centre's own spatial reference, WGS84 degrees, where a degree of longitude is shorter than a degree of latitude by cos(latitude); the "circle" renders as an ellipse that gets worse the further north you are. A click listener moves the pin to any point that is not the pin itself, re-adding the pin last so it draws above the ring.

When the alert is sent, captureAlertMapImage turns that same live map into the 1920 × 1080 PNG in the recipients' email:

  1. Clone view.viewpoint, set snapToZoom false so the fit is exact rather than snapped to a level (which would make framing depend on latitude).
  2. goTo a WGS84 extent computed purely in util/map/alertMapGeometry.ts: centred on the origin, half-height 4.4 × radius with a 3-mile floor, width from the 16:9 aspect. Web Mercator is conformal, so equal ground distances fill the frame edge to edge.
  3. Wait for view.updating to go busy then idle (reactiveUtils.whenOnce), bounded at 8 s, plus one more frame.
  4. view.takeScreenshot({ format: 'png', width, height }), which crops to the requested aspect around the centre, so a non-16:9 panel still yields the intended extent.
  5. Restore the viewpoint in finally, whatever happened.

Every recipient therefore sees the same framing of the real map, layers and all, no matter how the operator had it panned. A failed re-frame still sends whatever is on screen rather than no map.

Sightings and the cross-fix​

Locate-on-map writes fixes into sightingsStore; SightingsMapLayer draws them into view.graphics: an optional footprint polygon in a quiet blue dash, the sight line from camera to point, a halo and a dot, and a TextSymbol with distance and bearing. When the latest fix and the most recent fix from a different camera exist, their bearings are crossed and a violet diamond marks the result; it owes nothing to the terrain model and is the most accurate answer available. A new fix that lands outside the current extent brings the map to it (goTo the line's extent expanded 1.4×); existing fixes never move the map. Click a mark and a chip appears beside it, positioned with toScreen and re-projected on a reactiveUtils.watch of centre, scale and rotation, portalled into the light DOM because the view's container sits in a shadow root.

what3words and the companion marks​

W3WMapLayer keeps one GraphicsLayer (w3w-layer) and draws exactly the 3 m square what3words returns: a red fill with a white edge and a dashed red edge over it, and nothing else. A fixed-pixel locator ring was tried and removed because it implied an area the square does not have. CompanionMapMarks draws the PeakFinder companion's hovered terrain point as a small amber dot and its pinned bearing as a red diamond with a label, directly into view.graphics, sized to sit with the camera symbology rather than shout over it. All three components are exported from the single on-demand ArcGIS module and are mounted only while they have something to draw.

Markers on the view itself​

FeatureMap owns two small graphics of its own. The recenter marker (/images/distance.svg, 44 px) appears after a search, and a click listener attached after a one-second delay removes it when it is clicked; the delay stops the initiating interaction from dismissing it. The coordinates crosshair is a 7 px white circle with a blue outline at the right-clicked, long-pressed or picked point, shown while the card is open. They are deliberately independent so a search marker and a coordinates card can coexist.

Services that are not layers​

Geocoding. The search box's place suggestions come from Esri's World Geocoder, called directly with fetch: suggest with a California location bias and a category allowlist, then findAddressCandidates with the suggestion's magicKey and a California search extent to resolve the one the operator picks. Both are cached in memory per term and per key. The alert form uses the SDK's locator.addressToLocations against the same service to resolve a city name. These are public, unauthenticated endpoints with their own quota, and they are the only Esri calls the app makes without a token.

The widgets. Two Esri web components are slotted inside <arcgis-map>. The legend (<arcgis-legend slot="bottom-right" legendStyle="classic">) receives its layerInfos as a JS property from the catalog's list, so a layer restored later is legended like one that was on from the start; it is hidden unless the legend button, behind enable-map-legend-button, turns it on. The distance tool (<arcgis-distance-measurement-2d unit="miles">) is started and cleared imperatively from the ruler button, behind enable-map-ruler-button, with a close control we supply because Esri's component has none. The other map components imported in FeatureMap (fullscreen, zoom, home, layer list) are registered but not rendered; the app draws its own controls.

Wrapping up​

Short-lived, always-on-top graphics go on the view and are found again by attribute; grouped graphics get their own layer, found again by id, hidden from the menu. The alert ring is geodesic or it is an ellipse; the email capture re-frames the live map deterministically and puts it back. Sightings, what3words and the companion marks load through the one on-demand module. Geocoding is the only unauthenticated Esri call, and two Esri widgets survive, both behind flags.

Next: The Request Budget.