Skip to main content

Overview

OnlyMap JS (@nika-js/onlymap) builds an interactive WebGL map from a small set of HTML custom elements. You write the layers, widgets, popups, interactions, and guided tours as markup. The library does the rendering, the data loading, the live updates, the picking, and the validation. You do not write deck.gl setup code, shader code, or a build step. One HTML file is a complete map application. OnlyMap JS is also made for AI agents to write. HTML is a reliable output format for a model. The package includes an agent skill and an llms.txt file. OmMap.validate() returns structured errors, and each error has a fix. Together these give an agent a real authoring loop.

Install

Install the package from npm:
Import the library and its stylesheet:
You can also load it from a CDN with no build step:
Two optional commands complete the setup:
  • npx @nika-js/onlymap init — adds VS Code IntelliSense for every om-* element and attribute. It also adds !-prefix snippets (!starter, !map, …).
  • npx -y skills add NikaGeospatial/onlymapjs --skill onlymapjs — installs the OnlyMap authoring skill into coding agents (Claude Code, Cursor, Codex). The agents then write correct manifests on the first try.

Key Features

Declarative Everything

  • 37 layer types by name: all of deck.gl’s core, geo, aggregation, and mesh layers (Scatterplot, GeoJson, Arc, Path, Heatmap, Hexagon, Trips, Tile, Tile3D, Scenegraph, and more). It adds a built-in PopupLayer for WebGL labels at scale, a native COGLayer for GeoTIFF rasters, a ZarrLayer for chunked Zarr/GeoZarr rasters, an ImageOverlay for georeferenced drone photos, and a BIMLayer for in-browser IFC models.
  • Live manifest: the markup is the state. When you change an attribute — from a widget, a behavior, a script, or devtools — the map updates automatically.
  • Built-in undo and redo: you can undo a layer toggle, a filter change, a basemap switch, an element edit, or a drawn sketch. Use <om-widget type="undo-redo"> or Cmd/Ctrl-Z.

A Full 3D Scene, Still Just Attributes new in 0.3

  • Terrain: terrain="terrarium" adds real elevation with no configuration (keyless AWS tiles). Geographic layers drape onto the surface automatically. You can also give your own DEM tile URL or register presets.
  • Scene lighting: set lighting="daylight|studio|flat|custom", with lighting-* attributes for fine control. lighting-sun-date computes the sun position from a date and the map location. A lighting widget shows these as presets and sliders.
  • Snapshots: map.snapshot() captures the basemap and the WebGL canvas together, as a PNG data URL or a Blob. Use it for print and export pipelines.
  • BIM / IFC models new in 0.6.1: <om-layer type="BIMLayer" src="./model.ifc"> (or the ifc-loader drop-zone widget, with multi-model federation) parses IFC in the browser via CDN-fetched web-ifc and renders it as 3D Tiles. pick-features gives per-element picking on any 3D Tiles layer; declarative isolate/hide/ghost, colour-by-property, an ifc-browser model tree, a feature-inspector, and an ifc-clash overlay come as attributes and widgets.
  • Cut, export, and pick inside a 3D scene new in 0.6.2: three tools that work on whatever 3D content is already loaded — 3D Tiles, BIM models, extruded vector geometry alike. A clip box (<om-map clip-box-min="[lng,lat,elev]" clip-box-max="…">, or the clip-box widget with draggable face handles) cuts the scene open; clip-box-invert shows what is outside instead, clip-box-highlight dims the cut-away geometry instead of removing it, and clip="off" opts a layer out. Region export (<om-widget type="draw" export-3d>) downloads the 3D content inside a drawn footprint as a portable GLB, or export-3d="b3dm" for Cesium and 3D Tiles pipelines. pickable="3d" opts a layer into depth picking, so a click on a building face resolves on the face and carries a real elevation, instead of the ground point behind it.
  • Snapping to real geometry new in 0.6.2: <om-map snap="vertex edge midpoint" snap-tolerance="12"> refines a click or hover to the nearest vertex, edge, or edge midpoint of whichever feature is under the cursor — including a BIM model’s own wall corners. Vertex wins over midpoint over edge; hold Space to place a point without snapping, the usual CAD convention. snap="off" opts a layer out.
  • 3D city models new in 0.5: point data at a CityJSON file (.city.json, or a streamed .city.jsonl) — national building datasets such as 3DBAG and PLATEAU. The library decodes it to extruded footprints by default, or to real per-face roof geometry with ?om-surfaces=1, and reprojects national coordinate grids (NL, CH, DE, JP, AT, SG) automatically.

Data That Stays Fresh

  • Formats: JSON/GeoJSON, CSV/TSV, Apache Arrow and GeoArrow (columnar fast path, zstd IPC), Shapefile with .dbf attributes, KML, GPX, the cloud-native FlatGeobuf (.fgb) and GeoParquet (.parquet) vector formats, and CityJSON / CityJSONSeq (semantic 3D city models). Large parsers load on first use.
  • GeoTIFF and Cloud-Optimized GeoTIFF new in 0.3.1: <om-layer type="COGLayer" src="./dem.tif"> streams raster tiles by HTTP Range request. The min/max restretch and the colormap are GPU uniforms, so the tiles are not fetched again. Nodata pixels are transparent.
  • Zarr / GeoZarr rasters new in 0.5.11: <om-layer type="ZarrLayer" src="./x.zarr" variable="temp" select="time=0"> renders chunked N-dimensional array data (climate/weather grids, datacubes) on the GPU. Pin the non-spatial dimensions with select; GeoZarr stores georeference themselves, a plain Zarr takes manual bounds/crs/spatial-dims. src can point at any CORS-enabled public remote store — no server setup needed.
  • Georeferenced drone photos new in 0.5.7: <om-layer type="ImageOverlay" src="./DJI_0123.jpg" georeference="exif"> reads a JPEG’s EXIF/DJI-XMP, computes its WGS84 footprint, and overlays the image — no manual corner-pinning. OmMap.resolveImageOverlay(file) preprocesses once for saved maps; explicit bounds reconstructs without re-reading EXIF.
  • Live sources: WebSocket streams with upsert-by-key and burst coalescing. Polled REST snapshots with refresh="5s".
  • Private endpoints: OmMap.configureData({ headers, credentials, fetch }) keeps credentials out of the markup.

