leaflet_svelte/docs/superpowers/specs/2026-08-03-restricted-area-box-design.md
2026-08-03 22:12:32 +09:00

133 lines
6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Restricted-area box: rectangle in kilometres, not in degrees
**Goal:** the no-fly area filed with the regulator must be a usable rectangle at
every latitude, the poles included.
## Problem
The area was a rectangle in latitude/longitude, padded by a margin in kilometres.
It cannot be made to work near a pole, and the failure is geometric, not a bug:
> Every meridian passes through the pole, so any lat/lon rectangle that contains
> a pole spans all 360 degrees of longitude.
Measured on a real launch from 89.99 N, 0 E (GFS 2026-08-03 06Z, 5 km margin):
| Form | Area |
| --- | --- |
| lat/lon rectangle (south 88.93, north 90, west -180, east 180) | **44 200 km²** |
| corridor actually flown, 13.7 × 123.4 km | **1 695 km²** |
A 26× over-claim. The balloon also sweeps 79 degrees of longitude while
descending one degree of latitude, because near a pole a short displacement
crosses many meridians — so even without the pole clamp the degree form is wide.
There is no latitude threshold below which the degree form is safe. Above it the
box contains the pole and balloons; below it the box fails to contain the
trajectory. The form itself is what fails.
Rendering made this visible: at the pole the box drew as a circle following a
parallel. That was a correct picture of a wrong shape.
## Decision
Define the area as a **rectangle in kilometres**, axis-aligned to east/north at
its own centre, filed as four lat/lon corners joined by great circles.
Rejected alternatives:
- **Rectangle in degrees plus a circle near the pole.** A circle (centre +
radius) is the standard ICAO NOTAM form, but this needs two filing forms and a
latitude threshold to switch between them — the crutch this replaces.
- **Circle everywhere.** One form, always filable, but a circle around a long
corridor claims far more area than a rectangle.
- **Rotate the rectangle to the track's heading.** Tighter for a diagonal track,
up to 2×, but it puts an azimuth in the filing and the corners stop reading as
north/south/east/west. Not taken; revisit only if area pressure appears.
## Design
```ts
interface BoundingBox {
corners: [LatLngTuple, LatLngTuple, LatLngTuple, LatLngTuple]; // NW, NE, SE, SW
centre: LatLngTuple;
widthKm: number;
heightKm: number;
}
```
`computeBoundingBox(path, marginKm)` keeps its signature.
1. **Local frame.** Earth-centred radial/east/north unit vectors at a point. All
three are unit length and orthogonal at every latitude including the poles, so
nothing divides by `cos(latitude)`. Same construction as the predictor's
integrator, `internal/numerics/spherical.go`, for the same reason.
2. **Project** each path point to kilometres east/north by azimuthal
equidistant: exact in distance from the origin at any range.
3. **Two passes.** The first frame, on the track's mean direction, only locates
the box centre; the second frames on that centre, which makes the corners
symmetric about it and keeps projection error smallest where the corners are.
4. **Pad** the half-extents by the margin and unproject the four corners.
### Margin is a floor
A great-circle edge bows **away** from the frame origin relative to its chord in
this projection: `y_mid = hy · (1 + ρ² sin²β / 3)`, positive. Check by
inspection — the equator in an azimuthal-equidistant projection centred on the
pole is a circle at `R·π/2`, while the chord between two of its points 90° apart
would sag to `0.707·R·π/2`. So the filed quad **contains** the projected
rectangle and the requested clearance is never eaten. No correction is applied.
(The first draft of this design asserted the opposite sign and specified a
sagitta correction. It would have inflated the area for no reason.)
### The box may cross a pole, and must
A launch 1.1 km from the pole with a 5 km margin needs coverage 3.9 km past the
pole. The north edge therefore passes over it and comes down the far side, which
puts the two north corners ~180° apart in longitude — for the measured case,
-43.87 and -158.44. Correct, and unreadable from the corners alone, so the panel
also reports `width × height @ centre`.
### Drawing
`boundingBoxRing` samples 16 points per edge along the great circle (slerp
between corner vectors). The drawn shape is then the filed shape and does not
depend on the renderer's interpolation mode, and no single segment is long enough
to land degenerate on the antimeridian — which is what previously stopped
Cesium's render loop with "All attribute lists must have the same number of
attributes" in its `splitLongitude` pass.
## Removed
`KM_PER_DEG_LAT` and the `cos(latitude)` margin division, the latitude clamps,
the `circumpolar` branch, `LineOptions.arc`, and the `ArcType.RHUMB` branch in
`cesium-scene.ts` — no parallels remain in the box, so rhumb lines have no
remaining caller. Net less code, and no branch on latitude anywhere.
## Verification
`tests/unit/boundingBox.spec.ts` runs in Node against the pure module:
- every trajectory point clears all four filed edges by the full margin, measured
as distance to the edge's great circle — at 52.2 N over 500 km (where a
wrong-signed bow would have been 5.1 km, the whole margin) and on the real
polar track;
- the polar box is under 3 000 km² where the degree form gave 44 200;
- a meridional track of the same length in kilometres gives the same box at
52.2 N and at 89.99 N — the test that fails if any latitude branch returns;
- the drawn ring closes and no segment spans 90° of longitude.
`tests/e2e/bbox.spec.ts` covers what only a browser answers: `scene.renderError`
stays empty while a polar box is drawn, and the panel reports a corridor-sized
area rather than a cap.
Measured after the change: polar 13.3 × 123.4 km @ 89.4835, 78.3014; mid-latitude
61.4 × 68.9 km @ 52.4654, 0.4651, with the northernmost track point clearing the
north edge by 4.98 km against 5.0 asked.
## Not addressed
- **WGS84.** Everything here uses a spherical earth, R = 6371 km, matching the
predictor. The ellipsoid remains deferred there too.
- **Altitude.** The box is the lat/lon footprint; it does not encode a ceiling.