Skip to content

Add disasters mission profile : Disasters light demo - #347

Open
kyle-lesinger wants to merge 12 commits into
developmentfrom
feature/disasters-mission-profile
Open

Add disasters mission profile : Disasters light demo#347
kyle-lesinger wants to merge 12 commits into
developmentfrom
feature/disasters-mission-profile

Conversation

@kyle-lesinger

@kyle-lesinger kyle-lesinger commented Aug 26, 2026

Copy link
Copy Markdown
Member

Purpose

  • Adds a disasters mission profile so MMGIS can be stood up as the NASA Disasters Learning Portal's mapping interface, carrying the two GAIA gridded building-exposure collections from the VEDA disasters STAC as its initial layers.

Proposed Changes

  • [ADD] mission-profiles/disasters.json — the hand-maintained profile (tools, overrides, scaffold, layers).
  • [ADD] mission-profiles/generated/disasters-mission.json — the generated, uploadable mission config (9 tools, 2 layers).
  • [CHANGE] configure/package-lock.json — version field synced to configure/package.json, which it had drifted from.

Layer details:

  • Both layers reuse the colormap, nodata, bidx and resampling declared in each collection's STAC renders block, so they render the way VEDA intends.
  • Both set tileformat: "wmts". titiler-pgstac reports "scheme":"xyz" in its tilejson; MMGIS's tileformat default is tms, which would flip the Y axis.
  • GAIA is hazard-agnostic exposure data, so both layers carry all five hazard tags and the Public Safety sector.
  • The Event theme and its activation catalog are overridden away in LayerFilterThemes and LayerFilter — GAIA carries no activation.
  • The bottom Timeline panel is dropped: Timeline declares no defaults block so the generator cannot emit it, and neither layer is time-enabled (both are single January-2025 composites).

Issues

Testing

Verified locally against a fresh deployment (Postgres 16 + PostGIS, npm run start:no-docker):

  • node scripts/generate-mission-config.js disasters --checkOK: mission-profiles/generated/disasters-mission.json is up to date. (artifact reproduces deterministically from the profile)
  • The backend validator — the same validate() the server runs on upsert — returns {"valid": true} for the generated config.
  • Tile URLs extracted from the generated artifact return real imagery:
    • GAIA Building Exposure (Total)200 image/png 14774b at z5/5/12
    • GAIA Building Exposure (Wood)200 image/png 10529b at z5/5/12
  • No regression to existing profiles: full-demo --check and minimal --check both report up to date, so the CI drift check stays green.

Adds a `disasters` profile and its generated config for the NASA
Disasters learning portal, carrying the two GAIA gridded building-
exposure collections from the VEDA disasters STAC as titiler-pgstac
tile layers.

- Layers reuse each collection's STAC `renders` colormap, nodata, bidx
  and resampling, and set tileformat=wmts: titiler-pgstac serves xyz,
  and the MMGIS default of tms would flip the Y axis.
- GAIA is hazard-agnostic exposure data, so both layers carry all five
  hazard tags and the Public Safety sector.
- The Event theme and its activation catalog are overridden away in
  LayerFilterThemes and LayerFilter, since GAIA carries no activation.
- The bottom Timeline panel is dropped: Timeline declares no `defaults`
  block so the generator cannot emit it, and neither layer is
  time-enabled.

Also syncs configure/package-lock.json's version field, which had
drifted from configure/package.json.
The demo browses by hazard and by activation, not by sector, so the Need
theme is dropped and Event restored alongside Hazard, which becomes the
default theme.

Seeds three activations from the team's tracking sheet, keyed by their
STAC metadata event names:

- 202501_Fire_CA — California Wildfires January 2025
- 202606_Earthquake_Venezuela — Venezuela Earthquake June 2026
- 202604_Typhoon_Sinlaku — Typhoon Sinlaku April 2026

Each carries a polygon covering its named locations: the Location filter
is a geocode intersection against activation geometry, so a search for
"Los Angeles" only matches when the geometry actually covers it.

