Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ build/
*.tsbuildinfo

# Generated demo bundle (created by `pnpm demo:standalone`)
examples/public/standalone/
site/public/standalone/

# JSON Schema copied into the package by `pnpm build:npm` (canonical: packages/schema/v1.json)
packages/map0/schema/
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ still drafts and will change without a deprecation path until 1.0. See
## The idea in one paragraph

MapLibre made cartography declarative (the style spec is JSON). map0 extends the same idea to the
*map client*: basemaps, overlay tree/TOC, legends, feature popups, print, globe, GPS, theming — all
*web map client*: basemaps, overlay tree/TOC, legends, feature popups, print, globe, GPS, theming — all
declared in a single, schema-validated JSON document that lives in a CMS field. It renders straight
into the page (web component / script tag — no iframe, no backend) and looks like 2026: pretty,
responsive, themable, accessible.
Expand Down Expand Up @@ -82,15 +82,16 @@ packages/schema config types, validation (JSON-path errors), defaults, publish
packages/core headless engine: basemap manager, source adapters (wms/wmts/raster/cog/geojson/
vector), feature info, i18n — no DOM UI
packages/ui the <map0-viewer> web component (Lit) + panels, popups, theming
examples/ landing page, /demos gallery and demo pages
examples/public/ demo configs and data served as-is
site/ landing page, /demos gallery and demo pages (EN, with /de/ built from
per-page catalogues in site/i18n/)
site/public/ demo configs and data served as-is
e2e/ headless smoke verification (grows into the Playwright suite in M1)
docs/ specification
```

Distribution note: `packages/ui/dist/` is a flat folder — `map0.js`, its chunks, and MapLibre's
three files shipped verbatim. Deploy the folder as a unit; the embed stays one script tag.
A page pays **~23 KB gzip** for the element itself; the engine and MapLibre (~316 KB) load when the
A page pays **~31 KB gzip** for the element itself; the engine and MapLibre (~305 KB) load when the
map approaches the viewport, and capabilities parsing, proj4, PMTiles, the COG decoder, measuring
and the dialogs only when those features are used. `pnpm size` prints the breakdown and fails when
the page tier grows.
Expand Down
2 changes: 1 addition & 1 deletion docs/03-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
| ID | Requirement | Prio |
|---|---|---|
| F3.1 | "Add layer" dialog: paste a service URL (WMS/WMTS), client parses capabilities, user picks layers | M → D-03 |
| F3.2 | Add GeoJSON by URL; drag & drop a local GeoJSON/GPX file | S |
| F3.2 | Add GeoJSON by URL; drag & drop a local GeoJSON/KML/GPX file | S |
| F3.3 | Remove/rename user-added layers; clearly distinguished from configured layers | M |
| F3.4 | Catalog search (CSW / OGC API Records) as source for adding layers | C → D-05 |
| F3.5 | User-added layers survive in the share/permalink state (not in the page config) | S |
Expand Down
21 changes: 21 additions & 0 deletions docs/04-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ inside the config it would gate. It mirrors `<img loading>`:
<map0-viewer config-src="…" loading="eager"></map0-viewer> <!-- above the fold -->
```

**`theme` is an attribute for the same reason.** `theme.mode` in the config suits a map that owns
the light/dark decision, but a host page with its own dark-mode toggle knows better than the
config can. `theme="dark"` / `theme="light"` on the element overrides `theme.mode`, and flipping
the attribute restyles a running map — map0.net's own topbar toggle drives its demo maps this way.
Unset, the config decides as usual.

*(Element/API names indicative; final naming in [06-architecture.md](06-architecture.md), pending D-01.)*

