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
6 changes: 6 additions & 0 deletions .changeset/add-layer-control-maplibre.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@xtramaps/layer-control-maplibre": minor
"@xtramaps/layer-control-maplibre-react": minor
---

Add `@xtramaps/layer-control-maplibre` (core) and `@xtramaps/layer-control-maplibre-react`: the LayerControl component extracted from `ogcapi-html`, supporting nested groups, radio-groups and merge-groups (including `sourceLayer` auto-collection). Legend icons are now provided by `@xtramaps/legend-symbols-maplibre-react` instead of a local copy. Mounted via `@vis.gl/react-maplibre`'s `useControl`/`useMap` and composed explicitly as a child of `@xtramaps/web-map-maplibre-react`'s `MapLibre` component (no dependency between the two packages).
14 changes: 14 additions & 0 deletions .changeset/add-web-map-cesium.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@xtramaps/web-map-cesium": minor
"@xtramaps/web-map-cesium-react": minor
---

Add `@xtramaps/web-map-cesium` (core) and `@xtramaps/web-map-cesium-react`: the Cesium map component migrated from `ogcapi-html`, onto current `@cesium/engine`/`@cesium/widgets` (`^26`/`^16`, up from `^2.3.0`/`^2.2.0`). Same viewer construction (background imagery, terrain), 3D Tileset loading (including the vertical-shift correction math), and no LayerControl/legend equivalent - the original never had one for Cesium either.

Unlike the OpenLayers/MapLibre migrations, the original `Cesium` component was never a React component - it was a single imperative function called once per page load, with no cleanup, no error handling on its async tileset/style loading, and two previously-dead CSS rules (`style.css` was never imported). `web-map-cesium-react` wraps the same logic in a `useEffect`-driven component and makes three deliberate additions the original didn't have, since a reusable library component can be mounted and unmounted repeatedly (Storybook, demos, consumer apps) unlike a one-shot page script:

- `viewer.destroy()` runs on unmount
- viewer/tileset creation failures are caught and logged instead of failing silently
- the credit-bar CSS rules are now actually imported and applied

