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 top | The graphics belong together and may be hidden or removed as one |
| The owner tags its graphics with an attribute to find them again | The owner needs an id to find the layer again after a remount |
| Examples: recenter marker, coordinates crosshair, sightings, companion marks | Examples: 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:
- Clone
view.viewpoint, setsnapToZoomfalse so the fit is exact rather than snapped to a level (which would make framing depend on latitude). goToa WGS84 extent computed purely inutil/map/alertMapGeometry.ts: centred on the origin, half-height4.4 × radiuswith a 3-mile floor, width from the 16:9 aspect. Web Mercator is conformal, so equal ground distances fill the frame edge to edge.- Wait for
view.updatingto go busy then idle (reactiveUtils.whenOnce), bounded at 8 s, plus one more frame. 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.- 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.