Layer associations are empty for now. None of these activations involve
GAIA, and the products they do list are not yet in the mission; the
pairings land with those layers.
@kyle-lesinger kyle-lesinger changed the title Add disasters mission profile with GAIA building-exposure layers Add disasters mission profile : Disasters light demo Aug 26, 2026
Adds the two layers from the California sequence in disasters-tracking#61
that are currently ingested, and pairs them to 202501_Fire_CA in the
curated order:

- OPERA Surface Disturbance (DIST-S1), default on
- Sentinel-2 SWIR

Each layer's time window is pinned to a range that actually returns
imagery. DIST-S1's collection holds a single 2025-01-09 acquisition and
returns 204 for the activation's own 01-10 to 01-31 window, so the layer
is pinned to the data that exists rather than to the event dates; widen
it once the fire-period product lands. Selecting an activation does not
currently drive the time window — LayerFilter only emits
plugin:layerfilter:changed and calls layers:setListed — so the layer
config is the only thing steering the datetime parameter.

The remaining curated layers are not in the STAC yet: FIRMS, FEDS and
Sentinel-1 damaged structures for California; all five for Venezuela;
and four of five for Sinlaku. Sentinel-2 mNDWI is present but holds only
a 2019-03-16 scene over Nebraska and Iowa, so it is not wired to the
Sinlaku activation. Venezuela and Sinlaku keep empty layer lists.
nisar-gunw-composite is now ingested, so the first layer of the Venezuela
sequence in disasters-tracking#61 can be wired up. It is paired to
202606_Earthquake_Venezuela and defaults on.

Render parameters are taken from the collection's own STAC renders block
rather than chosen here: rdylbu_r rescaled -40 to 40 cm with nodata
-9999 and bilinear resampling. That answers the open question in
disasters-portal#395, which asked for a diverging colormap centred on
zero because line-of-sight displacement is signed.

The collection landed with three interferometric pairs rather than the
single scene NASA-AMMOS#395 anticipated, spanning 2026-06-13 to 2026-06-30. The
layer's time window covers that whole extent so all three mosaic;
narrowing it to the activation's own 06-26 to 07-14 window drops the
earliest pair and roughly halves the returned coverage.
Every time-enabled layer here is time.type "global", which means
TimeControl.updateLayersTime is what fills its time.start and time.end
from the global window. The top-level time block had been disabled back
when GAIA — which carries no time — was the only layer, so nothing ever
filled them.

The failure was silent and misattributed. With start and end left null
the time formatter yields empty strings, the tile URL degrades to
"datetime=/", and titiler answers HTTP 500. That error response carries
no CORS headers, so the browser reports it as a CORS policy failure
against dev.disasters.openveda.cloud and the real 500 never surfaces.
Verified directly: the same request without the empty datetime returns
200 with access-control-allow-origin set.

Restores the global time block from the reference 42-layer config, with
visible false: Timeline declares no defaults block, so the generator
cannot emit the tool and there is no panel for a timeline UI to render
into. The machinery still runs and still fills the placeholders.

Also builds the themes, which had never been compiled in this checkout —
dist/ was absent, so the disasters theme and the default fallback both
failed to load and the modern layout rendered without its panels.
Two gaps surfaced while standing up the disasters portal.

Filtering narrowed the layers list but never moved the map, so selecting
an activation on the other side of the world left the user on the default
view with its layers loading off-screen — indistinguishable from a layer
that failed to load. LayerFilter now resolves a destination for the
current match, most specific first: an authored `view` on the selected
entry's layer pairing (documented in SCHEMA.md but never read by
anything), else the union of the matched layers' extents, else the
selected entry's geometry. It fails soft at every step, and is gated on
real user interaction for the same reason the listed-updates are — on
boot the default theme is auto-selected, and flying the map then would
override the mission's configured starting view.

Timeline declared no `defaults` block, so generate-mission-config could
not emit it and a profile listing it threw. Any panel naming it was
therefore dead config, which is why the reference layout's bottom panel
rendered nothing. Adding the block lets a profile opt in; full-demo
excludes it explicitly to stay byte-identical, since that profile has no
bottom panel for it to occupy.

