GeoLeaf Core API - v3.19.0
    Preparing search index...

    Engine-agnostic map adapter interface.

    MaplibreAdapter implements this interface. All GeoLeaf modules interact with the map exclusively through this interface — no maplibregl.* references outside of the adapter implementation.

    Id-based management: Layers, markers, and cluster groups are managed by string identifiers. The adapter stores native engine objects internally — callers never interact with MapLibre overlays directly.

    Opaque handles: createPopup() returns unknown because the underlying type differs between engines. Callers must treat popup handles as opaque and pass them back to openPopup() / closePopup().

    // In a module's init():
    adapter.init({ container: 'map', center: { lat: 45.76, lng: 4.83 }, zoom: 13 });
    adapter.on('zoomend', () => console.log('zoom:', adapter.getZoom()));
    interface IMapAdapter {
        init(options: MapInitOptions): void;
        isReady(): boolean;
        destroy(): void;
        setView(center: GeoLeafLatLng, zoom: number): void;
        getCenter(): GeoLeafLatLng;
        getZoom(): number;
        setZoom(zoom: number): void;
        panTo(center: GeoLeafLatLng): void;
        flyTo(center: GeoLeafLatLng, zoom?: number): void;
        fitBounds(
            bounds: GeoLeafBounds,
            options?: { padding?: GeoLeafPoint; animate?: boolean; maxZoom?: number },
        ): void;
        getBounds(): GeoLeafBounds;
        on(event: MapEvent, handler: (e: unknown) => void): void;
        off(event: MapEvent, handler: (e: unknown) => void): void;
        once(event: MapEvent, handler: (e: unknown) => void): void;
        addGeoJSONLayer(
            id: string,
            data: unknown,
            options?: GeoLeafLayerOptions,
        ): void;
        removeLayer(id: string): void;
        hasLayer(id: string): boolean;
        showLayer(id: string): void;
        hideLayer(id: string): void;
        updateLayerData(id: string, data: unknown): void;
        applyDataDiff?(id: string, diff: LayerDataDiff): boolean;
        setFeatureState?(
            id: string,
            featureId: string | number,
            state: Record<string, unknown>,
        ): void;
        setLayerStyle(id: string, style: GeoLeafStyleOptions): void;
        setLayerFilter(id: string, filter: unknown, owner?: string): void;
        setLayerZoomRange(
            id: string,
            minZoom: number | null,
            maxZoom: number | null,
        ): void;
        setLayerZIndex?(id: string, zIndex: number): boolean;
        createMarker(
            id: string,
            position: GeoLeafLatLng,
            options?: GeoLeafMarkerOptions,
        ): void;
        removeMarker(id: string): void;
        updateMarkerPosition(id: string, position: GeoLeafLatLng): void;
        setMarkerRotation?(id: string, degrees: number): void;
        getMarkerHandle?(id: string): GeoLeafMarkerHandle | null;
        createPopup(
            content: string | HTMLElement,
            options?: GeoLeafPopupOptions,
        ): unknown;
        openPopup(popup: unknown, position?: GeoLeafLatLng): void;
        closePopup(popup?: unknown): void;
        addControl(
            control: unknown,
            position: GeoLeafControlPosition,
        ): GeoLeafControl;
        removeControl(control: GeoLeafControl): void;
        latLngToPoint(latlng: GeoLeafLatLng): GeoLeafPoint;
        pointToLatLng(point: GeoLeafPoint): GeoLeafLatLng;
        getContainer(): HTMLElement;
        getCanvas?(): HTMLCanvasElement;
        resize?(): void;
        getNativeMap?(): unknown;
        buildStyleChangeTransform?(options?: { attribution?: string }): unknown;
        declareOwnedStyleIds?(owner: string, ids: DeclaredStyleIds | null): void;
        enterExclusiveMode?(cursor?: string): void;
        exitExclusiveMode?(): void;
        isExclusiveMode?(): boolean;
        reregisterStyleImages?(): void;
        addVectorTileLayer?(layerId: string, spec: VectorTileLayerSpec): string[];
        updateVectorTileLayerStyle?(
            layerId: string,
            subLayerIds: string[],
            style: VectorTileStyleInput,
        ): void;
    }

    Implemented by

    Index
    • Adjusts the viewport to fit the given bounding box.

      Parameters

      • bounds: GeoLeafBounds

        Geographic area to fit.

      • Optionaloptions: { padding?: GeoLeafPoint; animate?: boolean; maxZoom?: number }

        padding: room kept around the bounds, in screen pixels. animate: false to jump. maxZoom: a ceiling on the zoom the framing may reach — a small area is then shown from further away instead of filling the viewport.

      Returns void

    • Subscribes to a map event.

      The handler receives an engine-specific event object (unknown). Adapters normalise geographic coordinates to GeoLeafLatLng within the event object where applicable.

      Parameters

      • event: MapEvent

        Event type token.

      • handler: (e: unknown) => void

        Callback invoked on each occurrence.

      Returns void

    • Unsubscribes a previously registered event handler. Silently does nothing if the handler was not registered.

      Parameters

      • event: MapEvent

        Event type token.

      • handler: (e: unknown) => void

        The exact function reference passed to on().

      Returns void

    • Creates a GeoJSON layer from raw GeoJSON data and adds it to the map.

      The adapter stores the layer internally keyed by id. Use removeLayer(), showLayer() / hideLayer(), updateLayerData(), and setLayerStyle() to manage it afterwards. The MapLibre adapter throws when id is already taken.

      Parameters

      • id: string

        Unique layer identifier.

      • data: unknown

        GeoJSON data (FeatureCollection, Feature, or Geometry). The adapter does not validate it: it is handed to the engine's GeoJSON source as it is.

      • Optionaloptions: GeoLeafLayerOptions

        Optional layer display options.

      Returns void

    • Replaces the GeoJSON data of an existing layer.

      Internally clears the current features and adds the new data. The layer keeps its id, options, and style.

      Parameters

      • id: string

        Layer identifier.

      • data: unknown

        New GeoJSON data (FeatureCollection, Feature, or Geometry).

      Returns void

    • Applies a LayerDataDiff to an existing layer — what MOVED, instead of the whole collection — and reports whether it took it.

      🛑 The return value is the whole point, and it is a BOOLEAN, not a promise. The only question the caller has is "did you take it?", and that is decidable synchronously: the source is present or it is not, it can identify its features or it cannot. false means the caller must fall back to IMapAdapter.updateLayerData with the full collection — which the caller holds and the adapter deliberately does not. Keeping the fallback on the caller's side is what makes it OBSERVABLE from a test with a fake adapter, and a fallback nobody can see fire is a fallback that silently becomes the only path.

      ⚠️ Not a promise, even though the underlying write is asynchronous. None of the public write methods promises completion today — setData is already asynchronous underneath and its promise is already dropped — so returning one here would widen a contract nobody asked to widen.

      ⚠️ Optional (?), like setFeatureState. One real adapter implements this interface, but a number of narrower structural look-alikes stand in for it; a required member would redden each of them and force an engine with no diff primitive to fake one out of setData, which is exactly the silent always-fallback this seam exists to prevent.

      Parameters

      • id: string

        Layer identifier.

      • diff: LayerDataDiff

        The change to apply.

      Returns boolean

      true when the diff was applied; false when the caller must re-feed the whole collection.

    • Sets feature-state on a layer's source feature, targeted by its promoted id — reactive paint (e.g. POI sync badge, hover/selection halo) without a source data rebuild. Maps to map.setFeatureState(). The source must declare promoteId for a string id to resolve.

      Parameters

      • id: string

        Layer identifier (resolves to the backing source).

      • featureId: string | number

        Promoted feature id to target.

      • state: Record<string, unknown>

        Feature-state key/values to set.

      Returns void

    • Applies a filter expression to an existing layer.

      Maps to map.setFilter() with a MapLibre expression array (e.g. ["==", ["get", "type"], "park"]).

      A layer holds one filter PER OWNER, and draws what passes all of them: a call writes and clears its own owner's filter only. The default owner, "filter", is the panel's and the visible subset's; GeoLeaf.Layers.hideFeatures writes "hidden". Until 3.12.0 a layer held one filter, and hiding an edited feature replaced the panel's.

      Parameters

      • id: string

        Layer identifier.

      • filter: unknown

        Engine-specific filter expression (unknown). Pass null to clear this owner's filter.

      • Optionalowner: string

        The filter slot written. Defaults to "filter".

      Returns void

    • Updates the zoom range of an existing layer, on every one of its sub-layers.

      Maps to map.setLayerZoomRange(). Needed because a scale bound converts to a zoom level THROUGH THE LATITUDE: the same 1:X sits at different zooms in Cayenne and in Tromsø. A range posted at load time therefore drifts as the user pans north or south, and has to be re-pushed — but never on zoom, which does not affect the conversion.

      Parameters

      • id: string

        Layer identifier.

      • minZoom: number | null

        Lowest zoom at which the layer renders, or null to clear.

      • maxZoom: number | null

        Highest zoom at which the layer renders, or null to clear.

      Returns void

    • Re-ranks an existing GeoJSON layer in place, and reports whether it did.

      The layer lands where an insertion at that rank would put it NOW: under the lowest layer of strictly higher rank — at the top of the ranked layers when there is none —, hence above the layers already holding that rank. Its sub-layers move together and keep their order: a fill stays under its outline.

      Nothing is rebuilt. The source and its data, the filters, the zoom range, the visibility and the listeners are kept: a hidden layer is re-ranked and stays hidden. The order obtained crosses a basemap switch.

      🛑 false means nothing moved, and the caller rebuilds — removeLayer, then addGeoJSONLayer at the rank it wants. It is the answer when:

      • id names no layer of this adapter;
      • zIndex is not a finite number;
      • the layer is a vector-tile layer;
      • none of its sub-layers is in the style;
      • the layer it should go under is not in the style — the engine would then take the layer out of the drawing order instead of moving it.

      Asking for the rank the layer already holds answers true and moves nothing: the layer is NOT lifted above the layers sharing that rank.

      ⚠️ What does not follow. The zIndex a layer was DECLARED with is not rewritten: the layer manager still orders its rows by it, and the hover tooltip still arbitrates two overlapping layers by it. A layer's labels do not move with it. No event is emitted.

      Optional and boolean, for the two reasons IMapAdapter.applyDataDiff gives. Call it as adapter.setLayerZIndex?.(id, zIndex) === true, so that an adapter without it — an older core — reads as a refusal.

      Parameters

      • id: string

        Layer identifier.

      • zIndex: number

        The rank to give the layer; higher is drawn on top. Need not be an integer.

      Returns boolean

      true when the layer holds that rank on return; false when nothing moved and the caller must rebuild.

      // Draw the layer above one ranked 60 — and rebuild it if the adapter refuses.
      if (adapter.setLayerZIndex?.("zones", 61) !== true) {
      adapter.removeLayer("zones");
      adapter.addGeoJSONLayer("zones", data, { zIndex: 61 });
      }
    • Creates a marker at the given position and adds it to the map.

      The adapter stores the marker internally keyed by id.

      Parameters

      • id: string

        Unique marker identifier.

      • position: GeoLeafLatLng

        Geographic position of the marker anchor.

      • Optionaloptions: GeoLeafMarkerOptions

        Marker display options.

      Returns void

      The options.icon field, if provided, must be a static hardcoded SVG string. Never pass user-provided or network-fetched content as icon — SVG can carry executable payloads (XSS).

    • Rotates an existing marker's icon.

      Pair it with rotationAlignment: "map" at creation: the engine then re-derives the on-screen angle from the map bearing on every frame, so a caller updating this once per position fix still gets a continuously correct icon during an animated camera move.

      Optional: an adapter whose engine has no marker-rotation model may omit it, and callers must handle undefined as they do for the other optional members.

      Parameters

      • id: string

        The marker identifier used in createMarker().

      • degrees: number

        Rotation clockwise, in degrees.

      Returns void

    • Returns a handle on an existing marker, or null when no marker carries that id.

      The id-based surface above covers the common cases; this exists for the two things it cannot express — reading a marker's position after the user dragged it, and subscribing to its own events. Prefer updateMarkerPosition() and the map-level on() whenever they suffice.

      Optional: an adapter whose engine has no per-marker event model may omit it, and callers must handle undefined as they do for the other optional members.

      Parameters

      • id: string

        The marker identifier used in createMarker().

      Returns GeoLeafMarkerHandle | null

    • Creates a popup with the given content.

      Returns an opaque handle (unknown) to pass to openPopup() / closePopup(). The popup is not opened automatically — call openPopup() after creation.

      Parameters

      • content: string | HTMLElement

        HTML string or DOM element to display inside the popup.

      • Optionaloptions: GeoLeafPopupOptions

      Returns unknown

      When content is a string, it must be sanitised by the caller before being passed here. The adapter does not sanitise content. Use GeoLeaf.Security.sanitizeHTML() (allowlist) or stripHtml(), or pass an already-built HTMLElement — the internal renderers use setDOMContent, never a raw string.

    • Closes a popup without destroying it. When popup is omitted, all open popups are closed.

      Parameters

      • Optionalpopup: unknown

        The opaque handle returned by createPopup(). Optional.

      Returns void

    • Returns the engine's drawing canvas — the element that actually paints the pointer.

      ⚠️ Not interchangeable with IMapAdapter.getContainer. MapLibre styles the cursor through CSS classes on .maplibregl-canvas-container, an element BETWEEN the root container and the canvas (cursor: grab on .maplibregl-interactive). A rule set on that descendant beats anything inherited from the root, so writing getContainer().style.cursor is silently overridden and never shows. Any code changing the map cursor must target the canvas.

      Optional, like IMapAdapter.resize: test doubles implement the contract without it, and making it required would turn every mock red for a capability none of them exercise. Call it as adapter.getCanvas?.() ?? adapter.getContainer() — the same fallback the editor plugin already uses.

      Returns HTMLCanvasElement

    • Asks the engine to re-read its container size and resize its drawing buffer.

      Needed after the container is moved into a parent of different dimensions — Core.reattach() calls it — because the WebGL canvas keeps the size it was built with and would otherwise render at the old dimensions in the new slot.

      Optional, like IMapAdapter.getNativeMap: test doubles implement the contract without it, and making it required would turn every mock red for a capability none of them exercise. ⚠️ Optional means a caller can invoke it on undefined and never notice — call it as adapter.resize?.() and treat its absence as "the engine does not need telling", never as success.

      Returns void

    • Returns the underlying native map instance (MapLibre GL Map object).

      Intended for low-level integrations that cannot be expressed through the standard adapter interface. Use sparingly — direct access bypasses the adapter contract and may break on engine upgrades.

      Returns unknown to avoid importing engine-specific types in this file.

      Returns unknown

    • Builds a transformStyle callback (setStyle option, since MapLibre v5) that preserves the adapter-owned sources and layers across a basemap style swap — with those declared to it through IMapAdapter.declareOwnedStyleIds —, so they survive natively rather than being torn down and re-injected, and carries the incoming basemap's declared credit onto its sources.

      🛑 What the adapter owns must be read when the engine RUNS the callback, not when it is built: for a style given by URL, that is once the style has downloaded, and the layers created in between are the adapter's too. For the same reason an empty registry at call time is no ground for returning nothing.

      Called by the basemap registry immediately before setStyle(). The return type is intentionally opaque here to keep the contract engine-agnostic. A falsy return makes the registry call a plain setStyle(), which carries nothing over.

      Parameters

      • Optionaloptions: { attribution?: string }

        attribution: the incoming basemap's declared credit.

      Returns unknown

    • Declares engine layers and sources placed on the map OUTSIDE this adapter — a plugin's own, drawn straight on the engine — so that a basemap switch replacing the style carries them into the incoming one, as it carries the adapter's own.

      Undeclared, such a layer is erased by the switch: the transform claims what is DECLARED, never what an id's shape suggests.

      Declarations are keyed by owner: a second call under the same key replaces the first, and null withdraws it. They are read when the switch RUNS, so a declaration made while a style downloads counts; an id no longer in the style by then is skipped, which makes a declaration that outlives its layers harmless. They die with the map, on destroy().

      ⚠️ Only what does not depend on the basemap belongs here. A label layer resolves its font against the style's glyph server: carried as-is into another style, it would ask the new server for a font it may not serve. The labels capability rebuilds its layers on the new style instead.

      Optional, like IMapAdapter.buildStyleChangeTransform: an adapter without it carries nothing it did not create. Call it as adapter.declareOwnedStyleIds?.(…).

      Parameters

      • owner: string

        A stable key naming the declarer, e.g. "measure" or "cog:<layerId>".

      • ids: DeclaredStyleIds | null

        The engine ids to carry, or null to withdraw the declaration.

      Returns void

      // A plugin placed its own source and line layer on the engine behind `adapter`.
      adapter.declareOwnedStyleIds?.("my-plugin", { layerIds: ["my-line"], sourceIds: ["my-src"] });
      // Once it removes them:
      adapter.declareOwnedStyleIds?.("my-plugin", null);
    • Enters exclusive mode: a tool now owns the map's clicks and cursor. While it holds, the core opens no feature popup on click and its hover handlers leave the cursor alone.

      With cursor, that CSS cursor is painted on the drawing canvas — the element that shows the pointer (see IMapAdapter.getCanvas) — and the one shown before is kept, to be restored by IMapAdapter.exitExclusiveMode. Without it, only the mode is entered and the cursor is left as it is, for a tool that manages its own or shows none.

      There is no ownership token: tools exclude each other. A tool exits the others before it enters; the last entry wins, and any exit releases the mode. Entering twice keeps the cursor shown before the first entry.

      Optional, like IMapAdapter.declareOwnedStyleIds: an adapter without it offers no exclusive mode, and nothing throws. Call it as adapter.enterExclusiveMode?.(…).

      Parameters

      • Optionalcursor: string

        CSS cursor keyword, e.g. "crosshair"; omit it to leave the cursor alone.

      Returns void

      // A pick-on-map tool takes the next click for itself.
      adapter.enterExclusiveMode?.("crosshair");
      // Once the point is picked, or the tool is cancelled:
      adapter.exitExclusiveMode?.();
    • Reports whether a tool currently holds exclusive mode — for a handler that must stand aside while another tool owns the map, e.g. one that writes the cursor on hover.

      Optional: call it as adapter.isExclusiveMode?.() === true, which reads false on an adapter that offers no exclusive mode.

      Returns boolean

      true between an IMapAdapter.enterExclusiveMode and the next exit.

    • Puts back the runtime images (hatch patterns, POI sprite icons) a style change may have left behind. Sources and layers are carried by the style transform; images are not part of the style spec, and an engine that rebuilds the style instead of diffing it starts with none. Only what is missing is added.

      Called by the basemap registry inside the post-swap style.load handler.

      Returns void

    • Adds a native vector-tile source and its styled sub-layers (fill / line / circle / fill-extrusion / casing), registering them in the adapter's layer registry. Returns the created sub-layer ids (for interaction binding and layer bookkeeping). Engine-specific (vector tiles are a MapLibre-native concept) — a non-MapLibre adapter may omit it. Socle B.1: the vector-tiles capability resolves config/style and delegates all rendering here.

      Parameters

      Returns string[]