Merge master
This commit is contained in:
commit
c358a4c415
72 changed files with 5113 additions and 1023 deletions
|
@ -1,174 +1,118 @@
|
|||
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<Polygon, EditorLayerIndexProperties> &
|
||||
RasterLayerPolygon)[] = <any>editorlayerindex.features;
|
||||
public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map(
|
||||
(properties) =>
|
||||
<RasterLayerPolygon>{
|
||||
public static EditorLayerIndex: (Feature<Polygon, EditorLayerIndexProperties> &
|
||||
RasterLayerPolygon)[] = <any>editorlayerindex.features
|
||||
public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map(
|
||||
(properties) =>
|
||||
<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",
|
||||
}
|
||||
|
||||
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 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<RasterLayerPolygon[]> {
|
||||
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<RasterLayerPolygon[]> {
|
||||
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.maptilerDefaultLayer)
|
||||
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<Polygon, RasterLayerProperties>
|
||||
|
@ -180,165 +124,165 @@ export type RasterLayerPolygon = Feature<Polygon, RasterLayerProperties>
|
|||
* 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
|
||||
}
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
import { InputElement } from "./InputElement"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Utils } from "../../Utils"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import InputElementMap from "./InputElementMap"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class CheckBox extends InputElementMap<number[], boolean> {
|
||||
constructor(el: BaseUIElement | string, defaultValue?: boolean) {
|
||||
super(
|
||||
new CheckBoxes([Translations.W(el)]),
|
||||
(x0, x1) => x0 === x1,
|
||||
(t) => t.length > 0,
|
||||
(x) => (x ? [0] : [])
|
||||
)
|
||||
if (defaultValue !== undefined) {
|
||||
this.GetValue().setData(defaultValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of individual checkboxes
|
||||
* The value will contain the indexes of the selected checkboxes
|
||||
*/
|
||||
export default class CheckBoxes extends InputElement<number[]> {
|
||||
private static _nextId = 0
|
||||
private readonly value: UIEventSource<number[]>
|
||||
private readonly _elements: BaseUIElement[]
|
||||
|
||||
constructor(elements: BaseUIElement[], value = new UIEventSource<number[]>([])) {
|
||||
super()
|
||||
this.value = value
|
||||
this._elements = Utils.NoNull(elements)
|
||||
this.SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
IsValid(ts: number[]): boolean {
|
||||
return ts !== undefined
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<number[]> {
|
||||
return this.value
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const formTag = document.createElement("form")
|
||||
|
||||
const value = this.value
|
||||
const elements = this._elements
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
let inputI = elements[i]
|
||||
const input = document.createElement("input")
|
||||
const id = CheckBoxes._nextId
|
||||
CheckBoxes._nextId++
|
||||
input.id = "checkbox" + id
|
||||
|
||||
input.type = "checkbox"
|
||||
input.classList.add("p-1", "cursor-pointer", "m-3", "pl-3", "mr-0")
|
||||
|
||||
const label = document.createElement("label")
|
||||
label.htmlFor = input.id
|
||||
label.appendChild(input)
|
||||
label.appendChild(inputI.ConstructElement())
|
||||
label.classList.add("block", "w-full", "p-2", "cursor-pointer")
|
||||
|
||||
formTag.appendChild(label)
|
||||
|
||||
value.addCallbackAndRunD((selectedValues) => {
|
||||
input.checked = selectedValues.indexOf(i) >= 0
|
||||
|
||||
if (input.checked) {
|
||||
label.classList.add("checked")
|
||||
} else {
|
||||
label.classList.remove("checked")
|
||||
}
|
||||
})
|
||||
|
||||
input.onchange = () => {
|
||||
// Index = index in the list of already checked items
|
||||
const index = value.data.indexOf(i)
|
||||
if (input.checked && index < 0) {
|
||||
value.data.push(i)
|
||||
value.ping()
|
||||
} else if (index >= 0) {
|
||||
value.data.splice(index, 1)
|
||||
value.ping()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formTag
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
import { InputElement } from "./InputElement"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export default class InputElementMap<T, X> extends InputElement<X> {
|
||||
private readonly _inputElement: InputElement<T>
|
||||
private isSame: (x0: X, x1: X) => boolean
|
||||
private readonly fromX: (x: X) => T
|
||||
private readonly toX: (t: T) => X
|
||||
private readonly _value: UIEventSource<X>
|
||||
|
||||
constructor(
|
||||
inputElement: InputElement<T>,
|
||||
isSame: (x0: X, x1: X) => boolean,
|
||||
toX: (t: T) => X,
|
||||
fromX: (x: X) => T,
|
||||
extraSources: Store<any>[] = []
|
||||
) {
|
||||
super()
|
||||
this.isSame = isSame
|
||||
this.fromX = fromX
|
||||
this.toX = toX
|
||||
this._inputElement = inputElement
|
||||
const self = this
|
||||
this._value = inputElement.GetValue().sync(
|
||||
(t) => {
|
||||
const newX = toX(t)
|
||||
const currentX = self.GetValue()?.data
|
||||
if (isSame(currentX, newX)) {
|
||||
return currentX
|
||||
}
|
||||
return newX
|
||||
},
|
||||
extraSources,
|
||||
(x) => {
|
||||
return fromX(x)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<X> {
|
||||
return this._value
|
||||
}
|
||||
|
||||
IsValid(x: X): boolean {
|
||||
if (x === undefined) {
|
||||
return false
|
||||
}
|
||||
const t = this.fromX(x)
|
||||
if (t === undefined) {
|
||||
return false
|
||||
}
|
||||
return this._inputElement.IsValid(t)
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
return this._inputElement.ConstructElement()
|
||||
}
|
||||
}
|
|
@ -422,6 +422,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background))
|
||||
}
|
||||
if (!map.getLayer(background.id)) {
|
||||
addLayerBeforeId ??= map
|
||||
.getStyle()
|
||||
.layers.find((l) => l.id.startsWith("mapcomplete_"))?.id
|
||||
console.log(
|
||||
"Adding background layer",
|
||||
background.id,
|
||||
|
|
|
@ -513,7 +513,7 @@ export default class ShowDataLayer {
|
|||
const l = new LineRenderingLayer(
|
||||
map,
|
||||
features,
|
||||
this._options.layer.id + "_linerendering_" + i,
|
||||
"mapcomplete_" + this._options.layer.id + "_linerendering_" + i,
|
||||
lineRenderingConfig,
|
||||
doShowLayer,
|
||||
fetchStore,
|
||||
|
|
|
@ -74,7 +74,7 @@ import NearbyImagesSearch from "../Logic/Web/NearbyImagesSearch"
|
|||
import AllReviews from "./Reviews/AllReviews.svelte"
|
||||
import StarsBarIcon from "./Reviews/StarsBarIcon.svelte"
|
||||
import ReviewForm from "./Reviews/ReviewForm.svelte"
|
||||
import Questionbox from "./Popup/TagRendering/Questionbox.svelte";
|
||||
import Questionbox from "./Popup/TagRendering/Questionbox.svelte"
|
||||
|
||||
class NearbyImageVis implements SpecialVisualization {
|
||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||
|
@ -181,7 +181,6 @@ class StealViz implements SpecialVisualization {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thin wrapper around QuestionBox.svelte to include it into the special Visualisations
|
||||
*/
|
||||
|
@ -189,7 +188,7 @@ export class QuestionViz implements SpecialVisualization {
|
|||
funcName = "questions"
|
||||
needsUrls = []
|
||||
docs =
|
||||
"The special element which shows the questions which are unkown. Added by default if not yet there"
|
||||
"The special element which shows the questions which are unkown. Added by default if not yet there"
|
||||
args = [
|
||||
{
|
||||
name: "labels",
|
||||
|
@ -202,20 +201,20 @@ export class QuestionViz implements SpecialVisualization {
|
|||
]
|
||||
|
||||
constr(
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
state: SpecialVisualizationState,
|
||||
tags: UIEventSource<Record<string, string>>,
|
||||
args: string[],
|
||||
feature: Feature,
|
||||
layer: LayerConfig
|
||||
): BaseUIElement {
|
||||
const labels = args[0]
|
||||
?.split(";")
|
||||
?.map((s) => s.trim())
|
||||
?.filter((s) => s !== "")
|
||||
?.split(";")
|
||||
?.map((s) => s.trim())
|
||||
?.filter((s) => s !== "")
|
||||
const blacklist = args[1]
|
||||
?.split(";")
|
||||
?.map((s) => s.trim())
|
||||
?.filter((s) => s !== "")
|
||||
?.split(";")
|
||||
?.map((s) => s.trim())
|
||||
?.filter((s) => s !== "")
|
||||
return new SvelteUIElement(Questionbox, {
|
||||
layer,
|
||||
tags,
|
||||
|
|
11
src/Utils.ts
11
src/Utils.ts
|
@ -1098,7 +1098,16 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
|||
}
|
||||
return { content: data }
|
||||
} catch (e) {
|
||||
console.error("Could not parse ", data, "due to", e, "\n", e.stack)
|
||||
console.error(
|
||||
"Could not parse the response of",
|
||||
url,
|
||||
"which contains",
|
||||
data,
|
||||
"due to",
|
||||
e,
|
||||
"\n",
|
||||
e.stack
|
||||
)
|
||||
return { error: "malformed", url }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"contributors": [
|
||||
{
|
||||
"commits": 6085,
|
||||
"commits": 6092,
|
||||
"contributor": "Pieter Vander Vennet"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,97 +1,169 @@
|
|||
{
|
||||
"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 <a href=\"http://stamen.com\">Stamen Design</a>, <a href=\"http://creativecommons.org/licenses/by/3.0\">CC BY 3.0</a> — 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 <a href=\"http://stamen.com\">Stamen Design</a>, <a href=\"http://creativecommons.org/licenses/by/3.0\">CC BY 3.0</a> — 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 <a href=\"http://stamen.com\">Stamen Design</a>, <a href=\"http://creativecommons.org/licenses/by/3.0\">CC BY 3.0</a> — 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": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"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": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"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": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"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": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"max_zoom": 20
|
||||
"text": "Stamen/Stadiamaps",
|
||||
"url": "https://stadiamaps.com/"
|
||||
}
|
||||
}, {
|
||||
"name": "Stamen Watercolor",
|
||||
"url": "https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg?key=14c5a900-7137-42f7-9cb9-fff0f4696f75",
|
||||
"category": "osmbasedmap",
|
||||
"id": "stamen.watercolor",
|
||||
"attribution": {
|
||||
"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": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"max_zoom": 20
|
||||
"text": "Stadiamaps",
|
||||
"url": "https://stadiamaps.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"url": "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Positron",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.positron",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"html": "<a href=\"https://carto.com/attributions\">CARTO</a>"
|
||||
},
|
||||
"max_zoom": 20
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Dark Matter",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.dark_matter",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Voyager",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.voyager",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Positron (no labels)",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.positron_no_labels",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Dark Matter (no labels)",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.dark_matter_no_labels",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "https://basemaps.cartocdn.com/gl/voyager-nolabels-gl-style/style.json?key=eyJhbGciOiJIUzI1NiJ9.eyJhIjoiYWNfdW4ybmhlbTciLCJqdGkiOiIwZGQxNjJmNyJ9.uATJpa6QcrtXhph3Bzvk2nX3QsxEw-Q8dj5khUG6hGk",
|
||||
"name": "Carto Voyager (no labels)",
|
||||
"category": "osmbasedmap",
|
||||
"id": "carto.voyager_no_labels",
|
||||
"type": "vector",
|
||||
"attribution": {
|
||||
"text": "<a href=\"https://carto.com/about-carto/\" target=\"_blank\" rel=\"noopener\">CARTO</a>",
|
||||
"url": "https://carto.com/"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"contributors": [
|
||||
{
|
||||
"commits": 310,
|
||||
"commits": 311,
|
||||
"contributor": "kjon"
|
||||
},
|
||||
{
|
||||
|
@ -348,6 +348,10 @@
|
|||
"commits": 3,
|
||||
"contributor": "SiegbjornSitumeang"
|
||||
},
|
||||
{
|
||||
"commits": 2,
|
||||
"contributor": "macpac"
|
||||
},
|
||||
{
|
||||
"commits": 2,
|
||||
"contributor": "Peter Brodersen"
|
||||
|
@ -444,6 +448,10 @@
|
|||
"commits": 2,
|
||||
"contributor": "Leo Alcaraz"
|
||||
},
|
||||
{
|
||||
"commits": 1,
|
||||
"contributor": "Michal Čermák"
|
||||
},
|
||||
{
|
||||
"commits": 1,
|
||||
"contributor": "Kelson Vibber"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue