import { PropertyValueSpecification, StyleSpecification } from '@maplibre/maplibre-gl-style-spec'; /** * 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; /** * 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; }; 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; }; /** * 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 SatelliteFeaturesOptions = { terrain?: TerrainOptions; hillshade?: HillshadeOptions; }; type OsmFeaturesOptions = { terrain?: TerrainOptions; hillshade?: HillshadeOptions; landcover?: boolean; buildings?: 'flat' | 'extruded'; }; /** 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; }; /** * 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 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; }; /** * 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; }; /** * 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 RecolorOptions = { invertBrightness?: boolean; rotateHue?: number; saturate?: number; tint?: { color?: string; amount?: number; }; gamma?: number; contrast?: number; brightness?: number; blend?: { color?: string; amount?: number; }; }; type SatelliteRasterOptions = { opacity?: number; hueRotate?: number; brightnessMin?: number; brightnessMax?: number; saturation?: number; contrast?: 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; }; 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; }; /** * `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; }; /** 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 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; /** A `fetch`-compatible function, used to download TileJSON documents. */ type FetchLike = typeof fetch; type OsmUrlsOptions = { base?: string; osm?: TileSource; elevation?: TileSource; glyphsPattern?: string; sprite?: SpriteEntries; }; type SatelliteUrlsOptions = OsmUrlsOptions & { satellite?: TileSource; }; type OsmOverlayOptions = { theme?: ThemeOptions; layers?: boolean | number | LayerGroupOptions; text?: TextOptions; icon?: IconOptions; colors?: ColorsOptions; recolor?: RecolorOptions; }; type OsmOptions = OsmOverlayOptions & { urls?: OsmUrlsOptions; features?: OsmFeaturesOptions; light?: LightOptions; sky?: boolean | SkyOptions; projection?: ProjectionOptions; }; /** * The overlay's options: everything `osm()` takes for its cartography, plus what only makes sense over * imagery. */ type SatelliteOverlayOptions = OsmOverlayOptions & { /** * Dim borders and motorways with `line-layer-opacity` instead of `line-opacity`. Default `false`. * * `line-opacity` applies to every feature on its own, so where a line crosses itself — a border * winding along a river at low zoom, a motorway's two carriageways — the overlap is blended twice * and shows as a brighter spot. `line-layer-opacity` composites the finished layer once, so the * line reads evenly. It costs an extra offscreen pass per layer, so it is applied to the three * layers that overlap themselves most: `boundary-country`, `boundary-state` and `street-motorway`. * * **Only for MapLibre GL JS 6.0 or newer.** MapLibre Native (Android, iOS) does not implement the * property and drops every layer that carries it — the map then has no borders or motorways * (maplibre-native#4298). Leave it off for any style a native app may load. */ layerOpacity?: boolean; }; type SatelliteOptions = { urls?: SatelliteUrlsOptions; /** * The OSM vector overlay over the imagery. `true` (the default) uses the overlay's own * defaults, `false` disables it, an object configures it — the same shape as `features.terrain` * and `features.hillshade`. */ osmOverlay?: boolean | SatelliteOverlayOptions; raster?: SatelliteRasterOptions; features?: SatelliteFeaturesOptions; light?: LightOptions; sky?: boolean | SkyOptions; projection?: ProjectionOptions; }; /** * Just enough of each schema to recognise its tiles: the source-layer ids, and, for the ids two schemas * share, a few fields only one of them carries. * * ── Why a copy, and not the vendored records ────────────────────────────────── * * `guessSchema` lives in the root entry and has to know all three schemas. Importing `OMT_SCHEMA` or * `PROTOMAPS_SCHEMA` would put `src/omt/` and `src/protomaps/` into the root bundle, which the bundle * isolation test forbids, and the records are mostly field lists detection has no use for. This table is * a few hundred bytes. `schema-signatures.test.ts` checks it against the three records, so it cannot * drift from them unnoticed. * * ── Why fields, for shared ids ──────────────────────────────────────────────── * * Several ids are used by more than one schema with different data behind them: `boundaries`, * `buildings` and `pois` (Shortbread and Protomaps), `landcover`, `landuse` and `water` (OpenMapTiles, * Protomaps and Mapbox), and `aeroway`, `building` and `waterway` (OpenMapTiles and Mapbox). By name * alone they are evidence for all of them. The fields listed here are structural ones — `class` against * `kind`, `admin_level` against `kind_detail` — rather than language fields, which vary with how a * tileset was built. A field need not rule out every other schema: OpenMapTiles and Mapbox both put a * `class` on `landuse`, which tells either from Protomaps and neither from the other. * * ── Mapbox ──────────────────────────────────────────────────────────────────── * * Mapbox Streets v8, with the two tilesets its styles load beside it (Terrain v2: `landcover`, * `hillshade`, `contour`; Bathymetry v2: `depth`) — a Mapbox style reads all three through one * composite source. There is no vendored record behind this one: Mapbox publishes its schema as a * documentation page, not as data, so the list is kept by hand. It is here to *recognise* such tiles, * which is all reading a Mapbox style needs; there is no builder for them (issue #137). */ /** The schemas `guessSchema` recognises. */ type SchemaName = 'shortbread' | 'openmaptiles' | 'protomaps' | 'mapbox'; /** How well a tileset's source-layers fit one schema. */ type SchemaScore = { schema: SchemaName; /** Tileset source-layers counted as this schema's. */ matched: string[]; /** This schema's source-layers the tileset does not carry — an extract, or a different schema. */ missing: string[]; /** Tileset source-layers not counted as this schema's. */ extra: string[]; /** `matched` as a share of all the tileset's source-layers, 0–1. */ score: number; }; /** * The result of {@link guessSchema}. * * - `vector` — `schema` is the recognised schema, or `undefined` when none qualifies or two tie. * `candidates` scores every schema, best first, so a caller can show why. * - `raster` — a TileJSON without `vector_layers`. Raster tiles have no schema. * - `unknown` — not a TileJSON at all. */ type SchemaGuess = { type: 'vector'; schema: SchemaName | undefined; candidates: SchemaScore[]; } | { type: 'raster'; } | { type: 'unknown'; }; /** * What a migration could not carry over, could only guess at, or had to choose between. * * A style editor needs more than a line of prose: it wants to mark *which setting* is a guess, offer * the alternatives it discarded, and block only on what is actually wrong. So a diagnostic is a record * with a stable `code` — the field tests, filters and translations key off — and the English `message` * is a fallback for a consumer that only prints strings. * * Two vocabularies meet here and are deliberately kept apart. `optionPath` names a setting of the * *output* (`colors.water`, `text.places.font`), which is where a UI puts the marker; `origin` names * where it came from in the *input* (a source, one of its layers, the probe that read it). A probe id * is a Shortbread layer id, which is why it does not sit loose beside the input style's own layer ids. */ /** How much a diagnostic should stop someone. `error` means no usable options came out. */ type Severity = 'error' | 'warning' | 'info'; /** Where a diagnostic came from, in the vocabulary of the *input* style. */ type DiagnosticOrigin = { /** The id of the source in the input style. */ sourceId?: string; /** The source-layer the reading came from. */ sourceLayer?: string; /** The probe that read it — a Shortbread layer id, not one of the input's. */ probe?: string; /** Layer ids of the input style. */ layers?: string[]; }; /** * Every diagnostic a migration can report, with the payload it carries. * * The payload is typed per code rather than left an open record, because for the codes that matter * most the payload *is* the diagnostic: `color.conflict` is worth reporting only if a consumer can * read the colours out of it and offer them as a choice, and an untyped field would force a cast at * exactly that point. Typing it later would be a breaking change, so the map exists from the start * even while it is short. * * Adding a code is not breaking for a consumer that switches on the ones it knows and prints * `message` for the rest. * * There is no `vote.tie` of its own, and that rests on an invariant worth stating: **every payload * whose value was chosen by a vote exposes its tally**, as a `count` or as an array whose length is * the count. A tie is then two equal counts, which a consumer can see without a second code saying so. * A vote-based payload added without a tally would break this quietly, and `vote.tie` would have to * come back. `icon.conflict` carries no count and needs none — ratios reduce to a mean, not a vote. */ type DiagnosticData = { /** The argument was not a MapLibre style document. */ 'input.notAStyle': { received: string; }; /** An option passed to `guessOptions` was not one it has. */ 'input.badOption': { cause: string; }; /** The style URL could not be fetched. */ 'input.fetchFailed': { url: string; status: number; }; /** Anything else that stopped the read. */ 'input.unreadable': { cause: string; }; /** No source of a known schema, and no imagery either. */ 'schema.none': { vectorSources: number; }; /** A vector source carries tiles of no schema this package knows; its layers were not read. */ 'source.schemaUnknown': { sourceId: string; }; /** No vector source could be read, but something else could — the vector half is missing. */ 'schema.partial': { vectorSources: number; }; /** A source's TileJSON could not be downloaded; its schema was read from the style instead. */ 'source.tilejsonUnavailable': { sourceId: string; url: string; cause: string; }; /** More than one vector source: all of them were read as one map. */ 'source.multiple': { sources: Record; }; /** Fonts the glyph server does not publish; the nearest face it has was used. */ 'font.unavailable': { requested: string[]; reason: 'not-published' | 'no-font-list'; }; /** A label language the VersaTiles tiles do not carry; local names are used. */ 'language.unavailable': { requested: string; }; /** A projection the options cannot express. */ 'projection.unsupported': { requested: string; }; /** The style's sprite is not carried over; the target's own icons are used. */ 'icons.replaced': { sprite: unknown; }; /** * Several layers drew a probe in different colours; the topmost was taken. `observed` groups the * layers by the colour they drew, which is the list a consumer offers as a choice. */ 'color.conflict': { key: string; chosen: string; /** How the winner was picked, as a rule rather than a sentence. */ rule: 'topmost'; observed: { color: string; layers: string[]; }[]; }; /** * The source draws features in different colours that the target has one setting for — not a * z-order contest like `color.conflict`, but a schema the target is coarser than. Shortbread's POI * layer is coarser than OpenMapTiles' by design, so for an OMT style this is systematic rather than * incidental, and `observed` names each feature the source told apart. */ 'color.collapsed': { key: string; chosen: string; observed: { feature: string; color: string; layers: string[]; }[]; }; /** A topic's labels were set in more than one font; the most used was taken. */ 'font.conflict': { topic: string; chosen: string; observed: { font: string; count: number; }[]; }; /** A topic's labels disagreed on a style property; the median, or most voted, was taken. */ 'labelStyle.conflict': { topic: string; property: string; chosen: number | string; observed: { value: number | string; count: number; }[]; }; /** Icons were sized inconsistently relative to the target's; one multiplier had to serve. */ 'icon.conflict': { option: 'scale' | 'spacing'; chosen: number; observed: { probe: string; ratio: number; }[]; }; /** * Labels were read in more than one language; the first place label's was taken. * * `chosen` is absent when that first label reads a plain `name` — local names, which sets no * `text.language` at all — or when the language it reads is one the tiles do not carry. It is * optional precisely so that case cannot be mistaken for a language: the field used to name the * first language *observed*, which was not always the one applied. */ 'language.conflict': { chosen?: string; observed: { language: string; probes: string[]; }[]; }; /** Two palettes fit almost equally well; the cheaper one was taken. */ 'theme.ambiguous': { chosen: string; cost: number; runnerUp: string; runnerUpCost: number; margin: number; }; /** A colour was observed and estimated, but not by enough to override the palette's own value. */ 'color.lowConfidence': { key: string; estimate: string; paletteColor: string; /** How far the estimate sat from the palette, and how far it had to sit to be taken. */ distance: number; threshold: number; /** 0..1 — how strongly the readings constrained this key. */ evidenceShare: number; residual: number; }; /** Colours nothing in the style spoke for, which kept the chosen palette's values. */ 'color.unobserved': { keys: string[]; count: number; total: number; }; /** A line's dash changes with zoom; `dashed` is what it is at `zoom`, and is all that was kept. */ 'line.dashByZoom': { zoom: number; dashed: number[]; }; /** Layers of the input that no probe read. Not necessarily lost — see the code's note. */ 'layer.unread': { count: number; }; }; type DiagnosticCode = keyof DiagnosticData; /** * One diagnostic. Discriminated on `code`, so `switch (d.code)` narrows `d.data` to that code's * payload with no cast — which is the point of typing the payloads at all. * * Written as a distributive conditional over the code union rather than as one object with a generic * `data`, which is what makes the narrowing work; the shape is spelled out inline so that no helper * type of its own leaks into the published API. */ type Diagnostic = C extends unknown ? { /** Stable and machine-readable. What to switch on, filter by, count and translate. */ readonly code: C; readonly severity: Severity; /** Plain English, so a consumer that only prints strings still works. */ readonly message: string; /** The setting this concerns, in the vocabulary of the *output* options. */ readonly optionPath?: string; readonly origin?: DiagnosticOrigin; /** Plain JSON — a report may cross a worker boundary or be stored. */ readonly data: DiagnosticData[C]; } : never; /** * Whether a diagnostic has this code, narrowing its payload where it does. * * `if (is(d, 'color.lowConfidence')) d.data.estimate` — without this a consumer holding a * `Diagnostic` has to compare `d.code` and then cast, which is the cast the typed payloads exist to * remove. */ declare function is(diagnostic: Diagnostic, code: C): diagnostic is Diagnostic; /** * Diagnostics in a fixed order: most severe first, then by code, then by the option they concern. * * Specified rather than merely deterministic — two implementations that both "sort somehow" produce * test diffs that disagree. Sorted once, at the end, so the order does not depend on which derivation * happened to run first. */ declare function sortDiagnostics(diagnostics: readonly Diagnostic[]): Diagnostic[]; /** The most severe level present, or `undefined` when there is nothing to report. */ declare function worst(diagnostics: readonly Diagnostic[]): Severity | undefined; /** Every diagnostic with this code, typed to that code's payload. */ declare function byCode(diagnostics: readonly Diagnostic[], code: C): Diagnostic[]; /** * Every diagnostic concerning an option, or anything under it: `byOption(d, 'colors')` finds * `colors.water`, and `byOption(d, 'text.places')` finds `text.places.font`. */ declare function byOption(diagnostics: readonly Diagnostic[], optionPath: string): Diagnostic[]; /** * Where each derived option came from — an annotation per setting, alongside the diagnostics. * * Two structures rather than one because two different things are being reported. A diagnostic is an * event: something happened that a person should read. Provenance is a property of every setting, * always present and mostly unremarkable, which a UI reads to mark a control rather than to write a * line. Folding one into the other would either flood the list — a palette is 47 colours, each with an * outcome — or lose the annotation on the settings nothing was said about. * * It answers a question the options object structurally cannot. `minimizeOptions` deletes every * derived value that equals the target's default, so "read from the style, and it happened to match" * and "never derived at all" both come out as an absent key. Provenance records `observed` for the * first and `default` for the second, which is exactly the difference between "this is what your style * says" and "we had nothing to go on". */ /** * How a value was arrived at. * * Deliberately only the four that have a site in the pipeline. A variant nothing can produce reads, in * review, as a case that is handled — the same trap as a warning whose condition can never hold. */ type Origin = /** Read from the input style, for this very setting. */ 'observed' /** Borrowed from a comparable setting the input did speak about — a sibling topic, or the style's * most-used font family — because nothing spoke for this one. */ | 'pooled' /** Taken from the palette that was chosen, rather than from anything the input said. */ | 'inherited' /** The target's own default stands; nothing in the input spoke for it, near or far. */ | 'default'; type Provenance = { readonly origin: Origin; /** * 0..1, and only where a number was genuinely computed — the colour solver's evidence share. Absent * where there is nothing to measure, rather than filled with a guess at how much of a guess a value * is, which would be worse than saying nothing. */ readonly confidence?: number; /** What fed the value: probe ids (Shortbread layer ids), or the topics it was pooled from. */ readonly from?: readonly string[]; }; /** Provenance per option path, in the vocabulary of the output options: `colors.water`, `theme`. */ type ProvenanceMap = Readonly>; /** * Which options carry provenance today. * * Absence means "this derivation does not report provenance yet", not "nothing is known" — so a * consumer needs to know where to expect it. Everything under these prefixes is annotated for every * setting the derivation considers, whether or not it ends up in the options. */ declare const PROVENANCE_COVERS: readonly string[]; /** Whether an option path is one provenance is recorded for, so a UI can tell silence from ignorance. */ declare function isCovered(optionPath: string): boolean; /** * What `deriveOptions` hands back: the options it found, and the report on how far to trust them. * * Apart from the pipeline in `derive.ts` because every step of it writes into the report, so each * `derive-*.ts` module needs the builder without needing the pipeline. */ type OptionsGuess = { kind: 'osm'; options: OsmOptions; report: GuessReport; } | { kind: 'satellite'; options: SatelliteOptions; report: GuessReport; } | { kind: 'unknown'; report: GuessReport; }; type GuessReport = { /** * What could not be carried over, could only be guessed at, or had to be chosen between. * * Replaces the `warnings: string[]` and `unmatched: string[]` this used to carry: a string can only * be printed, where a consumer wants to group, count, suppress, translate, and put a marker beside * the setting a diagnostic concerns. Sorted (see `sortDiagnostics`), so a test diff is stable. */ diagnostics: Diagnostic[]; /** * Where each derived option came from, keyed by option path. * * Records the thing the options cannot: `minimizeOptions` deletes a derived value that equals the * target's default, so a setting read from the style and one never derived both come out absent. * See `PROVENANCE_COVERS` for which options are annotated. */ provenance: ProvenanceMap; /** Every vector and raster source, with the schema recognised for it. */ sources: { id: string; type: string; guess: SchemaGuess; }[]; /** Per probe the style draws: the zoom it was read at and the layers it was read from, topmost first. */ evidence: { probe: string; zoom: number; layers: string[]; }[]; }; declare function deriveOptions(style: StyleSpecification, tileJSONs?: Readonly>, fontNames?: readonly string[]): OptionsGuess; type GuessOptionsOptions = { /** Used for the style document and every source's TileJSON; defaults to `globalThis.fetch`. */ fetch?: FetchLike; /** Resolves a relative style URL; relative TileJSON URLs resolve against the style's own URL. */ base?: string; }; /** * The asynchronous half of {@link deriveOptions}: downloads the style when given its URL, the TileJSON of * every vector source that references one, and the font list of the glyph server the options will use — * `osm()`'s default, via `fetchFontFaces()` — and derives the options from all three. Without a font * list, fonts carry over their weight only. * * A TileJSON that cannot be downloaded is not fatal — the schema is then recognised from the * source-layers the style reads — and is reported in `report.diagnostics`. Never throws: an invalid * argument or an unreachable style yields `kind: 'unknown'` with the reason in the warnings. */ declare function guessOptions(style: string | StyleSpecification, options?: GuessOptionsOptions): Promise; export { PROVENANCE_COVERS, byCode, byOption, deriveOptions, guessOptions, is, isCovered, sortDiagnostics, worst }; export type { Diagnostic, DiagnosticCode, DiagnosticData, DiagnosticOrigin, GuessOptionsOptions, GuessReport, OptionsGuess, Origin, Provenance, ProvenanceMap, Severity };