feat(globe): port to CeliumJS
This commit is contained in:
parent
ec03425067
commit
eb7698e034
51 changed files with 3521 additions and 1992 deletions
133
docs/superpowers/specs/2026-08-03-restricted-area-box-design.md
Normal file
133
docs/superpowers/specs/2026-08-03-restricted-area-box-design.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue