Aim Guide
Operators reported two things about the 60° image on the location page: it is hard to tell
exactly where the centre is, and after a double-click there is no sense of how far the
camera went. The image has no centre marker at all — the heading ruler
(MeasurementTickOverlay) is switched off there — so people were judging the centre by eye.
The aim guide is an opt-in reticle drawn over the photo. It is off by default, lives behind the Aim guide button in the camera control group (on a portrait phone, the Show aim guide row of the more-options drawer), and is remembered per browser.
What it draws
At rest — an open-centre crosshair. The arms are 30% of the image height in each direction and stop short of an 8px ring, so the exact centre pixel is never covered. The arms carry tick marks. No chip at rest: the frame already prints its pan in the heading tick on the top edge and its full pose in the stamp along the bottom.
While aiming (operators with control rights, hover-capable devices) — the major ticks
get their degree labels, a gold target ring follows the pointer with a dashed vector from the
centre, and a chip beside the pointer reads the pan and tilt a double-click there would move
the camera to: → 144.70° · 3.90°. The frame's own stamp shows where the centre is, the pointer
chip shows where it would go, and the ticks between them give the distance. Both print two
decimals.
While a move is in flight — the arm segments light gold out to the target's column and
row, the ghost ring holds at the target with a moving to 140.40° · 1.00° chip, and the whole
thing fades once the new frame lands. The ghost is projected through the live centre, so when
the camera reports its new position (before the new frame arrives) the ring and the gold
segments glide into the centre. The chip names the absolute target for the same reason: a
signed offset would read 0.0° the moment the position updates. For the zoom tool the box being drawn gets a gold outline with its
centre marked, since that centre is where the camera will point.
While a destination is typed in Go to position — a dashed gold
ring where it lands in the frame with a go to … chip, or a chevron on the frame edge in the
direction of the move with the words for how far (turn right 85.00° · tilt up 12.00°) when
it is outside the frame. The preview goes away when the form closes.
Arrow keys (operators, guide on) — each press moves the camera by one major tick of the guide in that direction; Shift makes it one minor tick. The ticks are what the guide is showing, so the move is exactly the distance the operator is looking at. Never while typing, while a move is in flight, while a modal or the Go to position form is open, or while Peak Finder's fine-tune owns the arrow keys.
Why the ticks are true angles
Every tick is placed with directionToPixel, the same pinhole model the double-click handler
uses, so a tick labelled 5° sits exactly where a double-click would move the camera by 5°. A
"pixels per degree" ruler would be ~1.5° off mid-frame at the wide end, which is worse than
the whole point of the feature.
The tick step adapts to zoom and to the frame's on-screen width (pickAimGuideTickStep in
util/cameras/aimGuide.ts): the major step is the finest of 1°, 2°, 5°, 10° that leaves at
least 28px between ticks at the current field of view, and its minor (0.2°, 0.5°, 1°, 2°
respectively) appears only with 12px of room. Fully zoomed out on a desktop frame you see 2°
ticks (5° on a phone-width one); fully zoomed in, 1° ticks with 0.2° minors. The ruler never
becomes a comb.
Where it is wired
| Piece | Role |
|---|---|
util/cameras/aimGuide.ts | Pure geometry: tick step, tick positions, target projection, chip formatting |
stores/aimGuideStore.ts | The on/off choice, persisted in localStorage (aim-guide-storage) |
AimGuideToggle | The button in CameraControlButtons, next to the pan/zoom toggle; gold when on, like the Peak Finder toggle. Signed-in users only, on desktop and in the phone drawer; a signed-out visitor never sees the toggle or the reticle, even if the browser remembers it as on |
AimGuideOverlay | Presentational layer rendered by XYMouseTrackerOverlay for the 60° view |
goToPositionStore.preview | The typed destination the overlay previews while the Go to position form is open |
lib/featureFlags.ts — enable-aim-guide | Rollout gate; off by default, on for localhost |
XYMouseTrackerOverlay already owns everything the reticle needs — the image box it sizes
itself to, the camera's centre, the pointer's position in pixels and degrees, the pending
PTZ update and the zoom-box rectangle — so the overlay is fed from there and holds no state
of its own except the ghost's fade.
Guard rails
- The layer is
pointer-events: none: double-click, the zoom box and the locate click behave exactly as before. - It is not drawn in playback mode or on the 360° strip, and the hover layer never shows on
touch devices (
ViewportContext.canHover), so taps are never swallowed. - Every mark is white over a dark halo, no filled backgrounds, so it reads on sky, haze and night frames.
- Off means off: nothing is drawn, no chip, no ghost.