diff --git a/.gitignore b/.gitignore index a9dcf16ad..c5a6e0be2 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ service-worker.js *.vsix public/*.webmanifest public/assets/generated/ +public/assets/langs/* diff --git a/package-lock.json b/package-lock.json index 434775519..95780b578 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "chart.js": "^3.8.0", "country-language": "^0.1.7", "country-to-currency": "^1.0.10", + "crypto": "^1.0.1", "csv-parse": "^5.1.0", "doctest-ts-improved": "^0.8.8", "dompurify": "^3.0.5", @@ -5392,6 +5393,12 @@ "node": ">= 8" } }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." + }, "node_modules/css-line-break": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", @@ -17341,6 +17348,11 @@ "which": "^2.0.1" } }, + "crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" + }, "css-line-break": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", diff --git a/package.json b/package.json index 4c547daad..ab9ee95e1 100644 --- a/package.json +++ b/package.json @@ -17,23 +17,10 @@ "Alternatively, you can override the `osm` credentials using the environment variables `VITE_OSM_OAUTH_CLIENT_ID` and `VITE_OSM_OAUTH_SECRET`" ], "oauth_credentials": { - "osm_pietervdvn": { - "#": "This client_id is registered by 'Pieter Vander Vennet' on OSM.org", - "oauth_client_id": "sa1ngLJBJ8McmzHElN8NYtIDm5TZTYEYhq3-0snO4Qc", - "oauth_secret": "XU_cD5Mvw9VKk9T0t_gO8V7cbRC4Hmw2Tb4Rv0Zmz-U", - "url": "https://www.openstreetmap.org" - }, - "osm": { - "#": "This client-id is registered by 'MapComplete' on osm.org", - "oauth_client_id": "K93H1d8ve7p-tVLE1ZwsQ4lAFLQk8INx5vfTLMu5DWk", - "oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg", - "url": "https://www.openstreetmap.org" - }, - "osm-test": { - "oauth_client_id": "HwUn6GPxGm1m9WwMarxTglhy6dBTM4YkaV1I9h6pDGU", - "oauth_secret": "luFZtPJg7j96K6WM6RpcZ_3M-r6muuDq6fG1ygk0I_4", - "url": "https://master.apis.dev.openstreetmap.org" - } + "#": "This client-id is registered by 'MapComplete' on osm.org", + "oauth_client_id": "K93H1d8ve7p-tVLE1ZwsQ4lAFLQk8INx5vfTLMu5DWk", + "oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg", + "url": "https://www.openstreetmap.org" }, "api_keys": { "#": "Various API-keys for various services. Feel free to reuse those in another MapComplete-hosted version", @@ -45,7 +32,8 @@ "https://overpass.kumi.systems/api/interpreter", "https://overpass.openstreetmap.ru/cgi/interpreter" ], - "country_coder_host": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country" + "country_coder_host": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country", + "nominatimEndpoint": "https://nominatim.openstreetmap.org/search?" }, "scripts": { "start": "npm run generate:layeroverview && npm run strt", @@ -120,6 +108,7 @@ "chart.js": "^3.8.0", "country-language": "^0.1.7", "country-to-currency": "^1.0.10", + "crypto": "^1.0.1", "csv-parse": "^5.1.0", "doctest-ts-improved": "^0.8.8", "dompurify": "^3.0.5", diff --git a/scripts/build.sh b/scripts/build.sh index 3b1cf07b8..6649d61ca 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -8,6 +8,8 @@ rm -rf dist/* rm -rf .cache mkdir dist 2> /dev/null mkdir dist/assets 2> /dev/null +mkdir dist/assets/langs 2> /dev/null +mkdir dist/assets/langs/layers 2> /dev/null export NODE_OPTIONS="--max-old-space-size=8192" @@ -38,7 +40,8 @@ then export ASSET_URL echo "$ASSET_URL" else - ASSET_URL="$BRANCH" + # ASSET_URL="$BRANCH" + ASSET_URL="./" export ASSET_URL echo "$ASSET_URL" fi @@ -51,5 +54,5 @@ vite build $SRC_MAPS cp -r assets/layers/ dist/assets/layers/ cp -r assets/themes/ dist/assets/themes/ cp -r assets/svg/ dist/assets/svg/ - +cp -r langs/layers/ dist/assets/langs/layers/ export NODE_OPTIONS="" diff --git a/scripts/generateLayouts.ts b/scripts/generateLayouts.ts index d62a736d2..14793d226 100644 --- a/scripts/generateLayouts.ts +++ b/scripts/generateLayouts.ts @@ -8,6 +8,11 @@ import LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig" import xml2js from "xml2js" import ScriptUtils from "./ScriptUtils" import { Utils } from "../src/Utils" +import SpecialVisualizations from "../src/UI/SpecialVisualizations" +import Constants from "../src/Models/Constants" +import { AvailableRasterLayers, RasterLayerPolygon } from "../src/Models/RasterLayers" +import { ImmutableStore } from "../src/Logic/UIEventSource" +import * as crypto from "crypto" const sharp = require("sharp") const template = readFileSync("theme.html", "utf8") @@ -195,31 +200,92 @@ function asLangSpan(t: Translation, tag = "span"): string { if (lang === "_context") { continue } - values.push(`<${tag} lang='${lang}'>${t.translations[lang]}`) + values.push(`<${tag} lang="${lang}">${t.translations[lang]}`) } return values.join("\n") } -let cspCached: string = undefined -function generateCsp(): string { - if (cspCached !== undefined) { - return cspCached +let previousSrc: Set = new Set() + +function generateCsp( + layout: LayoutConfig, + options: { + scriptSrcs: string[] + } +): string { + const apiUrls: string[] = [ + "'self'", + ...Constants.defaultOverpassUrls, + Constants.countryCoderEndpoint, + Constants.nominatimEndpoint, + "https://api.openstreetmap.org", + "https://pietervdvn.goatcounter.com", + ].concat(...SpecialVisualizations.specialVisualizations.map((sv) => sv.needsUrls)) + + const geojsonSources: string[] = layout.layers.map((l) => l.source?.geojsonSource) + const hosts = new Set() + const eliLayers: RasterLayerPolygon[] = AvailableRasterLayers.layersAvailableAt( + new ImmutableStore({ lon: 0, lat: 0 }) + ).data + const vectorLayers = eliLayers.filter((l) => l.properties.type === "vector") + const vectorSources = vectorLayers.map((l) => l.properties.url) + apiUrls.push(...vectorSources) + for (const connectSource of apiUrls.concat(geojsonSources)) { + if (!connectSource) { + continue + } + try { + const url = new URL(connectSource) + hosts.add("https://" + url.host) + } catch (e) { + hosts.add(connectSource) + } } - const csp = { + const connectSrc = Array.from(hosts).sort() + + const newSrcs = connectSrc.filter((newItem) => !previousSrc.has(newItem)) + + console.log( + "Got", + hosts.size, + "connect-src items for theme", + layout.id, + "(extra sources: ", + newSrcs.join(" ") + ")" + ) + previousSrc = hosts + + const csp: Record = { "default-src": "'self'", - "script-src": "'self'", - "img-src": "*", - "connect-src": "*", + "script-src": ["'self'", "https://gc.zgo.at/count.js", ...(options?.scriptSrcs ?? [])].join( + " " + ), + "img-src": "* data:", // maplibre depends on 'data:' to load + "connect-src": connectSrc.join(" "), + "report-to": "https://report.mapcomplete.org/csp", + "worker-src": "'self' blob:", // Vite somehow loads the worker via a 'blob' + "style-src": "'self' 'unsafe-inline'", // unsafe-inline is needed to change the default background pin colours } const content = Object.keys(csp) - .map((k) => k + ": " + csp[k]) + .map((k) => k + " " + csp[k]) .join("; ") - cspCached = `` - return cspCached + return [ + ``, + ``, + ].join("\n") } +const removeOtherLanguages = readFileSync("./src/UI/RemoveOtherLanguages.js", "utf8") + .split("\n") + .map((s) => s.trim()) + .join("\n") +const removeOtherLanguagesHash = crypto + .createHash("sha256") + .update(removeOtherLanguages) + .digest("base64") + async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alreadyWritten) { Locale.language.setData(layout.language[0]) const targetLanguage = layout.language[0] @@ -290,8 +356,11 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr ...apple_icons, ].join("\n") - const loadingText = Translations.t.general.loadingTheme.Subs({ theme: ogTitle }) - + const loadingText = Translations.t.general.loadingTheme.Subs({ theme: layout.title }) + const templateLines = template.split("\n") + const removeOtherLanguagesReference = templateLines.find( + (line) => line.indexOf("./src/UI/RemoveOtherLanguages.js") >= 0 + ) let output = template .replace("Loading MapComplete, hang on...", asLangSpan(loadingText, "h1")) .replace( @@ -299,7 +368,13 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr Translations.t.general.poweredByOsm.textFor(targetLanguage) ) .replace(/.*/s, themeSpecific) - .replace(//, generateCsp()) + .replace( + //, + generateCsp(layout, { + scriptSrcs: [`'sha256-${removeOtherLanguagesHash}'`], + }) + ) + .replace(removeOtherLanguagesReference, "") .replace( /.*/s, asLangSpan(layout.shortDescription) @@ -310,8 +385,8 @@ async function createLandingPage(layout: LayoutConfig, manifest, whiteIcons, alr ) .replace( - '', - `` + /.*\/src\/index\.ts.*/, + `` ) return output diff --git a/scripts/hetzner/config/Caddyfile b/scripts/hetzner/config/Caddyfile index a417808d2..27b328008 100644 --- a/scripts/hetzner/config/Caddyfile +++ b/scripts/hetzner/config/Caddyfile @@ -4,13 +4,16 @@ hosted.mapcomplete.org { header { +Permissions-Policy "interest-cohort=()" +Report-To `\{"group":"csp-endpoint", "max_age": 86400,"endpoints": [\{"url": "https://report.mapcomplete.org/csp"}], "include_subdomains": true}` - +Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' https://gc.zgo.at ; img-src * ; report-uri https://report.mapcomplete.org/csp ; report-to csp-endpoint ;" } } countrycoder.mapcomplete.org { root * tiles/ file_server + header { + +Permissions-Policy "interest-cohort=()" + +Access-Control-Allow-Origin https://hosted.mapcomplete.org https://dev.mapcomplete.org https://mapcomplete.org + } } diff --git a/scripts/hetzner/deployHetzner.sh b/scripts/hetzner/deployHetzner.sh index 6f1044d22..af9443faf 100755 --- a/scripts/hetzner/deployHetzner.sh +++ b/scripts/hetzner/deployHetzner.sh @@ -10,15 +10,16 @@ # unzip tiles.zip MAPCOMPLETE_CONFIGURATION="config_hetzner" +cp config.json config.json.bu && +cp ./scripts/hetzner/config.json . && # Copy the config _before_ building, as the config might contain some needed URLs npm run reset:layeroverview npm run test -cp config.json config.json.bu && -cp ./scripts/hetzner/config.json . && npm run prepare-deploy && mv config.json.bu config.json && zip dist.zip -r dist/* && -scp -r dist.zip hetzner:/root/ && -scp ./scripts/hetzner/config/* hetzner:/root/ -ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start" +scp ./scripts/hetzner/config/* hetzner:/root/ && +rsync -rzh --progress dist.zip hetzner:/root/ && +echo "Upload completed, deploying config and booting" && +ssh hetzner -t "unzip dist.zip && rm dist.zip && rm -rf public/ && mv dist public && caddy stop && caddy start" && rm dist.zip npm run clean diff --git a/src/InstallServiceWorker.ts b/src/InstallServiceWorker.ts new file mode 100644 index 000000000..49afbed20 --- /dev/null +++ b/src/InstallServiceWorker.ts @@ -0,0 +1,13 @@ +export {} +window.addEventListener("load", async () => { + if (!("serviceWorker" in navigator)) { + console.log("Service workers are not supported") + return + } + try { + await navigator.serviceWorker.register("/service-worker.js") + console.log("Service worker registration successful") + } catch (err) { + console.error("Service worker registration failed", err) + } +}) diff --git a/src/Logic/ImageProviders/AllImageProviders.ts b/src/Logic/ImageProviders/AllImageProviders.ts index 89318aeb3..5fc9c1ec7 100644 --- a/src/Logic/ImageProviders/AllImageProviders.ts +++ b/src/Logic/ImageProviders/AllImageProviders.ts @@ -23,27 +23,27 @@ export default class AllImageProviders { ) ), ] - + public static apiUrls: string[] = [].concat( + ...AllImageProviders.ImageAttributionSource.map((src) => src.apiUrls()) + ) + public static defaultKeys = [].concat( + AllImageProviders.ImageAttributionSource.map((provider) => provider.defaultKeyPrefixes) + ) private static providersByName = { imgur: Imgur.singleton, mapillary: Mapillary.singleton, wikidata: WikidataImageProvider.singleton, wikimedia: WikimediaImageProvider.singleton, } - - public static byName(name: string) { - return AllImageProviders.providersByName[name.toLowerCase()] - } - - public static defaultKeys = [].concat( - AllImageProviders.ImageAttributionSource.map((provider) => provider.defaultKeyPrefixes) - ) - private static _cache: Map> = new Map< string, UIEventSource >() + public static byName(name: string) { + return AllImageProviders.providersByName[name.toLowerCase()] + } + public static LoadImagesFor( tags: Store>, tagKey?: string[] diff --git a/src/Logic/ImageProviders/GenericImageProvider.ts b/src/Logic/ImageProviders/GenericImageProvider.ts index 4cb382b23..178137027 100644 --- a/src/Logic/ImageProviders/GenericImageProvider.ts +++ b/src/Logic/ImageProviders/GenericImageProvider.ts @@ -3,6 +3,10 @@ import ImageProvider, { ProvidedImage } from "./ImageProvider" export default class GenericImageProvider extends ImageProvider { public defaultKeyPrefixes: string[] = ["image"] + public apiUrls(): string[] { + return [] + } + private readonly _valuePrefixBlacklist: string[] public constructor(valuePrefixBlacklist: string[]) { diff --git a/src/Logic/ImageProviders/ImageProvider.ts b/src/Logic/ImageProviders/ImageProvider.ts index 92d3bc941..6d42623ce 100644 --- a/src/Logic/ImageProviders/ImageProvider.ts +++ b/src/Logic/ImageProviders/ImageProvider.ts @@ -65,4 +65,6 @@ export default abstract class ImageProvider { public abstract ExtractUrls(key: string, value: string): Promise[]> public abstract DownloadAttribution(url: string): Promise + + public abstract apiUrls(): string[] } diff --git a/src/Logic/ImageProviders/Imgur.ts b/src/Logic/ImageProviders/Imgur.ts index 536616403..b84fe606f 100644 --- a/src/Logic/ImageProviders/Imgur.ts +++ b/src/Logic/ImageProviders/Imgur.ts @@ -10,10 +10,16 @@ export class Imgur extends ImageProvider implements ImageUploader { public static readonly singleton = new Imgur() public readonly defaultKeyPrefixes: string[] = ["image"] public readonly maxFileSizeInMegabytes = 10 + public static readonly apiUrl = "https://api.imgur.com/3/image" + private constructor() { super() } + apiUrls(): string[] { + return [Imgur.apiUrl] + } + /** * Uploads an image, returns the URL where to find the image * @param title @@ -25,7 +31,7 @@ export class Imgur extends ImageProvider implements ImageUploader { description: string, blob: File ): Promise<{ key: string; value: string }> { - const apiUrl = "https://api.imgur.com/3/image" + const apiUrl = Imgur.apiUrl const apiKey = Constants.ImgurApiKey const formData = new FormData() diff --git a/src/Logic/ImageProviders/Mapillary.ts b/src/Logic/ImageProviders/Mapillary.ts index 102bb709b..96ec29ebc 100644 --- a/src/Logic/ImageProviders/Mapillary.ts +++ b/src/Logic/ImageProviders/Mapillary.ts @@ -17,6 +17,10 @@ export class Mapillary extends ImageProvider { ] defaultKeyPrefixes = ["mapillary", "image"] + apiUrls(): string[] { + return ["https://mapillary.com", "https://www.mapillary.com", "https://graph.mapillary.com"] + } + /** * Indicates that this is the same URL * Ignores 'stp' parameter diff --git a/src/Logic/ImageProviders/WikidataImageProvider.ts b/src/Logic/ImageProviders/WikidataImageProvider.ts index 043fee4b7..1c7cbea8a 100644 --- a/src/Logic/ImageProviders/WikidataImageProvider.ts +++ b/src/Logic/ImageProviders/WikidataImageProvider.ts @@ -5,6 +5,9 @@ import { WikimediaImageProvider } from "./WikimediaImageProvider" import Wikidata from "../Web/Wikidata" export class WikidataImageProvider extends ImageProvider { + public apiUrls(): string[] { + return Wikidata.neededUrls + } public static readonly singleton = new WikidataImageProvider() public readonly defaultKeyPrefixes = ["wikidata"] diff --git a/src/Logic/ImageProviders/WikimediaImageProvider.ts b/src/Logic/ImageProviders/WikimediaImageProvider.ts index f9f40261d..a9841b8fd 100644 --- a/src/Logic/ImageProviders/WikimediaImageProvider.ts +++ b/src/Logic/ImageProviders/WikimediaImageProvider.ts @@ -11,11 +11,11 @@ import Wikimedia from "../Web/Wikimedia" */ export class WikimediaImageProvider extends ImageProvider { public static readonly singleton = new WikimediaImageProvider() - public static readonly commonsPrefixes = [ + public static readonly apiUrls = [ "https://commons.wikimedia.org/wiki/", "https://upload.wikimedia.org", - "File:", ] + public static readonly commonsPrefixes = [...WikimediaImageProvider.apiUrls, "File:"] private readonly commons_key = "wikimedia_commons" public readonly defaultKeyPrefixes = [this.commons_key, "image"] @@ -66,6 +66,10 @@ export class WikimediaImageProvider extends ImageProvider { return value } + apiUrls(): string[] { + return WikimediaImageProvider.apiUrls + } + SourceIcon(backlink: string): BaseUIElement { const img = Svg.wikimedia_commons_white_svg().SetStyle("width:2em;height: 2em") if (backlink === undefined) { diff --git a/src/Logic/Maproulette.ts b/src/Logic/Maproulette.ts index 50f550c43..1eaf999b3 100644 --- a/src/Logic/Maproulette.ts +++ b/src/Logic/Maproulette.ts @@ -32,11 +32,12 @@ export default class Maproulette { private readonly apiKey: string public static singleton = new Maproulette() + public static readonly defaultEndpoint = "https://maproulette.org/api/v2" /** * Creates a new Maproulette instance * @param endpoint The API endpoint to use */ - constructor(endpoint: string = "https://maproulette.org/api/v2") { + constructor(endpoint: string = Maproulette.defaultEndpoint) { this.endpoint = endpoint this.apiKey = Constants.MaprouletteApiKey } diff --git a/src/Logic/Osm/AuthConfig.ts b/src/Logic/Osm/AuthConfig.ts new file mode 100644 index 000000000..b3c2330e2 --- /dev/null +++ b/src/Logic/Osm/AuthConfig.ts @@ -0,0 +1,6 @@ +export interface AuthConfig { + "#"?: string // optional comment + oauth_client_id: string + oauth_secret: string + url: string +} diff --git a/src/Logic/Osm/Geocoding.ts b/src/Logic/Osm/Geocoding.ts index 09da7af6d..d3af5d6a5 100644 --- a/src/Logic/Osm/Geocoding.ts +++ b/src/Logic/Osm/Geocoding.ts @@ -1,5 +1,6 @@ import { Utils } from "../../Utils" import { BBox } from "../BBox" +import Constants from "../../Models/Constants" export interface GeoCodeResult { display_name: string @@ -15,7 +16,7 @@ export interface GeoCodeResult { } export class Geocoding { - private static readonly host = "https://nominatim.openstreetmap.org/search?" + public static readonly host = Constants.nominatimEndpoint static async Search(query: string, bbox: BBox): Promise { const b = bbox ?? BBox.global diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index 6f4e3755b..13a029439 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -4,7 +4,8 @@ import { Store, Stores, UIEventSource } from "../UIEventSource" import { OsmPreferences } from "./OsmPreferences" import { Utils } from "../../Utils" import { LocalStorageSource } from "../Web/LocalStorageSource" -import * as config from "../../../package.json" +import { AuthConfig } from "./AuthConfig" +import Constants from "../../Models/Constants" export default class UserDetails { public loggedIn = false @@ -25,18 +26,9 @@ export default class UserDetails { } } -export interface AuthConfig { - "#"?: string // optional comment - oauth_client_id: string - oauth_secret: string - url: string -} - export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" export class OsmConnection { - public static readonly oauth_configs: Record = - config.config.oauth_credentials public auth public userDetails: UIEventSource public isLoggedIn: Store @@ -53,7 +45,7 @@ export class OsmConnection { public preferencesHandler: OsmPreferences public readonly _oauth_config: AuthConfig private readonly _dryRun: Store - private fakeUser: boolean + private readonly fakeUser: boolean private _onLoggedIn: ((userDetails: UserDetails) => void)[] = [] private readonly _iframeMode: Boolean | boolean private readonly _singlePage: boolean @@ -65,15 +57,12 @@ export class OsmConnection { oauth_token?: UIEventSource // Used to keep multiple changesets open and to write to the correct changeset singlePage?: boolean - osmConfiguration?: "osm" | "osm-test" attemptLogin?: true | boolean }) { - options = options ?? {} - this.fakeUser = options.fakeUser ?? false - this._singlePage = options.singlePage ?? true - this._oauth_config = - OsmConnection.oauth_configs[options.osmConfiguration ?? "osm"] ?? - OsmConnection.oauth_configs.osm + options ??= {} + this.fakeUser = options?.fakeUser ?? false + this._singlePage = options?.singlePage ?? true + this._oauth_config = Constants.osmAuthConfig console.debug("Using backend", this._oauth_config.url) this._iframeMode = Utils.runningFromConsole ? false : window !== window.top diff --git a/src/Logic/State/FeatureSwitchState.ts b/src/Logic/State/FeatureSwitchState.ts index a776ae6af..d2e25b987 100644 --- a/src/Logic/State/FeatureSwitchState.ts +++ b/src/Logic/State/FeatureSwitchState.ts @@ -28,14 +28,8 @@ class FeatureSwitchUtils { export class OsmConnectionFeatureSwitches { public readonly featureSwitchFakeUser: UIEventSource - public readonly featureSwitchApiURL: UIEventSource constructor() { - this.featureSwitchApiURL = QueryParameters.GetQueryParameter( - "backend", - "osm", - "The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'" - ) this.featureSwitchFakeUser = QueryParameters.GetBooleanQueryParameter( "fake-user", @@ -143,7 +137,6 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches { let testingDefaultValue = false if ( - this.featureSwitchApiURL.data !== "osm-test" && !Utils.runningFromConsole && (location.hostname === "localhost" || location.hostname === "127.0.0.1") ) { diff --git a/src/Logic/Web/NearbyImagesSearch.ts b/src/Logic/Web/NearbyImagesSearch.ts index 665cc66e7..2c81f19e0 100644 --- a/src/Logic/Web/NearbyImagesSearch.ts +++ b/src/Logic/Web/NearbyImagesSearch.ts @@ -1,9 +1,9 @@ import { IndexedFeatureSource } from "../FeatureSource/FeatureSource" import { GeoOperations } from "../GeoOperations" import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource" -import { Mapillary } from "../ImageProviders/Mapillary" import P4C from "pic4carto" import { Utils } from "../../Utils" + export interface NearbyImageOptions { lon: number lat: number @@ -35,17 +35,12 @@ export interface P4CPicture { } /** - * Uses Pic4wCarto to fetch nearby images from various providers + * Uses Pic4Carto to fetch nearby images from various providers */ export default class NearbyImagesSearch { - private static readonly services = [ - "mapillary", - "flickr", - "openstreetcam", - "wikicommons", - ] as const - - private individualStores + public static readonly services = ["mapillary", "flickr", "kartaview", "wikicommons"] as const + public static readonly apiUrls = ["https://api.flickr.com"] + private readonly individualStores: Store<{ images: P4CPicture[]; beforeFilter: number }>[] private readonly _store: UIEventSource = new UIEventSource([]) public readonly store: Store = this._store private readonly _options: NearbyImageOptions @@ -71,16 +66,16 @@ export default class NearbyImagesSearch { this.update() } - private static buildPictureFetcher( + private static async fetchImages( options: NearbyImageOptions, - fetcher: "mapillary" | "flickr" | "openstreetcam" | "wikicommons" - ): Store<{ images: P4CPicture[]; beforeFilter: number }> { + fetcher: P4CService + ): Promise { const picManager = new P4C.PicturesManager({ usefetchers: [fetcher] }) - const searchRadius = options.searchRadius ?? 100 const maxAgeSeconds = (options.maxDaysOld ?? 3 * 365) * 24 * 60 * 60 * 1000 + const searchRadius = options.searchRadius ?? 100 - const p4cStore = Stores.FromPromise( - picManager.startPicsRetrievalAround( + try { + const pics: P4CPicture[] = await picManager.startPicsRetrievalAround( new P4C.LatLng(options.lat, options.lon), searchRadius, { @@ -88,7 +83,21 @@ export default class NearbyImagesSearch { towardscenter: false, } ) + return pics + } catch (e) { + console.error("Could not fetch images from service", fetcher, e) + return [] + } + } + + private static buildPictureFetcher( + options: NearbyImageOptions, + fetcher: P4CService + ): Store<{ images: P4CPicture[]; beforeFilter: number }> { + const p4cStore = Stores.FromPromise( + NearbyImagesSearch.fetchImages(options, fetcher) ) + const searchRadius = options.searchRadius ?? 100 return p4cStore.map( (images) => { if (images === undefined) { @@ -220,3 +229,5 @@ class ImagesInLoadedDataFetcher { return foundImages } } + +type P4CService = (typeof NearbyImagesSearch.services)[number] diff --git a/src/Logic/Web/PlantNet.ts b/src/Logic/Web/PlantNet.ts index dab705ad0..4012040e0 100644 --- a/src/Logic/Web/PlantNet.ts +++ b/src/Logic/Web/PlantNet.ts @@ -1,7 +1,7 @@ import { Utils } from "../../Utils" export default class PlantNet { - private static baseUrl = + public static baseUrl = "https://my-api.plantnet.org/v2/identify/all?api-key=2b10AAsjzwzJvucA5Ncm5qxe" public static query(imageUrls: string[]): Promise { diff --git a/src/Logic/Web/Wikidata.ts b/src/Logic/Web/Wikidata.ts index 31eaa33d8..1f60f32ec 100644 --- a/src/Logic/Web/Wikidata.ts +++ b/src/Logic/Web/Wikidata.ts @@ -123,6 +123,11 @@ export interface WikidataAdvancedSearchoptions extends WikidataSearchoptions { * Utility functions around wikidata */ export default class Wikidata { + public static readonly neededUrls = [ + "https://www.wikidata.org/", + "https://wikidata.org/", + "https://query.wikidata.org", + ] private static readonly _identifierPrefixes = ["Q", "L"].map((str) => str.toLowerCase()) private static readonly _prefixesToRemove = [ "https://www.wikidata.org/wiki/Lexeme:", @@ -130,11 +135,11 @@ export default class Wikidata { "http://www.wikidata.org/entity/", "Lexeme:", ].map((str) => str.toLowerCase()) - private static readonly _storeCache = new Map< string, Store<{ success: WikidataResponse } | { error: any }> >() + /** * Same as LoadWikidataEntry, but wrapped into a UIEventSource * @param value @@ -388,6 +393,7 @@ export default class Wikidata { } private static _cache = new Map>() + public static async LoadWikidataEntryAsync(value: string | number): Promise { const key = "" + value const cached = Wikidata._cache.get(key) @@ -398,6 +404,7 @@ export default class Wikidata { Wikidata._cache.set(key, uncached) return uncached } + /** * Loads a wikidata page * @returns the entity of the given value diff --git a/src/Logic/Web/Wikipedia.ts b/src/Logic/Web/Wikipedia.ts index 0cde301db..81f7bba90 100644 --- a/src/Logic/Web/Wikipedia.ts +++ b/src/Logic/Web/Wikipedia.ts @@ -34,6 +34,8 @@ export default class Wikipedia { private static readonly idsToRemove = ["sjabloon_zie"] + public static readonly neededUrls = ["*.wikipedia.org"] + private static readonly _cache = new Map>() private static _fullDetailsCache = new Map>() public readonly backend: string diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index 75ea6f73f..ad68f6979 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -1,6 +1,7 @@ import * as packagefile from "../../package.json" import * as extraconfig from "../../config.json" import { Utils } from "../Utils" +import { AuthConfig } from "../Logic/Osm/AuthConfig" export type PriviligedLayerType = (typeof Constants.priviliged_layers)[number] @@ -104,8 +105,9 @@ export default class Constants { public static ImgurApiKey = Constants.config.api_keys.imgur public static readonly mapillary_client_token_v4 = Constants.config.api_keys.mapillary_v4 public static defaultOverpassUrls = Constants.config.default_overpass_urls - static countryCoderEndpoint: string = Constants.config.country_coder_host - + public static countryCoderEndpoint: string = Constants.config.country_coder_host + public static osmAuthConfig: AuthConfig = Constants.config.oauth_credentials + public static nominatimEndpoint: string = Constants.config.nominatimEndpoint /** * These are the values that are allowed to use as 'backdrop' icon for a map pin */ diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 1adc59b5a..eb7835991 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -140,8 +140,7 @@ export default class ThemeViewState implements SpecialVisualizationState { "oauth_token", undefined, "Used to complete the login" - ), - osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data, + ) }) this.userRelatedState = new UserRelatedState( this.osmConnection, diff --git a/src/UI/AllThemesGui.ts b/src/UI/AllThemesGui.ts index 437fb3ac4..1cf890316 100644 --- a/src/UI/AllThemesGui.ts +++ b/src/UI/AllThemesGui.ts @@ -22,8 +22,7 @@ export default class AllThemesGui { "oauth_token", undefined, "Used to complete the login" - ), - osmConfiguration: <"osm" | "osm-test">featureSwitches.featureSwitchApiURL.data, + ) }) const state = new UserRelatedState(osmConnection) const intro = new Combine([ diff --git a/src/UI/BigComponents/OpenJosm.ts b/src/UI/BigComponents/OpenJosm.ts index 8f86f6d98..3eafbd2ac 100644 --- a/src/UI/BigComponents/OpenJosm.ts +++ b/src/UI/BigComponents/OpenJosm.ts @@ -11,6 +11,7 @@ import { Utils } from "../../Utils" import Constants from "../../Models/Constants" export class OpenJosm extends Combine { + public static readonly needsUrls = ["http://127.0.0.1:8111/load_and_zoom"] constructor(osmConnection: OsmConnection, bounds: Store, iconStyle?: string) { const t = Translations.t.general.attribution diff --git a/src/UI/Popup/AddNoteCommentViz.ts b/src/UI/Popup/AddNoteCommentViz.ts index 15bc8c1cd..e40a6d041 100644 --- a/src/UI/Popup/AddNoteCommentViz.ts +++ b/src/UI/Popup/AddNoteCommentViz.ts @@ -10,9 +10,11 @@ import Combine from "../Base/Combine" import Title from "../Base/Title" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { UIEventSource } from "../../Logic/UIEventSource" +import Constants from "../../Models/Constants" export class AddNoteCommentViz implements SpecialVisualization { funcName = "add_note_comment" + needsUrls = [Constants.osmAuthConfig.url] docs = "A textfield to add a comment to a node (with the option to close the note)." args = [ { diff --git a/src/UI/Popup/AutoApplyButton.ts b/src/UI/Popup/AutoApplyButton.ts index 2a38c6dd1..4f89f23c5 100644 --- a/src/UI/Popup/AutoApplyButton.ts +++ b/src/UI/Popup/AutoApplyButton.ts @@ -9,7 +9,6 @@ import { Utils } from "../../Utils" import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" import { VariableUiElement } from "../Base/VariableUIElement" import Loading from "../Base/Loading" -import { OsmConnection } from "../../Logic/Osm/OsmConnection" import Translations from "../i18n/Translations" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { Changes } from "../../Logic/Osm/Changes" @@ -209,6 +208,8 @@ class ApplyButton extends UIElement { export default class AutoApplyButton implements SpecialVisualization { public readonly docs: BaseUIElement public readonly funcName: string = "auto_apply" + public readonly needsUrls = [] + public readonly args: { name: string defaultValue?: string @@ -271,14 +272,7 @@ export default class AutoApplyButton implements SpecialVisualization { argument: string[] ): BaseUIElement { try { - if ( - !state.layout.official && - !( - state.featureSwitchIsTesting.data || - state.osmConnection._oauth_config.url === - OsmConnection.oauth_configs["osm-test"].url - ) - ) { + if (!state.layout.official && !state.featureSwitchIsTesting.data) { const t = Translations.t.general.add.import return new Combine([ new FixedUiElement( diff --git a/src/UI/Popup/CloseNoteButton.ts b/src/UI/Popup/CloseNoteButton.ts index d1cf0ce29..21bce9253 100644 --- a/src/UI/Popup/CloseNoteButton.ts +++ b/src/UI/Popup/CloseNoteButton.ts @@ -8,9 +8,11 @@ import Toggle from "../Input/Toggle" import { LoginToggle } from "./LoginButton" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { UIEventSource } from "../../Logic/UIEventSource" +import Constants from "../../Models/Constants" export class CloseNoteButton implements SpecialVisualization { public readonly funcName = "close_note" + public readonly needsUrls = [Constants.osmAuthConfig.url] public readonly docs = "Button to close a note. A predifined text can be defined to close the note with. If the note is already closed, will show a small text." public readonly args = [ diff --git a/src/UI/Popup/ExportAsGpxViz.ts b/src/UI/Popup/ExportAsGpxViz.ts index 63ae33b5d..c7821da10 100644 --- a/src/UI/Popup/ExportAsGpxViz.ts +++ b/src/UI/Popup/ExportAsGpxViz.ts @@ -13,7 +13,7 @@ export class ExportAsGpxViz implements SpecialVisualization { funcName = "export_as_gpx" docs = "Exports the selected feature as GPX-file" args = [] - + needsUrls = [] constr( state: SpecialVisualizationState, tagSource: UIEventSource>, diff --git a/src/UI/Popup/HistogramViz.ts b/src/UI/Popup/HistogramViz.ts index fffc451d4..94b0f2b89 100644 --- a/src/UI/Popup/HistogramViz.ts +++ b/src/UI/Popup/HistogramViz.ts @@ -2,10 +2,13 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import Histogram from "../BigComponents/Histogram" import { Feature } from "geojson" +import Constants from "../../Models/Constants" export class HistogramViz implements SpecialVisualization { funcName = "histogram" docs = "Create a histogram for a list of given values, read from the properties." + needsUrls = [] + example = '`{histogram(\'some_key\')}` with properties being `{some_key: ["a","b","a","c"]} to create a histogram' args = [ diff --git a/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts b/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts index 8c924c37d..fee676bf7 100644 --- a/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts +++ b/src/UI/Popup/ImportButtons/ConflateImportButtonViz.ts @@ -24,6 +24,7 @@ export interface ConflateFlowArguments extends ImportFlowArguments { export default class ConflateImportButtonViz implements SpecialVisualization, AutoAction { supportsAutoAction: boolean = true + needsUrls = [] public readonly funcName: string = "conflate_button" public readonly args: { name: string diff --git a/src/UI/Popup/ImportButtons/ImportFlow.ts b/src/UI/Popup/ImportButtons/ImportFlow.ts index caf9f3b9c..50943d8af 100644 --- a/src/UI/Popup/ImportButtons/ImportFlow.ts +++ b/src/UI/Popup/ImportButtons/ImportFlow.ts @@ -194,10 +194,7 @@ export default abstract class ImportFlow { return { error: t.hasBeenImported } } - const usesTestUrl = - this.state.osmConnection._oauth_config.url === - OsmConnection.oauth_configs["osm-test"].url - if (!state.layout.official && !(isTesting || usesTestUrl)) { + if (!state.layout.official && !isTesting) { // Unofficial theme - imports not allowed return { error: t.officialThemesOnly, diff --git a/src/UI/Popup/ImportButtons/PointImportButtonViz.ts b/src/UI/Popup/ImportButtons/PointImportButtonViz.ts index eeb875c08..583e5d3c3 100644 --- a/src/UI/Popup/ImportButtons/PointImportButtonViz.ts +++ b/src/UI/Popup/ImportButtons/PointImportButtonViz.ts @@ -18,6 +18,7 @@ export class PointImportButtonViz implements SpecialVisualization { public readonly docs: string | BaseUIElement public readonly example?: string public readonly args: { name: string; defaultValue?: string; doc: string }[] + public needsUrls = [] constructor() { this.funcName = "import_button" diff --git a/src/UI/Popup/ImportButtons/WayImportButtonViz.ts b/src/UI/Popup/ImportButtons/WayImportButtonViz.ts index 13e71beee..d3e8bb6d4 100644 --- a/src/UI/Popup/ImportButtons/WayImportButtonViz.ts +++ b/src/UI/Popup/ImportButtons/WayImportButtonViz.ts @@ -20,6 +20,7 @@ import FullNodeDatabaseSource from "../../../Logic/FeatureSource/TiledFeatureSou */ export default class WayImportButtonViz implements AutoAction, SpecialVisualization { public readonly funcName: string = "import_way_button" + needsUrls = [] public readonly docs: string = "This button will copy the data from an external dataset into OpenStreetMap, copying the geometry and adding it as a 'line'" + ImportFlowUtils.documentationGeneral diff --git a/src/UI/Popup/LanguageElement.ts b/src/UI/Popup/LanguageElement.ts index 754026228..93c03039d 100644 --- a/src/UI/Popup/LanguageElement.ts +++ b/src/UI/Popup/LanguageElement.ts @@ -20,6 +20,7 @@ import { Feature } from "geojson" export class LanguageElement implements SpecialVisualization { funcName: string = "language_chooser" + needsUrls = [] docs: string | BaseUIElement = "The language element allows to show and pick all known (modern) languages. The key can be set" diff --git a/src/UI/Popup/MapillaryLinkVis.ts b/src/UI/Popup/MapillaryLinkVis.ts index 9722680a9..b65d24b8b 100644 --- a/src/UI/Popup/MapillaryLinkVis.ts +++ b/src/UI/Popup/MapillaryLinkVis.ts @@ -9,6 +9,8 @@ import MapillaryLink from "../BigComponents/MapillaryLink.svelte" export class MapillaryLinkVis implements SpecialVisualization { funcName = "mapillary_link" docs = "Adds a button to open mapillary on the specified location" + needsUrls = [] + args = [ { name: "zoom", diff --git a/src/UI/Popup/MinimapViz.ts b/src/UI/Popup/MinimapViz.ts index 556157537..873770b4e 100644 --- a/src/UI/Popup/MinimapViz.ts +++ b/src/UI/Popup/MinimapViz.ts @@ -13,6 +13,7 @@ import { BBox } from "../../Logic/BBox" export class MinimapViz implements SpecialVisualization { funcName = "minimap" docs = "A small map showing the selected feature." + needsUrls = [] args = [ { doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close", diff --git a/src/UI/Popup/MultiApplyViz.ts b/src/UI/Popup/MultiApplyViz.ts index 876678cb8..cb2513aac 100644 --- a/src/UI/Popup/MultiApplyViz.ts +++ b/src/UI/Popup/MultiApplyViz.ts @@ -4,6 +4,7 @@ import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisua export class MultiApplyViz implements SpecialVisualization { funcName = "multi_apply" + needsUrls = [] docs = "A button to apply the tagging of this object onto a list of other features. This is an advanced feature for which you'll need calculatedTags" args = [ diff --git a/src/UI/Popup/PlantNetDetectionViz.ts b/src/UI/Popup/PlantNetDetectionViz.ts index ed5a7acc4..101f37ec6 100644 --- a/src/UI/Popup/PlantNetDetectionViz.ts +++ b/src/UI/Popup/PlantNetDetectionViz.ts @@ -8,9 +8,10 @@ import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import SvelteUIElement from "../Base/SvelteUIElement" import PlantNet from "../PlantNet/PlantNet.svelte" - +import { default as PlantNetCode } from "../../Logic/Web/PlantNet" export class PlantNetDetectionViz implements SpecialVisualization { funcName = "plantnet_detection" + needsUrls = [PlantNetCode.baseUrl] docs = "Sends the images linked to the current object to plantnet.org and asks it what plant species is shown on it. The user can then select the correct species; the corresponding wikidata-identifier will then be added to the object (together with `source:species:wikidata=plantnet.org AI`). " diff --git a/src/UI/Popup/QuestionViz.ts b/src/UI/Popup/QuestionViz.ts index 8d5f8f4c2..f4cc957a3 100644 --- a/src/UI/Popup/QuestionViz.ts +++ b/src/UI/Popup/QuestionViz.ts @@ -11,6 +11,8 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig" */ export default 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" args = [ diff --git a/src/UI/Popup/ShareLinkViz.ts b/src/UI/Popup/ShareLinkViz.ts index 40ef684fd..1233e22b4 100644 --- a/src/UI/Popup/ShareLinkViz.ts +++ b/src/UI/Popup/ShareLinkViz.ts @@ -15,6 +15,7 @@ export class ShareLinkViz implements SpecialVisualization { doc: "The url to share (default: current URL)", }, ] + needsUrls = [] public constr( state: SpecialVisualizationState, diff --git a/src/UI/Popup/TagApplyButton.ts b/src/UI/Popup/TagApplyButton.ts index 2491f217a..fa3405b98 100644 --- a/src/UI/Popup/TagApplyButton.ts +++ b/src/UI/Popup/TagApplyButton.ts @@ -21,6 +21,7 @@ import Maproulette from "../../Logic/Maproulette" export default class TagApplyButton implements AutoAction, SpecialVisualization { public readonly funcName = "tag_apply" + needsUrls = [] public readonly docs = "Shows a big button; clicking this button will apply certain tags onto the feature.\n\nThe first argument takes a specification of which tags to add.\n" + Utils.Special_visualizations_tagsToApplyHelpText diff --git a/src/UI/Popup/UploadToOsmViz.ts b/src/UI/Popup/UploadToOsmViz.ts index 427235aeb..0679885fe 100644 --- a/src/UI/Popup/UploadToOsmViz.ts +++ b/src/UI/Popup/UploadToOsmViz.ts @@ -2,6 +2,7 @@ import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI" import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization" import { UIEventSource } from "../../Logic/UIEventSource" import { GeoOperations } from "../../Logic/GeoOperations" +import Constants from "../../Models/Constants" /** * Wrapper around 'UploadTraceToOsmUI' @@ -11,6 +12,7 @@ export class UploadToOsmViz implements SpecialVisualization { docs = "Uploads the GPS-history as GPX to OpenStreetMap.org; clears the history afterwards. The actual feature is ignored." args = [] + needsUrls = [Constants.osmAuthConfig.url] constr( state: SpecialVisualizationState, diff --git a/src/UI/RemoveOtherLanguages.js b/src/UI/RemoveOtherLanguages.js new file mode 100644 index 000000000..7486047ca --- /dev/null +++ b/src/UI/RemoveOtherLanguages.js @@ -0,0 +1,31 @@ +let lang = ( + (navigator.languages && navigator.languages[0]) || + navigator.language || + navigator["userLanguage"] || + "en" +).substr(0, 2) + +function filterLangs(maindiv) { + let foundLangs = 0 + for (const child of Array.from(maindiv.children)) { + if (child.attributes.getNamedItem("lang")?.value === lang) { + foundLangs++ + } + } + if (foundLangs === 0) { + lang = "en" + } + for (const child of Array.from(maindiv.children)) { + const childLang = child.attributes.getNamedItem("lang") + if (childLang === undefined) { + continue + } + if (childLang.value === lang) { + continue + } + child.parentElement.removeChild(child) + } +} + +filterLangs(document.getElementById("descriptions-while-loading")) +filterLangs(document.getElementById("default-title")) diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index edd3266e5..28125837c 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -88,6 +88,7 @@ export interface SpecialVisualization { readonly funcName: string readonly docs: string | BaseUIElement readonly example?: string + readonly needsUrls: string[] /** * Indicates that this special visualisation will make requests to the 'alLNodesDatabase' and that it thus should be included diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index 7038d1821..a0bc0909f 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -28,7 +28,6 @@ import Wikidata, { WikidataResponse } from "../Logic/Web/Wikidata" import { Translation } from "./i18n/Translation" import Translations from "./i18n/Translations" import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" -import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" import { SubtleButton } from "./Base/SubtleButton" import Svg from "../Svg" import NoteCommentElement from "./Popup/NoteCommentElement" @@ -68,6 +67,11 @@ import SendEmail from "./Popup/SendEmail.svelte" import NearbyImages from "./Popup/NearbyImages.svelte" import NearbyImagesCollapsed from "./Popup/NearbyImagesCollapsed.svelte" import UploadImage from "./Image/UploadImage.svelte" +import { Imgur } from "../Logic/ImageProviders/Imgur" +import Constants from "../Models/Constants" +import { MangroveReviews } from "mangrove-reviews-typescript" +import Wikipedia from "../Logic/Web/Wikipedia" +import NearbyImagesSearch from "../Logic/Web/NearbyImagesSearch" import AllReviews from "./Reviews/AllReviews.svelte" import StarsBarIcon from "./Reviews/StarsBarIcon.svelte" import ReviewForm from "./Reviews/ReviewForm.svelte" @@ -84,7 +88,7 @@ class NearbyImageVis implements SpecialVisualization { docs = "A component showing nearby images loaded from various online services such as Mapillary. In edit mode and when used on a feature, the user can select an image to add to the feature" funcName = "nearby_images" - + needsUrls = NearbyImagesSearch.apiUrls constr( state: SpecialVisualizationState, tags: UIEventSource>, @@ -122,6 +126,7 @@ class StealViz implements SpecialVisualization { required: true, }, ] + needsUrls = [] constr(state: SpecialVisualizationState, featureTags, args) { const [featureIdKey, layerAndtagRenderingIds] = args @@ -382,6 +387,7 @@ export default class SpecialVisualizations { funcName: "add_new_point", docs: "An element which allows to add a new point on the 'last_click'-location. Only makes sense in the layer `last_click`", args: [], + needsUrls: [], constr(state: SpecialVisualizationState, _, __, feature): BaseUIElement { let [lon, lat] = GeoOperations.centerpointCoordinates(feature) return new SvelteUIElement(AddNewPoint, { @@ -393,6 +399,7 @@ export default class SpecialVisualizations { { funcName: "user_profile", args: [], + needsUrls: [], docs: "A component showing information about the currently logged in user (username, profile description, profile picture + link to edit them). Mostly meant to be used in the 'user-settings'", constr(state: SpecialVisualizationState): BaseUIElement { return new SvelteUIElement(UserProfile, { @@ -403,6 +410,7 @@ export default class SpecialVisualizations { { funcName: "language_picker", args: [], + needsUrls: [], docs: "A component to set the language of the user interface", constr(state: SpecialVisualizationState): BaseUIElement { return new LanguagePicker( @@ -414,6 +422,7 @@ export default class SpecialVisualizations { { funcName: "logout", args: [], + needsUrls: [Constants.osmAuthConfig.url], docs: "Shows a button where the user can log out", constr(state: SpecialVisualizationState): BaseUIElement { return new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, { @@ -430,6 +439,7 @@ export default class SpecialVisualizations { funcName: "split_button", docs: "Adds a button which allows to split a way", args: [], + needsUrls: [], constr( state: SpecialVisualizationState, tagSource: UIEventSource> @@ -450,6 +460,7 @@ export default class SpecialVisualizations { funcName: "move_button", docs: "Adds a button which allows to move the object to another location. The config will be read from the layer config", args: [], + needsUrls: [], constr( state: SpecialVisualizationState, tagSource: UIEventSource>, @@ -473,6 +484,7 @@ export default class SpecialVisualizations { funcName: "delete_button", docs: "Adds a button which allows to delete the object at this location. The config will be read from the layer config", args: [], + needsUrls: [], constr( state: SpecialVisualizationState, tagSource: UIEventSource>, @@ -497,6 +509,7 @@ export default class SpecialVisualizations { { funcName: "open_note", args: [], + needsUrls: [Constants.osmAuthConfig.url], docs: "Creates a new map note on the given location. This options is placed in the 'last_click'-popup automatically if the 'notes'-layer is enabled", constr( state: SpecialVisualizationState, @@ -529,6 +542,7 @@ export default class SpecialVisualizations { defaultValue: "wikidata;wikipedia", }, ], + needsUrls: [...Wikidata.neededUrls, ...Wikipedia.neededUrls], example: "`{wikipedia()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the wikipedia page of whom the feature was named after. Also remember that these can be styled, e.g. `{wikipedia():max-height: 10rem}` to limit the height", constr: (_, tagsSource, args) => { @@ -552,6 +566,7 @@ export default class SpecialVisualizations { defaultValue: "wikidata", }, ], + needsUrls: Wikidata.neededUrls, example: "`{wikidata_label()}` is a basic example, `{wikipedia(name:etymology:wikidata)}` to show the label itself", constr: (_, tagsSource, args) => @@ -581,6 +596,7 @@ export default class SpecialVisualizations { funcName: "all_tags", docs: "Prints all key-value pairs of the object - used for debugging", args: [], + needsUrls: [], constr: (state, tags: UIEventSource) => new SvelteUIElement(AllTagsPanel, { tags, state }), }, @@ -594,6 +610,7 @@ export default class SpecialVisualizations { doc: "The keys given to the images, e.g. if image is given, the first picture URL will be added as image, the second as image:0, the third as image:1, etc... Multiple values are allowed if ';'-separated ", }, ], + needsUrls: AllImageProviders.apiUrls, constr: (state, tags, args) => { let imagePrefixes: string[] = undefined if (args.length > 0) { @@ -609,6 +626,7 @@ export default class SpecialVisualizations { { funcName: "image_upload", docs: "Creates a button where a user can upload an image to IMGUR", + needsUrls: [Imgur.apiUrl], args: [ { name: "image-key", @@ -633,6 +651,7 @@ export default class SpecialVisualizations { { funcName: "rating", docs: "Shows stars which represent the avarage rating on mangrove.reviews", + needsUrls: [MangroveReviews.ORIGINAL_API], args: [ { name: "subjectKey", @@ -670,6 +689,7 @@ export default class SpecialVisualizations { { funcName: "create_review", docs: "Invites the contributor to leave a review. Somewhat small UI-element until interacted", + needsUrls: [MangroveReviews.ORIGINAL_API], args: [ { name: "subjectKey", @@ -699,6 +719,7 @@ export default class SpecialVisualizations { { funcName: "list_reviews", docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten", + needsUrls: [MangroveReviews.ORIGINAL_API], example: "`{reviews()}` for a vanilla review, `{reviews(name, play_forest)}` to review a play forest. If a name is known, the name will be used as identifier, otherwise 'play_forest' is used", args: [ @@ -747,6 +768,7 @@ export default class SpecialVisualizations { doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__", }, ], + needsUrls: [], example: "A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`", constr: (state, tagSource: UIEventSource, args) => { @@ -759,38 +781,9 @@ export default class SpecialVisualizations { ) }, }, - { - funcName: "live", - docs: "Downloads a JSON from the given URL, e.g. '{live(example.org/data.json, shorthand:x.y.z, other:a.b.c, shorthand)}' will download the given file, will create an object {shorthand: json[x][y][z], other: json[a][b][c] out of it and will return 'other' or 'json[a][b][c]. This is made to use in combination with tags, e.g. {live({url}, {url:format}, needed_value)}", - example: - "{live({url},{url:format},hour)} {live(https://data.mobility.brussels/bike/api/counts/?request=live&featureID=CB2105,hour:data.hour_cnt;day:data.day_cnt;year:data.year_cnt,hour)}", - args: [ - { - name: "Url", - doc: "The URL to load", - required: true, - }, - { - name: "Shorthands", - doc: "A list of shorthands, of the format 'shorthandname:path.path.path'. separated by ;", - }, - { - name: "path", - doc: "The path (or shorthand) that should be returned", - }, - ], - constr: (_, tagSource: UIEventSource, args) => { - const url = args[0] - const shorthands = args[1] - const neededValue = args[2] - const source = LiveQueryHandler.FetchLiveData(url, shorthands.split(";")) - return new VariableUiElement( - source.map((data) => data[neededValue] ?? "Loading...") - ) - }, - }, { funcName: "canonical", + needsUrls: [], docs: "Converts a short, canonical value into the long, translated text including the unit. This only works if a `unit` is defined for the corresponding value. The unit specification will be included in the text. ", example: "If the object has `length=42`, then `{canonical(length)}` will be shown as **42 meter** (in english), **42 metre** (in french), ...", @@ -828,6 +821,7 @@ export default class SpecialVisualizations { funcName: "export_as_geojson", docs: "Exports the selected feature as GeoJson-file", args: [], + needsUrls: [], constr: (state, tagSource, tagsSource, feature, layer) => { const t = Translations.t.general.download @@ -857,6 +851,7 @@ export default class SpecialVisualizations { funcName: "open_in_iD", docs: "Opens the current view in the iD-editor", args: [], + needsUrls: [], constr: (state, feature) => { return new SvelteUIElement(OpenIdEditor, { mapProperties: state.mapProperties, @@ -868,6 +863,8 @@ export default class SpecialVisualizations { funcName: "open_in_josm", docs: "Opens the current view in the JOSM-editor", args: [], + needsUrls: OpenJosm.needsUrls, + constr: (state) => { return new OpenJosm(state.osmConnection, state.mapProperties.bounds) }, @@ -876,6 +873,7 @@ export default class SpecialVisualizations { funcName: "clear_location_history", docs: "A button to remove the travelled track information from the device", args: [], + needsUrls: [], constr: (state) => { return new SubtleButton( Svg.delete_icon_svg().SetStyle("height: 1.5rem"), @@ -901,6 +899,7 @@ export default class SpecialVisualizations { defaultValue: "0", }, ], + needsUrls: [Constants.osmAuthConfig.url], constr: (state, tags, args) => new VariableUiElement( tags @@ -929,6 +928,7 @@ export default class SpecialVisualizations { defaultValue: "id", }, ], + needsUrls: [Imgur.apiUrl], constr: (state, tags, args) => { const id = tags.data[args[0] ?? "id"] tags = state.featureProperties.getStore(id) @@ -939,6 +939,7 @@ export default class SpecialVisualizations { { funcName: "title", args: [], + needsUrls: [], docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'", example: "`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`.", @@ -959,6 +960,7 @@ export default class SpecialVisualizations { { funcName: "maproulette_task", args: [], + needsUrls: [Maproulette.defaultEndpoint], constr(state, tagSource) { let parentId = tagSource.data.mr_challengeId if (parentId === undefined) { @@ -1002,6 +1004,7 @@ export default class SpecialVisualizations { { funcName: "maproulette_set_status", docs: "Change the status of the given MapRoulette task", + needsUrls: [Maproulette.defaultEndpoint], example: " The following example sets the status to '2' (false positive)\n" + "\n" + @@ -1125,6 +1128,7 @@ export default class SpecialVisualizations { funcName: "statistics", docs: "Show general statistics about the elements currently in view. Intended to use on the `current_view`-layer", args: [], + needsUrls: [], constr: (state) => { return new Combine( state.layout.layers @@ -1167,6 +1171,8 @@ export default class SpecialVisualizations { required: true, }, ], + needsUrls: [], + constr(__, tags, args) { return new SvelteUIElement(SendEmail, { args, tags }) }, @@ -1194,6 +1200,7 @@ export default class SpecialVisualizations { doc: "If set, this link will act as a download-button. The contents of `href` will be offered for download; this parameter will act as the proposed filename", }, ], + needsUrls: [], constr( state: SpecialVisualizationState, tagSource: UIEventSource>, @@ -1215,6 +1222,7 @@ export default class SpecialVisualizations { { funcName: "multi", docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering", + needsUrls: [], example: "```json\n" + JSON.stringify( @@ -1275,6 +1283,7 @@ export default class SpecialVisualizations { required: true, }, ], + needsUrls: [], constr( state: SpecialVisualizationState, tagSource: UIEventSource>, diff --git a/src/UI/SubstitutedTranslation.ts b/src/UI/SubstitutedTranslation.ts index a6721532a..3aacaecb4 100644 --- a/src/UI/SubstitutedTranslation.ts +++ b/src/UI/SubstitutedTranslation.ts @@ -37,6 +37,7 @@ export class SubstitutedTranslation extends VariableUiElement { constr: typeof value === "function" ? value : () => value, docs: "Dynamically injected input element", args: [], + needsUrls: [], example: "", }) }) diff --git a/src/Utils/svgToPdf.ts b/src/Utils/svgToPdf.ts index 0ae239992..d591b9f14 100644 --- a/src/Utils/svgToPdf.ts +++ b/src/Utils/svgToPdf.ts @@ -566,6 +566,7 @@ class SvgToPdfPage { images: Record = {} rects: Record = {} readonly options: SvgToPdfOptions + public readonly status: UIEventSource private readonly importedTranslations: Record = {} private readonly layerTranslations: Record> = {} /** @@ -574,7 +575,6 @@ class SvgToPdfPage { */ private readonly _state: UIEventSource private _isPrepared = false - public readonly status: UIEventSource constructor( page: string, @@ -674,7 +674,10 @@ class SvgToPdfPage { public async PrepareLanguage(language: string) { // Always fetch the remote data - it's cached anyway this.layerTranslations[language] = await Utils.downloadJsonCached( - "https://raw.githubusercontent.com/pietervdvn/MapComplete/develop/langs/layers/" + + window.location.protocol + + "//" + + window.location.host + + "/assets/langs/layers/" + language + ".json", 24 * 60 * 60 * 1000 @@ -995,6 +998,7 @@ export interface PdfTemplateInfo { orientation: "portrait" | "landscape" isPublic: boolean } + export class SvgToPdf { public static readonly templates: Record< "flyer_a4" | "poster_a3" | "poster_a2" | "current_view_a4" | "current_view_a3", diff --git a/src/assets/editor-layer-index.json b/src/assets/editor-layer-index.json index 6b1d00bf7..f5f8b4ca4 100644 --- a/src/assets/editor-layer-index.json +++ b/src/assets/editor-layer-index.json @@ -159,7 +159,7 @@ {"properties":{"name":"Frankfurt am Main Luftbild 2017","id":"Frankfurt-am-Main-2017","url":"https://geowebdienste.frankfurt.de/OD_Luftbilder_2017?REQUEST=GetMap&VERSION=1.3.0&SERVICE=WMS&CRS={proj}&FORMAT=image/jpeg&STYLES=&bbox={bbox}&WIDTH={width}&HEIGHT={height}&LAYERS=opendata_luftbilder_2017","attribution":{"required":true,"text":"Stadtvermessungsam Frankfurt am Main","url":"https://offenedaten.frankfurt.de/dataset/wms-luftbild-2017"},"type":"wms","category":"photo"},"type":"Feature","geometry":{"coordinates":[[[8.8046,50.0111],[8.8046,50.228],[8.46726,50.228],[8.46726,50.0111],[8.8046,50.0111]]],"type":"Polygon"}}, {"properties":{"name":"Frankfurt am Main Luftbild 2018","id":"Frankfurt-am-Main-2018","url":"https://geowebdienste.frankfurt.de/OD_Luftbilder_2018?REQUEST=GetMap&VERSION=1.3.0&SERVICE=WMS&CRS={proj}&FORMAT=image/jpeg&STYLES=&bbox={bbox}&WIDTH={width}&HEIGHT={height}&LAYERS=opendata_luftbilder_2018","attribution":{"required":true,"text":"Stadtvermessungsamt Frankfurt am Main","url":"https://offenedaten.frankfurt.de/dataset/wms-luftbild-2018"},"type":"wms","category":"photo"},"type":"Feature","geometry":{"coordinates":[[[8.8046,50.0111],[8.8046,50.228],[8.46726,50.228],[8.46726,50.0111],[8.8046,50.0111]]],"type":"Polygon"}}, {"properties":{"name":"Frankfurt am Main Luftbild 2019","id":"Frankfurt-am-Main-2019","url":"https://geowebdienste.frankfurt.de/OD_Luftbilder_2019?REQUEST=GetMap&VERSION=1.3.0&SERVICE=WMS&CRS={proj}&FORMAT=image/jpeg&STYLES=&bbox={bbox}&WIDTH={width}&HEIGHT={height}&LAYERS=opendata_luftbilder_2019","attribution":{"required":true,"text":"Stadtvermessungsamt Frankfurt am Main","url":"https://www.offenedaten.frankfurt.de/dataset/wms-luftbilder-2019-frankfurt-am-main"},"type":"wms","category":"photo"},"type":"Feature","geometry":{"coordinates":[[[8.84647,50.0111],[8.84647,50.22807],[8.46726,50.22807],[8.46726,50.0111],[8.84647,50.0111]]],"type":"Polygon"}}, -{"properties":{"name":"Hamburg 20cm (HH LGV DOP20 2021)","id":"hamburg-20cm","url":"https://geodienste.hamburg.de/HH_WMS_DOP?LAYERS=DOP&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung","url":"https://www.hamburg.de/bsw/landesbetrieb-geoinformation-und-vermessung"},"type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[[9.76314,53.55521],[9.77434,53.55433],[9.77232,53.54352],[9.77352,53.52796],[9.78105,53.51838],[9.77107,53.52185],[9.76885,53.5053],[9.78203,53.49236],[9.8028,53.49383],[9.80021,53.47372],[9.80663,53.46648],[9.84872,53.44111],[9.86211,53.42942],[9.86885,53.44462],[9.89493,53.45583],[9.90436,53.45707],[9.91704,53.44664],[9.92305,53.43631],[9.90667,53.41596],[9.92552,53.41924],[9.92953,53.42007],[9.9581,53.42708],[9.97873,53.4142],[9.98243,53.41478],[9.99754,53.42546],[10.02294,53.43228],[10.01449,53.44203],[10.03517,53.4469],[10.05155,53.46394],[10.07581,53.45436],[10.1068,53.42658],[10.10949,53.42649],[10.14506,53.41614],[10.16555,53.39933],[10.24155,53.39797],[10.24578,53.40261],[10.25089,53.41024],[10.25598,53.41623],[10.30799,53.43332],[10.32514,53.44979],[10.31223,53.45229],[10.30962,53.44309],[10.29043,53.45512],[10.26592,53.47079],[10.25008,53.47898],[10.2367,53.49629],[10.21828,53.49923],[10.21043,53.51996],[10.18951,53.51148],[10.16919,53.51965],[10.16611,53.52013],[10.16327,53.52185],[10.16874,53.5374],[10.15465,53.53657],[10.15189,53.5417],[10.15942,53.56091],[10.15308,53.56242],[10.148,53.5639],[10.15067,53.56973],[10.15169,53.57619],[10.20117,53.58392],[10.19236,53.59474],[10.18887,53.61316],[10.22202,53.63349],[10.18973,53.63838],[10.19885,53.64675],[10.17153,53.66869],[10.14955,53.67545],[10.14643,53.67588],[10.14473,53.67613],[10.14176,53.67744],[10.14342,53.68057],[10.15829,53.68944],[10.15694,53.70451],[10.1779,53.70992],[10.19369,53.731],[10.16939,53.73896],[10.11908,53.71324],[10.08198,53.72044],[10.0707,53.70996],[10.071,53.69585],[10.0604,53.68833],[10.06925,53.67955],[10.05148,53.67759],[10.04338,53.68198],[10.02282,53.68157],[9.9996,53.68153],[9.98739,53.65072],[9.98492,53.6483],[9.97795,53.64887],[9.95155,53.65065],[9.95024,53.65085],[9.94552,53.65276],[9.93115,53.65262],[9.90678,53.65231],[9.89688,53.63492],[9.89637,53.63122],[9.89356,53.63026],[9.88697,53.6252],[9.88505,53.62199],[9.86931,53.61323],[9.86814,53.6093],[9.85416,53.59805],[9.84498,53.59498],[9.83773,53.59198],[9.81817,53.58591],[9.78993,53.60386],[9.79634,53.6103],[9.7707,53.61607],[9.77129,53.63131],[9.75793,53.61828],[9.73465,53.56536],[9.73047,53.55787],[9.76314,53.55521]]],[[[8.5275,53.90941],[8.52792,53.93577],[8.4826,53.9356],[8.48274,53.90924],[8.5275,53.90941]]]],"type":"MultiPolygon"}}, +{"properties":{"name":"Hamburg 20cm (HH LGV DOP20 2022)","id":"hamburg-20cm","url":"https://geodienste.hamburg.de/HH_WMS_DOP?LAYERS=DOP&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung","url":"https://www.hamburg.de/bsw/landesbetrieb-geoinformation-und-vermessung"},"type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[[9.76314,53.55521],[9.77434,53.55433],[9.77232,53.54352],[9.77352,53.52796],[9.78105,53.51838],[9.77107,53.52185],[9.76885,53.5053],[9.78203,53.49236],[9.8028,53.49383],[9.80021,53.47372],[9.80663,53.46648],[9.84872,53.44111],[9.86211,53.42942],[9.86885,53.44462],[9.89493,53.45583],[9.90436,53.45707],[9.91704,53.44664],[9.92305,53.43631],[9.90667,53.41596],[9.92552,53.41924],[9.92953,53.42007],[9.9581,53.42708],[9.97873,53.4142],[9.98243,53.41478],[9.99754,53.42546],[10.02294,53.43228],[10.01449,53.44203],[10.03517,53.4469],[10.05155,53.46394],[10.07581,53.45436],[10.1068,53.42658],[10.10949,53.42649],[10.14506,53.41614],[10.16555,53.39933],[10.24155,53.39797],[10.24578,53.40261],[10.25089,53.41024],[10.25598,53.41623],[10.30799,53.43332],[10.32514,53.44979],[10.31223,53.45229],[10.30962,53.44309],[10.29043,53.45512],[10.26592,53.47079],[10.25008,53.47898],[10.2367,53.49629],[10.21828,53.49923],[10.21043,53.51996],[10.18951,53.51148],[10.16919,53.51965],[10.16611,53.52013],[10.16327,53.52185],[10.16874,53.5374],[10.15465,53.53657],[10.15189,53.5417],[10.15942,53.56091],[10.15308,53.56242],[10.148,53.5639],[10.15067,53.56973],[10.15169,53.57619],[10.20117,53.58392],[10.19236,53.59474],[10.18887,53.61316],[10.22202,53.63349],[10.18973,53.63838],[10.19885,53.64675],[10.17153,53.66869],[10.14955,53.67545],[10.14643,53.67588],[10.14473,53.67613],[10.14176,53.67744],[10.14342,53.68057],[10.15829,53.68944],[10.15694,53.70451],[10.1779,53.70992],[10.19369,53.731],[10.16939,53.73896],[10.11908,53.71324],[10.08198,53.72044],[10.0707,53.70996],[10.071,53.69585],[10.0604,53.68833],[10.06925,53.67955],[10.05148,53.67759],[10.04338,53.68198],[10.02282,53.68157],[9.9996,53.68153],[9.98739,53.65072],[9.98492,53.6483],[9.97795,53.64887],[9.95155,53.65065],[9.95024,53.65085],[9.94552,53.65276],[9.93115,53.65262],[9.90678,53.65231],[9.89688,53.63492],[9.89637,53.63122],[9.89356,53.63026],[9.88697,53.6252],[9.88505,53.62199],[9.86931,53.61323],[9.86814,53.6093],[9.85416,53.59805],[9.84498,53.59498],[9.83773,53.59198],[9.81817,53.58591],[9.78993,53.60386],[9.79634,53.6103],[9.7707,53.61607],[9.77129,53.63131],[9.75793,53.61828],[9.73465,53.56536],[9.73047,53.55787],[9.76314,53.55521]]],[[[8.5275,53.90941],[8.52792,53.93577],[8.4826,53.9356],[8.48274,53.90924],[8.5275,53.90941]]]],"type":"MultiPolygon"}}, {"properties":{"name":"Hamburg DK5 (HH LGV DK5 2021)","id":"Hamburg-DK5","url":"https://geodienste.hamburg.de/HH_WMS_DK5?LAYERS=DK5&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/png&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Freie und Hansestadt Hamburg, Landesbetrieb Geoinformation und Vermessung","url":"https://www.hamburg.de/bsw/landesbetrieb-geoinformation-und-vermessung"},"type":"wms","category":"map"},"type":"Feature","geometry":{"coordinates":[[[[9.76314,53.55521],[9.77434,53.55433],[9.77232,53.54352],[9.77352,53.52796],[9.78105,53.51838],[9.77107,53.52185],[9.76885,53.5053],[9.78203,53.49236],[9.8028,53.49383],[9.80021,53.47372],[9.80663,53.46648],[9.84872,53.44111],[9.86211,53.42942],[9.86885,53.44462],[9.89493,53.45583],[9.90436,53.45707],[9.91704,53.44664],[9.92305,53.43631],[9.90667,53.41596],[9.92552,53.41924],[9.92953,53.42007],[9.9581,53.42708],[9.97873,53.4142],[9.98243,53.41478],[9.99754,53.42546],[10.02294,53.43228],[10.01449,53.44203],[10.03517,53.4469],[10.05155,53.46394],[10.07581,53.45436],[10.1068,53.42658],[10.10949,53.42649],[10.14506,53.41614],[10.16555,53.39933],[10.24155,53.39797],[10.24578,53.40261],[10.25089,53.41024],[10.25598,53.41623],[10.30799,53.43332],[10.32514,53.44979],[10.31223,53.45229],[10.30962,53.44309],[10.29043,53.45512],[10.26592,53.47079],[10.25008,53.47898],[10.2367,53.49629],[10.21828,53.49923],[10.21043,53.51996],[10.18951,53.51148],[10.16919,53.51965],[10.16611,53.52013],[10.16327,53.52185],[10.16874,53.5374],[10.15465,53.53657],[10.15189,53.5417],[10.15942,53.56091],[10.15308,53.56242],[10.148,53.5639],[10.15067,53.56973],[10.15169,53.57619],[10.20117,53.58392],[10.19236,53.59474],[10.18887,53.61316],[10.22202,53.63349],[10.18973,53.63838],[10.19885,53.64675],[10.17153,53.66869],[10.14955,53.67545],[10.14643,53.67588],[10.14473,53.67613],[10.14176,53.67744],[10.14342,53.68057],[10.15829,53.68944],[10.15694,53.70451],[10.1779,53.70992],[10.19369,53.731],[10.16939,53.73896],[10.11908,53.71324],[10.08198,53.72044],[10.0707,53.70996],[10.071,53.69585],[10.0604,53.68833],[10.06925,53.67955],[10.05148,53.67759],[10.04338,53.68198],[10.02282,53.68157],[9.9996,53.68153],[9.98739,53.65072],[9.98492,53.6483],[9.97795,53.64887],[9.95155,53.65065],[9.95024,53.65085],[9.94552,53.65276],[9.93115,53.65262],[9.90678,53.65231],[9.89688,53.63492],[9.89637,53.63122],[9.89356,53.63026],[9.88697,53.6252],[9.88505,53.62199],[9.86931,53.61323],[9.86814,53.6093],[9.85416,53.59805],[9.84498,53.59498],[9.83773,53.59198],[9.81817,53.58591],[9.78993,53.60386],[9.79634,53.6103],[9.7707,53.61607],[9.77129,53.63131],[9.75793,53.61828],[9.73465,53.56536],[9.73047,53.55787],[9.76314,53.55521]]],[[[8.5275,53.90941],[8.52792,53.93577],[8.4826,53.9356],[8.48274,53.90924],[8.5275,53.90941]]]],"type":"MultiPolygon"}}, {"properties":{"name":"Hesse DOP20","id":"Hessen-DOP20","url":"https://www.gds-srv.hessen.de/cgi-bin/lika-services/ogc-free-images.ows?LAYERS=he_dop20_rgb&STYLES=default&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Geobasisdaten © Hessische Verwaltung für Bodenmanagement und Geoinformation: Digitale Orthophotos","url":"https://hvbg.hessen.de"},"type":"wms","category":"photo","min_zoom":16,"max_zoom":19,"best":true},"type":"Feature","geometry":{"coordinates":[[[9.04158,49.49511],[9.06561,49.52721],[9.10595,49.5076],[9.1341,49.51406],[9.10847,49.55019],[9.10767,49.58467],[9.0699,49.61838],[9.10526,49.64118],[9.11041,49.66074],[9.09359,49.69184],[9.13101,49.69695],[9.15642,49.74379],[9.13204,49.79711],[9.08973,49.80149],[9.09067,49.8485],[9.05617,49.85514],[9.03935,49.89309],[9.03265,49.92487],[9.02269,49.98269],[9.05428,49.98986],[9.03308,50.04016],[9.02484,50.04523],[9.00604,50.04722],[8.98261,50.04402],[8.98827,50.06067],[9.00132,50.06937],[9.00578,50.09856],[9.02312,50.11117],[9.0778,50.11463],[9.11522,50.12498],[9.14749,50.10913],[9.14011,50.0946],[9.15316,50.08568],[9.21598,50.14578],[9.37992,50.11552],[9.40361,50.07918],[9.51897,50.08975],[9.53476,50.15887],[9.50798,50.22788],[9.59063,50.21334],[9.65458,50.22481],[9.6738,50.23996],[9.64943,50.25928],[9.71432,50.28133],[9.75054,50.30842],[9.74796,50.36101],[9.76925,50.41989],[9.86506,50.39054],[9.96288,50.41967],[10.04494,50.48351],[10.0458,50.51648],[10.06554,50.55642],[10.04734,50.61375],[10.09369,50.61985],[10.06004,50.67688],[9.9955,50.67753],[9.9519,50.66927],[9.94125,50.66383],[9.96082,50.64097],[9.9416,50.6303],[9.91276,50.64075],[9.88873,50.64271],[9.88564,50.67057],[9.92615,50.69407],[9.91722,50.70907],[9.93851,50.72276],[9.94572,50.75058],[9.9531,50.77197],[9.95619,50.78239],[9.96714,50.82389],[10.0288,50.82589],[10.06536,50.88874],[10.02485,50.91829],[9.98863,50.92457],[10.00151,50.93355],[10.05266,50.93636],[10.06983,50.94383],[10.04373,50.96891],[10.02399,50.98091],[10.04442,51.01116],[10.1433,50.99237],[10.20081,50.99766],[10.2202,51.02714],[10.15755,51.06923],[10.14999,51.08649],[10.2111,51.11613],[10.21505,51.16363],[10.24595,51.18462],[10.18587,51.20817],[10.14107,51.22151],[10.07378,51.23032],[10.08236,51.24354],[10.06176,51.27921],[10.00374,51.28941],[9.95567,51.3083],[9.94898,51.32948],[9.93318,51.35045],[9.9337,51.37778],[9.93387,51.39235],[9.90624,51.42201],[9.79414,51.40981],[9.78744,51.39305],[9.692,51.36749],[9.73509,51.3186],[9.73148,51.29756],[9.67209,51.31946],[9.58214,51.34359],[9.56806,51.3438],[9.5545,51.35892],[9.56944,51.36085],[9.58059,51.37242],[9.56102,51.37741],[9.58162,51.39797],[9.59338,51.3969],[9.63269,51.40295],[9.64514,51.41725],[9.63183,51.45786],[9.64926,51.4678],[9.58969,51.51911],[9.61561,51.51985],[9.62917,51.54666],[9.65046,51.54922],[9.68702,51.56555],[9.69595,51.57792],[9.64102,51.6178],[9.63261,51.63848],[9.61326,51.63526],[9.56755,51.62697],[9.55484,51.64039],[9.54111,51.64157],[9.52429,51.62803],[9.50523,51.62899],[9.49932,51.65916],[9.4388,51.65169],[9.42404,51.63144],[9.36996,51.62803],[9.33134,51.61471],[9.363,51.58944],[9.31383,51.55242],[9.30406,51.51885],[9.27864,51.51526],[9.22045,51.49229],[9.20843,51.46417],[9.17959,51.46941],[9.14852,51.44331],[9.1408,51.45187],[9.09205,51.44737],[9.09428,51.49592],[9.07883,51.50554],[9.03471,51.50778],[9.02063,51.52092],[8.89441,51.48841],[8.88794,51.48149],[8.89333,51.4683],[8.90854,51.46128],[8.91695,51.42876],[8.94249,51.4274],[8.93532,51.39353],[8.88811,51.39492],[8.85498,51.37917],[8.83644,51.39096],[8.79009,51.3931],[8.73516,51.37489],[8.69997,51.3795],[8.67422,51.3721],[8.60899,51.33211],[8.552,51.27846],[8.58976,51.24655],[8.61191,51.24451],[8.6404,51.26063],[8.71765,51.27094],[8.74924,51.17891],[8.69053,51.1366],[8.69018,51.11161],[8.65101,51.09641],[8.61362,51.10363],[8.54238,51.1087],[8.49775,51.08067],[8.49964,51.07387],[8.52144,51.06395],[8.51269,51.05273],[8.50016,51.04075],[8.52728,51.01689],[8.51149,51.00997],[8.45604,50.96675],[8.4478,50.94069],[8.4514,50.9184],[8.43321,50.91959],[8.38652,50.89264],[8.35939,50.86729],[8.30738,50.86513],[8.29382,50.88441],[8.26996,50.88441],[8.1225,50.78814],[8.13563,50.76187],[8.16207,50.73656],[8.13864,50.6957],[8.13023,50.69787],[8.10843,50.67569],[8.10774,50.65316],[8.13452,50.63074],[8.1225,50.60721],[8.151,50.5973],[8.14739,50.58847],[8.10224,50.53591],[8.0377,50.56143],[7.98088,50.5119],[7.97539,50.48176],[8.00646,50.45477],[7.97419,50.43805],[7.95942,50.40928],[7.99084,50.3967],[8.01439,50.39177],[8.01092,50.3816],[8.06053,50.36704],[8.06929,50.33012],[8.10207,50.32135],[8.10053,50.30831],[8.11684,50.27947],[8.1031,50.26466],[8.06122,50.27793],[8.03272,50.27124],[8.01865,50.25741],[8.04989,50.23304],[8.0286,50.22019],[7.99187,50.2415],[7.90381,50.2024],[7.88168,50.18169],[7.88218,50.16656],[7.92355,50.14171],[7.88664,50.11827],[7.86106,50.13147],[7.84469,50.12599],[7.82021,50.08479],[7.80287,50.08645],[7.76802,50.06579],[7.77154,50.05113],[7.85231,50.00548],[7.86209,49.97794],[7.87805,49.97005],[7.89359,49.96856],[7.96363,49.96972],[7.99393,49.97872],[8.04963,50.00178],[8.08285,50.00493],[8.13151,50.0153],[8.15615,50.02528],[8.18722,50.0325],[8.23846,50.02324],[8.26798,50.00917],[8.28215,49.99422],[8.3236,49.96806],[8.34652,49.91509],[8.33605,49.887],[8.3418,49.87168],[8.35218,49.86322],[8.37853,49.85608],[8.37313,49.82392],[8.41364,49.76996],[8.42866,49.76364],[8.47447,49.75984],[8.45467,49.74623],[8.43424,49.7247],[8.37776,49.70961],[8.35304,49.69595],[8.35115,49.6799],[8.36274,49.66002],[8.37467,49.62967],[8.38568,49.61717],[8.40617,49.6037],[8.41776,49.58312],[8.47097,49.58223],[8.48162,49.57354],[8.52333,49.54816],[8.53071,49.53602],[8.54101,49.52599],[8.57534,49.51496],[8.60882,49.52777],[8.62341,49.54181],[8.60539,49.61093],[8.66856,49.62027],[8.68401,49.55985],[8.70152,49.53412],[8.72692,49.51674],[8.81275,49.5057],[8.83352,49.48853],[8.82511,49.47136],[8.79919,49.41075],[8.80297,49.40103],[8.81876,49.39388],[8.85395,49.39321],[8.95351,49.45462],[8.96021,49.49968],[9.04158,49.49511]]],"type":"Polygon"}}, {"properties":{"name":"Mainz all aerial imageries","id":"mainzallaerialimageries","url":"https://gint.mainz.de/gint1-cgi/mapserv?map=/data/mapbender-int/umn-www/client/a62/luftbild.map","attribution":{"required":true,"text":"Vermessung und Geoinformation Mainz","url":"https://www.mainz.de/vv/oe/100140100000035141.php#tab-infos"},"type":"wms_endpoint","category":"photo","min_zoom":17},"type":"Feature","geometry":{"coordinates":[[[8.10355,49.865],[8.38356,49.865],[8.38356,50.0466],[8.10355,50.0466],[8.10355,49.865]]],"type":"Polygon"}}, @@ -169,7 +169,9 @@ {"properties":{"name":"Munich latest aerial imagery 60cm","id":"MunichLatestAerialImagery","url":"https://ogc.muenchen.de/wms/opendata_luftbild?LAYERS=bgl0&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Datenquelle: dl-de/by-2-0: Landeshauptstadt München – Kommunalreferat – GeodatenService – www.geodatenservice-muenchen.de","url":"https://www.muenchen.de/rathaus/Stadtverwaltung/Kommunalreferat/geodatenservice.html"},"type":"wms","category":"historicphoto","min_zoom":11},"type":"Feature","geometry":{"coordinates":[[[11.48878,48.053],[11.55589,48.05323],[11.55583,48.06224],[11.56915,48.06229],[11.56908,48.07128],[11.64986,48.07155],[11.64993,48.06256],[11.70326,48.06274],[11.70313,48.08074],[11.71673,48.08079],[11.71667,48.08968],[11.7299,48.08972],[11.72963,48.12566],[11.74313,48.1257],[11.74292,48.15276],[11.72943,48.15271],[11.72936,48.16152],[11.71612,48.16147],[11.71592,48.18859],[11.7027,48.18855],[11.70263,48.19752],[11.67558,48.19743],[11.67537,48.22446],[11.66176,48.22441],[11.66169,48.23355],[11.64863,48.2335],[11.64857,48.24246],[11.54064,48.2421],[11.54058,48.25093],[11.52735,48.25088],[11.52728,48.26001],[11.47335,48.25983],[11.47356,48.23291],[11.46014,48.23287],[11.46021,48.22373],[11.43336,48.22364],[11.43343,48.21439],[11.3798,48.21421],[11.37987,48.20518],[11.36607,48.20514],[11.36621,48.18741],[11.35259,48.18737],[11.35266,48.17817],[11.33946,48.17813],[11.33973,48.14216],[11.36684,48.14225],[11.36697,48.12443],[11.38083,48.12448],[11.3809,48.11558],[11.44769,48.1158],[11.44804,48.07087],[11.46186,48.07091],[11.46193,48.06193],[11.48872,48.06202],[11.48878,48.053]]],"type":"Polygon"}}, {"properties":{"name":"NRW Liegenschaftskataster","id":"nrw_alkis_wms","url":"https://www.wms.nrw.de/geobasis/wms_nw_alkis?LAYERS=adv_alkis_tatsaechliche_nutzung,adv_alkis_gewaesser,adv_alkis_vegetation,adv_alkis_verkehr,adv_alkis_siedlung,adv_alkis_gesetzl_festlegungen,adv_alkis_bodensch,adv_alkis_oeff_rechtl_sonst_festl,adv_alkis_weiteres,adv_alkis_bauw_einricht,adv_alkis_gebaeude,adv_alkis_flurstuecke&FORMAT=image/png&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","type":"wms","category":"map","best":true},"type":"Feature","geometry":{"coordinates":[[[6.48,50.32],[6.88,50.32],[6.88,50.4],[6.96,50.4],[6.96,50.48],[7.04,50.48],[7.04,50.56],[7.36,50.56],[7.36,50.64],[7.52,50.64],[7.52,50.72],[7.76,50.72],[7.76,50.8],[7.92,50.8],[7.92,50.72],[8,50.72],[8,50.64],[8.24,50.64],[8.24,50.8],[8.4,50.8],[8.4,50.88],[8.48,50.88],[8.48,50.96],[8.56,50.96],[8.56,51.04],[8.8,51.04],[8.8,51.28],[8.72,51.28],[8.72,51.36],[8.96,51.36],[8.96,51.44],[9.04,51.44],[9.04,51.36],[9.2,51.36],[9.2,51.44],[9.36,51.44],[9.36,51.52],[9.44,51.52],[9.44,51.6],[9.52,51.6],[9.52,51.68],[9.44,51.68],[9.44,51.76],[9.52,51.76],[9.52,51.92],[9.36,51.92],[9.36,52],[9.28,52],[9.28,52.08],[9.2,52.08],[9.2,52.16],[9.12,52.16],[9.12,52.32],[9.2,52.32],[9.2,52.56],[8.96,52.56],[8.96,52.48],[8.72,52.48],[8.72,52.56],[8.4,52.56],[8.4,52.48],[8.24,52.48],[8.24,52.32],[8.4,52.32],[8.4,52.16],[8.08,52.16],[8.08,52.24],[8,52.24],[8,52.4],[7.76,52.4],[7.76,52.48],[7.52,52.48],[7.52,52.4],[7.36,52.4],[7.36,52.32],[7.04,52.32],[7.04,52.24],[6.8,52.24],[6.8,52.16],[6.72,52.16],[6.72,52.08],[6.64,52.08],[6.64,51.92],[5.92,51.92],[5.92,51.68],[6,51.68],[6,51.6],[6.08,51.6],[6.08,51.52],[6.16,51.52],[6.16,51.36],[6.08,51.36],[6.08,51.28],[6,51.28],[6,51.12],[5.84,51.12],[5.84,50.96],[6,50.96],[6,50.88],[5.92,50.88],[5.92,50.72],[6,50.72],[6,50.64],[6.16,50.64],[6.16,50.48],[6.32,50.48],[6.32,50.32],[6.4,50.32],[6.4,50.24],[6.48,50.24],[6.48,50.32]]],"type":"Polygon"}}, {"properties":{"name":"NRW DTM Hillshade","id":"nrw_dtm_wms","url":"https://www.wms.nrw.de/geobasis/wms_nw_dgm-schummerung?LAYERS=nw_dgm-schummerung_pan&STYLES=default&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","type":"wms","category":"elevation"},"type":"Feature","geometry":{"coordinates":[[[6.48,50.32],[6.88,50.32],[6.88,50.4],[6.96,50.4],[6.96,50.48],[7.04,50.48],[7.04,50.56],[7.36,50.56],[7.36,50.64],[7.52,50.64],[7.52,50.72],[7.76,50.72],[7.76,50.8],[7.92,50.8],[7.92,50.72],[8,50.72],[8,50.64],[8.24,50.64],[8.24,50.8],[8.4,50.8],[8.4,50.88],[8.48,50.88],[8.48,50.96],[8.56,50.96],[8.56,51.04],[8.8,51.04],[8.8,51.28],[8.72,51.28],[8.72,51.36],[8.96,51.36],[8.96,51.44],[9.04,51.44],[9.04,51.36],[9.2,51.36],[9.2,51.44],[9.36,51.44],[9.36,51.52],[9.44,51.52],[9.44,51.6],[9.52,51.6],[9.52,51.68],[9.44,51.68],[9.44,51.76],[9.52,51.76],[9.52,51.92],[9.36,51.92],[9.36,52],[9.28,52],[9.28,52.08],[9.2,52.08],[9.2,52.16],[9.12,52.16],[9.12,52.32],[9.2,52.32],[9.2,52.56],[8.96,52.56],[8.96,52.48],[8.72,52.48],[8.72,52.56],[8.4,52.56],[8.4,52.48],[8.24,52.48],[8.24,52.32],[8.4,52.32],[8.4,52.16],[8.08,52.16],[8.08,52.24],[8,52.24],[8,52.4],[7.76,52.4],[7.76,52.48],[7.52,52.48],[7.52,52.4],[7.36,52.4],[7.36,52.32],[7.04,52.32],[7.04,52.24],[6.8,52.24],[6.8,52.16],[6.72,52.16],[6.72,52.08],[6.64,52.08],[6.64,51.92],[5.92,51.92],[5.92,51.68],[6,51.68],[6,51.6],[6.08,51.6],[6.08,51.52],[6.16,51.52],[6.16,51.36],[6.08,51.36],[6.08,51.28],[6,51.28],[6,51.12],[5.84,51.12],[5.84,50.96],[6,50.96],[6,50.88],[5.92,50.88],[5.92,50.72],[6,50.72],[6,50.64],[6.16,50.64],[6.16,50.48],[6.32,50.48],[6.32,50.32],[6.4,50.32],[6.4,50.24],[6.48,50.24],[6.48,50.32]]],"type":"Polygon"}}, -{"properties":{"name":"NRW Orthophoto","id":"nrw_ortho_wms","url":"https://www.wms.nrw.de/geobasis/wms_nw_dop?LAYERS=nw_dop_rgb&STYLES=default&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[6.48,50.32],[6.88,50.32],[6.88,50.4],[6.96,50.4],[6.96,50.48],[7.04,50.48],[7.04,50.56],[7.36,50.56],[7.36,50.64],[7.52,50.64],[7.52,50.72],[7.76,50.72],[7.76,50.8],[7.92,50.8],[7.92,50.72],[8,50.72],[8,50.64],[8.24,50.64],[8.24,50.8],[8.4,50.8],[8.4,50.88],[8.48,50.88],[8.48,50.96],[8.56,50.96],[8.56,51.04],[8.8,51.04],[8.8,51.28],[8.72,51.28],[8.72,51.36],[8.96,51.36],[8.96,51.44],[9.04,51.44],[9.04,51.36],[9.2,51.36],[9.2,51.44],[9.36,51.44],[9.36,51.52],[9.44,51.52],[9.44,51.6],[9.52,51.6],[9.52,51.68],[9.44,51.68],[9.44,51.76],[9.52,51.76],[9.52,51.92],[9.36,51.92],[9.36,52],[9.28,52],[9.28,52.08],[9.2,52.08],[9.2,52.16],[9.12,52.16],[9.12,52.32],[9.2,52.32],[9.2,52.56],[8.96,52.56],[8.96,52.48],[8.72,52.48],[8.72,52.56],[8.4,52.56],[8.4,52.48],[8.24,52.48],[8.24,52.32],[8.4,52.32],[8.4,52.16],[8.08,52.16],[8.08,52.24],[8,52.24],[8,52.4],[7.76,52.4],[7.76,52.48],[7.52,52.48],[7.52,52.4],[7.36,52.4],[7.36,52.32],[7.04,52.32],[7.04,52.24],[6.8,52.24],[6.8,52.16],[6.72,52.16],[6.72,52.08],[6.64,52.08],[6.64,51.92],[5.92,51.92],[5.92,51.68],[6,51.68],[6,51.6],[6.08,51.6],[6.08,51.52],[6.16,51.52],[6.16,51.36],[6.08,51.36],[6.08,51.28],[6,51.28],[6,51.12],[5.84,51.12],[5.84,50.96],[6,50.96],[6,50.88],[5.92,50.88],[5.92,50.72],[6,50.72],[6,50.64],[6.16,50.64],[6.16,50.48],[6.32,50.48],[6.32,50.32],[6.4,50.32],[6.4,50.24],[6.48,50.24],[6.48,50.32]]],"type":"Polygon"}}, +{"properties":{"name":"NRW iDOP","id":"nrw_idop_wms","url":"https://www.wms.nrw.de/geobasis/wms_nw_idop?LAYERS=nw_idop_rgb&STYLES=default&FORMAT=image/png&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","type":"wms","category":"photo"},"type":"Feature","geometry":{"coordinates":[[[6.48,50.32],[6.88,50.32],[6.88,50.4],[6.96,50.4],[6.96,50.48],[7.04,50.48],[7.04,50.56],[7.36,50.56],[7.36,50.64],[7.52,50.64],[7.52,50.72],[7.76,50.72],[7.76,50.8],[7.92,50.8],[7.92,50.72],[8,50.72],[8,50.64],[8.24,50.64],[8.24,50.8],[8.4,50.8],[8.4,50.88],[8.48,50.88],[8.48,50.96],[8.56,50.96],[8.56,51.04],[8.8,51.04],[8.8,51.28],[8.72,51.28],[8.72,51.36],[8.96,51.36],[8.96,51.44],[9.04,51.44],[9.04,51.36],[9.2,51.36],[9.2,51.44],[9.36,51.44],[9.36,51.52],[9.44,51.52],[9.44,51.6],[9.52,51.6],[9.52,51.68],[9.44,51.68],[9.44,51.76],[9.52,51.76],[9.52,51.92],[9.36,51.92],[9.36,52],[9.28,52],[9.28,52.08],[9.2,52.08],[9.2,52.16],[9.12,52.16],[9.12,52.32],[9.2,52.32],[9.2,52.56],[8.96,52.56],[8.96,52.48],[8.72,52.48],[8.72,52.56],[8.4,52.56],[8.4,52.48],[8.24,52.48],[8.24,52.32],[8.4,52.32],[8.4,52.16],[8.08,52.16],[8.08,52.24],[8,52.24],[8,52.4],[7.76,52.4],[7.76,52.48],[7.52,52.48],[7.52,52.4],[7.36,52.4],[7.36,52.32],[7.04,52.32],[7.04,52.24],[6.8,52.24],[6.8,52.16],[6.72,52.16],[6.72,52.08],[6.64,52.08],[6.64,51.92],[5.92,51.92],[5.92,51.68],[6,51.68],[6,51.6],[6.08,51.6],[6.08,51.52],[6.16,51.52],[6.16,51.36],[6.08,51.36],[6.08,51.28],[6,51.28],[6,51.12],[5.84,51.12],[5.84,50.96],[6,50.96],[6,50.88],[5.92,50.88],[5.92,50.72],[6,50.72],[6,50.64],[6.16,50.64],[6.16,50.48],[6.32,50.48],[6.32,50.32],[6.4,50.32],[6.4,50.24],[6.48,50.24],[6.48,50.32]]],"type":"Polygon"}}, +{"properties":{"name":"NRW Orthophoto","id":"nrw_ortho_wms","url":"https://www.wms.nrw.de/geobasis/wms_nw_dop?LAYERS=nw_dop_rgb&STYLES=default&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","type":"wms","category":"photo"},"type":"Feature","geometry":{"coordinates":[[[6.48,50.32],[6.88,50.32],[6.88,50.4],[6.96,50.4],[6.96,50.48],[7.04,50.48],[7.04,50.56],[7.36,50.56],[7.36,50.64],[7.52,50.64],[7.52,50.72],[7.76,50.72],[7.76,50.8],[7.92,50.8],[7.92,50.72],[8,50.72],[8,50.64],[8.24,50.64],[8.24,50.8],[8.4,50.8],[8.4,50.88],[8.48,50.88],[8.48,50.96],[8.56,50.96],[8.56,51.04],[8.8,51.04],[8.8,51.28],[8.72,51.28],[8.72,51.36],[8.96,51.36],[8.96,51.44],[9.04,51.44],[9.04,51.36],[9.2,51.36],[9.2,51.44],[9.36,51.44],[9.36,51.52],[9.44,51.52],[9.44,51.6],[9.52,51.6],[9.52,51.68],[9.44,51.68],[9.44,51.76],[9.52,51.76],[9.52,51.92],[9.36,51.92],[9.36,52],[9.28,52],[9.28,52.08],[9.2,52.08],[9.2,52.16],[9.12,52.16],[9.12,52.32],[9.2,52.32],[9.2,52.56],[8.96,52.56],[8.96,52.48],[8.72,52.48],[8.72,52.56],[8.4,52.56],[8.4,52.48],[8.24,52.48],[8.24,52.32],[8.4,52.32],[8.4,52.16],[8.08,52.16],[8.08,52.24],[8,52.24],[8,52.4],[7.76,52.4],[7.76,52.48],[7.52,52.48],[7.52,52.4],[7.36,52.4],[7.36,52.32],[7.04,52.32],[7.04,52.24],[6.8,52.24],[6.8,52.16],[6.72,52.16],[6.72,52.08],[6.64,52.08],[6.64,51.92],[5.92,51.92],[5.92,51.68],[6,51.68],[6,51.6],[6.08,51.6],[6.08,51.52],[6.16,51.52],[6.16,51.36],[6.08,51.36],[6.08,51.28],[6,51.28],[6,51.12],[5.84,51.12],[5.84,50.96],[6,50.96],[6,50.88],[5.92,50.88],[5.92,50.72],[6,50.72],[6,50.64],[6.16,50.64],[6.16,50.48],[6.32,50.48],[6.32,50.32],[6.4,50.32],[6.4,50.24],[6.48,50.24],[6.48,50.32]]],"type":"Polygon"}}, +{"properties":{"name":"NRW vDOP","id":"nrw_vdop_wms","url":"https://www.wms.nrw.de/geobasis/wms_nw_vdop?LAYERS=nw_vdop_rgb&STYLES=default&FORMAT=image/png&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[6.48,50.32],[6.88,50.32],[6.88,50.4],[6.96,50.4],[6.96,50.48],[7.04,50.48],[7.04,50.56],[7.36,50.56],[7.36,50.64],[7.52,50.64],[7.52,50.72],[7.76,50.72],[7.76,50.8],[7.92,50.8],[7.92,50.72],[8,50.72],[8,50.64],[8.24,50.64],[8.24,50.8],[8.4,50.8],[8.4,50.88],[8.48,50.88],[8.48,50.96],[8.56,50.96],[8.56,51.04],[8.8,51.04],[8.8,51.28],[8.72,51.28],[8.72,51.36],[8.96,51.36],[8.96,51.44],[9.04,51.44],[9.04,51.36],[9.2,51.36],[9.2,51.44],[9.36,51.44],[9.36,51.52],[9.44,51.52],[9.44,51.6],[9.52,51.6],[9.52,51.68],[9.44,51.68],[9.44,51.76],[9.52,51.76],[9.52,51.92],[9.36,51.92],[9.36,52],[9.28,52],[9.28,52.08],[9.2,52.08],[9.2,52.16],[9.12,52.16],[9.12,52.32],[9.2,52.32],[9.2,52.56],[8.96,52.56],[8.96,52.48],[8.72,52.48],[8.72,52.56],[8.4,52.56],[8.4,52.48],[8.24,52.48],[8.24,52.32],[8.4,52.32],[8.4,52.16],[8.08,52.16],[8.08,52.24],[8,52.24],[8,52.4],[7.76,52.4],[7.76,52.48],[7.52,52.48],[7.52,52.4],[7.36,52.4],[7.36,52.32],[7.04,52.32],[7.04,52.24],[6.8,52.24],[6.8,52.16],[6.72,52.16],[6.72,52.08],[6.64,52.08],[6.64,51.92],[5.92,51.92],[5.92,51.68],[6,51.68],[6,51.6],[6.08,51.6],[6.08,51.52],[6.16,51.52],[6.16,51.36],[6.08,51.36],[6.08,51.28],[6,51.28],[6,51.12],[5.84,51.12],[5.84,50.96],[6,50.96],[6,50.88],[5.92,50.88],[5.92,50.72],[6,50.72],[6,50.64],[6.16,50.64],[6.16,50.48],[6.32,50.48],[6.32,50.32],[6.4,50.32],[6.4,50.24],[6.48,50.24],[6.48,50.32]]],"type":"Polygon"}}, {"properties":{"name":"Saxony latest aerial imagery","id":"GEOSN-DOP-RGB","url":"https://geodienste.sachsen.de/wms_geosn_dop-rgb/guest?LAYERS=sn_dop_020&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Staatsbetrieb Geobasisinformation und Vermessung Sachsen","url":"https://geoportal.sachsen.de/cps/metadaten_portal.html?id=cd01c334-7e32-482f-bd43-af286707178a"},"type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[13.54901,50.69792],[13.84251,50.71126],[13.93239,50.74504],[14.04614,50.79389],[14.25257,50.85953],[14.40002,50.88966],[14.42671,50.9357],[14.35649,50.97197],[14.32559,50.99319],[14.27504,50.99054],[14.31577,51.04266],[14.41547,51.01263],[14.51939,51.0038],[14.5882,50.9817],[14.54467,50.91977],[14.57977,50.90649],[14.64718,50.92243],[14.64437,50.90915],[14.60505,50.85687],[14.7202,50.81785],[14.81008,50.81341],[14.85642,50.89055],[14.91259,50.94721],[14.99685,51.08679],[15.05303,51.24793],[15.05583,51.29274],[14.99264,51.34452],[14.98843,51.398],[14.97719,51.45754],[14.90979,51.49603],[14.73986,51.5371],[14.73986,51.59122],[14.70054,51.60605],[14.67948,51.5982],[14.68369,51.57813],[14.67386,51.55806],[14.61769,51.55718],[14.58399,51.59035],[14.51939,51.56941],[14.43513,51.5598],[14.327,51.52574],[14.13461,51.55544],[14.0672,51.49952],[14.02788,51.47854],[14.04333,51.45229],[13.99558,51.39274],[13.95767,51.40588],[13.88886,51.38836],[13.72455,51.37434],[13.55463,51.39274],[13.40437,51.45929],[13.35241,51.43916],[13.3159,51.44354],[13.28641,51.41815],[13.22602,51.40063],[13.21339,51.46104],[13.219,51.52661],[13.17406,51.5982],[13.00274,51.67751],[12.90584,51.65312],[12.90303,51.66619],[12.85388,51.69318],[12.76401,51.65922],[12.68817,51.67054],[12.64324,51.62959],[12.57723,51.63046],[12.42557,51.61041],[12.23037,51.57028],[12.17701,51.53011],[12.13909,51.46017],[12.16718,51.41727],[12.1742,51.33487],[12.13207,51.3182],[12.18684,51.21364],[12.15875,51.18812],[12.22054,51.09296],[12.49017,51.05414],[12.52106,50.99319],[12.60532,50.97286],[12.62639,50.91889],[12.50281,50.91092],[12.23739,50.81874],[12.21352,50.72993],[12.28654,50.665],[12.21773,50.6463],[12.13347,50.6276],[12.05343,50.56342],[12.01972,50.64719],[11.85963,50.54825],[11.87649,50.50808],[11.92704,50.5054],[11.93687,50.48664],[11.87087,50.44194],[11.93406,50.39989],[11.96917,50.33987],[12.12083,50.29773],[12.17279,50.3067],[12.18543,50.26094],[12.21212,50.25375],[12.25705,50.21603],[12.28233,50.15668],[12.35535,50.15848],[12.35535,50.22142],[12.41433,50.28158],[12.51123,50.34705],[12.53791,50.38735],[12.67835,50.40257],[12.71205,50.38646],[12.7289,50.39631],[12.75699,50.42584],[12.78648,50.43389],[12.81737,50.41779],[12.84686,50.43657],[12.94797,50.38735],[13.00976,50.41421],[13.04627,50.44999],[13.0561,50.48753],[13.21479,50.49289],[13.27517,50.56609],[13.34118,50.56877],[13.39173,50.61334],[13.47739,50.58571],[13.54761,50.63473],[13.56867,50.67212],[13.54901,50.69792]]],"type":"Polygon"}}, {"properties":{"name":"© GeoBasis-DE/LVermGeo LSA, DOP20","id":"LSA-DOP20","url":"https://www.geodatenportal.sachsen-anhalt.de/wss/service/ST_LVermGeo_DOP_WMS_OpenData/guest?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=lsa_lvermgeo_dop20_2&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"text":"© GeoBasis-DE/LVermGeo LSA","url":"https://www.lvermgeo.sachsen-anhalt.de/de/kostenfreie_geobasisdaten_lvermgeo.html"},"type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[10.56074,52.00392],[10.56588,52],[10.61351,51.92026],[10.56621,51.85598],[10.69013,51.64568],[10.88483,51.61439],[10.99158,51.41802],[11.34477,51.38654],[11.46667,51.30465],[11.36695,51.22636],[11.46956,51.10979],[12.2299,50.93098],[12.29319,51.02138],[12.15085,51.45859],[12.23452,51.55626],[13.02375,51.63743],[13.15432,51.68641],[13.15012,51.87062],[12.65345,52.0114],[12.44355,52.0167],[12.21787,52.17114],[12.29715,52.22891],[12.31883,52.49459],[12.18565,52.49612],[12.14428,52.53241],[12.23671,52.62868],[12.25584,52.79854],[12.23343,52.85993],[11.82449,52.91752],[11.84481,52.95218],[11.62298,53.0417],[11.51282,53.00726],[11.49252,52.95981],[11.3554,52.89056],[11.09873,52.9127],[10.99998,52.9112],[10.94128,52.8538],[10.76565,52.84212],[10.75152,52.78499],[10.79684,52.71404],[11.00297,52.49614],[10.9346,52.4741],[11.0153,52.38886],[11.04297,52.38843],[11.06922,52.35727],[10.98026,52.34328],[11.01205,52.29087],[11.08563,52.22908],[11.02578,52.20988],[11.01905,52.17829],[11.06046,52.1724],[10.97565,52.10553],[10.94254,52.10349],[10.94274,52.09386],[10.9704,52.08525],[10.96512,52.05748],[10.87552,52.05818],[10.71061,52.04976],[10.64827,52.04107],[10.65499,52.02454],[10.56074,52.00392]]],"type":"Polygon"}}, {"properties":{"name":"Saxony WebAtlasSN","id":"GEOSN-WebAtlas","url":"https://geodienste.sachsen.de/wms_geosn_webatlas-sn/guest?LAYERS=Vegetation,Siedlung,Gewaesser,Verkehr,Administrative_Einheiten,Beschriftung&STYLES=&FORMAT=image/png&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Staatsbetrieb Geobasisinformation und Vermessung Sachsen","url":"https://geoportal.sachsen.de/cps/metadaten_portal.html?id=475a9197-620f-4dcb-b8aa-7f71b626443f"},"type":"wms","category":"map"},"type":"Feature","geometry":{"coordinates":[[[13.54901,50.69792],[13.84251,50.71126],[13.93239,50.74504],[14.04614,50.79389],[14.25257,50.85953],[14.40002,50.88966],[14.42671,50.9357],[14.35649,50.97197],[14.32559,50.99319],[14.27504,50.99054],[14.31577,51.04266],[14.41547,51.01263],[14.51939,51.0038],[14.5882,50.9817],[14.54467,50.91977],[14.57977,50.90649],[14.64718,50.92243],[14.64437,50.90915],[14.60505,50.85687],[14.7202,50.81785],[14.81008,50.81341],[14.85642,50.89055],[14.91259,50.94721],[14.99685,51.08679],[15.05303,51.24793],[15.05583,51.29274],[14.99264,51.34452],[14.98843,51.398],[14.97719,51.45754],[14.90979,51.49603],[14.73986,51.5371],[14.73986,51.59122],[14.70054,51.60605],[14.67948,51.5982],[14.68369,51.57813],[14.67386,51.55806],[14.61769,51.55718],[14.58399,51.59035],[14.51939,51.56941],[14.43513,51.5598],[14.327,51.52574],[14.13461,51.55544],[14.0672,51.49952],[14.02788,51.47854],[14.04333,51.45229],[13.99558,51.39274],[13.95767,51.40588],[13.88886,51.38836],[13.72455,51.37434],[13.55463,51.39274],[13.40437,51.45929],[13.35241,51.43916],[13.3159,51.44354],[13.28641,51.41815],[13.22602,51.40063],[13.21339,51.46104],[13.219,51.52661],[13.17406,51.5982],[13.00274,51.67751],[12.90584,51.65312],[12.90303,51.66619],[12.85388,51.69318],[12.76401,51.65922],[12.68817,51.67054],[12.64324,51.62959],[12.57723,51.63046],[12.42557,51.61041],[12.23037,51.57028],[12.17701,51.53011],[12.13909,51.46017],[12.16718,51.41727],[12.1742,51.33487],[12.13207,51.3182],[12.18684,51.21364],[12.15875,51.18812],[12.22054,51.09296],[12.49017,51.05414],[12.52106,50.99319],[12.60532,50.97286],[12.62639,50.91889],[12.50281,50.91092],[12.23739,50.81874],[12.21352,50.72993],[12.28654,50.665],[12.21773,50.6463],[12.13347,50.6276],[12.05343,50.56342],[12.01972,50.64719],[11.85963,50.54825],[11.87649,50.50808],[11.92704,50.5054],[11.93687,50.48664],[11.87087,50.44194],[11.93406,50.39989],[11.96917,50.33987],[12.12083,50.29773],[12.17279,50.3067],[12.18543,50.26094],[12.21212,50.25375],[12.25705,50.21603],[12.28233,50.15668],[12.35535,50.15848],[12.35535,50.22142],[12.41433,50.28158],[12.51123,50.34705],[12.53791,50.38735],[12.67835,50.40257],[12.71205,50.38646],[12.7289,50.39631],[12.75699,50.42584],[12.78648,50.43389],[12.81737,50.41779],[12.84686,50.43657],[12.94797,50.38735],[13.00976,50.41421],[13.04627,50.44999],[13.0561,50.48753],[13.21479,50.49289],[13.27517,50.56609],[13.34118,50.56877],[13.39173,50.61334],[13.47739,50.58571],[13.54761,50.63473],[13.56867,50.67212],[13.54901,50.69792]]],"type":"Polygon"}}, @@ -185,6 +187,7 @@ {"properties":{"name":"Worms 2016","id":"Worms-2016","url":"https://geoportal-worms.de/ogc/wms/luftbild2016?LAYERS=FFF9DFB4F6814391AB0B4BC96B3B70B2&STYLES=&FORMAT=image/png&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"© Nibelungenstadt Worms","url":"https://www.worms.de"},"type":"wms","category":"photo"},"type":"Feature","geometry":{"coordinates":[[[8.41625,49.59524],[8.41147,49.60428],[8.40616,49.60903],[8.39679,49.61394],[8.38843,49.62225],[8.3867,49.62296],[8.37888,49.63745],[8.37361,49.65316],[8.37042,49.66052],[8.3646,49.66594],[8.36269,49.66817],[8.35869,49.67585],[8.35633,49.68297],[8.35897,49.69256],[8.36624,49.69906],[8.38915,49.70835],[8.42429,49.7152],[8.43416,49.71893],[8.44252,49.72434],[8.44789,49.7311],[8.44216,49.73321],[8.42298,49.72751],[8.41079,49.73292],[8.4077,49.73603],[8.39806,49.73727],[8.39279,49.73686],[8.37724,49.73439],[8.37615,49.72587],[8.37297,49.72593],[8.36942,49.72704],[8.37115,49.7308],[8.34915,49.73169],[8.34751,49.71752],[8.34651,49.71258],[8.35006,49.69718],[8.33842,49.69712],[8.3376,49.696],[8.33985,49.6856],[8.34087,49.68373],[8.33778,49.6839],[8.33658,49.68343],[8.33494,49.68375],[8.32789,49.6845],[8.32508,49.68428],[8.32426,49.68503],[8.32039,49.68482],[8.31701,49.68647],[8.30998,49.68522],[8.30857,49.68934],[8.29623,49.68853],[8.29587,49.69035],[8.29441,49.69106],[8.29005,49.69071],[8.28946,49.69332],[8.28814,49.694],[8.25672,49.68954],[8.25607,49.68856],[8.25827,49.6782],[8.25481,49.67838],[8.25418,49.67718],[8.26309,49.67094],[8.26436,49.6707],[8.26691,49.66597],[8.26991,49.6667],[8.27723,49.65567],[8.24409,49.65107],[8.23977,49.64713],[8.23895,49.63547],[8.23754,49.62852],[8.23636,49.62758],[8.2379,49.62346],[8.24181,49.62399],[8.24318,49.61624],[8.24527,49.6161],[8.25113,49.61689],[8.25104,49.60045],[8.256,49.58755],[8.26213,49.58769],[8.26282,49.58581],[8.26759,49.58536],[8.27614,49.58663],[8.27855,49.58557],[8.29164,49.58772],[8.2915,49.5892],[8.30905,49.59138],[8.32469,49.59415],[8.32887,49.59633],[8.33083,49.59606],[8.33324,49.59848],[8.34365,49.59388],[8.34474,49.59438],[8.35092,49.60537],[8.35228,49.6069],[8.35706,49.60632],[8.36001,49.60484],[8.36219,49.60089],[8.3686,49.60331],[8.37329,49.60219],[8.37511,49.60334],[8.37847,49.60428],[8.38606,49.60185],[8.38743,49.60275],[8.39006,49.60287],[8.39393,49.6006],[8.39943,49.5963],[8.40243,49.59521],[8.40302,49.59235],[8.40602,49.59158],[8.4097,49.59317],[8.41498,49.594],[8.4162,49.59453],[8.41625,49.59524]]],"type":"Polygon"}}, {"properties":{"name":"Worms 2020","id":"Worms-2020","url":"https://geoportal-worms.de/ogc/wms/luftbild2020?LAYERS=E1C1EF1295564C3E8B3504D516F081E9&STYLES=&FORMAT=image/png&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"© Nibelungenstadt Worms","url":"https://www.worms.de"},"type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[8.41904,49.59534],[8.41645,49.60086],[8.41243,49.60693],[8.40566,49.61182],[8.39547,49.6178],[8.38906,49.62422],[8.3797,49.64198],[8.37351,49.66058],[8.36388,49.67164],[8.35924,49.67959],[8.3626,49.694],[8.38597,49.7057],[8.42325,49.71311],[8.43598,49.71711],[8.45143,49.73086],[8.44216,49.73533],[8.42307,49.72957],[8.41434,49.73345],[8.40961,49.73791],[8.39234,49.73862],[8.38688,49.73674],[8.37579,49.73627],[8.37411,49.73433],[8.37413,49.7306],[8.37372,49.72766],[8.37304,49.72789],[8.37376,49.72989],[8.37354,49.73121],[8.37261,49.73208],[8.37042,49.7329],[8.34806,49.73304],[8.34651,49.73171],[8.34619,49.72954],[8.34396,49.71273],[8.34696,49.69888],[8.33815,49.69894],[8.33592,49.69773],[8.3351,49.69565],[8.33705,49.68541],[8.33378,49.68556],[8.3281,49.68632],[8.32655,49.68621],[8.32392,49.68694],[8.32237,49.68685],[8.31951,49.68762],[8.31646,49.68823],[8.31201,49.68741],[8.31,49.69073],[8.30819,49.69118],[8.29841,49.69062],[8.29628,49.69247],[8.29237,49.69285],[8.29059,49.69512],[8.28759,49.69579],[8.25663,49.69144],[8.25372,49.69023],[8.25345,49.68762],[8.25522,49.68021],[8.25191,49.67847],[8.25172,49.67626],[8.26091,49.66976],[8.26232,49.6685],[8.26427,49.66494],[8.26823,49.66408],[8.27368,49.65699],[8.24281,49.65263],[8.23731,49.64801],[8.23613,49.63677],[8.23649,49.63512],[8.23545,49.62255],[8.23863,49.62231],[8.23863,49.6216],[8.2399,49.62166],[8.24018,49.61621],[8.24136,49.61512],[8.24829,49.6152],[8.24829,49.61475],[8.24863,49.61484],[8.24865,49.61448],[8.24942,49.61448],[8.24968,49.59589],[8.25254,49.58993],[8.25377,49.58651],[8.25704,49.58545],[8.25995,49.58569],[8.26077,49.58492],[8.28964,49.58519],[8.29305,49.58589],[8.29409,49.58725],[8.29523,49.58805],[8.3,49.58855],[8.31487,49.59082],[8.3151,49.5912],[8.3196,49.59132],[8.32496,49.59217],[8.32951,49.59435],[8.33246,49.59474],[8.3336,49.59591],[8.34224,49.59223],[8.34546,49.59258],[8.34806,49.59488],[8.35319,49.60411],[8.35433,49.60496],[8.35578,49.60437],[8.35751,49.60349],[8.35919,49.60057],[8.36824,49.60078],[8.36906,49.60125],[8.36988,49.60072],[8.37561,49.60081],[8.37624,49.60157],[8.37811,49.60222],[8.38252,49.60084],[8.38879,49.60084],[8.38915,49.60113],[8.39415,49.59842],[8.39606,49.59674],[8.39706,49.59671],[8.39684,49.59568],[8.40025,49.59441],[8.40079,49.59129],[8.40293,49.59132],[8.40306,49.59085],[8.40506,49.59082],[8.40516,49.59126],[8.41043,49.59129],[8.41088,49.59149],[8.41688,49.59261],[8.41911,49.59459],[8.41904,49.59534]]],"type":"Polygon"}}, {"properties":{"name":"Aachen Liegenschaftskataster","id":"aachen_alkis_wms","url":"https://geodienste.staedteregion-aachen.de/cgi-bin/qgis_mapserv.fcgi?MAP=/home/geonet/inkasserver/QMAPS/ALKIS/ALKIS_LK_Inkas.qgs&LAYERS=alkis_lk_inkas&FORMAT=image/png&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","type":"wms","category":"map","best":true},"type":"Feature","geometry":{"coordinates":[[[6.2,50.48],[6.36,50.49],[6.43,50.6],[6.31,50.95],[6.11,50.95],[5.96,50.79],[6.2,50.48]]],"type":"Polygon"}}, +{"properties":{"name":"ALKIS Kreis Viersen","id":"viersen_alkis_wms","url":"https://gdi-niederrhein-geodienste.de/flurkarte_verb_sammeldienst/service?VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=FlurkarteAdV_Viersen&STYLES=&CRS={proj}&BBOX={bbox}&WIDTH={width}&HEIGHT={height}&FORMAT=image/png","type":"wms","category":"map","min_zoom":16,"best":true},"type":"Feature","geometry":{"coordinates":[[[6.064,51.22],[6.07,51.17],[6.211,51.164],[6.321,51.179],[6.34,51.22],[6.35,51.22],[6.37,51.21],[6.43,51.22],[6.46,51.24],[6.523,51.216],[6.64,51.25],[6.59,51.29],[6.514,51.293],[6.52,51.33],[6.5,51.35],[6.49,51.4],[6.52,51.39],[6.54,51.4],[6.53,51.43],[6.47,51.42],[6.46,51.41],[6.4,51.42],[6.35,51.38],[6.222,51.368],[6.17,51.337],[6.07,51.24],[6.064,51.22]]],"type":"Polygon"}}, {"properties":{"name":"SDFI Aerial Imagery","id":"Geodatastyrelsen_Denmark","url":"https://osmtools.septima.dk/mapproxy/tiles/1.0.0/kortforsyningen_ortoforaar/EPSG3857/{zoom}/{x}/{y}.jpeg","attribution":{"required":true,"text":"Styrelsen for Dataforsyning og Infrastruktur","url":"https://dataforsyningen.dk/asset/PDF/rettigheder_vilkaar/Vilk%C3%A5r%20for%20brug%20af%20frie%20geografiske%20data.pdf"},"type":"tms","category":"photo","max_zoom":21,"best":true},"type":"Feature","geometry":{"coordinates":[[[[15.28158,55.15442],[15.12556,55.16238],[15.13934,55.25174],[14.82638,55.26713],[14.83952,55.35652],[14.68259,55.36394],[14.63175,55.00625],[15.25356,54.97576],[15.28158,55.15442]]],[[[15.29572,55.24374],[15.30992,55.33306],[15.1532,55.34108],[15.13934,55.25174],[15.29572,55.24374]]],[[[11.57829,56.18804],[11.73923,56.18458],[11.74564,56.27432],[11.58433,56.27779],[11.57829,56.18804]]],[[[8.01851,56.75014],[8.05027,55.49247],[8.20873,55.49373],[8.2104,55.40398],[8.36838,55.40421],[8.37439,54.95517],[8.53143,54.95516],[8.5322,54.86638],[9.15628,54.86754],[9.15558,54.77696],[10.08737,54.77239],[10.09023,54.86221],[10.24598,54.86047],[10.2424,54.77059],[10.55472,54.76702],[10.5511,54.67817],[10.70411,54.67567],[10.70745,54.7113],[10.73844,54.71085],[10.73891,54.71976],[10.7544,54.71957],[10.75514,54.73758],[10.77073,54.73728],[10.77136,54.76439],[10.86512,54.76347],[10.86172,54.6734],[11.17064,54.66865],[11.16585,54.57822],[11.78374,54.56548],[11.7795,54.47536],[12.08586,54.46817],[12.10707,54.73782],[12.26102,54.73316],[12.27666,54.9119],[12.5872,54.90363],[12.60486,55.08329],[12.28973,55.09236],[12.2987,55.18223],[12.45529,55.17782],[12.46273,55.26722],[12.62009,55.26326],[12.62697,55.35238],[12.47024,55.35705],[12.47782,55.44707],[12.32061,55.45137],[12.32687,55.54121],[12.96129,55.52173],[12.97923,55.7014],[12.66111,55.71143],[12.70235,56.15944],[12.06085,56.17626],[12.05403,56.08713],[11.732,56.09521],[11.7265,56.00506],[11.08581,56.01783],[11.08028,55.92792],[10.91971,55.93094],[10.92587,56.02012],[10.60521,56.02475],[10.60797,56.11503],[10.76948,56.11201],[10.77197,56.20202],[10.93412,56.19948],[10.94299,56.37953],[11.10526,56.37683],[11.10993,56.46647],[10.94792,56.46922],[10.95242,56.55898],[10.4649,56.56567],[10.47503,56.83509],[10.31123,56.83693],[10.3144,56.92676],[10.47862,56.92491],[10.48577,57.10451],[10.65078,57.10245],[10.67104,57.55141],[10.504,57.55351],[10.5077,57.64331],[10.67516,57.6412],[10.68349,57.82077],[10.51521,57.82289],[10.51183,57.73303],[10.17542,57.73678],[10.17257,57.64628],[9.83749,57.64933],[9.8352,57.55963],[9.66873,57.56056],[9.66497,57.38116],[9.49886,57.38206],[9.49789,57.29196],[9.33191,57.29248],[9.33163,57.20276],[8.50339,57.20205],[8.50544,57.11232],[8.33925,57.11196],[8.34133,57.02199],[8.17633,57.02089],[8.18192,56.75099],[8.01851,56.75014]],[[10.28659,56.11868],[10.44667,56.11672],[10.44393,56.02704],[10.28315,56.02819],[10.28659,56.11868]],[[10.4335,55.66935],[10.44177,55.75792],[10.75623,55.75792],[10.74381,55.66469],[10.4335,55.66935]],[[10.74381,55.57123],[10.74381,55.66469],[10.92587,55.66702],[10.8969,55.57123],[10.74381,55.57123]],[[10.90518,55.39539],[10.8969,55.57123],[11.07896,55.57123],[11.06137,55.38128],[10.90518,55.39539]],[[11.04586,55.03186],[11.0593,55.11241],[11.20308,55.11714],[11.20308,55.02475],[11.04586,55.03186]]],[[[11.44596,56.64011],[11.77167,56.63328],[11.78492,56.81274],[11.45777,56.81955],[11.44596,56.64011]]],[[[11.31618,57.1818],[11.32747,57.3613],[10.82906,57.36953],[10.81577,57.10017],[11.14566,57.09496],[11.15087,57.18473],[11.31618,57.1818]]]],"type":"MultiPolygon"}}, {"properties":{"name":"SDFI Cadastral Parcels INSPIRE View","id":"Geodatastyrelsen_Cadastral_Parcels_INSPIRE_View","url":"https://kortforsyningen.kms.dk/cp_inspire?LAYERS=CP.CadastralParcel&STYLES=&FORMAT=image/png&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LOGIN=OpenStreetMapDK2015&PASSWORD=Gall4Peters","attribution":{"required":true,"text":"Geodatastyrelsen og Styrelsen for Dataforsyning og Infrastruktur","url":"https://dataforsyningen.dk/asset/PDF/rettigheder_vilkaar/Vilk%C3%A5r%20for%20brug%20af%20data%20fra%20GST.pdf"},"type":"wms","category":"other","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[[15.28158,55.15442],[15.12556,55.16238],[15.13934,55.25174],[14.82638,55.26713],[14.83952,55.35652],[14.68259,55.36394],[14.63175,55.00625],[15.25356,54.97576],[15.28158,55.15442]]],[[[15.29572,55.24374],[15.30992,55.33306],[15.1532,55.34108],[15.13934,55.25174],[15.29572,55.24374]]],[[[11.57829,56.18804],[11.73923,56.18458],[11.74564,56.27432],[11.58433,56.27779],[11.57829,56.18804]]],[[[8.01851,56.75014],[8.05027,55.49247],[8.20873,55.49373],[8.2104,55.40398],[8.36838,55.40421],[8.37439,54.95517],[8.53143,54.95516],[8.5322,54.86638],[9.15628,54.86754],[9.15558,54.77696],[10.08737,54.77239],[10.09023,54.86221],[10.24598,54.86047],[10.2424,54.77059],[10.55472,54.76702],[10.5511,54.67817],[10.70411,54.67567],[10.70745,54.7113],[10.73844,54.71085],[10.73891,54.71976],[10.7544,54.71957],[10.75514,54.73758],[10.77073,54.73728],[10.77136,54.76439],[10.86512,54.76347],[10.86172,54.6734],[11.17064,54.66865],[11.16585,54.57822],[11.78374,54.56548],[11.7795,54.47536],[12.08586,54.46817],[12.10707,54.73782],[12.26102,54.73316],[12.27666,54.9119],[12.5872,54.90363],[12.60486,55.08329],[12.28973,55.09236],[12.2987,55.18223],[12.45529,55.17782],[12.46273,55.26722],[12.62009,55.26326],[12.62697,55.35238],[12.47024,55.35705],[12.47782,55.44707],[12.32061,55.45137],[12.32687,55.54121],[12.96129,55.52173],[12.97923,55.7014],[12.66111,55.71143],[12.70235,56.15944],[12.06085,56.17626],[12.05403,56.08713],[11.732,56.09521],[11.7265,56.00506],[11.08581,56.01783],[11.08028,55.92792],[10.91971,55.93094],[10.92587,56.02012],[10.60521,56.02475],[10.60797,56.11503],[10.76948,56.11201],[10.77197,56.20202],[10.93412,56.19948],[10.94299,56.37953],[11.10526,56.37683],[11.10993,56.46647],[10.94792,56.46922],[10.95242,56.55898],[10.4649,56.56567],[10.47503,56.83509],[10.31123,56.83693],[10.3144,56.92676],[10.47862,56.92491],[10.48577,57.10451],[10.65078,57.10245],[10.67104,57.55141],[10.504,57.55351],[10.5077,57.64331],[10.67516,57.6412],[10.68349,57.82077],[10.51521,57.82289],[10.51183,57.73303],[10.17542,57.73678],[10.17257,57.64628],[9.83749,57.64933],[9.8352,57.55963],[9.66873,57.56056],[9.66497,57.38116],[9.49886,57.38206],[9.49789,57.29196],[9.33191,57.29248],[9.33163,57.20276],[8.50339,57.20205],[8.50544,57.11232],[8.33925,57.11196],[8.34133,57.02199],[8.17633,57.02089],[8.18192,56.75099],[8.01851,56.75014]],[[10.28659,56.11868],[10.44667,56.11672],[10.44393,56.02704],[10.28315,56.02819],[10.28659,56.11868]],[[10.4335,55.66935],[10.44177,55.75792],[10.75623,55.75792],[10.74381,55.66469],[10.4335,55.66935]],[[10.74381,55.57123],[10.74381,55.66469],[10.92587,55.66702],[10.8969,55.57123],[10.74381,55.57123]],[[10.90518,55.39539],[10.8969,55.57123],[11.07896,55.57123],[11.06137,55.38128],[10.90518,55.39539]],[[11.04586,55.03186],[11.0593,55.11241],[11.20308,55.11714],[11.20308,55.02475],[11.04586,55.03186]]],[[[11.44596,56.64011],[11.77167,56.63328],[11.78492,56.81274],[11.45777,56.81955],[11.44596,56.64011]]],[[[11.31618,57.1818],[11.32747,57.3613],[10.82906,57.36953],[10.81577,57.10017],[11.14566,57.09496],[11.15087,57.18473],[11.31618,57.1818]]]],"type":"MultiPolygon"}}, {"properties":{"name":"SDFI DTK Map25","id":"Geodatastyrelsen_DTK_Kort25","url":"https://api.dataforsyningen.dk/dtk_25_DAF?service=WMS&request=GetMap&token=52065b2ec5fda5a46a50b451f3f24473&FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&Layers=dtk25&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":true,"text":"Styrelsen for Dataforsyning og Infrastruktur","url":"https://dataforsyningen.dk/asset/PDF/rettigheder_vilkaar/Vilk%C3%A5r%20for%20brug%20af%20frie%20geografiske%20data.pdf"},"type":"wms","category":"map","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[[15.28158,55.15442],[15.12556,55.16238],[15.13934,55.25174],[14.82638,55.26713],[14.83952,55.35652],[14.68259,55.36394],[14.63175,55.00625],[15.25356,54.97576],[15.28158,55.15442]]],[[[15.29572,55.24374],[15.30992,55.33306],[15.1532,55.34108],[15.13934,55.25174],[15.29572,55.24374]]],[[[11.57829,56.18804],[11.73923,56.18458],[11.74564,56.27432],[11.58433,56.27779],[11.57829,56.18804]]],[[[8.01851,56.75014],[8.05027,55.49247],[8.20873,55.49373],[8.2104,55.40398],[8.36838,55.40421],[8.37439,54.95517],[8.53143,54.95516],[8.5322,54.86638],[9.15628,54.86754],[9.15558,54.77696],[10.08737,54.77239],[10.09023,54.86221],[10.24598,54.86047],[10.2424,54.77059],[10.55472,54.76702],[10.5511,54.67817],[10.70411,54.67567],[10.70745,54.7113],[10.73844,54.71085],[10.73891,54.71976],[10.7544,54.71957],[10.75514,54.73758],[10.77073,54.73728],[10.77136,54.76439],[10.86512,54.76347],[10.86172,54.6734],[11.17064,54.66865],[11.16585,54.57822],[11.78374,54.56548],[11.7795,54.47536],[12.08586,54.46817],[12.10707,54.73782],[12.26102,54.73316],[12.27666,54.9119],[12.5872,54.90363],[12.60486,55.08329],[12.28973,55.09236],[12.2987,55.18223],[12.45529,55.17782],[12.46273,55.26722],[12.62009,55.26326],[12.62697,55.35238],[12.47024,55.35705],[12.47782,55.44707],[12.32061,55.45137],[12.32687,55.54121],[12.96129,55.52173],[12.97923,55.7014],[12.66111,55.71143],[12.70235,56.15944],[12.06085,56.17626],[12.05403,56.08713],[11.732,56.09521],[11.7265,56.00506],[11.08581,56.01783],[11.08028,55.92792],[10.91971,55.93094],[10.92587,56.02012],[10.60521,56.02475],[10.60797,56.11503],[10.76948,56.11201],[10.77197,56.20202],[10.93412,56.19948],[10.94299,56.37953],[11.10526,56.37683],[11.10993,56.46647],[10.94792,56.46922],[10.95242,56.55898],[10.4649,56.56567],[10.47503,56.83509],[10.31123,56.83693],[10.3144,56.92676],[10.47862,56.92491],[10.48577,57.10451],[10.65078,57.10245],[10.67104,57.55141],[10.504,57.55351],[10.5077,57.64331],[10.67516,57.6412],[10.68349,57.82077],[10.51521,57.82289],[10.51183,57.73303],[10.17542,57.73678],[10.17257,57.64628],[9.83749,57.64933],[9.8352,57.55963],[9.66873,57.56056],[9.66497,57.38116],[9.49886,57.38206],[9.49789,57.29196],[9.33191,57.29248],[9.33163,57.20276],[8.50339,57.20205],[8.50544,57.11232],[8.33925,57.11196],[8.34133,57.02199],[8.17633,57.02089],[8.18192,56.75099],[8.01851,56.75014]],[[10.28659,56.11868],[10.44667,56.11672],[10.44393,56.02704],[10.28315,56.02819],[10.28659,56.11868]],[[10.4335,55.66935],[10.44177,55.75792],[10.75623,55.75792],[10.74381,55.66469],[10.4335,55.66935]],[[10.74381,55.57123],[10.74381,55.66469],[10.92587,55.66702],[10.8969,55.57123],[10.74381,55.57123]],[[10.90518,55.39539],[10.8969,55.57123],[11.07896,55.57123],[11.06137,55.38128],[10.90518,55.39539]],[[11.04586,55.03186],[11.0593,55.11241],[11.20308,55.11714],[11.20308,55.02475],[11.04586,55.03186]]],[[[11.44596,56.64011],[11.77167,56.63328],[11.78492,56.81274],[11.45777,56.81955],[11.44596,56.64011]]],[[[11.31618,57.1818],[11.32747,57.3613],[10.82906,57.36953],[10.81577,57.10017],[11.14566,57.09496],[11.15087,57.18473],[11.31618,57.1818]]]],"type":"MultiPolygon"}}, diff --git a/src/index_theme.ts.template b/src/index_theme.ts.template index c90fe9fba..218113851 100644 --- a/src/index_theme.ts.template +++ b/src/index_theme.ts.template @@ -3,6 +3,7 @@ import SvelteUIElement from "./src/UI/Base/SvelteUIElement" import ThemeViewGUI from "./src/UI/ThemeViewGUI.svelte" import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig"; import MetaTagging from "./src/Logic/MetaTagging"; +import { FixedUiElement } from "./src/UI/Base/FixedUiElement"; function webgl_support() { try { diff --git a/theme.html b/theme.html index 9c94eaf72..3aab458b1 100644 --- a/theme.html +++ b/theme.html @@ -4,7 +4,7 @@ - + @@ -65,57 +65,12 @@
Below
+ + + + - - - - - - - +