A GeoJSON editor for MapLibre GL JS.
Warning
v2 — Unstable.
Waymark JS renders GeoJSON layers on a MapLibre GL JS map. It is headless: the consumer creates and owns the map, container, style, camera, popups and cursors; Waymark mounts and manages its GeoJSON overlay on the injected map.
Waymark renders circle, line and fill geometry families with per-layer and typed paint (including icon name hooks), and keeps a serialisable document model that round-trips through toJSON(). It emits DOM CustomEvents for layer lifecycle, runtime state and feature interaction — presentation stays in the consumer's hands.
MapLibre GL JS is a peer dependency. Waymark ships no MapLibre code, worker or CSS: install MapLibre alongside Waymark and import MapLibre's stylesheet yourself.
npm install waymark-js maplibre-glimport { Map } from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { createInstance } from "waymark-js";
// The consumer creates and owns the map.
const map = new Map({
container: "map",
style: "https://tiles.openfreemap.org/styles/bright",
center: [-128.0097, 50.6539],
zoom: 13,
});
// Waymark mounts its GeoJSON overlay onto the injected map.
const instance = createInstance({
map,
document: {
config: { id: "map" },
data: {
layers: [
{
data: {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: { title: "The Scarlet Ibis" },
geometry: { type: "Point", coordinates: [-128.0097, 50.6539] },
},
],
},
},
],
},
},
});
// Presentation is consumer-owned: Waymark emits, you decide what to show.
instance.on("waymark:feature.click", (event) => {
const { feature, lngLat } = event.detail;
console.log(feature.properties, lngLat);
});
// Call destroy() when finished to remove Waymark-owned layers and listeners.
// The map itself stays yours to remove with map.remove().
// instance.destroy();Events are dispatched as CustomEvents on the injected map's container (map.getContainer()). Use the standard on/off/once API:
instance.on("waymark:feature.click", handler);
instance.once("waymark:instance.destroyed", handler);| Group | Events |
|---|---|
| Lifecycle | waymark:instance.created, waymark:instance.destroyed |
| Data layers | waymark:data.layer.added, waymark:data.layer.mounted, waymark:data.layer.error |
| State | waymark:state.changed, waymark:state.debug.changed, waymark:state.types.visibility.changed |
| Feature interaction | waymark:feature.click, waymark:feature.hover, waymark:feature.leave |
Popups, tooltips and cursor styling are consumer-owned. See the API reference for payload shapes.
instance.toJSON() returns the canonical InstanceDocument: a plain, serialisable object with config, state and data slices.
const document = instance.toJSON();
// The document is safe to persist and round-trip.
const restored = createInstance({ map: anotherMap, document });config— authored/default intent, kept stable across serialisation.state— runtime delta only; unchanged branches are omitted.data.layers[]— canonical{ type: "geojson", data }layers.
Runtime-enriched metadata (MapLibre source/layer IDs, lifecycle phase) is excluded, and map/camera/basemap state is never serialised — the consumer's map owns those.
npm run dev # Start the dev playground
npm run build # Build the library to dist/waymark.js
npm run check:dist # Assert the dist output shape (single bundle, bare maplibre-gl import, no CSS/worker/Vue)
npm run format # Format with Prettier
npm run fixtures:refresh # Regenerate the vendored basemap fixtures (fetches live assets; deliberate act)
npm run docs:sync # Regenerate llms-full.txt; check docs contract and API doc sync
npm run check:llms # Fail if docs/llms-full.txt or the docs/public copies are stale
npm run docs:dev # Serve the docs site locally (Nuxt dev server)
npm run docs:generate # Generate the static docs site to docs/.output/public
npm test # Unit tests (Vitest)
npm run test:browser # Functional browser tests (Playwright)
npm run screenshots # Regenerate the committed browser screenshots
npm run screenshots:verify # Regenerate into test-results/ and byte-compare against the committed set
npm run test:ci # Full CI gate: format → llms check → docs sync → build → dist check → docs generate → unit → browser testsFour GitHub Actions workflows automate the release pipeline:
- CI (
.github/workflows/ci.yml) — validates every PR and push tomasterwith the fulltest:cigate (functional browser tests only; no committed pixels). - Release (
.github/workflows/release.yml) — runs on every push tomaster; when the currentpackage.jsonversion has no existing tag, creates the tag and a GitHub Release with auto-generated release notes. - Docs (
.github/workflows/docs.yml) — builds the Nuxt docs site onmasterand deploys it to GitHub Pages; published at https://www.ogis.org/waymark-js/. - Screenshots identity (
.github/workflows/screenshots.yml) — regenerates the committed screenshots onmacos-15-inteland byte-compares them against the committed set as an environment-drift canary; uploads the verify report on failure.
- API reference — Consumer API contract, map injection, config defaults, events, and serialisation
- Development guide — Contributor workflow, conventions, and testing
- Instances — Internal lifecycle and serialisation semantics
- Map injection — Ownership boundaries, mounting, and cleanup
- Data & GeoJSON — Layer contract, validation, and runtime behaviour
- LLM-readable docs — Machine-readable index, with the single-file bundle, also published with the docs site
- Docs index
