diff --git a/assets/themes/width/width.json b/assets/themes/width/width.json index 6d0bfb1147..9778da3293 100644 --- a/assets/themes/width/width.json +++ b/assets/themes/width/width.json @@ -32,7 +32,7 @@ 51.190748429411705 ] ], - "defaultBackgroundId": "CartoDB.DarkMatterNoLabels", + "defaultBackgroundId": "alidade.smooth_dark", "layers": [ { "id": "street_with_width", @@ -271,4 +271,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/Models/RasterLayers.ts b/src/Models/RasterLayers.ts index 9ef7f9250a..47162b6908 100644 --- a/src/Models/RasterLayers.ts +++ b/src/Models/RasterLayers.ts @@ -1,174 +1,122 @@ -import { Feature, Polygon } from "geojson"; -import * as editorlayerindex from "../assets/editor-layer-index.json"; -import * as globallayers from "../assets/global-raster-layers.json"; -import { BBox } from "../Logic/BBox"; -import { Store, Stores } from "../Logic/UIEventSource"; -import { GeoOperations } from "../Logic/GeoOperations"; -import { RasterLayerProperties } from "./RasterLayerProperties"; +import { Feature, Polygon } from "geojson" +import * as editorlayerindex from "../assets/editor-layer-index.json" +import * as globallayers from "../assets/global-raster-layers.json" +import { BBox } from "../Logic/BBox" +import { Store, Stores } from "../Logic/UIEventSource" +import { GeoOperations } from "../Logic/GeoOperations" +import { RasterLayerProperties } from "./RasterLayerProperties" export class AvailableRasterLayers { - public static EditorLayerIndex: (Feature & - RasterLayerPolygon)[] = editorlayerindex.features; - public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map( - (properties) => - { + public static EditorLayerIndex: (Feature & + RasterLayerPolygon)[] = editorlayerindex.features + public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map( + (properties) => + { + type: "Feature", + properties, + geometry: BBox.global.asGeometry(), + } + ) + public static readonly osmCartoProperties: RasterLayerProperties = { + id: "osm", + name: "OpenStreetMap", + url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution: { + text: "OpenStreetMap", + url: "https://openStreetMap.org/copyright", + }, + best: true, + max_zoom: 19, + min_zoom: 0, + category: "osmbasedmap", + } + + public static readonly osmCarto: RasterLayerPolygon = { type: "Feature", - properties, - geometry: BBox.global.asGeometry() - } - ); - public static readonly osmCartoProperties: RasterLayerProperties = { - id: "osm", - name: "OpenStreetMap", - url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", - attribution: { - text: "OpenStreetMap", - url: "https://openStreetMap.org/copyright" - }, - best: true, - max_zoom: 19, - min_zoom: 0, - category: "osmbasedmap" - }; + properties: AvailableRasterLayers.osmCartoProperties, + geometry: BBox.global.asGeometry(), + } - public static readonly osmCarto: RasterLayerPolygon = { - type: "Feature", - properties: AvailableRasterLayers.osmCartoProperties, - geometry: BBox.global.asGeometry() - }; + public static readonly maptilerDefaultLayer: RasterLayerPolygon = { + type: "Feature", + properties: { + name: "MapTiler", + url: "https://api.maptiler.com/maps/15cc8f61-0353-4be6-b8da-13daea5f7432/style.json?key=GvoVAJgu46I5rZapJuAy", + category: "osmbasedmap", + id: "maptiler", + type: "vector", + attribution: { + text: "Maptiler", + url: "https://www.maptiler.com/copyright/", + }, + }, + geometry: BBox.global.asGeometry(), + } - public static readonly maptilerDefaultLayer: RasterLayerPolygon = { - type: "Feature", - properties: { - name: "MapTiler", - url: "https://api.maptiler.com/maps/15cc8f61-0353-4be6-b8da-13daea5f7432/style.json?key=GvoVAJgu46I5rZapJuAy", - category: "osmbasedmap", - id: "maptiler", - type: "vector", - attribution: { - text: "Maptiler", - url: "https://www.maptiler.com/copyright/" - } - }, - geometry: BBox.global.asGeometry() - }; + public static readonly vectorLayers = [ + AvailableRasterLayers.maptilerDefaultLayer, + AvailableRasterLayers.osmCarto, + ] - public static readonly maptilerCarto: RasterLayerPolygon = { - type: "Feature", - properties: { - name: "MapTiler Carto", - url: "https://api.maptiler.com/maps/openstreetmap/style.json?key=GvoVAJgu46I5rZapJuAy", - category: "osmbasedmap", - id: "maptiler.carto", - type: "vector", - attribution: { - text: "Maptiler", - url: "https://www.maptiler.com/copyright/" - } - }, - geometry: BBox.global.asGeometry() - }; - - public static readonly maptilerBackdrop: RasterLayerPolygon = { - type: "Feature", - properties: { - name: "MapTiler Backdrop", - url: "https://api.maptiler.com/maps/backdrop/style.json?key=GvoVAJgu46I5rZapJuAy", - category: "osmbasedmap", - id: "maptiler.backdrop", - type: "vector", - attribution: { - text: "Maptiler", - url: "https://www.maptiler.com/copyright/" - } - }, - geometry: BBox.global.asGeometry() - }; - public static readonly americana: RasterLayerPolygon = { - type: "Feature", - properties: { - name: "Americana", - url: "https://zelonewolf.github.io/openstreetmap-americana/style.json", - category: "osmbasedmap", - id: "americana", - type: "vector", - attribution: { - text: "Americana", - url: "https://github.com/ZeLonewolf/openstreetmap-americana/" - } - }, - geometry: BBox.global.asGeometry() - }; - - public static readonly vectorLayers = [ - AvailableRasterLayers.maptilerDefaultLayer, - AvailableRasterLayers.osmCarto, - AvailableRasterLayers.maptilerCarto, - AvailableRasterLayers.maptilerBackdrop, - AvailableRasterLayers.americana - ]; - - public static layersAvailableAt( - location: Store<{ lon: number; lat: number }> - ): Store { - const availableLayersBboxes = Stores.ListStabilized( - location.mapD((loc) => { - const lonlat: [number, number] = [loc.lon, loc.lat]; - return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) => - BBox.get(eliPolygon).contains(lonlat) - ); - }) - ); - const available = Stores.ListStabilized( - availableLayersBboxes.map((eliPolygons) => { - const loc = location.data; - const lonlat: [number, number] = [loc.lon, loc.lat]; - const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => { - if (eliPolygon.geometry === null) { - return true; // global ELI-layer - } - return GeoOperations.inside(lonlat, eliPolygon); - }); - matching.push(...AvailableRasterLayers.globalLayers); - matching.unshift(...AvailableRasterLayers.vectorLayers); - return matching; - }) - ); - return available; - } + public static layersAvailableAt( + location: Store<{ lon: number; lat: number }> + ): Store { + const availableLayersBboxes = Stores.ListStabilized( + location.mapD((loc) => { + const lonlat: [number, number] = [loc.lon, loc.lat] + return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) => + BBox.get(eliPolygon).contains(lonlat) + ) + }) + ) + return Stores.ListStabilized( + availableLayersBboxes.map((eliPolygons) => { + const loc = location.data + const lonlat: [number, number] = [loc.lon, loc.lat] + const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => { + if (eliPolygon.geometry === null) { + return true // global ELI-layer + } + return GeoOperations.inside(lonlat, eliPolygon) + }) + matching.push(...AvailableRasterLayers.globalLayers) + return matching + }) + ) + } } export class RasterLayerUtils { - /** - * Selects, from the given list of available rasterLayerPolygons, a rasterLayer. - * This rasterlayer will be of type 'preferredCategory' and will be of the 'best'-layer (if available). - * Returns 'undefined' if no such layer is available - * @param available - * @param preferredCategory - * @param ignoreLayer - */ - public static SelectBestLayerAccordingTo( - available: RasterLayerPolygon[], - preferredCategory: string, - ignoreLayer?: RasterLayerPolygon - ): RasterLayerPolygon { - let secondBest: RasterLayerPolygon = undefined; - for (const rasterLayer of available) { - if (rasterLayer === ignoreLayer) { - continue; - } - const p = rasterLayer.properties; - if (p.category === preferredCategory) { - if (p.best) { - return rasterLayer; + /** + * Selects, from the given list of available rasterLayerPolygons, a rasterLayer. + * This rasterlayer will be of type 'preferredCategory' and will be of the 'best'-layer (if available). + * Returns 'undefined' if no such layer is available + * @param available + * @param preferredCategory + * @param ignoreLayer + */ + public static SelectBestLayerAccordingTo( + available: RasterLayerPolygon[], + preferredCategory: string, + ignoreLayer?: RasterLayerPolygon + ): RasterLayerPolygon { + let secondBest: RasterLayerPolygon = undefined + for (const rasterLayer of available) { + if (rasterLayer === ignoreLayer) { + continue + } + const p = rasterLayer.properties + if (p.category === preferredCategory) { + if (p.best) { + return rasterLayer + } + if (!secondBest) { + secondBest = rasterLayer + } + } } - if (!secondBest) { - secondBest = rasterLayer; - } - } + return secondBest } - return secondBest; - } } export type RasterLayerPolygon = Feature @@ -180,165 +128,165 @@ export type RasterLayerPolygon = Feature * which was then converted with http://borischerny.com/json-schema-to-typescript-browser/ */ export interface EditorLayerIndexProperties extends RasterLayerProperties { - /** - * The name of the imagery source - */ - readonly name: string; - /** - * Whether the imagery name should be translated - */ - readonly i18n?: boolean; - readonly type: - | "tms" - | "wms" - | "bing" - | "scanex" - | "wms_endpoint" - | "wmts" - | "vector"; /* Vector is not actually part of the ELI-spec, we add it for vector layers */ - /** - * A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories. - */ - readonly category?: - | "photo" - | "map" - | "historicmap" - | "osmbasedmap" - | "historicphoto" - | "qa" - | "elevation" - | "other"; - /** - * A URL template for imagery tiles - */ - readonly url: string; - readonly min_zoom?: number; - readonly max_zoom?: number; - /** - * explicit/implicit permission by the owner for use in OSM - */ - readonly permission_osm?: "explicit" | "implicit" | "no"; - /** - * A URL for the license or permissions for the imagery - */ - readonly license_url?: string; - /** - * A URL for the privacy policy of the operator or false if there is no existing privacy policy for tis imagery. - */ - readonly privacy_policy_url?: string | boolean; - /** - * A unique identifier for the source; used in imagery_used changeset tag - */ - readonly id: string; - /** - * A short English-language description of the source - */ - readonly description?: string; - /** - * The ISO 3166-1 alpha-2 two letter country code in upper case. Use ZZ for unknown or multiple. - */ - readonly country_code?: string; - /** - * Whether this imagery should be shown in the default world-wide menu - */ - readonly default?: boolean; - /** - * Whether this imagery is the best source for the region - */ - readonly best?: boolean; - /** - * The age of the oldest imagery or data in the source, as an RFC3339 date or leading portion of one - */ - readonly start_date?: string; - /** - * The age of the newest imagery or data in the source, as an RFC3339 date or leading portion of one - */ - readonly end_date?: string; - /** - * HTTP header to check for information if the tile is invalid - */ - readonly no_tile_header?: { /** - * This interface was referenced by `undefined`'s JSON-Schema definition - * via the `patternProperty` "^.*$". + * The name of the imagery source */ - [k: string]: string[] | null - }; - /** - * 'true' if tiles are transparent and can be overlaid on another source - */ - readonly overlay?: boolean & string; - readonly available_projections?: string[]; - readonly attribution?: { - readonly url?: string - readonly text?: string - readonly html?: string - readonly required?: boolean - }; - /** - * A URL for an image, that can be displayed in the list of imagery layers next to the name - */ - readonly icon?: string; - /** - * A link to an EULA text that has to be accepted by the user, before the imagery source is added. Can contain {lang} to be replaced by a current user language wiki code (like FR:) or an empty string for the default English text. - */ - readonly eula?: string; - /** - * A URL for an image, that is displayed in the mapview for attribution - */ - readonly "logo-image"?: string; - /** - * Customized text for the terms of use link (default is "Background Terms of Use") - */ - readonly "terms-of-use-text"?: string; - /** - * Specify a checksum for tiles, which aren't real tiles. `type` is the digest type and can be MD5, SHA-1, SHA-256, SHA-384 and SHA-512, value is the hex encoded checksum in lower case. To create a checksum save the tile as file and upload it to e.g. https://defuse.ca/checksums.htm. - */ - readonly "no-tile-checksum"?: string; - /** - * header-name attribute specifies a header returned by tile server, that will be shown as `metadata-key` attribute in Show Tile Info dialog - */ - readonly "metadata-header"?: string; - /** - * Set to `true` if imagery source is properly aligned and does not need imagery offset adjustments. This is used for OSM based sources too. - */ - readonly "valid-georeference"?: boolean; - /** - * Size of individual tiles delivered by a TMS service - */ - readonly "tile-size"?: number; - /** - * Whether tiles status can be accessed by appending /status to the tile URL and can be submitted for re-rendering by appending /dirty. - */ - readonly "mod-tile-features"?: string; - /** - * HTTP headers to be sent to server. It has two attributes header-name and header-value. May be specified multiple times. - */ - readonly "custom-http-headers"?: { - readonly "header-name"?: string - readonly "header-value"?: string - }; - /** - * Default layer to open (when using WMS_ENDPOINT type). Contains list of layer tag with two attributes - name and style, e.g. `"default-layers": ["layer": { name="Basisdata_NP_Basiskart_JanMayen_WMTS_25829" "style":"default" } ]` (not allowed in `mirror` attribute) - */ - readonly "default-layers"?: { - layer?: { - "layer-name"?: string - "layer-style"?: string - [k: string]: unknown + readonly name: string + /** + * Whether the imagery name should be translated + */ + readonly i18n?: boolean + readonly type: + | "tms" + | "wms" + | "bing" + | "scanex" + | "wms_endpoint" + | "wmts" + | "vector" /* Vector is not actually part of the ELI-spec, we add it for vector layers */ + /** + * A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories. + */ + readonly category?: + | "photo" + | "map" + | "historicmap" + | "osmbasedmap" + | "historicphoto" + | "qa" + | "elevation" + | "other" + /** + * A URL template for imagery tiles + */ + readonly url: string + readonly min_zoom?: number + readonly max_zoom?: number + /** + * explicit/implicit permission by the owner for use in OSM + */ + readonly permission_osm?: "explicit" | "implicit" | "no" + /** + * A URL for the license or permissions for the imagery + */ + readonly license_url?: string + /** + * A URL for the privacy policy of the operator or false if there is no existing privacy policy for tis imagery. + */ + readonly privacy_policy_url?: string | boolean + /** + * A unique identifier for the source; used in imagery_used changeset tag + */ + readonly id: string + /** + * A short English-language description of the source + */ + readonly description?: string + /** + * The ISO 3166-1 alpha-2 two letter country code in upper case. Use ZZ for unknown or multiple. + */ + readonly country_code?: string + /** + * Whether this imagery should be shown in the default world-wide menu + */ + readonly default?: boolean + /** + * Whether this imagery is the best source for the region + */ + readonly best?: boolean + /** + * The age of the oldest imagery or data in the source, as an RFC3339 date or leading portion of one + */ + readonly start_date?: string + /** + * The age of the newest imagery or data in the source, as an RFC3339 date or leading portion of one + */ + readonly end_date?: string + /** + * HTTP header to check for information if the tile is invalid + */ + readonly no_tile_header?: { + /** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` "^.*$". + */ + [k: string]: string[] | null } - [k: string]: unknown - }[]; - /** - * format to use when connecting tile server (when using WMS_ENDPOINT type) - */ - readonly format?: string; - /** - * If `true` transparent tiles will be requested from WMS server - */ - readonly transparent?: boolean & string; - /** - * minimum expiry time for tiles in seconds. The larger the value, the longer entry in cache will be considered valid - */ - readonly "minimum-tile-expire"?: number; + /** + * 'true' if tiles are transparent and can be overlaid on another source + */ + readonly overlay?: boolean & string + readonly available_projections?: string[] + readonly attribution?: { + readonly url?: string + readonly text?: string + readonly html?: string + readonly required?: boolean + } + /** + * A URL for an image, that can be displayed in the list of imagery layers next to the name + */ + readonly icon?: string + /** + * A link to an EULA text that has to be accepted by the user, before the imagery source is added. Can contain {lang} to be replaced by a current user language wiki code (like FR:) or an empty string for the default English text. + */ + readonly eula?: string + /** + * A URL for an image, that is displayed in the mapview for attribution + */ + readonly "logo-image"?: string + /** + * Customized text for the terms of use link (default is "Background Terms of Use") + */ + readonly "terms-of-use-text"?: string + /** + * Specify a checksum for tiles, which aren't real tiles. `type` is the digest type and can be MD5, SHA-1, SHA-256, SHA-384 and SHA-512, value is the hex encoded checksum in lower case. To create a checksum save the tile as file and upload it to e.g. https://defuse.ca/checksums.htm. + */ + readonly "no-tile-checksum"?: string + /** + * header-name attribute specifies a header returned by tile server, that will be shown as `metadata-key` attribute in Show Tile Info dialog + */ + readonly "metadata-header"?: string + /** + * Set to `true` if imagery source is properly aligned and does not need imagery offset adjustments. This is used for OSM based sources too. + */ + readonly "valid-georeference"?: boolean + /** + * Size of individual tiles delivered by a TMS service + */ + readonly "tile-size"?: number + /** + * Whether tiles status can be accessed by appending /status to the tile URL and can be submitted for re-rendering by appending /dirty. + */ + readonly "mod-tile-features"?: string + /** + * HTTP headers to be sent to server. It has two attributes header-name and header-value. May be specified multiple times. + */ + readonly "custom-http-headers"?: { + readonly "header-name"?: string + readonly "header-value"?: string + } + /** + * Default layer to open (when using WMS_ENDPOINT type). Contains list of layer tag with two attributes - name and style, e.g. `"default-layers": ["layer": { name="Basisdata_NP_Basiskart_JanMayen_WMTS_25829" "style":"default" } ]` (not allowed in `mirror` attribute) + */ + readonly "default-layers"?: { + layer?: { + "layer-name"?: string + "layer-style"?: string + [k: string]: unknown + } + [k: string]: unknown + }[] + /** + * format to use when connecting tile server (when using WMS_ENDPOINT type) + */ + readonly format?: string + /** + * If `true` transparent tiles will be requested from WMS server + */ + readonly transparent?: boolean & string + /** + * minimum expiry time for tiles in seconds. The larger the value, the longer entry in cache will be considered valid + */ + readonly "minimum-tile-expire"?: number } diff --git a/src/assets/global-raster-layers.json b/src/assets/global-raster-layers.json index c33f71e080..25c0edf0a1 100644 --- a/src/assets/global-raster-layers.json +++ b/src/assets/global-raster-layers.json @@ -1,97 +1,92 @@ { "layers": [ { - "id": "Stamen.TonerLite", - "name": "Toner Lite (by Stamen)", - "url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png", + "name": "Americana", + "url": "https://zelonewolf.github.io/openstreetmap-americana/style.json", "category": "osmbasedmap", + "id": "americana", + "type": "vector", "attribution": { - "html": "Map tiles by Stamen Design, CC BY 3.0 — Map data {attribution.OpenStreetMap}" - }, - "min_zoom": 0, - "max_zoom": 20 + "text": "Americana", + "url": "https://github.com/ZeLonewolf/openstreetmap-americana/" + } }, { - "id": "Stamen.TonerBackground", - "name": "Toner Background - no labels (by Stamen)", + "name": "MapTiler Backdrop", + "url": "https://api.maptiler.com/maps/backdrop/style.json?key=GvoVAJgu46I5rZapJuAy", "category": "osmbasedmap", - "url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png", + "id": "maptiler.backdrop", + "type": "vector", "attribution": { - "html": "Map tiles by Stamen Design, CC BY 3.0 — Map data {attribution.OpenStreetMap}" - }, - "min_zoom": 0, - "max_zoom": 20 + "text": "Maptiler", + "url": "https://www.maptiler.com/copyright/" + } }, { - "id": "Stamen.Watercolor", - "name": "Watercolor (by Stamen)", + "name": "MapTiler Carto", + "url": "https://api.maptiler.com/maps/openstreetmap/style.json?key=GvoVAJgu46I5rZapJuAy", "category": "osmbasedmap", - "url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png", + "id": "maptiler.carto", + "type": "vector", "attribution": { - "html": "Map tiles by Stamen Design, CC BY 3.0 — Map data {attribution.OpenStreetMap}" - }, - "min_zoom": 0, - "max_zoom": 20 + "text": "Maptiler", + "url": "https://www.maptiler.com/copyright/" + } }, { - "id": "CartoDB.Positron", - "name": "Positron (by CartoDB)", - "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png", + "name": "Alidade Smooth", + "url": "https://tiles-eu.stadiamaps.com/styles/alidade_smooth.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75", + "category": "osmbasedmap", + "id": "alidade.smooth", + "type": "vector", "attribution": { - "html": "CARTO" - }, - "max_zoom": 20, - "category": "osmbasedmap" + "text": "Alidade", + "url": "https://stadiamaps.com/" + } }, { - "id": "CartoDB.PositronNoLabels", - "name": "Positron - no labels (by CartoDB)", - "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png", + "name": "Alidade Smooth Dark", + "url": "https://tiles-eu.stadiamaps.com/styles/alidade_smooth_dark.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75", "category": "osmbasedmap", + "id": "alidade.smooth_dark", + "type": "vector", "attribution": { - "html": "CARTO" - }, - "max_zoom": 20 + "text": "Alidade/Stadiamaps", + "url": "https://stadiamaps.com/" + } }, { - "id": "CartoDB.Voyager", - "name": "Voyager (by CartoDB)", - "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png", + "name": "Stamen Terrain", + "url": "https://tiles-eu.stadiamaps.com/styles/stamen_terrain.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75", "category": "osmbasedmap", + "id": "stamen.terrain", + "type": "vector", "attribution": { - "html": "CARTO" - }, - "max_zoom": 20 + "text": "Stamen/Stadiamaps", + "url": "https://stadiamaps.com/" + } }, { - "id": "CartoDB.VoyagerNoLabels", - "name": "Voyager - no labels (by CartoDB)", - "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png", + "name": "Stamen Toner", + "url": "https://tiles-eu.stadiamaps.com/styles/stamen_toner.json?key=14c5a900-7137-42f7-9cb9-fff0f4696f75", "category": "osmbasedmap", + "id": "stamen.toner", + "type": "vector", "attribution": { - "html": "CARTO" - }, - "max_zoom": 20 + "text": "Stamen/Stadiamaps", + "url": "https://stadiamaps.com/" + } }, { - "id": "CartoDB.DarkMatter", - "name": "Dark Matter (by CartoDB)", - "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png", + "url": "https://tiles-eu.stadiamaps.com/styles/osm_bright.json", + "name": "StadiaMaps OSM Bright", "category": "osmbasedmap", + "id": "stadia.bright", + "type": "vector", "attribution": { - "html": "CARTO" - }, - "max_zoom": 20 - }, - { - "id": "CartoDB.DarkMatterNoLabels", - "name": "Dark Matter - no labels (by CartoDB)", - "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png", - "category": "osmbasedmap", - "attribution": { - "html": "CARTO" - }, - "max_zoom": 20 + "text": "Stadiamaps", + "url": "https://stadiamaps.com/" + } } ] }