mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-13 06:42:23 +00:00
Add the Data Library: live regional datasets, tiles and packs
DATA LAYERS keeps its curated core layers; regional and niche sources live in the Data Library, opened from Browse data library at the foot of the panel. Each entry is a JSON manifest in src/library/entries/ pointing at a live upstream (query API, provider-hosted file or tile endpoint), so nothing is bundled and a self-hosted instance hosts no data. Sources without CORS go through one allowlisted /api/library route. Kinds: dataset (GeoJSON, CSV, JSON records, ArcGIS, partitioned GeoParquet), tiles (XYZ, WMS/ArcGIS bbox export, terrarium relief) and pack. Selections persist and travel in share links as lib=, separate from the core layer tokens. Library rows get Zoom and Remove icon chips that work while the layer is off, rendered through RailChips. Strings are translated in every locale.
This commit is contained in:
1 parent
10a788db78
commit
db80d82b8d
182 files changed
+15805
-44
No files matched your search
@@ -384,6 +384,206 @@ provider registration rejects compositions that cannot fit rather than truncatin
|
||||
identities. A malformed or duplicate entry invalidates the complete custom-switch
|
||||
field. Existing whole-link size limits still apply.
|
||||
|
||||
## Data Library — October 21, 2026
|
||||
|
||||
DATA LAYERS keeps its curated core layers; everything else lives in the Data
|
||||
Library, opened from **Browse data library** at the foot of the panel. An entry
|
||||
is a manifest in `src/library/entries/` that points at a live upstream its
|
||||
provider hosts — a query API, a provider-hosted static file or a tile endpoint.
|
||||
Nothing an entry describes is bundled, so a self-hosted instance serves every
|
||||
entry without hosting data. Manifests are validated strictly when
|
||||
`src/library/catalog.js` loads (unknown fields are errors), so an invalid entry
|
||||
fails the unit suite. Authoring guide: [DATA-LIBRARY.md](DATA-LIBRARY.md).
|
||||
|
||||
Three kinds ship. A `dataset` (GeoJSON, GeoJSON Lines, CSV with lat/lon
|
||||
columns, a JSON record list read from `source.records` with named or guessed
|
||||
lat/lon fields, or a paged ArcGIS FeatureServer query) renders through the same
|
||||
GeoJSON layer as Datacenters and Dams, with the manifest's label, up to two
|
||||
card fields and an optional `rank` field that orders a capped set and feeds the
|
||||
layer's level-of-detail priority (0–600, below the named/unnamed gap). A capped
|
||||
dataset reads PARTIAL. A dataset with `source.view` is view-bounded,
|
||||
for layers too large to load whole (FEMA USA Structures: ~135 million
|
||||
footprints): below `view.maxAltitudeM` it queries ArcGIS for what intersects a
|
||||
window around the point the camera looks at (half the camera height each way,
|
||||
330 m – 5.5 km, clipped to the visible rectangle), one page per view after the
|
||||
camera settles, replacing the previous set only when the new one is ready and
|
||||
aborting a superseded request; above it the row reads "zoom in to load this
|
||||
layer" and nothing is fetched. Footprints are ground-clamped polygons (draped on
|
||||
Google 3D tiles too), without outlines. Zoom to layer lands straight above the
|
||||
manifest's `view.sample` area at 60 % of the altitude limit. View-bounded
|
||||
entries are ArcGIS or GeoParquet, and browser-direct, in this build.
|
||||
|
||||
`geoparquet` datasets are read in the browser with `hyparquet` (with `fzstd`
|
||||
for ZSTD), imported lazily on first use; only the columns an entry uses are
|
||||
decoded, and WKB geometry arrives as GeoJSON. A partitioned set
|
||||
(`source.partitions`: an S3 ListObjectsV2 `listUrl` and a `keyBase`, both on the
|
||||
source host) must be view-bounded. Its index — each file's GeoParquet footer
|
||||
bbox, read with one range request per file — comes from `/api/library/index/<id>`
|
||||
(built once per `cacheSeconds` and shared), or is built in the browser and kept
|
||||
in `gev:library:index:<id>` on a static build. Footer bboxes can be stretched
|
||||
by stray geocodes, so candidates are ranked by how much of each file's bbox
|
||||
the view covers, read in parallel batches of four (up to eight per view)
|
||||
until a batch has features in view, and each file read once teaches its real
|
||||
extent (the box holding 98 % of its points, `gev:library:extent:<id>`), so
|
||||
later views skip files that do not cover them. The last four decoded files
|
||||
stay in memory. `display.colorBy` colours each feature by a field's value (a
|
||||
dotted path such as `categories.0.title` is flattened into the feature's
|
||||
`__gevColorKey`): view-bounded layers draw 6 px dots or filled polygons, other
|
||||
datasets keep the GeoJSON layer's stems, cards and level of detail with a
|
||||
per-feature colour (`featureColor` in `localGeojsonCore.js`). Values sharing a
|
||||
`colorBy.labels` label form one legend group (coal, gas and oil are "Fossil");
|
||||
values the manifest does not colour draw in `display.color` and count as
|
||||
Other. The row legend lists each group with its count (in view, or in the
|
||||
loaded set), one item per line as Fire Perimeters' does. Status-like fields
|
||||
(GDACS and PortWatch alert levels, NWS severity, bridge condition) use the
|
||||
fixed status palette (good #0ca30c, warning #fab219, serious #ec835a,
|
||||
critical #d03b3b); categorical ones (fuel, EONET category, building
|
||||
occupancy) use at most four hues that pass all-pairs colour-vision checks on
|
||||
the dark globe, plus a neutral Other. Tile layers carry a static
|
||||
`display.legend` copied from the provider's own scale (GBIF classic.poly,
|
||||
JRC occurrence, GIBS GPW, Macrostrat's ICS period colours); EMODnet publishes
|
||||
no machine-readable scale and has none. Aerial photo layers (PDOK, IGN, GSI
|
||||
photos, swisstopo SWISSIMAGE) set `display.outline`: their covered countries'
|
||||
borders draw as a thin white ground-clamped line, so the imagery's edge is
|
||||
plain, with a "Coverage outline" legend item. Borders come from Natural
|
||||
Earth's 1:50m admin-0 countries (public domain, fetched live once per page and
|
||||
credited); `countries` draws every part (Japan's islands, Liechtenstein beside
|
||||
Switzerland), `coverage` only the parts inside `coverage.bbox` (the European
|
||||
Netherlands, metropolitan France). A failed border fetch costs only the
|
||||
outline. The same layers set `display.aerial`: they drape above every other
|
||||
imagery layer and raise themselves again whenever another drape is added, so a
|
||||
basemap or weather overlay switched on later goes underneath. Each is
|
||||
`source.bounded` (tiles requested inside `coverage.bbox` only) and names its
|
||||
provider's no-imagery fill in `display.noDataColor`, drawn transparent through
|
||||
Cesium's colour-to-alpha (max channel distance 0.02): measured fills are PDOK
|
||||
and swisstopo white, IGN's flat ocean blue (#0a3b59) and GSI's ocean grey
|
||||
(#343434). Without both, PDOK's white fill covered the whole globe. A 4xx tile
|
||||
answer counts as "no imagery here", never as a tile error. IGN's overseas
|
||||
departments are outside its single bbox and are not shown. The aerial set now holds 68
|
||||
entries: 64 imported from the Open Imagery Register (national programmes in
|
||||
Austria, Belgium, Germany, Estonia, Spain, Luxembourg, Poland, Portugal,
|
||||
Geneva, Australia's NSW, and 51 US services: NAIP plus state, county and
|
||||
city programmes) plus PDOK, IGN, GSI and SWISSIMAGE, each with an `imagery`
|
||||
block (type, published resolution, true ortho, years flown, refresh) that
|
||||
Browse shows as a spec list. Tiles take `xyz` (with `{reverseY}` for TMS) or
|
||||
`bbox` templates (`{bbox-epsg-3857}`: WMS GetMap and ArcGIS export, filled
|
||||
by Cesium in the browser and by the proxy from z/x/y). Sub-national entries
|
||||
draw a `bbox` rectangle outline without fetching the country borders. 35
|
||||
entries whose licence the register gives only as "see publisher terms" are
|
||||
operator opt-in. Norway comes from Kartverket's open
|
||||
services (CC BY 4.0, "© Kartverket"; its zoom 12–20 Geovekst data may be shown
|
||||
in live services but not copied): the topographic, greyscale, raster-topo and
|
||||
nautical-chart caches (`xyz`), and WMS hillshade, DTM colour relief, sea depths
|
||||
(from zoom 11, Kartverket's 16-class depth legend), county and municipality
|
||||
boundaries, and the cadastre (from zoom 14). Norge i bilder needs
|
||||
authentication and is not included. `mapterhorn-terrain` is the first
|
||||
`source.encoding: terrarium` entry: Mapterhorn's global 512 px terrain-RGB
|
||||
tiles are decoded per tile in the browser (`src/library/relief.js`) into
|
||||
shaded relief with a six-class elevation legend in metres. A `tiles` entry (`{z}/{x}/{y}` template) drapes on the
|
||||
active imagery host — the globe, or the Google 3D tileset — and follows map
|
||||
stack switches; with no host it shows the existing "choose a globe map"
|
||||
guidance, and its row reads DRAPED rather than a count. A `pack` describes an
|
||||
existing host-layer source pack (today the opt-in CCTV packs): it is
|
||||
informational in Browse, names the host's own switch (`CCTV_NYC_ENABLED=1` and
|
||||
so on) and offers no Add. The `provider` kind from the RFC is refused by the
|
||||
validator until it ships.
|
||||
|
||||
Entries register their layer only when a viewer adds them, after the core
|
||||
catalog is sealed: `LayerLifecycle.registerLibraryLayer` accepts only ids in
|
||||
the `lib-<entry id>` namespace and only after `finalizeRegistrations`, and
|
||||
`unregisterLibraryLayer` destroys them. Library layers never enter
|
||||
`LAYER_STATE_REGISTRY` or the core token ledger. The panel groups them under a
|
||||
**Library** heading after every core group and rebuilds its rows on a
|
||||
`registrations` activity. Add switches the layer on; Remove destroys it.
|
||||
A library layer's row carries two icon chips, on or off (the panel shows
|
||||
row controls for an off layer only for Data Library rows, whose chips are
|
||||
marked `whileOff`): **Zoom to layer**
|
||||
(`zoom_in_map`) flies to the extent of the loaded features, or a tiles entry's
|
||||
declared coverage, taking the shorter way round the antimeridian and showing the
|
||||
whole globe for world-scale sets; **Remove from Data Layers** (`delete`)
|
||||
removes the entry. Zoom on a layer that is off switches it on first (a
|
||||
dataset only knows its extent once loaded). Zoom runs as an immediate
|
||||
navigation (`layer`), so Cockpit
|
||||
refuses it with its own message and the current follow owner is released first;
|
||||
reduced motion flies instantly. Icon chips (`chipGroup.js` `icon`/`action`) use
|
||||
their label as accessible name and tooltip and carry no `aria-pressed`.
|
||||
|
||||
This browser's selection persists in `gev:library:v1` (`{ added, on }`), written
|
||||
only for explicit (user, voice, tool) changes, like core layer state, so a
|
||||
Context session's temporary changes are not remembered. Share links carry the
|
||||
switched-on entries as `lib=<t>.<t>` with the permanent two-character tokens in
|
||||
`src/library/ledger.json` (append-only; packs take none). A link's entries join
|
||||
the viewer's library and, as for core layers, the link alone decides what is
|
||||
on. Unknown tokens are skipped. The live hash follows the existing
|
||||
initial-restore suppression.
|
||||
|
||||
Browser-direct entries are fetched without cookies or a referrer, redirects
|
||||
refused, bodies capped while streaming. Proxied entries go through
|
||||
`/api/library` (`server/providers/library.js`): `/catalog` reports each entry's
|
||||
availability and reason (`operator-opt-in`, `operator-disabled`, `key-missing`)
|
||||
without secrets; `/data/<id>` and `/tile/<id>/<z>/<x>/<y>` reach only the
|
||||
manifest's fixed upstream (the browser names an id, never a URL), refuse
|
||||
redirects and off-manifest hosts, cap dataset bodies at the manifest's
|
||||
`maxBytes` (≤ 32 MiB) and tiles at 2 MiB of `image/png|jpeg|webp`, cache per the
|
||||
manifest's `cacheSeconds` (60 s – 24 h; at most 512 tiles), serve the last good
|
||||
dataset as stale on failure, rate-limit per client, and never return upstream
|
||||
text. A keyed entry must be proxied; its key comes from the server environment
|
||||
through the key registry and its routes use the strict cross-site gate, while
|
||||
keyless routes use the keyless gate so they work behind a reverse proxy.
|
||||
Operators opt entries in with `GEV_LIBRARY_ENABLE` and remove them with
|
||||
`GEV_LIBRARY_DISABLE`. A static build with no server still serves every
|
||||
browser-direct, non-opt-in entry; proxied ones say they need the app's server.
|
||||
|
||||
The Browse dialog is a `dialog[data-panel-surface]`: modal, labelled, search
|
||||
focused on open, Escape and backdrop dismiss and return focus to Browse, and it
|
||||
closes when clean view, recording or Cockpit hides the panels. Its surface is
|
||||
frosted glass (a lighter `--glass-bg` than the rails, a 22 px backdrop blur
|
||||
clipped to the theme's frame, a barely dimmed backdrop), so the globe shows
|
||||
through without the HUD text behind it competing. An added entry that is on
|
||||
offers ZOOM, which closes the dialog and flies to it. Above the intro, four
|
||||
stat tiles summarise the library: Datasets (every entry, broken down by kind),
|
||||
Themes (how many distinct themes the entries cover), Added (and how many are on
|
||||
the map), and Map features (features loaded by dataset layers that are on;
|
||||
tiles have no count).
|
||||
They refresh with the dialog and once a second while it is open, so counts
|
||||
land as datasets finish loading. The line beside **REMOVE ALL** answers the
|
||||
filter ("Showing 2 of 8"). **REMOVE ALL** removes every added entry; its first
|
||||
press arms a confirm for four seconds. It lists
|
||||
addable entries first, then entries this instance would need to switch on, then
|
||||
packs, with kind, status, opt-in or key badges, provider, coverage and a licence
|
||||
link, and filters by text, theme and "only what this instance serves".
|
||||
Archived entries are hidden unless added. An entry's attribution and licence are
|
||||
registered in the Data attribution popover while it is in use.
|
||||
|
||||
Seeds: NASA EONET open events (GeoJSON, browser-direct), EIA US power plants
|
||||
≥ 100 MW (ArcGIS, browser-direct, ranked by capacity), Smithsonian GVP Holocene
|
||||
volcanoes (WFS GeoJSON, proxied — the upstream sends no CORS headers), NASA
|
||||
Black Marble 2016 night lights and OpenSeaMap seamarks (tiles), and the NYC,
|
||||
Lithuania and FL511 CCTV packs. Added from the Ask dataset registry (its live,
|
||||
mappable sources that are not core layers): the WRI Global Power Plant Database
|
||||
(provider-hosted CSV, the 10,000 largest of ~35,000 by capacity, so it reads
|
||||
PARTIAL), NWS active alerts that carry their own polygon (zone-wide alerts have
|
||||
no geometry and are dropped), NOAA CO-OPS tide stations (JSON records), and IMF
|
||||
PortWatch chokepoints and port disruptions (ArcGIS, operator opt-in until the
|
||||
reuse terms are confirmed, as Ask holds them in hosted mode). FEMA/ORNL USA
|
||||
Structures (CC BY 4.0) is the first view-bounded entry; its service refuses a
|
||||
query page smaller than its own 2,000 records, so the probe asks for a full
|
||||
page. Addresscloud's EPC ratings for England and Wales (Source Cooperative,
|
||||
read from `s3.us-west-2.amazonaws.com/us-west-2.opendata.source.coop`, 344
|
||||
local-authority GeoParquet files, OGL v3.0) are the first partitioned
|
||||
GeoParquet entry, coloured by band A–G with the official EPC colours. From an
|
||||
open/freemium registry review (commercial sources excluded): GDACS disaster
|
||||
alerts; the National Bridge Inventory (view-bounded, coloured Good/Fair/Poor);
|
||||
EPA Superfund sites and Toxics Release Inventory facilities (view-bounded); and
|
||||
tile overlays for Macrostrat geology, GBIF biodiversity density, USGS
|
||||
topographic and hydrography maps, PDOK (Netherlands) and IGN (France) aerial
|
||||
photos, BKG TopPlusOpen, GSI Japan map and photos, EMODnet bathymetry, SEDAC
|
||||
GPW 2020 population density (legend from the GIBS colour map) and JRC surface
|
||||
water occurrence. `npm run library:check` validates manifests,
|
||||
the ledger (append-only with `--base-ref`, run in CI on pull requests) and the
|
||||
generated DATA_SOURCES.md section; `npm run library:probe` hits every upstream
|
||||
once; `npm run qa:data-library` is the browser gate (fixture-backed).
|
||||
|
||||
## Utilities panel — October 29, 2026
|
||||
|
||||
UTILITIES is its own left-side panel, between Data Layers and Scenes, with
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
# Data Library
|
||||
|
||||
DATA LAYERS is the curated set of core layers. The Data Library holds
|
||||
everything else: regional sources, niche themes and community contributions.
|
||||
A viewer opens it from **DATA LAYERS ▸ Browse data library**, adds what they
|
||||
want, and the entry appears in the panel under **Library**. Nothing in the
|
||||
library is on until someone adds it.
|
||||
|
||||
Runtime behaviour is described in [CURRENT-STATE.md](CURRENT-STATE.md#data-library--october-21-2026).
|
||||
This page is the authoring guide.
|
||||
|
||||
## The one rule: live sources only
|
||||
|
||||
A library entry points at data its provider hosts. That can be a query API, a
|
||||
static file at a stable URL the provider controls, or a tile endpoint. The
|
||||
data is never copied into this repository. If the upstream goes away, the
|
||||
entry goes stale; this project never becomes the host of record. That is what
|
||||
lets every self-hosted instance serve the whole library with no data of its
|
||||
own.
|
||||
|
||||
It also keeps the existing rule in [CONTRIBUTING.md](../CONTRIBUTING.md): do
|
||||
not add data you have no right to redistribute. A live source still needs a
|
||||
licence that allows this use, and the manifest must say what it is.
|
||||
|
||||
## Kinds
|
||||
|
||||
| Kind | What it is | Renders through |
|
||||
| --- | --- | --- |
|
||||
| `dataset` | Vector features: `geojson`, `geojsonl`, `csv` (lat/lon columns), `json` (a record list with lat/lon fields), `arcgis` (a FeatureServer or MapServer layer, paged) or `geoparquet` (one file, or a partitioned set on S3) | The GeoJSON layer Datacenters and Dams use; view-bounded and GeoParquet sets draw as ground-clamped dots and polygons |
|
||||
| `tiles` | Raster tiles: `xyz` with a `{z}/{x}/{y}` template (`{reverseY}` for TMS rows), or `bbox` with a `{bbox-epsg-3857}` template (WMS GetMap, ArcGIS `export`/`exportImage`) | The active map's imagery host (globe or Google 3D tiles) |
|
||||
| `pack` | A source pack an existing host layer already loads (today: CCTV) | The host layer, switched on by its own environment variable |
|
||||
|
||||
A `dataset` or `tiles` entry needs no JavaScript. A `pack` documents a host
|
||||
layer's opt-in source so viewers can see it and operators know the switch; the
|
||||
host layer owns its code. Provider entries (code implementing a family
|
||||
contract, such as a Street Level provider) are not supported yet and are
|
||||
refused by the validator.
|
||||
|
||||
## Add an entry
|
||||
|
||||
1. Create `src/library/entries/<id>.json`. The id is 3–48 lowercase letters,
|
||||
digits or dashes and is permanent.
|
||||
2. Add one import line for it in `src/library/catalog.js` (keep the list in
|
||||
file-name order).
|
||||
3. For a `dataset` or `tiles` entry, run `npm run library:next -- <id>` and
|
||||
append `["<id>", "<token>"]` to `src/library/ledger.json`. Tokens are
|
||||
permanent: never change or reuse a row, even when an entry is removed.
|
||||
Packs take no token.
|
||||
4. Run `npm run library:docs` to regenerate the DATA_SOURCES.md table.
|
||||
5. Run `npm run library:check -- --base-ref origin/main`,
|
||||
`npm run library:probe`, `npm test` and `npm run qa:data-library`.
|
||||
6. Open a pull request with a `changelog.d/` fragment.
|
||||
|
||||
Start new entries at `"status": "community"`. Maintainers move an entry to
|
||||
`verified` after reviewing its licence and seeing it stay healthy; `stale` and
|
||||
`archived` mark entries whose upstream has stopped answering. Archived entries
|
||||
are hidden from Browse unless a viewer already added them.
|
||||
|
||||
## Manifest reference
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "eia-power-plants",
|
||||
"kind": "dataset",
|
||||
"family": "infrastructure",
|
||||
"title": "US power plants, 100 MW and up (EIA)",
|
||||
"provider": "EIA",
|
||||
"description": "Operating US power plants with at least 100 MW of capacity.",
|
||||
"icon": "ϟ",
|
||||
"maintainer": "github:your-handle",
|
||||
"coverage": { "countries": ["US"], "bbox": [-180, 13, -64, 72] },
|
||||
"source": {
|
||||
"format": "arcgis",
|
||||
"url": "https://services2.arcgis.com/…/Power_Plants_in_the_US/FeatureServer/0",
|
||||
"query": { "where": "Total_MW >= 100", "outFields": "Plant_Name,PrimSource,Total_MW" },
|
||||
"fetch": "browser-direct",
|
||||
"cacheSeconds": 86400,
|
||||
"maxFeatures": 4000,
|
||||
"maxPages": 3
|
||||
},
|
||||
"display": {
|
||||
"color": "#ffc23d",
|
||||
"label": "Plant_Name",
|
||||
"fields": ["PrimSource", "Total_MW"],
|
||||
"rank": { "field": "Total_MW", "order": "desc" }
|
||||
},
|
||||
"licence": { "name": "EIA data: public domain", "url": "https://www.eia.gov/about/copyrights_reuse.php" },
|
||||
"attribution": { "text": "U.S. Energy Information Administration", "url": "https://atlas.eia.gov/" },
|
||||
"status": "community",
|
||||
"reviewed": "2026-10-21"
|
||||
}
|
||||
```
|
||||
|
||||
Unknown fields are errors, so a typo cannot silently drop a cap or a licence.
|
||||
|
||||
| Field | Required | Notes |
|
||||
| --- | --- | --- |
|
||||
| `id`, `kind`, `family`, `title` (≤ 60), `provider` (≤ 32), `description` (≤ 240), `maintainer`, `status` | yes | `family` is one of movement, cameras, infrastructure, events, weather, environment, imagery. `provider` is the short name the panel row shows. |
|
||||
| `coverage` | yes | `global: true`, ISO 3166-1 alpha-2 `countries`, and/or a `bbox` `[west, south, east, north]`. |
|
||||
| `source.url` | yes | https, a real hostname, no credentials or fragment. |
|
||||
| `source.format` | yes | Datasets: `geojson`, `geojsonl`, `csv`, `json`, `arcgis`, `geoparquet`. Tiles: `xyz` (`{z}`, `{x}` and `{y}` or `{reverseY}`) or `bbox` (one `{bbox-epsg-3857}`, filled with the tile's Web Mercator bounds in metres, as MapLibre does). Packs: `host`. |
|
||||
| `source.records` | no | JSON only: the property path to the record list, such as `stations` for `{ "stations": [ … ] }`. Omit when the response is the list itself. |
|
||||
| `source.fetch` | datasets, tiles | `browser-direct` when the upstream sends CORS headers and needs no key; otherwise `proxied`. |
|
||||
| `source.query` | no | Query parameters added to the URL. For `arcgis`, `where` and `outFields`; geometry is always WGS84 GeoJSON. |
|
||||
| `source.columns` | no | CSV or JSON: `{ "lat": "…", "lon": "…" }` when the fields are not lat/latitude/y and lon/lng/longitude/x. |
|
||||
| `source.cacheSeconds` | no | 60–86,400, default 3,600. How long the proxy keeps a response. |
|
||||
| `source.maxBytes` | no | Body cap, default 16 MiB, at most 32 MiB. |
|
||||
| `source.maxFeatures` | no | Feature cap, default 10,000, at most 20,000. A capped dataset reads PARTIAL. |
|
||||
| `source.maxPages` | no | ArcGIS pages to follow, default 5, at most 10. |
|
||||
| `source.view` | no | `{ "maxAltitudeM": 300–50000, "sample": [w, s, e, n] }`. Makes a huge ArcGIS layer view-bounded: below that camera height it loads what is in view, one page at a time; above it the row asks the viewer to zoom in. Needs `arcgis` or `geoparquet`, and `browser-direct`. `sample` is where Zoom to layer lands and what `library:probe` queries. |
|
||||
| `source.partitions` | no | GeoParquet only: `{ "listUrl": "<S3 ListObjectsV2 URL with prefix>", "keyBase": "<URL keys are relative to>" }`, both on the source host. Needs `source.view`. The library indexes each file by its footer bbox and reads only the files in view. |
|
||||
| `source.minZoom`, `source.maxZoom` | no | Tiles only, 0–22. |
|
||||
| `source.encoding` | no | Tiles (`xyz`): `terrarium` when the pixels are elevation as terrain-RGB (height = R × 256 + G + B / 256 − 32768 m). The browser draws them as shaded relief: one orange ramp in six elevation classes (legend in metres), lit from the north-west; sea (exactly 0 m) is transparent. |
|
||||
| `source.tileSize` | no | Tiles: 256 (default) or 512 px. |
|
||||
| `source.bounded` | no | Tiles with `coverage.bbox`: `true` requests tiles inside the bbox only. Required for aerial imagery, whose servers paint a fill (white, a flat ocean, a coarse world mosaic) everywhere they have no photos. A 4xx tile answer is treated as "no imagery here", never retried or counted as an error. |
|
||||
| `source.key` | no | `{ "id": "<key registry id>", "in": "query" \| "header", "name": "…" }`. The id must exist in `src/keySetupCore.mjs`. A keyed entry must be `proxied`, so the key never reaches the browser. |
|
||||
| `display.color`, `display.label`, `display.fields` (≤ 4, two shown) | no | Datasets only, except `opacity`. |
|
||||
| `display.rank` | no | `{ "field": "…", "order": "desc" \| "asc" }`. Keeps the most important features when capped and lets the map prefer them at global zoom. Numbers or ISO dates. Without it, a zoomed-out view shows whichever features come first. |
|
||||
| `display.colorBy` | no | View-bounded datasets: `{ "field": "…", "colors": { "<value>": "#rrggbb" }, "labels": { "<value>": "…" } }`, up to 12 values. Colours each feature by value; the row legend lists each label with the count in view. |
|
||||
| `display.legend` | no | `[{ "label": "…", "color": "#rrggbb" }]`, up to 12: a static legend, such as a tile layer's colour scale. Take the colours from the provider's own legend or colour map. |
|
||||
| `display.outline` | no | Tiles: `countries` outlines every part of each covered country, `coverage` only the parts inside `coverage.bbox`, `bbox` the bbox rectangle itself (a state, county or city inside a country). Use it for aerial imagery so its edge shows. |
|
||||
| `display.aerial` | no | Tiles: `true` for aerial or satellite photography. It drapes above every other imagery layer, including core overlays, and stays there when another layer is switched on. Pair it with `source.bounded`. |
|
||||
| `display.noDataColor` | no | Tiles: `#rrggbb` of the provider's no-imagery fill, drawn transparent (within a small tolerance for JPEG noise along its edge). Measure it from a tile outside the coverage; do not guess. Globe maps only: Google 3D tiles ignore it. |
|
||||
| `display.opacity` | no | Tiles: (0, 1]. |
|
||||
| `imagery` | no | Tiles: what a photo layer is. `type` (`ortho`, `satellite-mosaic`, `oblique`, `historic`), `resolution` as the provider publishes it ("10 cm (plains) / 25 cm (Alps)"), `resolutionM` (the finest, in metres), `trueOrtho`, and optional `vintage` ("2021–present"), `updateCycle`, `orthoQuality` and `registry` (an https link to where the metadata came from). Browse shows it as a spec list on the card. |
|
||||
| `pack` | packs | `{ "host": "cctv", "env": "CCTV_EXAMPLE_ENABLED" }`. Packs are always `licence.operatorOptIn: true`. |
|
||||
| `licence` | yes | `name`, an https `url`, and `operatorOptIn: true` when the terms need the operator's own agreement or consent. |
|
||||
| `attribution` | yes | `text` and an optional https `url`; shown in the Data attribution popover while the entry is in use. |
|
||||
| `regionsServed` | no | ISO codes, when the upstream only answers from some countries. |
|
||||
| `reviewed` | no | `YYYY-MM-DD` of the last licence and endpoint review. |
|
||||
|
||||
## Aerial imagery from the Open Imagery Register
|
||||
|
||||
The aerial photo entries came from the [Open Imagery Register](https://imagery-registry.netlify.app/)
|
||||
on 2026-10-23: every register source with a map preview, less the ones that
|
||||
fail the library's rules. Each entry keeps the register's resolution, product
|
||||
type, true-ortho flag, years flown and refresh cycle in `imagery`. Before an
|
||||
entry was written, a tile inside its coverage was fetched in a browser from
|
||||
the app's origin: it had to be a real photo (not a blank fill), and the
|
||||
provider had to send CORS headers (or the entry would be `proxied`). The
|
||||
fill a provider paints at its coverage corners became `display.noDataColor`.
|
||||
|
||||
Left out, with the reason:
|
||||
|
||||
| Register source | Why |
|
||||
| --- | --- |
|
||||
| NASA GIBS true colour | Already in core (Recent imagery). |
|
||||
| Sentinel-2 cloudless (EOX) | CC BY-NC-SA: non-commercial. |
|
||||
| SWISSIMAGE Bern and Marion County PMTiles | Extracts hosted by the register, not the provider. |
|
||||
| Cabarrus County NC, Hawaii, Nashville, Sioux Falls | Credit a commercial imagery vendor (EagleView, Maxar WorldView, Pictometry, Nearmap). |
|
||||
| DVRPC, Missouri, Washington | The preview serves a blank white tile across the coverage centre. |
|
||||
| Sarasota County FL | The service now requires a token. |
|
||||
|
||||
Entries whose register licence reads "see publisher terms" or "verify portal
|
||||
terms" are `licence.operatorOptIn: true`: an operator reads the publisher's
|
||||
terms and lists the id in `GEV_LIBRARY_ENABLE`. Re-check an entry's terms and
|
||||
drop the opt-in when its publisher states an open licence.
|
||||
|
||||
## Choosing a fetch mode
|
||||
|
||||
Use `browser-direct` when the upstream answers a browser on another origin
|
||||
(`Access-Control-Allow-Origin`) and needs no key. Tile servers must send CORS
|
||||
headers for WebGL to use them; without them, use `proxied`.
|
||||
|
||||
Use `proxied` otherwise. The proxy is generic: there is no per-entry server
|
||||
code. It reaches only the manifest's host, refuses redirects, caps bodies,
|
||||
caches per `cacheSeconds` and never returns upstream error text. The browser
|
||||
names an entry id, never a URL, so the proxy cannot be pointed anywhere else.
|
||||
|
||||
## Operator switches
|
||||
|
||||
| Variable | Effect |
|
||||
| --- | --- |
|
||||
| `GEV_LIBRARY_ENABLE` | Comma-separated ids of `operatorOptIn` datasets and tiles to serve. |
|
||||
| `GEV_LIBRARY_DISABLE` | Comma-separated ids to remove from this instance. |
|
||||
| A pack's own variable | For example `CCTV_NYC_ENABLED=1`; Browse then shows the pack as switched on. |
|
||||
|
||||
## When an entry belongs in core instead
|
||||
|
||||
A library entry can be proposed for DATA LAYERS when it has global or
|
||||
near-global coverage, is keyless or uses a key the registry already has, has
|
||||
stayed `verified` with no stale period for about six months, and a core
|
||||
maintainer will own it. Promotion and demotion are maintainer decisions.
|
||||
Reference in new issue
Block a user