The component mounts once; prop changes after the initial render aren't reflected, matching the original's single-mount design (Cesium never had a dynamic update path the way OpenLayers/MapLibre's TileMatrixSet switching does).
6 changes: 6 additions & 0 deletions .changeset/add-web-map-maplibre.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@xtramaps/web-map-maplibre": minor
"@xtramaps/web-map-maplibre-react": minor
---

Add `@xtramaps/web-map-maplibre` (core) and `@xtramaps/web-map-maplibre-react`: the MapLibre map component extracted from `ogcapi-html`, rebuilt on `@vis.gl/react-maplibre` and current `maplibre-gl` (`^6`) instead of the unmaintained `react-maplibre-ui`. Includes style/data-layer helpers, hover/click popups, and a `CanvasOverlay` (formerly `CanvasPlugin`) for rendering arbitrary React children into the map canvas.
6 changes: 6 additions & 0 deletions .changeset/add-web-map-openlayers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@xtramaps/web-map-openlayers": minor
"@xtramaps/web-map-openlayers-react": minor
---

Add `@xtramaps/web-map-openlayers` (core) and `@xtramaps/web-map-openlayers-react`: the OpenLayers map component migrated 1:1 from `ogcapi-html`, onto current `rlayers` (`3.9.0`) and `ol` (`10.8.0`) instead of the outdated `rlayers@1.1.1`/`ol@6.9.0`. Unlike the MapLibre extraction, this is a pure migration with no new LayerControl/legend equivalent — the original component never had one. The `globalThis._map.setCurrentTileMatrixSet` global bridge is replaced with a `forwardRef`/`useImperativeHandle`-based ref API.
8 changes: 7 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [["@xtramaps/legend-symbols-maplibre", "@xtramaps/legend-symbols-maplibre-react", "@xtramaps/legend-symbols-maplibre-svelte", "@xtramaps/legend-symbols-maplibre-vue"]],
"linked": [
["@xtramaps/legend-symbols-maplibre", "@xtramaps/legend-symbols-maplibre-react", "@xtramaps/legend-symbols-maplibre-svelte", "@xtramaps/legend-symbols-maplibre-vue"],
["@xtramaps/web-map-maplibre", "@xtramaps/web-map-maplibre-react"],
["@xtramaps/layer-control-maplibre", "@xtramaps/layer-control-maplibre-react"],
["@xtramaps/web-map-openlayers", "@xtramaps/web-map-openlayers-react"],
["@xtramaps/web-map-cesium", "@xtramaps/web-map-cesium-react"]
],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
Expand Down
48 changes: 48 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"permissions": {
"allow": [
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://raw.githubusercontent.com/CesiumGS/3d-tiles-samples/main/tilesets/TilesetWithDiscreteLOD/tileset.json\")",
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://cdn.jsdelivr.net/gh/CesiumGS/3d-tiles-samples@main/tilesets/TilesetWithDiscreteLOD/tileset.json\")",
"Bash(curl -s \"https://demo.ldproxy.net/\")",
"Bash(curl -s \"https://demo.ldproxy.net/daraa/collections?f=json\")",
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://demo.ldproxy.net/daraa/collections/AeronauticSrf/tiles3d\")",
"Bash(curl -s \"https://demo.ldproxy.net/daraa\")",
"Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2?f=json\")",
"Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2/collections?f=json\")",
"Bash(python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\([c['id'] for c in d['collections']]\\)\")",
"Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles?f=json\")",
"Bash(curl -s -w \"\\\\nHTTP:%{http_code}\\\\n\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles/tileset.json\")",
"Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles?f=html\" -o /tmp/globe.html -w \"%{http_code}\\\\n\")",
"Read(//tmp/**)",
"Bash(grep -n *)",
"Read(//private/tmp/**)",
"Bash(curl -sI \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles?f=json\")",
"Bash(curl -s \"https://demo.ldproxy.net/cologne_lod2/collections/building/styles/surface-type?f=3dtiles\")",
"Bash(curl -sI \"https://demo.ldproxy.net/cologne_lod2/collections/building/styles/surface-type?f=3dtiles\")",
"Bash(curl -sI \"https://sgx.geodatenzentrum.de/wmts_basemapde/tile/1.0.0/de_basemapde_web_raster_farbe/default/GLOBAL_WEBMERCATOR/10/340/550.png\")",
"Bash(curl -sI \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles?f=json\" -H \"Origin: http://localhost:6006\")",
"Bash(npx turbo *)",
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Cesium.js\")",
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Workers/\")",
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Widgets/widgets.css\")",
"Bash(curl -s -o /dev/null -w \"%{http_code}\\\\n\" \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Workers/createVerticesFromHeightmap.js\")",
"Bash(curl -sI \"https://unpkg.com/cesium@1.144.0/Build/Cesium/Workers/createVerticesFromHeightmap.js\")",
"Bash(npx biome *)",
"Bash(npm run *)",
"Bash(curl -sf http://localhost:8080/)",
"Bash(npx playwright *)",
"Bash(node -e \"require\\('playwright'\\); console.log\\('playwright module resolvable'\\)\")",
"Bash(node -e \"require\\('@playwright/test'\\); console.log\\('@playwright/test resolvable'\\)\")",
"Bash(node /private/tmp/claude-501/-Users-pascal-Documents-GitHub-xtramaps/321bbf2b-9197-4bba-af79-5791d6180135/scratchpad/check-cesium-demo.mjs)",
"Bash(node .tmp-check-cesium-demo.mjs)",
"Bash(curl -s -o /dev/null -w \"%{http_code} %{content_type}\\\\n\" -H \"Accept: application/json\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles\")",
"Bash(curl -s -H \"Accept: application/json\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles\")",
"Bash(curl -s -o /dev/null -w \"%{http_code} %{content_type}\\\\n\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles\")",
"Bash(curl -s -o /dev/null -w \"%{http_code} %{content_type}\\\\n\" -H \"Accept: application/json, text/plain, */*\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/3dtiles\")",
"Bash(curl -s -o /dev/null -w \"style: %{http_code} %{content_type}\\\\n\" \"https://demo.ldproxy.net/cologne_lod2/collections/building/styles/surface-type\")",
"Bash(lsof -ti:8080 -sTCP:LISTEN)",
"Bash(npx vitest *)",
"Bash(gh pr *)"
]
}
}
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ jobs:
- run: npm run lint
- run: npm run build
- run: npm run typecheck
- run: npm run test
25 changes: 25 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
stories: ["../packages/react/*/src/**/*.stories.@(ts|tsx)"],
framework: {
name: "@storybook/react-vite",
options: {},
},
addons: ["@storybook/addon-vitest"],
async viteFinal(config) {
// maplibre-gl spawns its worker via a relative sibling-file URL
// (maplibre-gl-worker.mjs). Vite's dependency pre-bundler copies packages
// into a content-hashed cache dir and breaks that relative reference,
// so the worker 404s and the map never renders tile data. Excluding
// maplibre-gl from pre-bundling serves it straight from node_modules,
// where the sibling worker file resolves correctly.
config.optimizeDeps = {
...config.optimizeDeps,
exclude: [...(config.optimizeDeps?.exclude ?? []), "maplibre-gl"],
};
return config;
},
};

