mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
docs(cctv): describe the ground-height data plainly (#443)
Follow-up to #433.
This commit is contained in:
1 parent
12790b36c6
commit
41c416e329
8 files changed
+36
-50
No files matched your search
+3
-3
@@ -95,9 +95,9 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
|
|||||||
rigidly by the largest clearance deficit over a 3×3 grid of support points
|
rigidly by the largest clearance deficit over a 3×3 grid of support points
|
||||||
against the ground under each (the ground at the mount where nothing finer is
|
against the ground under each (the ground at the mount where nothing finer is
|
||||||
known), and the client honours pack ranges instead of inflating them to 220 m.
|
known), and the client honours pack ranges instead of inflating them to 220 m.
|
||||||
`src/data/local_data/cctv_ground_heights/` ships Google 3D Tiles ground heights under
|
`src/data/local_data/cctv_ground_heights/` ships precomputed ground heights under
|
||||||
every camera's mount and plane footprint (3,445 of 3,446 cameras), produced by
|
every camera's mount and plane footprint (3,445 of 3,446 cameras), aligned to work
|
||||||
`scripts/precompute-cctv-heights.mjs`; cameras with shipped heights are placed
|
with Google Photorealistic 3D Tiles; cameras with shipped heights are placed
|
||||||
with zero runtime sampling, and the rest resolve the ground under their plane
|
with zero runtime sampling, and the rest resolve the ground under their plane
|
||||||
from the Re:Earth DEM on activation. The footprint lift is capped at 60 m
|
from the Re:Earth DEM on activation. The footprint lift is capped at 60 m
|
||||||
above the mount-based lift so a tower under a far edge cannot launch the plane.
|
above the mount-based lift so a tower under a far edge cannot launch the plane.
|
||||||
|
|||||||
+5
-9
@@ -14,9 +14,9 @@ How to read this:
|
|||||||
## Live sources (fetched at runtime — not stored in this repo)
|
## Live sources (fetched at runtime — not stored in this repo)
|
||||||
|
|
||||||
| Source | Used for | License / terms | Attribution |
|
| Source | Used for | License / terms | Attribution |
|
||||||
|--------|----------|-----------------|-------------|
|
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Google Map Tiles API** (Photorealistic 3D Tiles) + Places/Geocoding | The 3D globe, voice scene context, and on-demand nearby installation search | Google Maps Platform ToS (proprietary, your own key + billing) | "Google" / "Google Maps" logo — **shown in-app**, required |
|
| **Google Map Tiles API** (Photorealistic 3D Tiles) + Places/Geocoding | The 3D globe, voice scene context, and on-demand nearby installation search | Google Maps Platform ToS (proprietary, your own key + billing) | "Google" / "Google Maps" logo — **shown in-app**, required |
|
||||||
| **OpenSky Network** | Primary worldwide live-flight snapshot | Non-commercial research/education license | Schäfer et al., *"Bringing Up OpenSky"*, IPSN 2014 + opensky-network.org |
|
| **OpenSky Network** | Primary worldwide live-flight snapshot | Non-commercial research/education license | Schäfer et al., _"Bringing Up OpenSky"_, IPSN 2014 + opensky-network.org |
|
||||||
| **adsb.lol point API** | Bounded live-flight fallback when OpenSky has no usable snapshot | ODbL 1.0 | adsb.lol contributors; `api.adsb.lol/v2/lat/{lat}/lon/{lon}/dist/{radius}` |
|
| **adsb.lol point API** | Bounded live-flight fallback when OpenSky has no usable snapshot | ODbL 1.0 | adsb.lol contributors; `api.adsb.lol/v2/lat/{lat}/lon/{lon}/dist/{radius}` |
|
||||||
| **adsb.lol** | Military flights + aircraft traces | ODbL 1.0 | "adsb.lol" (ODbL) |
|
| **adsb.lol** | Military flights + aircraft traces | ODbL 1.0 | "adsb.lol" (ODbL) |
|
||||||
| **AISStream.io** | Live vessels (AIS) | Free, beta, no formal ToS; AIS is a public broadcast | "AISStream.io" (courtesy) |
|
| **AISStream.io** | Live vessels (AIS) | Free, beta, no formal ToS; AIS is a public broadcast | "AISStream.io" (courtesy) |
|
||||||
@@ -76,17 +76,13 @@ How to read this:
|
|||||||
Static datasets shipped in the repo for an out-of-the-box experience. **None are MIT** — each keeps its own license (see the carve-out in [LICENSE](LICENSE)). Each folder also has its own provenance README.
|
Static datasets shipped in the repo for an out-of-the-box experience. **None are MIT** — each keeps its own license (see the carve-out in [LICENSE](LICENSE)). Each folder also has its own provenance README.
|
||||||
|
|
||||||
| Dataset | Folder | License | Commercial use? | Attribution |
|
| Dataset | Folder | License | Commercial use? | Attribution |
|
||||||
|---------|--------|---------|-----------------|-------------|
|
| --------------------------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------- |
|
||||||
| **Datacenters** (~4.3K) | `datacenters/` | **ODbL 1.0** (OpenStreetMap extract) | ✅ (attribution + share-alike on data) | "© OpenStreetMap contributors" |
|
| **Datacenters** (~4.3K) | `datacenters/` | **ODbL 1.0** (OpenStreetMap extract) | ✅ (attribution + share-alike on data) | "© OpenStreetMap contributors" |
|
||||||
| **Dams** (704) | `dams/` | **ODbL 1.0** (OpenInfraMap / OSM extract) | ✅ (attribution + share-alike on data) | "© OpenStreetMap contributors" (+ Open Infrastructure Map) |
|
| **Dams** (704) | `dams/` | **ODbL 1.0** (OpenInfraMap / OSM extract) | ✅ (attribution + share-alike on data) | "© OpenStreetMap contributors" (+ Open Infrastructure Map) |
|
||||||
| **TeleGeography Submarine Cable Map** (712 cables + 1,917 landing points) | `telegeography_submarine_cables/` | **CC BY-NC-SA 3.0** | ❌ **NonCommercial — remove for commercial use** | "© TeleGeography — submarinecablemap.com" |
|
| **TeleGeography Submarine Cable Map** (712 cables + 1,917 landing points) | `telegeography_submarine_cables/` | **CC BY-NC-SA 3.0** | ❌ **NonCommercial — remove for commercial use** | "© TeleGeography — submarinecablemap.com" |
|
||||||
| **Natural Earth physical regions** (1,046 land + 292 marine named polygons) | `natural_earth/` | **Public domain** | ✅ (no restrictions) | "Made with Natural Earth" (courtesy credit — not legally required) |
|
| **Natural Earth physical regions** (1,046 land + 292 marine named polygons) | `natural_earth/` | **Public domain** | ✅ (no restrictions) | "Made with Natural Earth" (courtesy credit — not legally required) |
|
||||||
| **DataSF Analysis Neighborhoods** (41 SF neighborhood polygons) | `neighborhoods/` | **PDDL 1.0** (public domain) | ✅ (no restrictions) | "City & County of San Francisco — DataSF" (courtesy — not legally required) |
|
| **DataSF Analysis Neighborhoods** (41 SF neighborhood polygons) | `neighborhoods/` | **PDDL 1.0** (public domain) | ✅ (no restrictions) | "City & County of San Francisco — DataSF" (courtesy — not legally required) |
|
||||||
| **CCTV ground heights** (3,445 cameras, 34,444 sampled points) | `cctv_ground_heights/` | Derived placement data: heights sampled from the rendered Google Photorealistic 3D Tiles surface (provenance in the folder README) | Same terms as the map it was sampled from | None beyond the map's own Google credit |
|
| **CCTV ground heights** (3,445 cameras) | `cctv_ground_heights/` | Precomputed camera placement heights, aligned to work with Google Photorealistic 3D Tiles (folder README) | — | — |
|
||||||
|
|
||||||
### CCTV ground heights are derived placement data
|
|
||||||
|
|
||||||
`cctv_ground_heights/` holds, per camera in the served CCTV catalog, the WGS84 ellipsoidal ground height Google Photorealistic 3D Tiles report at the camera's position and under the nine support points of its monitor plane, sampled from a running app by `scripts/precompute-cctv-heights.mjs`. It is numeric placement data (a pin dropped on the rendered surface), not imagery or tiles, and it exists so the client can place cameras and planes with zero runtime sampling. Each entry is keyed to the pose it was sampled for and is regenerated whenever a pack's poses change; the folder README documents the schema and the regeneration command. It lives with the other bundled datasets so any consumer that renders these cameras on the same surface reads the same file, under the same terms as the map it came from.
|
|
||||||
|
|
||||||
### ⚠️ TeleGeography is bundled but NonCommercial
|
### ⚠️ TeleGeography is bundled but NonCommercial
|
||||||
|
|
||||||
@@ -98,7 +94,7 @@ The richer structured dataset is licensed separately/commercially by TeleGeograp
|
|||||||
|
|
||||||
### ODbL share-alike (datacenters, dams)
|
### ODbL share-alike (datacenters, dams)
|
||||||
|
|
||||||
The OSM-derived datasets are under the **Open Database License**. ODbL's share-alike applies to the **data / derived database, not this MIT-licensed code** — the two coexist (exactly how Open Infrastructure Map ships: MIT software + ODbL data). If you publicly distribute a *modified* version of these databases, you must offer it under ODbL. Keep the "© OpenStreetMap contributors" notice (link: https://www.openstreetmap.org/copyright).
|
The OSM-derived datasets are under the **Open Database License**. ODbL's share-alike applies to the **data / derived database, not this MIT-licensed code** — the two coexist (exactly how Open Infrastructure Map ships: MIT software + ODbL data). If you publicly distribute a _modified_ version of these databases, you must offer it under ODbL. Keep the "© OpenStreetMap contributors" notice (link: https://www.openstreetmap.org/copyright).
|
||||||
|
|
||||||
### NASA FIRMS acknowledgement
|
### NASA FIRMS acknowledgement
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export function mergeSidecar(existing, updates, generatedAt) {
|
|||||||
const cameras = { ...existing?.cameras, ...updates };
|
const cameras = { ...existing?.cameras, ...updates };
|
||||||
return {
|
return {
|
||||||
schemaVersion: 1,
|
schemaVersion: 1,
|
||||||
provider: 'google-photorealistic-3d',
|
provider: 'google-3d-tiles',
|
||||||
heightReference: 'WGS84-ellipsoid',
|
heightReference: 'WGS84-ellipsoid',
|
||||||
generatedAt,
|
generatedAt,
|
||||||
cameras: Object.fromEntries(
|
cameras: Object.fromEntries(
|
||||||
@@ -283,7 +283,7 @@ async function loadSidecar(output) {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
existing?.schemaVersion !== 1 ||
|
existing?.schemaVersion !== 1 ||
|
||||||
existing.provider !== 'google-photorealistic-3d' ||
|
existing.provider !== 'google-3d-tiles' ||
|
||||||
existing.heightReference !== 'WGS84-ellipsoid' ||
|
existing.heightReference !== 'WGS84-ellipsoid' ||
|
||||||
!existing.cameras ||
|
!existing.cameras ||
|
||||||
typeof existing.cameras !== 'object' ||
|
typeof existing.cameras !== 'object' ||
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ export const DEFAULT_GROUND_HEIGHTS_FILE =
|
|||||||
let _cache = null;
|
let _cache = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the precomputed ground-height sidecar (Google mesh heights under each
|
* Load the precomputed ground-height sidecar (heights under each camera's mount
|
||||||
* camera's mount and monitor-plane support points). Cached by file mtime so a
|
* and monitor-plane support points, aligned to the Google 3D Tiles surface). Cached by file mtime so a
|
||||||
* catalog refresh re-reads only when the file changed. Missing or malformed
|
* catalog refresh re-reads only when the file changed. Missing or malformed
|
||||||
* files mean "no shipped heights", never an error.
|
* files mean "no shipped heights", never an error.
|
||||||
* @param {string} sourceRoot
|
* @param {string} sourceRoot
|
||||||
|
|||||||
@@ -1,37 +1,29 @@
|
|||||||
# CCTV ground heights
|
# CCTV ground heights
|
||||||
|
|
||||||
Precomputed ground height under every camera in the served CCTV catalog and
|
Precomputed ground height under every camera in the served CCTV catalog and
|
||||||
under the nine support points of its monitor plane, so the client can place a
|
under the nine support points of its monitor plane, aligned to work with
|
||||||
camera and its plane with no runtime sampling. Shared placement data: any
|
Google Photorealistic 3D Tiles. Heights are WGS84 ellipsoidal metres. The
|
||||||
consumer that renders these cameras on the same photorealistic surface can
|
client places a camera and its plane from this file with no runtime sampling.
|
||||||
reuse it.
|
|
||||||
|
|
||||||
Source surface: Google Photorealistic 3D Tiles, sampled at maximum detail with
|
|
||||||
CesiumJS `scene.sampleHeightMostDetailed` from a running God's Eye View
|
|
||||||
(scripts/precompute-cctv-heights.mjs). Heights are WGS84 **ellipsoidal**
|
|
||||||
metres; no geoid conversion is needed or applied. This is placement data
|
|
||||||
derived from the rendered surface, not imagery.
|
|
||||||
|
|
||||||
Runtime output: `cctv_ground_heights.json`
|
Runtime output: `cctv_ground_heights.json`
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"provider": "google-photorealistic-3d",
|
"provider": "google-3d-tiles",
|
||||||
"heightReference": "WGS84-ellipsoid",
|
"heightReference": "WGS84-ellipsoid",
|
||||||
"generatedAt": "<ISO>",
|
"generatedAt": "<ISO>",
|
||||||
"cameras": {
|
"cameras": {
|
||||||
"<camera id>": {
|
"<camera id>": {
|
||||||
"poseHash": "p1-…", // hash of the served pose the samples describe
|
"poseHash": "p1-…", // hash of the served pose the entry describes
|
||||||
"status": "ok" | "miss",
|
"status": "ok" | "miss",
|
||||||
"mountGroundM": <number>, // ground under the camera position (absent on a miss)
|
"mountGroundM": <number>, // ground under the camera position (absent on a miss)
|
||||||
"supports": { // ground under the plane's 3×3 support grid; a value is
|
"supports": { // ground under the plane's 3×3 support grid; null where
|
||||||
// null where that point had no sample (absent on a miss)
|
"bl","bm","br", // a point has no value (absent on a miss)
|
||||||
"bl","bm","br", // bottom row (left, middle, right)
|
"ml","mc","mr",
|
||||||
"ml","mc","mr", // middle row
|
"tl","tm","tr"
|
||||||
"tl","tm","tr" // top row
|
|
||||||
},
|
},
|
||||||
"misses": [<keys with no sample: "mount" and/or support keys>],
|
"misses": [<keys with no value>],
|
||||||
"sampledAt": "<ISO>",
|
"sampledAt": "<ISO>",
|
||||||
"attempts": <n>
|
"attempts": <n>
|
||||||
}
|
}
|
||||||
@@ -40,19 +32,15 @@ Runtime output: `cctv_ground_heights.json`
|
|||||||
```
|
```
|
||||||
|
|
||||||
Support point positions are defined by `src/data/cctvFootprint.js`
|
Support point positions are defined by `src/data/cctvFootprint.js`
|
||||||
(`planeSupportPoints`), which the client, the server join
|
(`planeSupportPoints`), shared with the server join
|
||||||
(`server/providers/cctv/groundHeights.js`) and the precompute all share. An
|
(`server/providers/cctv/groundHeights.js`). An entry is used only while the
|
||||||
entry is used only while the camera's served pose still hashes to
|
camera's served pose still hashes to `poseHash`; a camera whose feed moved it,
|
||||||
`poseHash`; a camera whose feed moved it, or whose pose a user edited, falls
|
or whose pose a user edited, falls back to runtime placement from the terrain
|
||||||
back to runtime placement from the Re:Earth DEM.
|
proxy.
|
||||||
|
|
||||||
Regenerate (resumable; only cameras whose pose changed or whose entry is
|
Regenerate against a running app (resumable; only cameras whose pose changed
|
||||||
incomplete are re-sampled):
|
or whose entry is incomplete are redone):
|
||||||
|
|
||||||
```
|
```
|
||||||
GEV_BASE=http://localhost:4173 node scripts/precompute-cctv-heights.mjs
|
GEV_BASE=http://localhost:4173 node scripts/precompute-cctv-heights.mjs
|
||||||
```
|
```
|
||||||
|
|
||||||
Coverage at generation (2026-09-14): 3,445 of 3,446 cameras `ok` (six of them
|
|
||||||
with one or more null supports), 34,444 sampled heights, 43 minutes on a real
|
|
||||||
GPU. Regenerate whenever a pack changes its poses or a new pack lands.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"provider": "google-photorealistic-3d",
|
"provider": "google-3d-tiles",
|
||||||
"heightReference": "WGS84-ellipsoid",
|
"heightReference": "WGS84-ellipsoid",
|
||||||
"generatedAt": "2026-09-14T01:42:02.486Z",
|
"generatedAt": "2026-09-14T01:42:02.486Z",
|
||||||
"cameras": {
|
"cameras": {
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
|||||||
* CURRENT pose, or null. Shipped precompute samples (`camera.groundHeights`)
|
* CURRENT pose, or null. Shipped precompute samples (`camera.groundHeights`)
|
||||||
* were taken for the nominal pose; they apply only while the pose hash still
|
* were taken for the nominal pose; they apply only while the pose hash still
|
||||||
* matches, so a calibrated (edited) camera falls back to the mount ground
|
* matches, so a calibrated (edited) camera falls back to the mount ground
|
||||||
* until its own footprint is resolved. Photoreal regime only: the samples
|
* until its own footprint is resolved. Photoreal regime only: the values
|
||||||
* are Google mesh heights and would be wrong against a globe DEM.
|
* are aligned to the Google 3D Tiles surface and would be wrong against a
|
||||||
|
* globe DEM.
|
||||||
* @param {Object} record
|
* @param {Object} record
|
||||||
* @returns {Record<string, number>|null}
|
* @returns {Record<string, number>|null}
|
||||||
*/
|
*/
|
||||||
@@ -72,7 +73,8 @@ export function createGeometry({ state: layerState, services, parts, source }) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* True when the record carries shipped precompute samples for its CURRENT
|
* True when the record carries shipped precompute samples for its CURRENT
|
||||||
* pose and the scene is rendering the photoreal mesh they were taken from.
|
* pose and the scene is rendering the Google 3D Tiles surface they are
|
||||||
|
* aligned to.
|
||||||
* @param {Object} record
|
* @param {Object} record
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ test('sidecar merging sorts ids, preserves unselected entries and replaces stale
|
|||||||
assert.deepEqual(result.cameras.a, { status: 'miss' });
|
assert.deepEqual(result.cameras.a, { status: 'miss' });
|
||||||
assert.equal(old.cameras.a.mountGroundM, 20);
|
assert.equal(old.cameras.a.mountGroundM, 20);
|
||||||
assert.equal(result.schemaVersion, 1);
|
assert.equal(result.schemaVersion, 1);
|
||||||
assert.equal(result.provider, 'google-photorealistic-3d');
|
assert.equal(result.provider, 'google-3d-tiles');
|
||||||
assert.equal(result.heightReference, 'WGS84-ellipsoid');
|
assert.equal(result.heightReference, 'WGS84-ellipsoid');
|
||||||
assert.equal(result.generatedAt, sampledAt);
|
assert.equal(result.generatedAt, sampledAt);
|
||||||
assert.deepEqual(mergeSidecar(null, {}, sampledAt).cameras, {});
|
assert.deepEqual(mergeSidecar(null, {}, sampledAt).cameras, {});
|
||||||
|
|||||||
Reference in new issue
Block a user