[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"docs-nav":3,"docs-page:data":22},[4,7,10,13,16,19],{"route":5,"label":6},"\u002F","Home",{"route":8,"label":9},"\u002Fapi","1. API",{"route":11,"label":12},"\u002Fdevelopment","2. Development",{"route":14,"label":15},"\u002Finstances","3. Instances",{"route":17,"label":18},"\u002Fmap","4. Map",{"route":20,"label":21},"\u002Fdata","5. Data",{"route":20,"title":23,"html":24,"prev":25,"next":26},"Data","\u003Ch1 id=\"data\">Data\u003C\u002Fh1>\n\u003Cblockquote>\n\u003Cp>Canonical data-layer and GeoJSON contract for Waymark instances.\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2 id=\"canonical-layer-shape\">\u003Ca class=\"heading-anchor\" href=\"#canonical-layer-shape\">Canonical layer shape\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Waymark uses this canonical input shape for each entry in \u003Ccode>data.layers\u003C\u002Fcode> and for \u003Ccode>instance.data.addLayer(...)\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{ type?: \u003Cspan class=\"hljs-string\">&quot;geojson&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: &lt;\u003Cspan class=\"hljs-title class_\">GeoJSON\u003C\u002Fspan>&gt;, paint?: { ... } }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n\u003Cli>\u003Ccode>type\u003C\u002Fcode> defaults to \u003Ccode>&quot;geojson&quot;\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>Only \u003Ccode>geojson\u003C\u002Fcode> is currently supported.\u003C\u002Fli>\n\u003Cli>\u003Ccode>paint\u003C\u002Fcode> is optional family-scoped paint overrides for this layer. See \u003Ca href=\"#paint--types\">Paint &amp; Types\u003C\u002Fa> below.\u003C\u002Fli>\n\u003Cli>Unknown keys on a layer are rejected.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Ccode>data\u003C\u002Fcode> must satisfy one of these minimal GeoJSON shapes:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>FeatureCollection\u003C\u002Fcode> with \u003Ccode>features\u003C\u002Fcode> array\u003C\u002Fli>\n\u003Cli>\u003Ccode>Feature\u003C\u002Fcode> with \u003Ccode>geometry\u003C\u002Fcode> key (\u003Ccode>object\u003C\u002Fcode> or \u003Ccode>null\u003C\u002Fcode>)\u003C\u002Fli>\n\u003Cli>Geometry object with \u003Ccode>type\u003C\u002Fcode> in \u003Ccode>Point\u003C\u002Fcode>, \u003Ccode>MultiPoint\u003C\u002Fcode>, \u003Ccode>LineString\u003C\u002Fcode>, \u003Ccode>MultiLineString\u003C\u002Fcode>, \u003Ccode>Polygon\u003C\u002Fcode>, \u003Ccode>MultiPolygon\u003C\u002Fcode>, or \u003Ccode>GeometryCollection\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>GeometryCollection\u003C\u002Fcode> additionally requires a \u003Ccode>geometries\u003C\u002Fcode> array (nested geometries follow the same geometry-type rule)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Validation is strict on essential geometry semantics (RFC7946-focused, non-topology-heavy):\u003C\u002Fp>\n\u003Cul>\n\u003Cli>position arrays require at least two finite numbers (\u003Ccode>longitude\u003C\u002Fcode>, \u003Ccode>latitude\u003C\u002Fcode>)\u003C\u002Fli>\n\u003Cli>\u003Ccode>LineString\u003C\u002Fcode> requires at least two positions\u003C\u002Fli>\n\u003Cli>\u003Ccode>LinearRing\u003C\u002Fcode> requires at least four positions and must be closed (first and last positions equivalent)\u003C\u002Fli>\n\u003Cli>\u003Ccode>Polygon\u003C\u002Fcode> requires a non-empty ring list and each ring must be a valid \u003Ccode>LinearRing\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>MultiPoint\u003C\u002Fcode>, \u003Ccode>MultiLineString\u003C\u002Fcode>, and \u003Ccode>MultiPolygon\u003C\u002Fcode> require valid per-member coordinate structures\u003C\u002Fli>\n\u003Cli>\u003Ccode>GeometryCollection\u003C\u002Fcode> validation is recursive\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"public-api-for-adding-data\">\u003Ca class=\"heading-anchor\" href=\"#public-api-for-adding-data\">Public API for adding data\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Runtime additions are done through:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">instance.\u003Cspan class=\"hljs-property\">data\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">addLayer\u003C\u002Fspan>(layer, { fitBounds?: boolean });\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>fitBounds\u003C\u002Fcode> defaults to \u003Ccode>true\u003C\u002Fcode>. When \u003Ccode>true\u003C\u002Fcode>, the injected map viewport is fitted to the layer&#39;s bounding box with \u003Ccode>padding: 20\u003C\u002Fcode> after the GeoJSON sublayers are mounted. This option is runtime-only and is not persisted in \u003Ccode>instance.toJSON()\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>To fit the viewport to \u003Cstrong>all\u003C\u002Fstrong> currently loaded data layers combined, use:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">instance.\u003Cspan class=\"hljs-property\">data\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">fitBounds\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">padding\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">20\u003C\u002Fspan> });\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>This iterates all loaded layers, computes the combined bounding box across every layer&#39;s GeoJSON, and calls \u003Ccode>map.fitBounds()\u003C\u002Fcode>. \u003Ccode>padding\u003C\u002Fcode> defaults to \u003Ccode>20\u003C\u002Fcode>. Useful when adding multiple layers in a batch where no single layer should drive the viewport.\u003C\u002Fp>\n\u003Cp>Runtime emits minimal data events:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>waymark:data.layer.added\u003C\u002Fcode> on successful layer add\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:data.layer.mounted\u003C\u002Fcode> when MapLibre sublayers for a logical data layer are mounted\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:data.layer.error\u003C\u002Fcode> on failure with \u003Ccode>{ id, stage: &quot;validation&quot; | &quot;runtime&quot;, message }\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Invalid \u003Ccode>instance.data.addLayer(...)\u003C\u002Fcode> calls emit \u003Ccode>waymark:data.layer.error\u003C\u002Fcode> and then throw the same error.\u003C\u002Fp>\n\u003Cp>Example:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">instance.\u003Cspan class=\"hljs-property\">data\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">addLayer\u003C\u002Fspan>({\n  \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;FeatureCollection&quot;\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-attr\">features\u003C\u002Fspan>: [],\n  },\n});\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>toJSON()\u003C\u002Fcode> serialises layers as explicit canonical objects:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">layers\u003C\u002Fspan>: [\n      {\n        \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;geojson&quot;\u003C\u002Fspan>,\n        \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;FeatureCollection&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">features\u003C\u002Fspan>: [] },\n      },\n    ],\n  },\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"initial-layers-and-runtime-behaviour\">\u003Ca class=\"heading-anchor\" href=\"#initial-layers-and-runtime-behaviour\">Initial layers and runtime behaviour\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>Initial layers can be provided in \u003Ccode>createInstance({ map, document: { data: { layers: [...] } } })\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>If the injected map is already loaded, initial layers mount immediately; otherwise Waymark waits for the map&#39;s \u003Ccode>load\u003C\u002Fcode> event and mounts then.\u003C\u002Fli>\n\u003Cli>Runtime-added and initial layers share the same ordering semantics: top-first (\u003Ccode>layers[0]\u003C\u002Fcode> visually on top).\u003C\u002Fli>\n\u003Cli>Layers are inserted below the first \u003Ccode>symbol\u003C\u002Fcode> layer already present in the map style; when the style has no symbol layer they are appended.\u003C\u002Fli>\n\u003Cli>Layers are re-mounted after a consumer style swap (\u003Ccode>style.load\u003C\u002Fcode>), for example \u003Ccode>map.setStyle(...)\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Ccode>destroy()\u003C\u002Fcode> removes only Waymark-owned layers and sources.\u003C\u002Fli>\n\u003Cli>A single logical layer may mount multiple MapLibre sublayers when its GeoJSON mixes geometry families.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Geometry families resolve to MapLibre layer types automatically:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>Point\u003C\u002Fcode> \u002F \u003Ccode>MultiPoint\u003C\u002Fcode> → \u003Ccode>circle\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>LineString\u003C\u002Fcode> \u002F \u003Ccode>MultiLineString\u003C\u002Fcode> → \u003Ccode>line\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>Polygon\u003C\u002Fcode> \u002F \u003Ccode>MultiPolygon\u003C\u002Fcode> → \u003Ccode>fill\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Every family sublayer is gated by a geometry-type filter so it renders only its own geometry: \u003Ccode>[&quot;==&quot;, [&quot;geometry-type&quot;], &quot;Point&quot; | &quot;LineString&quot; | &quot;Polygon&quot;]\u003C\u002Fcode>. MapLibre normalises \u003Ccode>Multi*\u003C\u002Fcode> geometries to these base types for \u003Ccode>[&quot;geometry-type&quot;]\u003C\u002Fcode>, so one equality per family covers every variant it owns. Without the gate, a mixed FeatureCollection would draw circles at every line and polygon vertex and render fills as closed bands; with it, each family renders only its own geometry. Type filters are combined with the geometry gate as \u003Ccode>[&quot;all&quot;, &lt;geometry filter&gt;, [&quot;==&quot;, [&quot;get&quot;, &quot;waymarkType&quot;], &quot;&lt;typeKey&gt;&quot;]]\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"instance-scoped-ids\">\u003Ca class=\"heading-anchor\" href=\"#instance-scoped-ids\">Instance-scoped IDs\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>GeoJSON source and layer IDs are instance-scoped and index-based to avoid collisions:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>source: \u003Ccode>waymark-{id}-geojson-source-{index}\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>sublayer: \u003Ccode>waymark-{id}-geojson-layer-{index}-{family}\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Additional sublayer ID patterns for type-based rendering:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>typed sublayer: \u003Ccode>waymark-{id}-geojson-layer-{index}-{family}-type-{typeKey}\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>symbol sublayer: \u003Ccode>waymark-{id}-geojson-layer-{index}-symbol-type-{typeKey}\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>hit-circle layer: \u003Ccode>waymark-{id}-geojson-layer-{index}-{family}-hit\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>icon-type hit-circle: \u003Ccode>waymark-{id}-geojson-layer-{index}-circle-hit-{typeKey}\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"paint--types\">\u003Ca class=\"heading-anchor\" href=\"#paint--types\">Paint &amp; Types\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Waymark supports per-layer paint overrides and type-based visual rendering. Each geometry family has a default colour key:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>circle\u003C\u002Fcode> → \u003Ccode>circle-color\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>line\u003C\u002Fcode> → \u003Ccode>line-color\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>fill\u003C\u002Fcode> → \u003Ccode>fill-color\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Each data layer is assigned a colour from a 20-entry pool, cycling by layer index. These pool colours are the baseline when no other paint source provides a colour value.\u003C\u002Fp>\n\u003Ch3 id=\"paint-merge-order-highest-to-lowest\">\u003Ca class=\"heading-anchor\" href=\"#paint-merge-order-highest-to-lowest\">Paint merge order (highest to lowest)\u003C\u002Fa>\u003C\u002Fh3>\n\u003Col>\n\u003Cli>Per-feature \u003Ccode>waymarkPaint\u003C\u002Fcode> — visible circle\u002Fline\u002Ffill sublayers only; see \u003Ca href=\"#per-feature-waymarkpaint\">Per-feature \u003Ccode>waymarkPaint\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>Type paint (\u003Ccode>config.types[typeKey].paint[family]\u003C\u002Fcode>)\u003C\u002Fli>\n\u003Cli>Layer paint (\u003Ccode>layer.paint[family]\u003C\u002Fcode>)\u003C\u002Fli>\n\u003Cli>Instance paint (\u003Ccode>config.paint[family]\u003C\u002Fcode>)\u003C\u002Fli>\n\u003Cli>Pool colour + family defaults (\u003Ccode>circle-radius: 5\u003C\u002Fcode>, \u003Ccode>line-width: 3\u003C\u002Fcode>, \u003Ccode>fill-opacity: 0.35\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"per-feature-waymarkpaint\">\u003Ca class=\"heading-anchor\" href=\"#per-feature-waymarkpaint\">Per-feature \u003Ccode>waymarkPaint\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>A GeoJSON feature can override its resolved paint with a \u003Ccode>waymarkPaint\u003C\u002Fcode> property. When a feature carrying the property matches a sublayer&#39;s type filter — a matching \u003Ccode>waymarkType\u003C\u002Fcode>, or its absence for the untyped fallback (any feature, when the layer has no types) — Waymark wraps every resolved paint property of that sublayer as a coalesce expression:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">[\n  \u003Cspan class=\"hljs-string\">&quot;coalesce&quot;\u003C\u002Fspan>,\n  [\u003Cspan class=\"hljs-string\">&quot;get&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;&lt;prop&gt;&quot;\u003C\u002Fspan>, [\u003Cspan class=\"hljs-string\">&quot;get&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;waymarkPaint&quot;\u003C\u002Fspan>, [\u003Cspan class=\"hljs-string\">&quot;properties&quot;\u003C\u002Fspan>]]],\n  \u003Cspan class=\"hljs-string\">&quot;&lt;resolved value&gt;&quot;\u003C\u002Fspan>,\n];\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;Feature&quot;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">properties\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">waymarkType\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;beer&quot;\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-attr\">waymarkPaint\u003C\u002Fspan>: { \u003Cspan class=\"hljs-string\">&quot;circle-color&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;#000000&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;circle-radius&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">10\u003C\u002Fspan> }\n  },\n  \u003Cspan class=\"hljs-attr\">geometry\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;Point&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">coordinates\u003C\u002Fspan>: [\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>] }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>The feature&#39;s value wins when present; otherwise the resolved default applies. \u003Ccode>waymarkPaint\u003C\u002Fcode> keys are MapLibre paint property names for the feature&#39;s family. Hit circles and icon symbol layers stay static, so per-feature paint affects visible circle\u002Fline\u002Ffill sublayers only. \u003Ccode>waymarkPaint\u003C\u002Fcode> is an ordinary feature property and round-trips through \u003Ccode>toJSON()\u003C\u002Fcode>. See \u003Ca href=\"\u002Fwaymark-js\u002Fapi#per-feature-waymarkpaint\">Per-feature \u003Ccode>waymarkPaint\u003C\u002Fcode>\u003C\u002Fa> in the API docs.\u003C\u002Fp>\n\u003Ch3 id=\"type-based-rendering\">\u003Ca class=\"heading-anchor\" href=\"#type-based-rendering\">Type-based rendering\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>When \u003Ccode>config.types\u003C\u002Fcode> is defined, each data layer&#39;s render plan splits into:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Untyped fallback\u003C\u002Fstrong> — features without \u003Ccode>waymarkType\u003C\u002Fcode>, using pool + \u003Ccode>config.paint\u003C\u002Fcode> + \u003Ccode>layer.paint\u003C\u002Fcode>. The filter is the family&#39;s geometry-type equality ANDed with \u003Ccode>[&quot;!&quot;, [&quot;has&quot;, &quot;waymarkType&quot;]]\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Per-type sublayers\u003C\u002Fstrong> — one per defined type with a matching family-scoped paint, filtered by the \u003Ccode>waymarkType\u003C\u002Fcode> feature property ANDed with the family geometry-type equality.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>When a type has an \u003Ccode>icon\u003C\u002Fcode> property and a truthy \u003Ccode>circle-color\u003C\u002Fcode> in \u003Ccode>paint.circle\u003C\u002Fcode> (see \u003Ca href=\"\u002Fwaymark-js\u002Fapi#icon-property\">API docs\u003C\u002Fa>), its point family renders as a MapLibre \u003Ccode>symbol\u003C\u002Fcode> sublayer plus an invisible hit circle, instead of a visible \u003Ccode>circle\u003C\u002Fcode> layer. \u003Ccode>icon\u003C\u002Fcode> is a name hook: the consumer registers the image with \u003Ccode>map.addImage(icon, image)\u003C\u002Fcode>, and Waymark uses the name as \u003Ccode>icon-image\u003C\u002Fcode> with \u003Ccode>iconSize\u003C\u002Fcode> (default \u003Ccode>1.5\u003C\u002Fcode>). Waymark never loads or registers images itself. The \u003Ccode>mountedFamilies\u003C\u002Fcode> payload includes \u003Ccode>&quot;symbol&quot;\u003C\u002Fcode> for such layers.\u003C\u002Fp>\n\u003Cp>Each point family (regular circle or icon symbol) also has a companion invisible hit-circle layer (radius 14, \u003Ccode>circle-opacity: 0\u003C\u002Fcode>) for reliable pointer targeting. See \u003Ca href=\"\u002Fwaymark-js\u002Fapi#hit-circle-layers\">Hit-circle layers\u003C\u002Fa> and \u003Ca href=\"\u002Fwaymark-js\u002Fapi#click-priority\">Click priority\u003C\u002Fa> in the API docs for layer ID naming and priority.\u003C\u002Fp>\n\u003Cp>Type visibility can be controlled at runtime via \u003Ccode>instance.types.setVisibility(key, boolean)\u003C\u002Fcode>. Toggling a type hides or restores every layer owned by it — typed family sublayers, icon symbol layers, and their hit circles.\u003C\u002Fp>\n\u003Cp>The \u003Ccode>waymarkType\u003C\u002Fcode> feature property is used as a string match against \u003Ccode>config.types\u003C\u002Fcode> keys. Type keys must match \u003Ccode>\u002F^[a-z0-9]+(-[a-z0-9]+)*$\u002F\u003C\u002Fcode> (1-64 characters, lowercase a-z, digits 0-9, and hyphens only — no leading or trailing hyphens).\u003C\u002Fp>\n\u003Ch3 id=\"paint-key-per-data-layer\">\u003Ca class=\"heading-anchor\" href=\"#paint-key-per-data-layer\">\u003Ccode>paint\u003C\u002Fcode> key per data layer\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;FeatureCollection&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">features\u003C\u002Fspan>: [...] },\n  \u003Cspan class=\"hljs-attr\">paint\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">circle\u003C\u002Fspan>: { \u003Cspan class=\"hljs-string\">&quot;circle-color&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;#ff0000&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;circle-radius&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">8\u003C\u002Fspan> },\n    \u003Cspan class=\"hljs-attr\">line\u003C\u002Fspan>: { \u003Cspan class=\"hljs-string\">&quot;line-color&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;#00ff00&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;line-width&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">2\u003C\u002Fspan> },\n    \u003Cspan class=\"hljs-attr\">fill\u003C\u002Fspan>: { \u003Cspan class=\"hljs-string\">&quot;fill-color&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;#0000ff&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;fill-opacity&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0.2\u003C\u002Fspan> }\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>paint\u003C\u002Fcode> is serialised alongside the layer in \u003Ccode>toJSON().data.layers[]\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch3 id=\"configtypes-example\">\u003Ca class=\"heading-anchor\" href=\"#configtypes-example\">\u003Ccode>config.types\u003C\u002Fcode> example\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">\u003Cspan class=\"hljs-title function_\">createInstance\u003C\u002Fspan>({\n  map,\n  \u003Cspan class=\"hljs-attr\">document\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">config\u003C\u002Fspan>: {\n      \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;map&quot;\u003C\u002Fspan>,\n      \u003Cspan class=\"hljs-attr\">types\u003C\u002Fspan>: {\n        \u003Cspan class=\"hljs-attr\">road\u003C\u002Fspan>: {\n          \u003Cspan class=\"hljs-attr\">title\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;Road&quot;\u003C\u002Fspan>,\n          \u003Cspan class=\"hljs-attr\">paint\u003C\u002Fspan>: {\n            \u003Cspan class=\"hljs-attr\">line\u003C\u002Fspan>: { \u003Cspan class=\"hljs-string\">&quot;line-color&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;#e6194b&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;line-width&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">4\u003C\u002Fspan> },\n          },\n        },\n        \u003Cspan class=\"hljs-attr\">park\u003C\u002Fspan>: {\n          \u003Cspan class=\"hljs-attr\">title\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;Park&quot;\u003C\u002Fspan>,\n          \u003Cspan class=\"hljs-attr\">paint\u003C\u002Fspan>: {\n            \u003Cspan class=\"hljs-attr\">fill\u003C\u002Fspan>: { \u003Cspan class=\"hljs-string\">&quot;fill-color&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;#3cb44b&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;fill-opacity&quot;\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">0.5\u003C\u002Fspan> },\n          },\n        },\n      },\n    },\n    \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: {\n      \u003Cspan class=\"hljs-attr\">layers\u003C\u002Fspan>: [\n        {\n          \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: {\n            \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;FeatureCollection&quot;\u003C\u002Fspan>,\n            \u003Cspan class=\"hljs-attr\">features\u003C\u002Fspan>: [\n              {\n                \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;Feature&quot;\u003C\u002Fspan>,\n                \u003Cspan class=\"hljs-attr\">properties\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">waymarkType\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;road&quot;\u003C\u002Fspan> },\n                \u003Cspan class=\"hljs-attr\">geometry\u003C\u002Fspan>: {\n                  \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;LineString&quot;\u003C\u002Fspan>,\n                  \u003Cspan class=\"hljs-attr\">coordinates\u003C\u002Fspan>: [\n                    [\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>],\n                    [\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>],\n                  ],\n                },\n              },\n              {\n                \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;Feature&quot;\u003C\u002Fspan>,\n                \u003Cspan class=\"hljs-attr\">properties\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">waymarkType\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;park&quot;\u003C\u002Fspan> },\n                \u003Cspan class=\"hljs-attr\">geometry\u003C\u002Fspan>: {\n                  \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;Polygon&quot;\u003C\u002Fspan>,\n                  \u003Cspan class=\"hljs-attr\">coordinates\u003C\u002Fspan>: [\n                    [\n                      [\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>],\n                      [\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>],\n                      [\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>],\n                      [\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>],\n                      [\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>],\n                    ],\n                  ],\n                },\n              },\n            ],\n          },\n        },\n      ],\n    },\n  },\n});\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"waymarkdatalayermounted-extended-payload\">\u003Ca class=\"heading-anchor\" href=\"#waymarkdatalayermounted-extended-payload\">\u003Ccode>waymark:data.layer.mounted\u003C\u002Fcode> extended payload\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>When types are involved, the mounted event includes the resolved type keys:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: string,\n  \u003Cspan class=\"hljs-attr\">layerIndex\u003C\u002Fspan>: number,\n  \u003Cspan class=\"hljs-attr\">mountedFamilies\u003C\u002Fspan>: [\u003Cspan class=\"hljs-string\">&quot;line&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;fill&quot;\u003C\u002Fspan>],\n  \u003Cspan class=\"hljs-attr\">mountedLayerIds\u003C\u002Fspan>: [\n    \u003Cspan class=\"hljs-string\">&quot;waymark-map-geojson-layer-0-line-type-road&quot;\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-string\">&quot;waymark-map-geojson-layer-0-fill-type-park&quot;\u003C\u002Fspan>\n  ],\n  \u003Cspan class=\"hljs-attr\">mountedTypes\u003C\u002Fspan>: [\u003Cspan class=\"hljs-string\">&quot;road&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-string\">&quot;park&quot;\u003C\u002Fspan>]\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"feature-interaction-events\">\u003Ca class=\"heading-anchor\" href=\"#feature-interaction-events\">Feature interaction events\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Waymark owns the layer knowledge needed to resolve what the pointer is over — hit-circle layers, per-layer priority, and the mapping from a rendered feature back to its MapLibre layer. Presentation is consumer-owned: popups, tooltips, cursor styling, and property filtering all live in the consumer.\u003C\u002Fp>\n\u003Cp>Feature events are dispatched on the instance container:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>waymark:feature.click\u003C\u002Fcode> — a click resolved to a rendered Waymark feature.\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:feature.hover\u003C\u002Fcode> — the pointer entered a rendered Waymark feature (once per entry).\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:feature.leave\u003C\u002Fcode> — the pointer left the hovered feature.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Detail shape (all three events):\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: string,              \u003Cspan class=\"hljs-comment\">\u002F\u002F instance namespacing id\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">feature\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">GeoJSON\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Feature\u003C\u002Fspan>, \u003Cspan class=\"hljs-comment\">\u002F\u002F { type, id?, geometry, properties }\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">layerId\u003C\u002Fspan>: string,         \u003Cspan class=\"hljs-comment\">\u002F\u002F MapLibre layer that produced the hit\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">lngLat\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">LngLat\u003C\u002Fspan> | \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan>,   \u003Cspan class=\"hljs-comment\">\u002F\u002F pointer event lngLat\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">originalEvent\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Event\u003C\u002Fspan> | \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan>\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>feature\u003C\u002Fcode> is a plain GeoJSON Feature — \u003Ccode>{ type: &quot;Feature&quot;, id?, geometry, properties }\u003C\u002Fcode> — with MapLibre presentation internals (\u003Ccode>layer\u003C\u002Fcode>, \u003Ccode>source\u003C\u002Fcode>, \u003Ccode>state\u003C\u002Fcode>, \u003Ccode>_vectorTileFeature\u003C\u002Fcode>, ...) stripped. \u003Ccode>id\u003C\u002Fcode> is included only when the rendered feature carried one.\u003C\u002Fp>\n\u003Cp>Hover fires on entry only; feature identity is \u003Ccode>layerId\u003C\u002Fcode> plus feature \u003Ccode>id\u003C\u002Fcode>, with a geometry\u002Fproperties fallback for features without an \u003Ccode>id\u003C\u002Fcode>. Leave fires when the pointer moves to empty space, onto a different feature (leave then hover), or off the canvas; its \u003Ccode>feature\u003C\u002Fcode> and \u003Ccode>layerId\u003C\u002Fcode> describe the feature that was left, while \u003Ccode>lngLat\u003C\u002Fcode> and \u003Ccode>originalEvent\u003C\u002Fcode> come from the current pointer event. After a consumer \u003Ccode>style.load\u003C\u002Fcode>, Waymark clears its observed-layer tracking and hover state; map-level pointer handlers survive and re-observe layers as they remount. Listeners detach on \u003Ccode>destroy()\u003C\u002Fcode>; no feature events fire after destroy.\u003C\u002Fp>\n\u003Cp>Waymark does not open popups, set the cursor, move the camera, or whitelist properties. The dev playground (\u003Ccode>dev\u002Fdev.js\u003C\u002Fcode>) shows the recommended consumer-owned popup and cursor pattern:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-title class_\">Popup\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;maplibre-gl&quot;\u003C\u002Fspan>;\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F Point features anchor on their coordinate; other geometries on the click.\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">function\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">resolvePopupLngLat\u003C\u002Fspan>(\u003Cspan class=\"hljs-params\">feature, lngLat\u003C\u002Fspan>) {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> geometry = feature?.\u003Cspan class=\"hljs-property\">geometry\u003C\u002Fspan>;\n\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (geometry?.\u003Cspan class=\"hljs-property\">type\u003C\u002Fspan> === \u003Cspan class=\"hljs-string\">&quot;Point&quot;\u003C\u002Fspan> &amp;&amp; \u003Cspan class=\"hljs-title class_\">Array\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">isArray\u003C\u002Fspan>(geometry.\u003Cspan class=\"hljs-property\">coordinates\u003C\u002Fspan>)) {\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> geometry.\u003Cspan class=\"hljs-property\">coordinates\u003C\u002Fspan>;\n  }\n\n  \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> lngLat;\n}\n\n\u003Cspan class=\"hljs-keyword\">let\u003C\u002Fspan> activePopup = \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan>;\n\ninstance.\u003Cspan class=\"hljs-title function_\">on\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&quot;waymark:feature.click&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">event\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> { feature, lngLat } = event.\u003Cspan class=\"hljs-property\">detail\u003C\u002Fspan>;\n\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (activePopup) {\n    activePopup.\u003Cspan class=\"hljs-title function_\">remove\u003C\u002Fspan>();\n    activePopup = \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan>;\n  }\n\n  \u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> anchor = \u003Cspan class=\"hljs-title function_\">resolvePopupLngLat\u003C\u002Fspan>(feature, lngLat);\n\n  \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (!anchor) {\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan>;\n  }\n\n  activePopup = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Popup\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">focusAfterOpen\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> })\n    .\u003Cspan class=\"hljs-title function_\">setLngLat\u003C\u002Fspan>(anchor)\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F The dev harness renders a title, a single image (first present of\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F image_thumbnail_url → image_medium_url → image_large_url), and the\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F remaining property rows.\u003C\u002Fspan>\n    .\u003Cspan class=\"hljs-title function_\">setDOMContent\u003C\u002Fspan>(\u003Cspan class=\"hljs-title function_\">buildPopupContent\u003C\u002Fspan>(feature))\n    .\u003Cspan class=\"hljs-title function_\">addTo\u003C\u002Fspan>(map);\n});\n\ninstance.\u003Cspan class=\"hljs-title function_\">on\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&quot;waymark:feature.hover&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n  map.\u003Cspan class=\"hljs-title function_\">getCanvas\u003C\u002Fspan>().\u003Cspan class=\"hljs-property\">style\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">cursor\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&quot;pointer&quot;\u003C\u002Fspan>;\n});\n\ninstance.\u003Cspan class=\"hljs-title function_\">on\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&quot;waymark:feature.leave&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> {\n  map.\u003Cspan class=\"hljs-title function_\">getCanvas\u003C\u002Fspan>().\u003Cspan class=\"hljs-property\">style\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">cursor\u003C\u002Fspan> = \u003Cspan class=\"hljs-string\">&quot;&quot;\u003C\u002Fspan>;\n});\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>lngLat\u003C\u002Fcode> is the pointer event position, so non-Point features anchor where the feature was clicked; Point features can anchor on \u003Ccode>feature.geometry.coordinates\u003C\u002Fcode> instead. \u003Ccode>feature\u003C\u002Fcode> is a plain GeoJSON Feature, so \u003Ccode>feature.properties\u003C\u002Fcode> is available for consumer-side presentation decisions. The dev harness&#39;s \u003Ccode>buildPopupContent(feature)\u003C\u002Fcode> renders a title heading, at most one image whose URL is the first present of \u003Ccode>image_thumbnail_url\u003C\u002Fcode> → \u003Ccode>image_medium_url\u003C\u002Fcode> → \u003Ccode>image_large_url\u003C\u002Fcode>, and the remaining properties as key\u002Fvalue rows; all three \u003Ccode>image_*_url\u003C\u002Fcode> keys are omitted from the rows whenever an image is displayed.\u003C\u002Fp>\n\u003Ch3 id=\"click-priority\">\u003Ca class=\"heading-anchor\" href=\"#click-priority\">Click priority\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>When multiple observed layers overlap at the pointer, the highest-priority layer wins; ties keep MapLibre render order. Priority (highest first): invisible hit-circles (2), regular circles (1), lines and fills (0). Symbol icon layers are never queried — MapLibre symbols only hit-test their opaque pixels — so an icon click resolves through its companion hit circle instead. See \u003Ca href=\"\u002Fwaymark-js\u002Fapi#click-priority\">Click priority\u003C\u002Fa> in the API docs for the full table and layer ID naming.\u003C\u002Fp>\n\u003Cp>Implementation references:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FOpenGIS\u002FWaymark-JS\u002Fblob\u002Fmaster\u002Fsrc\u002Fentry.js\">\u003Ccode>src\u002Fentry.js\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FOpenGIS\u002FWaymark-JS\u002Fblob\u002Fmaster\u002Fsrc\u002Fdocument\u002FinstanceDocument.js\">\u003Ccode>src\u002Fdocument\u002FinstanceDocument.js\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FOpenGIS\u002FWaymark-JS\u002Fblob\u002Fmaster\u002Fsrc\u002Fruntime\u002FruntimeRegistry.js\">\u003Ccode>src\u002Fruntime\u002FruntimeRegistry.js\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FOpenGIS\u002FWaymark-JS\u002Fblob\u002Fmaster\u002Fsrc\u002Fgeojson\u002FcreateGeoJSONModule.js\">\u003Ccode>src\u002Fgeojson\u002FcreateGeoJSONModule.js\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FOpenGIS\u002FWaymark-JS\u002Fblob\u002Fmaster\u002Fsrc\u002Fgeojson\u002FcreateFeatureInteractionModule.js\">\u003Ccode>src\u002Fgeojson\u002FcreateFeatureInteractionModule.js\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FOpenGIS\u002FWaymark-JS\u002Fblob\u002Fmaster\u002Fsrc\u002Fruntime\u002FcreateInstanceCore.js\">\u003Ccode>src\u002Fruntime\u002FcreateInstanceCore.js\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FOpenGIS\u002FWaymark-JS\u002Fblob\u002Fmaster\u002Fsrc\u002Futils\u002FtypeUtils.js\">\u003Ccode>src\u002Futils\u002FtypeUtils.js\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fli>\n\u003Cli>\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FOpenGIS\u002FWaymark-JS\u002Fblob\u002Fmaster\u002Fdev\u002Fdev.js\">\u003Ccode>dev\u002Fdev.js\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"dev-example-reference\">\u003Ca class=\"heading-anchor\" href=\"#dev-example-reference\">Dev example reference\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>The dev playground (\u003Ccode>dev\u002Fdev.js\u003C\u002Fcode>) creates its own MapLibre map on the \u003Ccode>#map\u003C\u002Fcode> container (the vendored OpenGIS basemap style served at \u003Ccode>\u002Fbasemap\u002Fstyle.json\u003C\u002Fcode>, initial centre on Holberg, \u003Ccode>fadeDuration: 0\u003C\u002Fcode>), registers an inline \u003Ccode>photo-marker\u003C\u002Fcode> PNG via \u003Ccode>map.loadImage\u003C\u002Fcode> + \u003Ccode>map.addImage\u003C\u002Fcode> before injecting the map, then calls \u003Ccode>createInstance({ map, document })\u003C\u002Fcode>. It loads one initial InstanceDocument from \u003Ccode>\u002Froute.json\u003C\u002Fcode> plus four GeoJSON layers (\u003Ccode>track-north-island.geojson\u003C\u002Fcode>, \u003Ccode>track-port-hardy-cape-scott.geojson\u003C\u002Fcode>, \u003Ccode>markers.json\u003C\u002Fcode>, \u003Ccode>shapes.json\u003C\u002Fcode>) from \u003Ccode>\u002Fgeojson\u002F\u003C\u002Fcode>. Those files live under \u003Ccode>tests\u002Ffixtures\u002F\u003C\u002Fcode> — the single source of truth for the dev harness and the browser tests, served by pointing \u003Ccode>vite.config.dev.js\u003C\u002Fcode>&#39;s \u003Ccode>publicDir\u003C\u002Fcode> at that directory. The route config gives every type a \u003Ccode>title\u003C\u002Fcode> (including the \u003Ccode>area\u003C\u002Fcode> fill type) and maps \u003Ccode>photo\u003C\u002Fcode> to the \u003Ccode>photo-marker\u003C\u002Fcode> icon; the GeoJSON fixtures carry \u003Ccode>waymarkType\u003C\u002Fcode> only (the legacy duplicated \u003Ccode>type\u003C\u002Fcode> property has been removed). \u003Ccode>shapes.json\u003C\u002Fcode> adds a single \u003Ccode>area\u003C\u002Fcode>-typed Polygon that carries only \u003Ccode>image_medium_url\u003C\u002Fcode>, exercising the popup fallback. The big route line is \u003Ccode>track-port-hardy-cape-scott\u003C\u002Fcode>, and Holberg (the pub &quot;The Scarlet Ibis&quot;) is the demo-closeup anchor. Its toolbar controls call \u003Ccode>instance.data.addLayer(...)\u003C\u002Fcode>, \u003Ccode>instance.data.fitBounds()\u003C\u002Fcode>, \u003Ccode>instance.types.setVisibility(...)\u003C\u002Fcode>, and \u003Ccode>instance.debug.setEnabled(...)\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Adding a layer calls:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">instance.\u003Cspan class=\"hljs-property\">data\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">addLayer\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: samplePoint }, { \u003Cspan class=\"hljs-attr\">fitBounds\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan> });\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>samplePoint\u003C\u002Fcode> is \u003Ccode>tests\u002Ffixtures\u002Fgeojson\u002Fsample-point.geojson\u003C\u002Fcode>, carrying \u003Ccode>waymarkType: &quot;beer&quot;\u003C\u002Fcode> so the added layer also responds to the type-visibility control. The fixture point sits at \u003Ccode>[-127.998, 50.661]\u003C\u002Fcode>; the add-layer browser test asserts that centre after \u003Ccode>fitBounds\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>The instance and map are also exposed as globals for manual console testing:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">\u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">waymarkInstance\u003C\u002Fspan>; \u003Cspan class=\"hljs-comment\">\u002F\u002F the instance\u003C\u002Fspan>\n\u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">waymarkInstances\u003C\u002Fspan>; \u003Cspan class=\"hljs-comment\">\u002F\u002F registry keyed by dev map key\u003C\u002Fspan>\n\u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">waymarkInstances\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">map\u003C\u002Fspan>; \u003Cspan class=\"hljs-comment\">\u002F\u002F the #map instance\u003C\u002Fspan>\n\u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">waymarkMaps\u003C\u002Fspan>; \u003Cspan class=\"hljs-comment\">\u002F\u002F registry keyed by dev map key\u003C\u002Fspan>\n\u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">waymarkMaps\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">map\u003C\u002Fspan>; \u003Cspan class=\"hljs-comment\">\u002F\u002F the MapLibre map\u003C\u002Fspan>\n\u003Cspan class=\"hljs-variable language_\">window\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">createWaymarkInstance\u003C\u002Fspan>; \u003Cspan class=\"hljs-comment\">\u002F\u002F the createInstance factory\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n",{"route":17,"label":18},null,1792013384664]