## Top-level shape
Expand Down Expand Up @@ -197,6 +203,10 @@ inside the config it would gate. It mirrors `<img loading>`:
"continuous": true, // default false = discrete classes
"reverse": true
}, // legend swatches are derived from this ramp
// — or classify explicitly instead of a ramp:
// "color": { "classes": [
// { "value": 1, "color": "#c22f2f", "label": "sealed" },
// { "from": 2, "to": 5, "color": "#67a9cf" } ] }
// DEM instead? "hillshade": true — or
// { "exaggeration": 0.6, "illuminationDirection": 315,
// "shadowColor": "…" } (mutually exclusive w/ color)
Expand Down Expand Up @@ -310,6 +320,17 @@ and the palettes themselves are documented at [colorbrewer2.org](https://colorbr
[carto.com/carto-colors](https://carto.com/carto-colors/). A name map0 does not recognise fails
fast: the layer goes to error state with *"… is not a supported color scheme"*.

**COG explicit classes** (`color.classes`, instead of a ramp): an array of classes, each an exact
`value` (categorical/binary rasters) or a `from`/`to` range, with a hex `color` (8-digit = with
alpha) and an optional legend `label`. Ranges include `from` and exclude `to` — except the class
with the highest `to`, which includes it, so the data maximum never falls off the top class; there
is deliberately no inclusive/exclusive knob. Exact values win over ranges; pixels matching no class
(and noData pixels) are transparent, which doubles as a way to blank out irrelevant value ranges.
Values are compared after the COG's scale/offset are applied — the numbers in the config are the
real-world values. One caveat: the underlying color function is keyed by the COG URL, so two layers
reading the **same file** cannot mix `classes` with a ramp/hillshade rendering (see engineering
notes).

## Runtime layer management (F3)

The add-layer dialog (TOC "+", `controls.layerSwitcher.allowAdd`) lets users paste a WMS URL;
Expand Down
14 changes: 7 additions & 7 deletions docs/06-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ map0/
│ │ # feature-info, legend resolution, i18n runtime — NO DOM UI
│ ├─ ui/ # <map0-viewer> Lit element + panels (TOC, legend, popup, print, …)
│ └─ react/ # (M2) thin wrapper
├─ examples/ # demo configs incl. live Austrian SDI services + plain-HTML/CMS embeds
├─ site/ # docs site (config reference generated from schema) + playground
└─ e2e/ # Playwright + visual regression against examples/
├─ site/ # map0.net: landing + /demos + demo configs (live Austrian SDI services,
# plain-HTML/CMS embeds); grows into docs site + playground
└─ e2e/ # Playwright + visual regression against site/
```

`core` is deliberately UI-free: it makes the web component thin, enables the React wrapper and
Expand Down Expand Up @@ -127,16 +127,16 @@ Measured with `pnpm size` (gzip, current). Three tiers, paid at different moment

| Tier | Size | Paid when |
|---|---|---|
| **page** — custom element + Lit | ~20 KB | the page loads — budget 40 KB, enforced in CI |
| **map** — engine, MapLibre (3 files), its stylesheet, popup renderer | ~314 KB | the element approaches the viewport |
| **page** — custom element + Lit | ~31 KB | the page loads — budget 40 KB, enforced in CI |
| **map** — engine, MapLibre (3 files), its stylesheet, popup renderer | ~305 KB | the element approaches the viewport |
| ogc-client (capabilities parsing) | ~62 KB | first add-layer dialog or WMTS layer |
| proj4 (+ wkt-parser, mgrs) | ~47 KB | first coordinate readout |
| PMTiles | ~8 KB | first `pmtiles://` layer |
| print / add-layer dialogs | ~5 KB | first open |

**Nothing but the element loads until the map is needed.** `<map0-viewer>` observes itself with an
IntersectionObserver (300 px root margin) and only then fetches the config, the engine and MapLibre
— so an article with a map at the bottom pays 20 KB unless a reader scrolls there. `loading="eager"`
— so an article with a map at the bottom pays 31 KB unless a reader scrolls there. `loading="eager"`
opts out, `load()` forces it, and elements inside a hidden tab stay unloaded until shown, which also
avoids MapLibre initialising into a zero-size container. This is an attribute rather than a config
key because it decides whether the config is fetched at all.
Expand Down Expand Up @@ -198,7 +198,7 @@ documented prominently (services must send CORS headers; no proxy in core, recip
- `schema`: golden tests for validation/defaults/migrations.
- `core`: adapter unit tests with mocked fetch (recorded capabilities/GFI fixtures from real
Austrian services).
- `e2e`: Playwright against `examples/` — interaction flows + **visual regression screenshots**;
- `e2e`: Playwright against `site/` — interaction flows + **visual regression screenshots**;
strict-CSP smoke test; a11y audit (axe) gate.
- CI budgets: bundle size check, Lighthouse on demo page.

Expand Down
14 changes: 8 additions & 6 deletions docs/07-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ What is left is mostly polish: a published JSON Schema and an accessibility pass

| Area | Status |
|---|---|
| Data sources | ✅ WMS, WMTS, vector tiles/PMTiles, GeoJSON, COG (RGB, single-band ramps, hillshade), style & raster basemaps · ⬜ WFS, OGC API Features (deferred to v1.x per D-03) |
| Data sources | ✅ WMS, WMTS, vector tiles/PMTiles, GeoJSON, COG (RGB, single-band ramps, explicit classes, hillshade), style & raster basemaps · ⬜ WFS, OGC API Features (deferred to v1.x per D-03) |
| Layer tree | ✅ groups, visibility, opacity, status, zoom hints, zoom-to-layer, metadata links, runtime add/remove · ⬜ drag reorder, filter box, radio groups |
| Feature info | ✅ GetFeatureInfo + vector query, templates, field tables, multi-hit, hover, highlight, coordinates · ⬜ mobile bottom sheet |
| Legend | ✅ service, style-derived, hand-written; in print |
Expand All @@ -22,7 +22,7 @@ What is left is mostly polish: a published JSON Schema and an accessibility pass
| Search | ✅ type-ahead geocoding, pluggable providers, coordinate input |
| Measuring | ✅ distance & area, geodesic, draggable vertices |
| Configuration | ✅ one document, validation with JSON-path errors (unknown keys, unique ids, https policy), `extends`, theming, i18n + overrides · 🟡 published JSON Schema |
| Performance | ✅ 23 KB page tier, engine and features load on demand, CI budget |
| Performance | ✅ 31 KB page tier, engine and features load on demand, CI budget |
| Accessibility | 🟡 keyboard operation, focus trap, reduced motion · ⬜ audit, DOM-mirrored results |
| Packaging | ✅ MIT licence, name, npm package `map0-viewer` published (prebuilt bundle + third-party notices), CDN via jsDelivr, demo site at map0.net · ⬜ TypeScript types |

Expand Down Expand Up @@ -53,7 +53,7 @@ What is left is mostly polish: a published JSON Schema and an accessibility pass
- ✅ **i18n** de/en + per-locale overrides (F11.1–F11.3)
- ✅ **Config inheritance** via `extends` (C6)
- ✅ **Error toasts** and TOC zoom-range hints (F2.5)
- ✅ **Code splitting + lazy loading** — 23 KB page tier, MapLibre deferred until the map is in view, size budget in CI (N1)
- ✅ **Code splitting + lazy loading** — 31 KB page tier, MapLibre deferred until the map is in view, size budget in CI (N1)
- ✅ **Dialog focus trap + Escape** (N4, partial)
- ✅ **Search** — type-ahead geocoding with Photon/Nominatim/custom providers, coordinate input (F8.1, F8.2)
- ✅ **Measure** — distance and area on the sphere, draggable vertices (F9.1)
Expand Down Expand Up @@ -82,15 +82,17 @@ What is left is mostly polish: a published JSON Schema and an accessibility pass

- ⬜ **WFS and OGC API Features** layer types (deferred from D-03)
- ⬜ **Drag-and-drop reorder** in the TOC (F2.6)
- ⬜ **GeoJSON/GPX by URL and file drop** (F3.2)
- ⬜ **GeoJSON/KML/GPX by URL and file drop** (F3.2)
- ⬜ **Terrain** (F1.8)
- ⬜ **Auth hooks** for protected services (C8)
- ⬜ **React wrapper** (`@map0/react`)
- ⬜ **React and Angular wrappers** (`@map0/react`, `@map0/angular`)
- ✅ **COG** layers (2026-08-19) — `type: "cog"`: RGB/grayscale imagery, single-band color
ramps with auto-derived legend, and DEM hillshade (`hillshade` key → raster-dem + hillshade
layer); bounds from the file header, decoder loaded on demand
(@geomatico/maplibre-cog-protocol, adopted at 0.9.x — see D-03 update) · 3D terrain itself
stays with F1.8
stays with F1.8 · **explicit classes** added 2026-08-20 (`color.classes`: exact values and
[from, to) ranges with hand-picked colors and labels, for categorical/binary rasters a ramp
cannot express)

## M2 — Ecosystem

Expand Down
5 changes: 3 additions & 2 deletions docs/08-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ style-JSON basemaps · GeoJSON (incl. clustering) · PMTiles.
**Update 2026-08-19 — COG pulled forward.** `type: "cog"` shipped (RGB/grayscale imagery +
single-band color ramps) on @geomatico/maplibre-cog-protocol **before** its 1.0, pinned at ^0.9.2 —
a deliberate exception to the "wait for 1.0" gate in the roadmap: the API surface map0 touches
(`cogProtocol`, `getCogMetadata`, `colorScale`) is small, the library is actively maintained
(`cogProtocol`, `getCogMetadata`, `colorScale`, `setColorFunction` since `color.classes`) is
small, the library is actively maintained
(0.9.2 released 2026-08-17), and the whole dependency loads as a lazy chunk only when a config
contains a cog layer. Consequence of the pin: review the changelog before any bump; `getCogMetadata`
is documented as unstable upstream. EPSG:3857-only fits D-02 (the protocol does not reproject; the
Expand Down Expand Up @@ -84,7 +85,7 @@ therefore targets GeoNetwork first: CSW 2.0.2 and, where available, OGC API Reco
| O-01 | License | **resolved 2026-08-17: MIT.** Supersedes the Apache-2.0 proposal — the shortest, most familiar licence for an embeddable client wins on adoption, and the engine below us (MapLibre, BSD-3-Clause) sets the same expectation. Accepted trade-off: no explicit patent grant. Root `LICENSE`; the published tarball carries it plus `THIRD-PARTY-NOTICES.md`. |
| O-02 | Product name + npm/domain availability | **resolved 2026-08-16:** the name is **map0** — "map" plus the zero code it takes to get one. npm (2026-08-17): the batteries-included bundle is published as **`map0-viewer`** — the registry rejects the unscoped `map0` under its typosquatting heuristic ("too similar to mcp1, hapi, tap, tape"; normalised, `map0` reads as *mapo*), even though the name is unregistered. Not appealable via the CLI; only npm support can release it, so the short name stays a wish, not a plan. The `map0` **org** is reserved for the later `@map0/*` split. Assembly: `pnpm build:npm`, see [09-engineering-notes.md](09-engineering-notes.md) §release. Domain: **map0.net** (secured 2026-08-17, serves the demo site — O-04). |
| O-03 | Which CMS(s) must be proven first? | **resolved 2026-08-14:** none for now — the reference embed is a **plain static HTML page** ("if it runs there, it runs in any CMS"). Org context: headless CMS (Squidex, Strapi) → map0 embeds into custom frontends built on top; raises the value of the M2 React wrapper. |
| O-04 | Hosting of demo/docs site | **resolved 2026-08-16:** Azure Static Web Apps — `pnpm build:site` → `dist-site/`, uploaded by `.github/workflows/deploy-site.yml`; served at **map0.net** (O-02). Legal pages added 2026-08-19: `/imprint.html` + `/privacy.html` (company data mirrors the spatial-focus.net legal notice; privacy covers Azure hosting, self-hosted Umami and the third-party demo services), linked from every footer. |
| O-04 | Hosting of demo/docs site | **resolved 2026-08-16:** Azure Static Web Apps — `pnpm build:site` → `dist-site/`, uploaded by `.github/workflows/deploy-site.yml`; served at **map0.net** (O-02). Legal pages added 2026-08-19: `/imprint.html` + `/privacy.html` (company data mirrors the spatial-focus.net legal notice; privacy covers Azure hosting, self-hosted Umami and the third-party demo services), linked from every footer. Bilingual since 2026-08-21: every page builds twice (EN at `/…`, DE at `/de/…`) from one English source plus a JSON catalogue in `site/i18n/de/` — a build-time Vite plugin, `hreflang` pairs, a browser-language redirect on `/` and a localStorage-persisted switcher, the same behaviour as spatial-focus.net without a framework. A topbar dark/light toggle (OS default, persisted) drives embedded maps via the viewer's `theme` attribute. The folder moved `examples/` → `site/` the same day. |
| O-05 | Browser floor | evergreen + WebGL2 (MapLibre v6 requirement) — confirm against portal analytics |
| O-06 | Geocoder default | **resolved 2026-08-15:** Photon (OSM, built for type-ahead, CORS-open) as the default, plus a provider interface — `"nominatim"` built in, and any gazetteer via a URL template. Public Photon instances carry fair-use expectations, so production installs self-host it or point at their own service. |
| O-07 | Repository home (GitHub org?) & governance | decide before M0 ends |
Expand Down
Loading
Loading