The disasters profile takes the timeline back: the reference config's
bottom panel, time.visible restored, and Timeline switched on.
Toggling a layer whose data sits outside the viewport drew it correctly
and looked broken: the tiles loaded, the checkbox ticked, and the screen
did not change. LayerManager now moves the map to a layer it has just
switched on.

Only when the layer's extent lies entirely outside the viewport. A layer
that already overlaps what the user is looking at is left alone, so
stacking layers over one area never yanks the camera, and switching a
layer off never moves anything.

Reading the viewport needs both adapter shapes: Leaflet returns plain
southWest/northEast corners while deck.gl returns Leaflet-style
getSouthWest()/getNorthEast() accessors. A corner tuple is accepted too.
An unreadable viewport, or a layer with no extent, leaves the map where
it is rather than guessing.

This is the layer-level counterpart to the filter-level move added for
activation selection; between them, both gestures that reveal data now
take the user to it.
Adds sentinel1-dmgassessment-daily as a sixth layer, bound to the
California Wildfires January 2025 activation alongside OPERA DIST-S1.
Tiles are rendered from the collection's own `damage` render definition
so the colours match what the data producer intended.

opera-distalert-s1-daily was already configured and is unchanged.
A deck.gl TileLayer was built without an extent, so a layer scoped to one
footprint still requested the full tile pyramid. Those tiles come back
empty at best, and from a single-item raster endpoint as an error, so a
scoped layer generated failed requests on every pan and zoom.

The tile path already builds bounds for Leaflet; it now passes the same
bounds to the deck.gl path, where resolveExtent converts them to deck.gl's
[minX, minY, maxX, maxY]. Leaflet's LatLngBounds reaches corners through
getSouthWest()/getNorthEast() rather than plain properties, so that shape
is unwrapped before conversion — reading it as a plain object yields
undefined and silently drops the extent.
A float zone had no height bound, so a panel holding tall content
stretched it past the viewport. Every descendant then resolved its height
against an unbounded parent, so a panel body set to scroll grew instead
and its overflow ran off the bottom of the screen with no way to reach it.

Clamps the zone to its parent and lets panels shrink to fit, which is what
lets the existing overflow-y on the panel body take effect.
The NISAR layer drew from a collection mosaic of three granules on
different tracks. titiler-pgstac picks contributing granules per tile, so
a tile at one zoom intersected three footprints and at the next only one:
coverage vanished as you zoomed in, and the discontinuities landed on tile
boundaries as straight seams no swath edge would produce. Each track is
now its own layer reading its own STAC item, so what draws no longer
depends on the tile grid.

The three granules also span different date pairs, which a single merged
layer left indistinguishable; separate layers make that legible.

Rescale moves from +/-40 to +/-25 cm. The wider range was not clipping
anything worth keeping — under 1% of pixels exceeded it — while
compressing the bulk of the field, which sits within a few cm, into the
middle of the ramp. Measured over an area of interest, the 2nd to 98th
percentile spans -20 to +5.

Also enables area analysis on every layer, each pointing at a concrete
item with the assets, bidx and nodata read back off its own tile URL so
statistics match what is drawn. The Chart tool moves to its own opaque
floating panel: a transparent panel turns off the panel body's overflow so
tool dropdowns can escape, which left a long results list unscrollable.
A float zone accepts exactly one panel, so moving the Chart tool into its
own panel alongside the existing one at float-top-right made the config
invalid and the modern interface refused to initialize. The tool goes back
where it was.

Scrolling is instead handled where the conflict actually is. A transparent
panel's body keeps visible overflow so tool dropdowns can escape, which
means it can never be the scroll container; a tool with more content than
the zone can show has to bound and scroll itself, and that requires its
card to give up height.

Which cards give way falls out of flex's automatic minimum rather than any
name in the stylesheet: a card keeps min-height auto, so it cannot shrink
past its content's intrinsic minimum. A tool that scrolls internally
reports almost none and yields; map controls report their full height and
stay rigid.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deploy MMGIS as the Disasters Learning Portal map interface

1 participant