Skip to main content

Move Cameras Here

Dispatch gives a location far more often than a bearing. Until now the operator found each camera that might see it, opened it, and moved it. Move cameras here does that in one confirmation: pick a point, see who can look at it, move them together, and watch the frames land. Operators with control rights, behind enable-aim-cameras.

Why it lives on the tile view​

The location page is one camera. This action is about several, and the operator needs two things at once: a map to see who will move and how they converge, and a grid of frames to see that each one did. The tile view has both side by side, and its grid already follows the map. Aim mode narrows the grid to the plan's cameras — nearest first, wherever the map extent is — so the tiles the operator watches are exactly the cameras that will move. A plan started from a location page brings the operator to the tile view.

Where it can be started​

EntryPoint
Right-click / long-press / Get CoordinatesThe coordinates card gets one primary action, Move cameras here
An ALERT marker on the mapThe alert modal gets an aim button; alerts carry a real incident point
A located point (Locate on map chip)Move cameras here beside Copy PTZ and Go to
An AI detection (AI alert modal)Move cameras to this detection: the detection's own pan/tilt/zoom is cast through terrain to a point first — see the note below
AI hits are derived points

An AI hit on the map is the camera's arrow turned red; the detection record carries the camera's location, not the smoke's, and still frames carry no box coordinates. So the point is where the camera's aim met terrain when the detection fired, through the same geometry Locate uses, and the plan labels it approximate. Once the backend forwards the detection box, the cast goes through the box centre and the label goes away.

The plan​

Candidates are every PTZ camera within 25 miles of the point, nearest first (buildAimCandidates). For each: distance, true bearing, the camera's own pan and tilt for the point with its boresight correction undone (cameraPoseForDirection), the tilt from the target's ground elevation and the camera's eye height with earth curvature and refraction, and the zoom a half-mile framing would need (zoomForFrameWidth, used only when that mode is chosen). Cameras that cannot be moved are listed greyed with the reason: offline, no control rights, simulated.

Visibility is terrain line of sight (checkAimVisibility): the sight ray at the target's apparent altitude, marched over the terrain model until it meets ground. Clear when the first ground it meets is the target; blocked, with the ridge's distance, otherwise; unknown when terrain data did not answer inside four seconds. Checks run in parallel for the whole set.

Pre-check: clear, controllable cameras that are not patrolling and not already pointing within a degree of the target, nearest first, up to eight, and one per site: two cameras on one mast see the same thing, so the nearest is ticked and its sibling left listed. Everything else stays listed and can be ticked. Once the operator ticks or unticks anything, the plan stops changing the selection.

The list is grouped so thirty candidates read as one short list: Ready (open, with Select all), Visibility unknown, Blocked by terrain and Unavailable (offline, no control, simulated), the last two folded under a count. The grid shows the tiles in the same order.

Zoom is the operator's call, as a three-way choice above the options: Keep each camera's (the default: the move changes pan and tilt only, since a tight framing was the wrong first look for most fires), Set all to a level on a 1.0–10.0 slider in the zoom slider's own units, or Frame ½ mi at each camera's distance. Each row shows the zoom it will be sent when one is.

Options, on by default: skip cameras already there; one camera per site.

What the operator sees​

  • Map (AimMapLayer): a gold target ring; a sight line from every candidate to it, green when clear, grey dashed with the ridge marked when blocked, amber while checking or unknown; ticked cameras drawn heavier; blue while moving, green on arrival, red when skipped or failed. The map frames the point and the pre-checked cameras once.
  • Panel (AimPanel, over the map): the list, the options, the count, one button naming the count. Hovering a row lights the camera's tile and its line through the hover state the grid and map already share. On phones and landscape phones the same panel is a bottom sheet (AimPanelHost), opened when the plan starts and reopened from the grid's strip (Choose cameras / Progress); the map there is the mobile map drawer, and the tiles are the page.
  • Grid (AimScopeBar + CameraListComponent): narrows to the candidates with a strip naming the point and offering Exit. Candidate tiles stay mounted and in the live set so their poses and frames update.
  • Tiles (AimTileBadge + ring in CameraDetails): gold ring while selected, blue while moving, green when aimed, red when skipped; the badge says which.
  • Confirmation: the reason form operators already know, once, for the set (CameraMovementModal with a camera-list override). The message is prefilled with the point and the operator's name so every camera's activity log reads the same.
  • After: per-camera progress; a camera another operator holds comes back skipped · leased by J. Ortiz; a failed request offers Retry; a move with no pose report in 45 seconds reads unconfirmed. The summary stays until Done. Undo all sends every moved camera back to the pose it had.

Guard rails​

  • Access is one hook (useAimAccess): flag on, signed in, on the UCSD team (the user profile's Entra companyName or SQL agency carries "UCSD", via useAdvancedMovementAccess), control rights on at least one camera. Per-camera rights apply again inside the plan, and the PTZ request itself still needs Camera.Operate on the BFF, exactly as a double-click does.
  • Eight pre-checked, more only by hand, with a warning above eight.
  • Leases are honoured, not forced: the lease check runs per camera, a refusal is reported and never retried silently.
  • Patrolling cameras are flagged and not pre-checked.
  • The plan leaves on Done, Exit, Escape (before anything moves), when access is lost, and after ten idle minutes; it always clears its map graphics.
  • Arrival is read from the camera's reported pose (within 1.5°), which the stream sends for the subscribed tiles; the candidates are in the grid, so they are subscribed.

Pieces​

PieceRole
util/locate/aimPlan.tsCandidates, pose, frame zoom, terrain visibility with a budget, arrival, words
stores/aimStore.tsTarget, candidates, selection, results, options, idle timer input
hooks/useAimPlan.ts / hooks/useAimMoves.tsResolves the plan; sends the moves, watches arrivals, undoes
hooks/useStartAim.tsstartAim(target) for every entry point
components/organisms/AimCameras/*Panel, panel host (portal over the map, idle exit, redirect), scope bar, tile badge, map layer
util/peakfinder/terrainRaycast.tsterrainElevationAt, apparentAltitudeDeg exported for the plan
lib/featureFlags.ts — enable-aim-camerasRollout gate; off by default, on for localhost