[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"docs-nav":3,"docs-page:api":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":8,"title":23,"html":24,"prev":25,"next":26},"API","\u003Ch1 id=\"api\">API\u003C\u002Fh1>\n\u003Cblockquote>\n\u003Cp>Consumer API reference for \u003Ccode>createInstance({ map, document? })\u003C\u002Fcode>.\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cdiv class=\"alert alert-note\">\n\u003Cp class=\"alert-title\">\u003Csvg viewBox=\"0 0 16 16\" aria-hidden=\"true\">\u003Cpath d=\"M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z\"\u002F>\u003C\u002Fsvg>Note\u003C\u002Fp>\n\u003Cp>API heading names and \u003Ccode>api-contract\u003C\u002Fcode> marker blocks are enforced by sync automation. Change them only alongside matching tests and sync scripts.\u003C\u002Fp>\n\u003C\u002Fdiv>\n\u003Ch2 id=\"quick-start\">\u003Ca class=\"heading-anchor\" href=\"#quick-start\">Quick start\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>The consumer creates and owns the MapLibre map, then injects it into Waymark:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan> } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;maplibre-gl&quot;\u003C\u002Fspan>;\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;maplibre-gl\u002Fdist\u002Fmaplibre-gl.css&quot;\u003C\u002Fspan>;\n\u003Cspan class=\"hljs-keyword\">import\u003C\u002Fspan> { createInstance } \u003Cspan class=\"hljs-keyword\">from\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;waymark-js&quot;\u003C\u002Fspan>;\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> map = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>({\n  \u003Cspan class=\"hljs-attr\">container\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;map&quot;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">style\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;https:\u002F\u002Ftiles.openfreemap.org\u002Fstyles\u002Fbright&quot;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">center\u003C\u002Fspan>: [-\u003Cspan class=\"hljs-number\">128.0097\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">50.6539\u003C\u002Fspan>],\n  \u003Cspan class=\"hljs-attr\">zoom\u003C\u002Fspan>: \u003Cspan class=\"hljs-number\">13\u003C\u002Fspan>,\n});\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> instance = \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>: { \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;map&quot;\u003C\u002Fspan> },\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\">title\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;The Scarlet Ibis&quot;\u003C\u002Fspan> },\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\">128.0097\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">50.6539\u003C\u002Fspan>] },\n              },\n            ],\n          },\n        },\n      ],\n    },\n  },\n});\n\ninstance.\u003Cspan class=\"hljs-title function_\">on\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&quot;waymark:data.layer.mounted&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">event\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> {\n  \u003Cspan class=\"hljs-variable language_\">console\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(\u003Cspan class=\"hljs-string\">&quot;Layer mounted&quot;\u003C\u002Fspan>, event.\u003Cspan class=\"hljs-property\">detail\u003C\u002Fspan>);\n});\n\n\u003Cspan class=\"hljs-variable language_\">console\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">log\u003C\u002Fspan>(instance.\u003Cspan class=\"hljs-title function_\">toJSON\u003C\u002Fspan>());\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>MapLibre GL JS is a peer dependency: install and bundle \u003Ccode>maplibre-gl\u003C\u002Fcode> yourself, and import its stylesheet (\u003Ccode>maplibre-gl\u002Fdist\u002Fmaplibre-gl.css\u003C\u002Fcode>). Waymark keeps \u003Ccode>maplibre-gl\u003C\u002Fcode> external at build time and ships no CSS or worker code, so the consumer&#39;s bundler resolves MapLibre — and its worker — from the consumer&#39;s own installation. When you are finished, call \u003Ccode>instance.destroy()\u003C\u002Fcode> to remove Waymark-owned layers and listeners; the map itself remains the consumer&#39;s to remove.\u003C\u002Fp>\n\u003Cfigure>\n\u003Cimg src=\"\u002Fwaymark-js\u002Fscreenshots\u002Fapi-quick-start.png\" alt=\"Quick start — an injected map with a GeoJSON layer rendered\">\n\u003Cfigcaption>Quick start — an injected map with a GeoJSON layer rendered\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\u003Ch2 id=\"factory-signature\">\u003Ca class=\"heading-anchor\" href=\"#factory-signature\">Factory signature\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>\u003Ccode>createInstance({ map, document? })\u003C\u002Fcode>\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\u003Cth>Parameter\u003C\u002Fth>\u003Cth>Type\u003C\u002Fth>\u003Cth>Required\u003C\u002Fth>\u003Cth>Behaviour\u003C\u002Fth>\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\u003Ctd>\u003Ccode>map\u003C\u002Fcode>\u003C\u002Ftd>\u003Ctd>MapLibre \u003Ccode>Map\u003C\u002Fcode>-compatible object\u003C\u002Ftd>\u003Ctd>Yes\u003C\u002Ftd>\u003Ctd>Consumer-owned map. Waymark validates the method contract and throws a \u003Ccode>TypeError\u003C\u002Fcode> when it fails. See \u003Ca href=\"#map-injection\">Map injection\u003C\u002Fa>.\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>\u003Ccode>document\u003C\u002Fcode>\u003C\u002Ftd>\u003Ctd>\u003Ccode>object\u003C\u002Fcode>\u003C\u002Ftd>\u003Ctd>No\u003C\u002Ftd>\u003Ctd>Canonical serialisable InstanceDocument with strict top-level keys: \u003Ccode>config\u003C\u002Fcode>, \u003Ccode>state\u003C\u002Fcode>, \u003Ccode>data\u003C\u002Fcode>. Unknown top-level keys are ignored.\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Waymark guarantees strict round-trip serialisation for canonical documents. Each map takes one runtime, so round-trip uses a second map:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> first = \u003Cspan class=\"hljs-title function_\">createInstance\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">map\u003C\u002Fspan>: mapA, \u003Cspan class=\"hljs-attr\">document\u003C\u002Fspan>: instanceDocument });\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> second = \u003Cspan class=\"hljs-title function_\">createInstance\u003C\u002Fspan>({ \u003Cspan class=\"hljs-attr\">map\u003C\u002Fspan>: mapB, \u003Cspan class=\"hljs-attr\">document\u003C\u002Fspan>: first.\u003Cspan class=\"hljs-title function_\">toJSON\u003C\u002Fspan>() });\n\nsecond.\u003Cspan class=\"hljs-title function_\">toJSON\u003C\u002Fspan>(); \u003Cspan class=\"hljs-comment\">\u002F\u002F strict match for serialisable fields\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Canonical InstanceDocument shape:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">config\u003C\u002Fspan>: {\n    id?: string,\n    paint?: {\n      circle?: {\n        \u003Cspan class=\"hljs-string\">&quot;circle-color&quot;\u003C\u002Fspan>?: string,\n        \u003Cspan class=\"hljs-string\">&quot;circle-radius&quot;\u003C\u002Fspan>?: number,\n        \u003Cspan class=\"hljs-string\">&quot;circle-opacity&quot;\u003C\u002Fspan>?: number\n      },\n      line?: {\n        \u003Cspan class=\"hljs-string\">&quot;line-color&quot;\u003C\u002Fspan>?: string,\n        \u003Cspan class=\"hljs-string\">&quot;line-width&quot;\u003C\u002Fspan>?: number,\n        \u003Cspan class=\"hljs-string\">&quot;line-opacity&quot;\u003C\u002Fspan>?: number\n      },\n      fill?: {\n        \u003Cspan class=\"hljs-string\">&quot;fill-color&quot;\u003C\u002Fspan>?: string,\n        \u003Cspan class=\"hljs-string\">&quot;fill-opacity&quot;\u003C\u002Fspan>?: number\n      }\n    },\n    types?: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;string, {\n      title?: string,\n      icon?: string,\n      iconSize?: number,\n      \u003Cspan class=\"hljs-attr\">paint\u003C\u002Fspan>: {\n        circle?: object,\n        line?: object,\n        fill?: object\n      }\n    }&gt;,\n    debug?: boolean\n  },\n  \u003Cspan class=\"hljs-attr\">state\u003C\u002Fspan>: {\n    types?: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;string, { visible?: boolean }&gt;,\n    debug?: boolean\n  },\n  \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">layers\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Array\u003C\u002Fspan>&lt;{\n      type?: \u003Cspan class=\"hljs-string\">&quot;geojson&quot;\u003C\u002Fspan>,\n      \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: object,\n      paint?: {\n        circle?: object,\n        line?: object,\n        fill?: object\n      }\n    }&gt;\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>data.layers[]\u003C\u002Fcode> input accepts \u003Ccode>{ type?: &quot;geojson&quot;, data }\u003C\u002Fcode>. \u003Ccode>type\u003C\u002Fcode> defaults to \u003Ccode>&quot;geojson&quot;\u003C\u002Fcode>, and serialised output normalises each layer to \u003Ccode>{ type: &quot;geojson&quot;, data }\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>\u003Ccode>data\u003C\u002Fcode> must use one of these RFC7946-aligned 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\u003C\u002Ful>\n\u003Cp>Waymark validates essential geometry semantics at the data-layer boundary:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>positions must be arrays with at least two finite numbers (\u003Ccode>longitude\u003C\u002Fcode>, \u003Ccode>latitude\u003C\u002Fcode>)\u003C\u002Fli>\n\u003Cli>\u003Ccode>LineString\u003C\u002Fcode> must contain at least two positions\u003C\u002Fli>\n\u003Cli>\u003Ccode>LinearRing\u003C\u002Fcode> must contain at least four positions and be closed (first and last positions equivalent)\u003C\u002Fli>\n\u003Cli>\u003Ccode>Polygon\u003C\u002Fcode> must contain at least one valid ring\u003C\u002Fli>\n\u003Cli>\u003Ccode>MultiPoint\u003C\u002Fcode>\u002F\u003Ccode>MultiLineString\u003C\u002Fcode>\u002F\u003Ccode>MultiPolygon\u003C\u002Fcode> must contain only valid per-member geometries\u003C\u002Fli>\n\u003Cli>\u003Ccode>GeometryCollection\u003C\u002Fcode> validates recursively through nested geometries\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"map-injection\">\u003Ca class=\"heading-anchor\" href=\"#map-injection\">Map injection\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>\u003Ccode>map\u003C\u002Fcode> is required. The consumer creates and owns the MapLibre \u003Ccode>Map\u003C\u002Fcode> — including its container, style, basemaps, camera and attribution — and injects it into Waymark. Waymark mounts its GeoJSON overlay onto that map and mounts nothing else.\u003C\u002Fp>\n\u003Cp>Waymark duck-types the injected value against this method contract:\u003C\u002Fp>\n\u003Cp>\u003Ccode>getContainer\u003C\u002Fcode>, \u003Ccode>on\u003C\u002Fcode>, \u003Ccode>off\u003C\u002Fcode>, \u003Ccode>addSource\u003C\u002Fcode>, \u003Ccode>addLayer\u003C\u002Fcode>, \u003Ccode>getStyle\u003C\u002Fcode>, \u003Ccode>fitBounds\u003C\u002Fcode>\u003C\u002Fp>\n\u003Cp>Validation errors are thrown as \u003Ccode>TypeError\u003C\u002Fcode> and name what is missing:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>No map, or a non-object value: \u003Ccode>createInstance({ map }) requires a MapLibre-compatible map instance; received undefined.\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>Missing methods: \u003Ccode>createInstance({ map }) requires a MapLibre-compatible map instance. Missing method(s): getContainer, on, off, addSource, addLayer, getStyle, fitBounds.\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Each map owns exactly one Waymark runtime:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>The runtime registry is a \u003Ccode>WeakMap\u003C\u002Fcode> keyed by the injected map.\u003C\u002Fli>\n\u003Cli>A second \u003Ccode>createInstance(...)\u003C\u002Fcode> call with the same map throws \u003Ccode>This map already has a Waymark instance. Call destroy() on the existing instance before creating another.\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>Call \u003Ccode>destroy()\u003C\u002Fcode> before creating a replacement instance for the same map.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Ccode>instanceDocument.config.id\u003C\u002Fcode> is an optional namespacing id, not a DOM container id. It names event payloads and Waymark source\u002Flayer IDs, and it does not need to exist in the DOM. When omitted, Waymark generates one (\u003Ccode>waymark-&lt;token&gt;\u003C\u002Fcode>).\u003C\u002Fp>\n\u003Cp>\u003Ccode>createInstance(...)\u003C\u002Fcode> is a browser-runtime API: it requires DOM access because the map does. In SSR applications, construct the map on the client only, then inject it.\u003C\u002Fp>\n\u003Cp>Waymark never reads map-related config. \u003Ccode>config.map\u003C\u002Fcode>, \u003Ccode>state.map\u003C\u002Fcode>, basemap configuration, map options and camera state are not part of the instance document — configure them on the map itself. Unknown config keys are dropped during normalisation.\u003C\u002Fp>\n\u003Ch2 id=\"config-defaults-and-merge-behaviour\">\u003Ca class=\"heading-anchor\" href=\"#config-defaults-and-merge-behaviour\">Config defaults and merge behaviour\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Waymark resolves runtime config from \u003Ccode>src\u002Fconfig\u002Fdefaults.js\u003C\u002Fcode> (\u003Ccode>defaultConfig\u003C\u002Fcode>), overridden by \u003Ccode>instanceDocument.config\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Base: \u003Ccode>src\u002Fconfig\u002Fdefaults.js\u003C\u002Fcode> (\u003Ccode>defaultConfig\u003C\u002Fcode>)\u003C\u002Fli>\n\u003Cli>Override: \u003Ccode>instanceDocument.config\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>Unknown config keys are dropped during normalisation\u003C\u002Fli>\n\u003Cli>\u003Ccode>debug\u003C\u002Fcode>: \u003Ccode>false\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>paint\u003C\u002Fcode>: omitted by default (no instance-wide paint overrides); passed through as authored when provided\u003C\u002Fli>\n\u003Cli>\u003Ccode>types\u003C\u002Fcode>: omitted by default (no type-based rendering); passed through as authored when provided\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"debug-logging\">\u003Ca class=\"heading-anchor\" href=\"#debug-logging\">Debug logging\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>When \u003Ccode>config.debug\u003C\u002Fcode> is \u003Ccode>true\u003C\u002Fcode>, Waymark logs every emitted Waymark event to the browser console with a \u003Ccode>[waymark:debug]\u003C\u002Fcode> prefix:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">[\u003Cspan class=\"hljs-attr\">waymark\u003C\u002Fspan>:debug] map \u003Cspan class=\"hljs-attr\">waymark\u003C\u002Fspan>:instance.\u003Cspan class=\"hljs-property\">created\u003C\u002Fspan>\n[\u003Cspan class=\"hljs-attr\">waymark\u003C\u002Fspan>:debug] map \u003Cspan class=\"hljs-attr\">waymark\u003C\u002Fspan>:data.\u003Cspan class=\"hljs-property\">layer\u003C\u002Fspan>.\u003Cspan class=\"hljs-property\">mounted\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Debug can be controlled at runtime via the public API:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">instance.\u003Cspan class=\"hljs-property\">debug\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">setEnabled\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">true\u003C\u002Fspan>); \u003Cspan class=\"hljs-comment\">\u002F\u002F start console logging\u003C\u002Fspan>\ninstance.\u003Cspan class=\"hljs-property\">debug\u003C\u002Fspan>.\u003Cspan class=\"hljs-title function_\">setEnabled\u003C\u002Fspan>(\u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan>); \u003Cspan class=\"hljs-comment\">\u002F\u002F stop console logging\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Debug logging is console-only; there is no debug UI panel or separate public debug payload contract.\u003C\u002Fp>\n\u003Cp>\u003Ccode>config.debug\u003C\u002Fcode> is a baseline toggle. Runtime changes are serialised as \u003Ccode>state.debug\u003C\u002Fcode> delta when they diverge from the config baseline.\u003C\u002Fp>\n\u003Ch2 id=\"returned-instance-shape\">\u003Ca class=\"heading-anchor\" href=\"#returned-instance-shape\">Returned instance shape\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>\u003Ccode>createInstance(...)\u003C\u002Fcode> returns:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: string, \u003Cspan class=\"hljs-comment\">\u002F\u002F namespacing id, not a container id\u003C\u002Fspan>\n  \u003Cspan class=\"hljs-attr\">toJSON\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">InstanceDocument\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">addLayer\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">\n      layer: { type?: \u003Cspan class=\"hljs-string\">&quot;geojson&quot;\u003C\u002Fspan>, data: object, paint?: object },\n      options?: { fitBounds?: boolean }\n    \u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-attr\">fitBounds\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">options?: { padding?: number }\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>\n  },\n  \u003Cspan class=\"hljs-attr\">types\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">setVisibility\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">typeKey: string, visible: boolean\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> { \u003Cspan class=\"hljs-attr\">previous\u003C\u002Fspan>: boolean, \u003Cspan class=\"hljs-attr\">next\u003C\u002Fspan>: boolean } | \u003Cspan class=\"hljs-literal\">null\u003C\u002Fspan>,\n    \u003Cspan class=\"hljs-attr\">getVisibility\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">typeKey: string\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> boolean,\n    \u003Cspan class=\"hljs-attr\">getAll\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;string, { \u003Cspan class=\"hljs-attr\">title\u003C\u002Fspan>: string, \u003Cspan class=\"hljs-attr\">visible\u003C\u002Fspan>: boolean }&gt;,\n    \u003Cspan class=\"hljs-attr\">resetVisibility\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>\n  },\n  \u003Cspan class=\"hljs-attr\">debug\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">setEnabled\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">enabled: boolean\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>\n  },\n  \u003Cspan class=\"hljs-attr\">destroy\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">() =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">on\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">type, handler, options?\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">off\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">type, handler, options?\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">once\u003C\u002Fspan>: \u003Cspan class=\"hljs-function\">(\u003Cspan class=\"hljs-params\">type, handler, options?\u003C\u002Fspan>) =&gt;\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan>\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>id\u003C\u002Fcode> is the instance&#39;s namespacing identifier (\u003Ccode>config.id\u003C\u002Fcode> when provided, otherwise a generated \u003Ccode>waymark-&lt;token&gt;\u003C\u002Fcode>). It names event payloads and Waymark source\u002Flayer IDs; it does not need to match a DOM container id. The public API has no \u003Ccode>ui\u003C\u002Fcode> namespace, and no basemap or camera API — the injected map owns those.\u003C\u002Fp>\n\u003Cp>\u003Ccode>toJSON()\u003C\u002Fcode> is the only public serialisation API.\u003C\u002Fp>\n\u003Ch2 id=\"instance-reuse-and-destroy-semantics\">\u003Ca class=\"heading-anchor\" href=\"#instance-reuse-and-destroy-semantics\">Instance reuse and destroy semantics\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>Reuse is map-first, via a \u003Ccode>WeakMap\u003C\u002Fcode>-backed registry keyed by the injected map.\u003C\u002Fli>\n\u003Cli>Each map has one Waymark runtime. Calling \u003Ccode>createInstance(...)\u003C\u002Fcode> again with the same map throws \u003Ccode>This map already has a Waymark instance. Call destroy() on the existing instance before creating another.\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>There is no ID-based recreate path; \u003Ccode>waymark:instance.recreated\u003C\u002Fcode> is no longer emitted.\u003C\u002Fli>\n\u003Cli>\u003Ccode>destroy()\u003C\u002Fcode> is idempotent and safe to call more than once.\u003C\u002Fli>\n\u003Cli>\u003Ccode>destroy()\u003C\u002Fcode> removes only Waymark-owned layers\u002Fsources, detaches Waymark&#39;s map listeners (\u003Ccode>load\u003C\u002Fcode>, \u003Ccode>style.load\u003C\u002Fcode>, feature interaction \u003Ccode>click\u003C\u002Fcode>\u002F\u003Ccode>mousemove\u003C\u002Fcode>\u002F\u003Ccode>mouseout\u003C\u002Fcode>), removes its registry entry, and never calls \u003Ccode>map.remove()\u003C\u002Fcode> or touches the map container.\u003C\u002Fli>\n\u003Cli>After \u003Ccode>destroy()\u003C\u002Fcode>, the same map can be passed to a fresh \u003Ccode>createInstance(...)\u003C\u002Fcode> call.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"instance-event-api\">\u003Ca class=\"heading-anchor\" href=\"#instance-event-api\">Instance event API\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Events are dispatched as \u003Ccode>CustomEvent\u003C\u002Fcode>s from the injected map&#39;s container (\u003Ccode>map.getContainer()\u003C\u002Fcode>), so consumers can also listen on that element directly.\u003C\u002Fp>\n\u003Cp>\u003Ccode>on(...)\u003C\u002Fcode>, \u003Ccode>off(...)\u003C\u002Fcode>, and \u003Ccode>once(...)\u003C\u002Fcode> follow browser \u003Ccode>EventTarget\u003C\u002Fcode> semantics because handlers are attached to the container element.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Use \u003Ccode>on(type, handler, options?)\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>Use \u003Ccode>off(type, handler, options?)\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>Use \u003Ccode>once(type, handler, options?)\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Lifecycle events:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>waymark:instance.created\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:instance.destroyed\u003C\u002Fcode>\nLifecycle event payload shape:\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: string;\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Waymark no longer forwards \u003Ccode>waymark:map.*\u003C\u002Fcode> events. Listen to the injected map directly for \u003Ccode>load\u003C\u002Fcode>, \u003Ccode>moveend\u003C\u002Fcode>, camera, style and error events.\u003C\u002Fp>\n\u003Cp>Data-layer runtime events:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>waymark:data.layer.added\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:data.layer.mounted\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:data.layer.error\u003C\u002Fcode>\nData-layer mounted payload shape (\u003Ccode>waymark:data.layer.mounted\u003C\u002Fcode>):\u003C\u002Fli>\n\u003C\u002Ful>\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-title class_\">Array\u003C\u002Fspan>&lt;\u003Cspan class=\"hljs-string\">&quot;circle&quot;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&quot;line&quot;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&quot;fill&quot;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&quot;symbol&quot;\u003C\u002Fspan>&gt;,\n  \u003Cspan class=\"hljs-attr\">mountedLayerIds\u003C\u002Fspan>: string[],\n  \u003Cspan class=\"hljs-attr\">mountedTypes\u003C\u002Fspan>: string[]\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Data-layer added payload shape (\u003Ccode>waymark:data.layer.added\u003C\u002Fcode>):\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: string,\n  \u003Cspan class=\"hljs-attr\">layer\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">type\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;geojson&quot;\u003C\u002Fspan>, \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: object, paint?: object }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Data-layer error payload shape (\u003Ccode>waymark:data.layer.error\u003C\u002Fcode>):\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: string,\n  \u003Cspan class=\"hljs-attr\">stage\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;validation&quot;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&quot;runtime&quot;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">message\u003C\u002Fspan>: string\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Canonical runtime state events:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>waymark:state.changed\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:state.debug.changed\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:state.types.visibility.changed\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>No-op runtime dispatches emit no state events. Camera and basemap state events (\u003Ccode>waymark:state.map.*\u003C\u002Fcode>) are gone — the injected map owns camera and style.\u003C\u002Fp>\n\u003Cp>Feature interaction events:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>waymark:feature.click\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:feature.hover\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:feature.leave\u003C\u002Fcode>\nSee \u003Ca href=\"#feature-interaction-events\">Feature interaction events\u003C\u002Fa> for detail shape, hover\u002Fleave semantics and click priority.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>State event payload shape:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: string,\n  \u003Cspan class=\"hljs-attr\">command\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;types.visibility.set&quot;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&quot;types.visibility.reset&quot;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&quot;debug.set&quot;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">scope\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;types.visibility&quot;\u003C\u002Fspan> | \u003Cspan class=\"hljs-string\">&quot;debug&quot;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">previous\u003C\u002Fspan>: unknown,\n  \u003Cspan class=\"hljs-attr\">next\u003C\u002Fspan>: unknown,\n  meta?: { typeKey?: string },\n  \u003Cspan class=\"hljs-attr\">source\u003C\u002Fspan>: string,\n  \u003Cspan class=\"hljs-attr\">snapshot\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">types\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;string, { \u003Cspan class=\"hljs-attr\">visible\u003C\u002Fspan>: boolean }&gt;,\n    \u003Cspan class=\"hljs-attr\">debug\u003C\u002Fspan>: boolean\n  }\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 observes pointer interaction with its own mounted layers and emits feature events. Presentation is consumer-owned: Waymark does not create popups, mutate the cursor, move the camera, or whitelist feature properties.\u003C\u002Fp>\n\u003Cp>Events:\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.\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:\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 and leave semantics:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>hover\u003C\u002Fcode> fires on entry only. While the pointer stays over the same rendered feature, Waymark suppresses repeats. Feature identity is the MapLibre \u003Ccode>layerId\u003C\u002Fcode> plus the feature \u003Ccode>id\u003C\u002Fcode>; when the feature has no \u003Ccode>id\u003C\u002Fcode>, Waymark falls back to a geometry\u002Fproperties signature.\u003C\u002Fli>\n\u003Cli>\u003Ccode>leave\u003C\u002Fcode> fires when the pointer moves onto empty map space, onto a different feature (the previous feature emits \u003Ccode>leave\u003C\u002Fcode>, then the new feature emits \u003Ccode>hover\u003C\u002Fcode>), or off the map canvas.\u003C\u002Fli>\n\u003Cli>The \u003Ccode>leave\u003C\u002Fcode> detail carries the previously hovered \u003Ccode>feature\u003C\u002Fcode> and \u003Ccode>layerId\u003C\u002Fcode>, paired with the current pointer event&#39;s \u003Ccode>lngLat\u003C\u002Fcode> and \u003Ccode>originalEvent\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>Listeners detach on \u003Ccode>destroy()\u003C\u002Fcode>; no feature events are emitted after destroy.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cfigure>\n\u003Cimg src=\"\u002Fwaymark-js\u002Fscreenshots\u002Fdev-popup.png\" alt=\"Consumer-owned popup on a marker click\">\n\u003Cfigcaption>Consumer-owned popup on a marker click\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\u003Cfigure>\n\u003Cimg src=\"\u002Fwaymark-js\u002Fscreenshots\u002Fdev-popup-line.png\" alt=\"Consumer-owned popup anchored at a line click\">\n\u003Cfigcaption>Consumer-owned popup anchored at a line click\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\u003Cp>Overlapping hits resolve by per-layer priority: the highest-priority observed layer wins, and ties keep MapLibre render order. See \u003Ca href=\"#click-priority\">Click priority\u003C\u002Fa> for the layer priority table. The \u003Ccode>layerId\u003C\u002Fcode> on each event is the MapLibre layer that produced the hit — an invisible hit-circle, a regular circle, or a line\u002Ffill layer. Icon symbol layers are visual only and are not queried; an icon click resolves through its companion hit circle.\u003C\u002Fp>\n\u003Ch5 id=\"hit-circle-layers\">\u003Ca class=\"heading-anchor\" href=\"#hit-circle-layers\">Hit-circle layers\u003C\u002Fa>\u003C\u002Fh5>\n\u003Cp>Every point family gets a companion invisible hit-circle layer for reliable pointer targeting. When a feature renders as a MapLibre \u003Ccode>circle\u003C\u002Fcode> or icon \u003Ccode>symbol\u003C\u002Fcode> layer, an invisible circle (radius 14, \u003Ccode>circle-opacity: 0\u003C\u002Fcode>) carrying the same \u003Ccode>waymarkType\u003C\u002Fcode> filter is mounted as its own layer.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Icon types\u003C\u002Fstrong>: the hit circle is \u003Ccode>{baseLayerId}-circle-hit-{typeKey}\u003C\u002Fcode>, coloured with the type&#39;s \u003Ccode>circle-color\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Regular circles\u003C\u002Fstrong>: the untyped hit circle is \u003Ccode>{baseLayerId}-{family}-hit\u003C\u002Fcode>; typed circle sublayers get \u003Ccode>{baseLayerId}-{family}-type-{typeKey}-hit\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>These hit-circles are invisible to the user but detectable by \u003Ccode>queryRenderedFeatures\u003C\u002Fcode>, ensuring clicks near point features are captured reliably even when the visual marker or icon is small. The feature interaction module queries hit-circles at a higher priority than visible circle layers.\u003C\u002Fp>\n\u003Ch5 id=\"click-priority\">\u003Ca class=\"heading-anchor\" href=\"#click-priority\">Click priority\u003C\u002Fa>\u003C\u002Fh5>\n\u003Cp>When multiple observed layers overlap at the cursor position, the layer with the highest numeric priority wins when resolving the clicked feature:\u003C\u002Fp>\n\u003Cdiv class=\"table-wrap\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\u003Cth>Priority\u003C\u002Fth>\u003Cth>Layer type\u003C\u002Fth>\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\u003Ctd>2\u003C\u002Ftd>\u003Ctd>Invisible hit-circle layers\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>1\u003C\u002Ftd>\u003Ctd>Regular circle layers\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>0\u003C\u002Ftd>\u003Ctd>Line and fill layers\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Symbol icon layers are never queried — MapLibre symbol hit areas cover only the icon&#39;s opaque pixels — so clicking an icon resolves through its companion invisible hit circle (priority 2). This ensures hit circles are preferred over visible circle layers, which in turn are preferred over line and fill layers. The priority system is internal feature-interaction layer knowledge and is not exposed in the public API beyond the \u003Ccode>layerId\u003C\u002Fcode> carried on feature event details.\u003C\u002Fp>\n\u003Ch2 id=\"instancedocument-shape\">\u003Ca class=\"heading-anchor\" href=\"#instancedocument-shape\">InstanceDocument shape\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>\u003Ccode>instance.toJSON()\u003C\u002Fcode> returns a canonical serialisable InstanceDocument object:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">config\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">id\u003C\u002Fspan>: string,\n    paint?: {\n      circle?: object,\n      line?: object,\n      fill?: object\n    },\n    types?: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;string, {\n      title?: string,\n      icon?: string,\n      iconSize?: number,\n      \u003Cspan class=\"hljs-attr\">paint\u003C\u002Fspan>: {\n        circle?: object,\n        line?: object,\n        fill?: object\n      }\n    }&gt;,\n    \u003Cspan class=\"hljs-attr\">debug\u003C\u002Fspan>: boolean\n  },\n  \u003Cspan class=\"hljs-attr\">state\u003C\u002Fspan>: {\n    types?: \u003Cspan class=\"hljs-title class_\">Record\u003C\u002Fspan>&lt;string, { visible?: boolean }&gt;,\n    debug?: boolean\n  },\n  \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-attr\">layers\u003C\u002Fspan>: \u003Cspan class=\"hljs-title class_\">Array\u003C\u002Fspan>&lt;{\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>: object,\n      paint?: {\n        circle?: object,\n        line?: object,\n        fill?: object\n      }\n    }&gt;\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>config\u003C\u002Fcode> carries only authored\u002Fdefault Waymark config. There is no \u003Ccode>config.map\u003C\u002Fcode> or \u003Ccode>state.map\u003C\u002Fcode> slice: style, basemaps and camera belong to the consumer&#39;s map and are never serialised by Waymark.\u003C\u002Fp>\n\u003Cp>\u003Ccode>state\u003C\u002Fcode> is persistence delta only. Unchanged\u002Fdefault branches are omitted.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>state.types\u003C\u002Fcode> appears only when type visibility diverges from default (\u003Ccode>&quot;visible&quot;\u003C\u002Fcode>); hidden types only.\u003C\u002Fli>\n\u003Cli>\u003Ccode>state.debug\u003C\u002Fcode> appears only when runtime debug state diverges from config baseline.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Ccode>instance.toJSON()\u003C\u002Fcode> is intentionally strict and serialisable so that \u003Ccode>createInstance({ map, document: instance.toJSON() })\u003C\u002Fcode> can be reused as canonical input.\u003C\u002Fp>\n\u003Cp>\u003Ccode>toJSON()\u003C\u002Fcode> keeps \u003Ccode>config\u003C\u002Fcode> stable to authored\u002Fdefault intent.\u003C\u002Fp>\n\u003Cp>Runtime-enriched metadata (for example GeoJSON source\u002Flayer IDs and lifecycle phase) is intentionally excluded from \u003Ccode>toJSON()\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Internal orchestration boundaries are documented in \u003Ca href=\"\u002Fwaymark-js\u002Finstances\">\u003Ccode>docs\u002F3.instances.md\u003C\u002Fcode>\u003C\u002Fa>.\u003C\u002Fp>\n\u003Ch2 id=\"initial-geojson-overlay\">\u003Ca class=\"heading-anchor\" href=\"#initial-geojson-overlay\">Initial GeoJSON overlay\u003C\u002Fa>\u003C\u002Fh2>\n\u003Cp>Waymark supports data layers from both:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>initial document input (\u003Ccode>instanceDocument.data.layers[]\u003C\u002Fcode>)\u003C\u002Fli>\n\u003Cli>runtime additions (\u003Ccode>instance.data.addLayer(layer, { fitBounds?: boolean })\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Canonical layer input shape:\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; }\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Current support is \u003Ccode>geojson\u003C\u002Fcode> only. If \u003Ccode>type\u003C\u002Fcode> is omitted, Waymark defaults it to \u003Ccode>&quot;geojson&quot;\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch3 id=\"mounting-and-remounting\">\u003Ca class=\"heading-anchor\" href=\"#mounting-and-remounting\">Mounting and remounting\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cul>\n\u003Cli>If the injected map is already loaded (\u003Ccode>map.loaded()\u003C\u002Fcode>), initial layers mount immediately and synchronously. Waymark performs no asynchronous asset loading before mounting.\u003C\u002Fli>\n\u003Cli>If the map is not yet loaded, Waymark waits for the map&#39;s \u003Ccode>load\u003C\u002Fcode> event and mounts then.\u003C\u002Fli>\n\u003Cli>Waymark listens for \u003Ccode>style.load\u003C\u002Fcode> and remounts every Waymark-owned source and sublayer after a consumer style swap (for example \u003Ccode>map.setStyle(...)\u003C\u002Fcode>), so the overlay survives style changes.\u003C\u002Fli>\n\u003Cli>\u003Ccode>destroy()\u003C\u002Fcode> removes only Waymark-owned layers and sources (top-first) and detaches these listeners. It never removes the map or touches its container.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Ccode>instance.data.addLayer(...)\u003C\u002Fcode> emits:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>waymark:data.layer.added\u003C\u002Fcode> on success\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:data.layer.mounted\u003C\u002Fcode> when MapLibre sublayers for the logical layer are mounted (initial load, runtime add, and style remount)\u003C\u002Fli>\n\u003Cli>\u003Ccode>waymark:data.layer.error\u003C\u002Fcode> on failure (\u003Ccode>stage\u003C\u002Fcode> is \u003Ccode>validation\u003C\u002Fcode> or \u003Ccode>runtime\u003C\u002Fcode>)\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Invalid runtime additions throw after emitting \u003Ccode>waymark:data.layer.error\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>\u003Ccode>instance.data.fitBounds({ padding?: number })\u003C\u002Fcode> fits the injected map viewport to encompass all currently loaded data layers. It computes the combined bounding box from every layer&#39;s GeoJSON and calls \u003Ccode>map.fitBounds()\u003C\u002Fcode>. \u003Ccode>padding\u003C\u002Fcode> defaults to \u003Ccode>20\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Multiple GeoJSON layers are supported.\u003C\u002Fli>\n\u003Cli>Data-layer stack order is top-first: \u003Ccode>layers[0]\u003C\u002Fcode> is visually on top.\u003C\u002Fli>\n\u003Cli>Data 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>Geometry families are rendered by layer type:\u003Cul>\n\u003Cli>\u003Ccode>Point\u003C\u002Fcode> and \u003Ccode>MultiPoint\u003C\u002Fcode> → \u003Ccode>circle\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>LineString\u003C\u002Fcode> and \u003Ccode>MultiLineString\u003C\u002Fcode> → \u003Ccode>line\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>Polygon\u003C\u002Fcode> and \u003Ccode>MultiPolygon\u003C\u002Fcode> → \u003Ccode>fill\u003C\u002Fcode>\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli>Each family sublayer is gated by its own geometry-type filter (\u003Ccode>[&quot;==&quot;, [&quot;geometry-type&quot;], &quot;Point&quot; | &quot;LineString&quot; | &quot;Polygon&quot;]\u003C\u002Fcode>), so a mixed FeatureCollection renders each family only for its own geometries — circles no longer appear at line\u002Fpolygon vertices, and fills no longer close line bands. Type filters are ANDed with this gate via \u003Ccode>[&quot;all&quot;, ...]\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cfigure>\n\u003Cimg src=\"\u002Fwaymark-js\u002Fscreenshots\u002Fapi-geojson-families.png\" alt=\"Geometry families — circle, line and fill sublayers\">\n\u003Cfigcaption>Geometry families — circle, line and fill sublayers\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\u003Cul>\n\u003Cli>A single logical data layer may mount multiple MapLibre sublayers when the GeoJSON contains mixed families.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cfigure>\n\u003Cimg src=\"\u002Fwaymark-js\u002Fscreenshots\u002Fapi-geojson-mixed.png\" alt=\"Mixed families from a single data layer\">\n\u003Cfigcaption>Mixed families from a single data layer\u003C\u002Ffigcaption>\n\u003C\u002Ffigure>\n\u003Cul>\n\u003Cli>Each data layer is assigned a colour from a 20-colour pool, cycling per-layer index. Pool colours are used as the default when no \u003Ccode>config.paint\u003C\u002Fcode>, \u003Ccode>layer.paint\u003C\u002Fcode>, or config.types-based paint is provided.\u003C\u002Fli>\n\u003Cli>Each sublayer may also participate in type-based rendering when \u003Ccode>config.types\u003C\u002Fcode> are defined and GeoJSON features include a \u003Ccode>waymarkType\u003C\u002Fcode> property. See \u003Ca href=\"#paint--types\">Paint &amp; Types\u003C\u002Fa> for the full merge semantics.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3 id=\"paint--types\">\u003Ca class=\"heading-anchor\" href=\"#paint--types\">Paint &amp; Types\u003C\u002Fa>\u003C\u002Fh3>\n\u003Cp>Waymark supports per-layer paint overrides and type-based visual rendering through \u003Ccode>config.paint\u003C\u002Fcode>, per-layer \u003Ccode>paint\u003C\u002Fcode>, and \u003Ccode>config.types\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch4 id=\"configpaint-instance-wide-paint-defaults\">\u003Ca class=\"heading-anchor\" href=\"#configpaint-instance-wide-paint-defaults\">\u003Ccode>config.paint\u003C\u002Fcode> (instance-wide paint defaults)\u003C\u002Fa>\u003C\u002Fh4>\n\u003Cp>\u003Ccode>config.paint\u003C\u002Fcode> provides instance-wide paint overrides scoped by geometry family:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">paint\u003C\u002Fspan>: {\n    circle?: {\n      \u003Cspan class=\"hljs-string\">&quot;circle-color&quot;\u003C\u002Fspan>?: string,\n      \u003Cspan class=\"hljs-string\">&quot;circle-radius&quot;\u003C\u002Fspan>?: number,\n      \u003Cspan class=\"hljs-string\">&quot;circle-opacity&quot;\u003C\u002Fspan>?: number\n    },\n    line?: {\n      \u003Cspan class=\"hljs-string\">&quot;line-color&quot;\u003C\u002Fspan>?: string,\n      \u003Cspan class=\"hljs-string\">&quot;line-width&quot;\u003C\u002Fspan>?: number,\n      \u003Cspan class=\"hljs-string\">&quot;line-opacity&quot;\u003C\u002Fspan>?: number\n    },\n    fill?: {\n      \u003Cspan class=\"hljs-string\">&quot;fill-color&quot;\u003C\u002Fspan>?: string,\n      \u003Cspan class=\"hljs-string\">&quot;fill-opacity&quot;\u003C\u002Fspan>?: number\n    }\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Values in \u003Ccode>config.paint\u003C\u002Fcode> override the pool-assigned colour but sit below per-layer and per-type paint in the merge hierarchy.\u003C\u002Fp>\n\u003Ch4 id=\"per-layer-paint\">\u003Ca class=\"heading-anchor\" href=\"#per-layer-paint\">Per-layer \u003Ccode>paint\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fh4>\n\u003Cp>Each data layer can supply its own family-scoped paint:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">data\u003C\u002Fspan>: { ... },\n  \u003Cspan class=\"hljs-attr\">paint\u003C\u002Fspan>: {\n    circle?: { \u003Cspan class=\"hljs-string\">&quot;circle-color&quot;\u003C\u002Fspan>?: string, ... },\n    line?: { \u003Cspan class=\"hljs-string\">&quot;line-color&quot;\u003C\u002Fspan>?: string, ... },\n    fill?: { \u003Cspan class=\"hljs-string\">&quot;fill-color&quot;\u003C\u002Fspan>?: string, ... }\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Per-layer paint overrides both the pool colour and \u003Ccode>config.paint\u003C\u002Fcode> for that layer&#39;s sublayers.\u003C\u002Fp>\n\u003Ch4 id=\"configtypes-type-based-rendering\">\u003Ca class=\"heading-anchor\" href=\"#configtypes-type-based-rendering\">\u003Ccode>config.types\u003C\u002Fcode> (type-based rendering)\u003C\u002Fa>\u003C\u002Fh4>\n\u003Cp>\u003Ccode>config.types\u003C\u002Fcode> enables data-driven visual rendering keyed by feature property values. When types are defined, features with a matching \u003Ccode>waymarkType\u003C\u002Fcode> property get dedicated sublayers with type-specific paint:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">types\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-string\">&quot;&lt;typeKey&gt;&quot;\u003C\u002Fspan>: {\n      title?: string,\n      icon?: string,\n      iconSize?: number,\n      \u003Cspan class=\"hljs-attr\">paint\u003C\u002Fspan>: {\n        circle?: { \u003Cspan class=\"hljs-string\">&quot;circle-color&quot;\u003C\u002Fspan>?: string, ... },\n        line?: { \u003Cspan class=\"hljs-string\">&quot;line-color&quot;\u003C\u002Fspan>?: string, ... },\n        fill?: { \u003Cspan class=\"hljs-string\">&quot;fill-color&quot;\u003C\u002Fspan>?: string, ... }\n      }\n    }\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>title\u003C\u002Fcode> is an optional non-empty string label for the type. It is validated, preserved in \u003Ccode>toJSON().config.types\u003C\u002Fcode>, and returned by \u003Ca href=\"#instancetypes-runtime-api\">\u003Ccode>instance.types.getAll()\u003C\u002Fcode>\u003C\u002Fa>, falling back to the type key when omitted.\u003C\u002Fp>\n\u003Cp>Every type definition requires a \u003Ccode>paint\u003C\u002Fcode> block with at least one family (\u003Ccode>circle\u003C\u002Fcode>, \u003Ccode>line\u003C\u002Fcode>, or \u003Ccode>fill\u003C\u002Fcode>).\u003C\u002Fp>\n\u003Cp>Type keys must be non-empty and 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). For each defined type, Waymark creates per-family sublayers filtered by the type key, ANDed with the family&#39;s geometry-type gate: \u003Ccode>[&quot;all&quot;, [&quot;==&quot;, [&quot;geometry-type&quot;], &quot;&lt;family geometry type&gt;&quot;], [&quot;==&quot;, [&quot;get&quot;, &quot;waymarkType&quot;], &quot;&lt;typeKey&gt;&quot;]]\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch5 id=\"icon-property\">\u003Ca class=\"heading-anchor\" href=\"#icon-property\">\u003Ccode>icon\u003C\u002Fcode> property\u003C\u002Fa>\u003C\u002Fh5>\n\u003Cp>\u003Ccode>config.types[typeKey].icon\u003C\u002Fcode> (optional non-empty string) is a \u003Cstrong>name hook\u003C\u002Fstrong>: it names a map image that the consumer must register with \u003Ccode>map.addImage(typeDef.icon, image)\u003C\u002Fcode>. Waymark never bundles, loads, or registers icon images — it only writes the name into the sublayer&#39;s \u003Ccode>icon-image\u003C\u002Fcode> layout.\u003C\u002Fp>\n\u003Cp>When \u003Ccode>icon\u003C\u002Fcode> is set and the type&#39;s paint includes a truthy \u003Ccode>circle-color\u003C\u002Fcode> (for example \u003Ccode>paint: { circle: { &quot;circle-color&quot;: &quot;#754423&quot; } }\u003C\u002Fcode>), point features of that type render as:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>a \u003Ccode>symbol\u003C\u002Fcode> sublayer (\u003Ccode>{baseLayerId}-symbol-type-{typeKey}\u003C\u002Fcode>) with \u003Ccode>layout\u003C\u002Fcode>: \u003Ccode>icon-image\u003C\u002Fcode> set to the \u003Ccode>icon\u003C\u002Fcode> value, \u003Ccode>icon-size\u003C\u002Fcode> set to \u003Ccode>iconSize ?? 1.5\u003C\u002Fcode>, \u003Ccode>icon-allow-overlap: true\u003C\u002Fcode>, \u003Ccode>icon-ignore-placement: true\u003C\u002Fcode>; paint \u003Ccode>{ &quot;icon-opacity&quot;: 1 }\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>an invisible hit circle (\u003Ccode>{baseLayerId}-circle-hit-{typeKey}\u003C\u002Fcode>, radius 14, \u003Ccode>circle-opacity: 0\u003C\u002Fcode>, painted with the type&#39;s \u003Ccode>circle-color\u003C\u002Fcode>) for reliable pointer targeting — MapLibre symbol hit areas cover only the icon&#39;s opaque pixels\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Register the image before the layer mounts (or as soon as it becomes available); until then MapLibre logs a missing-image warning and draws no icon. Waymark emits \u003Ccode>&quot;symbol&quot;\u003C\u002Fcode> in the \u003Ccode>mountedFamilies\u003C\u002Fcode> payload for these layers.\u003C\u002Fp>\n\u003Cp>If \u003Ccode>icon\u003C\u002Fcode> is set but the type&#39;s \u003Ccode>paint.circle\u003C\u002Fcode> has no truthy \u003Ccode>circle-color\u003C\u002Fcode>, the type does not take the symbol branch: it renders as the normal typed circle sublayer when \u003Ccode>paint.circle\u003C\u002Fcode> exists, or is skipped for point features.\u003C\u002Fp>\n\u003Ch5 id=\"iconsize-property\">\u003Ca class=\"heading-anchor\" href=\"#iconsize-property\">\u003Ccode>iconSize\u003C\u002Fcode> property\u003C\u002Fa>\u003C\u002Fh5>\n\u003Cp>\u003Ccode>config.types[typeKey].iconSize\u003C\u002Fcode> (optional positive number) sets the \u003Ccode>icon-size\u003C\u002Fcode> layout value for the icon symbol sublayer. Defaults to \u003Ccode>1.5\u003C\u002Fcode> when omitted. For example, \u003Ccode>iconSize: 1\u003C\u002Fcode> renders the consumer-registered image at its native size.\u003C\u002Fp>\n\u003Cp>\u003Ccode>iconSize\u003C\u002Fcode> has no effect unless the type takes the symbol branch — see \u003Ca href=\"#icon-property\">\u003Ccode>icon\u003C\u002Fcode> property\u003C\u002Fa>.\u003C\u002Fp>\n\u003Ch5 id=\"type-rendering-plan\">\u003Ca class=\"heading-anchor\" href=\"#type-rendering-plan\">Type rendering plan\u003C\u002Fa>\u003C\u002Fh5>\n\u003Cp>When types are defined, the render plan for each data layer produces:\u003C\u002Fp>\n\u003Col>\n\u003Cli>An \u003Cstrong>untyped fallback\u003C\u002Fstrong> sublayer per geometry family, filtered to features without \u003Ccode>waymarkType\u003C\u002Fcode> (the family geometry-type gate plus \u003Ccode>[&quot;!&quot;, [&quot;has&quot;, &quot;waymarkType&quot;]]\u003C\u002Fcode>), using pool colour merged with \u003Ccode>config.paint\u003C\u002Fcode> and \u003Ccode>layer.paint\u003C\u002Fcode>.\u003C\u002Fli>\n\u003Cli>A \u003Cstrong>per-type sublayer\u003C\u002Fstrong> for each type that provides a matching family-scoped paint (including icon symbol types), filtered by the \u003Ccode>waymarkType\u003C\u002Fcode> key ANDed with the family geometry-type gate.\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Type paint sits above pool colour, \u003Ccode>config.paint\u003C\u002Fcode>, and \u003Ccode>layers[].paint\u003C\u002Fcode> in the merge hierarchy for typed sublayers.\u003C\u002Fp>\n\u003Cp>When a type has an \u003Ccode>icon\u003C\u002Fcode> property and a truthy \u003Ccode>circle-color\u003C\u002Fcode>, its point family renders as an icon \u003Ccode>symbol\u003C\u002Fcode> sublayer (with its own invisible hit circle) instead of a visible typed circle. The symbol sublayer&#39;s paint uses \u003Ccode>icon-opacity\u003C\u002Fcode> only — the consumer-registered image supplies the visuals, and the type&#39;s \u003Ccode>circle-color\u003C\u002Fcode> colours the companion hit circle.\u003C\u002Fp>\n\u003Ch4 id=\"paint-merge-order-highest-to-lowest-priority\">\u003Ca class=\"heading-anchor\" href=\"#paint-merge-order-highest-to-lowest-priority\">Paint merge order (highest to lowest priority)\u003C\u002Fa>\u003C\u002Fh4>\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>) — icon symbol layers instead carry \u003Ccode>{ &quot;icon-opacity&quot;: 1 }\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 (radius, width, opacity)\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch4 id=\"per-feature-waymarkpaint\">\u003Ca class=\"heading-anchor\" href=\"#per-feature-waymarkpaint\">Per-feature \u003Ccode>waymarkPaint\u003C\u002Fcode>\u003C\u002Fa>\u003C\u002Fh4>\n\u003Cp>Any GeoJSON feature can override its resolved paint with a \u003Ccode>waymarkPaint\u003C\u002Fcode> property. When a layer contains a feature carrying the property that 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\">title\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;The Scarlet Ibis&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\">128.0097\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">50.6539\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 (\u003Ccode>circle-color\u003C\u002Fcode>, \u003Ccode>line-width\u003C\u002Fcode>, \u003Ccode>fill-opacity\u003C\u002Fcode>, and so on). Hit circles and icon symbol layers stay static — per-feature paint affects visible circle\u002Fline\u002Ffill sublayers only. \u003Ccode>waymarkPaint\u003C\u002Fcode> is an ordinary feature property: it is not stripped by feature interaction normalisation and round-trips through \u003Ccode>toJSON()\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch4 id=\"instancetypes-runtime-api\">\u003Ca class=\"heading-anchor\" href=\"#instancetypes-runtime-api\">\u003Ccode>instance.types\u003C\u002Fcode> runtime API\u003C\u002Fa>\u003C\u002Fh4>\n\u003Cpre>\u003Ccode>instance.types.setVisibility(typeKey, visible: boolean)  → { previous: boolean, next: boolean } | null\ninstance.types.getVisibility(typeKey)                    → boolean\ninstance.types.getAll()                                  → Record&lt;string, { title: string, visible: boolean }&gt;\ninstance.types.resetVisibility()\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n\u003Cli>\u003Ccode>setVisibility(boolean)\u003C\u002Fcode> hides or shows every layer owned by a type — typed family sublayers, icon symbol layers, and their hit circles — by setting MapLibre&#39;s \u003Ccode>visibility\u003C\u002Fcode> layout property to \u003Ccode>&quot;none&quot;\u003C\u002Fcode> or \u003Ccode>&quot;visible&quot;\u003C\u002Fcode>. Returns \u003Ccode>{ previous, next }\u003C\u002Fcode> on success, \u003Ccode>null\u003C\u002Fcode> if destroyed or type key unknown.\u003C\u002Fli>\n\u003Cli>\u003Ccode>getVisibility\u003C\u002Fcode> returns \u003Ccode>true\u003C\u002Fcode> (visible) or \u003Ccode>false\u003C\u002Fcode> (hidden). Returns \u003Ccode>true\u003C\u002Fcode> for unknown keys.\u003C\u002Fli>\n\u003Cli>\u003Ccode>getAll\u003C\u002Fcode> returns a map keyed by type key; each entry carries the type&#39;s \u003Ccode>title\u003C\u002Fcode> (falling back to the type key when omitted) and its current \u003Ccode>visible\u003C\u002Fcode> boolean. Paint, icon, and icon-size details are not included.\u003C\u002Fli>\n\u003Cli>\u003Ccode>resetVisibility\u003C\u002Fcode> restores all types to visible (no return value).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Type visibility produces \u003Ccode>waymark:state.types.visibility.changed\u003C\u002Fcode> (alongside \u003Ccode>waymark:state.changed\u003C\u002Fcode>) state events.\u003C\u002Fp>\n\u003Ch4 id=\"paint-serialisation\">\u003Ca class=\"heading-anchor\" href=\"#paint-serialisation\">Paint serialisation\u003C\u002Fa>\u003C\u002Fh4>\n\u003Cul>\n\u003Cli>\u003Ccode>config.paint\u003C\u002Fcode> and \u003Ccode>config.types\u003C\u002Fcode> are serialised in \u003Ccode>toJSON().config\u003C\u002Fcode> as authored.\u003C\u002Fli>\n\u003Cli>\u003Ccode>state.types\u003C\u002Fcode> appears in \u003Ccode>toJSON()\u003C\u002Fcode> only when type visibility diverges from default (any type set to \u003Ccode>false\u003C\u002Fcode>). It contains only the visibility delta:\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">{\n  \u003Cspan class=\"hljs-attr\">types\u003C\u002Fspan>: {\n    \u003Cspan class=\"hljs-string\">&quot;&lt;typeKey&gt;&quot;\u003C\u002Fspan>: { \u003Cspan class=\"hljs-attr\">visible\u003C\u002Fspan>: \u003Cspan class=\"hljs-literal\">false\u003C\u002Fspan> }\n  }\n}\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n\u003Cli>Per-layer \u003Ccode>paint\u003C\u002Fcode> is serialised alongside each layer in \u003Ccode>toJSON().data.layers[].paint\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode class=\"hljs language-js\">\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> map = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Map\u003C\u002Fspan>({\n  \u003Cspan class=\"hljs-attr\">container\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;map&quot;\u003C\u002Fspan>,\n  \u003Cspan class=\"hljs-attr\">style\u003C\u002Fspan>: \u003Cspan class=\"hljs-string\">&quot;https:\u002F\u002Ftiles.openfreemap.org\u002Fstyles\u002Fbright&quot;\u003C\u002Fspan>,\n});\n\n\u003Cspan class=\"hljs-keyword\">const\u003C\u002Fspan> instance = \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    },\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        },\n      ],\n    },\n  },\n});\n\ninstance.\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>GeoJSON source\u002Flayer IDs are instance-scoped 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>The \u003Ccode>{id}\u003C\u002Fcode> token is the instance&#39;s namespacing id, with characters outside \u003Ccode>[a-zA-Z0-9_-]\u003C\u002Fcode> replaced by \u003Ccode>-\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>For the full data contract (validation, serialisation, runtime mounting, and dev example usage), see \u003Ca href=\"\u002Fwaymark-js\u002Fdata\">\u003Ccode>docs\u002F5.data.md\u003C\u002Fcode>\u003C\u002Fa>.\u003C\u002Fp>\n",{"route":5,"label":6},{"route":11,"label":12},1792013384663]