export default config;
13 changes: 13 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Preview } from "@storybook/react-vite";

const preview: Preview = {
decorators: [
(Story) => (
<div style={{ height: "65vh", width: "100%" }}>
<Story />
</div>
),
],
};

export default preview;
6 changes: 6 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ Each library has: `package.json`, `tsconfig.json`, `vite.config.ts`, `src/index.
- `npm run lint` — lint and format check (Biome)
- `npm run lint:fix` — auto-fix lint/format issues
- `npm run typecheck` — type-check all packages
- `npm run test` — run all Storybook stories as automated tests (Vitest browser mode, real Chromium via Playwright)
- `npm run storybook` — start the Storybook dev server (root-level, covers all packages)
- `npm run build-storybook` — build a static Storybook

## Key Conventions

Expand All @@ -43,6 +46,8 @@ Each library has: `package.json`, `tsconfig.json`, `vite.config.ts`, `src/index.
- Core deps are listed in `dependencies` (not peers)
- Biome handles formatting and linting — no Prettier or ESLint
- Changesets for versioning — run `npx changeset` after changes
- Storybook config lives at the repo root (`.storybook/`); stories live next to the code they cover (`packages/react/<lib>/src/*.stories.tsx`), not in a separate app. `@storybook/addon-vitest` runs every story (including its `play` function) as a Vitest test in real Chromium — no separate test framework needed
- Playwright's Chromium binary installs automatically via the root `prepare` script (`npm install` → `playwright install chromium`)

## Adding a New Library

Expand All @@ -56,3 +61,4 @@ Each library has: `package.json`, `tsconfig.json`, `vite.config.ts`, `src/index.
1. `npm run lint` passes
2. `npm run build` succeeds
3. `npm run typecheck` passes
4. `npm run test` passes (if stories exist for the changed package)
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ Monorepo for libraries enhancing web map clients.
| `@xtramaps/legend-symbols-maplibre-react` | React bindings |
| `@xtramaps/legend-symbols-maplibre-vue` | Vue bindings |
| `@xtramaps/legend-symbols-maplibre-svelte` | Svelte bindings |
| `@xtramaps/web-map-maplibre` | MapLibre web map core logic (styles, data layers, popups) |
| `@xtramaps/web-map-maplibre-react` | React MapLibre map component |
| `@xtramaps/layer-control-maplibre` | LayerControl core logic (groups, radio-groups, merge-groups) |
| `@xtramaps/layer-control-maplibre-react` | React LayerControl component |
| `@xtramaps/web-map-openlayers` | OpenLayers web map core logic (projections, view/source switching) |
| `@xtramaps/web-map-openlayers-react` | React OpenLayers map component |
| `@xtramaps/web-map-cesium` | Cesium web map core logic (viewer construction, 3D Tileset loading) |
| `@xtramaps/web-map-cesium-react` | React Cesium map component |

## Setup

Expand All @@ -23,6 +31,7 @@ npm install
npm run build # Build all packages
npm run typecheck # Type-check all packages
npm run lint # Lint all packages
npm run test # Run every Storybook story as an automated test (Vitest + real Chromium)
```

Turborepo handles task orchestration and caching. To build a single package:
Expand All @@ -31,6 +40,16 @@ Turborepo handles task orchestration and caching. To build a single package:
npx turbo build --filter=@xtramaps/legend-symbols-maplibre
```

## Trying things out manually

- **Storybook** (`npm run storybook`, then open `http://localhost:6006`) is the main way to browse and interact with components — currently covers `legend-symbols-maplibre`, `layer-control-maplibre`, `web-map-openlayers`, and `web-map-cesium`. `npm run build-storybook` builds a static version. Storybook's CLI requires Node `>=20.19`; see `engines` in `package.json`.
- Some packages also ship a standalone HTML demo under `examples/*.html` (see that package's README). These aren't published anywhere yet, so they load sibling packages from local `dist/` output instead of a CDN — they only work when served over `http://`, not opened directly as a `file://` URL (browsers block ES module imports there). Any static file server works, e.g. from the repo root:
```sh
npm run build
python3 -m http.server 8080
```
then open e.g. `http://localhost:8080/packages/react/web-map-maplibre/examples/web-map.html`.

## Project Structure

```
Expand Down
Loading
Loading