Application UI Without an App Framework

  • Widgets: legend, layer switcher, basemap switcher, lighting controls, zoom controls, undo/redo, scale bar, attribution, GPU filter sliders (including readable date labels for epoch-millisecond fields), live Vega-Lite charts, a drawing toolbar, a measure tool, a clip-box control, and an event-driven dynamic-chart. You can also write custom widgets with a typed context (ctx.data, ctx.stats, ctx.viewport, ctx.emit).
  • Measurement, including cut/fill volume new in 0.6.2: <om-widget type="measure" modes="distance area volume"> measures geodesic distance and area, and — in volume mode — real earthworks cut and fill. Outline a footprint and the library integrates the terrain DEM cell by cell against a base surface, so one shape can report cut and fill on sloping ground, each with a published ± error quoted against the DEM’s own ground-sample distance. Choose the base surface with base-surface (custom drag-a-plane, triangulated boundary TIN, plane, lowest, highest, average). density, swell, and shrink add Bank/Loose/Compacted volumes and tonnage; profile samples an elevation profile around the footprint for a dynamic-chart to plot. Volume mode needs terrain on the map.
  • Safe date formatting: formatDate($time, 'datetime', 'UTC') turns epoch-millisecond or ISO fields into readable get-text labels without enabling arbitrary JavaScript. The same date style and time-zone contract is available on the built-in filter widget.
  • Managed, adaptive layout new in 0.4: position puts a widget in one of 8 logical slots (top-startbottom-end), and the library sets the pixel position. Widgets in the same slot stack with a shared gap, and small controls next to each other join into one group. The map attribution and the license badge sit in the bottom slots as normal members, so they do not cover a widget. On narrow maps (640px or less by default; change it with --om-widget-fold-breakpoint) the widgets fold into drawers, and a slot dims when an open popup covers it. <om-map widgets-hidden> hides all your widgets but keeps them in the page, and position="manual" turns off management for one widget.
  • Symbology-aware legends new in 0.3: the legend uses each layer’s real color styling. A continuous scale() ramp becomes a gradient bar with domain labels. A threshold scale becomes discrete class ranges. A categorical expression becomes a swatch palette. A raster colormap becomes its ramp. You do not configure the legend.
  • Themeable from plain CSS: each built-in widget reads --om-widget-* custom properties (om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }). You can set a dark theme with one CSS rule and no JavaScript.
  • Overlays and behaviors: HTML popups anchor to map coordinates or to a picked feature. Declarative on="click|hover|drag|load" interactions need no event-handler code.
  • Map stories: an <om-story> has steps that fire the same actions as behaviors, including basemap, lighting, and terrain changes. A player widget plays the steps, and you can scrub to any step.

Built for Verification

  • OmMap.validate(html) returns structured errors, and each error has a fix. Add the validate attribute for a live error panel on the page.
  • Headless test harness: mountForTest runs full behavioral tests (picks, widgets, overlays, real projection math) in jsdom or happy-dom, with no WebGL.
  • Layout audit new in 0.4: npx @nika-js/onlymap check-layout <manifest.html> checks the real widget layout in headless Chromium at several viewport widths. It confirms that no widgets overlap, that each widget is hit-testable and in its slot, and that same-slot edges align. It prints structured {severity, element, message, fix} diagnostics and exits 0 or 1. You can also import auditLayout(page) into your own Playwright suite.
  • React adapter: @nika-js/onlymap/react gives <OmMap>, <OmLayer>, <OmWidget>, <OmOverlay>, and a typed useOmMap() hook over the same core. React keeps the state, and the adapter does not conflict with the virtual DOM.
  • Custom layer classes: @nika-js/onlymap/deck re-exports the bundled deck.gl classes. OmMap.registerLayer extensions use the renderer’s own class hierarchy. A desktop app can register domain layers (local-file rasters, catalog sources) as first-class manifest types.

Framework & Programmatic Integration new in 0.4

  • MapController: a programmatic front-end for frameworks. Typed LayerDescriptors drive the same reconcile core, with no DOM manifest. It adds camera methods, emit, injectPick, snapshot, and onViewChange/onMapPoint/onTilesetLoad callbacks.
  • External-store contract: getStore(token) gives a {subscribe, getSnapshot} store for each token (viewport, selection, layers, data:<id>). The snapshots are cached, immutable, and tagged with an origin. You can use them directly with React’s useSyncExternalStore, MobX, Redux, or Zustand.
  • Consumer events: om-view-changed fires when the camera settles. om-map-point fires on each click or hover with the map coordinate — use it for custom capture tools that the draw widget does not cover. om-tileset-load gives a Tile3DLayer’s live Tileset3D, for tools such as region export. om-measure carries the measure widget’s full readout — distance, area, cut/fill volumes with their error bounds, material-adjusted figures, and the elevation profile series — so you can render your own readout instead of the built-in panel.

Roadmap

These features are designed but not shipped yet: Mapbox GL basemaps, depth-interleaved 3D compositing, globe projection, SSE transport, multi-field filters, dblclick behaviors, the transform data pipeline, the typed fluent builder, and stories and draw as React components. The README lists the current state in its “Not implemented yet” section at each release.

Resources