diff --git a/website/docs/documentation/01-getting-started/03-declarative-vs-imperative.md b/website/docs/documentation/02-advanced-concepts/01-declarative-vs-imperative.md similarity index 97% rename from website/docs/documentation/01-getting-started/03-declarative-vs-imperative.md rename to website/docs/documentation/02-advanced-concepts/01-declarative-vs-imperative.md index 2294d38..58e1150 100644 --- a/website/docs/documentation/01-getting-started/03-declarative-vs-imperative.md +++ b/website/docs/documentation/02-advanced-concepts/01-declarative-vs-imperative.md @@ -305,3 +305,7 @@ vs ``` + +## See Also + +- [Declarative API: Limitations and Performance](./02-declarative-limitations.md) — where the declarative API stops scaling and when to reach for the imperative paths diff --git a/website/docs/documentation/02-advanced-concepts/02-declarative-limitations.md b/website/docs/documentation/02-advanced-concepts/02-declarative-limitations.md new file mode 100644 index 0000000..dc22c83 --- /dev/null +++ b/website/docs/documentation/02-advanced-concepts/02-declarative-limitations.md @@ -0,0 +1,121 @@ +# Declarative API: Limitations and Performance + +The declarative API is ergonomic for small-to-medium maps. This page documents where it stops scaling, what each limitation stems from, and when to reach for the imperative paths instead. + +## Limitations + +### 1. Per-Element Source and Layer for Shape Primitives + +- Each ``, ``, and `` owns its **own** MapLibre source **and** layer (`src/GeomapShape.ts`) +- N shape elements therefore produce N sources + N layers; the component does **not** merge shapes into a shared source +- For many features, use `` + a layer element with a single GeoJSON — one source, one layer, GPU-rendered + +```html + + + + + + +``` + +vs + +```html + + + + + +``` + +### 2. Scale Thresholds for Per-Feature Elements + +The thresholds below are approximate and device-dependent; they are not based on empirical benchmarks of this component. Treat them as rules of thumb for ``, ``, ``, `` and similar per-feature elements. + +:::warning +- **> 5,000 elements** — noticeable degradation: slow pan/zoom, delayed click handlers +- **> 10,000 elements** — significant browser memory pressure and layout recalculation storms +- **> 50,000 elements** — risk of tab crash, especially on mobile or constrained devices +::: + +Rule of thumb: above ~1,000 features, prefer `` + a layer element over per-feature elements. + +### 3. Slot Mutations Are Not Diffed + +- Appending or removing a child triggers UI5Element's `MutationObserver`, which calls the geomap's `handleInvalidation` with a slot change (`src/Geomap.ts`) +- The geomap responds by re-iterating **all** children through `connectToItems()` + `connectToDraw()` — there is no delta +- Per-child `connectToMap` is idempotent, so steady-state cost is an O(N) scan rather than re-wiring; a tight loop appending 10,000 elements still produces 10,000 microtask-batched invalidations + +:::note +For high-frequency updates, write the full feature set to a `` by assigning its `geojson` property, instead of appending/removing many child elements. +::: + +```js +// Prefer this for frequent updates +document.getElementById("points").geojson = featureCollection; +``` + +vs + +```js +// Hot-loop DOM mutation — triggers one UI5 invalidation per append +for (const f of features) { + const el = document.createElement("ui5-geomap-spot"); + el.lat = f.lat; el.lng = f.lng; + geomap.appendChild(el); +} +``` + +### 4. Geometry Rebuilds on Any Point Change + +- For ``, ``, and ``, mutating any child point (or adding/removing one) causes a **full** geometry rebuild and `setData` on the underlying source (`src/GeomapShape.ts`, `src/GeomapPolygon.ts`) +- There is no partial update; a single moved vertex re-serializes the whole shape + +For high-frequency updates on a shape, assign the complete `geojson` to a `` once rather than mutating many point children in sequence. + +### 5. Markers Re-Sync on Every Render + +- `GeomapMarkerBase.onBeforeRendering` runs `updateMarkerInstance` on every invalidation (`src/GeomapMarkerBase.ts`, `src/GeomapSpot.ts`) +- With many `` elements, any attribute change on the parent map re-writes 7 visual properties per spot per render pass +- Visual-property syncing is intentional (markers are not recreated), but the cost scales linearly with the number of spots on the map + +### 6. Complex MapLibre Expressions Still Require JavaScript + +- HTML attributes serialize simple scalars; MapLibre expression arrays (e.g. `["case", ["get", "pop"], 10, 2]`) can only be set via the JS property +- See also point 6 of the sibling [Assessment of Declarative Features](./01-declarative-vs-imperative.md) page for a worked example + +```js +document.getElementById("layer").fillColor = ["interpolate", ["linear"], ["get", "population"], 0, "#F2F12D", 1000000, "#DA9C20"]; +``` + +### 7. Style-Change Listener Accumulation + +- Each layer registers a `styledata` listener in `createLayer` to re-add itself if the MapLibre style drops it (`src/GeomapLayerBase.ts`) +- Previously registered handlers are not removed across provider or style switches; the listener count grows linearly per layer with each style change + +:::note +Avoid repeatedly swapping `` or the map style at runtime on long-lived maps. If style switching is unavoidable, consider recreating the geomap element rather than mutating the provider in place. +::: + +### 8. Mixing Declarative Children with Imperative Map Writes + +- Direct calls on `geomap.map.*` (`addSource`, `addLayer`, `setStyle`, …) bypass the slot / invalidation pipeline entirely +- Imperative additions that share an `id` with a declarative child can conflict with the layer-restore-on-`styledata` path (point 7), producing duplicate or vanishing layers after a style change +- Choose one owner per source/layer id: + - **Declarative** for static scenes and HTML-authored maps + - **Imperative via `.geojson = ...`** for high-frequency data updates + - **Direct `geomap.map.*`** only for ids that are not also declared as children + +### 9. When to Prefer the Imperative Path + +Quick decision list: + +- **Dataset > ~1,000 features** → `` + layer element, not per-feature elements +- **Frequent data refresh** → assign `source.geojson`, do not append/remove many child elements +- **Complex paint/layout expressions** → assign the JS property, not an HTML attribute +- **High-frequency camera animation** → call `geomap.flyTo(...)` or `geomap.map.*` directly, do not update attributes in a loop + +## See Also + +- [Assessment of Declarative Features](./01-declarative-vs-imperative.md) — the counterpart page documenting the benefits of the declarative API diff --git a/website/docs/documentation/02-advanced-concepts/README.md b/website/docs/documentation/02-advanced-concepts/README.md new file mode 100644 index 0000000..c38ef44 --- /dev/null +++ b/website/docs/documentation/02-advanced-concepts/README.md @@ -0,0 +1,3 @@ +# Advanced Concepts + +Topics that go beyond basic usage: how the declarative API compares to the raw MapLibre API, and the limitations and performance characteristics you need to be aware of before scaling up.