Skip to main content

Go to Position

Double-clicking the 60° image is the way to move a camera, but it has no answer to "look at 245°", "set it to 132.4 / −2 / 3.5" or "a touch more to the left". Until now the only path was to double-click repeatedly and watch the heading tick. Go to position adds a typed move: three fields, one Move button, the same pipeline as a double-click underneath.

Operators with control rights only, behind enable-go-to-position. No new button in the control column.

Where it lives​

  • The heading pill. The pan readout on the top edge of the 60° image is already the number operators care about. For operators it is now a button: hovering it shows a pencil, clicking it drops the form down from the pill. Viewers see the plain readout exactly as before.
  • The "…" menu has a Go to position… row — the discoverable copy for people who look there first (it is where Adjust Focus and Adjust Brightness live).
  • The mobile more-options drawer has a Go to position row that opens a bottom sheet. The drawer has no PTZ controls at all, so on a phone this is the only typed-move entry.

All three open the same thing through goToPositionStore.isOpen; GoToPositionHost renders the popover on desktop and the sheet on phones and landscape phones.

The form​

Go to / Move by. Two modes, remembered for the session.

  • Go to prefills the three fields with the current pose, so the operator changes only what they mean to. A blank field keeps the current value. A value starting with + is read as a step even here — + never means an absolute value, so it is safe to.
  • Move by starts blank; every number is a signed step from where the camera is: -5 turns left five degrees, +2 tilts up two, +1 zooms in one slider unit.

Units match what operators already see. Pan is absolute degrees, shown 0–360 like the tick (typing -30 is fine; it becomes 330.00). Tilt is degrees, −90 to 20. Zoom is in the slider's units — 0.0 to 10.0, i.e. VAPIX ÷ 1000 — because that is the only zoom number anyone sees; the request carries the VAPIX integer 1–9999. Typing 0 or 10 lands on the real limits.

The summary line names the destination before anything moves: → 144.70° · 3.90° · 3.5× · ≈ 18° wide (the field of view comes from the camera model's optics table, so the zoom number means something), or in Move by mode turn right 5.00° · tilt up 2.00° → 149.70° · 5.90°. A field that cannot be sent is highlighted with the reason (Tilt is −90.00° to 20.00°, That would zoom to 11.0×. Zoom is 0.0 to 10.0), and Move stays disabled while the form resolves to "already there" or the camera is already moving.

Keys. Enter moves, Escape dismisses, Tab walks the fields.

What a move does​

Move writes goToPositionTarget to cameraMovementStore. XYMouseTrackerOverlay (the 'camera' instance only) picks it up and sends it through moveCamera — the lease check, the reason/message modal when one is needed, pendingPTZUpdate, "Camera Moving…", the activity log — exactly as a double-click. Zoom is null when it was not changed, which the request already treats as "keep the current zoom".

From Locate on map​

Locate on map already turns a click on the smoke into a bearing and a point. Its result card — and the Peak Finder overlay's own Locate card, and the point's chip on the map — now print this camera's pan and tilt for that point (camera pan 213.40° · tilt −1.25°) and offer two buttons (PtzActions):

  • Copy PTZ puts one plain line on the clipboard: 213.40, -1.25. Paste it into any field of this form and it spreads across pan and tilt (a third number is zoom in slider units), so the numbers travel to another operator or another screen as one paste, not three.
  • Go to (operators with control rights, while that camera is selected) leaves locate mode and opens this form prefilled.

The copied report carries the same pan/tilt line.

The pan/tilt is the inverse of the boresight correction Locate applies to the camera's aim (cameraPoseForDirection in util/locate/sightline.ts): the sight line is cast in true bearings, so a camera with a solved mount error needs its own numbers back, not the bearing. Without a solve the two are identical.

The aim guide previews it​

While the form is open and holds a valid, non-trivial destination, goToPositionStore.preview carries it, and the aim guide (when it is on) draws it before the move is sent: a dashed gold ring where the destination lands in the frame with a go to 144.70° · 3.90° chip, or, when the destination is outside the frame, a chevron on the frame edge in the direction of the move with turn right 85.00° · tilt up 12.00° → … along the bottom. A wrong digit is visible before the camera moves.

Pieces​

PieceRole
util/cameras/goToPosition.tsPure rules: parsing, absolute/relative resolution, limits, zoom unit conversion, words
stores/goToPositionStore.tsisOpen, mode, preview (session only)
hooks/useGoToPositionAccess.tsThe one access rule every entry point and the host share
GoToPositionFormThe shared body (popover and sheet)
GoToPositionPopover / GoToPositionSheetDesktop drop-down under the heading pill / mobile bottom sheet
GoToPositionHostPicks the surface; rendered by CameraImageWrapper beside the other photo overlays
MeasurementTickOverlayThe heading pill becomes the button for operators
cameraMovementStore.goToPositionTargetThe hand-off to the movement overlay
lib/featureFlags.ts — enable-go-to-positionRollout gate; off by default, on for localhost

Guard rails​

  • Access is one hook: flag on, signed in, on the UCSD team (the user profile's Entra companyName or SQL agency carries "UCSD", via useAdvancedMovementAccess), canControlCamera, not in playback, movement slider in its normal zoom mode. Losing access while the form is open closes it.
  • Only the 'camera' overlay instance sends the target, so the 360 overlay mounted alongside never doubles the move.
  • The popover swallows its own double-clicks so the image's handler never sees them.
  • The draft is seeded at open and on a mode switch; it does not chase the live pose, or a frame landing mid-typing would overwrite what is being entered.
  • No per-camera pan/tilt limits exist in the data today. Tilt uses the same ceiling the drag-pan code uses (20°) and a −90° floor; pan is unbounded and folded, as Axis wraps it.