@nika-js/onlymap 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/README.md +18 -6
- package/THIRD-PARTY-LICENSES.md +27 -0
- package/dist/{LercDecode.es-fbo6h5rt.js → LercDecode.es-CJnypw8j.js} +1 -1
- package/dist/{basemap-CprSan9q.js → basemap-DrQ0-eyR.js} +1 -1
- package/dist/cityjson-D_V5GY8b.js +332 -0
- package/dist/crs-Ciu7Xs7a.js +108 -0
- package/dist/crs.d.ts +50 -0
- package/dist/ctx.d.ts +8 -1
- package/dist/elements/om-map.d.ts +26 -2
- package/dist/elements/om-overlay.d.ts +1 -1
- package/dist/elements/om-widget.d.ts +4 -0
- package/dist/feature-colors.d.ts +41 -0
- package/dist/{geoparquet-Bm8pfxyo.js → geoparquet-Dix4lTNy.js} +1 -1
- package/dist/html-data.d.ts +1 -1
- package/dist/ifc-Dc-TdSJr.js +877 -0
- package/dist/ifc.d.ts +406 -0
- package/dist/{index-Bz-rbmhj.js → index-1UgNlfGR.js} +20892 -18886
- package/dist/{index-GOwMjqm6.js → index-BQMjW5w0.js} +1 -1
- package/dist/{index-DSRIZR81.js → index-B_1PPJgC.js} +1 -1
- package/dist/{index-CCQttJzO.js → index-CAuT5j9Y.js} +2 -2
- package/dist/{index-UzmNhuS0.js → index-CXPaeisL.js} +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/layer-registry.d.ts +14 -0
- package/dist/layers/bim-layer.d.ts +104 -0
- package/dist/layers/feature-mesh-layer.d.ts +288 -0
- package/dist/layers/gltf-scene-walk.d.ts +55 -0
- package/dist/{lerc-CuifOkoA.js → lerc-CbTjQ7uI.js} +2 -2
- package/dist/meshopt_simplifier-Co6uRDDA.js +370 -0
- package/dist/onlymap.standalone.js +57996 -54706
- package/dist/onlymapjs.js +69 -67
- package/dist/programmatic.d.ts +22 -2
- package/dist/{raster-dysYIQ_k.js → raster-0b0nSHUh.js} +2 -2
- package/dist/{raster-pipeline-ZdCOGfqV.js → raster-pipeline-D8siq7-4.js} +1 -1
- package/dist/react/om-overlay.d.ts +9 -0
- package/dist/react.js +144 -144
- package/dist/runtime-core.d.ts +110 -3
- package/dist/selection.d.ts +33 -0
- package/dist/site-placement.d.ts +85 -0
- package/dist/testing.d.ts +6 -2
- package/dist/tile3d-metadata.d.ts +185 -0
- package/dist/version.d.ts +1 -1
- package/dist/widget-registry.d.ts +11 -0
- package/dist/widgets/ifc-clash.d.ts +18 -0
- package/dist/widgets/ifc.d.ts +41 -0
- package/dist/{zarr-OewK7k2K.js → zarr-DCYro_Vs.js} +79 -66
- package/docs/testing.md +2 -2
- package/llms.txt +3 -3
- package/onlymapjs.html-data.json +152 -0
- package/package.json +3 -1
- package/skills/onlymapjs/SKILL.md +4 -2
- package/skills/onlymapjs/references/react.md +1 -1
- package/skills/onlymapjs/references/syntax.md +298 -11
- package/dist/cityjson-urQeujQv.js +0 -407
package/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,21 @@ Note: npm collapsed a few closely-spaced releases — the GPX/FlatGeobuf (0.5.4)
|
|
|
8
8
|
and GeoParquet (0.5.5) work shipped to npm together as **0.5.6**, so npm's
|
|
9
9
|
version list jumps 0.5.3 → 0.5.6. Each logical version is listed here regardless.
|
|
10
10
|
|
|
11
|
-
## 0.6.
|
|
11
|
+
## 0.6.1 — unreleased
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **`selection-type` on `<om-overlay>`** (React: `selectionType` on `<OmOverlay>`): scope a selection-anchored overlay to one pick type — `"click"` or `"hover"`. Fixes the reported ghost-popup bug: a popup wired to open on click re-anchored *and re-interpolated its template* on every hover pick (deck fires hover on all pointer movement), so it followed the pointer onto other features and rendered their objects into the wrong template with blank fields — with no author-side workaround. With `selection-type="click"`, hover picks are inert; a click on empty space still dismisses (empty picks now carry the pointer-event type internally), while hovering empty space no longer does. Absent attribute keeps the old behavior. Validation warns on a typoed value and on `selection-type` without `anchor-from="selection"`; the test harness's `clearSelection()` gains a kind (`"hover"` default, `"click"` for empty-space clicks).
|
|
15
|
+
- **BIM / IFC support**: `BIMLayer` loads `.ifc` files entirely in the browser (web-ifc WASM, CDN-fetched + integrity-pinned on first use, never bundled) and renders them as 3D Tiles; per-element **feature picking** on `Tile3DLayer` (`pick-features`, EXT_structural_metadata property tables, texture-backed IDs included); declarative **isolate / hide / ghost** (`feature-filter-field` + `isolate-features`/`hide-features`/`ghost-features`) and **style-by-property** (`feature-color-by`/`feature-color-scale`/`feature-palette`); **multi-model federation** (`ifc-loader federate`) and an AABB **clash-detection overlay**; widgets `ifc-loader`, `ifc-browser`, `feature-inspector` (alias `ifc-inspector`), `ifc-clash`; IFC georeferencing via `IfcMapConversion` (UTM + non-UTM projected CRS, grid-convergence heading correction) with a structured warning when a model's placement can't be trusted.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **Explicit-terrain contract for georeferenced BIM**: the auto-terrain reaction — the library writing `terrain="mapterhorn"` onto the map at load time — was removed as a violation of the what-you-write-is-what-you-see contract. In its place, a model that actually resolves real-world elevation (`IfcMapConversion` + `OrthogonalHeight`) loading on a map with no `terrain` attribute raises a structured **error** through the validation channel (report-only, never a write; any authored value satisfies it, including an explicit `terrain="off"`). Checked at load time against the file's resolved facts, so flat models with no elevation data don't false-positive.
|
|
19
|
+
- Same contract for **basemap**: the `ifc-loader` widget no longer switches the map's `basemap` on/off as a side effect of a drop — `<om-map>` scene attributes are author-owned. A georeferenced model landing on a map with no basemap and no terrain raises a structured "no spatial context" warning instead.
|
|
20
|
+
- Widgets gained a `destroy` teardown hook; removing an `ifc-loader` (or its map) now releases all loaded-model resources.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **`ZarrLayer` ignored `select` changes**: writing the `select` attribute (the documented animation path) re-rendered stale cached chunks instead of re-slicing — deck's `TileLayer` only refetches when `updateTriggers.getTileData` changes, and that trigger was never wired. Scrubbing or animating any non-spatial dimension now repaints; the store stays open and cached across frames. Also guards a first-paint race on the same path: the frame drawn right after a re-slice could rasterize the reprojection mesh incompletely and stick (nothing scheduled another frame on an idle map) — one extra redraw is now poked in after the tiles load.
|
|
24
|
+
|
|
25
|
+
## 0.6.0 — 2026-08-06
|
|
12
26
|
|
|
13
27
|
### Changed
|
|
14
28
|
- **Licensing gate reworked: free-tier caps apply only on hosted http(s) pages.** In a dev context — localhost/loopback, `file://`, any non-web scheme, or headless — every cap (5 layers / 25k rows / 20 MB) lifts entirely. The attribution badge stays in all contexts. The exemption is a technical convenience, not a license grant: commercial deployment (hosted or packaged) still requires a commercial key — enforcement for shipped apps is legal, not technical (LICENSE.md §3).
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
<om-widget type="legend" position="top-right"></om-widget>
|
|
27
27
|
|
|
28
|
-
<om-overlay id="detail" anchor-from="selection" visible="false">
|
|
28
|
+
<om-overlay id="detail" anchor-from="selection" selection-type="click" visible="false">
|
|
29
29
|
<div><b>{{place}}</b> — M {{magnitude}}</div>
|
|
30
30
|
</om-overlay>
|
|
31
31
|
<om-behavior on="click" layer="quakes" action="show-overlay" target="detail"></om-behavior>
|
|
@@ -80,9 +80,9 @@ A handful of elements, one rule: **attributes are kebab-case versions of deck.gl
|
|
|
80
80
|
| Element | Role |
|
|
81
81
|
|---|---|
|
|
82
82
|
| `<om-map>` | The map. `center`, `zoom`, `pitch`, `bearing`; `basemap` takes a free preset (`positron`, `liberty`, `dark-matter`, `osm`, …), a style URL, or `"none"` (standalone canvas) — and switches **live**; `validate` for a live on-page error panel. Give it a height: a custom element is `display:inline` by default, so the library injects a `display:block` default (fills a sized parent, else a 400px floor) to keep a bare map visible, but set an explicit height (`om-map { height: 100vh }`) for real layout — any height you set wins outright, including one below the floor. A map that still collapses warns in the console; `hidden` and `display:none` maps stay hidden and stay quiet. |
|
|
83
|
-
| `<om-layer>` | Any of **
|
|
84
|
-
| `<om-widget>` | UI panels. Built-ins: `legend` (symbology-aware: color scales render as gradient ramps or class ranges, categorical ternaries as discrete palettes), `layer-switcher`, `basemap-switcher`, `lighting`, `zoom-controls`, `undo-redo`, `scale-bar` (metric/imperial/nautical `units`), `attribution`, `filter`, `draw`, `measure` (geodesic distance + area — live labels, `units` toggle, an `om-measure` readout event), `vega-lite` (live charts)
|
|
85
|
-
| `<om-overlay>` | Rich HTML anchored to a map location — a static `anchor="[lng, lat]"`, the current selection, or a feature's own geometry via `anchor-layer`/`anchor-feature-id`. `{{field}}` interpolates the picked feature, HTML-escaped by default. |
|
|
83
|
+
| `<om-layer>` | Any of **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, …) plus the built-in `PopupLayer` for WebGL badges/labels at scale, the native `COGLayer` for GeoTIFF rasters, `ZarrLayer` for chunked Zarr/GeoZarr rasters, `ImageOverlay` for georeferenced drone JPEGs, and `BIMLayer` for BIM source files (`.ifc` today) loaded straight in the browser, no pre-conversion step. `id` required; `label`/`color` feed the legend. |
|
|
84
|
+
| `<om-widget>` | UI panels. Built-ins: `legend` (symbology-aware: color scales render as gradient ramps or class ranges, categorical ternaries as discrete palettes), `layer-switcher`, `basemap-switcher`, `lighting`, `zoom-controls`, `undo-redo`, `scale-bar` (metric/imperial/nautical `units`), `attribution`, `filter`, `draw`, `measure` (geodesic distance + area — live labels, `units` toggle, an `om-measure` readout event), `vega-lite` (live charts), and the BIM set `ifc-browser` / `feature-inspector` / `ifc-loader` / `ifc-clash`. Or write your own inline with HTML + a `<script type="om/widget">`. Adjacent compact button widgets (`zoom-controls`, `undo-redo`, `widgets-toggle`) **auto-cluster** into one control group (opt out per widget with `cluster="false"`), and `<om-map widgets-hidden>` / the `set-widgets-visible` action / `<om-widget type="widgets-toggle">` hide all authored chrome without destroying it — provider attribution and the license badge never hide. **Placement is managed**: `position` takes one of 8 logical, RTL-aware slots (`top-start`, `top-center`, `top-end`, `center-start`, `center-end`, `bottom-start`, `bottom-center`, `bottom-end`; legacy corner names alias) — same-slot widgets stack with flush edges and a shared gap, `order` sets in-slot ordering, and `position="manual"` opts out entirely (a plain block you style yourself, even outside the map). At map widths ≤640px, managed widgets automatically move into accessible top/end/bottom/start drawers; `fold="never"` keeps an essential control out, `widgets-fold="off"` disables folding, and `--om-widget-fold-breakpoint` changes the map-width threshold. Provider attribution is an in-flow member of `bottom-end` and the license badge of `bottom-start`, so neither covers a widget. A slot dims automatically while an open popup covers it (`widgets-dim="off"` to disable), except slots containing required chrome. Themeable from plain page CSS via custom properties: `om-map { --om-widget-bg: #111827; --om-widget-fg: #f9fafb; }` (also `-muted`, `-border`, `-hover-bg`, `-accent`), plus layout tokens (`--om-widget-inset-x/-y`, `--om-widget-gap-x/-y`, `--om-widget-opacity`, `--om-widget-radius`) or the no-CSS sugar `<om-map widget-style="gap:10 opacity:0.9">`. |
|
|
85
|
+
| `<om-overlay>` | Rich HTML anchored to a map location — a static `anchor="[lng, lat]"`, the current selection, or a feature's own geometry via `anchor-layer`/`anchor-feature-id`. Selection-anchored overlays scope with `layer` (one layer's picks) and `selection-type="click"`/`"hover"` (one pick type — give a click-opened popup `selection-type="click"` so hovering elsewhere doesn't drag it along). `{{field}}` interpolates the picked feature, HTML-escaped by default. |
|
|
86
86
|
| `<om-behavior>` | Declarative interactions: `on="click|hover|drag|load|data-loaded"` → a named action. |
|
|
87
87
|
| `<om-story>` | A storyboard: `<om-step>` children fire actions on a timeline. Controlled by the `player` widget, behaviors, or `storyEl.play()/pause()/seek()`. |
|
|
88
88
|
| `<om-fallback>` | What shows where scripts never run — chat-app/email file previews (iOS QuickLook renders HTML attachments with JS off), file managers, sandboxed webviews. A direct child of `<om-map>`, hidden automatically the moment the map boots; pages without one get a text-only default banner from the stylesheet. Good practice on any page that may travel as a file. The gate is pure CSS (`om-map:not(:defined)`), so `onlymapjs.css` must load without JS — a real `<link>`, a bundler-emitted sheet, or an inlined `<style>`. |
|
|
@@ -268,7 +268,19 @@ Plus `OmMap.snapshotIR(html)` to lock down what a manifest *means* in a snapshot
|
|
|
268
268
|
|
|
269
269
|
## 3D
|
|
270
270
|
|
|
271
|
-
`ScenegraphLayer` instances glTF/GLB models at coordinates (no three.js, no loader wiring), `Tile3DLayer` consumes OGC 3D Tiles, `GeoJsonLayer` extrudes polygons (`extruded get-elevation="$height"`), and the camera tilts via `pitch`/`bearing` attributes. Terrain is one attribute: `<om-map terrain="terrarium">` raises a real DEM surface (keyless AWS tiles; `maptiler-terrain` keyed; or a bring-your-own `{z}/{x}/{y}` DEM URL + `terrain-decoder`), geographic layers drape onto it automatically (per-layer `terrain="drape|offset|off"` overrides; 3D models sit ON the surface), `terrain-exaggeration` scales the relief, and `terrain-texture` drapes imagery. Terrain replaces an active basemap while on (flat canvas vs raised surface) and restores it when off. Scene lighting is declarative: `<om-map lighting="daylight">` (presets `daylight`/`studio`/`flat`/`custom`, tuned via `lighting-ambient`, `lighting-sun`, `lighting-sun-azimuth`/`-elevation`, or `lighting-sun-date` for a solar-position sun) — attribute-backed, so lighting changes are undoable and story-steppable via the `set-lighting` action. `<om-widget type="lighting">` gives users native preset radios + tuning sliders over the same attributes. Converting IFC/CAD upstream: [docs/3d-assets.md](docs/3d-assets.md).
|
|
271
|
+
`ScenegraphLayer` instances glTF/GLB models at coordinates (no three.js, no loader wiring), `Tile3DLayer` consumes OGC 3D Tiles, `GeoJsonLayer` extrudes polygons (`extruded get-elevation="$height"`), and the camera tilts via `pitch`/`bearing` attributes. Terrain is one attribute: `<om-map terrain="terrarium">` raises a real DEM surface (keyless AWS tiles; `mapterhorn` keyless too, paired with a CARTO Positron drape by default; `maptiler-terrain` keyed; or a bring-your-own `{z}/{x}/{y}` DEM URL + `terrain-decoder`), geographic layers drape onto it automatically (per-layer `terrain="drape|offset|off"` overrides; 3D models sit ON the surface), `terrain-exaggeration` scales the relief, and `terrain-texture` drapes imagery. Terrain replaces an active basemap while on (flat canvas vs raised surface) and restores it when off. **BIM models require explicit terrain**: a georeferenced model's absolute elevation only means anything against a DEM surface, and the library never writes attributes for you — a model that resolves real elevation on a map without a `terrain` attribute raises an error through the validation channel at load time (any authored value satisfies it, including an explicit `terrain="off"` for flat-ground siting). Scene lighting is declarative: `<om-map lighting="daylight">` (presets `daylight`/`studio`/`flat`/`custom`, tuned via `lighting-ambient`, `lighting-sun`, `lighting-sun-azimuth`/`-elevation`, or `lighting-sun-date` for a solar-position sun) — attribute-backed, so lighting changes are undoable and story-steppable via the `set-lighting` action. `<om-widget type="lighting">` gives users native preset radios + tuning sliders over the same attributes. Converting IFC/CAD upstream: [docs/3d-assets.md](docs/3d-assets.md).
|
|
272
|
+
|
|
273
|
+
**Per-element picking on 3D Tiles (BIM/photogrammetry).** A `pickable` `Tile3DLayer` picks a whole tile; adding `pick-features` picks an individual ELEMENT — a wall, a window, one IFC product — and the `selection` carries that element's `featureId`, `properties` and `class`, resolved from the tile's own `EXT_mesh_features` + `EXT_structural_metadata` (`feature-id-property` selects the ID set, default `_FEATURE_ID_0`). Isolate/hide/ghost are declarative and mirror the vector filter vocabulary — `feature-filter-field="component"` names the metadata field, then `isolate-features`/`hide-features`/`ghost-features` take JSON value lists (`ghost-opacity` tunes the fade); hiding is a shader discard, so a hidden element also stops being pickable and you can select what's behind it, and `isolate-features` is exclusive (anything unlisted is hidden). Because they're attributes they're undoable and story-steppable. Underneath, `feature-styles` recolours, fades or highlights elements by feature ID — an array indexed by id of `{color, strength, opacity}` — uploaded as a small lookup texture, so restyling costs no refetch and no re-tesselation. IDs stored per texel (how photogrammetry classification ships) are sampled per FRAGMENT, so element boundaries are pixel-sharp rather than snapped to triangle edges. Multi-material models fan out correctly (glTF allows one material per primitive, so a five-material house is five primitives and a real IFC export is often dozens); only genuinely instanced i3dm content falls back to tile-granularity picking. Two limits, stated plainly: texture-backed IDs need `load-options` with `gltf.loadImages`/`loadBuffers` **and** `image: {"type": "data"}` (without it loaders.gl reads the whole ID texture back through a canvas once per vertex and the tileset takes minutes to appear); and `opacity` below 1 currently blanks the model, so use `strength` to tint for now. See `dev/examples/ferry-building-features.html`.
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
**The BIM widgets.** Three built-ins turn a `pick-features` tileset into a usable model browser without page JS. `ifc-browser` (renamed from `ifc-legend`, which still works — it browses a model, it doesn't explain symbology) groups the model by any property-table field (`fields="ifcClass material container spatialPath"`), counts each value, and gives every row I/H/G buttons that write `isolate-features`/`hide-features`/`ghost-features` — so the panel is a UI over the attributes, and everything it does is undoable and story-steppable; it keeps the companion outline layer's `filter-categories` in step too, since hidden elements would otherwise leave their edges behind. It also offers whichever **model trees** the file supports — spatial (IfcSite → IfcBuilding → IfcStorey), type, system, and classification — each expandable, counts aggregated upward, the same I/H/G on every node so isolating a storey, a duct system or a CCS code reaches everything beneath it. Spatial isn't privileged: on a real Danish project the classification tree covered 3,415 elements to spatial's 660. A tree isn't a second mode or a second widget — it's a group-by on a metadata column, which is why it reuses the visibility attributes untouched (and why there's one browser per layer: those attributes are single-valued). `loadIfc` extracts every hierarchy a model has and emits a column only when the file populates it, so a tree that would render empty is never offered. `feature-inspector` (renamed from `ifc-inspector`, which still works — its body is generic property-row rendering with no IFC dependency, so it works unchanged on a non-IFC `pick-features` layer) shows the picked element's properties (`fields="ifcClass material container netVolume"`). `ifc-loader` is a drop zone that parses an `.ifc` in the browser and builds both layers for it; add `federate` and one drop zone accepts SEVERAL models into a co-registered scene, each with its own visibility toggle — the shape coordination tools use, because a real project has architecture, structure, mechanical, electrical and plumbing rather than two. `ifc-clash` compares any two loaded models. Colour is a question, not decoration: `feature-color-by` colours categorically by a field, `feature-color-scale` graduates over a numeric one, `feature-palette` and `feature-color-strength` tune them — and setting *neither* is the default, leaving the model in its own IFC surface colours. Widget scripts reach the same data through `ctx.features(layerId)` (the decoded property table) plus the `features` watch token, which fires when the table decodes off the first tile.
|
|
277
|
+
|
|
278
|
+
**Clash detection, coarse and honest.** `<om-widget type="ifc-clash">` flags element pairs from two co-registered models whose bounding boxes interpenetrate, colours both sides, and flies to the centre of each overlap. Results are grouped by element the way coordination tools report them — one wall crossing four ducts is one row, not four — with a highlight switch and an isolation mode (`Dim Other` / `Hide Other`, Navisworks' own terms) so the pair you selected is the only thing that reads. On a real architecture-vs-HVAC pair it finds 1,443 clashes across 9.6M candidate combinations in ~96 ms, entirely in the browser.
|
|
279
|
+
|
|
280
|
+
It is deliberately **not** Navisworks. An axis-aligned box test over-reports anything diagonal, and it cannot tell a resolved penetration from a collision: on that same pair, 676 of the results are air diffusers sitting inside ceilings, which is how buildings work. Treat the number as *"here are the places two disciplines occupy the same box — go look"*, not as a defect list. Persisting and sharing results is what BCF exists for and is not attempted here.
|
|
281
|
+
|
|
282
|
+
**In-browser IFC.** `loadIfc(bytes)` parses an `.ifc` with web-ifc (WASM) and returns a 3D Tiles model held entirely in memory — nothing uploaded, nothing written to disk. Because the output *is* a tileset, per-element picking, styling, `site-*` and isolate/hide/ghost work on it unchanged. It returns `tilesetUrl`/`edgesUrl` blob URLs, the property table, the georeferencing (`lonLat`, `heading`, `scale`, and a `headingSource` that distinguishes a real reading from an assumption), per-phase `timings`, and a `revoke()` you must call when swapping models. web-ifc is MPL-2.0 and is *not* a package dependency — it's dynamic-imported from unpkg on first use, so it never reaches the bundle and pages that never open an IFC pay nothing (measured: the IFC chunk is 8.5 KB gzipped and contains only the URL). **For offline, air-gapped or strict-CSP deployments**, `npm run vendor:web-ifc public/vendor/web-ifc` copies the four files you serve yourself, then `wasm-path="/vendor/web-ifc/"` on the loader — 1.37 MB gzipped of *static assets*, with the JS bundle unchanged. Note that web-ifc is only one network dependency: a georeferenced model also switches a basemap on, so an offline page wants `basemap="none"` and `telemetry="off"` too. See `dev/examples/bim-workbench.html`.
|
|
283
|
+
**Georeferencing is declarative too.** `site-origin="[lng, lat]"` (optionally `[lng, lat, elevation]`), `site-heading` (a bearing, degrees clockwise from true north) and `site-scale` place a model from the markup instead of baking a position into the tileset — `site-origin` overrides whatever the root transform carries, and rotation/scale pivot on the model's own anchor so a heading change spins the building about itself. They work on `Tile3DLayer` and on `PathLayer` (whose paths are then local east/north/up metres), which is what lets an IFC mesh and its outline overlay move together. This matters more than it sounds: authoring tools ship a default project location and a default is indistinguishable from a survey — the buildingSMART Medical-Dental Clinic sample carries Revit's Boston default, the Duplex a Chicago city-centre point — while `IfcMapConversion` is absent from most IFC2x3 exports and `TrueNorth` is routinely unset. Being attributes, corrections are undoable and story-steppable rather than a reconversion. Whenever a model resolves anything short of a real `IfcMapConversion` — `IfcSite` coordinates or no georeference at all — `BIMLayer` and `ifc-loader` raise a structured `"warning"` through the same validation channel other `om-layer` errors use (the `validate` on-page panel, `om-validation-error`'s `detail.warnings`), rather than only a status-line sentence a human has to notice; it never fails the map (`valid` stays `true`), it just names the layer and points at `site-origin`/`site-heading` as the fix. See `dev/examples/bim-sample.html`.
|
|
272
284
|
|
|
273
285
|
## Programmatic surface
|
|
274
286
|
|
|
@@ -300,7 +312,7 @@ The library reports one **deployment-scoped** usage snapshot per map per page lo
|
|
|
300
312
|
|
|
301
313
|
## Not implemented yet (honestly)
|
|
302
314
|
|
|
303
|
-
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/draw as React components (both work via the HTML manifest).
|
|
315
|
+
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/draw as React components (both work via the HTML manifest). On `pick-features`: instanced (i3dm) tiles keep tile-granularity picking, and `feature-styles`' `opacity` below 1 is still being validated. On BIM: clash detection is bounding-box only (no mesh-level test, and it cannot tell a resolved penetration from a collision), there is no BCF export, no equivalent of Navisworks' Auto Reveal, and prepared tilesets converted before the `bbox*` columns existed cannot take part in a clash pass — `strength` tinting and `opacity: 0` (hide, a shader discard) work.
|
|
304
316
|
|
|
305
317
|
## Going deeper
|
|
306
318
|
|
package/THIRD-PARTY-LICENSES.md
CHANGED
|
@@ -168,6 +168,7 @@ hand.
|
|
|
168
168
|
| long | 3.2.0 | Apache-2.0 |
|
|
169
169
|
| maplibre-gl | 5.24.0 | BSD-3-Clause |
|
|
170
170
|
| md5 | 2.3.0 | BSD-3-Clause |
|
|
171
|
+
| meshoptimizer | 1.2.0 | MIT |
|
|
171
172
|
| mgrs | 1.0.0 | MIT |
|
|
172
173
|
| minimist | 1.2.8 | MIT |
|
|
173
174
|
| mjolnir.js | 3.0.0 | MIT |
|
|
@@ -7178,6 +7179,32 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
7178
7179
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
7179
7180
|
```
|
|
7180
7181
|
|
|
7182
|
+
## meshoptimizer@1.2.0 (MIT)
|
|
7183
|
+
|
|
7184
|
+
```
|
|
7185
|
+
MIT License
|
|
7186
|
+
|
|
7187
|
+
Copyright (c) 2016-2026 Arseny Kapoulkine
|
|
7188
|
+
|
|
7189
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7190
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7191
|
+
in the Software without restriction, including without limitation the rights
|
|
7192
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7193
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
7194
|
+
furnished to do so, subject to the following conditions:
|
|
7195
|
+
|
|
7196
|
+
The above copyright notice and this permission notice shall be included in all
|
|
7197
|
+
copies or substantial portions of the Software.
|
|
7198
|
+
|
|
7199
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
7200
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
7201
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
7202
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
7203
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
7204
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
7205
|
+
SOFTWARE.
|
|
7206
|
+
```
|
|
7207
|
+
|
|
7181
7208
|
## mgrs@1.0.0 (MIT)
|
|
7182
7209
|
|
|
7183
7210
|
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ny, L as sy, M as oy, m as ay, c as ka, z as oc, a as Sd, G as sm, V as ly, W as cy, b as uy, g as hy, d as Vu, f as Ed, e as dy, l as py, u as fy, D as Qf, h as my } from "./index-
|
|
1
|
+
import { C as ny, L as sy, M as oy, m as ay, c as ka, z as oc, a as Sd, G as sm, V as ly, W as cy, b as uy, g as hy, d as Vu, f as Ed, e as dy, l as py, u as fy, D as Qf, h as my } from "./index-1UgNlfGR.js";
|
|
2
2
|
const Cd = Math.PI / 180, gy = 180 / Math.PI;
|
|
3
3
|
function em(Pe, Q = 0) {
|
|
4
4
|
const me = Math.min(180, Pe) * Cd;
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { C as F, a as q } from "./crs-Ciu7Xs7a.js";
|
|
2
|
+
const j = 0.5;
|
|
3
|
+
function G(e, t) {
|
|
4
|
+
if (!Array.isArray(e)) return [];
|
|
5
|
+
const n = t?.scale?.[0] ?? 1, o = t?.scale?.[1] ?? 1, s = t?.scale?.[2] ?? 1, a = t?.translate?.[0] ?? 0, i = t?.translate?.[1] ?? 0, f = t?.translate?.[2] ?? 0, r = new Array(e.length);
|
|
6
|
+
for (let c = 0; c < e.length; c++) {
|
|
7
|
+
const u = e[c];
|
|
8
|
+
r[c] = [u[0] * n + a, u[1] * o + i, (u[2] ?? 0) * s + f];
|
|
9
|
+
}
|
|
10
|
+
return r;
|
|
11
|
+
}
|
|
12
|
+
const T = {
|
|
13
|
+
MultiPoint: 1,
|
|
14
|
+
MultiLineString: 2,
|
|
15
|
+
MultiSurface: 3,
|
|
16
|
+
CompositeSurface: 3,
|
|
17
|
+
Solid: 4,
|
|
18
|
+
MultiSolid: 5,
|
|
19
|
+
CompositeSolid: 5
|
|
20
|
+
};
|
|
21
|
+
function J(e, t) {
|
|
22
|
+
let n = e;
|
|
23
|
+
for (let o = 0; o < t; o++) n = n.flat();
|
|
24
|
+
return n;
|
|
25
|
+
}
|
|
26
|
+
function N(e) {
|
|
27
|
+
const t = T[e.type ?? ""];
|
|
28
|
+
return !t || t < 3 || !Array.isArray(e.boundaries) ? [] : J(e.boundaries, t - 3);
|
|
29
|
+
}
|
|
30
|
+
function X(e, t) {
|
|
31
|
+
const n = new Array(t).fill(void 0), o = T[e.type ?? ""], s = e.semantics?.surfaces, a = e.semantics?.values;
|
|
32
|
+
if (!o || !Array.isArray(s) || !Array.isArray(a)) return n;
|
|
33
|
+
const i = J(a, o - 3);
|
|
34
|
+
for (let f = 0; f < t; f++) {
|
|
35
|
+
const r = i[f];
|
|
36
|
+
typeof r == "number" && (n[f] = s[r]?.type);
|
|
37
|
+
}
|
|
38
|
+
return n;
|
|
39
|
+
}
|
|
40
|
+
function _(e) {
|
|
41
|
+
const t = typeof e == "number" ? e : Number.parseFloat(String(e ?? ""));
|
|
42
|
+
return Number.isFinite(t) ? t : -1;
|
|
43
|
+
}
|
|
44
|
+
function K(e, t) {
|
|
45
|
+
if (!Array.isArray(e)) return;
|
|
46
|
+
const n = e.filter((o) => N(o).length > 0);
|
|
47
|
+
if (n.length !== 0) {
|
|
48
|
+
if (t !== void 0) {
|
|
49
|
+
const o = n.find((s) => _(s.lod) === t);
|
|
50
|
+
if (o) return o;
|
|
51
|
+
}
|
|
52
|
+
return n.reduce((o, s) => _(s.lod) > _(o.lod) ? s : o);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const Q = /EPSG.*?(\d{4,6})\D*$/i;
|
|
56
|
+
function tt(e) {
|
|
57
|
+
if (!e) return;
|
|
58
|
+
const t = Number(Q.exec(e)?.[1]);
|
|
59
|
+
return Number.isFinite(t) ? t : void 0;
|
|
60
|
+
}
|
|
61
|
+
let P = !1;
|
|
62
|
+
async function U(e, t) {
|
|
63
|
+
const n = { toLngLat: (r, c) => [r, c], projected: !1 }, o = tt(e);
|
|
64
|
+
if (o === void 0)
|
|
65
|
+
return P || (P = !0, console.warn(
|
|
66
|
+
`[onlymapjs] data="${t}": CityJSON has no metadata.referenceSystem — assuming coordinates are already WGS84 lon/lat. Add a referenceSystem to the file if they are not.`
|
|
67
|
+
)), n;
|
|
68
|
+
const s = q[o] ?? o, a = F[s];
|
|
69
|
+
if (!a)
|
|
70
|
+
throw new Error(
|
|
71
|
+
`CityJSON declares EPSG:${o}${s === o ? "" : ` (horizontal component EPSG:${s})`}, which is not one of the bundled coordinate systems (${Object.keys(F).join(", ")}). Reproject the file to EPSG:4326 server-side (cjio --reproject 4326), or claim the URL with OmMap.registerFormat() and supply your own projection.`
|
|
72
|
+
);
|
|
73
|
+
if (a.geographic)
|
|
74
|
+
return a.swapXY ? { toLngLat: (r, c) => [c, r], projected: !1 } : n;
|
|
75
|
+
const i = (await import("./index-CW1n5LdO.js")).default, f = i(a.def, "+proj=longlat +datum=WGS84 +no_defs");
|
|
76
|
+
return {
|
|
77
|
+
toLngLat: (r, c) => {
|
|
78
|
+
const [u, p] = f.forward(a.swapXY ? [c, r] : [r, c]);
|
|
79
|
+
return [u, p];
|
|
80
|
+
},
|
|
81
|
+
projected: !0
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function I(e) {
|
|
85
|
+
let t = 0, n = 0, o = 0;
|
|
86
|
+
for (let s = 0; s < e.length; s++) {
|
|
87
|
+
const a = e[s], i = e[(s + 1) % e.length];
|
|
88
|
+
t += (a[1] - i[1]) * (a[2] + i[2]), n += (a[2] - i[2]) * (a[0] + i[0]), o += (a[0] - i[0]) * (a[1] + i[1]);
|
|
89
|
+
}
|
|
90
|
+
return Math.hypot(t, n, o) / 2;
|
|
91
|
+
}
|
|
92
|
+
function et(e) {
|
|
93
|
+
const t = e.slice().sort((o, s) => o - s), n = t.length >> 1;
|
|
94
|
+
return t.length % 2 ? t[n] : (t[n - 1] + t[n]) / 2;
|
|
95
|
+
}
|
|
96
|
+
function B(e, t, n, o, s) {
|
|
97
|
+
const a = K(t.geometry, s);
|
|
98
|
+
if (!a) return;
|
|
99
|
+
const i = N(a);
|
|
100
|
+
if (i.length === 0) return;
|
|
101
|
+
const f = X(a, i.length), r = i.map(
|
|
102
|
+
(d) => d.map((y) => y.map((l) => n[l]).filter((l) => l !== void 0))
|
|
103
|
+
);
|
|
104
|
+
let c = 1 / 0, u = -1 / 0;
|
|
105
|
+
for (const d of r)
|
|
106
|
+
for (const y of d)
|
|
107
|
+
for (const [, , l] of y)
|
|
108
|
+
l < c && (c = l), l > u && (u = l);
|
|
109
|
+
if (!Number.isFinite(c)) return;
|
|
110
|
+
const p = c, g = f.some((d) => d === "RoofSurface"), h = [];
|
|
111
|
+
let S = 1 / 0, m = -1 / 0;
|
|
112
|
+
for (let d = 0; d < r.length; d++)
|
|
113
|
+
if (!(g && f[d] !== "RoofSurface"))
|
|
114
|
+
for (const y of r[d])
|
|
115
|
+
for (const [, , l] of y)
|
|
116
|
+
(g || l > p + j) && (h.push(l), l < S && (S = l), l > m && (m = l));
|
|
117
|
+
h.length === 0 && (h.push(u), S = u, m = u);
|
|
118
|
+
let w = f.indexOf("GroundSurface");
|
|
119
|
+
if (w < 0) {
|
|
120
|
+
let d = -1;
|
|
121
|
+
for (let y = 0; y < r.length; y++) {
|
|
122
|
+
const l = r[y][0];
|
|
123
|
+
if (!l || l.length < 3) continue;
|
|
124
|
+
const b = l.every(([, , R]) => R <= p + j), C = I(l) * (b ? 1e3 : 1);
|
|
125
|
+
C > d && (d = C, w = y);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (w < 0) return;
|
|
129
|
+
const O = r[w];
|
|
130
|
+
if (!O[0] || O[0].length < 3) return;
|
|
131
|
+
const [, k] = o.toLngLat(O[0][0][0], O[0][0][1]), z = 111320 * Math.cos(k * Math.PI / 180), Y = 110540, V = (d) => o.projected ? d : d.map(([y, l, b]) => {
|
|
132
|
+
const [C, R] = o.toLngLat(y, l);
|
|
133
|
+
return [C * z, R * Y, b];
|
|
134
|
+
});
|
|
135
|
+
let L = 0, E = 0;
|
|
136
|
+
for (let d = 0; d < r.length; d++) {
|
|
137
|
+
if (g && f[d] !== "RoofSurface") continue;
|
|
138
|
+
const y = r[d][0];
|
|
139
|
+
if (!y || y.length < 3) continue;
|
|
140
|
+
const l = I(V(y));
|
|
141
|
+
L += l, E += l * (y.reduce((b, [, , C]) => b + C, 0) / y.length);
|
|
142
|
+
}
|
|
143
|
+
const W = L > 0 ? E / L : et(h), H = {
|
|
144
|
+
cityobject_id: e,
|
|
145
|
+
cityobject_type: t.type ?? "Unknown",
|
|
146
|
+
...t.parents?.[0] !== void 0 ? { parent_id: t.parents[0] } : {},
|
|
147
|
+
...a.lod !== void 0 ? { lod: String(a.lod) } : {},
|
|
148
|
+
ground_height: v(p),
|
|
149
|
+
roof_height: v(W - p),
|
|
150
|
+
eaves_height: v(S - p),
|
|
151
|
+
ridge_height: v(m - p),
|
|
152
|
+
roof_area: v(L),
|
|
153
|
+
surface_count: i.length
|
|
154
|
+
};
|
|
155
|
+
return { faceRings: r, semantics: f, footprintIndex: w, derived: H };
|
|
156
|
+
}
|
|
157
|
+
function nt(e, t, n, o, s, a) {
|
|
158
|
+
const i = B(e, t, n, o, a);
|
|
159
|
+
if (!i) return;
|
|
160
|
+
const r = i.faceRings[i.footprintIndex].filter((c) => c.length >= 3).map((c) => {
|
|
161
|
+
const u = c.map(([p, g]) => o.toLngLat(p, g));
|
|
162
|
+
return u.push(u[0]), u;
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
type: "Feature",
|
|
166
|
+
id: e,
|
|
167
|
+
geometry: { type: "Polygon", coordinates: r },
|
|
168
|
+
// Source attributes first: the derived names are the ones manifests bind
|
|
169
|
+
// to, so they must be deterministic even against a same-named source field.
|
|
170
|
+
properties: { ...s, ...t.attributes, ...i.derived }
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const M = {
|
|
174
|
+
GroundSurface: "#999999",
|
|
175
|
+
WallSurface: "#ffffff",
|
|
176
|
+
RoofSurface: "#ff0000",
|
|
177
|
+
TrafficArea: "#6e6e6e",
|
|
178
|
+
AuxiliaryTrafficArea: "#2c8200",
|
|
179
|
+
Window: "#0059ff",
|
|
180
|
+
Door: "#640000"
|
|
181
|
+
}, x = {
|
|
182
|
+
Building: "#7497df",
|
|
183
|
+
BuildingPart: "#7497df",
|
|
184
|
+
BuildingInstallation: "#7497df",
|
|
185
|
+
Bridge: "#999999",
|
|
186
|
+
BridgePart: "#999999",
|
|
187
|
+
BridgeInstallation: "#999999",
|
|
188
|
+
BridgeConstructiveElement: "#999999",
|
|
189
|
+
CityObjectGroup: "#ffffb3",
|
|
190
|
+
LandUse: "#ffffb3",
|
|
191
|
+
CityFurniture: "#cc0000",
|
|
192
|
+
GenericCityObject: "#cc0000",
|
|
193
|
+
PlantCover: "#39ac39",
|
|
194
|
+
SolitaryVegetationObject: "#39ac39",
|
|
195
|
+
Railway: "#000000",
|
|
196
|
+
Road: "#999999",
|
|
197
|
+
TransportSquare: "#999999",
|
|
198
|
+
TINRelief: "#ffdb99",
|
|
199
|
+
Tunnel: "#999999",
|
|
200
|
+
TunnelPart: "#999999",
|
|
201
|
+
TunnelInstallation: "#999999",
|
|
202
|
+
WaterBody: "#4da6ff"
|
|
203
|
+
}, ot = "#b0b0b0";
|
|
204
|
+
function rt(e, t) {
|
|
205
|
+
return e && M[e] ? M[e] : t && x[t] ? x[t] : ot;
|
|
206
|
+
}
|
|
207
|
+
function it(e, t, n, o, s, a) {
|
|
208
|
+
const i = B(e, t, n, o, a);
|
|
209
|
+
if (!i) return [];
|
|
210
|
+
const f = [];
|
|
211
|
+
for (let r = 0; r < i.faceRings.length; r++) {
|
|
212
|
+
const c = i.faceRings[r], u = c[0];
|
|
213
|
+
if (!u || u.length < 3) continue;
|
|
214
|
+
const p = c.filter((h) => h.length >= 3).map((h) => h.map(([S, m, w]) => [...o.toLngLat(S, m), w])), g = p[0];
|
|
215
|
+
f.push({
|
|
216
|
+
...s,
|
|
217
|
+
...t.attributes,
|
|
218
|
+
...i.derived,
|
|
219
|
+
surface_type: i.semantics[r],
|
|
220
|
+
fill_color: rt(i.semantics[r], t.type),
|
|
221
|
+
polygon: p,
|
|
222
|
+
outline: [...g, g[0]]
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return f;
|
|
226
|
+
}
|
|
227
|
+
function v(e) {
|
|
228
|
+
return Number.isFinite(e) ? Math.round(e * 1e3) / 1e3 : 0;
|
|
229
|
+
}
|
|
230
|
+
function D(e, t, n, o, s = "footprint") {
|
|
231
|
+
if (!e) return [];
|
|
232
|
+
const a = (f) => Array.isArray(f.children) && f.children.some((r) => {
|
|
233
|
+
const c = e[r];
|
|
234
|
+
return c?.type === `${f.type}Part` && (c.geometry?.some((u) => N(u).length > 0) ?? !1);
|
|
235
|
+
}), i = [];
|
|
236
|
+
for (const [f, r] of Object.entries(e)) {
|
|
237
|
+
if (!r || typeof r != "object" || a(r)) continue;
|
|
238
|
+
const u = (r.parents?.[0] ? e[r.parents[0]] : void 0)?.attributes ?? {};
|
|
239
|
+
if (s === "surfaces")
|
|
240
|
+
i.push(...it(f, r, t, n, u, o));
|
|
241
|
+
else {
|
|
242
|
+
const p = nt(f, r, t, n, u, o);
|
|
243
|
+
p && i.push(p);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return i;
|
|
247
|
+
}
|
|
248
|
+
function A(e, t, n) {
|
|
249
|
+
if (e?.type !== n)
|
|
250
|
+
throw new Error(
|
|
251
|
+
`expected a ${n} object but found type="${e?.type ?? "undefined"}" — this does not look like CityJSON. If it is a different newline-delimited format, claim the URL with OmMap.registerFormat().`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
function $(e) {
|
|
255
|
+
const t = /[?&]om-lod=([^&#]+)/.exec(e)?.[1];
|
|
256
|
+
if (t === void 0) return;
|
|
257
|
+
const n = Number.parseFloat(decodeURIComponent(t));
|
|
258
|
+
if (!Number.isFinite(n)) {
|
|
259
|
+
console.warn(`[onlymapjs] om-lod="${t}" is not a number — ignoring and using the highest available LoD.`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
return n;
|
|
263
|
+
}
|
|
264
|
+
function Z(e) {
|
|
265
|
+
const t = /[?&]om-surfaces=([^&#]+)/.exec(e)?.[1];
|
|
266
|
+
if (t === void 0) return !1;
|
|
267
|
+
const n = decodeURIComponent(t).toLowerCase();
|
|
268
|
+
return n === "1" || n === "true";
|
|
269
|
+
}
|
|
270
|
+
async function ut(e, t) {
|
|
271
|
+
const n = await e.json();
|
|
272
|
+
A(n, t, "CityJSON");
|
|
273
|
+
const o = await U(n.metadata?.referenceSystem, t), s = G(n.vertices, n.transform), a = Z(t) ? "surfaces" : "footprint";
|
|
274
|
+
return D(n.CityObjects, s, o, $(t), a);
|
|
275
|
+
}
|
|
276
|
+
const st = 250, at = 512;
|
|
277
|
+
async function* ct(e) {
|
|
278
|
+
if (!e.body) {
|
|
279
|
+
for (const s of (await e.text()).split(`
|
|
280
|
+
`)) yield s;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const t = e.body.getReader(), n = new TextDecoder();
|
|
284
|
+
let o = "";
|
|
285
|
+
for (; ; ) {
|
|
286
|
+
const { done: s, value: a } = await t.read();
|
|
287
|
+
if (s) break;
|
|
288
|
+
o += n.decode(a, { stream: !0 });
|
|
289
|
+
let i;
|
|
290
|
+
for (; (i = o.indexOf(`
|
|
291
|
+
`)) >= 0; )
|
|
292
|
+
yield o.slice(0, i), o = o.slice(i + 1);
|
|
293
|
+
}
|
|
294
|
+
o += n.decode(), o.length > 0 && (yield o);
|
|
295
|
+
}
|
|
296
|
+
async function lt(e, t, n) {
|
|
297
|
+
const o = $(t), s = Z(t) ? "surfaces" : "footprint";
|
|
298
|
+
let a, i, f = !1;
|
|
299
|
+
const r = [];
|
|
300
|
+
let c = 0, u = 0;
|
|
301
|
+
for await (const p of ct(e)) {
|
|
302
|
+
const g = p.trim();
|
|
303
|
+
if (g.length === 0) continue;
|
|
304
|
+
const h = JSON.parse(g);
|
|
305
|
+
if (f)
|
|
306
|
+
A(h, t, "CityJSONFeature");
|
|
307
|
+
else if (A(h, t, "CityJSON"), i = h.transform, a = await U(h.metadata?.referenceSystem, t), f = !0, h.CityObjects && Object.keys(h.CityObjects).length === 0) continue;
|
|
308
|
+
const S = G(h.vertices, i);
|
|
309
|
+
r.push(...D(h.CityObjects, S, a, o, s)), c++;
|
|
310
|
+
const m = performance.now();
|
|
311
|
+
(c >= at || m - u >= st) && (c = 0, u = m, n(r.slice()));
|
|
312
|
+
}
|
|
313
|
+
if (!f)
|
|
314
|
+
throw new Error(
|
|
315
|
+
'CityJSONSeq file is empty or has no header line — the first line must be a CityJSON object carrying "transform" and "metadata", followed by one CityJSONFeature per line.'
|
|
316
|
+
);
|
|
317
|
+
return r;
|
|
318
|
+
}
|
|
319
|
+
export {
|
|
320
|
+
K as chooseGeometry,
|
|
321
|
+
D as decodeCityObjects,
|
|
322
|
+
G as decompressVertices,
|
|
323
|
+
rt as defaultFillColor,
|
|
324
|
+
X as faceSemantics,
|
|
325
|
+
N as flattenToFaces,
|
|
326
|
+
ut as parseCityJsonDocument,
|
|
327
|
+
lt as parseCityJsonSeq,
|
|
328
|
+
tt as parseEpsgCode,
|
|
329
|
+
$ as parseLodPin,
|
|
330
|
+
Z as parseSurfacesFlag,
|
|
331
|
+
U as resolveCrs
|
|
332
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const c = {
|
|
2
|
+
7415: 28992,
|
|
3
|
+
// Amersfoort / RD New + NAP height — the 3DBAG default
|
|
4
|
+
6697: 6668,
|
|
5
|
+
// JGD2011 geographic 3D + height — the PLATEAU default
|
|
6
|
+
5555: 25832,
|
|
7
|
+
// ETRS89 / UTM 32N + DHHN92 height
|
|
8
|
+
5556: 25833
|
|
9
|
+
// ETRS89 / UTM 33N + DHHN92 height
|
|
10
|
+
}, f = [
|
|
11
|
+
[33, 129.5],
|
|
12
|
+
[33, 131],
|
|
13
|
+
[36, 132.166666666667],
|
|
14
|
+
[33, 133.5],
|
|
15
|
+
[36, 134.333333333333],
|
|
16
|
+
[36, 136],
|
|
17
|
+
[36, 137.166666666667],
|
|
18
|
+
[36, 138.5],
|
|
19
|
+
[36, 139.833333333333],
|
|
20
|
+
[40, 140.833333333333],
|
|
21
|
+
[44, 140.25],
|
|
22
|
+
[44, 142.25],
|
|
23
|
+
[44, 144.25],
|
|
24
|
+
[26, 142],
|
|
25
|
+
[26, 127.5],
|
|
26
|
+
[26, 124],
|
|
27
|
+
[26, 131],
|
|
28
|
+
[20, 136],
|
|
29
|
+
[26, 154]
|
|
30
|
+
], u = {
|
|
31
|
+
// Netherlands — Amersfoort / RD New (3DBAG).
|
|
32
|
+
28992: {
|
|
33
|
+
def: "+proj=sterea +lat_0=52.1561605555556 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.4171,50.3319,465.5524,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs"
|
|
34
|
+
},
|
|
35
|
+
// Switzerland — CH1903+ / LV95.
|
|
36
|
+
2056: {
|
|
37
|
+
def: "+proj=somerc +lat_0=46.9524055555556 +lon_0=7.43958333333333 +k_0=1 +x_0=2600000 +y_0=1200000 +ellps=bessel +towgs84=674.374,15.056,405.346,0,0,0,0 +units=m +no_defs"
|
|
38
|
+
},
|
|
39
|
+
// Germany — ETRS89 / UTM zones 32N and 33N.
|
|
40
|
+
25832: { def: "+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs" },
|
|
41
|
+
25833: { def: "+proj=utm +zone=33 +ellps=GRS80 +units=m +no_defs" },
|
|
42
|
+
// Plain WGS84 UTM zones 32N/33N (EPSG:326xx) — the generic, non-European-specific
|
|
43
|
+
// code many CityJSON files declare instead of the ETRS89 pair above (e.g.
|
|
44
|
+
// Ingolstadt's own open-data LoD3 export). Numerically near-identical to
|
|
45
|
+
// 25832/25833 at map-rendering precision (WGS84 vs. ETRS89 differ by ~cm in
|
|
46
|
+
// Europe), so no +towgs84 shift is needed — same bare projection string.
|
|
47
|
+
32632: { def: "+proj=utm +zone=32 +datum=WGS84 +units=m +no_defs" },
|
|
48
|
+
32633: { def: "+proj=utm +zone=33 +datum=WGS84 +units=m +no_defs" },
|
|
49
|
+
// Austria — MGI / Austria GK West/Central/East (the national cadastral
|
|
50
|
+
// system's three meridian-strip zones; Vienna sits in GK East). Same
|
|
51
|
+
// Bessel ellipsoid + towgs84 shift and y_0=-5,000,000 false-northing
|
|
52
|
+
// convention across all three — proj4 strings verified against epsg.io,
|
|
53
|
+
// not hand-derived, and cross-checked by reprojecting a real Vienna
|
|
54
|
+
// open-data file's own extent and confirming it lands in Vienna.
|
|
55
|
+
31254: { def: "+proj=tmerc +lat_0=0 +lon_0=10.3333333333333 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs" },
|
|
56
|
+
31255: { def: "+proj=tmerc +lat_0=0 +lon_0=13.3333333333333 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs" },
|
|
57
|
+
31256: { def: "+proj=tmerc +lat_0=0 +lon_0=16.3333333333333 +k=1 +x_0=0 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs" },
|
|
58
|
+
// Singapore — SVY21 / Singapore TM (the national grid; NUS's own Singapore
|
|
59
|
+
// HDB buildings dataset declares this via metadata.referenceSystem
|
|
60
|
+
// directly, unlike the Austria/3DBAG files, which had none). proj4 string
|
|
61
|
+
// verified against epsg.io, cross-checked by reprojecting a real dataset's
|
|
62
|
+
// own extent and confirming it lands in Singapore (103.68-103.99°E,
|
|
63
|
+
// 1.27-1.46°N — matches).
|
|
64
|
+
3414: { def: "+proj=tmerc +lat_0=1.36666666666667 +lon_0=103.833333333333 +k=1 +x_0=28001.642 +y_0=38744.572 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs" },
|
|
65
|
+
// Japan — JGD2011 geographic 2D. EPSG axis order is (lat, lon).
|
|
66
|
+
6668: { def: "+proj=longlat +ellps=GRS80 +no_defs", geographic: !0, swapXY: !0 },
|
|
67
|
+
// WGS 84. Files in the wild store lon/lat despite EPSG's formal (lat, lon).
|
|
68
|
+
4326: { def: "+proj=longlat +datum=WGS84 +no_defs", geographic: !0 },
|
|
69
|
+
3857: { def: "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +no_defs" }
|
|
70
|
+
};
|
|
71
|
+
for (let t = 0; t < f.length; t++) {
|
|
72
|
+
const [e, o] = f[t];
|
|
73
|
+
u[6669 + t] = {
|
|
74
|
+
def: `+proj=tmerc +lat_0=${e} +lon_0=${o} +k=0.9999 +x_0=0 +y_0=0 +ellps=GRS80 +units=m +no_defs`,
|
|
75
|
+
swapXY: !0
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
let p = null;
|
|
79
|
+
async function m() {
|
|
80
|
+
return (await import("./index-CW1n5LdO.js")).default;
|
|
81
|
+
}
|
|
82
|
+
function g() {
|
|
83
|
+
return p ??= m(), p;
|
|
84
|
+
}
|
|
85
|
+
function j() {
|
|
86
|
+
return Object.keys(u).map(Number).sort((t, e) => t - e);
|
|
87
|
+
}
|
|
88
|
+
async function i(t, e, o) {
|
|
89
|
+
const a = c[t] ?? t, n = u[a];
|
|
90
|
+
if (!n) return null;
|
|
91
|
+
const [s, l] = n.swapXY ? [o, e] : [e, o];
|
|
92
|
+
if (n.geographic) return [s, l];
|
|
93
|
+
const _ = await g(), [r, d] = _(n.def, "+proj=longlat +datum=WGS84 +no_defs").forward([s, l]);
|
|
94
|
+
return Number.isFinite(r) && Number.isFinite(d) ? [r, d] : null;
|
|
95
|
+
}
|
|
96
|
+
async function w(t, e, o, a = 100) {
|
|
97
|
+
const n = await i(t, e, o), s = await i(t, e, o + a);
|
|
98
|
+
if (!n || !s) return null;
|
|
99
|
+
const l = (n[1] + s[1]) / 2 * Math.PI / 180, _ = (s[0] - n[0]) * Math.cos(l), r = s[1] - n[1];
|
|
100
|
+
return Math.atan2(_, r) * 180 / Math.PI;
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
u as C,
|
|
104
|
+
c as a,
|
|
105
|
+
j as b,
|
|
106
|
+
w as g,
|
|
107
|
+
i as p
|
|
108
|
+
};
|
package/dist/crs.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundled coordinate reference systems, shared by every decoder that has to
|
|
3
|
+
* turn projected coordinates into lon/lat.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from cityjson.ts so the IFC path can reuse it. An IFC file's
|
|
6
|
+
* `IfcMapConversion` states its position in a projected CRS named by
|
|
7
|
+
* `IfcProjectedCRS`, exactly as CityJSON states one in
|
|
8
|
+
* `metadata.referenceSystem`, so both need the same table and the same lazy
|
|
9
|
+
* proj4 import. Duplicating it would have meant a Dutch model georeferencing
|
|
10
|
+
* correctly as CityJSON and 108 m out as IFC.
|
|
11
|
+
*
|
|
12
|
+
* proj4 is imported LAZILY at the point of use, never at module scope: most
|
|
13
|
+
* files need no projection at all, and the ones that do are already paying for
|
|
14
|
+
* a parse.
|
|
15
|
+
*/
|
|
16
|
+
export interface CrsEntry {
|
|
17
|
+
def: string;
|
|
18
|
+
/** Source stores [northing, easting] / [lat, lon] — swap before handing to proj4. */
|
|
19
|
+
swapXY?: boolean;
|
|
20
|
+
/** Already angular: no reprojection, but horizontal metrics need a local metric frame. */
|
|
21
|
+
geographic?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Compound (horizontal + vertical) codes → the horizontal component that actually projects. */
|
|
24
|
+
export declare const COMPOUND_TO_HORIZONTAL: Record<number, number>;
|
|
25
|
+
export declare const CRS_DEFS: Record<number, CrsEntry>;
|
|
26
|
+
/** The EPSG codes this build can convert without help. */
|
|
27
|
+
export declare function bundledCrsCodes(): number[];
|
|
28
|
+
/**
|
|
29
|
+
* Projected (or geographic) coordinates in EPSG:`code` -> `[lon, lat]`.
|
|
30
|
+
*
|
|
31
|
+
* Returns null when the code is not bundled, which callers must treat as "I
|
|
32
|
+
* cannot place this" rather than silently substituting something else — a
|
|
33
|
+
* wrong position looks exactly like a right one.
|
|
34
|
+
*
|
|
35
|
+
* `x`/`y` are given easting-first; entries flagged `swapXY` (Japan's plane
|
|
36
|
+
* systems, RD's formal axis order) are swapped here so callers never have to
|
|
37
|
+
* know which convention a CRS uses.
|
|
38
|
+
*/
|
|
39
|
+
export declare function projectedToLonLat(code: number, x: number, y: number): Promise<[number, number] | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Bearing of the projection's GRID north at a point, in degrees clockwise from
|
|
42
|
+
* true north — the grid convergence.
|
|
43
|
+
*
|
|
44
|
+
* Measured rather than derived: unproject the point and a second one a short
|
|
45
|
+
* step further north IN GRID SPACE, then take the true bearing between them.
|
|
46
|
+
* That works for any projection in the table without per-CRS formulae, and it
|
|
47
|
+
* matters because a model aligned to a national grid is NOT aligned to true
|
|
48
|
+
* north. Dutch RD at Rotterdam is off by -0.735 degrees.
|
|
49
|
+
*/
|
|
50
|
+
export declare function gridConvergence(code: number, x: number, y: number, step?: number): Promise<number | null>;
|