Map Configuration
Map configuration defines the initial view, interaction settings, controls, and legends for map blocks. All configuration follows the MapLibre GL JS API.
Map Config Structure
Section titled “Map Config Structure”- type: map id: my-map config: # Map configuration center: [lng, lat] zoom: 12 mapStyle: "..." pitch: 0 bearing: 0 # ... additional options state: {...} # Runtime-tunable values (optional) parameters: {...} # Control metadata for state keys → params panel (optional) markers: [...] # Standalone DOM pins (optional) images: {...} # Named images for icons and patterns (optional) light: {...} # Style-spec light for fill-extrusion shading (optional) layers: [...] # Map layers controls: {...} # Map controls (optional) legend: {...} # Legend (optional)Required Properties
Section titled “Required Properties”| Property | Type | Description |
|---|---|---|
center | [number, number] | Initial map center [longitude, latitude] |
zoom | number | Initial zoom level (0-24) |
mapStyle | string | object | MapLibre style URL or style object |
Example
Section titled “Example”config: center: [-74.006, 40.7128] zoom: 12 mapStyle: "https://demotiles.maplibre.org/style.json"Runtime state and parameters
Section titled “Runtime state and parameters”state: declares runtime-tunable values a map can change after it loads, read
from expressions via global-state. It is a document-root key — a sibling of
config:, not nested inside it — and it uses the MapLibre style spec’s own
shape, { key: { default: value } }:
type: mapid: massingstate: scenario: default: builtconfig: center: [-73.98, 40.75] zoom: 12layers: - id: buildings type: fill-extrusion source: parcels paint: fill-extrusion-height: - case - ["==", ["global-state", "scenario"], "built"] - ["get", "built_height"] - ["get", "zoned_height"]parameters: carries the presentation metadata a control UI needs to render a
picker for a state key — the library owns the parameter’s shape, the host owns
how it looks. It holds no default; the value lives in state:.
parameters: scenario: label: Massing scenario type: enum values: [built, potential]Declaring parameters: renders the built-in params panel (top-right, in
the same corner system as the legend): each entry becomes a control writing
its state key via setGlobalStateProperty, seeded from the key’s state:
default. Each entry:
| Property | Type | Description |
|---|---|---|
label | string | Control caption (defaults to the state key) |
type | string | range, select (alias enum), or toggle; unrecognized types render a labeled read-only row. Omitted: inferred — values present → select, boolean default → toggle |
values | array | The options, for select — authored value types survive (a numeric enum writes numbers) |
min / max | number | Bounds, for range (both required — a range without them renders read-only) |
step | number | Increment, for range |
The panel also lists a visibility checkbox for every layer with an
authored label: (set toggleable: false to opt a labeled layer out).
The label is the opt-in on purpose: toggleable defaults to true on
every layer, so it cannot signal intent by itself. Layer toggles write
plain layout visibility and work on every supported runtime; parameter
controls need maplibre-gl ≥ 5.6 and degrade to one notice below it. Panel
writes are observable as ml-map:parameter-change and
ml-map:layer-visibility events.
Markers
Section titled “Markers”markers: places standalone DOM pins (maplibregl.Marker) — a document-root
key, a sibling of config:. Each entry:
| Property | Type | Description |
|---|---|---|
at | [number, number] | Marker position [longitude, latitude] (required) |
color | color | Pin color (default pin only; ignored when icon is set) |
size | number | Scale multiplier over MapLibre’s default pin (1 = default) |
icon | string | Image URL replacing the default pin |
popup | popup content | Popup opened by clicking the marker — same structured content as layer popups |
type: mapid: pinsconfig: center: [-73.98, 40.75] zoom: 12markers: - at: [-73.985, 40.748] color: "#e63946" popup: - h3: - str: Empire State BuildingMarkers export with a fallback (class exports-with-fallback): mlym emit --with-fallbacks lowers them to a symbol layer with generated pin sprites
(the pins render; dragging and popup content do not compile), and --strict
refuses marker documents. icon: URLs are fetched into the document sprite
at compile time, so exported icon markers keep their images. See the export
classes reference.
Popups
Section titled “Popups”popups: opens standalone popups at coordinates: a popup with no layer
under it and no marker above it (MapLibre’s new Popup().setLngLat(...) .addTo(map)). It is a document-root key, a sibling of config:. Each entry:
| Property | Type | Description |
|---|---|---|
at | [number, number] | Popup anchor [longitude, latitude] (required) |
content | popup content | Same structured content as layer popups (required) |
closeButton | boolean | Show the close button (MapLibre default: true) |
closeOnClick | boolean | Close when the map is clicked (MapLibre default: true) |
maxWidth | string | CSS max-width, e.g. "300px" (MapLibre default: 240px) |
type: mapid: helloconfig: center: [-96, 37.8] zoom: 3popups: - at: [-96, 37.8] closeOnClick: false content: - h1: - str: Hello World!Popups are open from load. Content runs through the same trust gate as every
other popup: tags are allowlisted, str: values are escaped, and there is no
feature, so property: lookups resolve empty. A marker with its pin hidden
can’t stand in for this. A marker’s popup opens on a click on the pin, so a
pinless marker’s popup could never open.
Popups don’t export (class no-export). mlym emit reports them as a
contract warning, and the emitted style has no popups.
Images
Section titled “Images”images: declares named images for symbol layers and patterns — a
document-root key, a sibling of config:. Each entry is a URL string or an
options object:
| Property | Type | Description |
|---|---|---|
url | string | Image URL (required; a bare string is shorthand for { url }) |
sdf | boolean | Treat as a signed-distance-field icon (tintable via icon-color) |
pixelRatio | number | Source density — 2 means the file is @2x its CSS size |
type: mapid: iconicconfig: center: [0, 0] zoom: 2images: poi: https://example.com/poi.png arrow: url: https://example.com/arrow.png sdf: truelayers: - id: spots type: symbol source: pts layout: icon-image: poiLive, every image loads before layers are added, so references resolve
on first render. Images load with crossOrigin="anonymous" (a WebGL
texture must be untainted), so the host must allow cross-origin access
on every response in the chain, redirects included — a host that serves
plain <img> tags fine can still fail here. A failed image warns once and
the document keeps rendering. On export the construct fully compiles (class exports):
mlym emit fetches each URL into the document sprite and rewrites literal
references (icon-image, fill-pattern, line-pattern,
background-pattern, fill-extrusion-pattern) to mlym:<name> —
--strict accepts image documents. Dynamic references
(["image", ["get", "icon"]]) resolve live but cannot be rewritten at
compile time; emit reports them, and runtime values naming declared images
must spell mlym:<name> themselves. A *-pattern (or icon-image) may be
an expression that selects among declared names —
fill-pattern: [match, [get, class], sand, earth, landuse] — and every
literal output is rewritten, with no warning. Relative URLs render live but cannot
be fetched at compile time — emit reports them as lossy, so --strict
refuses them.
Terrain, sky & projection
Section titled “Terrain, sky & projection”Three document-root keys, siblings of config:, author the map-level 3D
scene. Each one is the style spec’s own root property, in the spec’s own
shape, so what you write is exactly what an exported style.json carries. In
a format-v2 document they live under style: instead
(style.terrain, style.sky, style.projection).
type: mapid: alpsconfig: center: [11.39, 47.27] zoom: 12 pitch: 70 maxPitch: 85 mapStyle: "https://demotiles.maplibre.org/style.json"sources: terrainSource: type: raster-dem url: "https://tiles.mapterhorn.com/tilejson.json" encoding: terrariumterrain: source: terrainSource exaggeration: 1.5sky: sky-color: "#199EF3" horizon-color: "#ffffff" fog-color: "#ffffff" fog-ground-blend: 0.1 atmosphere-blend: ["interpolate", ["linear"], ["zoom"], 0, 1, 5, 1, 7, 0]projection: type: globecontrols: navigation: true globe: true terrain: trueterrain:
Section titled “terrain:”| Property | Type | Description |
|---|---|---|
source | string | Name of a raster-dem source: one in sources:, or one the basemap supplies (required) |
exaggeration | number ≥ 0 | Vertical exaggeration (spec default 1) |
Declare the DEM as a named source. Give a hillshade layer its own DEM
source rather than sharing the terrain’s; one source serving both renders at
reduced quality. A source that names nothing, or a non-DEM source, warns
once and the map renders flat.
| Property | Type | Description |
|---|---|---|
sky-color | color or expression | The sky above the horizon |
horizon-color | color or expression | The band where sky meets fog |
fog-color | color or expression | Fog over distant 3D terrain |
sky-horizon-blend | 0–1 or expression | How far the horizon color blends up into the sky |
horizon-fog-blend | 0–1 or expression | How far the horizon color blends down into the fog |
fog-ground-blend | 0–1 or expression | Where fog starts: 0 at the camera, 1 at the horizon |
atmosphere-blend | 0–1 or expression | Globe atmosphere opacity; draws under globe projection only |
sky: {} turns on MapLibre’s default sky. The sky is only visible when a
pitched camera can see the horizon, and fog only affects 3D terrain.
projection:
Section titled “projection:”| Property | Type | Description |
|---|---|---|
type | mercator | globe | mercator is the default |
Runtime requirements
Section titled “Runtime requirements”The live map applies each key with its MapLibre setter. Each setter is feature-detected: on a runtime that lacks one, the document logs one console warning naming the minimum version and keeps rendering without that effect. It never throws.
| Key | Needs maplibre-gl | On an older runtime |
|---|---|---|
terrain: | 2.2.0 (setTerrain) | Every supported peer version has it |
sky: | 4.5.0 (setSky) | Renders without sky or fog |
sky.atmosphere-blend | 5.0.0 (draws under globe only) | No atmosphere (there is no globe) |
projection: globe | 5.0.0 (setProjection) | Renders in mercator |
controls.globe | 5.0.0 (GlobeControl) | The control is omitted |
The minimums come from the maplibre-gl changelog: 3D terrain arrived in
2.2.0, the spec sky in 4.5.0, and globe projection with its atmosphere in
5.0.0.
On export
Section titled “On export”All three keys are class exports. mlym emit copies them verbatim to the
style.json root. When the basemap also defines one, the document’s value
replaces the basemap’s wholesale, as it does live, and emit notes the
override. A terrain.source that resolves to no raster-dem source, after
the basemap merge, is dropped with a lossy warning rather than shipped as a
style MapLibre would reject. With --target below a key’s floor, emit keeps
the key (it still validates on 4.x) and reports the visual difference as
lossy, so --strict --target 4.7.1 refuses a globe document. See the
export classes reference.
light: is the style-spec root light
— the single light that shades fill-extrusion faces. A document-root key,
a sibling of config: (in format v2 it lives in the style: half). Every
property is optional and accepts a zoom expression:
| Property | Type | Default | Description |
|---|---|---|---|
anchor | map | viewport | viewport | map fixes the light to the map, so shading stays put as the map turns; viewport fixes it to the screen |
position | [radial, azimuthal°, polar°] | [1.15, 210, 30] | Distance from the base of an object, direction clockwise from north (at anchor: map), and angle from overhead |
color | color | white | Light color |
intensity | number 0–1 | 0.5 | Higher is more extreme contrast between faces |
type: mapid: litconfig: center: [-74.0118, 40.7075] zoom: 15.9 pitch: 60light: anchor: map position: [1.15, 239, 30] intensity: 0.5The object is closed: an unknown key is a validation error, not a silently
ignored setting. Live, the renderer applies it with map.setLight once the
style loads. On export it compiles through verbatim to the style’s light
(class exports), replacing any light the basemap carried. The
crosshatch classic uses it to shade its
hatched buildings.
View Options
Section titled “View Options”Control the camera position and orientation.
| Property | Type | Default | Range | Description |
|---|---|---|---|---|
pitch | number | 0 | 0-85 | Camera tilt angle in degrees |
bearing | number | 0 | -180 to 180 | Camera rotation in degrees |
bounds | array | - | - | Fit map to geographic bounds |
fitTo | object | - | - | Fit the initial camera to a GeoJSON source’s data (see below) |
Examples
Section titled “Examples”3D Perspective View
Section titled “3D Perspective View”config: center: [-122.4194, 37.7749] zoom: 15 pitch: 60 bearing: 30 mapStyle: "https://demotiles.maplibre.org/style.json"Fit to Bounds
Section titled “Fit to Bounds”config: center: [-74.006, 40.7128] # Fallback center zoom: 12 # Fallback zoom bounds: [-74.3, 40.5, -73.7, 40.9] # [west, south, east, north] mapStyle: "https://demotiles.maplibre.org/style.json"Fit to a Source’s Data
Section titled “Fit to a Source’s Data”fitTo is the data-driven sibling of bounds. Instead of literal
coordinates it names a GeoJSON entry in sources:, and the camera frames
whatever that source holds.
| Property | Type | Description |
|---|---|---|
source | string | Name of a GeoJSON entry in sources: (required) |
padding | number | Padding in pixels around the fitted bounds |
maxZoom | number | Upper zoom limit for the fit. Without it, a single point zooms all the way in. |
type: mapid: routeconfig: center: [-77.02, 38.89] # Before the data arrives, and the fallback zoom: 12 fitTo: source: route padding: 20sources: route: type: geojson url: https://example.com/route.geojsonlayers: - id: route type: line source: route- Inline
data:: the map is constructed already framed, with no camera jump. url:: the map opens atcenter/zoom, then frames the data when the first fetch lands. Refreshes don’t re-fit, because the user owns the camera after load. Anml-map:camera-fitevent fires when the fit applies.- vector / raster / raster-dem: not supported. Tiled sources have no
client-side extent. The renderer warns once and keeps
center/zoom. fitTowins overboundswhen both are set. In format v2 it lives atruntime.fitTo.
On export fitTo exports with a fallback (class exports-with-fallback). mlym emit --with-fallbacks
computes the fitted center/zoom from an inline source for a 1024×768
reference viewport. That’s lossy, because a live map fits its own container.
For a fetched source the authored camera stands in, also reported as lossy.
--strict refuses fitTo documents.
Constraints
Section titled “Constraints”Limit user interaction to specific zoom levels, pitch angles, or geographic areas.
| Property | Type | Description |
|---|---|---|
minZoom | number | Minimum zoom level (0-24) |
maxZoom | number | Maximum zoom level (0-24) |
minPitch | number | Minimum pitch angle (0-85) |
maxPitch | number | Maximum pitch angle (0-85) |
maxBounds | array | Maximum geographic bounds [west, south, east, north] |
Example
Section titled “Example”config: center: [-74.006, 40.7128] zoom: 12 minZoom: 8 maxZoom: 18 maxBounds: [-74.3, 40.5, -73.7, 40.9] mapStyle: "https://demotiles.maplibre.org/style.json"Interaction Settings
Section titled “Interaction Settings”Control user interaction capabilities.
| Property | Type | Default | Description |
|---|---|---|---|
interactive | boolean | true | Enable all map interactions |
scrollZoom | boolean | true | Enable scroll to zoom |
boxZoom | boolean | true | Enable shift+drag box zoom |
dragRotate | boolean | true | Enable right-drag to rotate |
dragPan | boolean | true | Enable drag to pan |
keyboard | boolean | true | Enable keyboard shortcuts |
doubleClickZoom | boolean | true | Enable double-click zoom |
touchZoomRotate | boolean | true | Enable touch zoom/rotate |
touchPitch | boolean | true | Enable two-finger touch pitch |
Non-Interactive Map
Section titled “Non-Interactive Map”Create a static map for presentation:
config: center: [-74.006, 40.7128] zoom: 12 interactive: false mapStyle: "https://demotiles.maplibre.org/style.json"Custom Interaction
Section titled “Custom Interaction”Disable specific interactions:
config: center: [-74.006, 40.7128] zoom: 12 dragRotate: false # Disable rotation touchPitch: false # Disable pitch on mobile doubleClickZoom: false # Disable double-click zoom mapStyle: "https://demotiles.maplibre.org/style.json"Display Options
Section titled “Display Options”Control map display and behavior.
| Property | Type | Default | Description |
|---|---|---|---|
hash | boolean | false | Sync map state with URL hash |
attributionControl | boolean | true | Show attribution control |
logoPosition | string | "bottom-left" | MapLibre logo position |
fadeDuration | number | 300 | Fade duration in milliseconds |
crossSourceCollisions | boolean | true | Check for cross-source symbol collisions |
Example
Section titled “Example”config: center: [-74.006, 40.7128] zoom: 12 hash: true # Enable URL hash syncing attributionControl: false # Hide attribution logoPosition: bottom-right # Move logo fadeDuration: 500 # Slower fade mapStyle: "https://demotiles.maplibre.org/style.json"Rendering Options
Section titled “Rendering Options”Advanced rendering configuration.
| Property | Type | Default | Description |
|---|---|---|---|
antialias | boolean | false | Enable antialiasing |
refreshExpiredTiles | boolean | true | Refresh expired tiles |
renderWorldCopies | boolean | true | Render multiple world copies |
maxTileCacheSize | number | - | Maximum tiles to cache |
preserveDrawingBuffer | boolean | false | Preserve drawing buffer for export |
failIfMajorPerformanceCaveat | boolean | false | Fail if WebGL performance is poor |
Example
Section titled “Example”config: center: [-74.006, 40.7128] zoom: 12 antialias: true # Smoother rendering preserveDrawingBuffer: true # Enable map.getCanvas().toDataURL() maxTileCacheSize: 100 # Cache more tiles mapStyle: "https://demotiles.maplibre.org/style.json"Localization
Section titled “Localization”Customize UI text for different languages.
config: center: [-74.006, 40.7128] zoom: 12 locale: "AttributionControl.ToggleAttribution": "Show attribution" "FullscreenControl.Enter": "Enter fullscreen" "FullscreenControl.Exit": "Exit fullscreen" "GeolocateControl.FindMyLocation": "Find my location" "NavigationControl.ResetBearing": "Reset bearing" "NavigationControl.ZoomIn": "Zoom in" "NavigationControl.ZoomOut": "Zoom out" mapStyle: "https://demotiles.maplibre.org/style.json"Map Controls
Section titled “Map Controls”Configure which controls are displayed and where.
Controls Structure
Section titled “Controls Structure”controls: navigation: true | { enabled: true, position: "top-right" } geolocate: true | { enabled: true, position: "top-right" } scale: true | { enabled: true, position: "bottom-left" } fullscreen: true | { enabled: true, position: "top-right" } attribution: true | { enabled: true, position: "bottom-right" }Available Controls
Section titled “Available Controls”| Control | Default Position | Description |
|---|---|---|
navigation | top-right | Zoom and rotation controls |
geolocate | top-right | User geolocation button |
scale | bottom-left | Distance scale indicator |
fullscreen | top-right | Fullscreen toggle |
attribution | bottom-right | Attribution text |
globe | top-right | Globe/mercator projection toggle (maplibre-gl ≥ 5.0.0; older runtimes warn and omit it) |
terrain | top-right | 3D terrain on/off toggle for the document’s terrain: (warns and is omitted when the document declares none) |
Control Positions
Section titled “Control Positions”top-lefttop-rightbottom-leftbottom-right
Examples
Section titled “Examples”Enable All Controls
Section titled “Enable All Controls”controls: navigation: true geolocate: true scale: true fullscreen: trueCustom Positions
Section titled “Custom Positions”controls: navigation: enabled: true position: top-left scale: enabled: true position: bottom-right fullscreen: enabled: true position: top-rightMinimal Controls
Section titled “Minimal Controls”controls: navigation: true scale: trueLegend
Section titled “Legend”Display a map legend showing layer information.
Legend Structure
Section titled “Legend Structure”legend: title: "Map Legend" position: top-left collapsed: false items: - color: "#ff0000" label: "Category 1" shape: circle - color: "#00ff00" label: "Category 2" shape: squareLegend Properties
Section titled “Legend Properties”| Property | Type | Default | Description |
|---|---|---|---|
position | string | "top-left" | Legend position |
title | string | - | Legend title |
collapsed | boolean | false | Start collapsed |
items | array | - | Custom legend items |
Legend Item Properties
Section titled “Legend Item Properties”| Property | Type | Required | Default | Description |
|---|---|---|---|---|
color | string | Yes | - | Item color (CSS color) |
label | string | Yes | - | Item label text |
shape | string | No | "square" | Symbol shape: circle, square, line, icon |
icon | string | No | - | Icon name or URL (when shape: icon) |
Examples
Section titled “Examples”Automatic Legend
Section titled “Automatic Legend”If items is not provided, legend items are extracted from layers with legend configuration:
legend: title: "Earthquake Magnitude" position: top-leftCustom Legend Items
Section titled “Custom Legend Items”legend: title: "Features" position: top-right collapsed: false items: - color: "#e74c3c" label: "High Priority" shape: circle - color: "#f39c12" label: "Medium Priority" shape: circle - color: "#2ecc71" label: "Low Priority" shape: circleLine Legend
Section titled “Line Legend”legend: title: "Routes" position: bottom-left items: - color: "#3b82f6" label: "Highway" shape: line - color: "#10b981" label: "Local Road" shape: lineIcon Legend
Section titled “Icon Legend”legend: title: "Points of Interest" position: top-left items: - color: "#000000" label: "Restaurant" shape: icon icon: "restaurant" - color: "#000000" label: "Hotel" shape: icon icon: "hotel"Complete Examples
Section titled “Complete Examples”Basic Interactive Map
Section titled “Basic Interactive Map”- type: map id: basic-map config: center: [-74.006, 40.7128] zoom: 12 mapStyle: "https://demotiles.maplibre.org/style.json" layers: - id: points type: circle source: type: geojson url: "https://example.com/points.geojson" paint: circle-radius: 8 circle-color: "#3b82f6" controls: navigation: true scale: true3D Map with Custom Controls
Section titled “3D Map with Custom Controls”- type: map id: 3d-map config: center: [-122.4194, 37.7749] zoom: 15 pitch: 60 bearing: -45 mapStyle: "https://demotiles.maplibre.org/style.json" layers: - id: buildings type: fill-extrusion source: type: vector url: "https://demotiles.maplibre.org/tiles/tiles.json" source-layer: building paint: fill-extrusion-color: "#aaa" fill-extrusion-height: ["get", "height"] fill-extrusion-opacity: 0.8 controls: navigation: enabled: true position: top-left fullscreen: enabled: true position: top-rightConstrained Map with Legend
Section titled “Constrained Map with Legend”- type: map id: constrained-map config: center: [-74.006, 40.7128] zoom: 12 minZoom: 8 maxZoom: 16 maxBounds: [-74.3, 40.5, -73.7, 40.9] mapStyle: "https://demotiles.maplibre.org/style.json" layers: - id: zones type: fill source: type: geojson url: "https://example.com/zones.geojson" paint: fill-color: - match - ["get", "type"] - "residential" - "#fef3c7" - "commercial" - "#dbeafe" - "industrial" - "#f3e8ff" - "#e5e7eb" fill-opacity: 0.6 controls: navigation: true scale: true legend: title: "Zone Types" position: top-right items: - color: "#fef3c7" label: "Residential" shape: square - color: "#dbeafe" label: "Commercial" shape: square - color: "#f3e8ff" label: "Industrial" shape: squareNon-Interactive Presentation Map
Section titled “Non-Interactive Presentation Map”- type: map id: static-map style: "height: 400px; width: 600px;" config: center: [0, 20] zoom: 2 interactive: false attributionControl: false mapStyle: "https://demotiles.maplibre.org/style.json" layers: - id: earthquakes type: circle source: type: geojson url: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_week.geojson" paint: circle-radius: 6 circle-color: "#e74c3c" circle-opacity: 0.8Full-Page Immersive Map
Section titled “Full-Page Immersive Map”- type: map-fullpage id: immersive-map config: center: [-122.4194, 37.7749] zoom: 13 pitch: 45 bearing: 0 hash: true mapStyle: "https://demotiles.maplibre.org/style.json" layers: - id: buildings type: fill-extrusion source: type: vector url: "https://demotiles.maplibre.org/tiles/tiles.json" source-layer: building paint: fill-extrusion-color: "#aaa" fill-extrusion-height: ["get", "height"] controls: navigation: true geolocate: true fullscreen: true scale: trueUsing Global Default Style
Section titled “Using Global Default Style”Define a default map style once in global config:
config: defaultMapStyle: "https://demotiles.maplibre.org/style.json"
pages: - path: "/" title: "Home" blocks: - type: map id: map1 config: center: [-74, 40] zoom: 12 # mapStyle inherited from global config
- type: map id: map2 config: center: [139, 35] zoom: 12 # mapStyle inherited from global configTypeScript Types
Section titled “TypeScript Types”import { type MapConfig, type ControlsConfig, type LegendConfig, type MapBlock} from '@maplibre-yaml/core/schemas';
const mapConfig: MapConfig = { center: [-74.006, 40.7128], zoom: 12, mapStyle: "https://demotiles.maplibre.org/style.json", pitch: 45, bearing: 0};
const controls: ControlsConfig = { navigation: true, scale: { enabled: true, position: "bottom-right" }};
const legend: LegendConfig = { title: "Map Legend", position: "top-left", collapsed: false, items: [ { color: "#ff0000", label: "High", shape: "circle" } ]};