import { PropertyValueSpecification, StyleSpecification } from '@maplibre/maplibre-gl-style-spec'; /** Represents the structure of a vector layer in a TileJSON specification. */ interface VectorLayer { id: string; fields: Record; description?: string; minzoom?: number; maxzoom?: number; } /** Basic structure for TileJSON specification, applicable to both raster and vector types. */ interface TileJSONSpecificationRaster { tilejson?: '3.0.0'; tiles: string[]; attribution?: string; bounds?: [number, number, number, number]; center?: [number, number] | [number, number, number]; data?: string[]; description?: string; fillzoom?: number; grids?: string[]; legend?: string; maxzoom?: number; minzoom?: number; name?: string; scheme?: 'tms' | 'xyz'; template?: string; version?: string; tile_schema?: string; tile_size?: number; encoding?: 'terrarium' | 'mapbox' | 'custom'; } /** Structure for TileJSON specification of vector type, specifying vector-specific properties. */ interface TileJSONSpecificationVector extends TileJSONSpecificationRaster { vector_layers: VectorLayer[]; } /** Represents a TileJSON specification, which can be either raster or vector. */ type TileJSONSpecification = TileJSONSpecificationRaster | TileJSONSpecificationVector; /** * A theme name. Each of the five palettes is a light theme and has a dark theme of its own, named * with a `-dark` suffix. * * The names after those are lookalikes: themes whose colour scheme resembles another project's map. * A lookalike has a dark theme only where that map has one — `positrino` does, `protozoa` does not, `fnord` is itself * dark and has no light partner — so ask `isDarkPalette`, not the suffix, whether a theme is dark. */ type Palette = 'colorful' | 'colorful-dark' | 'natural' | 'natural-dark' | 'muted' | 'muted-dark' | 'gray' | 'gray-dark' | 'toner' | 'toner-dark' | 'positrino' | 'positrino-dark' | 'fnord' | 'protocol' | 'protocol-dark' | 'protostar' | 'protostar-dark' | 'protozoa' | 'classic' | 'googol' | 'freedom' | 'macbob' | 'mactaylor' | 'bingo'; type ThemeOptions = Palette; type ResolvedTheme = Palette; /** * The colour slots a palette fills: their names, their order, and the option type that mirrors them. * * A leaf on purpose — it imports nothing — so anything may read the key list without dragging the * resolver, and its `checkKeys` dependency, along with it. `options/parts/v5-hints.ts` is why: it needs * the list to map v5 colour names onto v6 ones, and while the list lived beside `resolveColors` that * single import closed a cycle (`keys → v5-hints → colors → keys`), survived only because `v5-hints` * built its tables lazily. * * It sits in `themes/` rather than `options/` because the order is load-bearing here: `tables.ts` stores * each derived palette as bare values in exactly this sequence, and `getPaletteColors` zips them back * together against this array. `options` re-exports both names from `parts/colors.ts`, so callers still * import them from the options surface; the dependency runs options → themes, never back. */ type ColorsOptions = { background?: string; land?: string; water?: string; glacier?: string; natureWood?: string; natureGrass?: string; naturePark?: string; natureAgriculture?: string; natureSand?: string; natureRock?: string; natureWetland?: string; natureLeisure?: string; areaResidential?: string; areaCommercial?: string; areaIndustrial?: string; areaWaste?: string; areaBurial?: string; siteConstruction?: string; siteEducation?: string; siteHospital?: string; siteDanger?: string; sitePrison?: string; siteParking?: string; siteSports?: string; building?: string; buildingBg?: string; roadStreet?: string; roadStreetBg?: string; roadMotorway?: string; roadMotorwayBg?: string; roadTrunk?: string; roadTrunkBg?: string; roadSecondary?: string; roadSecondaryBg?: string; transitRail?: string; transitSubway?: string; transitCycle?: string; transitFoot?: string; boundary?: string; boundaryDisputed?: string; label?: string; labelHalo?: string; labelShield?: string; labelSymbol?: string; labelPoi?: string; labelHousenumber?: string; /** Lake, sea and river names — a darkened water tone, so they read as water, not as places. */ labelWater?: string; }; /** * The colour table of a palette. * * `colorful` is the hand-written reference (`colorful.ts`); the other nine are derived from it and * stored in `tables.ts` as one string each — the 47 colours of `colorOptionsKeys`, in that order, * comma-separated and without their `#`. Writing them as objects cost 5.4 KB in the browser bundle for * nothing but the same 47 identifiers repeated nine times; this form is a tenth of that. * * Decoding is cheap and the result is not cached: every caller here — `resolveColors`, the palette * search in `deriveOptions` — copies or reshapes the table anyway, so a shared frozen object would buy * nothing and risk being mutated by one of them. * * Every palette therefore returns a **fresh** object, `colorful` included. It used to hand back the * `COLORFUL` constant itself, which is public API through `osm.colors` — so a caller editing the * returned palette (exactly what a style editor does) silently repainted every later `osm()` in the * process, and every derived table with it, since all nine are generated from this one. */ declare function getPaletteColors(palette: Palette): ResolvedColors; type ResolvedColors = Required; type HillshadeOptions = boolean | { exaggeration?: number; shadowColor?: string; highlightColor?: string; /** Only with `light: false`: lit, the hillshade uses a method that draws no accent colour. */ accentColor?: string; }; /** Whether the shading is fixed to the screen is `light.fixedToScreen`, shared with 3D buildings. */ type ResolvedHillshade = false | { exaggeration: number; shadowColor: string; highlightColor: string; accentColor: string; }; /** * 3D terrain: the map's surface raised by the elevation source. MapLibre does not light that surface, * so it shows no relief from above and `light` does not shade it; add `features.hillshade` for that. */ type TerrainOptions = boolean | { exaggeration?: number; }; type ResolvedTerrain = false | { exaggeration: number; }; /** Size and spacing of icons: POI and transit-stop icons, motorway shields and road markings. */ type IconOptions = { /** Multiplies each icon's own size. Default `1`. */ scale?: number; /** * How far apart icons keep. Along a line (oneway arrows) it multiplies the repeat distance; at a point * each step above 1 adds 14 px of collision padding. Default `1`. */ spacing?: number; }; type ResolvedIcon = Required; /** * One thing wrong with an options object, as data rather than as English. * * `path` is where it sits, written the way the caller wrote it and relative to the options object * itself (`colors.wood`, `text.places.font`) — so a UI can mark the offending line. `message` says what * is wrong with that one node, without the path prefixed, since a caller that wants a sentence can * build one and a caller that wants to mark a control cannot unbuild one. `suggestion` is the v6 key to * use instead, where `v5-hints.ts` knows of one. */ type OptionIssue = { path: string; message: string; suggestion?: string; }; /** * How a line is drawn, for the groups that are one: borders and paths. Takes the place of the plain * `boolean | number` those groups also accept — `state: 0.5` and `state: { opacity: 0.5 }` say the * same thing. */ type LineStyle = { /** As the plain value: `false` hides the line, a number in (0, 1] is its opacity. Default `true`. */ opacity?: boolean | number; /** * `true` draws the line in the style's own dash pattern for it, `false` draws it solid, and a list * is a dash pattern of your own — the lengths of dashes and gaps in turn, in multiples of the line * width, as MapLibre's `line-dasharray` takes them. The default depends on the line: a country * border is solid, the other five are dashed. */ dashed?: boolean | number[]; /** * Multiplies the line's width, at every zoom: `0.5` draws it half as wide, `2` twice. The dash * follows, since a pattern is in multiples of the width, and so does the casing under a border. * Default `1`. */ width?: number; }; type ResolvedLineStyle = Required; type LayerGroupOptions = { land?: boolean | number | { forest?: boolean | number; vegetation?: boolean | number; rock?: boolean | number; wetland?: boolean | number; sand?: boolean | number; glacier?: boolean | number; agriculture?: boolean | number; urban?: boolean | number; }; water?: boolean | number | { ocean?: boolean | number; rivers?: boolean | number; lakes?: boolean | number; piers?: boolean | number; }; roads?: boolean | number | { motorways?: boolean | number; highways?: boolean | number; streets?: boolean | number | { residential?: boolean | number; service?: boolean | number; pedestrian?: boolean | number; track?: boolean | number; bus?: boolean | number; }; paths?: boolean | number | LineStyle; footway?: boolean | number | LineStyle; steps?: boolean | number | LineStyle; }; transit?: boolean | number | { rail?: boolean | number; aerialways?: boolean | number; ferries?: boolean | number; stops?: boolean | number; }; buildings?: boolean | number; sites?: boolean | number; airport?: boolean | number; pois?: boolean | number; boundaries?: boolean | number | { country?: boolean | number | LineStyle; state?: boolean | number | LineStyle; disputed?: boolean | number | LineStyle; }; markings?: boolean | number; labels?: boolean | number | { boundaries?: boolean | number | { countries?: boolean | number; states?: boolean | number; }; places?: boolean | number | { capitals?: boolean | number; statecapitals?: boolean | number; cities?: boolean | number; towns?: boolean | number; villages?: boolean | number; hamlets?: boolean | number; districts?: boolean | number; }; streets?: boolean | number | { names?: boolean | number; refs?: boolean | number; exits?: boolean | number; }; water?: boolean | number | { lakes?: boolean | number; rivers?: boolean | number; }; addresses?: boolean | number; }; icons?: boolean | number; }; type ResolvedLayerGroups = { land: { forest: boolean | number; vegetation: boolean | number; rock: boolean | number; wetland: boolean | number; sand: boolean | number; glacier: boolean | number; agriculture: boolean | number; urban: boolean | number; }; water: { ocean: boolean | number; rivers: boolean | number; lakes: boolean | number; piers: boolean | number; }; roads: { motorways: boolean | number; highways: boolean | number; streets: { residential: boolean | number; service: boolean | number; pedestrian: boolean | number; track: boolean | number; bus: boolean | number; }; paths: ResolvedLineStyle; footway: ResolvedLineStyle; steps: ResolvedLineStyle; }; transit: { rail: boolean | number; aerialways: boolean | number; ferries: boolean | number; stops: boolean | number; }; buildings: boolean | number; sites: boolean | number; airport: boolean | number; pois: boolean | number; boundaries: { country: ResolvedLineStyle; state: ResolvedLineStyle; disputed: ResolvedLineStyle; }; markings: boolean | number; labels: { boundaries: { countries: boolean | number; states: boolean | number; }; places: { capitals: boolean | number; statecapitals: boolean | number; cities: boolean | number; towns: boolean | number; villages: boolean | number; hamlets: boolean | number; districts: boolean | number; }; streets: { names: boolean | number; refs: boolean | number; exits: boolean | number; }; water: { lakes: boolean | number; rivers: boolean | number; }; addresses: boolean | number; }; icons: boolean | number; }; /** * The one light of the map: it shades the terrain (`features.hillshade`), lights 3D buildings * (`features.buildings: 'extruded'`) and, on a globe, decides which side of the atmosphere glows * (`sky.atmosphereBlend`). Set once, all three are lit from the same side. `features.terrain` alone * is not shaded: MapLibre never lights the 3D terrain surface, so only the hillshade shows the light. * * On by default: unset or `true` uses the defaults below, an object overrides individual values. * `false` writes no `light`, and hillshade and buildings fall back to MapLibre's own lights, which * disagree. The style's root `light` is written only where 3D buildings or a visible atmosphere read * it; the hillshade carries the light in its own paint properties. */ type LightOptions = boolean | { /** * Where the light comes from, in degrees clockwise: 0 = north, 90 = east (with `fixedToScreen`: * 0 = top, 90 = right). Default `315`, the north-west — the top left while the map points north, * the cartographic convention, under which relief never reads inverted. Any angle is accepted and * wrapped: `-45` is `315`. */ direction?: number; /** * The light's height above the horizon, in degrees, 0–90. Default `45`. A low light deepens the * hillshade's shadows, a high one flattens the relief. With `features.hillshade`, `light` switches * the hillshade to MapLibre's `combined` method, the one that takes the altitude. */ altitude?: number; /** * Whether the light is fixed to the screen. Default `false`: the light turns with the map, so * rotating the map leaves the shading as it is. `true` keeps `direction` on screen however the map * is rotated, but MapLibre then relights the hillshade on every frame of a rotation, which is slow. * The globe's atmosphere (`sky.atmosphereBlend` > 0) is always fixed to the screen; it glows from * `direction` on screen, which matches the terrain while the map points north. */ fixedToScreen?: boolean; /** The light's colour, on 3D buildings only. Default `#ffffff`. */ color?: string; /** The light's intensity on 3D buildings, 0–1. Default `0.5`. */ intensity?: number; }; /** `false` when there is no light, otherwise every value — so it can be fed back in as options. */ type ResolvedLight = false | Required>; /** * Map projection. * * Web Mercator grossly exaggerates area away from the equator — Greenland reads as the size of * Africa — and that distortion is worst at exactly the low zooms where the whole world is visible. * `globe` is correct there, and MapLibre transitions back to Mercator as you zoom in, so the cost is * confined to the zooms where Mercator is wrong (issue #129). * * Requires MapLibre GL JS 5.0+. Renderers without projection support — MapLibre GL JS 4.x, and the * native/server renderers, which are Mercator-only — ignore the property and draw Mercator, which is * the pre-v6 behaviour. Note the style spec accepts *any* string here, so an unsupported value * validates cleanly and then silently does nothing. */ type ProjectionOptions = 'globe' | 'mercator' | 'vertical-perspective'; type ResolvedProjection = ProjectionOptions; type RecolorOptions = { invertBrightness?: boolean; rotateHue?: number; saturate?: number; tint?: { color?: string; amount?: number; }; gamma?: number; contrast?: number; brightness?: number; blend?: { color?: string; amount?: number; }; }; type ResolvedRecolor = { invertBrightness: boolean; rotateHue: number; saturate: number; tint: { color: string; amount: number; }; gamma: number; contrast: number; brightness: number; blend: { color: string; amount: number; }; }; /** * Atmosphere above the horizon. `true` (the default) uses the defaults below, `false` omits the * style's `sky` block entirely, an object overrides individual values — the same * `boolean | object` shape as `features.terrain` and `features.hillshade`. * * MapLibre only renders the sky when the horizon is in frame — in globe projection, or in Mercator * once the pitch passes ~65° (measured; nothing shows at MapLibre's default `maxPitch` of 60). A * flat 2D map therefore carries paint properties it never draws; `sky: false` drops them. */ type SkyOptions = { /** Default `#ffffff`. */ fogColor?: string; /** Default `#ffffff`. */ horizonColor?: string; /** Default: the palette's `colors.water` — see `resolveSky`. */ skyColor?: string; /** * Default `0`: no haze, so the globe's edge stays crisp. The haze is lit from `light`: on a globe it * glows on the side of the screen the light comes from — unless `light.fixedToScreen`, only while the * map points north. */ atmosphereBlend?: PropertyValueSpecification; /** Default `0.5`. */ fogGroundBlend?: PropertyValueSpecification; /** Default `0.8`. */ horizonFogBlend?: PropertyValueSpecification; /** Default `0.8`. */ skyHorizonBlend?: PropertyValueSpecification; }; /** * `false` when the sky is off. Otherwise every sky value but `skyColor`, which is set only when the * caller set it. */ type ResolvedSky = false | (Required> & Pick); type SpriteEntries = string | { id: string; url: string; }[]; /** * A glyph name, as the glyph server publishes it: `'noto_sans_regular'`, `'fira_sans_semibold_italic'`, * … — or any name a custom server uses. Not checked: `osm()` does no I/O, so it cannot know which * faces a server has. `fetchFontFaces()` asks the server. */ type FontName = string; /** Capitalization of a label's text. */ type TextTransform = 'none' | 'uppercase' | 'lowercase'; /** * How labels that follow a line — street, river and motorway names — sit in a tilted map: lying on * the ground (`map`, MapLibre's own behaviour for line labels) or standing up facing the viewer * (`viewport`). Point labels, such as places and POIs, face the viewer either way. */ type PitchAlignment = 'map' | 'viewport'; /** * How a node of the text tree sets its labels. Every property is optional: a topic takes each one from * the nearest node above it that sets it, else from the style's default for that topic. */ type LabelStyle = { /** A glyph name, e.g. `'noto_sans_regular'`. */ font?: FontName; /** Multiplies the layer's own text size. Default `1`. */ scale?: number; /** * How far apart labels keep. Along a line it multiplies the repeat distance; at a point each step above * 1 adds 14 px of collision padding. Default `1`. */ spacing?: number; /** Wrap width in ems. Default `10`. */ maxWidth?: number; /** Line height in ems. Default `1.2`. */ lineHeight?: number; /** Letter spacing in ems. Default `0`. */ letterSpacing?: number; /** Capitalization. Default `'none'`, `'uppercase'` for boundaries, hamlets and districts. */ transform?: TextTransform; /** Halo width in px. Default `2`; `1` for motorway exits, `0.1` for motorway refs, `0.5` for POIs, `0` for addresses. */ haloWidth?: number; /** Halo blur in px. Default `1`; `0.5` for POIs, `0` for addresses. */ haloBlur?: number; }; type ResolvedLabelStyle = Required; /** * `text`: the label language, and a tree of label topics whose every node — the root, a group, a topic — * takes the same `LabelStyle` properties. `language`, `languageStrict` and `pitchAlignment` apply to * every label and are set on the root only. */ type TextOptions = LabelStyle & { /** `'local'` (each feature's own name), `'user'` (the browser language), or a code such as `'de'`. Default `'local'`. */ language?: string; /** Omit labels that have no name in `language`, instead of falling back to the local name. Default `false`. */ languageStrict?: boolean; /** Line labels in a tilted map. Default `'map'`. */ pitchAlignment?: PitchAlignment; boundaries?: LabelStyle & { countries?: LabelStyle; states?: LabelStyle; }; places?: LabelStyle & { capitals?: LabelStyle; statecapitals?: LabelStyle; cities?: LabelStyle; towns?: LabelStyle; villages?: LabelStyle; hamlets?: LabelStyle; districts?: LabelStyle; }; streets?: LabelStyle & { names?: LabelStyle; refs?: LabelStyle; exits?: LabelStyle; }; water?: LabelStyle & { lakes?: LabelStyle; rivers?: LabelStyle; }; /** Names drawn with POI icons (`general`) and transit stop icons (`transit`). */ pois?: LabelStyle & { general?: LabelStyle; transit?: LabelStyle; }; addresses?: LabelStyle; }; /** Every topic with every `LabelStyle` property. Valid as input, where it builds the same style. */ type ResolvedText = { language: string; languageStrict: boolean; pitchAlignment: PitchAlignment; boundaries: { countries: ResolvedLabelStyle; states: ResolvedLabelStyle; }; places: { capitals: ResolvedLabelStyle; statecapitals: ResolvedLabelStyle; cities: ResolvedLabelStyle; towns: ResolvedLabelStyle; villages: ResolvedLabelStyle; hamlets: ResolvedLabelStyle; districts: ResolvedLabelStyle; }; streets: { names: ResolvedLabelStyle; refs: ResolvedLabelStyle; exits: ResolvedLabelStyle; }; water: { lakes: ResolvedLabelStyle; rivers: ResolvedLabelStyle; }; pois: { general: ResolvedLabelStyle; transit: ResolvedLabelStyle; }; addresses: ResolvedLabelStyle; }; /** * A tile source: a URL string, or a TileJSON document the caller already holds. * * A string is a TileJSON URL unless it contains a `{z}` placeholder, in which case it * is a raw tile template. Pass an object (see `fetchTileJSON`) when the style must be * self-contained at build time, or when the source needs fields MapLibre cannot infer. */ type TileSource = string | TileJSONSpecification; type OsmUrlsOptions = { base?: string; osm?: TileSource; elevation?: TileSource; glyphsPattern?: string; sprite?: SpriteEntries; }; /** * What `validateOptions` gives back: the resolved options, or everything wrong with them. * * A discriminated union rather than `{ issues, options? }`, so a caller that checks `ok` gets the * resolved options without a second narrowing step, and one that forgets cannot read an `options` that * was never produced. */ type ValidationResult = { ok: true; issues: readonly []; options: T; } | { ok: false; issues: readonly OptionIssue[]; }; type OsmOverlayOptions = { theme?: ThemeOptions; layers?: boolean | number | LayerGroupOptions; text?: TextOptions; icon?: IconOptions; colors?: ColorsOptions; recolor?: RecolorOptions; }; type ResolvedOsmOverlay = { theme: ResolvedTheme; layers: ResolvedLayerGroups; text: ResolvedText; icon: ResolvedIcon; colors: ResolvedColors; recolor: ResolvedRecolor; }; /** * A tree mirroring `LayerGroupOptions`, with the layer IDs each group controls at the leaves. * Branch nodes (e.g. `roads.streets`) hold their sub-groups rather than IDs. */ type LayerGroupMap = { [key: string]: string[] | LayerGroupMap; }; /** The subset of the URL options a guessed style can carry over: everything but the tile source. */ type SchemaUrls = Pick | undefined; /** * The language codes a tileset carries, from its TileJSON. * * Both naming conventions are scanned, as a schema-neutral static must: * Shortbread and OpenMapTiles spell a translation `name_de`, Protomaps spells it `name:de`, and * OpenMapTiles carries both — `name_de`/`name_en`/`name_int` alongside the full `name:xx` set. A caller * asking which languages a tileset offers wants the union, not whichever spelling the style happens to * prefer. */ declare function getLanguages(tileJSON: TileJSONSpecification): string[]; /** * Where a snippet is meant to run. * * `'npm'` is an ES module in a project with a bundler. `'browser'` is a plain HTML page that loads the * CDN bundle from a `