From a32ab16a5ea34a970ddc39f27d726bbd5f9a3efd Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 19 Nov 2023 18:11:27 +0100 Subject: [PATCH 01/22] UI test --- src/UI/Test.svelte | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/UI/Test.svelte b/src/UI/Test.svelte index 22f2d9313d..47b2ce975a 100644 --- a/src/UI/Test.svelte +++ b/src/UI/Test.svelte @@ -4,12 +4,8 @@ import Translations from "./i18n/Translations"; import Tr from "./Base/Tr.svelte"; import Locale from "./i18n/Locale"; + import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte"; let language = Locale.language -
- - - - {$language} -
+ From f9827dd6aeabbc2f52a9cd46b1f3c683f7caeadd Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 22 Nov 2023 19:39:19 +0100 Subject: [PATCH 02/22] Feature: add favourite --- .../layers/animal_shelter/animal_shelter.json | 3 +- assets/layers/atm/atm.json | 3 +- assets/layers/bank/bank.json | 3 +- assets/layers/bench/bench.json | 48 +- assets/layers/bench_at_pt/bench_at_pt.json | 6 +- .../layers/bicycle_rental/bicycle_rental.json | 10 +- .../bicycle_tube_vending_machine.json | 60 +- assets/layers/bike_cafe/bike_cafe.json | 2 +- .../layers/bike_cleaning/bike_cleaning.json | 4 +- .../bike_repair_station.json | 2 +- assets/layers/bike_shop/bike_shop.json | 4 +- .../bike_themed_object.json | 2 +- assets/layers/cafe_pub/cafe_pub.json | 19 +- .../charging_station/charging_station.json | 4 +- assets/layers/crossings/crossings.json | 4 +- .../cycleways_and_roads.json | 6 +- .../layers/defibrillator/defibrillator.json | 4 +- .../layers/drinking_water/drinking_water.json | 3 +- assets/layers/entrance/entrance.json | 2 +- assets/layers/favourite/favourite.json | 71 ++ assets/layers/filters/filters.json | 2 +- assets/layers/food/food.json | 14 +- assets/layers/guidepost/guidepost.json | 18 +- assets/layers/ice_cream/ice_cream.json | 24 +- assets/layers/icons/icons.json | 19 +- assets/layers/note/note.json | 2 +- assets/layers/playground/playground.json | 2 +- assets/layers/questions/questions.json | 15 +- assets/layers/recycling/recycling.json | 2 +- assets/layers/route_marker/route_marker.json | 33 +- assets/layers/shops/shops.json | 12 +- assets/layers/slow_roads/slow_roads.json | 48 +- assets/layers/toilet/toilet.json | 6 +- assets/layers/trail/trail.json | 45 +- assets/layers/tree_node/tree_node.json | 6 +- assets/themes/cyclenodes/cyclenodes.json | 3 +- assets/themes/icecream/icecream.json | 6 +- .../mapcomplete-changes.json | 216 +++++- assets/themes/walkingnodes/walkingnodes.json | 45 +- langs/en.json | 9 + langs/layers/de.json | 708 +++++++++--------- langs/layers/en.json | 12 + langs/layers/es.json | 300 ++++---- langs/layers/nl.json | 12 + langs/themes/de.json | 64 +- public/css/index-tailwind-output.css | 16 +- .../Sources/FavouritesFeatureSource.ts | 157 ++++ .../Sources/NearbyFeatureSource.ts | 63 +- src/Logic/Osm/OsmPreferences.ts | 9 + src/Models/Constants.ts | 3 + .../ThemeConfig/Conversion/PrepareLayer.ts | 39 +- .../ThemeConfig/Conversion/Validation.ts | 2 +- src/Models/ThemeConfig/LayoutConfig.ts | 3 + src/Models/ThemeViewState.ts | 27 +- src/UI/Base/LogoutButton.svelte | 10 +- src/UI/Map/DynamicIcon.svelte | 26 +- src/UI/Map/DynamicMarker.svelte | 6 +- src/UI/Map/Icon.svelte | 110 +-- src/UI/Map/Marker.svelte | 8 +- src/UI/Map/ShowDataLayer.ts | 2 - src/UI/Popup/AllTagsPanel.svelte | 2 +- src/UI/Popup/MarkAsFavourite.svelte | 48 ++ src/UI/Popup/MarkAsFavouriteMini.svelte | 36 + .../TagRendering/TagRenderingMapping.svelte | 8 +- src/UI/SpecialVisualization.ts | 4 +- src/UI/SpecialVisualizations.ts | 44 +- src/UI/Test.svelte | 17 +- src/UI/ThemeViewGUI.svelte | 3 +- 68 files changed, 1641 insertions(+), 885 deletions(-) create mode 100644 assets/layers/favourite/favourite.json create mode 100644 src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts create mode 100644 src/UI/Popup/MarkAsFavourite.svelte create mode 100644 src/UI/Popup/MarkAsFavouriteMini.svelte diff --git a/assets/layers/animal_shelter/animal_shelter.json b/assets/layers/animal_shelter/animal_shelter.json index 82c782e197..aaeb7bfffb 100644 --- a/assets/layers/animal_shelter/animal_shelter.json +++ b/assets/layers/animal_shelter/animal_shelter.json @@ -9,7 +9,8 @@ "description": { "en": "An animal shelter is a facility where animals in trouble are brought and facility's staff (volunteers or not) feeds them and cares of them, rehabilitating and healing them if necessary. This definition includes kennels for abandoned dogs, catteries for abandoned cats, shelters for other abandoned pets and wildlife recovery centres. ", "es": "Un refugio de animales es una instalación donde se llevan animales con problemas y el personal de la instalación (voluntario o no) los alimenta y cuida, rehabilitándolos y curándolos si es necesario. Esta definición incluye las perreras para perros abandonados, los criaderos para gatos abandonados, los refugios para otros animales de compañía abandonados y los centros de recuperación de la fauna salvaje. ", - "zh_Hans": "动物收容所是一个设施,将遇到麻烦的动物带到这里,设施的工作人员(志愿者或非志愿者)喂养并照顾它们,并在必要时使它们康复和治愈。该定义包括被遗弃的狗的狗舍、被遗弃的猫的猫舍、其他被遗弃的宠物的庇护所和野生动物恢复中心。 " + "zh_Hans": "动物收容所是一个设施,将遇到麻烦的动物带到这里,设施的工作人员(志愿者或非志愿者)喂养并照顾它们,并在必要时使它们康复和治愈。该定义包括被遗弃的狗的狗舍、被遗弃的猫的猫舍、其他被遗弃的宠物的庇护所和野生动物恢复中心。 ", + "de": "Ein Tierheim ist eine Einrichtung, in die notleidende Tiere gebracht werden und wo das Personal (ob freiwillig oder nicht) sie füttert und pflegt, sie rehabilitiert und bei Bedarf heilt. Diese Definition umfasst Zwinger für ausgesetzte Hunde, Katzenheime für ausgesetzte Katzen, Unterkünfte für andere ausgesetzte Haustiere und Wildtier-Auffangstationen. " }, "source": { "osmTags": "amenity=animal_shelter" diff --git a/assets/layers/atm/atm.json b/assets/layers/atm/atm.json index 42f47c3d84..fd0df6a515 100644 --- a/assets/layers/atm/atm.json +++ b/assets/layers/atm/atm.json @@ -544,7 +544,8 @@ "ca": "Aquest caixer té sortida de veu en {language():font-bold}", "cs": "Tento bankomat má řečový výstup v {language():font-bold}", "he": "לכספומט הזה יש פלט דיבור ב {language():font-bold}", - "pt_BR": "Este caixa eletrônico tem saída de fala em {language():font-bold}" + "pt_BR": "Este caixa eletrônico tem saída de fala em {language():font-bold}", + "es": "Este cajero automático tiene salida de voz en {language():font-bold}" } } } diff --git a/assets/layers/bank/bank.json b/assets/layers/bank/bank.json index 0185dc46ed..865c1ec8c8 100644 --- a/assets/layers/bank/bank.json +++ b/assets/layers/bank/bank.json @@ -49,7 +49,8 @@ "cs": "Má tato banka bankomat?", "he": "האם לבנק הזה יש כספומט?", "pl": "Czy ten bank ma bankomat?", - "pt_BR": "Esse banco tem caixa eletrônico?" + "pt_BR": "Esse banco tem caixa eletrônico?", + "es": "¿Este banco tiene cajero automático?" }, "mappings": [ { diff --git a/assets/layers/bench/bench.json b/assets/layers/bench/bench.json index 6db54ddd67..e237d81f03 100644 --- a/assets/layers/bench/bench.json +++ b/assets/layers/bench/bench.json @@ -865,7 +865,8 @@ "cs": "Zjištěno dnes!", "pt": "Pesquisado hoje!", "he": "נבדק היום!", - "pt_BR": "Pesquisado hoje!" + "pt_BR": "Pesquisado hoje!", + "es": "¡Encuestado hoy!" } } ], @@ -887,7 +888,8 @@ "ca": "Aquest banc té la següent inscripció:

{inscription}

", "cs": "Tato lavice má následující nápis:

{inscription}

", "pt": "Este banco tem a seguinte inscrição:

{inscription}

", - "pt_BR": "Esse banco tem a seguinte inscrição:

{inscription}

" + "pt_BR": "Esse banco tem a seguinte inscrição:

{inscription}

", + "es": "Este banco tiene la siguiente inscripción:

{inscription}

" }, "question": { "en": "Does this bench have an inscription?", @@ -897,7 +899,8 @@ "ca": "Aquest banc té una inscripció?", "cs": "Má tato lavička nápis?", "pt": "Esse banco tem inscrição?", - "pt_BR": "Esse banco tem uma inscrição?" + "pt_BR": "Esse banco tem uma inscrição?", + "es": "¿Este banco tiene una inscripción?" }, "freeform": { "key": "inscription", @@ -917,7 +920,8 @@ "ca": "Aquest banc no té cap inscripció", "cs": "Tato lavička nemá nápis", "pt": "Este banco não tem inscrição", - "pt_BR": "Esse banco não tem uma inscrição" + "pt_BR": "Esse banco não tem uma inscrição", + "es": "Este banco no tiene inscripción" }, "addExtraTags": [ "inscription=" @@ -946,7 +950,8 @@ "ca": "P. ex. en una placa, al respatller, …", "cs": "Např. na připevněné desce, v opěradle, …", "pt": "Por exemplo: em placa montada, no encosto, ...", - "pt_BR": "P. ex. em uma placa montada, no encosto, …" + "pt_BR": "P. ex. em uma placa montada, no encosto, …", + "es": "Por ejemplo, en una placa montada, en el respaldo, …" } }, { @@ -960,7 +965,8 @@ "he": "האם לספסל הזה יש אלמנט אומנותי?", "fr": "Est-ce que ce banc inclut un élément artistique ?", "pl": "Czy ta ławka ma wbudowane dzieło sztuki?", - "pt_BR": "Esse banco tem um elemento artístico?" + "pt_BR": "Esse banco tem um elemento artístico?", + "es": "¿Tiene este banco un elemento artístico?" }, "mappings": [ { @@ -977,7 +983,8 @@ "cs": "Tato lavička má integrované umělecké dílo", "he": "לספסל זה יצירת אמנות משולבת", "pl": "Ta ławka ma wbudowane dzieło sztuki", - "pt_BR": "Esse banco tem uma obra de arte integrada" + "pt_BR": "Esse banco tem uma obra de arte integrada", + "es": "Este banco tiene una obra de arte integrada" } }, { @@ -1006,7 +1013,8 @@ "ca": "Aquest banc probablement no té cap obra d'art integrada", "de": "Die Bank hat vermutlich kein integriertes Kunstwerk", "cs": "Tato lavička pravděpodobně nemá integrované umělecké dílo", - "pt_BR": "Esse banco provavelmente não tem uma obra de arte integrada" + "pt_BR": "Esse banco provavelmente não tem uma obra de arte integrada", + "es": "Este banco probablemente no tiene una obra de arte integrada" }, "hideInAnswer": true } @@ -1019,7 +1027,8 @@ "cs": "Např. má integrovaný obraz, sochu nebo jiné netriviální tvůrčí dílo", "fr": "Par ex. il intègre une peinture, statue ou autre élément non commune, travail cratif", "pl": "Np. jest na niej coś namalowane, ma rzeźbę lub inną nietrywialną pracę", - "pt_BR": "Ex. possui uma pintura integrada, estátua ou outro trabalho criativo não trivial" + "pt_BR": "Ex. possui uma pintura integrada, estátua ou outro trabalho criativo não trivial", + "es": "Por ejemplo, si lleva integrado un cuadro, una estatua u otra obra creativa no trivial" } }, { @@ -1044,7 +1053,8 @@ "cs": "Slouží tato lavička jako památník někoho nebo něčeho?", "pt": "Este banco serve como memorial para alguém ou algo?", "pl": "Czy ta ławka służy jako pomnik upamiętniający kogoś lub coś?", - "pt_BR": "Esse banco serve como memorial para alguém ou alguma coisa?" + "pt_BR": "Esse banco serve como memorial para alguém ou alguma coisa?", + "es": "¿Actúa este banco como monumento conmemorativo de alguien o algo?" }, "mappings": [ { @@ -1058,7 +1068,8 @@ "cs": "Tato lavička je pomníkem pro někoho nebo něco", "pt": "Este banco é um memorial para alguém ou algo", "pl": "Ta ławka jest pomnikiem upamiętniającym kogoś lub coś", - "pt_BR": "Esse banco é um memorial para alguém ou alguma coisa" + "pt_BR": "Esse banco é um memorial para alguém ou alguma coisa", + "es": "Este banco es un memorial para alguien o algo" }, "addExtraTags": [ "memorial=bench", @@ -1081,7 +1092,8 @@ "cs": "Tato lavička není pro někoho nebo něco památníkem", "pt": "Este banco não é um memorial para alguém ou algo", "pl": "Ta ławka nie jest pomnikiem upamiętniającym kogoś lub coś", - "pt_BR": "Esse banco não é um memorial para alguém ou alguma coisa" + "pt_BR": "Esse banco não é um memorial para alguém ou alguma coisa", + "es": "Este banco es un no memorial para alguien o algo" }, "addExtraTags": [ "memorial=" @@ -1115,7 +1127,8 @@ "ca": "és un memorial", "cs": "je památník", "he": "הוא אנדרטה", - "pt_BR": "é um memorial" + "pt_BR": "é um memorial", + "es": "es un monumento conmemorativo" } } ] @@ -1133,7 +1146,8 @@ "cs": "S opěradlem i bez něj", "he": "עם ובלי משענת גב", "pl": "Z oraz bez oparcia", - "pt_BR": "Com e sem encosto" + "pt_BR": "Com e sem encosto", + "es": "Con y sin respaldo" } }, { @@ -1147,7 +1161,8 @@ "cs": "Má opěradlo", "he": "בעל משענת גב", "pl": "Ma oparcie", - "pt_BR": "Tem um encosto" + "pt_BR": "Tem um encosto", + "es": "Tiene respaldo" } }, { @@ -1161,7 +1176,8 @@ "cs": "Nemá opěradlo", "he": "אין משענת גב", "pl": "Nie ma oparcia", - "pt_BR": "Não tem um encosto" + "pt_BR": "Não tem um encosto", + "es": "Sin respaldo" } } ] diff --git a/assets/layers/bench_at_pt/bench_at_pt.json b/assets/layers/bench_at_pt/bench_at_pt.json index 1c9f8bc4b5..10fb1b0bf0 100644 --- a/assets/layers/bench_at_pt/bench_at_pt.json +++ b/assets/layers/bench_at_pt/bench_at_pt.json @@ -261,7 +261,8 @@ "cs": "Na této autobusové zastávce není lavička (nikdy zde nebyla nebo byla odstraněna)", "pt": "Este ponto de ônibus não tem banco (nunca houve ou foi removido)", "pl": "Ten przystanek autobusowy nie ma ławki (nigdy jej nie było lub została usunięta)", - "pt_BR": "Essa parada de ônibus não tem um banco (nunca teve ou ele foi removido)" + "pt_BR": "Essa parada de ônibus não tem um banco (nunca teve ou ele foi removido)", + "es": "Esta parada de autobús no tiene banco (nunca lo hubo o lo han quitado)" } } ], @@ -277,7 +278,8 @@ "cs": "Tato autobusová zastávka se již nepoužívá", "pt": "Este ponto de ônibus não é mais usado", "pl": "Ten przystanek autobusowy nie jest już używany", - "pt_BR": "Essa parada de ônibus não é mais usada" + "pt_BR": "Essa parada de ônibus não é mais usada", + "es": "Esta parada ya no se usa" } } ], diff --git a/assets/layers/bicycle_rental/bicycle_rental.json b/assets/layers/bicycle_rental/bicycle_rental.json index c35c3dc51f..d0ddb56ffc 100644 --- a/assets/layers/bicycle_rental/bicycle_rental.json +++ b/assets/layers/bicycle_rental/bicycle_rental.json @@ -182,7 +182,7 @@ "en": "This is a rental business which rents out various objects and/or vehicles. It rents out bicycles too, but this is not the main focus", "nl": "Dit is een zaak die verschillende voorwerpen en/of voertuigen verhuurt, waaronder ook fietsen; al zijn fietsen niet de hoofdfocus", "de": "Dies ist ein Geschäft, das verschiedene Gegenstände und/oder Fahrzeuge vermietet. Es vermietet auch Fahrräder, aber das ist nicht der Hauptschwerpunkt", - "es": "Este es un negocio de alquileres que alquila varios objetos y/o vehículos. También alquila bicicletas, pero este no es el enfoque principal", + "es": "Se trata de una empresa de alquiler que alquila diversos objetos y/o vehículos. También alquila bicicletas, pero este no es el objetivo principal", "da": "Dette er en udlejningsvirksomhed, som udlejer forskellige genstande og/eller køretøjer. Den udlejer også cykler, men det er ikke det primære fokus", "fr": "C'est une agence louant diverses choses et/ou voitures. Elle loue également des vélos, mais ce n'est pas sa principale activité", "cs": "Jedná se o půjčovnu, která pronajímá různé předměty a/nebo vozidla. Pronajímá také jízdní kola, ale to není hlavní náplní", @@ -218,7 +218,7 @@ "en": "This is an automated docking station, where a bicycle is mechanically locked to a structure", "nl": "Dit is een docking station waar de fietsen mechanisch in een grotere structuur worden vastgemaakt", "de": "Dies ist eine automatisierte Radstation, bei der ein Fahrrad mechanisch an einer Struktur befestigt wird", - "es": "Esta es una estación automática, en la que una bici se asegura mecánicamente a una estructura", + "es": "Se trata de una estación de bicicletas automatizada donde una bicicleta se fija mecánicamente a una estructura", "fr": "C'est un point d’attache automatisé où le vélo est attaché mécaniquement à une structure", "da": "Dette er en automatiseret dockingstation, hvor en cykel låses mekanisk fast i en struktur", "cs": "Jedná se o automatickou dokovací stanici, kde je jízdní kolo mechanicky uzamčeno ke konstrukci", @@ -253,7 +253,7 @@ "en": "This is a dropoff point, e.g. a reserved parking to place the bicycles clearly marked as being for the rental service only", "nl": "Dit is een dropzone, bv. een fietsparkeerplaats die is voorbehouden voor fietsverhuur", "de": "Dies ist ein Rückgabepunkt, z.B. ein reservierter Parkplatz, um die Fahrräder abzustellen, die eindeutig als nur für den Verleih gekennzeichnet sind", - "es": "Este es un punto de entrega, ej. un aparcamiento reservado para colocar las bicicletas, claramente marcado como solo para el servicio de alquiler", + "es": "Se trata de un punto de entrega, por ejemplo, un aparcamiento reservado para colocar las bicicletas claramente señalizado como exclusivo para el servicio de alquiler", "fr": "C'est un point de dépôt, p.ex. un emplacement de parking réservé aux vélos de location", "da": "Dette er et afleveringssted, f.eks. en reserveret parkeringsplads til cykler, som er tydeligt markeret som værende forbeholdt udlejningstjenesten", "cs": "Jedná se o místo předání, např. vyhrazené parkoviště pro umístění jízdních kol, zřetelně označené jako místo určené pouze pro půjčovnu", @@ -519,7 +519,7 @@ "nl": "mountainbikes", "ca": "bicicletas de muntanya", "de": "Mountainbikes", - "es": "bicis de montaña", + "es": "bicicletas de montaña", "da": "mountainbike", "eo": "montobicikloj", "fr": "vélos de montagne", @@ -573,7 +573,7 @@ "de": "Wie viele type_plural können hier gemietet werden?", "fr": "Combien de type_plural peuvent être loués ici ?", "cs": "Kolik typů kol si zde můžete pronajmout?", - "es": "¿Cuántas type_plural pueden alquilarse aquí?", + "es": "¿Cuántos type_plural se pueden alquilar aquí?", "ca": "Quants type_plural es poden llogar aquí?", "pt_BR": "Quantos type_plural podem ser alugados aqui?" }, diff --git a/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json b/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json index 559b314dc1..a3e405fa5b 100644 --- a/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json +++ b/assets/layers/bicycle_tube_vending_machine/bicycle_tube_vending_machine.json @@ -12,7 +12,8 @@ "pt": "Máquina de venda automática de tubos de bicicleta", "da": "Automat til salg af cykelslanger", "cs": "Automat na cyklistické duše", - "ca": "Màquina expenedora de tubs de bicicleta" + "ca": "Màquina expenedora de tubs de bicicleta", + "es": "Máquina expendedora de tubos de bicicleta" }, "description": { "en": "A layer showing vending machines for bicycle tubes (either purpose-built bicycle tube vending machines or classical vending machines with bicycle tubes and optionally additional bicycle related objects such as lights, gloves, locks, …)", @@ -22,7 +23,8 @@ "fr": "Une couche affichant des distributeurs automatiques de chambre à air (que ce soit des distributeurs conçus spécifiquement pour les chambres à air ou des distributeurs classiques incluant des chambres à air ainsi des objets apparentés tels que de l'éclairage pour vélo, des gants, des cadenas, ...)", "cs": "Vrstva zobrazující automaty na cyklistické duše (buď speciální automaty na cyklistické duše, nebo klasické automaty s cyklistickými dušemi a případně dalšími předměty souvisejícími s jízdními koly, jako jsou světla, rukavice, zámky, ...)", "ca": "Una capa que mostra màquines expenedores per a tubs de bicicleta (ja siguin màquines expenedores de tubs de bicicleta o màquines expenedores clàssiques amb tubs de bicicleta i opcionalment objectes addicionals relacionats amb la bicicleta com ara llums, guants, panys, ...)", - "pt_BR": "Uma camada que mostra máquinas de venda de câmaras de ar de bicicleta (sejam máquinas de venda de câmaras de ar de bicicleta específicas ou máquinas de venda clássicas com câmaras de ar de bicicleta e opcionalmente objetos relacionados a bicicletas, como luzes, luvas, travas, ...)" + "pt_BR": "Uma camada que mostra máquinas de venda de câmaras de ar de bicicleta (sejam máquinas de venda de câmaras de ar de bicicleta específicas ou máquinas de venda clássicas com câmaras de ar de bicicleta e opcionalmente objetos relacionados a bicicletas, como luzes, luvas, travas, ...)", + "es": "Una capa que muestra máquinas expendedoras de cámaras de bicicleta (ya sean máquinas expendedoras de cámaras de bicicleta especialmente diseñadas o máquinas expendedoras clásicas con cámaras de bicicleta y, opcionalmente, objetos adicionales relacionados con la bicicleta, como luces, guantes, candados, etc.)" }, "source": { "osmTags": { @@ -57,7 +59,8 @@ "de": "{name} Fahrradschlauch-Automat", "cs": "Automat na cyklistické pláště{name}", "nl": "Fietsbanden-verkoopautomaat {name}", - "pt_BR": "Máquina de venda de câmara de ar para bicicleta {name}" + "pt_BR": "Máquina de venda de câmara de ar para bicicleta {name}", + "es": "Máquina expendedora de cámaras de bicicleta {name}" } } ] @@ -118,7 +121,8 @@ "pt": "uma máquina de venda automática de tubos de bicicleta", "da": "en automat til salg af cykelslanger", "cs": "automat na cyklistické duše", - "ca": "una màquina expenedora de tubs de bicicleta" + "ca": "una màquina expenedora de tubs de bicicleta", + "es": "una máquina expendedora de tubos de bicicleta" }, "tags": [ "amenity=vending_machine", @@ -233,7 +237,8 @@ "de": "Wie viel kostet ein Fahrradschlauch?", "cs": "Kolik stojí duše na kolo?", "nl": "Hoeveel kost een fietsband?", - "pt_BR": "Quanto custa uma câmara de ar para bicicleta?" + "pt_BR": "Quanto custa uma câmara de ar para bicicleta?", + "es": "¿Cuánto cuesta una cámara para la bicicleta?" }, "render": { "en": "A bicycle tube costs {charge}", @@ -241,7 +246,8 @@ "de": "Ein Fahrradschlauch kostet {charge}", "cs": "Cena jedné duše {charge}", "nl": "Een fietsband kost {charge}", - "pt_BR": "Uma câmara de ar para bicicleta custa {charge}" + "pt_BR": "Uma câmara de ar para bicicleta custa {charge}", + "es": "Una cámara para bicicletas cuesta {charge}" }, "freeform": { "key": "charge" @@ -256,7 +262,8 @@ "de": "Welche Fahrradschläuche werden hier verkauft?", "cs": "Jaká značka duší je zde prodávána?", "nl": "Welk merk banden wordt hier verkocht?", - "pt_BR": "Quais marcas de câmara de ar são vendidas aqui?" + "pt_BR": "Quais marcas de câmara de ar são vendidas aqui?", + "es": "¿Qué cámaras se venden aquí?" }, "freeform": { "key": "brand" @@ -267,7 +274,8 @@ "de": "Hier werden Fahrradschläuche von {brand} verkauft", "cs": "{brand} duše jsou zde prodávány", "nl": "{brand} banden worden hier verkocht", - "pt_BR": "câmaras de ar {brand} são vendidas aqui" + "pt_BR": "câmaras de ar {brand} são vendidas aqui", + "es": "Las cámaras {brand} se venden aquí" }, "mappings": [ { @@ -278,7 +286,8 @@ "de": "Hier werden Fahrradschläuche von Continental verkauft", "cs": "Continental duše jsou zde prodávány", "nl": "Continental banden worden hier verkocht", - "pt_BR": "Câmaras de ar Continental são vendidas aqui" + "pt_BR": "Câmaras de ar Continental são vendidas aqui", + "es": "Las cámaras Continental se venden aquí" } }, { @@ -289,7 +298,8 @@ "de": "Hier werden Fahrradschläuche von Schwalbe verkauft", "cs": "Schwalbe duše jsou zde prodávány", "nl": "Schwalbe banden worden hier verkocht", - "pt_BR": "Câmaras de ar Schwalbe são vendidas aqui" + "pt_BR": "Câmaras de ar Schwalbe são vendidas aqui", + "es": "Las cámaras Schwalbe se venden aquí" } } ], @@ -303,7 +313,8 @@ "de": "Wer betreibt den Automaten?", "cs": "Kdo se stará o tento automat?", "nl": "Wie onderhoudt deze verkoopautomaat?", - "pt_BR": "Quem mantém essa máquina de venda?" + "pt_BR": "Quem mantém essa máquina de venda?", + "es": "¿Quién mantiene esta máquina expendedora?" }, "render": "This vending machine is maintained by {operator}", "mappings": [ @@ -315,7 +326,8 @@ "de": "Betrieben von Schwalbe", "cs": "Udržuje Schwalbe", "nl": "Onderhouden door Schwalbe", - "pt_BR": "Mantido pela Schwalbe" + "pt_BR": "Mantido pela Schwalbe", + "es": "Mantenido por Schwalbe" } }, { @@ -326,7 +338,8 @@ "de": "Betrieben von Continental", "cs": "Udržuje Continental", "nl": "Onderhouden door Continental", - "pt_BR": "Mantido pela Continental" + "pt_BR": "Mantido pela Continental", + "es": "Mantenido por Continental" } } ], @@ -343,7 +356,8 @@ "de": "Wird weiteres Fahrradzubehör verkauft?", "cs": "Prodávají se zde další doplňky na kolo?", "nl": "Worden hier andere fietsaccessoires verkocht?", - "pt_BR": "Outros acessórios para bicicleta são vendidos aqui?" + "pt_BR": "Outros acessórios para bicicleta são vendidos aqui?", + "es": "¿Se venden aquí otros accesorios para bicicletas?" }, "mappings": [ { @@ -354,7 +368,8 @@ "ca": "Aquí es venen cambres d'aire de bicicletes", "de": "Hier werden Fahrradschläuche verkauft", "cs": "Zde se prodávají cyklistické duše", - "pt_BR": "Câmaras de ar para bicicletas são vendidas aqui" + "pt_BR": "Câmaras de ar para bicicletas são vendidas aqui", + "es": "Aquí se venden cámaras de aire para bicicletas" } }, { @@ -365,7 +380,8 @@ "ca": "Aquí es venen llums per a bicicletes", "de": "Hier werden Fahrradlampen verkauft", "cs": "Zde se prodávají světla na jízdní kola", - "pt_BR": "Luzes para bicicleta são vendidas aqui" + "pt_BR": "Luzes para bicicleta são vendidas aqui", + "es": "Las luces para bicicletas se venden aquí" } }, { @@ -376,7 +392,8 @@ "ca": "Aquí es venen guants", "de": "Hier werden Fahrradhandschuhe verkauft", "cs": "Prodávají se zde rukavice", - "pt_BR": "Luvas são vendidas aqui" + "pt_BR": "Luvas são vendidas aqui", + "es": "Los guantes se venden aquí" } }, { @@ -387,7 +404,8 @@ "ca": "Aquí es venen kits de reparació de bicicletes", "de": "Hier werden Fahrrad-Reparatursets verkauft", "cs": "Zde se prodávají sady na opravu jízdních kol", - "pt_BR": "Kits para reparo de bicicleta são vendidos aqui" + "pt_BR": "Kits para reparo de bicicleta são vendidos aqui", + "es": "Los kits de reparación de bicicletas se venden aquí" } }, { @@ -398,7 +416,8 @@ "ca": "Aquí es venen bombes de bicicletes", "de": "Hier werden Fahrradpumpen verkauft", "cs": "Prodávají se zde pumpy na kolo", - "pt_BR": "Bombas de ar para bicicleta são vendidas aqui" + "pt_BR": "Bombas de ar para bicicleta são vendidas aqui", + "es": "Las bombas para bicicletas se venden aquí" } }, { @@ -409,7 +428,8 @@ "ca": "Aquí es venen cadenats per a bicicletes", "de": "Hier werden Fahrradschlösser verkauft", "cs": "Prodávají se zde zámky na kola", - "pt_BR": "Cadeados para bicicleta são vendidos aqui" + "pt_BR": "Cadeados para bicicleta são vendidos aqui", + "es": "Aquí se venden candados para bicicletas" } } ], diff --git a/assets/layers/bike_cafe/bike_cafe.json b/assets/layers/bike_cafe/bike_cafe.json index d87b439d69..17e2289fd2 100644 --- a/assets/layers/bike_cafe/bike_cafe.json +++ b/assets/layers/bike_cafe/bike_cafe.json @@ -249,7 +249,7 @@ "ru": "Есть ли здесь инструменты для починки вашего велосипеда?", "pt_BR": "Há ferramentas aqui para consertar sua bicicleta?", "pt": "Há ferramentas aqui para consertar a sua própria bicicleta?", - "es": "¿Hay herramientas para reparar su propia bicicleta?", + "es": "¿Se ofrecen herramientas para reparar tu propia bicicleta?", "da": "Er der værktøj her til at reparere din egen cykel?", "cs": "Je nabízeno nářadí k opravě vlastního kola?", "ca": "S'ofereixen eines per reparar la teva pròpia bicicleta?" diff --git a/assets/layers/bike_cleaning/bike_cleaning.json b/assets/layers/bike_cleaning/bike_cleaning.json index af97377570..90eed4059f 100644 --- a/assets/layers/bike_cleaning/bike_cleaning.json +++ b/assets/layers/bike_cleaning/bike_cleaning.json @@ -193,7 +193,7 @@ "en": "Free to use", "de": "Kostenlose Nutzung", "nl": "Gratis te gebruiken", - "es": "Gratis", + "es": "Uso gratuito", "fr": "Utilisation gratuite", "da": "Gratis at bruge", "cs": "Bezplatné používání", @@ -247,7 +247,7 @@ "en": "This cleaning service is free to use", "de": "Der Reinigungsservice ist kostenlos", "nl": "Dit fietsschoonmaakpunt is gratis te gebruiken", - "es": "Servicio de limpieza gratis", + "es": "Este servicio de limpieza es gratuito", "fr": "Service de nettoyage gratuit", "da": "Gratis at bruge rengøringsservice", "cs": "Tato mycí služba je bezplatná", diff --git a/assets/layers/bike_repair_station/bike_repair_station.json b/assets/layers/bike_repair_station/bike_repair_station.json index 52c89c353b..81ddf1bb48 100644 --- a/assets/layers/bike_repair_station/bike_repair_station.json +++ b/assets/layers/bike_repair_station/bike_repair_station.json @@ -937,7 +937,7 @@ "it": "Sclaverand (detta anche Presta)", "ru": "Клапан Presta (также известный как французский клапан)", "da": "Sclaverand/Presta (cykeldæk med smal bredde)", - "es": "Sclaverand/Presata (ruedas de bicicleta estrechas)", + "es": "Sclaverand/Presta (neumáticos de bicicleta estrechos)", "cs": "Sclaverand/Presta (úzké cyklistické pláště)", "ca": "Sclaverand/Presta (pneumàtics per a bicis estrets)" } diff --git a/assets/layers/bike_shop/bike_shop.json b/assets/layers/bike_shop/bike_shop.json index 340c487227..6eb5e8181d 100644 --- a/assets/layers/bike_shop/bike_shop.json +++ b/assets/layers/bike_shop/bike_shop.json @@ -86,7 +86,7 @@ "de": "Sportartikelgeschäft {name}", "pt_BR": "Loja de equipamentos esportivos {name}", "pt": "Loja de equipamentos desportivos {name}", - "es": "Tienda de artículos deportivos {name}", + "es": "Tienda de material deportivo {name}", "da": "Butik med sportsudstyr {name}", "ca": "Botiga d'equipament esportiu {name}", "cs": "Obchod se sportovním vybavením {name}" @@ -133,7 +133,7 @@ "de": "Fahrradverleih {name}", "pt_BR": "Aluguel de bicicletas {name}", "pt": "Aluguel de bicicletas {name}", - "es": "Alquiler de bicicletas {name}", + "es": "Tienda de alquiler de bicicletas {name}", "da": "Cykeludlejning {name}", "ca": "Botiga de lloguer de bicicletes {name}", "cs": "Půjčovna kol {name}" diff --git a/assets/layers/bike_themed_object/bike_themed_object.json b/assets/layers/bike_themed_object/bike_themed_object.json index d6e7472fed..963a5232f2 100644 --- a/assets/layers/bike_themed_object/bike_themed_object.json +++ b/assets/layers/bike_themed_object/bike_themed_object.json @@ -45,7 +45,7 @@ "fr": "Objet cycliste", "de": "Fahrradbezogenes Objekt", "it": "Oggetto relativo alle bici", - "es": "Objeto relacionado con bicis", + "es": "Objeto relacionado con las bicicletas", "da": "Cykelrelateret objekt", "ca": "Objecte relacionat amb bicis", "cs": "Objekt související s jízdním kolem", diff --git a/assets/layers/cafe_pub/cafe_pub.json b/assets/layers/cafe_pub/cafe_pub.json index ec122c5697..bb14175894 100644 --- a/assets/layers/cafe_pub/cafe_pub.json +++ b/assets/layers/cafe_pub/cafe_pub.json @@ -7,7 +7,7 @@ "fr": "Cafés et pubs", "zh_Hant": "咖啡廳與酒吧", "hu": "Kávézók és kocsmák", - "es": "Cafeterías y bares", + "es": "Cafeterías y pubs", "da": "Caféer og pubber", "ca": "Cafés i bars", "pl": "Kawiarnie i puby", @@ -73,19 +73,22 @@ { "if": "amenity=bar", "then": { - "en": "Bar" + "en": "Bar", + "de": "Bar" } }, { "if": "amenity=cafe", "then": { - "en": "Cafe" + "en": "Cafe", + "de": "Café" } }, { "if": "amenity=nightclub", "then": { - "en": "Nightclub" + "en": "Nightclub", + "de": "Nachtclub" } } ] @@ -213,7 +216,7 @@ "nl": "Dit is een cafe - een plaats waar men rustig kan zitten om een thee, koffie of alcoholische drank te nuttigen.", "de": "Ein Café, um in ruhiger Umgebung Tee, Kaffee oder ein alkoholisches Getränk zu trinken", "da": "En café til at drikke te, kaffe eller en alkoholisk drik i rolige omgivelser", - "es": "Una cafetería para beber té, café o una bebida alcohólica en un ambiente tranquilo", + "es": "Un café para tomar té, café o una bebida alcohólica en un ambiente tranquilo", "fr": "Un café pour prendre un thé, un café ou une boisson alcoolisée dans un environnement calme", "ca": "Una cafeteria per a a beure té, café o una beguda alcohólica en un ambient tranquil", "cs": "kavárna, kde si můžete v klidném prostředí vypít čaj, kávu nebo alkoholický nápoj" @@ -257,7 +260,7 @@ "fr": "Quel est le nom de ce pub ?", "hu": "Mi a neve ennek a kocsmának?", "da": "Hvad hedder denne pub?", - "es": "¿Cual es el nombre de este pub?", + "es": "¿Cuál es el nombre de esta empresa?", "ca": "Quin és el nom d'aquest negoci?", "cs": "Jak se tento podnik jmenuje?" }, @@ -268,7 +271,7 @@ "fr": "Ce pub se nomme {name}", "hu": "A kocsma neve: {name}", "da": "Denne pub hedder {name}", - "es": "Este pub se llama {name}", + "es": "Esta empresa se llama {name}", "ca": "Aquest negoci es diu {name}", "cs": "Tento podnik se jmenuje {name}" }, @@ -337,7 +340,7 @@ "nl": "Dit is een restaurant waar men een maaltijd geserveerd krijgt", "de": "Ein Restaurant, in dem man ordentlich essen kann", "da": "En restaurant, hvor man kan få et ordentligt måltid", - "es": "Un restaurante donde puedes comer una comida de verdad", + "es": "Un restaurante donde se pueda comer como es debido", "fr": "Un restaurant où l'on peut prendre un bon repas", "ca": "Un restaurant on es pot menjar bé", "cs": "Restaurace, kde se dá pořádně najíst" diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 1878f1ffe4..91d305c3ff 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -259,7 +259,7 @@ "cs": "Nepřístupné široké veřejnosti (např. přístupné pouze majitelům, zaměstnancům, ...)", "da": "Ikke tilgængelig for offentligheden (f.eks. kun tilgængelig for ejere, ansatte, ...)", "de": "Die Station ist nicht für die Allgemeinheit zugänglich (z. B. nur für die Eigentümer, Mitarbeiter, …)", - "es": "No accesible al público general (ej. solo accesible a los propietarios, empleados, ...)" + "es": "No accesible al público en general (por ejemplo, sólo accesible a los propietarios, empleados, ...)" } }, { @@ -4343,7 +4343,7 @@ "cs": "Placené použití, ale zdarma pro zákazníky hotelu/podniku/nemocnice/..., který dobíjecí stanici provozuje", "da": "Betalt brug, men gratis for kunder på det hotel/pub/hospital/... der driver ladestationen", "de": "Die Nutzung ist kostenpflichtig, aber für Kunden des Betreibers der Einrichtung, wie Hotel, Krankenhaus, … kostenlos", - "es": "De pago, pero gratis para clientes del hotel/pub/hostpital... quien opera la estación de carga" + "es": "Pago por uso, pero gratuito para los clientes del hotel/pub/hospital/... que gestiona la estación de carga" } }, { diff --git a/assets/layers/crossings/crossings.json b/assets/layers/crossings/crossings.json index b7936f6825..efe9304baf 100644 --- a/assets/layers/crossings/crossings.json +++ b/assets/layers/crossings/crossings.json @@ -7,7 +7,7 @@ "fr": "Traversée", "ca": "Encreuaments", "da": "Overgange", - "es": "Cruces", + "es": "Pasos", "pa_PK": "کراسنگاں", "cs": "Přechody" }, @@ -17,7 +17,7 @@ "de": "Übergänge für Fußgänger und Radfahrer", "fr": "Traversée pour piétons et cyclistes", "da": "Overgange for fodgængere og cyklister", - "es": "Cruces para peatones y ciclistas", + "es": "Pasos para peatones y ciclistas", "ca": "Creuaments per a vianants i ciclistes", "cs": "Přechody pro chodce a cyklisty" }, diff --git a/assets/layers/cycleways_and_roads/cycleways_and_roads.json b/assets/layers/cycleways_and_roads/cycleways_and_roads.json index a40134ee13..ad0af1e8d8 100644 --- a/assets/layers/cycleways_and_roads/cycleways_and_roads.json +++ b/assets/layers/cycleways_and_roads/cycleways_and_roads.json @@ -13,7 +13,7 @@ "en": "All infrastructure that someone can cycle over, accompanied with questions about this infrastructure", "nl": "Alle infrastructuur waar je over kunt fietsen, met vragen over die infrastructuur", "de": "Infrastruktur, die man mit dem Fahrrad befahren kann, begleitet von diesbezüglichen Fragen", - "es": "Toda la infraestructura sobre la que alguien puede ir en bici, acompañado de preguntas sobre esta infraestructura\"", + "es": "Todas las infraestructuras sobre las que se puede circular en bicicleta, acompañadas de preguntas sobre las mismas", "fr": "Toutes les infrastructures sur lesquelles quelqu'un peut rouler, accompagnées de questions sur cette infrastructure", "ca": "Totes les infraestructures per les quals algú pot ciclar, acompanyades de preguntes sobre aquesta infraestructura", "cs": "Veškerá infrastruktura, kterou může někdo projet na kole, doplněná o otázky týkající se této infrastruktury" @@ -91,7 +91,7 @@ "fr": "Piste cyclable", "ca": "Via ciclista", "da": "Cykelsti", - "es": "Carril compartido", + "es": "Carril bici", "pa_PK": "سائیکل‌وے", "cs": "Cyklostezka" } @@ -121,7 +121,7 @@ "de": "Gemeinsame Fahrspur", "fr": "Voie partagée", "ca": "Carril compartit", - "es": "Vía ciclista al lado de la carretera", + "es": "Carril compartido", "cs": "Sdílený jízdní pruh" } }, diff --git a/assets/layers/defibrillator/defibrillator.json b/assets/layers/defibrillator/defibrillator.json index 07a4ea92c9..23fa175074 100644 --- a/assets/layers/defibrillator/defibrillator.json +++ b/assets/layers/defibrillator/defibrillator.json @@ -253,7 +253,7 @@ "then": { "en": "Not accessible to the general public (e.g. only accesible to staff, the owners, …)", "ca": "No accessible al públic en general (ex. només accesible a treballadors, propietaris, ...)", - "es": "No accesible al público en general (ex. sólo accesible a trabajadores, propietarios, ...)", + "es": "No accesible al público en general (por ejemplo, sólo accesible para el personal, los propietarios, ...)", "fr": "Non accessible au public (par exemple réservé au personnel, au propriétaire…)", "nl": "Niet toegankelijk voor het publiek (bv. enkel voor personeel, de eigenaar, …)", "de": "Der Defibrillator ist nicht öffentlich zugänglich (z.B. nur für Personal, Eigentümer, …)", @@ -443,7 +443,7 @@ "it": "Informazioni supplementari circa la posizione (in lingua locale):
{defibrillator:location}", "de": "Zusätzliche Informationen über den Standort (in der Landessprache):
{defibrillator:location}", "sl": "Dodatne informacije o lokaciji (v lokalnem jeziku):
{defibrillator:location}", - "es": "Información a mayores sobre la localización (en el idioma local):
{defibrillator:location}", + "es": "Información adicional sobre la ubicación (en el idioma local):
{defibrillator:location}", "ca": "Informació extra sobre la localització (en la llengua local):
{defibrillator:location}", "pl": "Dodatkowe informacje o lokalizacji (w lokalnym języku):
{defibrillator:location}", "cs": "Další informace o místě (v místním jazyce):
{defibrillator:location}" diff --git a/assets/layers/drinking_water/drinking_water.json b/assets/layers/drinking_water/drinking_water.json index 6baf77ad3e..23dd623aa0 100644 --- a/assets/layers/drinking_water/drinking_water.json +++ b/assets/layers/drinking_water/drinking_water.json @@ -108,7 +108,8 @@ "cs": "pitná voda" }, "description": { - "en": "Typically a drinking fountain, water tap, water well or natural spring" + "en": "Typically a drinking fountain, water tap, water well or natural spring", + "de": "Typischerweise ein Trinkbrunnen, Wasserhahn, Brunnen oder eine natürliche Quelle" }, "tags": [ "amenity=drinking_water" diff --git a/assets/layers/entrance/entrance.json b/assets/layers/entrance/entrance.json index 3d5f77edc7..1d17bdea59 100644 --- a/assets/layers/entrance/entrance.json +++ b/assets/layers/entrance/entrance.json @@ -500,7 +500,7 @@ "en": "This door has a width of {canonical(width)}", "nl": "Deze deur heeft een breedte van {canonical(width)}", "de": "Diese Tür hat eine Durchgangsbreite von {canonical(width)}", - "es": "Esta puerta tiene una ancho de {canonical(width)} metros", + "es": "Esta puerta tiene una ancho de {canonical(width)}", "fr": "Cette porte a une largeur de {canonical(width)} mètre", "ca": "Aquesta porta té una amplària de {canonical(width)}", "cs": "Tyto dveře mají šířku {canonical(width)}" diff --git a/assets/layers/favourite/favourite.json b/assets/layers/favourite/favourite.json new file mode 100644 index 0000000000..73ab805729 --- /dev/null +++ b/assets/layers/favourite/favourite.json @@ -0,0 +1,71 @@ +{ + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "marker": [ + { + "icon": { + "render": "heart", + "mappings": [ + { + "if": "_favourite=no", + "then": "heart_outline" + } + ] + }, + "color": "red" + } + ] + } + ], + "description": { + "en": "A generic map layer which shows locations that a contributor marked as favourite", + "nl": "Een laag met persoonlijke favourieten" + }, + "name": { + "en": "Favourites", + "nl": "Favorieten" + }, + "id": "favourite", + "source": "special", + "isShown": "_favourite=yes", + "minzoom": 0, + "title": { + "render": { + "en": "Favourite location", + "nl": "Favoriete locatie" + }, + "mappings": [ + { + "if": "name~*", + "then": { + "*": "{name}" + } + } + ] + }, + "tagRenderings": [ + { + "id": "Explanation", + "classes": "thanks", + "icon": { + "class": "large", + "path": "heart" + }, + "render": { + "en": "You marked this location as a personal favourite. As such, it is shown on every map you load.", + "nl": "Je hebt deze locatie als persoonlijke favoriet aangeduid en worden op alle MapComplete-kaarten getoond." + } + }, + { + "id": "show_images", + "render": { + "*": "{image_carousel()}" + } + }, + "all_tags" + ] +} diff --git a/assets/layers/filters/filters.json b/assets/layers/filters/filters.json index 20173e4eb0..fafcd6dba9 100644 --- a/assets/layers/filters/filters.json +++ b/assets/layers/filters/filters.json @@ -14,7 +14,7 @@ "nl": "Nu open", "de": "Jetzt geöffnet", "ca": "Obert ara", - "es": "Abierta ahora", + "es": "Abierto", "fr": "Ouvert maintenant", "hu": "Most nyitva van", "da": "Åbent nu", diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index 41248ae398..138498c463 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -247,7 +247,7 @@ "nl": "Wat is de naam van deze eetgelegenheid?", "en": "What is the name of this business?", "de": "Was ist der Name dieses Unternehmens?", - "es": "¿Cual es el nombre de este restaurante?", + "es": "¿Cuál es el nombre de este negocio?", "fr": "Quel est le nom de ce restaurant ?", "ca": "Quin és el nom d'aquest negoci?", "cs": "Jak se tento podnik jmenuje?" @@ -256,7 +256,7 @@ "nl": "De naam van deze eetgelegeheid is {name}", "en": "The name of this business is {name}", "de": "Dieses Unternehmen heißt {name}", - "es": "El nombre de este restaurante es {name}", + "es": "El nombre de este negocio es {name}", "fr": "Le nom de ce restaurant est {name}", "ca": "El nom d'aquest negoci és {name}", "cs": "Název tohoto podniku je {name}" @@ -283,7 +283,7 @@ "en": "This is a fast-food business, focused on fast service. If seating is available, it is rather limited and functional.", "nl": "Dit is een fastfood-zaak. De focus ligt op snelle bediening, zitplaatsen zijn vaak beperkt en functioneel.", "de": "Es handelt sich um einen Schnellimbiss, mit Fokus auf schnelle Bedienung am Tresen. Sitzmöglichkeiten sind begrenzt und funktional.", - "es": "Este es un negocio de comida rápida, centrado en servicio rápido. Si hay asientos disponibles, son más bien limitados y funcionales.", + "es": "Se trata de un negocio de comida rápida, centrado en el servicio rápido. Si hay asientos disponibles, son más bien limitados y funcionales.", "fr": "C'est un fast-food, centrée sur le service rapide. Si des places sont disponibles, elles sont plutôt limitées et fonctionnelles.", "hu": "Ez egy gyorsétterem (büfé), amely a gyors kiszolgálásra összpontosít. Ha vannak is ülőhelyek, ezek meglehetősen korlátozottak és funkcionálisak.", "ca": "Aquest és un negoci de menjar ràpid, centrat en el servei ràpid. Si hi han seients disponibles, aquests seràn limitats i funcionals.", @@ -296,7 +296,7 @@ "en": "A restaurant, focused on creating a nice experience where one is served at the table", "nl": "Dit is een restaurant. De focus ligt op een aangename ervaring waar je aan tafel wordt bediend", "de": "Es handelt sich um ein Restaurant, mit Fokus auf eine nette Atmosphäre und Tischbedienung", - "es": "Un restaurante, centrado en crear una buena experiencia donde se sirve en la mesa", + "es": "Un restaurante, centrado en crear una experiencia agradable en la que uno se sirve en mesa", "fr": "Un restaurant, axé sur la création d'une expérience agréable où l'on est servi à table", "ca": "Un restaurant, centrat en crear una bona experiència on es serveix a taula", "cs": "Restaurace zaměřená na vytvoření příjemného zážitku, kde se obsluhuje u stolu" @@ -317,7 +317,7 @@ "nl": "Welk soort gerechten worden hier geserveerd?", "en": "What kind of food is served here?", "de": "Was für Essen gibt es hier?", - "es": "¿Qué comida se sirve aquí?", + "es": "¿Qué tipo de comida sirven aquí?", "fr": "Quelle type de nourriture est servie ici ?", "ca": "Quin menjar es serveix aquí?", "cs": "Jaké jídlo se zde podává?" @@ -442,7 +442,7 @@ "en": "This is an Italian restaurant (which serves more than pasta and pizza)", "nl": "Dit is een Italiaans restaurant (dat meer dan enkel pasta of pizza verkoopt)", "de": "Dies ist ein italienisches Restaurant (das mehr als nur Pasta und Pizza serviert)", - "es": "Este es un restaurante italiano (que sirve más que pasta y pizza)", + "es": "Este es un restaurante italiano (que sirve algo más que pasta y pizza)", "fr": "C'est un Restaurant Italien (qui sert plus que des pâtes et des pizzas)", "ca": "Això és un restaurant italià (que serveix més que pasta i pizza)", "cs": "Toto je italská restaurace (která nabízí více než těstoviny a pizzu)" @@ -588,7 +588,7 @@ "nl": "Biedt deze zaak een afhaalmogelijkheid aan?", "en": "Does this place offer take-away?", "de": "Werden Gerichte zum Mitnehmen angeboten?", - "es": "¿Este lugar ofrece para llevar?", + "es": "¿Este local ofrece comida para llevar?", "fr": "Cet établissement propose-t-il des plats à emporter ?", "ca": "Aquest lloc ofereix per a emportar?", "cs": "Nabízí toto místo jídlo s sebou?" diff --git a/assets/layers/guidepost/guidepost.json b/assets/layers/guidepost/guidepost.json index f96f0a53a1..a3bfcd6967 100644 --- a/assets/layers/guidepost/guidepost.json +++ b/assets/layers/guidepost/guidepost.json @@ -59,7 +59,8 @@ { "id": "type", "question": { - "en": "What kind of routes are shown on this guidepost?" + "en": "What kind of routes are shown on this guidepost?", + "de": "Welche Arten von Routen werden auf diesem Wegweiser angezeigt?" }, "multiAnswer": true, "mappings": [ @@ -67,35 +68,40 @@ "if": "bicycle=yes", "ifnot": "bicycle=", "then": { - "en": "This guidepost shows bicycle routes" + "en": "This guidepost shows bicycle routes", + "de": "Dieser Wegweiser zeigt Fahrradrouten" } }, { "if": "hiking=yes", "ifnot": "hiking=", "then": { - "en": "This guidepost shows hiking routes" + "en": "This guidepost shows hiking routes", + "de": "Dieser Wegweiser zeigt Wanderwege" } }, { "if": "mtb=yes", "ifnot": "mtb=", "then": { - "en": "This guidepost shows mountain bike routes" + "en": "This guidepost shows mountain bike routes", + "de": "Dieser Wegweiser zeigt Mountainbike-Routen" } }, { "if": "horse=yes", "ifnot": "horse=", "then": { - "en": "This guidepost shows horse riding routes" + "en": "This guidepost shows horse riding routes", + "de": "Dieser Wegweiser zeigt Reitwege" } }, { "if": "ski=yes", "ifnot": "ski=", "then": { - "en": "This guidepost shows ski routes" + "en": "This guidepost shows ski routes", + "de": "Dieser Wegweiser zeigt Skirouten" } } ] diff --git a/assets/layers/ice_cream/ice_cream.json b/assets/layers/ice_cream/ice_cream.json index da26f36562..b6d6576a97 100644 --- a/assets/layers/ice_cream/ice_cream.json +++ b/assets/layers/ice_cream/ice_cream.json @@ -1,23 +1,27 @@ { "id": "ice_cream", "name": { - "en": "Ice cream parlors" + "en": "Ice cream parlors", + "de": "Eisdielen" }, "description": { - "en": "A place where ice cream is sold over the counter" + "en": "A place where ice cream is sold over the counter", + "de": "Ein Ort, an dem Eiscreme an der Theke verkauft wird" }, "source": { "osmTags": "amenity=ice_cream" }, "title": { "render": { - "en": "Ice cream parlor" + "en": "Ice cream parlor", + "de": "Eisdiele" }, "mappings": [ { "if": "name~*", "then": { - "en": "{name}" + "en": "{name}", + "de": "{name}" } } ] @@ -48,13 +52,15 @@ "presets": [ { "title": { - "en": "an ice cream parlor" + "en": "an ice cream parlor", + "de": "eine Eisdiele" }, "tags": [ "amenity=ice_cream" ], "description": { - "en": "A shop where one can buy only icecream and related items. Ice cream is normally hand-scooped." + "en": "A shop where one can buy only icecream and related items. Ice cream is normally hand-scooped.", + "de": "Ein Geschäft, in dem man nur Eiscreme und damit verbundene Artikel kaufen kann. Normalerweise wird das Eis mit der Hand portioniert." } } ], @@ -63,7 +69,8 @@ { "question": { "en": "What is the name of this ice cream parlor?", - "nl": "Wat is de naam van dit ijssalon?" + "nl": "Wat is de naam van dit ijssalon?", + "de": "Wie heißt diese Eisdiele?" }, "id": "1", "freeform": { @@ -71,7 +78,8 @@ }, "render": { "en": "This ice cream parlor is named {name}", - "nl": "Dit ijssalon heet {name}" + "nl": "Dit ijssalon heet {name}", + "de": "Diese Eisdiele heißt {name}" } }, "opening_hours", diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index cb5da75434..72a387f9e1 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -1,7 +1,7 @@ { "id": "icons", "description": { - "en": "A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI", + "en": "A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI or as icon next to the title", "de": "Eine Ebene, die als Bibliothek für Symbol-Tag-Renderings dient, insbesondere um als Abzeichen neben einem POI angezeigt zu werden", "ca": "Una capa que actua com a biblioteca per a les icones d'etiquetes, especialment per mostrar-se com a insígnia al costat d'un PDI", "cs": "Vrstva sloužící jako knihovna pro ikony-značky, zejména pro zobrazení jako odznak vedle bodu zájmu" @@ -140,6 +140,15 @@ "render": "{share_link()}", "metacondition": "_supports_sharing=yes" }, + { + "id": "favourite_title_icon", + "labels": [ + "defaults" + ], + "render": { + "*":"{favourite_icon()}" + } + }, { "id": "osmlink", "labels": [ @@ -193,6 +202,14 @@ "class": "w-20 mx-1 flex items-center" }, "render": "{rating()}" + }, + { + "id": "favourite_icon", + "description": "Only for rendering", + "condition": "_favourite=yes", + "icon": "circle:white;heart:red", + "metacondition": "__showTimeSensitiveIcons!=no" + } ] } diff --git a/assets/layers/note/note.json b/assets/layers/note/note.json index db0771c2a4..ff548401f4 100644 --- a/assets/layers/note/note.json +++ b/assets/layers/note/note.json @@ -127,7 +127,7 @@ "en": "Report {_first_user} for spam or inappropriate messages", "nl": "{_first_user} melden voor spam of ongepaste opmerkingen", "de": "", - "es": "Reportar {_first_user}", + "es": "Reportar {_first_user} por spam o mensajes inapropiados", "ca": "Reporta {_first_user} per spam o missatges inapropiats", "cs": "Nahlásit uživateli {_first_user} spam nebo nevhodné zprávy" }, diff --git a/assets/layers/playground/playground.json b/assets/layers/playground/playground.json index c29fcdee54..3372d6a051 100644 --- a/assets/layers/playground/playground.json +++ b/assets/layers/playground/playground.json @@ -207,7 +207,7 @@ "ru": "Поверхность - брусчатка", "de": "Der Bodenbelag ist aus Pflastersteinen", "fr": "La surface est en pavés", - "es": "La superficie es adoquines", + "es": "La superficie es de adoquines", "ca": "La superfície són llambordes", "cs": "Povrch je dlažební kostky" } diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index 63c5c66ea5..0618c54594 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -2564,31 +2564,36 @@ "diets" ], "question": { - "en": "Does this place offer a vegan option?" + "en": "Does this place offer a vegan option?", + "de": "Bietet dieser Ort eine vegane Option an?" }, "mappings": [ { "if": "diet:vegan=only", "then": { - "en": "This place only sells vegan products" + "en": "This place only sells vegan products", + "de": "Dieser Ort verkauft nur vegane Produkte" } }, { "if": "diet:vegan=yes", "then": { - "en": "This shop has a big vegan offering" + "en": "This shop has a big vegan offering", + "de": "Dieser Laden hat ein großes veganes Angebot" } }, { "if": "diet:vegan=limited", "then": { - "en": "This shop has a limited vegan offering" + "en": "This shop has a limited vegan offering", + "de": "Dieser Laden hat ein begrenztes veganes Angebot" } }, { "if": "diet:vegan=no", "then": { - "en": "This shop has no vegan offering" + "en": "This shop has no vegan offering", + "de": "Dieser Laden bietet keine veganen Produkte an" } } ] diff --git a/assets/layers/recycling/recycling.json b/assets/layers/recycling/recycling.json index 52dd111c78..2d359d3a66 100644 --- a/assets/layers/recycling/recycling.json +++ b/assets/layers/recycling/recycling.json @@ -1134,7 +1134,7 @@ "en": "Shoes can be recycled here", "nl": "Schoenen kunnen hier gerecycled worden", "de": "Schuhe können hier recycelt werden", - "es": "Aquí se pueden reciclar zapatos", + "es": "El calzado se puede reciclar aquí", "it": "Scarpe", "ca": "Aquí es poden reciclar sabates", "fr": "Les chaussures peuvent être recyclées ici", diff --git a/assets/layers/route_marker/route_marker.json b/assets/layers/route_marker/route_marker.json index 379c9b3103..f47c22eba3 100644 --- a/assets/layers/route_marker/route_marker.json +++ b/assets/layers/route_marker/route_marker.json @@ -1,10 +1,12 @@ { "id": "route_marker", "name": { - "en": "Route markers" + "en": "Route markers", + "de": "Routenmarkierungen" }, "description": { - "en": "Route markers are small markers often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route." + "en": "Route markers are small markers often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route.", + "de": "Routenmarkierungen sind kleine Markierungen, die häufig entlang offizieller Wander-/Rad-/Reit-/Skirouten zu finden sind, um die Richtung der Route anzuzeigen." }, "source": { "osmTags": "information=route_marker" @@ -12,7 +14,8 @@ "minzoom": 14, "title": { "render": { - "en": "Route marker" + "en": "Route marker", + "de": "Routenmarker" } }, "pointRendering": [ @@ -31,14 +34,16 @@ "presets": [ { "title": { - "en": "a route marker" + "en": "a route marker", + "de": "ein Routenmarker" }, "tags": [ "tourism=information", "information=route_marker" ], "description": { - "en": "A route marker is a small marker often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route." + "en": "A route marker is a small marker often found along official hiking/cycling/riding/skiing routes to indicate the direction of the route.", + "de": "Ein Routenmarker ist ein kleiner Marker, der oft entlang offizieller Wander-/Fahrrad-/Reit-/Skirouten zu finden ist, um die Richtung der Route anzuzeigen." }, "exampleImages": [ "./assets/layers/route_marker/bicycle_route_marker.jpg", @@ -51,7 +56,8 @@ { "id": "type", "question": { - "en": "For what kind of route is this marker?" + "en": "For what kind of route is this marker?", + "de": "Für welche Art von Route ist dieser Marker?" }, "multiAnswer": true, "mappings": [ @@ -59,35 +65,40 @@ "if": "bicycle=yes", "ifnot": "biycle=", "then": { - "en": "This is a route marker for a bicycle route." + "en": "This is a route marker for a bicycle route.", + "de": "Dies ist ein Routenmarker für eine Fahrradstrecke." } }, { "if": "hiking=yes", "ifnot": "hiking=", "then": { - "en": "This is a route marker for a hiking route." + "en": "This is a route marker for a hiking route.", + "de": "Dies ist ein Routenmarker für eine Wanderroute." } }, { "if": "mtb=yes", "ifnot": "mtb=", "then": { - "en": "This is a route marker for a mountain bike route." + "en": "This is a route marker for a mountain bike route.", + "de": "Dies ist ein Routenmarker für eine Mountainbikestrecke." } }, { "if": "horse=yes", "ifnot": "horse=", "then": { - "en": "This is a route marker for a horse riding route." + "en": "This is a route marker for a horse riding route.", + "de": "Dies ist ein Routenmarker für eine Reitroute." } }, { "if": "ski=yes", "ifnot": "ski=", "then": { - "en": "This is a route marker for a ski route." + "en": "This is a route marker for a ski route.", + "de": "Dies ist ein Routenmarker für eine Skiroute." } } ] diff --git a/assets/layers/shops/shops.json b/assets/layers/shops/shops.json index e78f1361e4..f52867616c 100644 --- a/assets/layers/shops/shops.json +++ b/assets/layers/shops/shops.json @@ -399,7 +399,8 @@ { "id": "key_cutter", "question": { - "en": "Does this shop offer key cutting?" + "en": "Does this shop offer key cutting?", + "de": "Bietet dieser Laden Schlüsselschneiden an?" }, "mappings": [ { @@ -408,7 +409,8 @@ "hideInAnswer": "shop!=shoe_repair", "icon": "./assets/layers/id_presets/fas-key.svg", "then": { - "en": "This shop is also specialized in key cutting" + "en": "This shop is also specialized in key cutting", + "de": "Dieser Laden ist auch auf das Schlüsselschneiden spezialisiert" }, "addExtraTags": [ "service:key_cutting=yes" @@ -418,7 +420,8 @@ "if": "service:key_cutting=yes", "icon": "./assets/layers/id_presets/fas-key.svg", "then": { - "en": "This shop offers key cutting as a service" + "en": "This shop offers key cutting as a service", + "de": "Dieser Laden bietet Schlüsselschneiden als Dienstleistung an" } }, { @@ -429,7 +432,8 @@ ] }, "then": { - "en": "This shops does not offer key cutting as a service" + "en": "This shops does not offer key cutting as a service", + "de": "Dieser Laden bietet kein Schlüsselschneiden als Dienstleistung an" } } ], diff --git a/assets/layers/slow_roads/slow_roads.json b/assets/layers/slow_roads/slow_roads.json index ccb9e7e03c..f14bdc3497 100644 --- a/assets/layers/slow_roads/slow_roads.json +++ b/assets/layers/slow_roads/slow_roads.json @@ -2,7 +2,8 @@ "id": "slow_roads", "name": { "en": "Paths, carfree and slow roads", - "nl": "Paadjes, trage wegen en autoluwe straten" + "nl": "Paadjes, trage wegen en autoluwe straten", + "de": "Pfade, autofreie und geschwindigkeitsreduzierte Straßen" }, "description": { "en": "All carfree roads", @@ -32,7 +33,8 @@ "title": { "render": { "en": "Slow road", - "nl": "Trage weg" + "nl": "Trage weg", + "de": "Geschwindigkeitsreduzierte Straße" }, "mappings": [ { @@ -46,35 +48,40 @@ "if": "highway=footway", "then": { "en": "Footway", - "nl": "Voetpad" + "nl": "Voetpad", + "de": "Fußweg" } }, { "if": "highway=cycleway", "then": { "en": "Cycleway", - "nl": "Fietspad" + "nl": "Fietspad", + "de": "Radweg" } }, { "if": "highway=pedestrian", "then": { "en": "Pedestrian street", - "nl": "Voetgangersstraat" + "nl": "Voetgangersstraat", + "de": "Fußgängerzone" } }, { "if": "highway=living_street", "then": { "en": "Living street", - "nl": "Woonerf" + "nl": "Woonerf", + "de": "Wohnstraße" } }, { "if": "highway=path", "then": { "en": "Small path", - "nl": "Klein pad" + "nl": "Klein pad", + "de": "Schmaler Pfad" } } ] @@ -133,7 +140,8 @@ "if": "highway=living_street", "then": { "en": "This is a living street", - "nl": "
Dit is een woonerf:
  • Voetgangers mogen hier de volledige breedte van de straat gebruiken
  • Gemotoriseerd verkeer mag maximaal 20km/h rijden
" + "nl": "
Dit is een woonerf:
  • Voetgangers mogen hier de volledige breedte van de straat gebruiken
  • Gemotoriseerd verkeer mag maximaal 20km/h rijden
", + "de": "Dies ist eine Wohnstraße" }, "icon": { "path": "./assets/layers/slow_roads/woonerf.svg", @@ -144,35 +152,40 @@ "if": "highway=pedestrian", "then": { "en": "This is a wide, carfree street", - "nl": "Dit is een brede, autovrije straat" + "nl": "Dit is een brede, autovrije straat", + "de": "Dies ist eine breite, autofreie Straße" } }, { "if": "highway=footway", "then": { "en": "This is a footway", - "nl": "Dit is een voetpaadje" + "nl": "Dit is een voetpaadje", + "de": "Dies ist ein Fußweg" } }, { "if": "highway=path", "then": { "en": "This is a small path", - "nl": "Dit is een wegeltje of bospad" + "nl": "Dit is een wegeltje of bospad", + "de": "Dies ist ein schmaler Pfad" } }, { "if": "highway=bridleway", "then": { "en": "This is a bridleway", - "nl": "Dit is een ruiterswegel" + "nl": "Dit is een ruiterswegel", + "de": "Dies ist ein Reitweg" } }, { "if": "highway=track", "then": { "en": "This is a land access road", - "nl": "Dit is een tractorspoor of weg om landbouwgrond te bereikken" + "nl": "Dit is een tractorspoor of weg om landbouwgrond te bereikken", + "de": "Dies ist eine Zufahrtsstraße" } } ] @@ -180,7 +193,8 @@ { "question": { "en": "What surface does this road have?", - "nl": "Wat is de wegverharding van dit pad?" + "nl": "Wat is de wegverharding van dit pad?", + "de": "Welche Oberfläche hat diese Straße?" }, "render": { "nl": "De ondergrond is {surface}", @@ -318,7 +332,8 @@ "id": "slow_road_is_lit", "question": { "en": "Is this road lit at night?", - "nl": "Is deze weg 's nachts verlicht?" + "nl": "Is deze weg 's nachts verlicht?", + "de": "Ist diese Straße nachts beleuchtet?" }, "mappings": [ { @@ -329,7 +344,8 @@ "if": "lit=no", "then": { "en": "Not lit", - "nl": "Niet verlicht" + "nl": "Niet verlicht", + "de": "Nicht beleuchtet" } } ] diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index 22bb4f1fbc..91dc7dc9d4 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -487,7 +487,7 @@ "nl": "Welke toiletten zijn dit?", "it": "Di che tipo di servizi igienici si tratta?", "ru": "Какие это туалеты?", - "es": "¿Qué tipo de baños son estos?", + "es": "¿Qué tipo de aseos son?", "da": "Hvilken slags toiletter er det?", "ca": "Quin tipus de lavabo són aquests?", "cs": "O jaký druh záchodů se jedná?" @@ -707,7 +707,7 @@ "en": "These toilets have a sink to wash your hands", "nl": "Deze toiletten hebben een lavabo waar men de handen kan wassen", "de": "Die Toilette hat ein Handwaschbecken", - "es": "Estos baños tienen una pileta para lavarse las manos", + "es": "Estos aseos tienen un lavabo para lavarse las manos", "fr": "Ces toilettes ont un lavabo pour se laver les mains", "da": "Dette toilet har en vask til at vaske dine hænder", "ca": "Aquests lavabos tenen una pica per a rentar-te les mans", @@ -720,7 +720,7 @@ "en": "These toilets don't have a sink to wash your hands", "nl": "Deze toiletten hebben geen lavabo waar men de handen kan wassen", "de": "Die Toilette hat kein Handwaschbecken", - "es": "Estos baños no tienen una pileta para lavarse las manos", + "es": "Estos aseos no tienen lavabo para lavarse las manos", "fr": "Ces toilettes n'ont pas de lavabo pour se laver les mains", "da": "Disse toiletter har ikke en vask til at vaske dine hænder", "ca": "Aquests lavabos no tenen una pica per a rentar-te les mans", diff --git a/assets/layers/trail/trail.json b/assets/layers/trail/trail.json index e6bacfdf37..f90dabfe35 100644 --- a/assets/layers/trail/trail.json +++ b/assets/layers/trail/trail.json @@ -12,7 +12,8 @@ }, "description": { "en": "Waymarked trails", - "nl": "Aangeduide wandeltochten" + "nl": "Aangeduide wandeltochten", + "de": "Markierte Wanderwege" }, "source": { "osmTags": { @@ -105,11 +106,13 @@ { "question": { "en": "What is the name of this trail?", - "nl": "Wat is de naam van deze wandeling?" + "nl": "Wat is de naam van deze wandeling?", + "de": "Wie heißt dieser Weg?" }, "render": { "en": "This trail is called {name}", - "nl": "Deze wandeling heet {name}" + "nl": "Deze wandeling heet {name}", + "de": "Dieser Weg heißt {name}" }, "freeform": { "key": "name" @@ -119,11 +122,13 @@ { "render": { "en": "This trail is maintained by {operator}", - "nl": "Beheer door {operator}" + "nl": "Beheer door {operator}", + "de": "Dieser Weg wird von {operator} gepflegt" }, "question": { "en": "Who maintains this trail?", - "nl": "Wie beheert deze wandeltocht?" + "nl": "Wie beheert deze wandeltocht?", + "de": "Wer pflegt diesen Weg?" }, "freeform": { "key": "operator" @@ -137,7 +142,8 @@ }, "then": { "en": "This trail is maintained by Natuurpunt", - "nl": "Dit gebied wordt beheerd door Natuurpunt" + "nl": "Dit gebied wordt beheerd door Natuurpunt", + "de": "Dieser Weg wird von Natuurpunt gepflegt" }, "icon": { "path": "./assets/themes/buurtnatuur/Natuurpunt.jpg", @@ -152,7 +158,8 @@ }, "then": { "en": "This trail is maintained by {operator}", - "nl": "Dit gebied wordt beheerd door {operator}" + "nl": "Dit gebied wordt beheerd door {operator}", + "de": "Dieser Weg wird von {operator} gepflegt" }, "hideInAnswer": true, "icon": { @@ -166,11 +173,13 @@ { "question": { "en": "What is the reference colour of this trail?", - "nl": "Welke kleur heeft deze wandeling?" + "nl": "Welke kleur heeft deze wandeling?", + "de": "Was ist die Referenzfarbe dieses Weges?" }, "render": { "en": "The reference colour is {colour}", - "nl": "Deze wandeling heeft kleur {colour}" + "nl": "Deze wandeling heeft kleur {colour}", + "de": "Die Referenzfarbe ist {colour}" }, "freeform": { "key": "colour", @@ -231,20 +240,23 @@ { "question": { "en": "Is this trail wheelchair accessible?", - "nl": "Is deze wandeling toegankelijk met de rolstoel?" + "nl": "Is deze wandeling toegankelijk met de rolstoel?", + "de": "Ist dieser Weg rollstuhlgerecht?" }, "mappings": [ { "then": { "en": "This trail is wheelchair-accessible", - "nl": "deze wandeltocht is toegankelijk met de rolstoel" + "nl": "deze wandeltocht is toegankelijk met de rolstoel", + "de": "Dieser Weg ist rollstuhlgerecht" }, "if": "wheelchair=yes" }, { "then": { "en": "This trail is not wheelchair accessible", - "nl": "deze wandeltocht is niet toegankelijk met de rolstoel" + "nl": "deze wandeltocht is niet toegankelijk met de rolstoel", + "de": "Dieser Weg ist nicht rollstuhlgerecht" }, "if": "wheelchair=no" } @@ -254,20 +266,23 @@ { "question": { "en": "Is this trail accessible with a pushchair?", - "nl": "Is deze wandeltocht toegankelijk met de buggy?" + "nl": "Is deze wandeltocht toegankelijk met de buggy?", + "de": "Ist dieser Weg mit einem Kinderwagen zugänglich?" }, "mappings": [ { "then": { "en": "This trail is accessible with a pushchair", - "nl": "deze wandeltocht is toegankelijk met de buggy" + "nl": "deze wandeltocht is toegankelijk met de buggy", + "de": "Dieser Weg ist mit einem Kinderwagen zugänglich" }, "if": "pushchair=yes" }, { "then": { "en": "This trail is not accessible with a pushchair", - "nl": "deze wandeltocht is niet toegankelijk met de buggy" + "nl": "deze wandeltocht is niet toegankelijk met de buggy", + "de": "Dieser Weg ist nicht mit einem Kinderwagen zugänglich" }, "if": "pushchair=no" } diff --git a/assets/layers/tree_node/tree_node.json b/assets/layers/tree_node/tree_node.json index 85e6bbabda..c6a7c66cf1 100644 --- a/assets/layers/tree_node/tree_node.json +++ b/assets/layers/tree_node/tree_node.json @@ -319,7 +319,7 @@ "pt_BR": "Qual a altura dessa árvore?", "ca": "Quina és l'alçada d'aquest arbre?", "cs": "Jaká je výška tohoto stromu?", - "es": "¿Cuál es la altura de este árbol?" + "es": "¿Qué altura tiene este árbol?" }, "freeform": { "key": "height", @@ -440,7 +440,7 @@ "en": "The tree is in a residential garden.", "it": "L’albero è un giardino residenziale.", "fr": "Cet arbre est dans une jardin de résidence.", - "es": "El árbol está en un jardín privado o residencial.", + "es": "El árbol está en un jardín de una vivienda.", "de": "Der Baum steht in einem Wohngarten.", "da": "Træet står i en villahave.", "ca": "L'arbre està en un jardí residencial.", @@ -478,7 +478,7 @@ "en": "The tree is in an urban area.", "it": "L’albero si trova in un’area urbana.", "fr": "L'arbre est dans une zone urbaine.", - "es": "El árbol está en un zona urbana.", + "es": "El árbol está en una zona urbana.", "de": "Der Baum steht in einem städtischen Gebiet.", "da": "Træet står i et byområde.", "ca": "L'arbre està en una àrea urbana.", diff --git a/assets/themes/cyclenodes/cyclenodes.json b/assets/themes/cyclenodes/cyclenodes.json index b022ba6eab..2bb5eb2bbd 100644 --- a/assets/themes/cyclenodes/cyclenodes.json +++ b/assets/themes/cyclenodes/cyclenodes.json @@ -329,7 +329,8 @@ "bicycle=yes" ], "title": { - "en": "a route marker for a node to node link" + "en": "a route marker for a node to node link", + "de": "Eine Routenmarkierung für eine Verbindung von Knoten zu Knoten" }, "=exampleImages": [ "./assets/layers/route_marker/bicycle_route_marker.jpg" diff --git a/assets/themes/icecream/icecream.json b/assets/themes/icecream/icecream.json index 3d7e00a1b8..e9c135f3ee 100644 --- a/assets/themes/icecream/icecream.json +++ b/assets/themes/icecream/icecream.json @@ -1,10 +1,12 @@ { "id": "icecream", "title": { - "en": "Icecream" + "en": "Icecream", + "de": "Eiscreme" }, "description": { - "en": "A map showing ice cream parlors and ice cream vending machines" + "en": "A map showing ice cream parlors and ice cream vending machines", + "de": "Eine Karte, die Eisdielen und Eisautomaten zeigt" }, "icon": "./assets/layers/ice_cream/ice_cream.svg", "layers": [ diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index 9b37d1f439..f4986e0aa5 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -1,13 +1,33 @@ { "id": "mapcomplete-changes", "title": { - "en": "Changes made with MapComplete" + "en": "Changes made with MapComplete", + "ca": "Canvis fets amb MapComplete", + "cs": "Změny provedené pomocí MapComplete", + "de": "Mit MapComplete erstellte Änderungen", + "es": "Cambios realizados con MapComplete", + "fr": "Changements faits avec MapComplete", + "nl": "Wijzigingen gemaakt met MapComplete", + "pl": "Zmiany wprowadzone za pomocą MapComplete" }, "shortDescription": { - "en": "Shows changes made by MapComplete" + "en": "Show changes made with MapComplete", + "ca": "Mostra els canvis fets amb MapComplete", + "cs": "Zobrazení změn provedených pomocí nástroje MapComplete", + "de": "Mit MapComplete erstellte Änderungen anzeigen", + "es": "Mostrar cambios realizados con MapComplete", + "nl": "Toon wijzigingen gemaakt met MapComplete", + "pl": "Pokaż zmiany wprowadzone za pomocą MapComplete" }, "description": { - "en": "This maps shows all the changes made with MapComplete" + "en": "This maps shows all the changes made with MapComplete", + "ca": "Aquest mapa mostra tots els canvis fets amb MapComplete", + "cs": "Tato mapa zobrazuje všechny změny provedené pomocí MapComplete", + "de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen", + "es": "Este mapa muestra todos los cambios realizados con MapComplete", + "fr": "Cette carte montre tous les changements faits avec MapComplete", + "nl": "Deze kaart toont alle wijzigingen die met MapComplete gemaakt werden", + "pl": "Ta mapa pokazuje wszystkie zmiany wprowadzone za pomocą MapComplete" }, "icon": "./assets/svg/logo.svg", "hideFromOverview": true, @@ -20,7 +40,13 @@ { "id": "mapcomplete-changes", "name": { - "en": "Changeset centers" + "en": "Changeset centers", + "ca": "Centre del conjunt de canvis", + "cs": "Centrum změn", + "de": "Zentrum der Änderungssätze", + "es": "Centro del conjunto de cambios", + "nl": "Centerpunt van changeset", + "pl": "Centra zmian" }, "minzoom": 0, "source": { @@ -31,41 +57,85 @@ }, "title": { "render": { - "en": "Changeset for {theme}" + "en": "Changeset for {theme}", + "ca": "Conjunt de canvis per a {theme}", + "cs": "Změna pro {theme}", + "de": "Änderungssatz für {theme}", + "es": "Conjunto de cambios para {theme}", + "fr": "Groupe de modifications pour {theme}", + "pl": "Zestaw zmian dla {theme}" } }, "description": { - "en": "Shows all MapComplete changes" + "en": "Show all MapComplete changes", + "ca": "Mostra tots els canvis de MapComplete", + "cs": "Zobrazit všechny změny MapComplete", + "de": "Alle MapComplete-Änderungen anzeigen", + "es": "Mostrar todos los cambios de MapComplete", + "nl": "Toon alle MapComplete wijzigingen", + "pl": "Wyświetl wszystkie zmiany MapComplete" }, "tagRenderings": [ { "id": "show_changeset_id", "render": { - "en": "Changeset
{id}" + "en": "Changeset {id}", + "ca": "Conjunt de canvi {id}", + "cs": "Změny {id}", + "de": "Änderungssatz {id}", + "es": "Conjunto de cambios {id}", + "fr": "Groupe de modifications {id}", + "pl": "Zestaw zmian {id}" } }, { "id": "contributor", "question": { - "en": "What contributor did make this change?" + "en": "Which contributor made this change?", + "ca": "Quin col·laborador va fer aquest canvi?", + "cs": "Který přispěvatel tuto změnu provedl?", + "de": "Wer hat diese Änderung vorgenommen?", + "es": "¿Qué contribuidor hizo este cambio?", + "fr": "Quel contributeur a fait cette modification ?", + "nl": "Welke bijdrager maakte deze wijziging?", + "pl": "Który współautor dokonał tej zmiany?" }, "freeform": { "key": "user" }, "render": { - "en": "Change made by {user}" + "en": "Change made by {user}", + "ca": "Canvi fet per {user}", + "cs": "Změna provedená {user}", + "de": "Änderung von {user}", + "es": "Cambio realizado por {user}", + "fr": "Modification faite par {user}", + "nl": "Wijziging gemaakt door {user}", + "pl": "Zmiana dokonana przez {user}" } }, { "id": "theme-id", "question": { - "en": "What theme was used to make this change?" + "en": "What theme was used to make this change?", + "ca": "Quin tema es va utilitzar per fer aquest canvi?", + "cs": "Jaké téma bylo použito k provedení této změny?", + "de": "Welches Thema wurde für diese Änderung verwendet?", + "es": "¿Qué tema se utilizó para realizar este cambio?", + "fr": "Quel thème a été utilisé pour faire cette modification ?", + "pl": "Jakiego tematu użyto do wprowadzenia tej zmiany?" }, "freeform": { "key": "theme" }, "render": { - "en": "Change with theme {theme}" + "en": "Change with theme {theme}", + "ca": "Canvi amb el tema {theme}", + "cs": "Změna s motivem {theme}", + "de": "Geändert mit Thema {theme}", + "es": "Cambio con tema {theme}", + "fr": "Modifié avec le thème {theme}", + "pl": "Zmiana za pomocą motywu {theme}" } }, { @@ -74,19 +144,45 @@ "key": "locale" }, "question": { - "en": "What locale (language) was this change made in?" + "en": "What locale (language) was this change made in?", + "ca": "Amb quina configuració regional (idioma) s'ha fet aquest canvi?", + "cs": "V jakém národním prostředí (jazyce) byla tato změna provedena?", + "de": "In welcher Benutzersprache wurde diese Änderung vorgenommen?", + "es": "¿En qué configuración regional (idioma) se realizó este cambio?", + "fr": "En quelle langue est-ce que ce changement a été fait ?", + "nl": "In welke locale (taal) werd deze wijziging gemaakt?", + "pl": "W jakim języku wprowadzono tę zmianę?" }, "render": { - "en": "User locale is {locale}" + "en": "User locale is {locale}", + "ca": "La configuració regional de l'usuari és {locale}", + "cs": "Uživatelské prostředí je {locale}", + "de": "Benutzersprache {locale}", + "es": "La configuración regional del usuario es {locale}", + "nl": "De gebruikerstaal is {locale}", + "pl": "Ustawienia regionalne użytkownika to {locale}" } }, { "id": "host", "render": { - "en": "Change with with {host}" + "en": "Change made with {host}", + "ca": "Canviat fet amb {host}", + "cs": "Změna provedená pomocí {host}", + "de": "Geändert über {host}", + "es": "Cambio realizado con {host}", + "fr": "Modification faite avec {host}", + "nl": "Wijziging gemaakt met {host}", + "pl": "Zmiana dokonana w {host}" }, "question": { - "en": "What host (website) was this change made with?" + "en": "What host (website) was this change made with?", + "ca": "Amb quin amfitrió (lloc web) es va fer aquest canvi?", + "cs": "U jakého hostitele (webové stránky) byla tato změna provedena?", + "de": "Über welchen Host (Webseite) wurde diese Änderung vorgenommen?", + "es": "¿Con qué host (página web) se realizó este cambio?", + "nl": "Met welke host (website) werd deze wijziging gemaakt?", + "pl": "Na jakim hoście (stronie internetowej) dokonano tej zmiany?" }, "freeform": { "key": "host" @@ -107,10 +203,22 @@ { "id": "version", "question": { - "en": "What version of MapComplete was used to make this change?" + "en": "What version of MapComplete was used to make this change?", + "ca": "Quina versió de MapComplete es va utilitzar per fer aquest canvi?", + "cs": "Jaká verze aplikace MapComplete byla použita k provedení této změny?", + "de": "Mit welcher Version von MapComplete wurde diese Änderung gemacht?", + "es": "¿Qué versión de MapComplete se usó para realizar este cambio?", + "fr": "Quelle version de MapComplete a été utilisée pour faire cette modification ?", + "pl": "Która wersja MapComplete została wykorzystana, aby zrobić tę zmianę?" }, "render": { - "en": "Made with {editor}" + "en": "Made with {editor}", + "ca": "Fet amb {editor}", + "cs": "Vyrobeno pomocí {editor}", + "de": "Erstellt mit {editor}", + "es": "Realizado con {editor}", + "fr": "Fait avec {editor}", + "pl": "Zrobione za pomocą {editor}" }, "freeform": { "key": "editor" @@ -456,7 +564,13 @@ } ], "question": { - "en": "Themename contains {search}" + "en": "Theme name contains {search}", + "ca": "El nom del tema conté {search}", + "cs": "Název motivu obsahuje {search}", + "de": "Themenname enthält {search}", + "es": "El nombre del tema contiene {search}", + "nl": "Themenaam bevat {search}", + "pl": "Nazwa tematu zawiera {search}" } } ] @@ -472,7 +586,13 @@ } ], "question": { - "en": "Made by contributor {search}" + "en": "Made by contributor {search}", + "ca": "Fet pel col·laborador {search}", + "cs": "Vytvořil přispěvatel {search}", + "de": "Erstellt von {search}", + "es": "Hecho por el colaborador {search}", + "nl": "Gemaakt door bijdrager {search}", + "pl": "Wykonane przez współautora {search}" } } ] @@ -488,7 +608,13 @@ } ], "question": { - "en": "Not made by contributor {search}" + "en": "Not made by contributor {search}", + "ca": "No fet pel col·laborador {search}", + "cs": "Není vytvořeno přispěvatelem {search}", + "de": "Nicht erstellt von {search}", + "es": "No hecho por el colaborador {search}", + "nl": "Niet gemaakt door bijdrager {search}", + "pl": "Nie wykonane przez współautora {search}" } } ] @@ -505,7 +631,13 @@ } ], "question": { - "en": "Made before {search}" + "en": "Made before {search}", + "ca": "Fet abans de {search}", + "cs": "Vytvořeno před {search}", + "de": "Erstellt vor {search}", + "es": "Hecho antes de {search}", + "nl": "Gemaakt voor {search}", + "pl": "Stworzone przed {search}" } } ] @@ -522,7 +654,13 @@ } ], "question": { - "en": "Made after {search}" + "en": "Made after {search}", + "ca": "Fet després de {search}", + "cs": "Vytvořeno po {search}", + "de": "Erstellt nach {search}", + "es": "Hecho después de {search}", + "nl": "Gemaakt na {search}", + "pl": "Stworzone po {search}" } } ] @@ -538,7 +676,14 @@ } ], "question": { - "en": "User language (iso-code) {search}" + "en": "User language (iso-code) {search}", + "ca": "Idioma de l'usuari (codi iso) {search}", + "cs": "Jazyk uživatele (iso-kód) {search}", + "de": "Benutzersprache (ISO-Code) {search}", + "es": "Use idioma (ISO-code) {search}", + "fr": "Langage utilisateur (code-ISO) {search}", + "nl": "De taal van de bijdrager is {search}", + "pl": "Język użytkownika (kod iso) {search}" } } ] @@ -554,7 +699,13 @@ } ], "question": { - "en": "Made with host {search}" + "en": "Made with host {search}", + "ca": "Fet amb l'amfitrió {search}", + "cs": "Vytvořeno pomocí hostitele {search}", + "de": "Erstellt mit Host {search}", + "es": "Hecho con el host {search}", + "nl": "Gemaakt met host {search}", + "pl": "Wykonane z hostem {search}" } } ] @@ -565,7 +716,14 @@ { "osmTags": "add-image>0", "question": { - "en": "Changeset added at least one image" + "en": "Changeset added at least one image", + "ca": "El conjunt de canvis ha afegit almenys una imatge", + "cs": "Sada změn přidala alespoň jeden obrázek", + "de": "Im Änderungssatz wurde mindestens ein Bild hinzugefügt", + "es": "El conjunto de cambios ha añadido al menos una imagen", + "fr": "Le groupe de modifications a ajouté au moins une image", + "nl": "Changeset bevat minstens één afbeelding", + "pl": "Zestaw zmian dodał co najmniej jedno zdjęcie" } } ] @@ -580,7 +738,13 @@ { "id": "link_to_more", "render": { - "en": "More statistics can be found here" + "en": "More statistics can be found here", + "ca": "Es pot trobar més estadística aquí", + "cs": "Další statistiky najdete here", + "de": "Mehr Statistiken gibt es hier", + "es": "Puede encontrar más estadísticas aquí", + "fr": "D'autres statistiques sont disponibles ici", + "pl": "Więcej statystyk można znaleźć tutaj" } }, { diff --git a/assets/themes/walkingnodes/walkingnodes.json b/assets/themes/walkingnodes/walkingnodes.json index d0ec2c8161..3d08f50190 100644 --- a/assets/themes/walkingnodes/walkingnodes.json +++ b/assets/themes/walkingnodes/walkingnodes.json @@ -2,11 +2,13 @@ "id": "walkingnodes", "title": { "en": "Walking Node Networks", - "nl": "Wandelknooppuntnetwerken" + "nl": "Wandelknooppuntnetwerken", + "de": "Netzwerke von Wanderknoten" }, "description": { "en": "This map shows walking node networks and allows you to add new nodes easily", - "nl": "Deze kaart toont wandelknooppunten en laat je toe om eenvoudigweg nieuwe knooppunten toe te voegen" + "nl": "Deze kaart toont wandelknooppunten en laat je toe om eenvoudigweg nieuwe knooppunten toe te voegen", + "de": "Diese Karte zeigt Wandernetzwerke und ermöglicht es Ihnen, einfach neue Knoten hinzuzufügen" }, "icon": "./assets/themes/walkingnodes/logo.svg", "startZoom": 11, @@ -149,7 +151,8 @@ "title": { "render": { "en": "Walking node {rwn_ref}", - "nl": "Wandelknooppunt {rwn_ref}" + "nl": "Wandelknooppunt {rwn_ref}", + "de": "Wanderknoten {rwn_ref}" } }, "tagRenderings": [ @@ -157,19 +160,22 @@ "id": "node-rwn_ref", "question": { "en": "What is the reference number of this walking node?", - "nl": "Wat is het referentienummer van dit wandelknooppunt?" + "nl": "Wat is het referentienummer van dit wandelknooppunt?", + "de": "Was ist die Referenznummer dieses Wanderknotens?" }, "freeform": { "key": "rwn_ref", "type": "int", "placeholder": { "en": "e.g. 1", - "nl": "bijv. 1" + "nl": "bijv. 1", + "de": "z.B. 1" } }, "render": { "en": "This walking node has reference number {rwn_ref}", - "nl": "Dit wandelknooppunt heeft referentienummer {rwn_ref}" + "nl": "Dit wandelknooppunt heeft referentienummer {rwn_ref}", + "de": "Dieser Wanderknoten hat die Referenznummer {rwn_ref}" } }, { @@ -177,29 +183,34 @@ "override": { "question": { "en": "When was this walking node last surveyed?", - "nl": "Wanneer is dit wandelknooppunt het laatst gesurveyed?" + "nl": "Wanneer is dit wandelknooppunt het laatst gesurveyed?", + "de": "Wann wurde dieser Wanderknoten zuletzt überprüft?" }, "render": { "en": "This walking node was last surveyed on {survey:date}", - "nl": "Dit wandelknooppunt werd het laatst gesurveyed op {survey:date}" + "nl": "Dit wandelknooppunt werd het laatst gesurveyed op {survey:date}", + "de": "Dieser Wanderknoten wurde zuletzt am {survey:date} überprüft" } } }, { "question": { "en": "How many other walking nodes does this node link to?", - "nl": "Met hoeveel andere wandelknooppunten heeft dit knooppunt een verbinding?" + "nl": "Met hoeveel andere wandelknooppunten heeft dit knooppunt een verbinding?", + "de": "Mit wie vielen anderen Wanderknoten ist dieser Knoten verbunden?" }, "render": { "en": "This node links to {expected_rwn_route_relations} other walking nodes.", - "nl": "Dit knooppunt verbindt met {expected_rwn_route_relations} andere wandelknooppunten." + "nl": "Dit knooppunt verbindt met {expected_rwn_route_relations} andere wandelknooppunten.", + "de": "Dieser Knoten ist mit {expected_rwn_route_relations} anderen Wanderknoten verbunden." }, "freeform": { "key": "expected_rwn_route_relations", "type": "int", "placeholder": { "en": "e.g. 3", - "nl": "bijv. 3" + "nl": "bijv. 3", + "de": "z.B. 3" } }, "id": "node-expected_rwn_route_relations" @@ -214,7 +225,8 @@ ], "title": { "en": "a walking node", - "nl": "een wandelknooppunt" + "nl": "een wandelknooppunt", + "de": "ein Wanderknoten" }, "snapToLayer": [ "cycleways_and_roads" @@ -246,11 +258,13 @@ } ], "name": { - "en": "Hiking guideposts" + "en": "Hiking guideposts", + "de": "Wanderwegweiser" }, "title": { "render": { - "en": "Hiking guidepost" + "en": "Hiking guidepost", + "de": "Wanderwegweiser" } } }, @@ -280,7 +294,8 @@ "hiking=yes" ], "title": { - "en": "a route marker for a node to node link" + "en": "a route marker for a node to node link", + "de": "Eine Routenmarkierung für eine Verbindung von Knoten zu Knoten" }, "=exampleImages": [ "./assets/layers/route_marker/walking_route_marker.jpg" diff --git a/langs/en.json b/langs/en.json index 51286265dd..d7a2241cb5 100644 --- a/langs/en.json +++ b/langs/en.json @@ -50,6 +50,15 @@ "panelIntro": "

Your personal theme

Activate your favourite layers from all the official themes", "reload": "Reload the data" }, + "favouritePoi": { + "button": { + "isFavourite": "This location is currently marked as favourite and will show up on all thematic maps of MapComplete you visit.", + "markAsFavouriteTitle": "Mark this location as favourite location", + "markDescription": "Add this location to a personal list of your favourites", + "unmark": "Remove from your personal list of favourites", + "unmarkNotDeleted": "This point will not be deleted and still be visible on the appropriate map for you and others" + } + }, "flyer": { "aerial": "This map uses a different background, namely aerial imagery by Agentschap Informatie Vlaanderen", "callToAction": "Test it on mapcomplete.org", diff --git a/langs/layers/de.json b/langs/layers/de.json index 765464ff44..4067c6339a 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -35,16 +35,6 @@ "1": { "title": "eine freistehende Posterbox" }, - "10": { - "description": "Verwendet für Werbeschilder, Leuchtreklamen, Logos und institutionelle Eingangsschilder", - "title": "ein Schild" - }, - "11": { - "title": "eine Skulptur" - }, - "12": { - "title": "eine Wandmalerei" - }, "2": { "title": "eine wandmontierte Posterbox" }, @@ -71,6 +61,16 @@ }, "9": { "title": "ein Totem" + }, + "10": { + "description": "Verwendet für Werbeschilder, Leuchtreklamen, Logos und institutionelle Eingangsschilder", + "title": "ein Schild" + }, + "11": { + "title": "eine Skulptur" + }, + "12": { + "title": "eine Wandmalerei" } }, "tagRenderings": { @@ -165,9 +165,6 @@ "1": { "then": "Dies ist ein Brett" }, - "10": { - "then": "Dies ist eine Wandmalerei" - }, "2": { "then": "Dies ist eine Litfaßsäule" }, @@ -191,6 +188,9 @@ }, "9": { "then": "Dies ist ein Totem" + }, + "10": { + "then": "Dies ist eine Wandmalerei" } }, "question": "Welche Art von Werbung ist das?", @@ -205,9 +205,6 @@ "1": { "then": "Brett" }, - "10": { - "then": "Wandmalerei" - }, "2": { "then": "Posterbox" }, @@ -231,6 +228,9 @@ }, "9": { "then": "Totem" + }, + "10": { + "then": "Wandmalerei" } } } @@ -353,15 +353,6 @@ "1": { "then": "Wandbild" }, - "10": { - "then": "Azulejo (spanische dekorative Fliesenarbeit)" - }, - "11": { - "then": "Fliesenarbeit" - }, - "12": { - "then": "Holzschnitzerei" - }, "2": { "then": "Malerei" }, @@ -385,6 +376,15 @@ }, "9": { "then": "Relief" + }, + "10": { + "then": "Azulejo (spanische dekorative Fliesenarbeit)" + }, + "11": { + "then": "Fliesenarbeit" + }, + "12": { + "then": "Holzschnitzerei" } }, "question": "Um welche Art Kunstwerk handelt es sich?", @@ -1942,27 +1942,6 @@ "1": { "question": "Verfügt über einen
Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)
" }, - "10": { - "question": "Hat einen
Typ 2 (Mennekes)
Anschluss mit Kabel" - }, - "11": { - "question": "Hat einen
Tesla Supercharger CCS (Typ 2 CSS vonTesla)
Anschluss" - }, - "12": { - "question": "Hat einen
Tesla Supercharger (Destination)
Anschluss" - }, - "13": { - "question": "Hat einen
Tesla Supercharger (Destination) (Typ 2 von Tesla)
Anschluss mit Kabel" - }, - "14": { - "question": "Hat einen
USB-Anschluss zum Aufladen von Telefonen und kleinen Elektrogeräten
" - }, - "15": { - "question": "Hat einen
Bosch Active Connect Anschluss mit 3 Pins
und Kabel" - }, - "16": { - "question": "Hat einen
Bosch Active Connect Anschluss mit 5 Pins
und Kabel" - }, "2": { "question": "Verfügt über einen
europäischen Netzstecker mit Erdungsstift (CEE7/4 Typ E)
Anschluss" }, @@ -1986,6 +1965,27 @@ }, "9": { "question": "Hat einen
Typ 2 CCS (Mennekes)
Anschluss" + }, + "10": { + "question": "Hat einen
Typ 2 (Mennekes)
Anschluss mit Kabel" + }, + "11": { + "question": "Hat einen
Tesla Supercharger CCS (Typ 2 CSS vonTesla)
Anschluss" + }, + "12": { + "question": "Hat einen
Tesla Supercharger (Destination)
Anschluss" + }, + "13": { + "question": "Hat einen
Tesla Supercharger (Destination) (Typ 2 von Tesla)
Anschluss mit Kabel" + }, + "14": { + "question": "Hat einen
USB-Anschluss zum Aufladen von Telefonen und kleinen Elektrogeräten
" + }, + "15": { + "question": "Hat einen
Bosch Active Connect Anschluss mit 3 Pins
und Kabel" + }, + "16": { + "question": "Hat einen
Bosch Active Connect Anschluss mit 5 Pins
und Kabel" } } } @@ -2041,6 +2041,30 @@ "1": { "then": "Schuko-Stecker ohne Erdungsstift (CEE7/4 Typ F)" }, + "2": { + "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" + }, + "3": { + "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" + }, + "4": { + "then": "Chademo-Anschluss" + }, + "5": { + "then": "Chademo-Anschluss" + }, + "6": { + "then": "Typ 1 mit Kabel (J1772)" + }, + "7": { + "then": "Typ 1 mit Kabel (J1772)" + }, + "8": { + "then": "Typ 1 ohne Kabel (J1772)" + }, + "9": { + "then": " Typ 1 ohne Kabel (J1772)" + }, "10": { "then": "Typ 1 CCS (Typ 1 Combo)" }, @@ -2071,9 +2095,6 @@ "19": { "then": "Typ 2 mit Kabel (mennekes)" }, - "2": { - "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" - }, "20": { "then": "Tesla Supercharger CCS (Typ 2 CSS von Tesla)" }, @@ -2104,32 +2125,11 @@ "29": { "then": " Bosch Active Connect mit 3 Pins und Kabel" }, - "3": { - "then": "Europäischer Netzstecker mit Erdungsstift (CEE7/4 Typ E)" - }, "30": { "then": "Bosch Active Connect mit 5 Pins und Kabel" }, "31": { "then": " Bosch Active Connect mit 5 Pins und Kabel" - }, - "4": { - "then": "Chademo-Anschluss" - }, - "5": { - "then": "Chademo-Anschluss" - }, - "6": { - "then": "Typ 1 mit Kabel (J1772)" - }, - "7": { - "then": "Typ 1 mit Kabel (J1772)" - }, - "8": { - "then": "Typ 1 ohne Kabel (J1772)" - }, - "9": { - "then": " Typ 1 ohne Kabel (J1772)" } }, "question": "Welche Ladeanschlüsse gibt es hier?" @@ -3667,15 +3667,6 @@ "1": { "then": "Dieser Radweg hat einen festen Belag" }, - "10": { - "then": "Dieser Radweg besteht aus feinem Schotter" - }, - "11": { - "then": "Der Radweg ist aus Kies" - }, - "12": { - "then": "Dieser Radweg besteht aus Rohboden" - }, "2": { "then": "Der Radweg ist aus Asphalt" }, @@ -3699,6 +3690,15 @@ }, "9": { "then": "Der Radweg ist aus Schotter" + }, + "10": { + "then": "Dieser Radweg besteht aus feinem Schotter" + }, + "11": { + "then": "Der Radweg ist aus Kies" + }, + "12": { + "then": "Dieser Radweg besteht aus Rohboden" } }, "question": "Was ist der Belag dieses Radwegs?", @@ -3747,15 +3747,6 @@ "1": { "then": "Dieser Radweg hat einen festen Belag" }, - "10": { - "then": "Dieser Radweg besteht aus feinem Schotter" - }, - "11": { - "then": "Der Radweg ist aus Kies" - }, - "12": { - "then": "Dieser Radweg besteht aus Rohboden" - }, "2": { "then": "Der Radweg ist aus Asphalt" }, @@ -3779,6 +3770,15 @@ }, "9": { "then": "Der Radweg ist aus Schotter" + }, + "10": { + "then": "Dieser Radweg besteht aus feinem Schotter" + }, + "11": { + "then": "Der Radweg ist aus Kies" + }, + "12": { + "then": "Dieser Radweg besteht aus Rohboden" } }, "question": "Was ist der Belag dieser Straße?", @@ -4678,54 +4678,6 @@ } } }, - "10": { - "options": { - "0": { - "question": "Keine Bevorzugung von Hunden" - }, - "1": { - "question": "Hunde erlaubt" - }, - "2": { - "question": "Keine Hunde erlaubt" - } - } - }, - "11": { - "options": { - "0": { - "question": "Internetzugang vorhanden" - } - } - }, - "12": { - "options": { - "0": { - "question": "Stromanschluss vorhanden" - } - } - }, - "13": { - "options": { - "0": { - "question": "Hat zuckerfreie Angebote" - } - } - }, - "14": { - "options": { - "0": { - "question": "Hat glutenfreie Angebote" - } - } - }, - "15": { - "options": { - "0": { - "question": "Hat laktosefreie Angebote" - } - } - }, "2": { "options": { "0": { @@ -4796,6 +4748,54 @@ "question": "Nutzung kostenlos" } } + }, + "10": { + "options": { + "0": { + "question": "Keine Bevorzugung von Hunden" + }, + "1": { + "question": "Hunde erlaubt" + }, + "2": { + "question": "Keine Hunde erlaubt" + } + } + }, + "11": { + "options": { + "0": { + "question": "Internetzugang vorhanden" + } + } + }, + "12": { + "options": { + "0": { + "question": "Stromanschluss vorhanden" + } + } + }, + "13": { + "options": { + "0": { + "question": "Hat zuckerfreie Angebote" + } + } + }, + "14": { + "options": { + "0": { + "question": "Hat glutenfreie Angebote" + } + } + }, + "15": { + "options": { + "0": { + "question": "Hat laktosefreie Angebote" + } + } } } }, @@ -4915,6 +4915,30 @@ "1": { "then": "Die Fitness-Station hat ein Schild mit Anweisungen für eine bestimmte Übung." }, + "2": { + "then": "Die Fitness-Station hat eine Einrichtung für Sit-ups." + }, + "3": { + "then": "Die Fitness-Station hat eine Vorrichtung für Liegestütze. In der Regel eine oder mehrere niedrige Reckstangen." + }, + "4": { + "then": "Die Fitness-Station hat Stangen zum Dehnen." + }, + "5": { + "then": "Die Fitness-Station hat eine Vorrichtung für Rückenstrecker (Hyperextensions)." + }, + "6": { + "then": "Die Fitness-Station hat Ringe für Gymnastikübungen." + }, + "7": { + "then": "Die Fitness-Station hat eine horizontale Leiter (Monkey Bars)." + }, + "8": { + "then": "Die Fitness-Station hat eine Sprossenwand zum Klettern." + }, + "9": { + "then": "Die Fitness-Station hat Pfosten für Slalomübungen." + }, "10": { "then": "Die Fitness-Station hat Trittsteine." }, @@ -4945,9 +4969,6 @@ "19": { "then": "Die Fitness-Station hat Kampfseile (battle ropes)." }, - "2": { - "then": "Die Fitness-Station hat eine Einrichtung für Sit-ups." - }, "20": { "then": "Die Fitness-Station hat ein Fahrradergometer." }, @@ -4962,27 +4983,6 @@ }, "24": { "then": "Die Fitness-Station hat eine Slackline." - }, - "3": { - "then": "Die Fitness-Station hat eine Vorrichtung für Liegestütze. In der Regel eine oder mehrere niedrige Reckstangen." - }, - "4": { - "then": "Die Fitness-Station hat Stangen zum Dehnen." - }, - "5": { - "then": "Die Fitness-Station hat eine Vorrichtung für Rückenstrecker (Hyperextensions)." - }, - "6": { - "then": "Die Fitness-Station hat Ringe für Gymnastikübungen." - }, - "7": { - "then": "Die Fitness-Station hat eine horizontale Leiter (Monkey Bars)." - }, - "8": { - "then": "Die Fitness-Station hat eine Sprossenwand zum Klettern." - }, - "9": { - "then": "Die Fitness-Station hat Pfosten für Slalomübungen." } }, "question": "Welche Übungsgeräte gibt es an dieser Fitness-Station?" @@ -5102,21 +5102,6 @@ "1": { "then": "Dies ist eine Pommesbude" }, - "10": { - "then": "Hier werden chinesische Gerichte serviert" - }, - "11": { - "then": "Hier werden griechische Gerichte serviert" - }, - "12": { - "then": "Hier werden indische Gerichte serviert" - }, - "13": { - "then": "Hier werden türkische Gerichte serviert" - }, - "14": { - "then": "Hier werden thailändische Gerichte serviert" - }, "2": { "then": "Bietet vorwiegend Pastagerichte an" }, @@ -5140,6 +5125,21 @@ }, "9": { "then": "Hier werden französische Gerichte serviert" + }, + "10": { + "then": "Hier werden chinesische Gerichte serviert" + }, + "11": { + "then": "Hier werden griechische Gerichte serviert" + }, + "12": { + "then": "Hier werden indische Gerichte serviert" + }, + "13": { + "then": "Hier werden türkische Gerichte serviert" + }, + "14": { + "then": "Hier werden thailändische Gerichte serviert" } }, "question": "Was für Essen gibt es hier?", @@ -5777,6 +5777,30 @@ "1": { "then": "Dies ist ein Auditorium" }, + "2": { + "then": "Dies ist ein Schlafzimmer" + }, + "3": { + "then": "Dies ist eine Kapelle" + }, + "4": { + "then": "Dies ist ein Klassenzimmer" + }, + "5": { + "then": "Dies ist ein Klassenzimmer" + }, + "6": { + "then": "Dies ist ein Computerraum" + }, + "7": { + "then": "Dies ist ein Konferenzraum" + }, + "8": { + "then": "Dies ist eine Krypta" + }, + "9": { + "then": "Dies ist eine Küche" + }, "10": { "then": "Dies ist ein Labor" }, @@ -5807,9 +5831,6 @@ "19": { "then": "Dies ist ein Lagerraum" }, - "2": { - "then": "Dies ist ein Schlafzimmer" - }, "20": { "then": "Dies ist ein Technikraum" }, @@ -5818,27 +5839,6 @@ }, "22": { "then": "Dies ist ein Wartezimmer" - }, - "3": { - "then": "Dies ist eine Kapelle" - }, - "4": { - "then": "Dies ist ein Klassenzimmer" - }, - "5": { - "then": "Dies ist ein Klassenzimmer" - }, - "6": { - "then": "Dies ist ein Computerraum" - }, - "7": { - "then": "Dies ist ein Konferenzraum" - }, - "8": { - "then": "Dies ist eine Krypta" - }, - "9": { - "then": "Dies ist eine Küche" } }, "question": "Wie wird dieser Raum genutzt?" @@ -6493,19 +6493,6 @@ } } }, - "10": { - "options": { - "0": { - "question": "Alle Notizen" - }, - "1": { - "question": "Importnotizen ausblenden" - }, - "2": { - "question": "Nur Importnotizen anzeigen" - } - } - }, "2": { "options": { "0": { @@ -6561,6 +6548,19 @@ "question": "Nur offene Notizen anzeigen" } } + }, + "10": { + "options": { + "0": { + "question": "Alle Notizen" + }, + "1": { + "question": "Importnotizen ausblenden" + }, + "2": { + "question": "Nur Importnotizen anzeigen" + } + } } }, "name": "OpenStreetMap-Hinweise", @@ -6889,21 +6889,6 @@ "1": { "then": "Dies ist ein normaler Stellplatz." }, - "10": { - "then": "Dies ist ein Stellplatz, der für Eltern mit Kindern reserviert ist." - }, - "11": { - "then": "Dies ist ein Stellplatz, der für das Personal reserviert ist." - }, - "12": { - "then": "Dies ist ein Stellplatz, der für Taxis reserviert ist." - }, - "13": { - "then": "Dies ist ein Stellplatz, der für Fahrzeuge mit Anhänger reserviert ist." - }, - "14": { - "then": "Dies ist ein Stellplatz, der für Carsharing reserviert ist." - }, "2": { "then": "Dies ist ein Behindertenstellplatz." }, @@ -6927,6 +6912,21 @@ }, "9": { "then": "Dies ist ein Stellplatz, der für Motorräder reserviert ist." + }, + "10": { + "then": "Dies ist ein Stellplatz, der für Eltern mit Kindern reserviert ist." + }, + "11": { + "then": "Dies ist ein Stellplatz, der für das Personal reserviert ist." + }, + "12": { + "then": "Dies ist ein Stellplatz, der für Taxis reserviert ist." + }, + "13": { + "then": "Dies ist ein Stellplatz, der für Fahrzeuge mit Anhänger reserviert ist." + }, + "14": { + "then": "Dies ist ein Stellplatz, der für Carsharing reserviert ist." } }, "question": "Welche Art von Stellplatz ist dies?" @@ -7518,21 +7518,6 @@ "1": { "then": "2-Cent-Münzen werden akzeptiert" }, - "10": { - "then": "20-Centime-Münzen werden akzeptiert" - }, - "11": { - "then": "½-Schweizer Franken-Münzen werden akzeptiert" - }, - "12": { - "then": "1-Schweizer Franken-Münzen werden akzeptiert" - }, - "13": { - "then": "2-Schweizer Franken-Münzen werden akzeptiert" - }, - "14": { - "then": "5-Schweizer Franken-Münzen werden akzeptiert" - }, "2": { "then": "5-Cent-Münzen werden akzeptiert" }, @@ -7556,6 +7541,21 @@ }, "9": { "then": "10-Centime-Münzen werden akzeptiert" + }, + "10": { + "then": "20-Centime-Münzen werden akzeptiert" + }, + "11": { + "then": "½-Schweizer Franken-Münzen werden akzeptiert" + }, + "12": { + "then": "1-Schweizer Franken-Münzen werden akzeptiert" + }, + "13": { + "then": "2-Schweizer Franken-Münzen werden akzeptiert" + }, + "14": { + "then": "5-Schweizer Franken-Münzen werden akzeptiert" } }, "question": "Mit welchen Münzen kann man hier bezahlen?" @@ -7568,15 +7568,6 @@ "1": { "then": "10-Euro-Scheine werden angenommen" }, - "10": { - "then": "100-Schweizer Franken-Scheine werden akzeptiert" - }, - "11": { - "then": "200-Schweizer Franken-Scheine werden akzeptiert" - }, - "12": { - "then": "1000-Schweizer Franken-Scheine werden akzeptiert" - }, "2": { "then": "20-Euro-Scheine werden angenommen" }, @@ -7600,6 +7591,15 @@ }, "9": { "then": "50-Schweizer Franken-Scheine werden akzeptiert" + }, + "10": { + "then": "100-Schweizer Franken-Scheine werden akzeptiert" + }, + "11": { + "then": "200-Schweizer Franken-Scheine werden akzeptiert" + }, + "12": { + "then": "1000-Schweizer Franken-Scheine werden akzeptiert" } }, "question": "Mit welchen Banknoten kann man hier bezahlen?" @@ -8050,6 +8050,30 @@ "1": { "question": "Recycling von Batterien" }, + "2": { + "question": "Recycling von Getränkekartons" + }, + "3": { + "question": "Recycling von Dosen" + }, + "4": { + "question": "Recycling von Kleidung" + }, + "5": { + "question": "Recycling von Speiseöl" + }, + "6": { + "question": "Recycling von Motoröl" + }, + "7": { + "question": "Recycling von Leuchtstoffröhren" + }, + "8": { + "question": "Recycling von Grünabfällen" + }, + "9": { + "question": "Recycling von Glasflaschen" + }, "10": { "question": "Recycling von Glas" }, @@ -8080,35 +8104,11 @@ "19": { "question": "Recycling von Restabfällen" }, - "2": { - "question": "Recycling von Getränkekartons" - }, "20": { "question": "Recycling von Druckerpatronen" }, "21": { "question": "Recycling von Fahrrädern" - }, - "3": { - "question": "Recycling von Dosen" - }, - "4": { - "question": "Recycling von Kleidung" - }, - "5": { - "question": "Recycling von Speiseöl" - }, - "6": { - "question": "Recycling von Motoröl" - }, - "7": { - "question": "Recycling von Leuchtstoffröhren" - }, - "8": { - "question": "Recycling von Grünabfällen" - }, - "9": { - "question": "Recycling von Glasflaschen" } } }, @@ -8176,6 +8176,30 @@ "1": { "then": "Getränkekartons können hier recycelt werden" }, + "2": { + "then": "Dosen können hier recycelt werden" + }, + "3": { + "then": "Kleidung kann hier recycelt werden" + }, + "4": { + "then": "Speiseöl kann hier recycelt werden" + }, + "5": { + "then": "Motoröl kann hier recycelt werden" + }, + "6": { + "then": "Hier können Leuchtstoffröhren recycelt werden" + }, + "7": { + "then": "Grünabfälle können hier recycelt werden" + }, + "8": { + "then": "Bio-Abfall kann hier recycelt werden" + }, + "9": { + "then": "Glasflaschen können hier recycelt werden" + }, "10": { "then": "Glas kann hier recycelt werden" }, @@ -8206,9 +8230,6 @@ "19": { "then": "Schuhe können hier recycelt werden" }, - "2": { - "then": "Dosen können hier recycelt werden" - }, "20": { "then": "Elektrokleingeräte können hier recycelt werden" }, @@ -8223,27 +8244,6 @@ }, "24": { "then": "Fahrräder können hier recycelt werden" - }, - "3": { - "then": "Kleidung kann hier recycelt werden" - }, - "4": { - "then": "Speiseöl kann hier recycelt werden" - }, - "5": { - "then": "Motoröl kann hier recycelt werden" - }, - "6": { - "then": "Hier können Leuchtstoffröhren recycelt werden" - }, - "7": { - "then": "Grünabfälle können hier recycelt werden" - }, - "8": { - "then": "Bio-Abfall kann hier recycelt werden" - }, - "9": { - "then": "Glasflaschen können hier recycelt werden" } }, "question": "Was kann hier recycelt werden?" @@ -9161,12 +9161,6 @@ "1": { "then": "Diese Straßenlaterne verwendet LEDs" }, - "10": { - "then": "Diese Straßenlaterne verwendet Hochdruck-Natriumdampflampen (orange mit weiß)" - }, - "11": { - "then": "Diese Straßenlaterne wird mit Gas beleuchtet" - }, "2": { "then": "Diese Straßenlaterne verwendet Glühlampenlicht" }, @@ -9190,6 +9184,12 @@ }, "9": { "then": "Diese Straßenlaterne verwendet Niederdruck-Natriumdampflampen (einfarbig orange)" + }, + "10": { + "then": "Diese Straßenlaterne verwendet Hochdruck-Natriumdampflampen (orange mit weiß)" + }, + "11": { + "then": "Diese Straßenlaterne wird mit Gas beleuchtet" } }, "question": "Mit welcher Art von Beleuchtung arbeitet diese Straßenlaterne?" @@ -10367,6 +10367,30 @@ "1": { "question": "Verkauf von Getränken" }, + "2": { + "question": "Verkauf von Süßigkeiten" + }, + "3": { + "question": "Verkauf von Lebensmitteln" + }, + "4": { + "question": "Verkauf von Zigaretten" + }, + "5": { + "question": "Verkauf von Kondomen" + }, + "6": { + "question": "Verkauf von Kaffee" + }, + "7": { + "question": "Verkauf von Trinkwasser" + }, + "8": { + "question": "Verkauf von Zeitungen" + }, + "9": { + "question": "Verkauf von Fahrradschläuchen" + }, "10": { "question": "Verkauf von Milch" }, @@ -10397,9 +10421,6 @@ "19": { "question": "Verkauf von Blumen" }, - "2": { - "question": "Verkauf von Süßigkeiten" - }, "20": { "question": "Verkauf von Parkscheinen" }, @@ -10423,27 +10444,6 @@ }, "27": { "question": "Verkauf von Fahrradschlössern" - }, - "3": { - "question": "Verkauf von Lebensmitteln" - }, - "4": { - "question": "Verkauf von Zigaretten" - }, - "5": { - "question": "Verkauf von Kondomen" - }, - "6": { - "question": "Verkauf von Kaffee" - }, - "7": { - "question": "Verkauf von Trinkwasser" - }, - "8": { - "question": "Verkauf von Zeitungen" - }, - "9": { - "question": "Verkauf von Fahrradschläuchen" } } } @@ -10490,6 +10490,30 @@ "1": { "then": "Süßigkeiten werden verkauft" }, + "2": { + "then": "Lebensmittel werden verkauft" + }, + "3": { + "then": "Zigaretten werden verkauft" + }, + "4": { + "then": "Kondome werden verkauft" + }, + "5": { + "then": "Kaffee wird verkauft" + }, + "6": { + "then": "Trinkwasser wird verkauft" + }, + "7": { + "then": "Zeitungen werden verkauft" + }, + "8": { + "then": "Fahrradschläuche werden verkauft" + }, + "9": { + "then": "Milch wird verkauft" + }, "10": { "then": "Brot wird verkauft" }, @@ -10520,9 +10544,6 @@ "19": { "then": "Parkscheine werden verkauft" }, - "2": { - "then": "Lebensmittel werden verkauft" - }, "20": { "then": "Souvenirmünzen werden verkauft" }, @@ -10543,27 +10564,6 @@ }, "26": { "then": "Fahrradschlösser werden verkauft" - }, - "3": { - "then": "Zigaretten werden verkauft" - }, - "4": { - "then": "Kondome werden verkauft" - }, - "5": { - "then": "Kaffee wird verkauft" - }, - "6": { - "then": "Trinkwasser wird verkauft" - }, - "7": { - "then": "Zeitungen werden verkauft" - }, - "8": { - "then": "Fahrradschläuche werden verkauft" - }, - "9": { - "then": "Milch wird verkauft" } }, "question": "Was wird in diesem Automaten verkauft?", @@ -10892,4 +10892,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/layers/en.json b/langs/layers/en.json index 0f2e3d6b5e..14ed9817bf 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -4662,6 +4662,18 @@ "render": "Extinguishers" } }, + "favourite": { + "description": "A generic map layer which shows locations that a contributor marked as favourite", + "name": "Favourites", + "tagRenderings": { + "Explanation": { + "render": "You marked this location as a personal favourite. As such, it is shown on every map you load." + } + }, + "title": { + "render": "Favourite location" + } + }, "filters": { "filter": { "0": { diff --git a/langs/layers/es.json b/langs/layers/es.json index 8c4f0fce65..8d002852d2 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -35,16 +35,6 @@ "1": { "title": "un mupi" }, - "10": { - "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", - "title": "un señal" - }, - "11": { - "title": "una escultura" - }, - "12": { - "title": "una pared pintada" - }, "2": { "title": "un mupi sobre la pared" }, @@ -71,6 +61,16 @@ }, "9": { "title": "un tótem" + }, + "10": { + "description": "Se utiliza para carteles publicitarios, letreros de neón, logotipos y carteles en entradas institucionales", + "title": "un señal" + }, + "11": { + "title": "una escultura" + }, + "12": { + "title": "una pared pintada" } }, "tagRenderings": { @@ -165,9 +165,6 @@ "1": { "then": "Esto es un tablón de anuncios" }, - "10": { - "then": "Esto es una pared pintada" - }, "2": { "then": "Esto es una columna" }, @@ -191,6 +188,9 @@ }, "9": { "then": "Esto es un tótem" + }, + "10": { + "then": "Esto es una pared pintada" } }, "question": "¿Qué tipo de elemento publicitario es?", @@ -205,9 +205,6 @@ "1": { "then": "Tablon de anuncios" }, - "10": { - "then": "Pared Pintada" - }, "2": { "then": "Mupi" }, @@ -231,6 +228,9 @@ }, "9": { "then": "Tótem" + }, + "10": { + "then": "Pared Pintada" } } } @@ -353,15 +353,6 @@ "1": { "then": "Mural" }, - "10": { - "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" - }, - "11": { - "then": "Cerámica" - }, - "12": { - "then": "Tallado en madera" - }, "2": { "then": "Pintura" }, @@ -385,6 +376,15 @@ }, "9": { "then": "Relieve" + }, + "10": { + "then": "Azulejo (Baldosas decorativas Españolas y Portuguesas)" + }, + "11": { + "then": "Cerámica" + }, + "12": { + "then": "Tallado en madera" } }, "question": "¿Qué tipo de obra es esta pieza?", @@ -1768,27 +1768,6 @@ "0": { "question": "Todos los conectores" }, - "10": { - "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" - }, - "11": { - "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" - }, - "12": { - "question": "Tiene un conector
Tesla Supercharger (destination)
" - }, - "13": { - "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" - }, - "14": { - "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" - }, - "15": { - "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" - }, - "16": { - "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" - }, "2": { "question": "Tiene un conector
enchufe de pared Europeo con un pin de tierra (CEE7/4 tipo E)
" }, @@ -1812,6 +1791,27 @@ }, "9": { "question": "Tiene un conector
Tipo 2 CCS (mennekes)
" + }, + "10": { + "question": "Tiene un conector
Tipo 2 con cable (mennekes)
" + }, + "11": { + "question": "Tiene un conector
Tesla Supercharger CCS (un tipo2_css de marca)
" + }, + "12": { + "question": "Tiene un conector
Tesla Supercharger (destination)
" + }, + "13": { + "question": "Tiene un conector
Tesla Supercharger (Destination) (Tipo2 A con un cable de marca tesla)
" + }, + "14": { + "question": "Tiene un conector
USB para cargar teléfonos y dispositivos electrónicos pequeños
" + }, + "15": { + "question": "Tiene un conector
Bosch Active Connect con 3 pines y cable
" + }, + "16": { + "question": "Tiene un conector
Bosch Active Connect con 5 pines y cable
" } } } @@ -1866,6 +1866,30 @@ "1": { "then": "Enchufe de pared Schuko sin pin de tierra (CEE7/4 tipo F)" }, + "2": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, + "3": { + "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" + }, + "4": { + "then": "Chademo" + }, + "5": { + "then": "Chademo" + }, + "6": { + "then": "Tipo 1 con cable (J1772)" + }, + "7": { + "then": "Tipo 1 con cable (J1772)" + }, + "8": { + "then": "Tipo 1 sin cable (J1772)" + }, + "9": { + "then": "Tipo 1 sin cable (J1772)" + }, "10": { "then": "CSS Tipo 1 (también conocido como Tipo 1 Combo)" }, @@ -1896,9 +1920,6 @@ "19": { "then": "Tipo 2 con cable (mennekes)" }, - "2": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, "20": { "then": "CCS Supercargador Tesla (un tipo2_css con marca)" }, @@ -1929,32 +1950,11 @@ "29": { "then": "Bosch Active Connect con 3 pines y cable" }, - "3": { - "then": "Enchufe de pared Europeo con pin de tierra (CEE7/4 tipo E)" - }, "30": { "then": "Bosch Active Connect con 5 pines y cable" }, "31": { "then": "Bosch Active Connect con 5 pines y cable" - }, - "4": { - "then": "Chademo" - }, - "5": { - "then": "Chademo" - }, - "6": { - "then": "Tipo 1 con cable (J1772)" - }, - "7": { - "then": "Tipo 1 con cable (J1772)" - }, - "8": { - "then": "Tipo 1 sin cable (J1772)" - }, - "9": { - "then": "Tipo 1 sin cable (J1772)" } }, "question": "¿Qué tipo de conexiones de carga están disponibles aquí?" @@ -2349,12 +2349,6 @@ "1": { "then": "Este carril bici está pavimentado" }, - "10": { - "then": "Este carril bici está hecho de gravilla" - }, - "12": { - "then": "Este carril bici está hecho de tierra natural" - }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2369,6 +2363,12 @@ }, "9": { "then": "Este carril bici está hecho de grava" + }, + "10": { + "then": "Este carril bici está hecho de gravilla" + }, + "12": { + "then": "Este carril bici está hecho de tierra natural" } }, "question": "¿De qué superficie está hecho este carril bici?", @@ -2414,9 +2414,6 @@ "1": { "then": "Este carril bici está pavimentado" }, - "10": { - "then": "Este carril bici está hecho de gravilla" - }, "2": { "then": "Este carril bici está hecho de asfalto" }, @@ -2428,6 +2425,9 @@ }, "9": { "then": "Este carril bici está hecho de grava" + }, + "10": { + "then": "Este carril bici está hecho de gravilla" } }, "question": "¿De qué esta hecha la superficie de esta calle?", @@ -3069,18 +3069,6 @@ "0": { "then": "Esto es una pizzería" }, - "10": { - "then": "Aquí se sirven platos Chinos" - }, - "11": { - "then": "Aquí se sirven platos Griegos" - }, - "12": { - "then": "Aquí se sirven platos Indios" - }, - "13": { - "then": "Aquí se sirven platos Turcos" - }, "2": { "then": "Principalmente sirve pasta" }, @@ -3101,6 +3089,18 @@ }, "9": { "then": "Aquí se sirven platos Franceses" + }, + "10": { + "then": "Aquí se sirven platos Chinos" + }, + "11": { + "then": "Aquí se sirven platos Griegos" + }, + "12": { + "then": "Aquí se sirven platos Indios" + }, + "13": { + "then": "Aquí se sirven platos Turcos" } }, "question": "¿Qué tipo de comida sirven aquí?", @@ -3499,19 +3499,6 @@ } } }, - "10": { - "options": { - "0": { - "question": "Todas las notas" - }, - "1": { - "question": "Ocultar las notas de importación" - }, - "2": { - "question": "Solo mostrar las notas de importación" - } - } - }, "2": { "options": { "0": { @@ -3567,6 +3554,19 @@ "question": "Solo mostrar las notas abiertas" } } + }, + "10": { + "options": { + "0": { + "question": "Todas las notas" + }, + "1": { + "question": "Ocultar las notas de importación" + }, + "2": { + "question": "Solo mostrar las notas de importación" + } + } } }, "name": "Notas de OpenStreetMap", @@ -4182,6 +4182,24 @@ "1": { "question": "Reciclaje de baterías" }, + "3": { + "question": "Reciclaje de latas" + }, + "4": { + "question": "Reciclaje de ropa" + }, + "5": { + "question": "Reciclaje de aceite de cocina" + }, + "6": { + "question": "Reciclaje de aceite de motor" + }, + "8": { + "question": "Reciclaje de residuos orgánicos" + }, + "9": { + "question": "Reciclaje de botellas de cristal" + }, "10": { "question": "Reciclaje de cristal" }, @@ -4208,24 +4226,6 @@ }, "18": { "question": "Reciclaje de pequeños electrodomésticos" - }, - "3": { - "question": "Reciclaje de latas" - }, - "4": { - "question": "Reciclaje de ropa" - }, - "5": { - "question": "Reciclaje de aceite de cocina" - }, - "6": { - "question": "Reciclaje de aceite de motor" - }, - "8": { - "question": "Reciclaje de residuos orgánicos" - }, - "9": { - "question": "Reciclaje de botellas de cristal" } } } @@ -4268,6 +4268,27 @@ "0": { "then": "Aquí se pueden reciclar baterías" }, + "2": { + "then": "Aquí se pueden reciclar latas" + }, + "3": { + "then": "Aquí se puede reciclar ropa" + }, + "4": { + "then": "Aquí se puede reciclar aceite de cocina" + }, + "5": { + "then": "Aquí se puede reciclar aceite de motor" + }, + "7": { + "then": "Los residuos orgánicos pueden reciclarse aquí" + }, + "8": { + "then": "Aquí se pueden reciclar residuos orgánicos" + }, + "9": { + "then": "Aquí se pueden reciclar botellas de cristal" + }, "10": { "then": "Aquí se puede reciclar cristal" }, @@ -4294,27 +4315,6 @@ }, "19": { "then": "El calzado se puede reciclar aquí" - }, - "2": { - "then": "Aquí se pueden reciclar latas" - }, - "3": { - "then": "Aquí se puede reciclar ropa" - }, - "4": { - "then": "Aquí se puede reciclar aceite de cocina" - }, - "5": { - "then": "Aquí se puede reciclar aceite de motor" - }, - "7": { - "then": "Los residuos orgánicos pueden reciclarse aquí" - }, - "8": { - "then": "Aquí se pueden reciclar residuos orgánicos" - }, - "9": { - "then": "Aquí se pueden reciclar botellas de cristal" } }, "question": "¿Qué se puede reciclar aquí?" @@ -4665,12 +4665,6 @@ "1": { "then": "Esta lámpara utiliza LEDs" }, - "10": { - "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" - }, - "11": { - "then": "Esta lampara se ilumina con gas" - }, "2": { "then": "Esta lámpara utiliza iluminación incandescente" }, @@ -4691,6 +4685,12 @@ }, "9": { "then": "Esta lámpara utiliza lámparas de sodio de baja presión (naranja monocromo)" + }, + "10": { + "then": "Esta lámpara utiliza lámparas de sodio de alta presión (naranja con blanco)" + }, + "11": { + "then": "Esta lampara se ilumina con gas" } }, "question": "¿Qué tipo de iluminación utiliza esta lámpara?" @@ -5262,4 +5262,4 @@ } } } -} +} \ No newline at end of file diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 35f8706f07..8a4b3736c6 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -4364,6 +4364,18 @@ "render": "Brandblussers" } }, + "favourite": { + "description": "Een laag met persoonlijke favourieten", + "name": "Favorieten", + "tagRenderings": { + "Explanation": { + "render": "Je hebt deze locatie als persoonlijke favoriet aangeduid en worden op alle MapComplete-kaarten getoond." + } + }, + "title": { + "render": "Favoriete locatie" + } + }, "filters": { "filter": { "0": { diff --git a/langs/themes/de.json b/langs/themes/de.json index 6e9ebb26d1..cc768eb169 100644 --- a/langs/themes/de.json +++ b/langs/themes/de.json @@ -1024,33 +1024,6 @@ "onwheels": { "description": "Auf dieser Karte können Sie öffentlich zugängliche Orte für Rollstuhlfahrer ansehen, bearbeiten oder hinzufügen", "layers": { - "19": { - "override": { - "=title": { - "render": "Statistik" - } - } - }, - "20": { - "override": { - "+tagRenderings": { - "0": { - "render": { - "special": { - "text": "Import" - } - } - }, - "1": { - "render": { - "special": { - "message": "Alle vorgeschlagenen Tags hinzufügen" - } - } - } - } - } - }, "4": { "override": { "filter": { @@ -1093,6 +1066,33 @@ "override": { "name": "Barrierefreie Parkplätze" } + }, + "19": { + "override": { + "=title": { + "render": "Statistik" + } + } + }, + "20": { + "override": { + "+tagRenderings": { + "0": { + "render": { + "special": { + "text": "Import" + } + } + }, + "1": { + "render": { + "special": { + "message": "Alle vorgeschlagenen Tags hinzufügen" + } + } + } + } + } } }, "title": "Auf Rädern" @@ -1253,6 +1253,10 @@ "stations": { "description": "Bahnhofsdetails ansehen, bearbeiten und hinzufügen", "layers": { + "3": { + "description": "Ebene mit Bahnhöfen", + "name": "Bahnhöfe" + }, "16": { "description": "Anzeigen der Züge, die von diesem Bahnhof abfahren", "name": "Abfahrtstafeln", @@ -1284,10 +1288,6 @@ "title": { "render": "Abfahrtstafel" } - }, - "3": { - "description": "Ebene mit Bahnhöfen", - "name": "Bahnhöfe" } }, "title": "Bahnhöfe" @@ -1458,4 +1458,4 @@ "shortDescription": "Eine Karte mit Abfalleimern", "title": "Abfalleimer" } -} +} \ No newline at end of file diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 3d49a619bc..d66b0bb916 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -765,10 +765,6 @@ video { float: left; } -.m-8 { - margin: 2rem; -} - .m-4 { margin: 1rem; } @@ -781,6 +777,10 @@ video { margin: 0px; } +.m-8 { + margin: 2rem; +} + .m-2 { margin: 0.5rem; } @@ -841,6 +841,10 @@ video { margin-right: 3rem; } +.mb-4 { + margin-bottom: 1rem; +} + .mt-4 { margin-top: 1rem; } @@ -877,10 +881,6 @@ video { margin-right: 0.25rem; } -.mb-4 { - margin-bottom: 1rem; -} - .ml-1 { margin-left: 0.25rem; } diff --git a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts new file mode 100644 index 0000000000..4589d94935 --- /dev/null +++ b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts @@ -0,0 +1,157 @@ +import StaticFeatureSource from "./StaticFeatureSource" +import { Feature } from "geojson" +import { Store, UIEventSource } from "../../UIEventSource" +import { OsmConnection } from "../../Osm/OsmConnection" +import { OsmId } from "../../../Models/OsmFeature" +import { GeoOperations } from "../../GeoOperations" +import FeaturePropertiesStore from "../Actors/FeaturePropertiesStore" +import { IndexedFeatureSource } from "../FeatureSource" +import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" + +/** + * Generates the favourites from the preferences and marks them as favourite + */ +export default class FavouritesFeatureSource extends StaticFeatureSource { + public static readonly prefix = "mapcomplete-favourite-" + private readonly _osmConnection: OsmConnection + + constructor( + connection: OsmConnection, + indexedSource: FeaturePropertiesStore, + allFeatures: IndexedFeatureSource, + layout: LayoutConfig + ) { + const detectedIds = new UIEventSource>(undefined) + const features: Store = connection.preferencesHandler.preferences.map( + (prefs) => { + const feats: Feature[] = [] + const allIds = new Set() + for (const key in prefs) { + if (!key.startsWith(FavouritesFeatureSource.prefix)) { + continue + } + const id = key.substring(FavouritesFeatureSource.prefix.length) + const osmId = id.replace("-", "/") + if (id.indexOf("-property-") > 0 || id.indexOf("-layer") > 0) { + continue + } + allIds.add(osmId) + const geometry = <[number, number]>JSON.parse(prefs[key]) + const properties = FavouritesFeatureSource.getPropertiesFor(connection, id) + properties._orig_layer = prefs[FavouritesFeatureSource.prefix + id + "-layer"] + if (layout.layers.some((l) => l.id === properties._orig_layer)) { + continue + } + properties.id = osmId + properties._favourite = "yes" + feats.push({ + type: "Feature", + properties, + geometry: { + type: "Point", + coordinates: geometry, + }, + }) + } + console.log("Favouritess are", feats) + detectedIds.setData(allIds) + return feats + } + ) + + super(features) + + this._osmConnection = connection + detectedIds.addCallbackAndRunD((detected) => + this.markFeatures(detected, indexedSource, allFeatures) + ) + // We use the indexedFeatureSource as signal to update + allFeatures.features.map((_) => + this.markFeatures(detectedIds.data, indexedSource, allFeatures) + ) + } + + private static getPropertiesFor( + osmConnection: OsmConnection, + id: string + ): Record { + const properties: Record = {} + const prefs = osmConnection.preferencesHandler.preferences.data + const minLength = FavouritesFeatureSource.prefix.length + id.length + "-property-".length + for (const key in prefs) { + if (key.length < minLength) { + continue + } + if (!key.startsWith(FavouritesFeatureSource.prefix + id)) { + continue + } + const propertyName = key.substring(minLength) + properties[propertyName] = prefs[key] + } + return properties + } + + public markAsFavourite( + feature: Feature, + layer: string, + theme: string, + tags: UIEventSource & { id: OsmId }>, + isFavourite: boolean = true + ) { + { + const id = tags.data.id.replace("/", "-") + const pref = this._osmConnection.GetPreference("favourite-" + id) + if (isFavourite) { + const center = GeoOperations.centerpointCoordinates(feature) + pref.setData(JSON.stringify(center)) + this._osmConnection.GetPreference("favourite-" + id + "-layer").setData(layer) + this._osmConnection.GetPreference("favourite-" + id + "-theme").setData(theme) + + for (const key in tags.data) { + const pref = this._osmConnection.GetPreference( + "favourite-" + id + "-property-" + key.replaceAll(":", "__") + ) + pref.setData(tags.data[key]) + } + } else { + this._osmConnection.preferencesHandler.removeAllWithPrefix( + "mapcomplete-favourite-" + id + ) + } + } + if (isFavourite) { + tags.data._favourite = "yes" + tags.ping() + } else { + delete tags.data._favourite + tags.ping() + } + } + + private markFeatures( + detected: Set, + featureProperties: FeaturePropertiesStore, + allFeatures: IndexedFeatureSource + ) { + const feature = allFeatures.features.data + for (const f of feature) { + const id = f.properties.id + if (!id) { + continue + } + const store = featureProperties.getStore(id) + const origValue = store.data._favourite + if (detected.has(id)) { + if (origValue !== "yes") { + store.data._favourite = "yes" + store.ping() + } + } else { + if (origValue) { + store.data._favourite = "" + store.ping() + } + } + } + } +} diff --git a/src/Logic/FeatureSource/Sources/NearbyFeatureSource.ts b/src/Logic/FeatureSource/Sources/NearbyFeatureSource.ts index 669c86a114..cec3f4de9f 100644 --- a/src/Logic/FeatureSource/Sources/NearbyFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/NearbyFeatureSource.ts @@ -6,10 +6,14 @@ import FilteringFeatureSource from "./FilteringFeatureSource" import LayerState from "../../State/LayerState" export default class NearbyFeatureSource implements FeatureSource { + private readonly _result = new UIEventSource(undefined) + public readonly features: Store private readonly _targetPoint: Store<{ lon: number; lat: number }> private readonly _numberOfNeededFeatures: number + private readonly _layerState?: LayerState private readonly _currentZoom: Store + private readonly _allSources: Store<{ feat: Feature; d: number }[]>[] = [] constructor( targetPoint: Store<{ lon: number; lat: number }>, @@ -18,41 +22,44 @@ export default class NearbyFeatureSource implements FeatureSource { layerState?: LayerState, currentZoom?: Store ) { + this._layerState = layerState this._targetPoint = targetPoint.stabilized(100) this._numberOfNeededFeatures = numberOfNeededFeatures this._currentZoom = currentZoom.stabilized(500) - const allSources: Store<{ feat: Feature; d: number }[]>[] = [] + this.features = Stores.ListStabilized(this._result) + + sources.forEach((source, layer) => {}) + } + + public registerSource(source: FeatureSource, layerId: string) { let minzoom = 999 - - const result = new UIEventSource(undefined) - this.features = Stores.ListStabilized(result) - - function update() { - let features: { feat: Feature; d: number }[] = [] - for (const src of allSources) { - features.push(...src.data) - } - features.sort((a, b) => a.d - b.d) - if (numberOfNeededFeatures !== undefined) { - features = features.slice(0, numberOfNeededFeatures) - } - result.setData(features.map((f) => f.feat)) + const flayer = this._layerState?.filteredLayers.get(layerId) + if (!flayer) { + return } - - sources.forEach((source, layer) => { - const flayer = layerState?.filteredLayers.get(layer) - minzoom = Math.min(minzoom, flayer.layerDef.minzoom) - const calcSource = this.createSource( - source.features, - flayer.layerDef.minzoom, - flayer.isDisplayed - ) - calcSource.addCallbackAndRunD((features) => { - update() - }) - allSources.push(calcSource) + minzoom = Math.min(minzoom, flayer.layerDef.minzoom) + const calcSource = this.createSource( + source.features, + flayer.layerDef.minzoom, + flayer.isDisplayed + ) + calcSource.addCallbackAndRunD((features) => { + this.update() }) + this._allSources.push(calcSource) + } + + private update() { + let features: { feat: Feature; d: number }[] = [] + for (const src of this._allSources) { + features.push(...src.data) + } + features.sort((a, b) => a.d - b.d) + if (this._numberOfNeededFeatures !== undefined) { + features = features.slice(0, this._numberOfNeededFeatures) + } + this._result.setData(features.map((f) => f.feat)) } /** diff --git a/src/Logic/Osm/OsmPreferences.ts b/src/Logic/Osm/OsmPreferences.ts index 778b741365..e365362587 100644 --- a/src/Logic/Osm/OsmPreferences.ts +++ b/src/Logic/Osm/OsmPreferences.ts @@ -285,4 +285,13 @@ export class OsmPreferences { } ) } + + removeAllWithPrefix(prefix: string) { + for (const key in this.preferences.data) { + if (key.startsWith(prefix)) { + this.GetPreference(key, undefined, { prefix: "" }).setData(undefined) + console.log("Clearing preference", key) + } + } + } } diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index f15232e3d7..bf2e0e5ccf 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -23,6 +23,7 @@ export default class Constants { "gps_track", "range", "last_click", + "favourite", ] as const /** * Special layers which are not included in a theme by default @@ -131,6 +132,8 @@ export default class Constants { "clock", "invalid", "close", + "heart", + "heart_outline", ] as const public static readonly defaultPinIcons: string[] = Constants._defaultPinIcons diff --git a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts index fd041335c6..00b84c7640 100644 --- a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -26,7 +26,7 @@ import predifined_filters from "../../../../assets/layers/filters/filters.json" import { TagConfigJson } from "../Json/TagConfigJson" import PointRenderingConfigJson, { IconConfigJson } from "../Json/PointRenderingConfigJson" import ValidationUtils from "./ValidationUtils" -import { RenderingSpecification, SpecialVisualization } from "../../../UI/SpecialVisualization" +import { RenderingSpecification } from "../../../UI/SpecialVisualization" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import { ConfigMeta } from "../../../UI/Studio/configMeta" import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" @@ -639,6 +639,13 @@ export class AddEditingElements extends DesugaringStep { json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit")) } + if (!usedSpecialFunctions.has("favourite_status")) { + json.tagRenderings.push({ + id: "favourite_status", + render: { "*": "{favourite_status()}" }, + }) + } + if (!usedSpecialFunctions.has("all_tags")) { const trc: QuestionableTagRenderingConfigJson = { id: "all-tags", @@ -1193,6 +1200,31 @@ class ExpandMarkerRenderings extends DesugaringStep { } } +class AddFavouriteBadges extends DesugaringStep { + constructor() { + super( + "Adds the favourite heart to the title and the rendering badges", + [], + "AddFavouriteBadges" + ) + } + + convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson { + if (json.id === "favourite") { + return json + } + const pr = json.pointRendering?.[0] + if (pr) { + pr.iconBadges ??= [] + if (!pr.iconBadges.some((ti) => ti.if === "_favourite=yes")) { + pr.iconBadges.push({ if: "_favourite=yes", then: "circle:white;heart:red" }) + } + } + + return json + } +} + export class AddRatingBadge extends DesugaringStep { constructor() { super( @@ -1206,6 +1238,10 @@ export class AddRatingBadge extends DesugaringStep { if (!json.tagRenderings) { return json } + if (json.titleIcons.some((ti) => ti === "icons.rating" || ti["id"] === "rating")) { + // already added + return json + } const specialVis: Exclude[] = < Exclude[] @@ -1247,6 +1283,7 @@ export class PrepareLayer extends Fuse { ), new SetDefault("titleIcons", ["icons.defaults"]), new AddRatingBadge(), + new AddFavouriteBadges(), new On( "titleIcons", (layer) => diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index 9002c3a9aa..62a495bf72 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -968,7 +968,7 @@ export class ValidateTagRenderings extends Fuse { "Various validation on tagRenderingConfigs", new DetectShadowedMappings(layerConfig), new DetectConflictingAddExtraTags(), - new DetectNonErasedKeysInMappings(), + // new DetectNonErasedKeysInMappings(), new DetectMappingsWithImages(doesImageExist), new On("render", new ValidatePossibleLinks()), new On("question", new ValidatePossibleLinks()), diff --git a/src/Models/ThemeConfig/LayoutConfig.ts b/src/Models/ThemeConfig/LayoutConfig.ts index ce538d6b29..77ee2ef489 100644 --- a/src/Models/ThemeConfig/LayoutConfig.ts +++ b/src/Models/ThemeConfig/LayoutConfig.ts @@ -305,6 +305,9 @@ export default class LayoutConfig implements LayoutInformation { } for (const layer of this.layers) { if (!layer.source) { + if (layer.isShown?.matchesProperties(tags)) { + return layer + } continue } if (layer.source.osmTags.matchesProperties(tags)) { diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 0cc0119e6c..a94ad07b4c 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -58,6 +58,7 @@ import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLay import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" import { Imgur } from "../Logic/ImageProviders/Imgur" import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSource" +import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource" /** * @@ -96,10 +97,11 @@ export default class ThemeViewState implements SpecialVisualizationState { readonly indexedFeatures: IndexedFeatureSource & LayoutSource readonly currentView: FeatureSource> readonly featuresInView: FeatureSource + readonly favourites: FavouritesFeatureSource /** * Contains a few (<10) >features that are near the center of the map. */ - readonly closestFeatures: FeatureSource + readonly closestFeatures: NearbyFeatureSource readonly newFeatures: WritableFeatureSource readonly layerState: LayerState readonly perLayer: ReadonlyMap @@ -220,8 +222,6 @@ export default class ThemeViewState implements SpecialVisualizationState { this.fullNodeDatabase ) - this.indexedFeatures = layoutSource - let currentViewIndex = 0 const empty = [] this.currentView = new StaticFeatureSource( @@ -242,13 +242,19 @@ export default class ThemeViewState implements SpecialVisualizationState { this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) this.dataIsLoading = layoutSource.isLoading + this.indexedFeatures = layoutSource + this.featureProperties = new FeaturePropertiesStore(layoutSource) + this.favourites = new FavouritesFeatureSource( + this.osmConnection, + this.featureProperties, + layoutSource, + layout + ) - const indexedElements = this.indexedFeatures - this.featureProperties = new FeaturePropertiesStore(indexedElements) this.changes = new Changes( { dryRun: this.featureSwitches.featureSwitchIsTesting, - allElements: indexedElements, + allElements: layoutSource, featurePropertiesStore: this.featureProperties, osmConnection: this.osmConnection, historicalUserLocations: this.geolocation.historicalUserLocations, @@ -258,7 +264,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.historicalUserLocations = this.geolocation.historicalUserLocations this.newFeatures = new NewGeometryFromChangesFeatureSource( this.changes, - indexedElements, + layoutSource, this.featureProperties ) layoutSource.addSource(this.newFeatures) @@ -627,7 +633,10 @@ export default class ThemeViewState implements SpecialVisualizationState { ) ), current_view: this.currentView, + favourite: this.favourites, } + + this.closestFeatures.registerSource(specialLayers.favourite, "favourite") if (this.layout?.lockLocation) { const bbox = new BBox(this.layout.lockLocation) this.mapProperties.maxbounds.setData(bbox) @@ -663,12 +672,16 @@ export default class ThemeViewState implements SpecialVisualizationState { rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) } + // enumarate all 'normal' layers and match them with the appropriate 'special' layer - if applicable this.layerState.filteredLayers.forEach((flayer) => { const id = flayer.layerDef.id const features: FeatureSource = specialLayers[id] if (features === undefined) { return } + if (id === "favourite") { + console.log("Matching special layer", id, flayer) + } this.featureProperties.trackFeatureSource(features) new ShowDataLayer(this.map, { diff --git a/src/UI/Base/LogoutButton.svelte b/src/UI/Base/LogoutButton.svelte index 020c411e1f..0da388fb89 100644 --- a/src/UI/Base/LogoutButton.svelte +++ b/src/UI/Base/LogoutButton.svelte @@ -4,12 +4,10 @@ import Translations from "../i18n/Translations"; import Tr from "./Tr.svelte"; - export let osmConnection: OsmConnection + export let osmConnection: OsmConnection; - diff --git a/src/UI/Map/DynamicIcon.svelte b/src/UI/Map/DynamicIcon.svelte index 51d7668c14..e7a28699e6 100644 --- a/src/UI/Map/DynamicIcon.svelte +++ b/src/UI/Map/DynamicIcon.svelte @@ -1,27 +1,7 @@ @@ -16,7 +16,9 @@ {#if marker && marker}
{#each marker as icon} - +
+ +
{/each}
{/if} diff --git a/src/UI/Map/Icon.svelte b/src/UI/Map/Icon.svelte index cccbef469b..a6a49eb521 100644 --- a/src/UI/Map/Icon.svelte +++ b/src/UI/Map/Icon.svelte @@ -1,27 +1,29 @@ {#if icon} -
{#if icon === "pin"} - + {:else if icon === "square"} - + {:else if icon === "circle"} - + {:else if icon === "checkmark"} - + {:else if icon === "clock"} - + {:else if icon === "close"} - + {:else if icon === "crosshair"} - + {:else if icon === "help"} - + {:else if icon === "home"} - + {:else if icon === "invalid"} - + {:else if icon === "location"} - + {:else if icon === "location_empty"} - + {:else if icon === "location_locked"} - + {:else if icon === "note"} - + {:else if icon === "resolved"} - + {:else if icon === "ring"} - + {:else if icon === "scissors"} - + {:else if icon === "teardrop"} - + {:else if icon === "teardrop_with_hole_green"} - + {:else if icon === "triangle"} - + {:else if icon === "brick_wall_square"} - + {:else if icon === "brick_wall_round"} - + {:else if icon === "gps_arrow"} - + {:else if icon === "checkmark"} - + {:else if icon === "help"} - + {:else if icon === "close"} - + {:else if icon === "invalid"} - + + {:else if icon === "heart"} + + {:else if icon === "heart_outline"} + {:else} - + {/if} -
{/if} diff --git a/src/UI/Map/Marker.svelte b/src/UI/Map/Marker.svelte index f7b0d12e2d..6afd6b68e0 100644 --- a/src/UI/Map/Marker.svelte +++ b/src/UI/Map/Marker.svelte @@ -1,16 +1,18 @@ {#if icons !== undefined && icons.length > 0}
{#each icons as icon} - +
+ +
{/each}
{/if} diff --git a/src/UI/Map/ShowDataLayer.ts b/src/UI/Map/ShowDataLayer.ts index 33be0dcdea..2f286148ab 100644 --- a/src/UI/Map/ShowDataLayer.ts +++ b/src/UI/Map/ShowDataLayer.ts @@ -12,11 +12,9 @@ import { Feature, Point } from "geojson" import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig" import { Utils } from "../../Utils" import * as range_layer from "../../../assets/layers/range/range.json" -import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter" import FilteredLayer from "../../Models/FilteredLayer" import SimpleFeatureSource from "../../Logic/FeatureSource/Sources/SimpleFeatureSource" -import { CLIENT_RENEG_LIMIT } from "tls" class PointRenderingLayer { private readonly _config: PointRenderingConfig diff --git a/src/UI/Popup/AllTagsPanel.svelte b/src/UI/Popup/AllTagsPanel.svelte index 81bc9219e3..f4d3dd5264 100644 --- a/src/UI/Popup/AllTagsPanel.svelte +++ b/src/UI/Popup/AllTagsPanel.svelte @@ -18,7 +18,7 @@ ...(state?.layoutToUse?.layers?.map((l) => l.calculatedTags?.map((c) => c[0]) ?? []) ?? []) ) - const allTags = tags.map((tags) => { + const allTags = tags.mapD((tags) => { const parts: (string | BaseUIElement)[][] = [] for (const key in tags) { let v = tags[key] diff --git a/src/UI/Popup/MarkAsFavourite.svelte b/src/UI/Popup/MarkAsFavourite.svelte new file mode 100644 index 0000000000..394e4a1c39 --- /dev/null +++ b/src/UI/Popup/MarkAsFavourite.svelte @@ -0,0 +1,48 @@ + + + +{#if $isFavourite} +
+ markFavourite(false)} /> +
+ +
+
+ +{:else} +
+ markFavourite(true)} /> + +
+{/if} +
diff --git a/src/UI/Popup/MarkAsFavouriteMini.svelte b/src/UI/Popup/MarkAsFavouriteMini.svelte new file mode 100644 index 0000000000..b8d242aacd --- /dev/null +++ b/src/UI/Popup/MarkAsFavouriteMini.svelte @@ -0,0 +1,36 @@ + + + + {#if $isFavourite} + + {:else} + + {/if} + diff --git a/src/UI/Popup/TagRendering/TagRenderingMapping.svelte b/src/UI/Popup/TagRendering/TagRenderingMapping.svelte index eaf9707a57..242f206e7b 100644 --- a/src/UI/Popup/TagRendering/TagRenderingMapping.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingMapping.svelte @@ -6,6 +6,7 @@ import { UIEventSource } from "../../../Logic/UIEventSource" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import { twJoin } from "tailwind-merge" + import Icon from "../../Map/Icon.svelte"; export let selectedElement: Feature export let tags: UIEventSource> @@ -28,12 +29,7 @@ {#if mapping.icon !== undefined}
- +
{:else if mapping.then !== undefined} diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 28125837cd..5fd4f9fce4 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -17,6 +17,7 @@ import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" import { RasterLayerPolygon } from "../Models/RasterLayers" import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" import { OsmTags } from "../Models/OsmFeature" +import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource" /** * The state needed to render a special Visualisation. @@ -33,7 +34,6 @@ export interface SpecialVisualizationState { } readonly indexedFeatures: IndexedFeatureSource - /** * Some features will create a new element that should be displayed. * These can be injected by appending them to this featuresource (and pinging it) @@ -59,6 +59,8 @@ export interface SpecialVisualizationState { readonly selectedLayer: UIEventSource readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> + readonly favourites: FavouritesFeatureSource + /** * If data is currently being fetched from external sources */ diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index f32f28d9e8..d2da94d4ba 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -79,6 +79,8 @@ import ThemeViewState from "../Models/ThemeViewState" import LanguagePicker from "./InputElement/LanguagePicker.svelte" import LogoutButton from "./Base/LogoutButton.svelte" import OpenJosm from "./Base/OpenJosm.svelte" +import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte" +import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte" class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -1481,7 +1483,7 @@ export default class SpecialVisualizations { const tags = (( state )).geolocation.currentUserLocation.features.map( - (features) => features[0].properties + (features) => features[0]?.properties ) return new SvelteUIElement(AllTagsPanel, { state, @@ -1489,6 +1491,46 @@ export default class SpecialVisualizations { }) }, }, + { + funcName: "favourite_status", + needsUrls: [], + docs: "A button that allows a (logged in) contributor to mark a location as a favourite location", + args: [], + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement { + return new SvelteUIElement(MarkAsFavourite, { + tags: tagSource, + state, + layer, + feature, + }) + }, + }, + { + funcName: "favourite_icon", + needsUrls: [], + docs: "A small button that allows a (logged in) contributor to mark a location as a favourite location, sized to fit a title-icon", + args: [], + constr( + state: SpecialVisualizationState, + tagSource: UIEventSource>, + argument: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement { + return new SvelteUIElement(MarkAsFavouriteMini, { + tags: tagSource, + state, + layer, + feature, + }) + }, + }, ] specialVisualizations.push(new AutoApplyButton(specialVisualizations)) diff --git a/src/UI/Test.svelte b/src/UI/Test.svelte index 47b2ce975a..7103dc25e0 100644 --- a/src/UI/Test.svelte +++ b/src/UI/Test.svelte @@ -1,11 +1,16 @@ - + + diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index a3b81e5592..0db04b1f87 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -76,7 +76,6 @@ let showCrosshair = state.userRelatedState.showCrosshair; let arrowKeysWereUsed = state.mapProperties.lastKeyNavigation; let centerFeatures = state.closestFeatures.features; - $: console.log("Centerfeatures are", $centerFeatures) const selectedElementView = selectedElement.map( (selectedElement) => { // Svelte doesn't properly reload some of the legacy UI-elements @@ -232,7 +231,7 @@ - {#if $arrowKeysWereUsed !== undefined} + {#if $arrowKeysWereUsed !== undefined && $centerFeatures?.length > 0}
{#each $centerFeatures as feat, i (feat.properties.id)}
From 3ce21f61cb92f50c8ae90a97b7b090773f7f3e86 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 23 Nov 2023 17:06:30 +0100 Subject: [PATCH 03/22] Favourites: stabilize preferences and adding/removing favourites --- .../Sources/FavouritesFeatureSource.ts | 95 +++++++++++-------- src/Logic/Osm/OsmPreferences.ts | 33 ++++++- src/Logic/State/UserRelatedState.ts | 11 ++- src/Logic/Web/MangroveReviews.ts | 2 +- src/UI/Map/DynamicMarker.svelte | 2 +- src/UI/Map/Icon.svelte | 4 +- src/Utils.ts | 6 +- .../Conversion/PrepareTheme.spec.ts | 16 +++- 8 files changed, 122 insertions(+), 47 deletions(-) diff --git a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts index 4589d94935..bd6d52b4d5 100644 --- a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts @@ -1,6 +1,6 @@ import StaticFeatureSource from "./StaticFeatureSource" import { Feature } from "geojson" -import { Store, UIEventSource } from "../../UIEventSource" +import { Store, Stores, UIEventSource } from "../../UIEventSource" import { OsmConnection } from "../../Osm/OsmConnection" import { OsmId } from "../../../Models/OsmFeature" import { GeoOperations } from "../../GeoOperations" @@ -14,6 +14,7 @@ import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" export default class FavouritesFeatureSource extends StaticFeatureSource { public static readonly prefix = "mapcomplete-favourite-" private readonly _osmConnection: OsmConnection + private readonly _detectedIds: Store constructor( connection: OsmConnection, @@ -21,62 +22,78 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { allFeatures: IndexedFeatureSource, layout: LayoutConfig ) { - const detectedIds = new UIEventSource>(undefined) - const features: Store = connection.preferencesHandler.preferences.map( - (prefs) => { + const features: Store = Stores.ListStabilized( + connection.preferencesHandler.preferences.map((prefs) => { const feats: Feature[] = [] const allIds = new Set() for (const key in prefs) { if (!key.startsWith(FavouritesFeatureSource.prefix)) { continue } - const id = key.substring(FavouritesFeatureSource.prefix.length) - const osmId = id.replace("-", "/") - if (id.indexOf("-property-") > 0 || id.indexOf("-layer") > 0) { - continue + + try { + const feat = FavouritesFeatureSource.ExtractFavourite(key, prefs) + if (!feat) { + continue + } + feats.push(feat) + allIds.add(feat.properties.id) + } catch (e) { + console.error("Could not create favourite from", key, "due to", e) } - allIds.add(osmId) - const geometry = <[number, number]>JSON.parse(prefs[key]) - const properties = FavouritesFeatureSource.getPropertiesFor(connection, id) - properties._orig_layer = prefs[FavouritesFeatureSource.prefix + id + "-layer"] - if (layout.layers.some((l) => l.id === properties._orig_layer)) { - continue - } - properties.id = osmId - properties._favourite = "yes" - feats.push({ - type: "Feature", - properties, - geometry: { - type: "Point", - coordinates: geometry, - }, - }) } - console.log("Favouritess are", feats) - detectedIds.setData(allIds) return feats - } + }) ) - super(features) + const featuresWithoutAlreadyPresent = features.map((features) => + features.filter( + (feat) => !layout.layers.some((l) => l.id === feat.properties._orig_layer) + ) + ) + + super(featuresWithoutAlreadyPresent) this._osmConnection = connection - detectedIds.addCallbackAndRunD((detected) => + this._detectedIds = Stores.ListStabilized( + features.map((feats) => feats.map((f) => f.properties.id)) + ) + this._detectedIds.addCallbackAndRunD((detected) => this.markFeatures(detected, indexedSource, allFeatures) ) // We use the indexedFeatureSource as signal to update allFeatures.features.map((_) => - this.markFeatures(detectedIds.data, indexedSource, allFeatures) + this.markFeatures(this._detectedIds.data, indexedSource, allFeatures) ) } + private static ExtractFavourite(key: string, prefs: Record): Feature { + const id = key.substring(FavouritesFeatureSource.prefix.length) + const osmId = id.replace("-", "/") + if (id.indexOf("-property-") > 0 || id.endsWith("-layer") || id.endsWith("-theme")) { + return undefined + } + const geometry = <[number, number]>JSON.parse(prefs[key]) + const properties = FavouritesFeatureSource.getPropertiesFor(prefs, id) + properties._orig_layer = prefs[FavouritesFeatureSource.prefix + id + "-layer"] + + properties.id = osmId + properties._favourite = "yes" + return { + type: "Feature", + properties, + geometry: { + type: "Point", + coordinates: geometry, + }, + } + } + private static getPropertiesFor( - osmConnection: OsmConnection, + prefs: Record, id: string ): Record { const properties: Record = {} - const prefs = osmConnection.preferencesHandler.preferences.data const minLength = FavouritesFeatureSource.prefix.length + id.length + "-property-".length for (const key in prefs) { if (key.length < minLength) { @@ -104,14 +121,18 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { if (isFavourite) { const center = GeoOperations.centerpointCoordinates(feature) pref.setData(JSON.stringify(center)) + this._osmConnection.GetPreference("favourite-" + id + "-layer").setData(layer) this._osmConnection.GetPreference("favourite-" + id + "-theme").setData(theme) - for (const key in tags.data) { const pref = this._osmConnection.GetPreference( "favourite-" + id + "-property-" + key.replaceAll(":", "__") ) - pref.setData(tags.data[key]) + const v = tags.data[key] + if (v === "" || !v) { + continue + } + pref.setData("" + v) } } else { this._osmConnection.preferencesHandler.removeAllWithPrefix( @@ -129,7 +150,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { } private markFeatures( - detected: Set, + detected: string[], featureProperties: FeaturePropertiesStore, allFeatures: IndexedFeatureSource ) { @@ -141,7 +162,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { } const store = featureProperties.getStore(id) const origValue = store.data._favourite - if (detected.has(id)) { + if (detected.indexOf(id) >= 0) { if (origValue !== "yes") { store.data._favourite = "yes" store.ping() diff --git a/src/Logic/Osm/OsmPreferences.ts b/src/Logic/Osm/OsmPreferences.ts index e365362587..1035dec823 100644 --- a/src/Logic/Osm/OsmPreferences.ts +++ b/src/Logic/Osm/OsmPreferences.ts @@ -12,6 +12,10 @@ export class OsmPreferences { "all-osm-preferences", {} ) + /** + * A map containing the individual preference sources + * @private + */ private readonly preferenceSources = new Map>() private auth: any private userDetails: UIEventSource @@ -21,7 +25,10 @@ export class OsmPreferences { this.auth = auth this.userDetails = osmConnection.userDetails const self = this - osmConnection.OnLoggedIn(() => self.UpdatePreferences()) + osmConnection.OnLoggedIn(() => { + self.UpdatePreferences(true) + return true + }) } /** @@ -72,11 +79,19 @@ export class OsmPreferences { let i = 0 while (str !== "") { if (str === undefined || str === "undefined") { + source.setData(undefined) throw ( "Got 'undefined' or a literal string containing 'undefined' for a long preference with name " + key ) } + if (str === "undefined") { + source.setData(undefined) + throw ( + "Got a literal string containing 'undefined' for a long preference with name " + + key + ) + } if (i > 100) { throw "This long preference is getting very long... " } @@ -197,7 +212,7 @@ export class OsmPreferences { }) } - private UpdatePreferences() { + private UpdatePreferences(forceUpdate?: boolean) { const self = this this.auth.xhr( { @@ -210,11 +225,22 @@ export class OsmPreferences { return } const prefs = value.getElementsByTagName("preference") + const seenKeys = new Set() for (let i = 0; i < prefs.length; i++) { const pref = prefs[i] const k = pref.getAttribute("k") const v = pref.getAttribute("v") self.preferences.data[k] = v + seenKeys.add(k) + } + if (forceUpdate) { + for (let key in self.preferences.data) { + if (seenKeys.has(key)) { + continue + } + console.log("Deleting key", key, "as we didn't find it upstream") + delete self.preferences.data[key] + } } // We merge all the preferences: new keys are uploaded @@ -289,9 +315,10 @@ export class OsmPreferences { removeAllWithPrefix(prefix: string) { for (const key in this.preferences.data) { if (key.startsWith(prefix)) { - this.GetPreference(key, undefined, { prefix: "" }).setData(undefined) + this.GetPreference(key, "", { prefix: "" }).setData(undefined) console.log("Clearing preference", key) } } + this.preferences.ping() } } diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index f38f32badd..bbc7c9f8ce 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -294,6 +294,9 @@ export default class UserRelatedState { osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { for (const k in newPrefs) { const v = newPrefs[k] + if (v === "undefined" || !v) { + continue + } if (k.endsWith("-combined-length")) { const l = Number(v) const key = k.substring(0, k.length - "length".length) @@ -308,7 +311,6 @@ export default class UserRelatedState { } amendedPrefs.ping() - console.log("Amended prefs are:", amendedPrefs.data) }) const translationMode = osmConnection.GetPreference("translation-mode") @@ -395,6 +397,13 @@ export default class UserRelatedState { } if (tags[key + "-combined-0"]) { // A combined value exists + console.log( + "Trying to get a long preference for ", + key, + "with length value", + tags[key], + "as -combined-0 exists" + ) this.osmConnection.GetLongPreference(key, "").setData(tags[key]) } else { this.osmConnection diff --git a/src/Logic/Web/MangroveReviews.ts b/src/Logic/Web/MangroveReviews.ts index 19e2228d49..8bcfe38076 100644 --- a/src/Logic/Web/MangroveReviews.ts +++ b/src/Logic/Web/MangroveReviews.ts @@ -14,7 +14,7 @@ export class MangroveIdentity { const keypairEventSource = new UIEventSource(undefined) this.keypair = keypairEventSource mangroveIdentity.addCallbackAndRunD(async (data) => { - if (data === "") { + if (!data) { return } const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data)) diff --git a/src/UI/Map/DynamicMarker.svelte b/src/UI/Map/DynamicMarker.svelte index 609fbda604..a332cda0e0 100644 --- a/src/UI/Map/DynamicMarker.svelte +++ b/src/UI/Map/DynamicMarker.svelte @@ -8,7 +8,7 @@ * Renders a 'marker', which consists of multiple 'icons' */ export let marker: IconConfig[] = config?.marker; - export let rotation: TagRenderingConfig; + export let rotation: TagRenderingConfig = undefined; export let tags: Store>; let _rotation = rotation ? tags.map(tags => rotation.GetRenderValue(tags).Subs(tags).txt) : new ImmutableStore(0); diff --git a/src/UI/Map/Icon.svelte b/src/UI/Map/Icon.svelte index a6a49eb521..dc8783494f 100644 --- a/src/UI/Map/Icon.svelte +++ b/src/UI/Map/Icon.svelte @@ -32,8 +32,8 @@ */ export let icon: string | undefined; - export let color: string | undefined; - export let clss: string | undefined + export let color: string | undefined = undefined + export let clss: string | undefined = undefined {#if icon} diff --git a/src/Utils.ts b/src/Utils.ts index c89e4a468e..7c5c67012e 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -301,10 +301,14 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be if (str === undefined || str === null) { return undefined } + if (typeof str !== "string") { + console.error("Not a string:", str) + return undefined + } if (str.length <= l) { return str } - return str.substr(0, l - 3) + "..." + return str.substr(0, l - 1) + "…" } /** diff --git a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts index f6ab8d60a3..009c9f08a4 100644 --- a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts +++ b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts @@ -125,7 +125,21 @@ describe("PrepareTheme", () => { en: "Test layer - please ignore", }, titleIcons: [], - pointRendering: [{ location: ["point"], label: "xyz" }], + pointRendering: [ + { + location: ["point"], + label: "xyz", + iconBadges: [ + { + if: "_favourite=yes", + then: { + id: "circlewhiteheartred", + render: "circle:white;heart:red", + }, + }, + ], + }, + ], lineRendering: [{ width: 1 }], } const sharedLayers = constructSharedLayers() From 78238dccc7b5df6044618ac06452951165d37574 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 29 Nov 2023 14:29:11 +0100 Subject: [PATCH 04/22] Logic: enable to re-export tagsFilter as TagsConfig; improve optimization for comparingTag --- .../{favourite.json => favourite.proto.json} | 0 scripts/generateFavouritesLayer.ts | 25 +++++++ src/Logic/Tags/And.ts | 5 ++ src/Logic/Tags/ComparingTag.ts | 68 +++++++++++++++++-- src/Logic/Tags/Or.ts | 11 ++- src/Logic/Tags/RegexTag.ts | 5 ++ src/Logic/Tags/SubstitutingTag.ts | 5 ++ src/Logic/Tags/Tag.ts | 5 ++ src/Logic/Tags/TagUtils.ts | 24 +++---- src/Logic/Tags/TagsFilter.ts | 4 ++ src/UI/Favourites/FavouriteSummary.svelte | 0 src/UI/Favourites/Favourites.svelte | 8 +++ 12 files changed, 140 insertions(+), 20 deletions(-) rename assets/layers/favourite/{favourite.json => favourite.proto.json} (100%) create mode 100644 scripts/generateFavouritesLayer.ts create mode 100644 src/UI/Favourites/FavouriteSummary.svelte create mode 100644 src/UI/Favourites/Favourites.svelte diff --git a/assets/layers/favourite/favourite.json b/assets/layers/favourite/favourite.proto.json similarity index 100% rename from assets/layers/favourite/favourite.json rename to assets/layers/favourite/favourite.proto.json diff --git a/scripts/generateFavouritesLayer.ts b/scripts/generateFavouritesLayer.ts new file mode 100644 index 0000000000..334742b45d --- /dev/null +++ b/scripts/generateFavouritesLayer.ts @@ -0,0 +1,25 @@ +import Script from "./Script" +import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson" +import { readFileSync, writeFileSync } from "fs" +import { AllSharedLayers } from "../src/Customizations/AllSharedLayers" + +class PrepareFavouritesLayerJson extends Script { + constructor() { + super("Prepares the 'favourites'-layer") + } + + async main(args: string[]): Promise { + const allConfigs = AllSharedLayers.getSharedLayersConfigs() + const proto = this.readLayer("favourite/favourite.proto.json") + const questions = allConfigs.get("questions") + proto.tagRenderings.push(...questions.tagRenderings) + + writeFileSync("./assets/layers/favourite/favourite.json", JSON.stringify(proto, null, " ")) + } + + private readLayer(path: string): LayerConfigJson { + return JSON.parse(readFileSync("./assets/layers/" + path, "utf8")) + } +} + +new PrepareFavouritesLayerJson().run() diff --git a/src/Logic/Tags/And.ts b/src/Logic/Tags/And.ts index 378518dd14..98da75ae0e 100644 --- a/src/Logic/Tags/And.ts +++ b/src/Logic/Tags/And.ts @@ -3,6 +3,7 @@ import { Or } from "./Or" import { TagUtils } from "./TagUtils" import { Tag } from "./Tag" import { RegexTag } from "./RegexTag" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" export class And extends TagsFilter { public and: TagsFilter[] @@ -72,6 +73,10 @@ export class And extends TagsFilter { return allChoices } + asJson(): TagConfigJson { + return { and: this.and.map((a) => a.asJson()) } + } + asHumanString(linkToWiki: boolean, shorten: boolean, properties: Record) { return this.and .map((t) => { diff --git a/src/Logic/Tags/ComparingTag.ts b/src/Logic/Tags/ComparingTag.ts index 685cd2550a..8009662f4b 100644 --- a/src/Logic/Tags/ComparingTag.ts +++ b/src/Logic/Tags/ComparingTag.ts @@ -1,18 +1,23 @@ import { TagsFilter } from "./TagsFilter" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" +import { Tag } from "./Tag" export default class ComparingTag implements TagsFilter { private readonly _key: string private readonly _predicate: (value: string) => boolean - private readonly _representation: string + private readonly _representation: "<" | ">" | "<=" | ">=" + private readonly _boundary: string constructor( key: string, predicate: (value: string | undefined) => boolean, - representation: string = "" + representation: "<" | ">" | "<=" | ">=", + boundary: string ) { this._key = key this._predicate = predicate this._representation = representation + this._boundary = boundary } asChange(properties: Record): { k: string; v: string }[] { @@ -20,15 +25,64 @@ export default class ComparingTag implements TagsFilter { } asHumanString(linkToWiki: boolean, shorten: boolean, properties: Record) { - return this._key + this._representation + return this._key + this._representation + this._boundary } asOverpass(): string[] { throw "A comparable tag can not be used as overpass filter" } + /** + * const tg = new ComparingTag("key", value => (Number(value) < 42), "<", "42") + * const tg0 = new ComparingTag("key", value => (Number(value) < 42), "<", "42") + * const tg1 = new ComparingTag("key", value => (Number(value) <= 42), "<=", "42") + * const against = new ComparingTag("key", value => (Number(value) > 0), ">", "0") + * tg.shadows(new Tag("key", "41")) // => true + * tg.shadows(new Tag("key", "0")) // => true + * tg.shadows(new Tag("key", "43")) // => false + * tg.shadows(new Tag("key", "0")) // => true + * tg.shadows(tg) // => true + * tg.shadows(tg0) // => true + * tg.shadows(against) // => false + * tg1.shadows(tg0) // => true + * tg0.shadows(tg1) // => false + * + */ shadows(other: TagsFilter): boolean { - return other === this + if (other === this) { + return true + } + if (other instanceof ComparingTag) { + if (other._key !== this._key) { + return false + } + const selfDesc = this._representation === "<" || this._representation === "<=" + const otherDesc = other._representation === "<" || other._representation === "<=" + if (selfDesc !== otherDesc) { + return false + } + if ( + this._boundary === other._boundary && + this._representation === other._representation + ) { + return true + } + if (this._predicate(other._boundary)) { + return true + } + return false + } + + if (other instanceof Tag) { + if (other.key !== this._key) { + return false + } + if (this.matchesProperties({ [other.key]: other.value })) { + return true + } + } + + return false } isUsableAsAnswer(): boolean { @@ -38,7 +92,7 @@ export default class ComparingTag implements TagsFilter { /** * Checks if the properties match * - * const t = new ComparingTag("key", (x => Number(x) < 42)) + * const t = new ComparingTag("key", (x => Number(x) < 42), "<", "42") * t.matchesProperties({key: 42}) // => false * t.matchesProperties({key: 41}) // => true * t.matchesProperties({key: 0}) // => true @@ -56,6 +110,10 @@ export default class ComparingTag implements TagsFilter { return [] } + asJson(): TagConfigJson { + return this._key + this._representation + } + optimize(): TagsFilter | boolean { return this } diff --git a/src/Logic/Tags/Or.ts b/src/Logic/Tags/Or.ts index f7018c40b0..da932049a1 100644 --- a/src/Logic/Tags/Or.ts +++ b/src/Logic/Tags/Or.ts @@ -1,6 +1,7 @@ import { TagsFilter } from "./TagsFilter" import { TagUtils } from "./TagUtils" import { And } from "./And" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" export class Or extends TagsFilter { public or: TagsFilter[] @@ -27,6 +28,10 @@ export class Or extends TagsFilter { return false } + asJson(): TagConfigJson { + return { or: this.or.map((o) => o.asJson()) } + } + /** * * import {Tag} from "./Tag"; @@ -174,9 +179,9 @@ export class Or extends TagsFilter { const newOrs: TagsFilter[] = [] let containedAnds: And[] = [] for (const tf of optimized) { - if (tf instanceof Or) { + if (tf["or"]) { // expand all the nested ors... - newOrs.push(...tf.or) + newOrs.push(...tf["or"]) } else if (tf instanceof And) { // partition of all the ands containedAnds.push(tf) @@ -191,7 +196,7 @@ export class Or extends TagsFilter { const cleanedContainedANds: And[] = [] outer: for (let containedAnd of containedAnds) { for (const known of newOrs) { - // input for optimazation: (K=V | (X=Y & K=V)) + // input for optimization: (K=V | (X=Y & K=V)) // containedAnd: (X=Y & K=V) // newOrs (and thus known): (K=V) --> false const cleaned = containedAnd.removePhraseConsideredKnown(known, false) diff --git a/src/Logic/Tags/RegexTag.ts b/src/Logic/Tags/RegexTag.ts index e6486113a7..d133c3663d 100644 --- a/src/Logic/Tags/RegexTag.ts +++ b/src/Logic/Tags/RegexTag.ts @@ -1,5 +1,6 @@ import { Tag } from "./Tag" import { TagsFilter } from "./TagsFilter" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" export class RegexTag extends TagsFilter { public readonly key: RegExp | string @@ -82,6 +83,10 @@ export class RegexTag extends TagsFilter { } } + asJson(): TagConfigJson { + return this.asHumanString() + } + isUsableAsAnswer(): boolean { return false } diff --git a/src/Logic/Tags/SubstitutingTag.ts b/src/Logic/Tags/SubstitutingTag.ts index 7d5435a068..6e9d293116 100644 --- a/src/Logic/Tags/SubstitutingTag.ts +++ b/src/Logic/Tags/SubstitutingTag.ts @@ -1,6 +1,7 @@ import { TagsFilter } from "./TagsFilter" import { Tag } from "./Tag" import { Utils } from "../../Utils" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" /** * The substituting-tag uses the tags of a feature a variables and replaces them. @@ -45,6 +46,10 @@ export default class SubstitutingTag implements TagsFilter { ) } + asJson(): TagConfigJson { + return this._key + (this._invert ? "!" : "") + ":=" + this._value + } + asOverpass(): string[] { throw "A variable with substitution can not be used to query overpass" } diff --git a/src/Logic/Tags/Tag.ts b/src/Logic/Tags/Tag.ts index 7cb01a9d5e..b532b7053c 100644 --- a/src/Logic/Tags/Tag.ts +++ b/src/Logic/Tags/Tag.ts @@ -1,5 +1,6 @@ import { Utils } from "../../Utils" import { TagsFilter } from "./TagsFilter" +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" export class Tag extends TagsFilter { public key: string @@ -67,6 +68,10 @@ export class Tag extends TagsFilter { return [`["${this.key}"="${this.value}"]`] } + asJson(): TagConfigJson { + return this.key + "=" + this.value + } + /** const t = new Tag("key", "value") diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index adc8629229..91d3a4ca6f 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -15,13 +15,14 @@ type Tags = Record export type UploadableTag = Tag | SubstitutingTag | And export class TagUtils { - public static readonly comparators: ReadonlyArray<[string, (a: number, b: number) => boolean]> = - [ - ["<=", (a, b) => a <= b], - [">=", (a, b) => a >= b], - ["<", (a, b) => a < b], - [">", (a, b) => a > b], - ] + public static readonly comparators: ReadonlyArray< + ["<" | ">" | "<=" | ">=", (a: number, b: number) => boolean] + > = [ + ["<=", (a, b) => a <= b], + [">=", (a, b) => a >= b], + ["<", (a, b) => a < b], + [">", (a, b) => a > b], + ] public static modeDocumentation: Record< string, { name: string; docs: string; uploadable?: boolean; overpassSupport: boolean } @@ -735,11 +736,10 @@ export class TagUtils { const tag = json as string for (const [operator, comparator] of TagUtils.comparators) { if (tag.indexOf(operator) >= 0) { - const split = Utils.SplitFirst(tag, operator) - - let val = Number(split[1].trim()) + const split = Utils.SplitFirst(tag, operator).map((v) => v.trim()) + let val = Number(split[1]) if (isNaN(val)) { - val = new Date(split[1].trim()).getTime() + val = new Date(split[1]).getTime() } const f = (value: string | number | undefined) => { @@ -762,7 +762,7 @@ export class TagUtils { } return comparator(b, val) } - return new ComparingTag(split[0], f, operator + val) + return new ComparingTag(split[0], f, operator, "" + val) } } diff --git a/src/Logic/Tags/TagsFilter.ts b/src/Logic/Tags/TagsFilter.ts index b06158b4f9..e925a76ef6 100644 --- a/src/Logic/Tags/TagsFilter.ts +++ b/src/Logic/Tags/TagsFilter.ts @@ -1,3 +1,5 @@ +import { TagConfigJson } from "../../Models/ThemeConfig/Json/TagConfigJson" + export abstract class TagsFilter { abstract asOverpass(): string[] @@ -17,6 +19,8 @@ export abstract class TagsFilter { properties: Record ): string + abstract asJson(): TagConfigJson + abstract usedKeys(): string[] /** diff --git a/src/UI/Favourites/FavouriteSummary.svelte b/src/UI/Favourites/FavouriteSummary.svelte new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/UI/Favourites/Favourites.svelte b/src/UI/Favourites/Favourites.svelte new file mode 100644 index 0000000000..ff6e0331f1 --- /dev/null +++ b/src/UI/Favourites/Favourites.svelte @@ -0,0 +1,8 @@ + From 595503cfc35c8db3cb4ee4b28f82b7c91ee75dff Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 30 Nov 2023 00:30:46 +0100 Subject: [PATCH 05/22] Fix: improve optimization; add tests --- src/Logic/Tags/And.ts | 32 ++++++++++++++++++++++++++------ src/Logic/Tags/Or.ts | 15 +++++++++++++-- src/Logic/Tags/TagUtils.ts | 8 ++++++++ 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/Logic/Tags/And.ts b/src/Logic/Tags/And.ts index 98da75ae0e..dff0919f87 100644 --- a/src/Logic/Tags/And.ts +++ b/src/Logic/Tags/And.ts @@ -233,6 +233,15 @@ export class And extends TagsFilter { return And.construct(newAnds) } + /** + * const raw = {"and": [{"or":["leisure=playground","playground!=forest"]},{"or":["leisure=playground","playground!=forest"]}]} + * const parsed = TagUtils.Tag(raw) + * parsed.optimize().asJson() // => {"or":["leisure=playground","playground!=forest"]} + * + * const raw = {"and": [{"and":["advertising=screen"]}, {"and":["advertising~*"]}]}] + * const parsed = TagUtils.Tag(raw) + * parsed.optimize().asJson() // => "advertising=screen" + */ optimize(): TagsFilter | boolean { if (this.and.length === 0) { return true @@ -294,9 +303,17 @@ export class And extends TagsFilter { optimized.splice(i, 1) i-- } - } else if (v !== opt.value) { - // detected an internal conflict - return false + } else { + if (!v.match(opt.value)) { + // We _know_ that for the key of the RegexTag `opt`, the value will be `v`. + // As such, if `opt.value` cannot match `v`, we detected an internal conflict and can fail + + return false + } else { + // Another tag already provided a _stricter_ value then this regex, so we can remove this one! + optimized.splice(i, 1) + i-- + } } } } @@ -374,10 +391,13 @@ export class And extends TagsFilter { const elements = containedOr.or.filter( (candidate) => !commonValues.some((cv) => cv.shadows(candidate)) ) - newOrs.push(Or.construct(elements)) + if (elements.length > 0) { + newOrs.push(Or.construct(elements)) + } + } + if (newOrs.length > 0) { + commonValues.push(And.construct(newOrs)) } - - commonValues.push(And.construct(newOrs)) const result = new Or(commonValues).optimize() if (result === false) { return false diff --git a/src/Logic/Tags/Or.ts b/src/Logic/Tags/Or.ts index da932049a1..a0c0f6622c 100644 --- a/src/Logic/Tags/Or.ts +++ b/src/Logic/Tags/Or.ts @@ -162,6 +162,12 @@ export class Or extends TagsFilter { return Or.construct(newOrs) } + /** + * const raw = {"or": [{"and":["leisure=playground","playground!=forest"]},{"and":["leisure=playground","playground!=forest"]}]} + * const parsed = TagUtils.Tag(raw) + * parsed.optimize().asJson() // => {"and":["leisure=playground","playground!=forest"]} + * + */ optimize(): TagsFilter | boolean { if (this.or.length === 0) { return false @@ -241,16 +247,21 @@ export class Or extends TagsFilter { const elements = containedAnd.and.filter( (candidate) => !commonValues.some((cv) => cv.shadows(candidate)) ) + if (elements.length == 0) { + continue + } newAnds.push(And.construct(elements)) } + if (newAnds.length > 0) { + commonValues.push(Or.construct(newAnds)) + } - commonValues.push(Or.construct(newAnds)) const result = new And(commonValues).optimize() if (result === true) { return true } else if (result === false) { // neutral element: skip - } else { + } else if (commonValues.length > 0) { newOrs.push(And.construct(commonValues)) } } diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index 91d3a4ca6f..0a8de68d69 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -325,6 +325,14 @@ export class TagUtils { return tags } + static optimzeJson(json: TagConfigJson): TagConfigJson | boolean { + const optimized = TagUtils.Tag(json).optimize() + if (optimized === true || optimized === false) { + return optimized + } + return optimized.asJson() + } + /** * Given multiple tagsfilters which can be used as answer, will take the tags with the same keys together as set. * From 8f7b731d29ca7daec1063a6833716e86333b0ffd Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 30 Nov 2023 00:31:26 +0100 Subject: [PATCH 06/22] Fix: actually include _all_ features --- src/Logic/FeatureSource/Sources/NearbyFeatureSource.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Logic/FeatureSource/Sources/NearbyFeatureSource.ts b/src/Logic/FeatureSource/Sources/NearbyFeatureSource.ts index cec3f4de9f..703103bc01 100644 --- a/src/Logic/FeatureSource/Sources/NearbyFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/NearbyFeatureSource.ts @@ -29,16 +29,16 @@ export default class NearbyFeatureSource implements FeatureSource { this.features = Stores.ListStabilized(this._result) - sources.forEach((source, layer) => {}) + sources.forEach((source, layer) => { + this.registerSource(source, layer) + }) } public registerSource(source: FeatureSource, layerId: string) { - let minzoom = 999 const flayer = this._layerState?.filteredLayers.get(layerId) if (!flayer) { return } - minzoom = Math.min(minzoom, flayer.layerDef.minzoom) const calcSource = this.createSource( source.features, flayer.layerDef.minzoom, From eb444ab84965ae75de0df12e638cbc67ef325e9c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 30 Nov 2023 00:38:37 +0100 Subject: [PATCH 07/22] Accessibility: small improvements to make keyboard navigation easier. See #1181 --- src/UI/Popup/TagRendering/Questionbox.svelte | 1 - .../TagRendering/TagRenderingEditable.svelte | 4 ++-- .../TagRendering/TagRenderingQuestion.svelte | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/UI/Popup/TagRendering/Questionbox.svelte b/src/UI/Popup/TagRendering/Questionbox.svelte index ff789eb760..6a1bcf8e70 100644 --- a/src/UI/Popup/TagRendering/Questionbox.svelte +++ b/src/UI/Popup/TagRendering/Questionbox.svelte @@ -8,7 +8,6 @@ import type { Feature } from "geojson"; import type { SpecialVisualizationState } from "../../SpecialVisualization"; import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; - import If from "../../Base/If.svelte"; import TagRenderingQuestion from "./TagRenderingQuestion.svelte"; import Tr from "../../Base/Tr.svelte"; import Translations from "../../i18n/Translations.js"; diff --git a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte index 27d6f09c45..a8c583e688 100644 --- a/src/UI/Popup/TagRendering/TagRenderingEditable.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingEditable.svelte @@ -11,7 +11,7 @@ import Translations from "../../i18n/Translations.js" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import { Utils } from "../../../Utils" - + import { twMerge } from "tailwind-merge" export let config: TagRenderingConfig export let tags: UIEventSource> export let selectedElement: Feature | undefined @@ -71,7 +71,7 @@ } -
+
{#if config.question && (!editingEnabled || $editingEnabled)} {#if editMode} diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 94f28218f2..ab918974e8 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -88,6 +88,9 @@ // TODO this has _to much_ values freeformInput.setData(unseenFreeformValues.join(";")) checkedMappings.push(unseenFreeformValues.length > 0) + freeformInput.addCallbackAndRun(freeformValue => { + checkedMappings[checkedMappings.length - 1] = !!freeformValue + }) } } if (confg.freeform?.key) { @@ -168,6 +171,12 @@ .then((changes) => state.changes.applyChanges(changes)) .catch(console.error) } + + function onInputKeypress(e: Event){ + if (e.key === "Enter") { + onSave(); + } + } let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false) let featureSwitchIsDebugging = @@ -254,9 +263,8 @@ bind:group={selectedMapping} name={"mappings-radio-" + config.id} value={i} - on:keypress={(e) => { - if (e.key === "Enter") onSave() - }} + on:keypress={onInputKeypress} + /> {/each} @@ -267,6 +275,7 @@ bind:group={selectedMapping} name={"mappings-radio-" + config.id} value={config.mappings?.length} + on:keypress={onInputKeypress} /> {/each} @@ -307,6 +317,7 @@ type="checkbox" name={"mappings-checkbox-" + config.id + "-" + config.mappings?.length} bind:checked={checkedMappings[config.mappings.length]} + on:keypress={onInputKeypress} /> (checkedMappings[config.mappings.length] = true)} on:submit={onSave} /> From 473931891c4aa58d28afcb220f7050a8dbb5c91b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 30 Nov 2023 00:39:55 +0100 Subject: [PATCH 08/22] Favourites: include _all_ tagRenderings --- .gitignore | 1 + assets/layers/favourite/favourite.proto.json | 31 +-- package.json | 2 +- scripts/generateFavouritesLayer.ts | 232 +++++++++++++++++- scripts/generateLayerOverview.ts | 26 +- src/Customizations/AllKnownLayouts.ts | 55 +++-- src/Logic/Actors/GeoLocationHandler.ts | 1 - .../Sources/FavouritesFeatureSource.ts | 7 + .../ThemeConfig/Conversion/Conversion.ts | 23 +- .../Conversion/CreateNoteImportLayer.ts | 2 +- .../ThemeConfig/Conversion/PrepareLayer.ts | 12 +- .../ThemeConfig/Conversion/Validation.ts | 34 ++- src/Models/ThemeViewState.ts | 43 +--- src/UI/BigComponents/UploadTraceToOsmUI.ts | 6 +- src/UI/Favourites/FavouriteSummary.svelte | 10 + src/UI/Favourites/Favourites.svelte | 15 +- src/UI/Popup/ExportAsGpxViz.ts | 18 +- src/UI/SpecialVisualizations.ts | 33 +-- src/UI/ThemeViewGUI.svelte | 23 +- .../Conversion/PrepareTheme.spec.ts | 16 +- 20 files changed, 436 insertions(+), 154 deletions(-) diff --git a/.gitignore b/.gitignore index c5a6e0be26..7124fdc776 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ scratch assets/editor-layer-index.json assets/generated/* src/assets/generated/ +assets/layers/favourite/favourite.json public/*.webmanifest /*.html !/index.html diff --git a/assets/layers/favourite/favourite.proto.json b/assets/layers/favourite/favourite.proto.json index 73ab805729..57ff79f1f7 100644 --- a/assets/layers/favourite/favourite.proto.json +++ b/assets/layers/favourite/favourite.proto.json @@ -1,4 +1,5 @@ { + "#":"no-translations", "pointRendering": [ { "location": [ @@ -37,35 +38,9 @@ "render": { "en": "Favourite location", "nl": "Favoriete locatie" - }, - "mappings": [ - { - "if": "name~*", - "then": { - "*": "{name}" - } - } - ] + } }, "tagRenderings": [ - { - "id": "Explanation", - "classes": "thanks", - "icon": { - "class": "large", - "path": "heart" - }, - "render": { - "en": "You marked this location as a personal favourite. As such, it is shown on every map you load.", - "nl": "Je hebt deze locatie als persoonlijke favoriet aangeduid en worden op alle MapComplete-kaarten getoond." - } - }, - { - "id": "show_images", - "render": { - "*": "{image_carousel()}" - } - }, - "all_tags" + ] } diff --git a/package.json b/package.json index f1ecefd071..816714f4f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.35.0", + "version": "0.36.0", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", diff --git a/scripts/generateFavouritesLayer.ts b/scripts/generateFavouritesLayer.ts index 334742b45d..2cc0abf9d6 100644 --- a/scripts/generateFavouritesLayer.ts +++ b/scripts/generateFavouritesLayer.ts @@ -2,24 +2,242 @@ import Script from "./Script" import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson" import { readFileSync, writeFileSync } from "fs" import { AllSharedLayers } from "../src/Customizations/AllSharedLayers" +import { AllKnownLayoutsLazy } from "../src/Customizations/AllKnownLayouts" +import { Utils } from "../src/Utils" +import { AddEditingElements } from "../src/Models/ThemeConfig/Conversion/PrepareLayer" +import { + MappingConfigJson, + QuestionableTagRenderingConfigJson, +} from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson" +import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson" +import { TagUtils } from "../src/Logic/Tags/TagUtils" +import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" +import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" + +export class GenerateFavouritesLayer extends Script { + private readonly layers: LayerConfigJson[] = [] -class PrepareFavouritesLayerJson extends Script { constructor() { super("Prepares the 'favourites'-layer") + const allThemes = new AllKnownLayoutsLazy(false).values() + for (const theme of allThemes) { + if (theme.hideFromOverview) { + continue + } + for (const layer of theme.layers) { + if (!layer.source) { + continue + } + if (layer.source.geojsonSource) { + continue + } + const layerConfig = AllSharedLayers.getSharedLayersConfigs().get(layer.id) + if (!layerConfig) { + continue + } + this.layers.push(layerConfig) + } + } + } + + private addTagRenderings(proto: LayerConfigJson) { + const blacklistedIds = new Set([ + "images", + "questions", + "mapillary", + "leftover-questions", + "last_edit", + "minimap", + "move-button", + "delete-button", + "all-tags", + ...AddEditingElements.addedElements, + ]) + + const generateTagRenderings: (string | QuestionableTagRenderingConfigJson)[] = [] + const trPerId = new Map< + string, + { conditions: TagConfigJson[]; tr: QuestionableTagRenderingConfigJson } + >() + for (const layerConfig of this.layers) { + if (!layerConfig.tagRenderings) { + continue + } + for (const tagRendering of layerConfig.tagRenderings) { + if (typeof tagRendering === "string") { + if (blacklistedIds.has(tagRendering)) { + continue + } + generateTagRenderings.push(tagRendering) + blacklistedIds.add(tagRendering) + continue + } + if (tagRendering["builtin"]) { + continue + } + const id = tagRendering.id + if (blacklistedIds.has(id)) { + continue + } + if (trPerId.has(id)) { + const old = trPerId.get(id).tr + + // We need to figure out if this was a 'recycled' tag rendering or just happens to have the same id + function isSame(fieldName: string) { + return old[fieldName]?.["en"] === tagRendering[fieldName]?.["en"] + } + + const sameQuestion = isSame("question") && isSame("render") + if (!sameQuestion) { + const newTr = Utils.Clone(tagRendering) + newTr.id = layerConfig.id + "_" + newTr.id + if (blacklistedIds.has(newTr.id)) { + continue + } + newTr.condition = { + and: Utils.NoNull([(newTr.condition, layerConfig.source["osmTags"])]), + } + generateTagRenderings.push(newTr) + blacklistedIds.add(newTr.id) + continue + } + } + if (!trPerId.has(id)) { + const newTr = Utils.Clone(tagRendering) + generateTagRenderings.push(newTr) + trPerId.set(newTr.id, { tr: newTr, conditions: [] }) + } + const conditions = trPerId.get(id).conditions + if (tagRendering["condition"]) { + conditions.push({ + and: [tagRendering["condition"], layerConfig.source["osmTags"]], + }) + } else { + conditions.push(layerConfig.source["osmTags"]) + } + } + } + + for (const { tr, conditions } of Array.from(trPerId.values())) { + const optimized = TagUtils.optimzeJson({ or: conditions }) + if (optimized === true) { + continue + } + if (optimized === false) { + throw "Optimized into 'false', this is weird..." + } + tr.condition = optimized + } + + const allTags: QuestionableTagRenderingConfigJson = { + id: "all-tags", + render: { "*": "{all_tags()}" }, + + metacondition: { + or: [ + "__featureSwitchIsDebugging=true", + "mapcomplete-show_tags=full", + "mapcomplete-show_debug=yes", + ], + }, + } + proto.tagRenderings = [ + ...generateTagRenderings, + ...proto.tagRenderings, + "questions", + allTags, + ] + } + + private addTitle(proto: LayerConfigJson) { + const mappings: MappingConfigJson[] = [] + for (const layer of this.layers) { + const t = layer.title + const tags: TagConfigJson = layer.source["osmTags"] + if (!t) { + continue + } + if (typeof t === "string") { + mappings.push({ if: tags, then: t }) + } else if (t["render"] !== undefined || t["mappings"] !== undefined) { + const tr = t + for (let i = 0; i < (tr.mappings ?? []).length; i++) { + const mapping = tr.mappings[i] + const optimized = TagUtils.optimzeJson({ + and: [mapping.if, tags], + }) + if (optimized === false) { + console.warn( + "The following tags yielded 'false':", + JSON.stringify(mapping.if), + JSON.stringify(tags) + ) + continue + } + if (optimized === true) { + console.error( + "The following tags yielded 'false':", + JSON.stringify(mapping.if), + JSON.stringify(tags) + ) + throw "Tags for title optimized to true" + } + + if (!mapping.then) { + throw ( + "The title has a missing 'then' for mapping " + + i + + " in layer " + + layer.id + ) + } + mappings.push({ + if: optimized, + then: mapping.then, + }) + } + if (tr.render) { + mappings.push({ + if: tags, + then: tr.render, + }) + } + } else { + mappings.push({ if: tags, then: >t }) + } + } + + if (proto.title["mappings"]) { + mappings.unshift(...proto.title["mappings"]) + } + if (proto.title["render"]) { + mappings.push({ + if: "id~*", + then: proto.title["render"], + }) + } + + proto.title = { + mappings, + } } async main(args: string[]): Promise { - const allConfigs = AllSharedLayers.getSharedLayersConfigs() + console.log("Generating the favourite layer: stealing _all_ tagRenderings") const proto = this.readLayer("favourite/favourite.proto.json") - const questions = allConfigs.get("questions") - proto.tagRenderings.push(...questions.tagRenderings) - + this.addTagRenderings(proto) + this.addTitle(proto) writeFileSync("./assets/layers/favourite/favourite.json", JSON.stringify(proto, null, " ")) } private readLayer(path: string): LayerConfigJson { - return JSON.parse(readFileSync("./assets/layers/" + path, "utf8")) + try { + return JSON.parse(readFileSync("./assets/layers/" + path, "utf8")) + } catch (e) { + console.error("Could not read ./assets/layers/" + path) + throw e + } } } -new PrepareFavouritesLayerJson().run() +new GenerateFavouritesLayer().run() diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 32f06ee64a..dcfbb71ad7 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -28,6 +28,7 @@ import { QuestionableTagRenderingConfigJson } from "../src/Models/ThemeConfig/Js import LayerConfig from "../src/Models/ThemeConfig/LayerConfig" import PointRenderingConfig from "../src/Models/ThemeConfig/PointRenderingConfig" import { ConversionContext } from "../src/Models/ThemeConfig/Conversion/ConversionContext" +import { GenerateFavouritesLayer } from "./generateFavouritesLayer" // This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files. // It spits out an overview of those to be used to load them @@ -381,16 +382,11 @@ class LayerOverviewUtils extends Script { forceReload ) - writeFileSync( - "./src/assets/generated/known_themes.json", - JSON.stringify({ - themes: Array.from(sharedThemes.values()), - }) - ) - writeFileSync( "./src/assets/generated/known_layers.json", - JSON.stringify({ layers: Array.from(sharedLayers.values()) }) + JSON.stringify({ + layers: Array.from(sharedLayers.values()).filter((l) => l.id !== "favourite"), + }) ) const mcChangesPath = "./assets/themes/mapcomplete-changes/mapcomplete-changes.json" @@ -428,6 +424,19 @@ class LayerOverviewUtils extends Script { ConversionContext.construct([], []) ) + for (const [_, theme] of sharedThemes) { + theme.layers = theme.layers.filter( + (l) => Constants.added_by_default.indexOf(l["id"]) < 0 + ) + } + + writeFileSync( + "./src/assets/generated/known_themes.json", + JSON.stringify({ + themes: Array.from(sharedThemes.values()), + }) + ) + const end = new Date() const millisNeeded = end.getTime() - start.getTime() if (AllSharedLayers.getSharedLayersConfigs().size == 0) { @@ -791,4 +800,5 @@ class LayerOverviewUtils extends Script { } } +new GenerateFavouritesLayer().run() new LayerOverviewUtils().run() diff --git a/src/Customizations/AllKnownLayouts.ts b/src/Customizations/AllKnownLayouts.ts index 12c27223c5..cbd28cf5b3 100644 --- a/src/Customizations/AllKnownLayouts.ts +++ b/src/Customizations/AllKnownLayouts.ts @@ -1,45 +1,54 @@ import known_themes from "../assets/generated/known_themes.json" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" +import favourite from "../assets/generated/layers/favourite.json" import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson" +import { AllSharedLayers } from "./AllSharedLayers" +import Constants from "../Models/Constants" /** * Somewhat of a dictionary, which lazily parses needed themes */ export class AllKnownLayoutsLazy { - private readonly dict: Map LayoutConfig }> = - new Map() - constructor() { + private readonly raw: Map = new Map() + private readonly dict: Map = new Map() + + constructor(includeFavouriteLayer = true) { for (const layoutConfigJson of known_themes["themes"]) { - this.dict.set(layoutConfigJson.id, { - func: () => { - const layout = new LayoutConfig(layoutConfigJson, true) - for (let i = 0; i < layout.layers.length; i++) { - let layer = layout.layers[i] - if (typeof layer === "string") { - throw "Layer " + layer + " was not expanded in " + layout.id - } + for (const layerId of Constants.added_by_default) { + if (layerId === "favourite") { + if (includeFavouriteLayer) { + layoutConfigJson.layers.push(favourite) } - return layout - }, - }) + continue + } + const defaultLayer = AllSharedLayers.getSharedLayersConfigs().get(layerId) + if (defaultLayer === undefined) { + console.error("Could not find builtin layer", layerId) + continue + } + layoutConfigJson.layers.push(defaultLayer) + } + this.raw.set(layoutConfigJson.id, layoutConfigJson) } } + public getConfig(key: string): LayoutConfigJson { + return this.raw.get(key) + } + public get(key: string): LayoutConfig { - const thunk = this.dict.get(key) - if (thunk === undefined) { - return undefined + const cached = this.dict.get(key) + if (cached !== undefined) { + return cached } - if (thunk["data"]) { - return thunk["data"] - } - const layout = thunk["func"]() - this.dict.set(key, { data: layout }) + + const layout = new LayoutConfig(this.getConfig(key)) + this.dict.set(key, layout) return layout } public keys() { - return this.dict.keys() + return this.raw.keys() } public values() { diff --git a/src/Logic/Actors/GeoLocationHandler.ts b/src/Logic/Actors/GeoLocationHandler.ts index 9333a84134..691be8dbbd 100644 --- a/src/Logic/Actors/GeoLocationHandler.ts +++ b/src/Logic/Actors/GeoLocationHandler.ts @@ -173,7 +173,6 @@ export default class GeoLocationHandler { properties[k] = location[k] } } - console.debug("Current location object:", location) properties["_all"] = JSON.stringify(location) const feature = { diff --git a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts index bd6d52b4d5..01703987f6 100644 --- a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts @@ -16,6 +16,11 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { private readonly _osmConnection: OsmConnection private readonly _detectedIds: Store + /** + * All favourites, including the ones which are filtered away because they are already displayed + */ + public readonly allFavourites: Store + constructor( connection: OsmConnection, indexedSource: FeaturePropertiesStore, @@ -53,6 +58,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { ) super(featuresWithoutAlreadyPresent) + this.allFavourites = features this._osmConnection = connection this._detectedIds = Stores.ListStabilized( @@ -76,6 +82,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { const geometry = <[number, number]>JSON.parse(prefs[key]) const properties = FavouritesFeatureSource.getPropertiesFor(prefs, id) properties._orig_layer = prefs[FavouritesFeatureSource.prefix + id + "-layer"] + properties._orig_theme = prefs[FavouritesFeatureSource.prefix + id + "-theme"] properties.id = osmId properties._favourite = "yes" diff --git a/src/Models/ThemeConfig/Conversion/Conversion.ts b/src/Models/ThemeConfig/Conversion/Conversion.ts index b6422bd55b..d740287316 100644 --- a/src/Models/ThemeConfig/Conversion/Conversion.ts +++ b/src/Models/ThemeConfig/Conversion/Conversion.ts @@ -2,6 +2,7 @@ import { LayerConfigJson } from "../Json/LayerConfigJson" import { Utils } from "../../../Utils" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import { ConversionContext } from "./ConversionContext" +import { T } from "vitest/dist/types-aac763a5" export interface DesugaringContext { tagRenderings: Map @@ -81,18 +82,36 @@ export class Pure extends Conversion { } } +export class Bypass extends DesugaringStep { + private readonly _applyIf: (t: T) => boolean + private readonly _step: DesugaringStep + constructor(applyIf: (t: T) => boolean, step: DesugaringStep) { + super("Applies the step on the object, if the object satisfies the predicate", [], "Bypass") + this._applyIf = applyIf + this._step = step + } + + convert(json: T, context: ConversionContext): T { + if (!this._applyIf(json)) { + return json + } + return this._step.convert(json, context) + } +} + export class Each extends Conversion { private readonly _step: Conversion private readonly _msg: string + private readonly _filter: (x: X) => boolean - constructor(step: Conversion, msg?: string) { + constructor(step: Conversion, options?: { msg?: string }) { super( "Applies the given step on every element of the list", [], "OnEach(" + step.name + ")" ) this._step = step - this._msg = msg + this._msg = options?.msg } convert(values: X[], context: ConversionContext): Y[] { diff --git a/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts b/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts index c88eb65417..aa587e65e2 100644 --- a/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts +++ b/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts @@ -85,7 +85,7 @@ export default class CreateNoteImportLayer extends Conversion { } export class AddEditingElements extends DesugaringStep { + static addedElements: string[] = [ + "minimap", + "just_created", + "split_button", + "move_button", + "delete_button", + "last_edit", + "favourite_state", + "all_tags", + ] private readonly _desugaring: DesugaringContext constructor(desugaring: DesugaringContext) { @@ -1210,7 +1220,7 @@ class AddFavouriteBadges extends DesugaringStep { } convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson { - if (json.id === "favourite") { + if (json.source === "special" || json.source === "special:library") { return json } const pr = json.pointRendering?.[0] diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index 62a495bf72..8db411c2fd 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -1,4 +1,4 @@ -import { Conversion, DesugaringStep, Each, Fuse, On, Pipe, Pure } from "./Conversion" +import { Bypass, Conversion, DesugaringStep, Each, Fuse, On } from "./Conversion" import { LayerConfigJson } from "../Json/LayerConfigJson" import LayerConfig from "../LayerConfig" import { Utils } from "../../../Utils" @@ -11,7 +11,6 @@ import { TagUtils } from "../../../Logic/Tags/TagUtils" import { ExtractImages } from "./FixImages" import { And } from "../../../Logic/Tags/And" import Translations from "../../../UI/i18n/Translations" -import Svg from "../../../Svg" import FilterConfigJson from "../Json/FilterConfigJson" import DeleteConfig from "../DeleteConfig" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" @@ -276,9 +275,9 @@ export class ValidateThemeAndLayers extends Fuse { new On( "layers", new Each( - new Pipe( - new ValidateLayer(undefined, isBuiltin, doesImageExist, false, true), - new Pure((x) => x?.raw) + new Bypass( + (layer) => Constants.added_by_default.indexOf(layer.id) < 0, + new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true) ) ) ) @@ -968,7 +967,7 @@ export class ValidateTagRenderings extends Fuse { "Various validation on tagRenderingConfigs", new DetectShadowedMappings(layerConfig), new DetectConflictingAddExtraTags(), - // new DetectNonErasedKeysInMappings(), + // TODO enable new DetectNonErasedKeysInMappings(), new DetectMappingsWithImages(doesImageExist), new On("render", new ValidatePossibleLinks()), new On("question", new ValidatePossibleLinks()), @@ -1350,6 +1349,29 @@ export class PrevalidateLayer extends DesugaringStep { } } +export class ValidateLayerConfig extends DesugaringStep { + private readonly validator: ValidateLayer + constructor( + path: string, + isBuiltin: boolean, + doesImageExist: DoesImageExist, + studioValidations: boolean = false, + skipDefaultLayers: boolean = false + ) { + super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig") + this.validator = new ValidateLayer( + path, + isBuiltin, + doesImageExist, + studioValidations, + skipDefaultLayers + ) + } + + convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson { + return this.validator.convert(json, context).raw + } +} export class ValidateLayer extends Conversion< LayerConfigJson, { parsed: LayerConfig; raw: LayerConfigJson } diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index a94ad07b4c..fbf17de776 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -462,6 +462,7 @@ export default class ThemeViewState implements SpecialVisualizationState { * @private */ private selectClosestAtCenter(i: number = 0) { + this.mapProperties.lastKeyNavigation.setData(Date.now() / 1000) const toSelect = this.closestFeatures.features.data[i] if (!toSelect) { return @@ -567,46 +568,6 @@ export default class ThemeViewState implements SpecialVisualizationState { }) } - private addLastClick(last_click: LastClickFeatureSource) { - // The last_click gets a _very_ special treatment as it interacts with various parts - - this.featureProperties.trackFeatureSource(last_click) - this.indexedFeatures.addSource(last_click) - - last_click.features.addCallbackAndRunD((features) => { - if (this.selectedLayer.data?.id === "last_click") { - // The last-click location moved, but we have selected the last click of the previous location - // So, we update _after_ clearing the selection to make sure no stray data is sticking around - this.selectedElement.setData(undefined) - this.selectedElement.setData(features[0]) - } - }) - - new ShowDataLayer(this.map, { - features: new FilteringFeatureSource(this.newPointDialog, last_click), - doShowLayer: this.featureSwitches.featureSwitchEnableLogin, - layer: this.newPointDialog.layerDef, - selectedElement: this.selectedElement, - selectedLayer: this.selectedLayer, - metaTags: this.userRelatedState.preferencesAsTags, - onClick: (feature: Feature) => { - if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) { - this.map.data.flyTo({ - zoom: Constants.minZoomLevelToAddNewPoint, - center: this.mapProperties.lastClickLocation.data, - }) - return - } - // We first clear the selection to make sure no weird state is around - this.selectedLayer.setData(undefined) - this.selectedElement.setData(undefined) - - this.selectedElement.setData(feature) - this.selectedLayer.setData(this.newPointDialog.layerDef) - }, - }) - } - /** * Add the special layers to the map */ @@ -663,9 +624,7 @@ export default class ThemeViewState implements SpecialVisualizationState { } const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range") - const rangeIsDisplayed = rangeFLayer?.isDisplayed - if ( !QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef)) ) { diff --git a/src/UI/BigComponents/UploadTraceToOsmUI.ts b/src/UI/BigComponents/UploadTraceToOsmUI.ts index 5344da228d..2b281ee068 100644 --- a/src/UI/BigComponents/UploadTraceToOsmUI.ts +++ b/src/UI/BigComponents/UploadTraceToOsmUI.ts @@ -121,9 +121,9 @@ export default class UploadTraceToOsmUI extends LoginToggle { ]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"), new Toggle( confirmPanel, - new SubtleButton(new SvelteUIElement(Upload), t.title).onClick(() => - clicked.setData(true) - ), + new SubtleButton(new SvelteUIElement(Upload), t.title) + .onClick(() => clicked.setData(true)) + .SetClass("w-full"), clicked ), uploadFinished diff --git a/src/UI/Favourites/FavouriteSummary.svelte b/src/UI/Favourites/FavouriteSummary.svelte index e69de29bb2..92f34be74e 100644 --- a/src/UI/Favourites/FavouriteSummary.svelte +++ b/src/UI/Favourites/FavouriteSummary.svelte @@ -0,0 +1,10 @@ + + +
+ {JSON.stringify(properties)} + {properties?.id ?? "undefined"} + OSM +
diff --git a/src/UI/Favourites/Favourites.svelte b/src/UI/Favourites/Favourites.svelte index ff6e0331f1..cb0382e46f 100644 --- a/src/UI/Favourites/Favourites.svelte +++ b/src/UI/Favourites/Favourites.svelte @@ -1,8 +1,19 @@ + +
+ You marked {$favourites.length} locations as a favourite location. + + This list is only visible to you +{#each $favourites as f} + +{/each} +
diff --git a/src/UI/Popup/ExportAsGpxViz.ts b/src/UI/Popup/ExportAsGpxViz.ts index c7821da100..a5acf85823 100644 --- a/src/UI/Popup/ExportAsGpxViz.ts +++ b/src/UI/Popup/ExportAsGpxViz.ts @@ -31,14 +31,16 @@ export class ExportAsGpxViz implements SpecialVisualization { t.downloadFeatureAsGpx.SetClass("font-bold text-lg"), t.downloadGpxHelper.SetClass("subtle"), ]).SetClass("flex flex-col") - ).onClick(() => { - console.log("Exporting as GPX!") - const tags = tagSource.data - const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track" - const gpx = GeoOperations.toGpx(>feature, title) - Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", { - mimetype: "{gpx=application/gpx+xml}", + ) + .SetClass("w-full") + .onClick(() => { + console.log("Exporting as GPX!") + const tags = tagSource.data + const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track" + const gpx = GeoOperations.toGpx(>feature, title) + Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", { + mimetype: "{gpx=application/gpx+xml}", + }) }) - }) } } diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index d2da94d4ba..139a9e60e4 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -534,6 +534,9 @@ export default class SpecialVisualizations { feature: Feature, layer: LayerConfig ): BaseUIElement { + if (!layer.deletion) { + return undefined + } return new SvelteUIElement(DeleteWizard, { tags: tagSource, deleteConfig: layer.deletion, @@ -873,20 +876,22 @@ export default class SpecialVisualizations { t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"), t.downloadGeoJsonHelper.SetClass("subtle"), ]).SetClass("flex flex-col") - ).onClick(() => { - console.log("Exporting as Geojson") - const tags = tagSource.data - const title = - layer?.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson" - const data = JSON.stringify(feature, null, " ") - Utils.offerContentsAsDownloadableFile( - data, - title + "_mapcomplete_export.geojson", - { - mimetype: "application/vnd.geo+json", - } - ) - }) + ) + .onClick(() => { + console.log("Exporting as Geojson") + const tags = tagSource.data + const title = + layer?.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson" + const data = JSON.stringify(feature, null, " ") + Utils.offerContentsAsDownloadableFile( + data, + title + "_mapcomplete_export.geojson", + { + mimetype: "application/vnd.geo+json", + } + ) + }) + .SetClass("w-full") }, }, { diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index 0db04b1f87..221dfeef31 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -15,7 +15,7 @@ import type { MapProperties } from "../Models/MapProperties"; import Geosearch from "./BigComponents/Geosearch.svelte"; import Translations from "./i18n/Translations"; - import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"; + import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"; import Tr from "./Base/Tr.svelte"; import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"; import FloatOver from "./Base/FloatOver.svelte"; @@ -64,6 +64,8 @@ import Community from "../assets/svg/Community.svelte"; import Download from "../assets/svg/Download.svelte"; import Share from "../assets/svg/Share.svelte"; + import FavouriteSummary from "./Favourites/FavouriteSummary.svelte"; + import Favourites from "./Favourites/Favourites.svelte"; export let state: ThemeViewState; let layout = state.layout; @@ -493,22 +495,31 @@
+ + Your favourites +
+ +
+

Your favourite locations

+ +
+
-
+
-
+
-
+
new PrivacyPolicy()} />
- -
+ +
diff --git a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts index 009c9f08a4..ec4118934c 100644 --- a/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts +++ b/test/Models/ThemeConfig/Conversion/PrepareTheme.spec.ts @@ -179,7 +179,21 @@ describe("PrepareTheme", () => { id: "layer-example", name: null, minzoom: 18, - pointRendering: [{ location: ["point"], label: "xyz" }], + pointRendering: [ + { + location: ["point"], + label: "xyz", + iconBadges: [ + { + if: "_favourite=yes", + then: { + id: "circlewhiteheartred", + render: "circle:white;heart:red", + }, + }, + ], + }, + ], lineRendering: [{ width: 1 }], titleIcons: [], }) From 9287eed06365629cc34f792f896b85a85642f746 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 2 Dec 2023 03:17:40 +0100 Subject: [PATCH 09/22] CI: increase build memory size --- scripts/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build.sh b/scripts/build.sh index d352e3166c..697ddff4a4 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -10,7 +10,7 @@ mkdir dist 2> /dev/null mkdir dist/assets 2> /dev/null -export NODE_OPTIONS="--max-old-space-size=8192" +export NODE_OPTIONS="--max-old-space-size=16384" # This script ends every line with '&&' to chain everything. A failure will thus stop the build npm run generate:editor-layer-index && @@ -48,7 +48,7 @@ else exit 1 fi -export NODE_OPTIONS=--max-old-space-size=7000 +export NODE_OPTIONS=--max-old-space-size=16000 which vite vite build --sourcemap # Copy the layer files, as these might contain assets (e.g. svgs) From 3b9035e3b436de6cf26610be30f0c2d710c34d6f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 2 Dec 2023 03:18:23 +0100 Subject: [PATCH 10/22] Refactoring: remove some more of the obsolete images --- scripts/generateIncludedImages.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/generateIncludedImages.ts b/scripts/generateIncludedImages.ts index dd7160a40e..6e0d643481 100644 --- a/scripts/generateIncludedImages.ts +++ b/scripts/generateIncludedImages.ts @@ -27,7 +27,8 @@ function genImages(dryrun = false) { "star_outline", "star", "osm_logo_us", - + "triangle", + "teardrop_with_hole_green", "SocialImageForeground", "wikipedia", "Upload", From 37cdf0f01ac9a3dfea919a786712902cfe75ec90 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 2 Dec 2023 03:18:53 +0100 Subject: [PATCH 11/22] Add title icons to favourites layer --- assets/layers/favourite/favourite.proto.json | 1 + assets/layers/icons/icons.json | 12 +++--- scripts/generateFavouritesLayer.ts | 44 +++++++++++++++++--- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/assets/layers/favourite/favourite.proto.json b/assets/layers/favourite/favourite.proto.json index 57ff79f1f7..7ad0e3d256 100644 --- a/assets/layers/favourite/favourite.proto.json +++ b/assets/layers/favourite/favourite.proto.json @@ -1,5 +1,6 @@ { "#":"no-translations", + "#dont-translate": "*", "pointRendering": [ { "location": [ diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 72a387f9e1..16edab7591 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -14,7 +14,7 @@ { "id": "wikipedialink", "labels": [ - "defaults" + "defaults", "in_favourite" ], "render": "Wikipedia", "condition": { @@ -69,7 +69,7 @@ { "id": "phonelink", "labels": [ - "defaults" + "defaults", "in_favourite" ], "render": "phone", "mappings": [ @@ -89,7 +89,7 @@ { "id": "emaillink", "labels": [ - "defaults" + "defaults", "in_favourite" ], "render": "email", "mappings": [ @@ -109,7 +109,7 @@ { "id": "websitelink", "labels": [ - "defaults" + "defaults", "in_favourite" ], "render": "website", "condition": "website~*" @@ -117,7 +117,7 @@ { "id": "smokingicon", "labels": [ - "defaults" + "defaults", "in_favourite" ], "mappings": [ { @@ -171,7 +171,7 @@ { "id": "dogicon", "labels": [ - "defaults" + "defaults", "in_favourite" ], "mappings": [ { diff --git a/scripts/generateFavouritesLayer.ts b/scripts/generateFavouritesLayer.ts index 2cc0abf9d6..a60e9f22c9 100644 --- a/scripts/generateFavouritesLayer.ts +++ b/scripts/generateFavouritesLayer.ts @@ -13,7 +13,7 @@ import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson" import { TagUtils } from "../src/Logic/Tags/TagUtils" import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" - +import icons from "../src/assets/generated/layers/icons.json" export class GenerateFavouritesLayer extends Script { private readonly layers: LayerConfigJson[] = [] @@ -51,10 +51,11 @@ export class GenerateFavouritesLayer extends Script { "move-button", "delete-button", "all-tags", + "all_tags", ...AddEditingElements.addedElements, ]) - const generateTagRenderings: (string | QuestionableTagRenderingConfigJson)[] = [] + const generatedTagRenderings: (string | QuestionableTagRenderingConfigJson)[] = [] const trPerId = new Map< string, { conditions: TagConfigJson[]; tr: QuestionableTagRenderingConfigJson } @@ -68,7 +69,7 @@ export class GenerateFavouritesLayer extends Script { if (blacklistedIds.has(tagRendering)) { continue } - generateTagRenderings.push(tagRendering) + generatedTagRenderings.push(tagRendering) blacklistedIds.add(tagRendering) continue } @@ -97,14 +98,14 @@ export class GenerateFavouritesLayer extends Script { newTr.condition = { and: Utils.NoNull([(newTr.condition, layerConfig.source["osmTags"])]), } - generateTagRenderings.push(newTr) + generatedTagRenderings.push(newTr) blacklistedIds.add(newTr.id) continue } } if (!trPerId.has(id)) { const newTr = Utils.Clone(tagRendering) - generateTagRenderings.push(newTr) + generatedTagRenderings.push(newTr) trPerId.set(newTr.id, { tr: newTr, conditions: [] }) } const conditions = trPerId.get(id).conditions @@ -142,13 +143,43 @@ export class GenerateFavouritesLayer extends Script { }, } proto.tagRenderings = [ - ...generateTagRenderings, + "images", + ...generatedTagRenderings, ...proto.tagRenderings, "questions", allTags, ] } + private addTitleIcons(proto: LayerConfigJson) { + proto.titleIcons = [] + const seenTitleIcons = new Set() + for (const layer of this.layers) { + for (const titleIcon of layer.titleIcons) { + if (typeof titleIcon === "string") { + continue + } + if (titleIcon["labels"]?.indexOf("defaults") >= 0) { + continue + } + if (titleIcon.id === "rating") { + if (!seenTitleIcons.has("rating")) { + proto.titleIcons.unshift("icons.rating") + seenTitleIcons.add("rating") + } + continue + } + if (seenTitleIcons.has(titleIcon.id)) { + continue + } + seenTitleIcons.add(titleIcon.id) + console.log("Adding ", titleIcon.id) + proto.titleIcons.push(titleIcon) + } + } + proto.titleIcons.push("icons.defaults") + } + private addTitle(proto: LayerConfigJson) { const mappings: MappingConfigJson[] = [] for (const layer of this.layers) { @@ -227,6 +258,7 @@ export class GenerateFavouritesLayer extends Script { const proto = this.readLayer("favourite/favourite.proto.json") this.addTagRenderings(proto) this.addTitle(proto) + this.addTitleIcons(proto) writeFileSync("./assets/layers/favourite/favourite.json", JSON.stringify(proto, null, " ")) } From 59090fdb39a864b7d8b64a26fe35e98eacfa2121 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 2 Dec 2023 03:19:50 +0100 Subject: [PATCH 12/22] Themes: improve favourite panel --- assets/svg/center.svg | 62 +++++++++++++++++++ assets/svg/center.svg.license | 2 + assets/svg/license_info.json | 26 +++----- assets/themes/climbing/climbing.json | 3 + package.json | 2 +- src/Customizations/AllKnownLayouts.ts | 2 +- src/Models/MenuState.ts | 1 + .../ThemeConfig/Conversion/Validation.ts | 15 +++-- .../ThemeConfig/Json/LayerConfigJson.ts | 2 +- src/UI/Favourites/FavouriteSummary.svelte | 59 ++++++++++++++++-- src/UI/Favourites/Favourites.svelte | 8 +-- src/UI/ThemeViewGUI.svelte | 3 +- src/assets/editor-layer-index.json | 38 +++++++----- src/assets/svg/Center.svelte | 4 ++ 14 files changed, 176 insertions(+), 51 deletions(-) create mode 100644 assets/svg/center.svg create mode 100644 assets/svg/center.svg.license create mode 100644 src/assets/svg/Center.svelte diff --git a/assets/svg/center.svg b/assets/svg/center.svg new file mode 100644 index 0000000000..d2c0ce8d41 --- /dev/null +++ b/assets/svg/center.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + diff --git a/assets/svg/center.svg.license b/assets/svg/center.svg.license new file mode 100644 index 0000000000..2452bee1e8 --- /dev/null +++ b/assets/svg/center.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Pieter Vander Vennet +SPDX-License-Identifier: CC0 \ No newline at end of file diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 8a9e84cc7c..0ba4ac2b90 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -153,6 +153,14 @@ "https://commons.wikimedia.org/wiki/File:Camera_font_awesome.svg" ] }, + { + "path": "center.svg", + "license": "CC0-1.0", + "authors": [ + "Pieter Vander Vennet" + ], + "sources": [] + }, { "path": "checkmark.svg", "license": "CC0-1.0", @@ -859,24 +867,6 @@ ], "sources": [] }, - { - "path": "no_checkmark.svg", - "license": "TRIVIAL", - "authors": [], - "sources": [] - }, - { - "path": "no_checkmark.svg", - "license": "TRIVIAL", - "authors": [], - "sources": [] - }, - { - "path": "none.svg", - "license": "TRIVIAL", - "authors": [], - "sources": [] - }, { "path": "not_found.svg", "license": "CC-BY-4.0", diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index 426c72999d..e9a67f8226 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -69,10 +69,12 @@ }, "+titleIcons": [ { + "id": "climbing_length", "render": "
{climbing:length}m
", "condition": "climbing:length~*" }, { + "id":"climbing_bolts", "mappings": [ { "if": "__bolts_max~*", @@ -95,6 +97,7 @@ "render": "
{__difficulty_max}
" }, { + "id": "difficulty", "render": "
{climbing:grade:french}
", "condition": "__difficulty:char~*" } diff --git a/package.json b/package.json index 816714f4f4..f79fe26b0b 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js", "optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'", "generate:stats": "vite-node scripts/GenerateSeries.ts", - "reset:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && npm run generate:layeroverview && vite-node scripts/generateLayerOverview.ts -- --force", + "reset:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && echo '{}' > ./src/assets/generated/layers/favourite.json && npm run generate:layeroverview && vite-node scripts/generateLayerOverview.ts -- --force", "generate": "mkdir -p ./assets/generated; npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:translations; npm run reset:layeroverview; npm run generate:service-worker", "generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -", "prepare-deploy": "npm run generate:service-worker && ./scripts/build.sh", diff --git a/src/Customizations/AllKnownLayouts.ts b/src/Customizations/AllKnownLayouts.ts index cbd28cf5b3..6d3ada24bb 100644 --- a/src/Customizations/AllKnownLayouts.ts +++ b/src/Customizations/AllKnownLayouts.ts @@ -15,7 +15,7 @@ export class AllKnownLayoutsLazy { constructor(includeFavouriteLayer = true) { for (const layoutConfigJson of known_themes["themes"]) { for (const layerId of Constants.added_by_default) { - if (layerId === "favourite") { + if (layerId === "favourite" && favourite.id) { if (includeFavouriteLayer) { layoutConfigJson.layers.push(favourite) } diff --git a/src/Models/MenuState.ts b/src/Models/MenuState.ts index 63dda397cf..1238580237 100644 --- a/src/Models/MenuState.ts +++ b/src/Models/MenuState.ts @@ -24,6 +24,7 @@ export class MenuState { public static readonly _menuviewTabs = [ "about", "settings", + "favourites", "community", "privacy", "advanced", diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index 8db411c2fd..23b52a804f 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -22,7 +22,7 @@ import { TagsFilter } from "../../../Logic/Tags/TagsFilter" import { Translatable } from "../Json/Translatable" import { ConversionContext } from "./ConversionContext" -class ValidateLanguageCompleteness extends DesugaringStep { +class ValidateLanguageCompleteness extends DesugaringStep { private readonly _languages: string[] constructor(...languages: string[]) { @@ -34,7 +34,9 @@ class ValidateLanguageCompleteness extends DesugaringStep { this._languages = languages ?? ["en"] } - convert(obj: any, context: ConversionContext): LayerConfig { + convert(obj: LayoutConfig, context: ConversionContext): LayoutConfig { + const origLayers = obj.layers + obj.layers = [...obj.layers].filter((l) => l["id"] !== "favourite") const translations = Translation.ExtractAllTranslationsFrom(obj) for (const neededLanguage of this._languages) { translations @@ -56,7 +58,7 @@ class ValidateLanguageCompleteness extends DesugaringStep { ) }) } - + obj.layers = origLayers return obj } } @@ -1369,7 +1371,12 @@ export class ValidateLayerConfig extends DesugaringStep { } convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson { - return this.validator.convert(json, context).raw + const prepared = this.validator.convert(json, context) + if (!prepared) { + context.err("Preparing layer failed") + return undefined + } + return prepared?.raw } } export class ValidateLayer extends Conversion< diff --git a/src/Models/ThemeConfig/Json/LayerConfigJson.ts b/src/Models/ThemeConfig/Json/LayerConfigJson.ts index 0cd857bde4..35113d6d3d 100644 --- a/src/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/src/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -243,7 +243,7 @@ export interface LayerConfigJson { * Type: icon[] * group: infobox */ - titleIcons?: (string | TagRenderingConfigJson)[] | ["defaults"] + titleIcons?: (string | (TagRenderingConfigJson & { id?: string }))[] | ["defaults"] /** * Creates points to render on the map. diff --git a/src/UI/Favourites/FavouriteSummary.svelte b/src/UI/Favourites/FavouriteSummary.svelte index 92f34be74e..fc23cbbb9e 100644 --- a/src/UI/Favourites/FavouriteSummary.svelte +++ b/src/UI/Favourites/FavouriteSummary.svelte @@ -1,10 +1,59 @@ -
- {JSON.stringify(properties)} - {properties?.id ?? "undefined"} - OSM +
+

select()} class="cursor-pointer ml-1 m-0"> + +

+ +
+ {#each favConfig.titleIcons as titleIconConfig} + {#if (titleIconBlacklist.indexOf(titleIconConfig.id) < 0) && (titleIconConfig.condition?.matchesProperties(properties) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ...properties, ...state.userRelatedState.preferencesAsTags.data } ) ?? true) && titleIconConfig.IsKnown(properties)} +
+ +
+ {/if} + {/each} + +
diff --git a/src/UI/Favourites/Favourites.svelte b/src/UI/Favourites/Favourites.svelte index cb0382e46f..790dc9a7bd 100644 --- a/src/UI/Favourites/Favourites.svelte +++ b/src/UI/Favourites/Favourites.svelte @@ -11,9 +11,9 @@
You marked {$favourites.length} locations as a favourite location. - + This list is only visible to you -{#each $favourites as f} - -{/each} + {#each $favourites as feature (feature.properties.id)} + + {/each}
diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index 221dfeef31..2610c51787 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -64,7 +64,6 @@ import Community from "../assets/svg/Community.svelte"; import Download from "../assets/svg/Download.svelte"; import Share from "../assets/svg/Share.svelte"; - import FavouriteSummary from "./Favourites/FavouriteSummary.svelte"; import Favourites from "./Favourites/Favourites.svelte"; export let state: ThemeViewState; @@ -499,7 +498,7 @@ Your favourites
-
+

Your favourite locations

diff --git a/src/assets/editor-layer-index.json b/src/assets/editor-layer-index.json index a27388a8a2..49cf8e29a7 100644 --- a/src/assets/editor-layer-index.json +++ b/src/assets/editor-layer-index.json @@ -137,9 +137,10 @@ {"properties":{"name":"Stadt Zürich Luftbild 2013","id":"Zuerich-zh_luftbild2013-wms","url":"https://www.ogd.stadt-zuerich.ch/wms/geoportal/Orthofoto_2013_Stadt_Zuerich___Fruehling?FORMAT=image/jpeg&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=OP_2013_STZH.tif&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"text":"Stadt Zürich Open Government Data"},"type":"wms","category":"photo","max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[8.44624,47.44143],[8.44381,47.31555],[8.62895,47.31377],[8.63178,47.43968],[8.44624,47.44143]]],"type":"Polygon"}}, {"properties":{"name":"Stadt Zürich Übersichtsplan","id":"Zuerich-zh_uebersichtsplan-tms","url":"https://mapproxy.osm.ch/tiles/zh_uebersichtsplan/EPSG900913/{zoom}/{x}/{y}.png?origin=nw","attribution":{"required":false,"text":"Stadt Zürich Open Government Data"},"type":"tms","category":"map","min_zoom":3,"max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[8.45788,47.44582],[8.45745,47.43231],[8.42864,47.43259],[8.42713,47.35161],[8.45609,47.35135],[8.45582,47.33787],[8.48478,47.33762],[8.48418,47.31062],[8.54212,47.31013],[8.54236,47.3236],[8.57152,47.32292],[8.57162,47.33679],[8.6295,47.33628],[8.6318,47.41716],[8.60231,47.41746],[8.60266,47.43096],[8.57362,47.43124],[8.57392,47.44477],[8.45788,47.44582]]],"type":"Polygon"}}, {"properties":{"name":"swisstopo SWISSIMAGE","id":"swisstopo_swissimage","url":"https://wms.geo.admin.ch?LAYERS=ch.swisstopo.swissimage&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":"Federal Office of Topography swisstopo"},"type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[5.95012,46.13048],[5.9669,46.12535],[5.9931,46.13924],[6.0358,46.12984],[6.05582,46.14677],[6.13828,46.13728],[6.18998,46.16083],[6.1935,46.17716],[6.22839,46.19771],[6.29968,46.22229],[6.31605,46.24192],[6.31527,46.2586],[6.29373,46.26902],[6.26889,46.25319],[6.24481,46.27746],[6.25922,46.28922],[6.25462,46.30418],[6.22817,46.31395],[6.25816,46.3575],[6.33795,46.3995],[6.42909,46.412],[6.52094,46.45185],[6.68091,46.44985],[6.81276,46.42411],[6.79581,46.39041],[6.79943,46.38037],[6.76588,46.36335],[6.76564,46.34971],[6.79576,46.31688],[6.85814,46.28152],[6.84857,46.25632],[6.81635,46.23529],[6.79728,46.20575],[6.80522,46.18309],[6.78553,46.16425],[6.7902,46.13778],[6.81289,46.12504],[6.89056,46.12031],[6.87601,46.09643],[6.88274,46.07615],[6.8661,46.04894],[6.89265,46.0383],[6.92715,46.06028],[6.94619,46.0462],[6.98,46.00175],[7.004,45.995],[7.01462,45.97958],[7.00272,45.96702],[7.03035,45.95217],[7.03781,45.92139],[7.057,45.90878],[7.09711,45.85577],[7.12068,45.85521],[7.15564,45.87436],[7.17257,45.85884],[7.19969,45.85636],[7.22215,45.88506],[7.25987,45.88419],[7.29504,45.91546],[7.32302,45.90576],[7.34317,45.90961],[7.38777,45.89276],[7.44795,45.92684],[7.47824,45.93207],[7.48155,45.94843],[7.50149,45.95533],[7.5461,45.95313],[7.55248,45.98191],[7.57329,45.9828],[7.58756,45.96609],[7.66032,45.97164],[7.67694,45.95351],[7.7024,45.94583],[7.70173,45.93254],[7.71862,45.91948],[7.7385,45.92015],[7.75288,45.93512],[7.79697,45.91299],[7.82169,45.92213],[7.86529,45.91233],[7.88297,45.92541],[7.87544,45.93813],[7.88622,45.9708],[7.91245,45.992],[7.98981,45.99146],[8.01827,46.01008],[8.02098,46.03071],[8.04181,46.04344],[8.0292,46.07063],[8.0403,46.09568],[8.11235,46.10816],[8.12232,46.12787],[8.14768,46.13295],[8.16145,46.14614],[8.15623,46.16106],[8.17145,46.18402],[8.14447,46.22843],[8.11883,46.24138],[8.11416,46.25304],[8.09174,46.25773],[8.09284,46.26378],[8.14051,46.29746],[8.16228,46.29143],[8.20207,46.29839],[8.21784,46.3073],[8.23129,46.33186],[8.2675,46.3428],[8.26955,46.3608],[8.28628,46.35935],[8.31773,46.37406],[8.32207,46.39998],[8.30096,46.40914],[8.30912,46.41964],[8.32852,46.42175],[8.36944,46.44769],[8.44458,46.45895],[8.45779,46.44204],[8.4504,46.42361],[8.46114,46.4112],[8.45434,46.38678],[8.46237,46.36267],[8.45747,46.33453],[8.42133,46.299],[8.4477,46.26581],[8.43865,46.24698],[8.45945,46.24039],[8.46694,46.22845],[8.52619,46.21549],[8.56886,46.16128],[8.59174,46.15164],[8.58642,46.14185],[8.60762,46.11825],[8.6442,46.1188],[8.65554,46.10845],[8.71378,46.09274],[8.74045,46.11533],[8.75754,46.09742],[8.78184,46.08975],[8.80782,46.09521],[8.84678,46.07131],[8.84143,46.05424],[8.82366,46.04952],[8.82139,46.0344],[8.7878,46.01068],[8.78015,45.98701],[8.8287,45.98284],[8.86398,45.95728],[8.89022,45.95354],[8.88703,45.93019],[8.91843,45.90303],[8.93071,45.86606],[8.90803,45.84345],[8.90716,45.8276],[8.94969,45.83853],[8.96901,45.82791],[8.98544,45.83215],[8.98986,45.81845],[9.01905,45.81362],[9.03985,45.82056],[9.05684,45.86373],[9.0954,45.90115],[9.06295,45.9249],[9.02733,45.93317],[9.01986,45.96237],[9.00048,45.96965],[9.03491,45.99365],[9.01579,46.03551],[9.02362,46.04763],[9.08143,46.06076],[9.09604,46.08749],[9.07991,46.11618],[9.12508,46.13106],[9.16565,46.16659],[9.18739,46.16665],[9.22866,46.22597],[9.25443,46.23209],[9.25882,46.26494],[9.29023,46.2945],[9.289,46.30883],[9.3058,46.32697],[9.30225,46.35734],[9.28344,46.3697],[9.28654,46.4153],[9.25466,46.43445],[9.25412,46.44392],[9.28397,46.45964],[9.28746,46.49271],[9.36281,46.50281],[9.36556,46.48715],[9.3849,46.47011],[9.41165,46.46243],[9.42981,46.47395],[9.43727,46.49285],[9.45555,46.50043],[9.45511,46.48912],[9.4426,46.48403],[9.45854,46.4695],[9.44795,46.42109],[9.46001,46.39702],[9.4551,46.3743],[9.49209,46.36074],[9.51273,46.32502],[9.54617,46.29851],[9.63354,46.28171],[9.67579,46.29509],[9.71223,46.28775],[9.73092,46.30886],[9.72666,46.3248],[9.73901,46.34544],[9.77755,46.33035],[9.83674,46.35624],[9.87095,46.35828],[9.90553,46.375],[9.9242,46.36154],[9.94926,46.37359],[9.98825,46.34725],[9.97348,46.32274],[9.99116,46.31017],[9.99007,46.283],[10.04906,46.26287],[10.05321,46.25047],[10.03846,46.22718],[10.0694,46.21272],[10.09134,46.22349],[10.1349,46.22075],[10.18301,46.25841],[10.15987,46.29508],[10.12255,46.31688],[10.11182,46.33698],[10.1155,46.34841],[10.13526,46.35941],[10.13452,46.37583],[10.16739,46.38498],[10.17317,46.40822],[10.14633,46.43219],[10.12815,46.4362],[10.08658,46.42628],[10.06614,46.43108],[10.06197,46.44305],[10.04779,46.44639],[10.0609,46.46399],[10.04958,46.48032],[10.06044,46.52203],[10.05066,46.5387],[10.07289,46.54741],[10.08899,46.57165],[10.10621,46.58034],[10.10624,46.60474],[10.12959,46.60082],[10.18803,46.62062],[10.21759,46.61272],[10.23432,46.62839],[10.24995,46.61047],[10.23562,46.59296],[10.24063,46.57253],[10.28256,46.56635],[10.29308,46.54598],[10.32532,46.54616],[10.33765,46.53824],[10.35677,46.55097],[10.3955,46.53955],[10.41582,46.54639],[10.45304,46.52619],[10.47678,46.54012],[10.49829,46.61674],[10.44754,46.64565],[10.40815,46.63985],[10.39157,46.68349],[10.42225,46.70761],[10.42481,46.72086],[10.4114,46.73293],[10.44867,46.75206],[10.4482,46.77235],[10.43329,46.78869],[10.45603,46.80154],[10.47298,46.83515],[10.47307,46.88505],[10.49222,46.91364],[10.49584,46.93845],[10.45833,46.95693],[10.43125,46.96068],[10.43124,46.97886],[10.40302,47.00028],[10.3825,47.00423],[10.34069,46.99166],[10.32182,46.95663],[10.30154,46.94875],[10.30925,46.93022],[10.2353,46.93403],[10.21924,46.89659],[10.2247,46.87087],[10.19226,46.87083],[10.17552,46.8584],[10.10839,46.84623],[10.09085,46.86504],[10.05765,46.86645],[10.05756,46.87906],[10.02063,46.90568],[9.99313,46.90702],[9.97944,46.92025],[9.93956,46.91762],[9.88667,46.93799],[9.87907,46.96301],[9.8988,46.99107],[9.89445,47.00301],[9.8786,47.01032],[9.88678,47.01625],[9.88126,47.02439],[9.85873,47.02765],[9.83663,47.01847],[9.78775,47.04255],[9.72093,47.04838],[9.6824,47.06658],[9.6213,47.05775],[9.619,47.0737],[9.64002,47.08265],[9.64074,47.10227],[9.63014,47.11186],[9.64105,47.12984],[9.62693,47.15403],[9.61055,47.15237],[9.60127,47.16616],[9.57995,47.17558],[9.59121,47.20661],[9.5623,47.22792],[9.57216,47.24545],[9.53931,47.27056],[9.59374,47.31499],[9.61022,47.34847],[9.62617,47.36062],[9.66712,47.36792],[9.6807,47.39034],[9.65853,47.40782],[9.65099,47.43453],[9.66509,47.45395],[9.62565,47.46141],[9.61111,47.47482],[9.59938,47.46717],[9.56828,47.49774],[9.56379,47.5448],[9.49895,47.55556],[9.39725,47.62448],[9.25743,47.66317],[9.17322,47.66001],[9.15322,47.67182],[9.13974,47.66892],[9.09601,47.68375],[9.02305,47.69134],[8.93811,47.66033],[8.89603,47.65264],[8.85957,47.68491],[8.88102,47.69302],[8.8787,47.70608],[8.8219,47.72286],[8.80905,47.74248],[8.76356,47.7201],[8.76412,47.70405],[8.79183,47.69954],[8.78707,47.68101],[8.73356,47.69739],[8.74281,47.71732],[8.71949,47.7332],[8.7481,47.74828],[8.73118,47.76594],[8.71253,47.76977],[8.69489,47.76304],[8.68697,47.78866],[8.6575,47.80492],[8.64109,47.79689],[8.64198,47.77242],[8.63162,47.76476],[8.62273,47.80186],[8.56584,47.8127],[8.55553,47.79147],[8.56921,47.78401],[8.55152,47.78903],[8.51787,47.77813],[8.48673,47.77743],[8.46771,47.76667],[8.44365,47.74006],[8.44791,47.7277],[8.39905,47.70047],[8.41258,47.68489],[8.39933,47.67266],[8.40794,47.66288],[8.45711,47.64828],[8.47221,47.63399],[8.50414,47.64265],[8.53498,47.6418],[8.5554,47.66484],[8.57843,47.65705],[8.60035,47.66568],[8.62226,47.65029],[8.60533,47.65691],[8.5905,47.64577],[8.5985,47.61447],[8.57812,47.60128],[8.57137,47.60265],[8.58086,47.61338],[8.56241,47.62776],[8.51249,47.63774],[8.50152,47.62155],[8.47587,47.6191],[8.45051,47.60397],[8.45444,47.5866],[8.46887,47.57945],[8.46247,47.57654],[8.43144,47.57109],[8.39775,47.58168],[8.38077,47.5712],[8.33152,47.57526],[8.30333,47.59169],[8.29592,47.61273],[8.25853,47.61981],[8.22945,47.61254],[8.20519,47.62554],[8.16154,47.59805],[8.14687,47.60006],[8.13095,47.58795],[8.10579,47.58613],[8.08768,47.56208],[8.06619,47.56892],[8.02093,47.5549],[7.95913,47.5626],[7.9436,47.54851],[7.91678,47.5539],[7.91666,47.57328],[7.89515,47.59141],[7.84218,47.58664],[7.82222,47.59245],[7.79134,47.56098],[7.69263,47.5369],[7.6725,47.53927],[7.65028,47.55568],[7.69128,47.56295],[7.69623,47.57211],[7.68118,47.5875],[7.69823,47.60413],[7.67086,47.59642],[7.64357,47.60123],[7.6185,47.58201],[7.58715,47.59421],[7.5781,47.58112],[7.56339,47.58165],[7.55161,47.56835],[7.49387,47.54282],[7.49164,47.5189],[7.50287,47.49965],[7.48513,47.48653],[7.42777,47.49981],[7.41497,47.47825],[7.44472,47.47005],[7.41133,47.44661],[7.38524,47.43701],[7.33956,47.44567],[7.30134,47.44309],[7.25178,47.42833],[7.23161,47.44403],[7.20071,47.43976],[7.17958,47.44669],[7.19312,47.4807],[7.20943,47.49033],[7.20318,47.49852],[7.16344,47.49448],[7.1279,47.50838],[7.0783,47.49382],[7.02101,47.50895],[6.97881,47.49806],[6.99225,47.45381],[6.96811,47.45136],[6.95973,47.43949],[6.93531,47.43628],[6.93336,47.41025],[6.90806,47.40737],[6.90577,47.38856],[6.87732,47.37429],[6.87286,47.35153],[6.97532,47.35568],[7.01511,47.36719],[7.04346,47.35862],[7.04346,47.34663],[7.05321,47.34302],[7.04673,47.33361],[7.0051,47.32755],[7.0094,47.31461],[6.99432,47.30061],[6.93576,47.28941],[6.94647,47.24458],[6.83572,47.17406],[6.84482,47.15989],[6.80074,47.13403],[6.73624,47.11303],[6.73778,47.09487],[6.69857,47.08563],[6.68496,47.06715],[6.70654,47.05094],[6.69426,47.04203],[6.65467,47.03061],[6.63511,47.00558],[6.61564,46.99609],[6.59171,46.9962],[6.50706,46.97147],[6.49282,46.9779],[6.42715,46.93109],[6.45796,46.8896],[6.45364,46.8528],[6.42452,46.8128],[6.42925,46.79908],[6.45031,46.78735],[6.44531,46.77694],[6.42639,46.76043],[6.39152,46.75219],[6.38401,46.73766],[6.34703,46.71854],[6.27816,46.69432],[6.10453,46.57829],[6.14737,46.54527],[6.06656,46.46673],[6.07968,46.44342],[6.05742,46.41575],[6.09261,46.40539],[6.16249,46.36731],[6.13254,46.34125],[6.1137,46.29918],[6.09612,46.28609],[6.11409,46.24976],[6.10145,46.24379],[6.08999,46.25142],[6.06045,46.24983],[6.04481,46.23761],[6.03073,46.24257],[5.96804,46.21629],[5.95789,46.19505],[5.98733,46.18245],[5.95012,46.13048]]],"type":"Polygon"}}, -{"properties":{"name":"IPR ortofoto LAST (tmsproxy)","id":"IPR-orotofoto-last-tms","url":"https://osm-{switch:a,b,c}.zby.cz/tiles_ipr_last.php/{zoom}/{x}/{y}.jpg","type":"tms","min_zoom":1,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[14.81232,49.93089],[14.74502,50.25247],[14.12025,50.19882],[14.18755,49.87687],[14.81232,49.93089]]],"type":"Polygon"}}, -{"properties":{"name":"IPR ortofoto Low-Vegetation (tmsproxy)","id":"IPR-orotofoto-vege-tms","url":"https://osm-{switch:a,b,c}.zby.cz/tiles_ipr_vege.php/{zoom}/{x}/{y}.jpg","type":"tms","min_zoom":2,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[14.30454,49.99538],[14.31604,49.94205],[14.35,49.94508],[14.35384,49.92726],[14.42385,49.93352],[14.42009,49.95097],[14.48865,49.95709],[14.48479,49.97501],[14.55386,49.98117],[14.55012,49.99852],[14.58455,50.00159],[14.5883,49.98424],[14.69168,49.99346],[14.67634,50.06453],[14.71279,50.06777],[14.70115,50.12158],[14.6647,50.11834],[14.661,50.13543],[14.62755,50.13246],[14.61965,50.16895],[14.58543,50.16591],[14.58163,50.18344],[14.40776,50.168],[14.41156,50.15045],[14.37765,50.14744],[14.3738,50.16524],[14.33893,50.16214],[14.34278,50.14434],[14.27368,50.1382],[14.27749,50.12058],[14.2088,50.11447],[14.21289,50.09557],[14.24656,50.09857],[14.25417,50.06336],[14.21987,50.0603],[14.2237,50.04259],[14.258,50.04565],[14.26953,49.99226],[14.30454,49.99538]]],"type":"Polygon"}}, -{"properties":{"name":"Praha IPR low-vegetation orthophoto","id":"PrahaIPRlow-vegetationorthophoto","url":"https://giswas1.mepnet.cz/arcgis/services/MAP/mimovegetacni_snimkovani_cache/ImageServer/WMSServer?LAYERS=0&STYLES=default&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","type":"wms","category":"historicphoto","min_zoom":1,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[14.30454,49.99538],[14.31604,49.94205],[14.35,49.94508],[14.35384,49.92726],[14.42385,49.93352],[14.42009,49.95097],[14.48865,49.95709],[14.48479,49.97501],[14.55386,49.98117],[14.55012,49.99852],[14.58455,50.00159],[14.5883,49.98424],[14.69168,49.99346],[14.67634,50.06453],[14.71279,50.06777],[14.70115,50.12158],[14.6647,50.11834],[14.661,50.13543],[14.62755,50.13246],[14.61965,50.16895],[14.58543,50.16591],[14.58163,50.18344],[14.40776,50.168],[14.41156,50.15045],[14.37765,50.14744],[14.3738,50.16524],[14.33893,50.16214],[14.34278,50.14434],[14.27368,50.1382],[14.27749,50.12058],[14.2088,50.11447],[14.21289,50.09557],[14.24656,50.09857],[14.25417,50.06336],[14.21987,50.0603],[14.2237,50.04259],[14.258,50.04565],[14.26953,49.99226],[14.30454,49.99538]]],"type":"Polygon"}}, +{"properties":{"name":"Praha IPR latest orthophoto (TMS mirror)","id":"IPR-orotofoto-last-tms","url":"https://osm-{switch:a,b,c}.zby.cz/tiles_ipr_last.php/{zoom}/{x}/{y}.jpg","attribution":{"text":"IPR Praha; OSM CZ","url":"https://iprpraha.cz/"},"type":"tms","category":"photo","min_zoom":2,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[14.30454,49.99538],[14.31604,49.94205],[14.35,49.94508],[14.35384,49.92726],[14.42385,49.93352],[14.42009,49.95097],[14.48865,49.95709],[14.48479,49.97501],[14.55386,49.98117],[14.55012,49.99852],[14.58455,50.00159],[14.5883,49.98424],[14.69168,49.99346],[14.67634,50.06453],[14.71279,50.06777],[14.70115,50.12158],[14.6647,50.11834],[14.661,50.13543],[14.62755,50.13246],[14.61965,50.16895],[14.58543,50.16591],[14.58163,50.18344],[14.40776,50.168],[14.41156,50.15045],[14.37765,50.14744],[14.3738,50.16524],[14.33893,50.16214],[14.34278,50.14434],[14.27368,50.1382],[14.27749,50.12058],[14.2088,50.11447],[14.21289,50.09557],[14.24656,50.09857],[14.25417,50.06336],[14.21987,50.0603],[14.2237,50.04259],[14.258,50.04565],[14.26953,49.99226],[14.30454,49.99538]]],"type":"Polygon"}}, +{"properties":{"name":"Praha IPR low-vegetation orthophoto (TMS mirror)","id":"IPR-orotofoto-vege-tms","url":"https://osm-{switch:a,b,c}.zby.cz/tiles_ipr_vege.php/{zoom}/{x}/{y}.jpg","attribution":{"text":"IPR Praha; OSM CZ","url":"https://iprpraha.cz/"},"type":"tms","category":"photo","min_zoom":2,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[14.30454,49.99538],[14.31604,49.94205],[14.35,49.94508],[14.35384,49.92726],[14.42385,49.93352],[14.42009,49.95097],[14.48865,49.95709],[14.48479,49.97501],[14.55386,49.98117],[14.55012,49.99852],[14.58455,50.00159],[14.5883,49.98424],[14.69168,49.99346],[14.67634,50.06453],[14.71279,50.06777],[14.70115,50.12158],[14.6647,50.11834],[14.661,50.13543],[14.62755,50.13246],[14.61965,50.16895],[14.58543,50.16591],[14.58163,50.18344],[14.40776,50.168],[14.41156,50.15045],[14.37765,50.14744],[14.3738,50.16524],[14.33893,50.16214],[14.34278,50.14434],[14.27368,50.1382],[14.27749,50.12058],[14.2088,50.11447],[14.21289,50.09557],[14.24656,50.09857],[14.25417,50.06336],[14.21987,50.0603],[14.2237,50.04259],[14.258,50.04565],[14.26953,49.99226],[14.30454,49.99538]]],"type":"Polygon"}}, +{"properties":{"name":"Praha IPR latest orthophoto","id":"PrahaIPRlatestorthophoto","url":"https://gs-pub.praha.eu/imgs/services/ort/letecke_snimkovani/ImageServer/WMSServer?FORMAT=image/jpeg&VERSION=1.1.1&SERVICE=WMS&REQUEST=GetMap&LAYERS=0&STYLES=&SRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","type":"wms","category":"photo","min_zoom":1,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[14.30454,49.99538],[14.31604,49.94205],[14.35,49.94508],[14.35384,49.92726],[14.42385,49.93352],[14.42009,49.95097],[14.48865,49.95709],[14.48479,49.97501],[14.55386,49.98117],[14.55012,49.99852],[14.58455,50.00159],[14.5883,49.98424],[14.69168,49.99346],[14.67634,50.06453],[14.71279,50.06777],[14.70115,50.12158],[14.6647,50.11834],[14.661,50.13543],[14.62755,50.13246],[14.61965,50.16895],[14.58543,50.16591],[14.58163,50.18344],[14.40776,50.168],[14.41156,50.15045],[14.37765,50.14744],[14.3738,50.16524],[14.33893,50.16214],[14.34278,50.14434],[14.27368,50.1382],[14.27749,50.12058],[14.2088,50.11447],[14.21289,50.09557],[14.24656,50.09857],[14.25417,50.06336],[14.21987,50.0603],[14.2237,50.04259],[14.258,50.04565],[14.26953,49.99226],[14.30454,49.99538]]],"type":"Polygon"}}, +{"properties":{"name":"Praha IPR low-vegetation orthophoto","id":"PrahaIPRlow-vegetationorthophoto","url":"https://gs-pub.praha.eu/imgs/services/ort/mimovegetacni_letecke_snimkovani/ImageServer/WMSServer?FORMAT=image/jpeg&VERSION=1.1.1&SERVICE=WMS&REQUEST=GetMap&LAYERS=0&STYLES=&SRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"text":"IPR Praha","url":"https://iprpraha.cz/"},"type":"wms","category":"photo","min_zoom":1,"max_zoom":20,"best":true},"type":"Feature","geometry":{"coordinates":[[[14.30454,49.99538],[14.31604,49.94205],[14.35,49.94508],[14.35384,49.92726],[14.42385,49.93352],[14.42009,49.95097],[14.48865,49.95709],[14.48479,49.97501],[14.55386,49.98117],[14.55012,49.99852],[14.58455,50.00159],[14.5883,49.98424],[14.69168,49.99346],[14.67634,50.06453],[14.71279,50.06777],[14.70115,50.12158],[14.6647,50.11834],[14.661,50.13543],[14.62755,50.13246],[14.61965,50.16895],[14.58543,50.16591],[14.58163,50.18344],[14.40776,50.168],[14.41156,50.15045],[14.37765,50.14744],[14.3738,50.16524],[14.33893,50.16214],[14.34278,50.14434],[14.27368,50.1382],[14.27749,50.12058],[14.2088,50.11447],[14.21289,50.09557],[14.24656,50.09857],[14.25417,50.06336],[14.21987,50.0603],[14.2237,50.04259],[14.258,50.04565],[14.26953,49.99226],[14.30454,49.99538]]],"type":"Polygon"}}, {"properties":{"name":"Berlin/Geoportal TrueDOP20RGB (2020)","id":"Berlin-2020-TrueDOP","url":"https://tiles.codefor.de/berlin-2020-truedop20rgb/{zoom}/{x}/{y}.png","attribution":{"required":true,"text":"Geoportal Berlin/Digitale farbige TrueOrthophotos 2020 (TrueDOP20RGB) (codefor.de mirror)"},"type":"tms","category":"photo","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[13.05872,52.42427],[13.05951,52.4063],[13.0889,52.40677],[13.08967,52.3888],[13.11905,52.38927],[13.11981,52.3713],[13.14917,52.37177],[13.14842,52.38974],[13.17779,52.3902],[13.20717,52.39064],[13.23654,52.39109],[13.26592,52.39152],[13.2953,52.39195],[13.32467,52.39237],[13.35405,52.39278],[13.35472,52.37481],[13.38409,52.37521],[13.38474,52.35724],[13.4141,52.35764],[13.41474,52.33966],[13.41539,52.32169],[13.44472,52.32208],[13.47405,52.32246],[13.50339,52.32284],[13.53272,52.32321],[13.56206,52.32357],[13.59139,52.32392],[13.62073,52.32427],[13.65007,52.3246],[13.67941,52.32494],[13.67887,52.34291],[13.67833,52.36089],[13.7077,52.36121],[13.70717,52.37919],[13.73655,52.37951],[13.73603,52.39748],[13.76542,52.39779],[13.76492,52.41577],[13.76442,52.43375],[13.76391,52.45172],[13.76341,52.4697],[13.73397,52.46939],[13.73346,52.48736],[13.70401,52.48705],[13.67456,52.48672],[13.67402,52.50469],[13.67348,52.52267],[13.67294,52.54065],[13.64346,52.54031],[13.64291,52.55829],[13.61341,52.55795],[13.61285,52.57592],[13.58334,52.57557],[13.58276,52.59355],[13.55325,52.59319],[13.55265,52.61116],[13.55206,52.62914],[13.55147,52.64711],[13.55087,52.66509],[13.52131,52.66472],[13.5207,52.68269],[13.49113,52.68232],[13.46155,52.68194],[13.43198,52.68155],[13.43262,52.66358],[13.40306,52.66319],[13.3735,52.66278],[13.34394,52.66237],[13.31438,52.66196],[13.31369,52.67993],[13.28412,52.6795],[13.25455,52.67907],[13.25527,52.6611],[13.25598,52.64313],[13.22644,52.64269],[13.19689,52.64225],[13.19763,52.62427],[13.1681,52.62382],[13.16885,52.60585],[13.13933,52.60539],[13.10982,52.60492],[13.11059,52.58695],[13.11136,52.56898],[13.11214,52.55101],[13.11291,52.53304],[13.11368,52.51507],[13.11445,52.4971],[13.11521,52.47913],[13.08578,52.47866],[13.08656,52.46069],[13.08734,52.44272],[13.08812,52.42474],[13.05872,52.42427]]],"type":"Polygon"}}, {"properties":{"name":"Berlin/Geoportal ALKIS","id":"Berlin-Alkis","url":"https://mapproxy.codefor.de/tiles/1.0.0/alkis_30/mercator/{zoom}/{x}/{y}.png","attribution":{"required":true,"text":"Geoportal Berlin/ALKIS Berlin (Amtliches Liegenschaftskatasterinformationssystem) (codefor.de proxy)"},"type":"tms","category":"other","max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[13.29535,52.392],[13.35417,52.39279],[13.38418,52.36617],[13.42861,52.36674],[13.44254,52.38473],[13.53103,52.38581],[13.5313,52.37719],[13.58956,52.37786],[13.61959,52.36012],[13.62038,52.33319],[13.66454,52.33367],[13.66428,52.34287],[13.67876,52.34302],[13.67826,52.36081],[13.70772,52.36111],[13.70722,52.37923],[13.75097,52.39814],[13.75027,52.42468],[13.76454,52.42482],[13.7636,52.46069],[13.73406,52.4604],[13.7188,52.47807],[13.68929,52.48692],[13.65989,52.48661],[13.65912,52.51344],[13.67365,52.51359],[13.67314,52.53139],[13.65822,52.53124],[13.62826,52.55788],[13.5988,52.55755],[13.59798,52.58464],[13.56782,52.59313],[13.53842,52.59279],[13.52345,52.6017],[13.52288,52.61946],[13.53757,52.61964],[13.53643,52.6558],[13.50681,52.65545],[13.50593,52.68261],[13.44682,52.68189],[13.43254,52.67251],[13.43315,52.65458],[13.32953,52.65323],[13.31405,52.67086],[13.26973,52.67025],[13.27041,52.65222],[13.21212,52.63346],[13.18403,52.60593],[13.13972,52.60527],[13.11055,52.59579],[13.11534,52.47934],[13.10073,52.47912],[13.10259,52.43394],[13.07356,52.42447],[13.07431,52.4067],[13.08906,52.40693],[13.11888,52.38921],[13.11926,52.38001],[13.14877,52.38046],[13.14839,52.3897],[13.19241,52.39035],[13.19206,52.39937],[13.29502,52.40083],[13.29535,52.392]]],"type":"Polygon"}}, {"properties":{"name":"Berlin/Geoportal Baumbestand","id":"Berlin-Baumbestand","url":"https://mapproxy.codefor.de/tiles/1.0.0/baumbestand_0_1_3_4_merged/mercator/{zoom}/{x}/{y}.png","attribution":{"required":true,"text":"Geoportal Berlin/Straßen- und Anlagenbaumbestand Berlin (codefor.de proxy)"},"type":"tms","category":"other","max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[13.29535,52.392],[13.35417,52.39279],[13.38418,52.36617],[13.42861,52.36674],[13.44254,52.38473],[13.53103,52.38581],[13.5313,52.37719],[13.58956,52.37786],[13.61959,52.36012],[13.62038,52.33319],[13.66454,52.33367],[13.66428,52.34287],[13.67876,52.34302],[13.67826,52.36081],[13.70772,52.36111],[13.70722,52.37923],[13.75097,52.39814],[13.75027,52.42468],[13.76454,52.42482],[13.7636,52.46069],[13.73406,52.4604],[13.7188,52.47807],[13.68929,52.48692],[13.65989,52.48661],[13.65912,52.51344],[13.67365,52.51359],[13.67314,52.53139],[13.65822,52.53124],[13.62826,52.55788],[13.5988,52.55755],[13.59798,52.58464],[13.56782,52.59313],[13.53842,52.59279],[13.52345,52.6017],[13.52288,52.61946],[13.53757,52.61964],[13.53643,52.6558],[13.50681,52.65545],[13.50593,52.68261],[13.44682,52.68189],[13.43254,52.67251],[13.43315,52.65458],[13.32953,52.65323],[13.31405,52.67086],[13.26973,52.67025],[13.27041,52.65222],[13.21212,52.63346],[13.18403,52.60593],[13.13972,52.60527],[13.11055,52.59579],[13.11534,52.47934],[13.10073,52.47912],[13.10259,52.43394],[13.07356,52.42447],[13.07431,52.4067],[13.08906,52.40693],[13.11888,52.38921],[13.11926,52.38001],[13.14877,52.38046],[13.14839,52.3897],[13.19241,52.39035],[13.19206,52.39937],[13.29502,52.40083],[13.29535,52.392]]],"type":"Polygon"}}, @@ -166,7 +167,10 @@ {"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 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 ALKIS","id":"Hessen-ALKIS","url":"https://www.gds-srv.hessen.de/cgi-bin/lika-services/ogc-free-maps.ows?LAYERS=he_alk&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 @ Hessisches Landesamt für Bodenmanagement und Geoinformation","url":"https://hvbg.hessen.de"},"type":"wms","category":"map","min_zoom":16,"max_zoom":21},"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":"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":"Hesse DTK","id":"Hessen-DTK","url":"https://www.gds-srv.hessen.de/cgi-bin/lika-services/ogc-free-maps.ows?LAYERS=he_dtk&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 @ Hessisches Landesamt für Bodenmanagement und Geoinformation","url":"https://hvbg.hessen.de"},"type":"wms","category":"map","min_zoom":16,"max_zoom":19},"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":"Hesse WebAtlas","id":"Hessen-WebAtlas","url":"https://www.gds-srv.hessen.de/cgi-bin/lika-services/ogc-free-maps.ows?LAYERS=he_pg&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 @ Hessisches Landesamt für Bodenmanagement und Geoinformation","url":"https://hvbg.hessen.de"},"type":"wms","category":"map","min_zoom":13,"max_zoom":19},"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"}}, {"properties":{"name":"Mainz latest aerial imagery","id":"mainzlatestaerialimagery","url":"https://geodaten.mainz.de/map/service?LAYERS=Orthophoto&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Vermessung und Geoinformation Mainz","url":"https://www.mainz.de/vv/oe/100140100000035141.php#tab-infos"},"type":"wms","category":"photo"},"type":"Feature","geometry":{"coordinates":[[[8.12577,49.9882],[8.12851,49.9722],[8.12714,49.95242],[8.16802,49.9492],[8.17177,49.92458],[8.18318,49.92246],[8.18482,49.89189],[8.19168,49.88438],[8.23837,49.88438],[8.24357,49.88821],[8.26121,49.89058],[8.26867,49.88835],[8.32283,49.88835],[8.3267,49.89722],[8.35167,49.89763],[8.35367,49.90596],[8.35991,49.90868],[8.35991,49.97892],[8.35183,49.98183],[8.35167,49.99527],[8.33946,49.9985],[8.33793,50.00454],[8.32546,50.00754],[8.3242,50.01603],[8.31222,50.01618],[8.30909,50.02398],[8.29593,50.02521],[8.29467,50.04077],[8.29124,50.0421],[8.26929,50.04217],[8.26507,50.04696],[8.15392,50.04696],[8.15222,50.03339],[8.14087,50.03105],[8.13797,50.02457],[8.12783,50.02266],[8.12577,49.9882]]],"type":"Polygon"}}, {"properties":{"name":"Offene Regionalkarte Mecklenburg-Vorpommern (ORKa.MV)","id":"orka.mv","url":"https://www.orka-mv.de/geodienste/orkamv/tiles/1.0.0/orkamv/GLOBAL_WEBMERCATOR/{zoom}/{x}/{y}.png","attribution":{"text":"ORKa.MV","url":"https://www.orka-mv.de/nutzung.html"},"type":"tms","category":"osmbasedmap","min_zoom":12,"max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[13.32019,54.72212],[12.49001,54.60327],[11.04611,54.03758],[10.73651,53.87637],[10.54746,53.36447],[11.05159,53.14154],[11.31736,53.10208],[12.98593,53.1399],[14.17503,53.21051],[14.45449,53.30557],[14.24626,53.93125],[13.81063,54.51907],[13.5613,54.69996],[13.32019,54.72212]]],"type":"Polygon"}}, @@ -210,7 +214,7 @@ {"properties":{"name":"ICGC - Topogràfic de Catalunya ","id":"ICGC-topo","url":"https://geoserveis.icgc.cat/icc_mapesmultibase/utm/wms/service?LAYERS=topo&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Institut Cartogràfic i Geològic de Catalunya"},"type":"wms","category":"other"},"type":"Feature","geometry":{"coordinates":[[[0.64816,42.69653],[0.65695,42.68887],[0.69162,42.65037],[0.69591,42.62714],[0.70707,42.61918],[0.75806,42.6044],[0.74552,42.55751],[0.71686,42.52513],[0.68699,42.48716],[0.70896,42.43106],[0.72235,42.40381],[0.7172,42.3673],[0.74123,42.32403],[0.73334,42.25559],[0.71154,42.2247],[0.69076,42.15144],[0.68956,42.10102],[0.67789,42.08905],[0.64613,42.02532],[0.58416,41.96766],[0.55344,41.93383],[0.59137,41.92157],[0.59052,41.8826],[0.54932,41.85038],[0.48237,41.80306],[0.47069,41.78872],[0.39705,41.75864],[0.36547,41.72405],[0.32804,41.68817],[0.32375,41.67484],[0.32427,41.6597],[0.34727,41.59606],[0.42108,41.58823],[0.42727,41.56357],[0.44083,41.54507],[0.39482,41.49238],[0.3471,41.48942],[0.33611,41.48235],[0.33955,41.46666],[0.34332,41.43063],[0.33783,41.41351],[0.315,41.39561],[0.35912,41.36702],[0.36976,41.35169],[0.34367,41.33145],[0.34882,41.32204],[0.36238,41.30193],[0.38195,41.27794],[0.37113,41.26181],[0.37079,41.24322],[0.3186,41.22722],[0.29526,41.16392],[0.25062,41.15307],[0.20393,41.13549],[0.19844,41.12644],[0.19895,41.0831],[0.21595,41.0422],[0.23226,41.03171],[0.25801,41.00983],[0.2817,40.96823],[0.28062,40.95514],[0.2738,40.95064],[0.26994,40.9448],[0.2453,40.90923],[0.23603,40.88263],[0.25114,40.87329],[0.24805,40.85563],[0.25715,40.83693],[0.27088,40.82199],[0.23191,40.78691],[0.15638,40.75142],[0.16583,40.72918],[0.23912,40.6973],[0.2829,40.68624],[0.2647,40.65942],[0.2611,40.64457],[0.27826,40.62281],[0.32959,40.60705],[0.39722,40.60001],[0.40203,40.59349],[0.4331,40.57498],[0.4319,40.56546],[0.43499,40.5457],[0.47713,40.53024],[0.61077,40.49239],[0.9391,40.68471],[1.0446,40.98465],[2.12562,41.2353],[2.77469,41.57929],[3.24367,41.88469],[3.33097,42.21861],[3.33679,42.35709],[3.2004,42.35694],[3.17629,42.43562],[3.1684,42.43587],[3.16338,42.43654],[3.16003,42.43565],[3.15771,42.43473],[3.15359,42.4359],[3.13746,42.43777],[3.12355,42.43967],[3.10844,42.43739],[3.09746,42.42827],[3.08266,42.43083],[3.05489,42.45361],[3.04032,42.47583],[3.03034,42.47666],[3.01266,42.46969],[3.00133,42.47412],[2.99075,42.47493],[2.97185,42.47084],[2.9451,42.48326],[2.92975,42.47539],[2.91927,42.45893],[2.90279,42.46045],[2.86983,42.46881],[2.86417,42.46678],[2.85799,42.45677],[2.83795,42.46027],[2.78558,42.41971],[2.77492,42.41456],[2.75448,42.42688],[2.72452,42.42495],[2.67123,42.40522],[2.66796,42.38746],[2.65234,42.39037],[2.64955,42.38615],[2.67036,42.34231],[2.65024,42.34572],[2.57653,42.35984],[2.55377,42.3554],[2.54002,42.33469],[2.50059,42.34459],[2.48342,42.34357],[2.47476,42.35113],[2.46708,42.36169],[2.44169,42.37868],[2.43502,42.39494],[2.38122,42.40277],[2.34538,42.41715],[2.31326,42.42901],[2.25416,42.43992],[2.24505,42.431],[2.21325,42.4249],[2.16755,42.42649],[2.12356,42.41409],[2.10818,42.38265],[2.09035,42.37576],[2.05488,42.35935],[1.99787,42.35982],[1.98035,42.3715],[1.96538,42.38767],[1.95923,42.42428],[1.93604,42.4559],[1.89149,42.45025],[1.88309,42.45999],[1.82417,42.48769],[1.80488,42.491],[1.76206,42.49093],[1.72574,42.50555],[1.70449,42.49191],[1.66786,42.50716],[1.66159,42.49384],[1.65638,42.4698],[1.59656,42.46894],[1.57428,42.45577],[1.56308,42.46173],[1.55837,42.45864],[1.54851,42.43528],[1.51174,42.43142],[1.45,42.44011],[1.44839,42.46045],[1.44359,42.46475],[1.44367,42.4721],[1.4319,42.48901],[1.47071,42.50969],[1.45517,42.53171],[1.45264,42.53954],[1.44024,42.54581],[1.42179,42.54005],[1.42075,42.54783],[1.42822,42.55713],[1.44599,42.568],[1.4441,42.60366],[1.43097,42.61981],[1.41361,42.65762],[1.38789,42.69026],[1.35766,42.72167],[1.3298,42.72572],[1.30787,42.71996],[1.25903,42.72145],[1.23098,42.72999],[1.16532,42.71061],[1.13284,42.74064],[1.13511,42.75069],[1.11373,42.77002],[1.07988,42.78936],[1.01816,42.78832],[0.9601,42.80707],[0.92171,42.79588],[0.90714,42.80513],[0.84959,42.83009],[0.832,42.82956],[0.80944,42.84145],[0.79771,42.84186],[0.78278,42.83796],[0.71465,42.8632],[0.67635,42.85575],[0.65626,42.83846],[0.66369,42.80408],[0.64528,42.78633],[0.64236,42.7823],[0.65798,42.77209],[0.63843,42.75414],[0.67514,42.72545],[0.66553,42.72041],[0.67789,42.70742],[0.66925,42.69249],[0.64816,42.69653]]],"type":"Polygon"}}, {"properties":{"name":"PNOA Spain","id":"PNOA-Spain-TMS","url":"https://tms-pnoa-ma.idee.es/1.0.0/pnoa-ma/{zoom}/{x}/{-y}.jpeg","attribution":{"required":true,"text":"PNOA"},"type":"tms","category":"photo","max_zoom":20,"best":true},"type":"Feature","geometry":{"coordinates":[[[[-17.88913,27.85905],[-17.99065,27.85675],[-18.03868,27.76558],[-18.11464,27.76379],[-18.11546,27.78636],[-18.1661,27.78516],[-18.16349,27.69492],[-18.08898,27.69634],[-18.08734,27.67387],[-18.03641,27.67537],[-18.03501,27.63026],[-17.959,27.6324],[-17.86033,27.7926],[-17.86303,27.83688],[-17.8884,27.83649],[-17.88913,27.85905]]],[[[-15.75328,28.08143],[-15.7783,28.03632],[-15.82911,28.03558],[-15.82895,27.99066],[-15.8542,27.99018],[-15.85374,27.90089],[-15.80167,27.81105],[-15.70075,27.74335],[-15.62415,27.74199],[-15.62361,27.72134],[-15.54771,27.72161],[-15.52277,27.76678],[-15.47188,27.76665],[-15.47181,27.78939],[-15.421,27.78797],[-15.39598,27.83474],[-15.37057,27.83521],[-15.3708,27.92524],[-15.34548,27.92544],[-15.34578,28.01532],[-15.37065,28.01532],[-15.37103,28.03802],[-15.39644,28.03836],[-15.39728,28.19614],[-15.45016,28.19614],[-15.44978,28.15075],[-15.49874,28.15041],[-15.49897,28.1728],[-15.72826,28.17186],[-15.72788,28.08157],[-15.75328,28.08143]]],[[[-17.08208,28.13518],[-17.18579,28.22388],[-17.33846,28.2213],[-17.33756,28.17637],[-17.36297,28.17572],[-17.36038,28.06398],[-17.30867,28.01923],[-17.25785,28.01997],[-17.25768,27.99784],[-17.23199,27.99804],[-17.23211,28.02037],[-17.15633,28.02146],[-17.13154,28.06681],[-17.08084,28.068],[-17.08208,28.13518]]],[[[-16.44571,28.49113],[-16.52143,28.42261],[-16.87807,28.39542],[-16.87767,28.37293],[-16.92866,28.37219],[-16.92782,28.32758],[-16.90239,28.32793],[-16.90151,28.28277],[-16.87608,28.28322],[-16.87565,28.26055],[-16.85028,28.26079],[-16.8487,28.17075],[-16.82317,28.17087],[-16.8224,28.1259],[-16.7707,28.08161],[-16.74534,28.08181],[-16.7447,28.03679],[-16.7191,28.03714],[-16.71842,27.99192],[-16.61681,27.99305],[-16.61684,28.01532],[-16.51533,28.01648],[-16.4151,28.13113],[-16.41584,28.19761],[-16.34083,28.2893],[-16.34208,28.37951],[-16.31631,28.38019],[-16.31675,28.40176],[-16.26586,28.403],[-16.26535,28.44761],[-16.23858,28.44847],[-16.16475,28.49386],[-16.16481,28.51612],[-16.09934,28.51638],[-16.09951,28.5925],[-16.1278,28.59218],[-16.12793,28.60782],[-16.19162,28.60684],[-16.19125,28.58372],[-16.3446,28.58221],[-16.34433,28.55976],[-16.42092,28.55884],[-16.42059,28.53627],[-16.44625,28.53597],[-16.44571,28.49113]]],[[[-14.21537,28.33903],[-14.21688,28.22788],[-14.41575,28.11561],[-14.52244,28.11841],[-14.52156,28.04678],[-14.49593,28.04585],[-14.49628,28.06826],[-14.44666,28.0658],[-14.44578,28.04698],[-14.33197,28.03687],[-14.29132,28.04524],[-14.21472,28.11189],[-14.21537,28.15781],[-14.13866,28.15791],[-14.13871,28.17999],[-14.03696,28.17958],[-13.98564,28.22357],[-13.91582,28.22414],[-13.83151,28.39702],[-13.82885,28.53847],[-13.80339,28.53842],[-13.80258,28.6519],[-13.82786,28.6518],[-13.82757,28.71935],[-13.8013,28.71899],[-13.80007,28.78793],[-13.82896,28.78798],[-13.82902,28.76643],[-13.95617,28.76659],[-13.95652,28.74494],[-14.03358,28.72267],[-14.11151,28.4748],[-14.16417,28.45283],[-14.21537,28.33903]]],[[[-17.9424,28.87261],[-18.02241,28.80384],[-18.02125,28.74819],[-17.89395,28.52255],[-17.88463,28.44606],[-17.80601,28.4469],[-17.80861,28.4925],[-17.78316,28.49366],[-17.78331,28.54167],[-17.76225,28.5592],[-17.75197,28.58337],[-17.74573,28.61656],[-17.74579,28.67435],[-17.75708,28.67413],[-17.75744,28.69318],[-17.71293,28.73037],[-17.71398,28.76497],[-17.73949,28.76422],[-17.74127,28.832],[-17.76698,28.83122],[-17.76759,28.85378],[-17.89033,28.85151],[-17.89118,28.87371],[-17.9424,28.87261]]],[[[-13.85025,29.01659],[-13.85182,28.98343],[-13.85244,28.91486],[-13.90131,28.89245],[-13.9024,28.84698],[-13.80066,28.84566],[-13.80093,28.82311],[-13.77569,28.82305],[-13.69729,28.88982],[-13.69729,28.91277],[-13.60725,28.9118],[-13.43886,29.00024],[-13.43746,29.13513],[-13.4117,29.13499],[-13.41056,29.22298],[-13.45928,29.25559],[-13.45974,29.2942],[-13.50913,29.29456],[-13.51006,29.31635],[-13.56354,29.31729],[-13.56406,29.27138],[-13.53892,29.2712],[-13.53897,29.25004],[-13.56613,29.25013],[-13.5666,29.203],[-13.51565,29.20223],[-13.51565,29.18206],[-13.5398,29.18278],[-13.54089,29.13753],[-13.65782,29.13685],[-13.71322,29.09351],[-13.76634,29.09345],[-13.85025,29.01659]]],[[[1.50355,38.72532],[1.48133,38.91551],[1.55189,38.92544],[1.56673,38.95666],[1.64874,38.95833],[1.64799,38.99907],[1.73217,38.99936],[1.73147,39.04417],[1.64895,39.04319],[1.64816,39.12764],[1.39486,39.12657],[1.39544,39.08642],[1.22811,39.08526],[1.22911,39.0029],[1.14487,39.0018],[1.14528,38.832],[1.31136,38.83316],[1.31219,38.79065],[1.39469,38.79162],[1.39519,38.75296],[1.31128,38.75193],[1.31259,38.62388],[1.6489,38.62511],[1.64807,38.71115],[1.58456,38.71012],[1.58116,38.70054],[1.54915,38.70028],[1.51972,38.70921],[1.50355,38.72532]]],[[[2.31192,39.54179],[2.31223,39.49934],[2.43902,39.49934],[2.43871,39.48469],[2.43933,39.41611],[2.54507,39.41667],[2.54528,39.49942],[2.64538,39.49966],[2.64569,39.45774],[2.72885,39.45814],[2.72874,39.33342],[2.97904,39.3335],[2.97986,39.25015],[3.08302,39.24994],[3.31462,39.37855],[3.31506,39.47846],[3.48035,39.5959],[3.48148,39.79318],[3.14844,39.79357],[3.14823,39.83316],[3.23129,39.83292],[3.23139,40.00198],[3.14608,40.00198],[3.14566,39.96005],[2.92704,39.96016],[2.34601,39.62709],[2.22835,39.62606],[2.22907,39.541],[2.31192,39.54179]]],[[[-9.21855,42.90163],[-9.14661,42.77503],[-9.08834,42.72696],[-9.03655,42.73066],[-9.1092,42.57511],[-8.89106,41.82289],[-8.60704,42.03405],[-8.36762,42.05575],[-8.24848,42.1008],[-8.12933,42.03488],[-8.25185,41.90786],[-8.13045,41.78058],[-7.95398,41.84593],[-7.42569,41.78477],[-7.16829,41.87188],[-7.13345,41.94048],[-6.62988,41.91121],[-6.54783,41.85597],[-6.58717,41.68832],[-6.51523,41.64129],[-6.31628,41.64465],[-6.21737,41.5791],[-6.56244,41.26303],[-6.64112,41.26556],[-6.76814,41.13871],[-6.80186,41.03959],[-6.9536,41.03704],[-6.83783,40.87576],[-6.85356,40.60664],[-6.86818,40.44516],[-6.8086,40.34501],[-7.01767,40.26615],[-7.04128,40.13479],[-6.88616,40.02299],[-6.92213,39.87909],[-6.99519,39.81954],[-7.05027,39.67522],[-7.55271,39.67954],[-7.51449,39.58865],[-7.3279,39.45599],[-7.33689,39.35351],[-7.25596,39.28133],[-7.24472,39.19689],[-7.15255,39.16029],[-7.15368,39.09577],[-7.00081,39.08879],[-6.96934,39.01983],[-7.06151,38.90796],[-7.04352,38.87297],[-7.26383,38.73807],[-7.34027,38.44024],[-7.08062,38.15708],[-6.96147,38.20125],[-6.99632,38.10756],[-7.0143,38.02438],[-7.11771,38.05536],[-7.15368,38.01552],[-7.26833,37.98895],[-7.31666,37.83997],[-7.4249,37.75992],[-7.45013,37.66958],[-7.52196,37.57237],[-7.51916,37.52292],[-7.50197,37.51641],[-7.4647,37.45305],[-7.46963,37.40758],[-7.4481,37.39094],[-7.44597,37.33261],[-7.43805,37.2452],[-7.42492,37.23505],[-7.42029,37.21183],[-7.40832,37.16822],[-7.37535,37.15354],[-7.26474,37.18435],[-7.04264,37.18507],[-6.87448,37.10838],[-6.62989,37.0194],[-6.49052,36.91738],[-6.41588,36.79939],[-6.47433,36.74897],[-6.40419,36.6235],[-6.32146,36.58163],[-6.33585,36.53106],[-6.23154,36.37701],[-6.15061,36.28646],[-6.07752,36.22241],[-6.03573,36.1781],[-5.99834,36.1645],[-5.94003,36.16556],[-5.93848,36.12215],[-5.85668,36.12421],[-5.85506,36.03856],[-5.68996,36.04053],[-5.68886,36.00365],[-5.43532,36.00344],[-5.43658,36.03889],[-5.35161,36.04014],[-5.35248,36.12247],[-5.26809,36.12418],[-5.28411,36.19702],[-5.16995,36.35135],[-4.9188,36.45313],[-4.65712,36.44042],[-4.37435,36.66333],[-3.72804,36.69291],[-3.46108,36.65488],[-3.09402,36.71263],[-2.92015,36.66756],[-2.68129,36.65911],[-2.37219,36.78018],[-2.21588,36.66192],[-2.06835,36.69291],[-1.85408,36.91229],[-1.6767,37.27652],[-1.44089,37.39037],[-1.33832,37.52867],[-1.11071,37.51641],[-0.91963,37.53758],[-0.71939,37.58784],[-0.66415,37.62315],[-0.68831,37.734],[-0.72158,37.78306],[-0.73237,37.88107],[-0.68111,37.94562],[-0.63705,37.96122],[-0.61367,38.11986],[-0.584,38.17219],[-0.45451,38.14886],[-0.42933,38.16583],[-0.48238,38.19481],[-0.50487,38.28309],[-0.45091,38.33108],[-0.38347,38.33813],[-0.37987,38.39312],[-0.27197,38.47624],[-0.05705,38.52691],[-0.0022,38.60706],[0.09581,38.61338],[0.25587,38.72642],[0.23429,38.79864],[0.12189,38.87218],[0.00949,38.88268],[-0.11101,38.97222],[-0.21352,39.15585],[-0.18564,39.17746],[-0.28636,39.33343],[-0.29085,39.50363],[-0.18474,39.63117],[-0.09392,39.81169],[0.01039,39.89522],[0.07513,40.01447],[0.16415,40.06472],[0.26756,40.19192],[0.43392,40.37576],[0.55801,40.55022],[0.66502,40.53587],[0.87813,40.67514],[0.9114,40.73376],[0.75854,40.81956],[1.08585,41.04849],[1.17937,41.04646],[1.3763,41.11627],[1.86008,41.22322],[2.16492,41.29893],[2.26293,41.42716],[2.78358,41.63718],[3.06054,41.76474],[3.19452,41.85589],[3.24668,41.95294],[3.23055,42.126],[3.14759,42.12606],[3.14759,42.2073],[3.31397,42.20702],[3.31389,42.29084],[3.39739,42.29009],[3.39785,42.33404],[3.31412,42.33399],[3.31415,42.37604],[3.23049,42.37644],[3.23078,42.45934],[3.06388,42.45915],[3.06388,42.50085],[2.81126,42.50104],[2.81133,42.45961],[2.64448,42.45924],[2.64479,42.37626],[2.48098,42.37594],[2.48048,42.41797],[2.39768,42.41784],[2.39693,42.45994],[2.06456,42.45902],[2.06386,42.50164],[1.73164,42.50118],[1.73041,42.54342],[1.64328,42.54245],[1.64436,42.50203],[1.4813,42.50107],[1.48061,42.71034],[1.41137,42.70939],[1.36326,42.74155],[1.17532,42.73429],[1.10878,42.79898],[0.9676,42.81811],[0.71492,42.88272],[0.64118,42.85767],[0.62769,42.7224],[0.44875,42.71447],[0.36422,42.74287],[0.30218,42.71777],[0.18618,42.7541],[-0.03143,42.71249],[-0.14563,42.81086],[-0.31648,42.86558],[-0.42889,42.82009],[-0.50442,42.84845],[-0.55118,42.82207],[-0.69685,42.90314],[-0.71484,42.96108],[-0.7562,42.98213],[-0.94234,42.9749],[-1.00619,43.00778],[-1.19232,43.06496],[-1.27955,43.07744],[-1.23549,43.13325],[-1.30203,43.13522],[-1.36407,43.11159],[-1.37307,43.05117],[-1.41983,43.06036],[-1.44231,43.08336],[-1.37037,43.1713],[-1.35688,43.23815],[-1.36677,43.27614],[-1.47799,43.31284],[-1.56305,43.31338],[-1.56359,43.29212],[-1.60344,43.29266],[-1.60299,43.31295],[-1.64498,43.31332],[-1.64467,43.33372],[-1.68811,43.33413],[-1.68904,43.31291],[-1.72259,43.31318],[-1.71135,43.33125],[-1.71005,43.37569],[-1.77005,43.37605],[-1.7698,43.39644],[-1.85528,43.39725],[-1.9854,43.3563],[-2.30462,43.31706],[-2.74524,43.47551],[-2.93857,43.46246],[-3.1083,43.38163],[-3.56128,43.54236],[-3.74,43.48693],[-3.80295,43.51954],[-4.18399,43.42492],[-4.61562,43.4192],[-4.90899,43.48367],[-5.17875,43.49916],[-5.28553,43.56191],[-5.60363,43.57087],[-5.85204,43.6799],[-6.12293,43.57901],[-6.24882,43.6075],[-7.19975,43.58308],[-7.31889,43.67827],[-7.66284,43.80982],[-7.83591,43.73743],[-7.85605,43.79146],[-7.9172,43.78264],[-7.99921,43.7234],[-8.06467,43.72392],[-8.27761,43.57088],[-8.33444,43.57974],[-8.36033,43.46342],[-8.36105,43.41118],[-8.45745,43.39184],[-8.52507,43.36465],[-8.52435,43.3364],[-8.55097,43.32332],[-8.60996,43.3296],[-8.70635,43.305],[-8.78548,43.31914],[-8.82217,43.37354],[-8.87613,43.37407],[-8.8639,43.32908],[-8.92936,43.32699],[-8.93727,43.30553],[-8.99986,43.29558],[-8.99842,43.24477],[-9.03367,43.24267],[-9.06748,43.19916],[-9.14733,43.21018],[-9.2315,43.17032],[-9.25236,43.10417],[-9.30991,43.06004],[-9.27898,42.9822],[-9.30991,42.93113],[-9.2761,42.86051],[-9.21855,42.90163]]],[[[4.0628,40.12722],[4.06242,40.08499],[3.81287,40.08529],[3.81204,40.04344],[3.72908,40.0438],[3.72862,39.95842],[3.81266,39.9576],[3.81228,39.91644],[3.9609,39.91598],[4.19381,39.79131],[4.31503,39.79058],[4.31599,39.83293],[4.39874,39.83204],[4.39737,39.91858],[4.3158,39.91933],[4.31619,40.0434],[4.2319,40.04436],[4.23248,40.08478],[4.14915,40.08611],[4.14906,40.12552],[4.0628,40.12722]]]],"type":"MultiPolygon"}}, {"properties":{"name":"geoEuskadiren Ortoargazkiak","id":"geoEuskadi-ORTO","url":"https://www.geo.euskadi.eus/WMS_ORTOARGAZKIAK?LAYERS=ORTO_EGUNERATUENA_MAS_ACTUALIZADA&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"Eusko Jaurlaritza / Gobierno Vasco. geoEuskadi"},"type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[-3.11024,43.43771],[-3.15462,43.35277],[-3.15239,43.34796],[-3.15535,43.34091],[-3.15823,43.30694],[-3.19462,43.30082],[-3.21299,43.28858],[-3.26311,43.29807],[-3.29573,43.30269],[-3.33418,43.30382],[-3.34705,43.29782],[-3.34534,43.27958],[-3.37641,43.27658],[-3.39392,43.26558],[-3.39958,43.25258],[-3.41675,43.2527],[-3.45434,43.2372],[-3.44679,43.22019],[-3.43602,43.20608],[-3.43941,43.19179],[-3.43529,43.16838],[-3.41949,43.12993],[-3.38963,43.13469],[-3.25316,43.19316],[-3.22792,43.17151],[-3.21608,43.17076],[-3.18707,43.16638],[-3.16887,43.17514],[-3.14569,43.16763],[-3.16544,43.15536],[-3.17059,43.14546],[-3.18672,43.12141],[-3.18157,43.11301],[-3.15239,43.10086],[-3.16183,43.07215],[-3.14449,43.06287],[-3.15291,43.03188],[-3.18226,43.02962],[-3.17986,43.01783],[-3.1481,43.00314],[-3.08922,42.99812],[-3.0451,42.98092],[-3.0518,42.97162],[-3.01866,42.94901],[-2.99292,42.94034],[-3.03103,42.90967],[-3.09076,42.90489],[-3.10175,42.91683],[-3.14535,42.93908],[-3.20578,42.95316],[-3.22775,42.95291],[-3.24492,42.94411],[-3.28251,42.90389],[-3.29092,42.88376],[-3.28491,42.8698],[-3.22809,42.82562],[-3.13557,42.78545],[-3.15531,42.75332],[-3.1299,42.75332],[-3.08201,42.75432],[-3.04047,42.73579],[-2.98142,42.69972],[-2.96459,42.70565],[-2.93232,42.70616],[-2.93301,42.69859],[-2.92262,42.6996],[-2.9185,42.69695],[-2.90442,42.69259],[-2.90468,42.68635],[-2.8985,42.6765],[-2.90108,42.6731],[-2.90108,42.66729],[-2.89807,42.65366],[-2.89069,42.6476],[-2.87129,42.63819],[-2.86331,42.63712],[-2.85627,42.6368],[-2.844,42.62954],[-2.84512,42.62404],[-2.84966,42.61741],[-2.84881,42.61527],[-2.84451,42.61078],[-2.84254,42.60674],[-2.82349,42.61078],[-2.82512,42.60017],[-2.84254,42.58184],[-2.83825,42.56863],[-2.82529,42.55327],[-2.81877,42.55352],[-2.79825,42.56535],[-2.77508,42.57976],[-2.76169,42.61678],[-2.73903,42.61767],[-2.71397,42.60048],[-2.68702,42.59315],[-2.69028,42.56964],[-2.70933,42.5164],[-2.69543,42.51374],[-2.64856,42.48083],[-2.63792,42.48349],[-2.62316,42.49299],[-2.60084,42.49349],[-2.6053,42.48071],[-2.60153,42.47159],[-2.58522,42.46919],[-2.57183,42.48691],[-2.56857,42.49134],[-2.55707,42.48248],[-2.52909,42.47982],[-2.52497,42.48526],[-2.47707,42.48501],[-2.47004,42.49109],[-2.43519,42.48792],[-2.42133,42.48817],[-2.41845,42.48849],[-2.4218,42.50969],[-2.39021,42.51526],[-2.39468,42.54492],[-2.38704,42.54783],[-2.38729,42.56364],[-2.39348,42.59941],[-2.41897,42.60907],[-2.42455,42.60693],[-2.4509,42.57318],[-2.45974,42.58924],[-2.48772,42.59183],[-2.46034,42.62777],[-2.45253,42.63371],[-2.41605,42.66003],[-2.38936,42.65593],[-2.38395,42.63282],[-2.34447,42.63206],[-2.28979,42.65056],[-2.28344,42.65845],[-2.29185,42.67884],[-2.30824,42.67991],[-2.30988,42.70912],[-2.31546,42.73434],[-2.3043,42.7339],[-2.27134,42.74115],[-2.25932,42.74695],[-2.27468,42.78671],[-2.26576,42.79332],[-2.2316,42.83481],[-2.23005,42.84344],[-2.23872,42.86615],[-2.23941,42.88565],[-2.23658,42.89596],[-2.23065,42.9311],[-2.16019,42.93418],[-2.11676,42.93858],[-2.09873,42.94813],[-2.0886,42.96836],[-2.04844,42.97376],[-2.03556,42.97979],[-2.02732,42.98795],[-2.02372,43.00088],[-2.03453,43.02737],[-2.02578,43.02987],[-2.01428,43.05133],[-2.01736,43.0645],[-1.96741,43.08356],[-1.95213,43.09594],[-1.94617,43.09694],[-1.93626,43.10546],[-1.92029,43.11583],[-1.89969,43.13557],[-1.90175,43.14634],[-1.91497,43.16957],[-1.90776,43.20855],[-1.88201,43.20468],[-1.87231,43.21049],[-1.85987,43.21049],[-1.82262,43.22801],[-1.7924,43.24564],[-1.7882,43.27702],[-1.77644,43.28558],[-1.76253,43.28458],[-1.72747,43.29304],[-1.72639,43.29911],[-1.72863,43.29782],[-1.73545,43.31262],[-1.74054,43.31681],[-1.7368,43.32162],[-1.73698,43.3241],[-1.73794,43.32596],[-1.73661,43.33042],[-1.7412,43.33197],[-1.74462,43.33184],[-1.75024,43.33183],[-1.75253,43.33404],[-1.75099,43.33592],[-1.7523,43.33954],[-1.75307,43.34135],[-1.75438,43.3423],[-1.75747,43.34405],[-1.75884,43.34431],[-1.76037,43.34434],[-1.76221,43.34441],[-1.76532,43.34341],[-1.76648,43.34309],[-1.7679,43.34263],[-1.76938,43.34242],[-1.77191,43.34266],[-1.77416,43.34353],[-1.77609,43.34437],[-1.77665,43.34537],[-1.77674,43.34617],[-1.77697,43.34742],[-1.77753,43.34818],[-1.77845,43.34826],[-1.78009,43.34848],[-1.78118,43.34842],[-1.78309,43.34882],[-1.78438,43.34951],[-1.78622,43.35083],[-1.78691,43.35208],[-1.7867,43.35398],[-1.77851,43.36299],[-1.78802,43.36918],[-1.78946,43.37277],[-1.78906,43.37611],[-1.76571,43.38409],[-1.8091,43.40669],[-2.7502,43.4637],[-3.11024,43.43771]]],"type":"Polygon"}}, -{"properties":{"name":"IDEIB - Ortofoto 2021 (SITIBSA - scne.es)","id":"IDEIB","url":"https://ideib.caib.es/geoserveis/services/imatges/GOIB_Orto_IB/MapServer/WmsServer?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":true,"text":"Infraestructura de Dades Espacials de les Illes Balears"},"type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[1.20849,38.05675],[3.28491,38.90386],[4.93835,39.71987],[4.28466,40.7098],[1.8457,40.02341],[0.61523,39.05332],[1.20849,38.05675]]],"type":"Polygon"}}, +{"properties":{"name":"IDEIB - Ortofoto més recent de les Illes Balears","id":"IDEIB","url":"https://ideib.caib.es/geoserveis/services/imatges/GOIB_Orto_IB/MapServer/WmsServer?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=0&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":true,"text":"Infraestructura de Dades Espacials de les Illes Balears"},"type":"wms","category":"photo","best":true},"type":"Feature","geometry":{"coordinates":[[[1.20849,38.05675],[3.28491,38.90386],[4.93835,39.71987],[4.28466,40.7098],[1.8457,40.02341],[0.61523,39.05332],[1.20849,38.05675]]],"type":"Polygon"}}, {"properties":{"name":"Helsinki region orthophoto","id":"hri-orto","url":"https://kartta.hsy.fi/geoserver/ows?LAYERS=taustakartat_ja_aluejaot:Ortoilmakuva_2017&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"© Espoon, Helsingin ja Vantaan kaupungit, Kirkkonummen ja Nurmijärven kunnat sekä HSL ja HSY","url":"https://hri.fi/data/en_GB/dataset/paakaupunkiseudun-ortokuva-2017"},"type":"wms","category":"photo","max_zoom":19,"best":true},"type":"Feature","geometry":{"coordinates":[[[24.26948,59.8593],[25.19577,60.08813],[25.27954,60.2943],[25.18066,60.34122],[25.17517,60.45248],[25.06943,60.44096],[24.9884,60.35073],[24.90051,60.38197],[25.00625,60.54175],[24.76387,60.60079],[24.50363,60.49038],[24.55307,60.36703],[24.26674,60.22549],[24.26948,59.8593]]],"type":"Polygon"}}, {"properties":{"name":"MML Orthophoto","id":"mml-orto","url":"https://tiles.kartat.kapsi.fi/ortokuva/{zoom}/{x}/{y}.jpg","attribution":{"required":true,"text":"© Maanmittauslaitos","url":"https://www.maanmittauslaitos.fi/en"},"type":"tms","category":"photo","min_zoom":2,"max_zoom":19,"best":true},"type":"Feature","geometry":{"coordinates":[[[27.96569,70.0988],[27.57431,70.07728],[27.10876,69.93548],[26.70913,69.97549],[26.45507,69.94207],[25.87142,69.6671],[25.94833,69.61024],[25.83023,69.55323],[25.66955,69.20794],[25.73822,69.01797],[25.60089,68.90309],[25.45806,68.91199],[25.11749,68.80699],[25.07354,68.64355],[24.88128,68.62003],[23.97491,68.84568],[23.74969,68.8308],[23.63433,68.71645],[23.18939,68.68053],[22.52197,68.7553],[21.63894,69.28191],[21.26953,69.31783],[20.94131,69.21622],[21.08963,69.09307],[21.05941,69.04352],[20.72296,69.12491],[20.54443,69.0558],[20.84655,68.97416],[20.81634,68.91742],[21.38754,68.68461],[22.04734,68.47066],[22.80212,68.35464],[23.12072,68.13169],[23.5437,67.9633],[23.44757,67.8393],[23.48602,67.59352],[23.36517,67.46545],[23.71124,67.41592],[23.72772,67.32186],[23.54644,67.26885],[23.53128,67.16724],[23.89251,66.86863],[23.84582,66.57775],[23.61843,66.44562],[23.67171,66.20303],[23.87191,66.14551],[24.09988,65.87247],[24.1658,65.66959],[24.11636,65.39143],[21.37939,63.68037],[20.17639,63.29787],[19.08325,60.16064],[20.22033,59.44786],[22.29125,59.44507],[25.82336,59.933],[27.52075,60.23435],[27.83386,60.53229],[29.29641,61.26165],[31.20803,62.44759],[31.62826,62.90585],[31.2635,63.22106],[29.99605,63.75387],[30.28656,63.81704],[30.58319,64.0782],[30.5104,64.26428],[30.09979,64.39218],[30.02563,64.58736],[30.16845,64.63329],[30.09429,64.79518],[29.78393,64.79811],[29.65347,64.89733],[29.65759,65.05939],[29.91027,65.09527],[29.93225,65.20895],[29.72076,65.27853],[29.91577,65.63788],[30.1863,65.66223],[29.9913,66.09771],[29.07119,66.91983],[30.11077,67.63431],[29.3486,68.08099],[28.67568,68.20166],[28.46547,68.54039],[28.72375,68.72642],[28.82675,68.87341],[28.44985,68.90792],[28.95996,69.05089],[28.83324,69.10563],[28.87207,69.22132],[29.36096,69.46526],[29.15634,69.69667],[28.38455,69.83488],[28.35845,69.88312],[28.17169,69.92511],[28.00415,70.01495],[27.96569,70.0988]]],"type":"Polygon"}}, {"properties":{"name":"MML Background Map","id":"mml-tausta","url":"https://tiles.kartat.kapsi.fi/taustakartta/{zoom}/{x}/{y}.jpg","attribution":{"required":true,"text":"© Maanmittauslaitos","url":"https://www.maanmittauslaitos.fi/en"},"type":"tms","category":"map","min_zoom":2,"max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[27.96569,70.0988],[27.57431,70.07728],[27.10876,69.93548],[26.70913,69.97549],[26.45507,69.94207],[25.87142,69.6671],[25.94833,69.61024],[25.83023,69.55323],[25.66955,69.20794],[25.73822,69.01797],[25.60089,68.90309],[25.45806,68.91199],[25.11749,68.80699],[25.07354,68.64355],[24.88128,68.62003],[23.97491,68.84568],[23.74969,68.8308],[23.63433,68.71645],[23.18939,68.68053],[22.52197,68.7553],[21.63894,69.28191],[21.26953,69.31783],[20.94131,69.21622],[21.08963,69.09307],[21.05941,69.04352],[20.72296,69.12491],[20.54443,69.0558],[20.84655,68.97416],[20.81634,68.91742],[21.38754,68.68461],[22.04734,68.47066],[22.80212,68.35464],[23.12072,68.13169],[23.5437,67.9633],[23.44757,67.8393],[23.48602,67.59352],[23.36517,67.46545],[23.71124,67.41592],[23.72772,67.32186],[23.54644,67.26885],[23.53128,67.16724],[23.89251,66.86863],[23.84582,66.57775],[23.61843,66.44562],[23.67171,66.20303],[23.87191,66.14551],[24.09988,65.87247],[24.1658,65.66959],[24.11636,65.39143],[21.37939,63.68037],[20.17639,63.29787],[19.08325,60.16064],[20.22033,59.44786],[22.29125,59.44507],[25.82336,59.933],[27.52075,60.23435],[27.83386,60.53229],[29.29641,61.26165],[31.20803,62.44759],[31.62826,62.90585],[31.2635,63.22106],[29.99605,63.75387],[30.28656,63.81704],[30.58319,64.0782],[30.5104,64.26428],[30.09979,64.39218],[30.02563,64.58736],[30.16845,64.63329],[30.09429,64.79518],[29.78393,64.79811],[29.65347,64.89733],[29.65759,65.05939],[29.91027,65.09527],[29.93225,65.20895],[29.72076,65.27853],[29.91577,65.63788],[30.1863,65.66223],[29.9913,66.09771],[29.07119,66.91983],[30.11077,67.63431],[29.3486,68.08099],[28.67568,68.20166],[28.46547,68.54039],[28.72375,68.72642],[28.82675,68.87341],[28.44985,68.90792],[28.95996,69.05089],[28.83324,69.10563],[28.87207,69.22132],[29.36096,69.46526],[29.15634,69.69667],[28.38455,69.83488],[28.35845,69.88312],[28.17169,69.92511],[28.00415,70.01495],[27.96569,70.0988]]],"type":"Polygon"}}, @@ -241,9 +245,9 @@ {"properties":{"name":"Hautes-Alpes 2010","id":"fr.dpt.05.2010","url":"https://wms.openstreetmap.fr/tms/1.0.0/paca05_2010/{zoom}/{x}/{y}","attribution":{"required":true,"text":"ORTHO 2010 © PACA-04-05-84","url":"http://www.crige-paca.org/geoportail/geocatalogue.html?id_lot_donnee_carto=206"},"type":"tms","category":"historicphoto","min_zoom":2,"max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[6.22101,45.13071],[6.18393,45.0119],[6.2368,44.98374],[6.31027,44.98423],[6.3089,44.93856],[6.34735,44.9371],[6.34186,44.85733],[6.30478,44.88458],[6.28143,44.88653],[6.23955,44.86852],[6.11526,44.87242],[6.0569,44.83347],[6.03699,44.84759],[6.01364,44.84857],[5.93605,44.81448],[5.93399,44.79499],[5.94566,44.79548],[5.94292,44.76916],[5.89348,44.77111],[5.89142,44.76087],[5.81863,44.76331],[5.78979,44.71747],[5.78842,44.69843],[5.80147,44.69892],[5.80147,44.68428],[5.78705,44.68281],[5.78705,44.66426],[5.73761,44.66572],[5.71289,44.64961],[5.70053,44.65693],[5.63599,44.6584],[5.63461,44.62566],[5.59547,44.57873],[5.59273,44.53372],[5.60646,44.52344],[5.62843,44.52344],[5.62843,44.5063],[5.61745,44.50679],[5.61539,44.47985],[5.56389,44.48083],[5.54123,44.49944],[5.47806,44.50091],[5.47806,44.50924],[5.45334,44.50973],[5.44853,44.43819],[5.41351,44.43819],[5.41077,44.4019],[5.42175,44.40141],[5.42107,44.35822],[5.45883,44.35724],[5.45814,44.33711],[5.48355,44.33711],[5.48218,44.32827],[5.50896,44.32729],[5.50964,44.33711],[5.53368,44.33711],[5.5323,44.31844],[5.59479,44.31648],[5.59479,44.29879],[5.62157,44.29781],[5.61882,44.27274],[5.64491,44.25159],[5.66688,44.25159],[5.66482,44.19993],[5.64217,44.19944],[5.63942,44.17974],[5.62706,44.18122],[5.62569,44.17285],[5.93948,44.16349],[5.94704,44.34497],[6.01089,44.34349],[6.01364,44.38669],[6.20109,44.38178],[6.19904,44.3376],[6.26221,44.33809],[6.26633,44.38031],[6.32812,44.37884],[6.3295,44.42397],[6.70647,44.41171],[6.71196,44.50091],[6.77513,44.49847],[6.77856,44.544],[6.84311,44.54155],[6.84654,44.58509],[6.90697,44.58411],[6.91246,44.63006],[6.9873,44.62811],[6.98524,44.63837],[6.95915,44.63983],[6.9619,44.65302],[6.97563,44.654],[6.97632,44.67207],[6.98936,44.67158],[6.99074,44.68135],[7.0388,44.6794],[7.03812,44.66914],[7.09167,44.66768],[7.09167,44.69599],[7.07725,44.69599],[7.08069,44.72234],[7.04292,44.7243],[7.0443,44.74283],[7.03125,44.74186],[7.03468,44.77842],[7.01958,44.7794],[7.02988,44.8325],[7.00928,44.83299],[7.01408,44.85198],[6.96258,44.8549],[6.9619,44.86317],[6.93855,44.86366],[6.93855,44.87242],[6.92757,44.87242],[6.90079,44.85636],[6.86165,44.85782],[6.86371,44.8656],[6.83899,44.86658],[6.84036,44.87631],[6.81427,44.8768],[6.81496,44.89528],[6.79024,44.89528],[6.79092,44.9055],[6.76346,44.90501],[6.76758,44.94925],[6.77925,44.94974],[6.782,44.96771],[6.75659,44.98811],[6.75934,45.02258],[6.68518,45.02647],[6.68655,45.05412],[6.67282,45.05364],[6.67625,45.07934],[6.6378,45.10891],[6.63917,45.12635],[6.56296,45.12974],[6.53686,45.11133],[6.49773,45.11327],[6.49773,45.10261],[6.47095,45.10212],[6.4682,45.06722],[6.37962,45.07061],[6.38374,45.10697],[6.35834,45.12587],[6.31851,45.12684],[6.31988,45.11812],[6.28212,45.11908],[6.28281,45.12877],[6.22101,45.13071]]],"type":"Polygon"}}, {"properties":{"name":"Loire-Atlantique - Orthophotos 2016 - 10 cm","id":"Loire_Atlantique-Orthophotos-2016","url":"https://wms-vuduciel2.makina-corpus.net/geoserver/wms?LAYERS=cg44:ortho44-2016&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":true,"text":"© Loire-Atlantique ouverture des données publiques","url":"http://data2.loire-atlantique.fr/licences/"},"type":"wms","category":"photo","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-1.48638,46.87691],[-1.48622,46.89724],[-1.51142,46.91371],[-1.50222,46.92973],[-1.50507,46.94439],[-1.52961,46.97252],[-1.52764,47.00541],[-1.49213,47.02722],[-1.48644,46.99943],[-1.49235,46.98433],[-1.48775,46.93063],[-1.47504,46.9176],[-1.45014,46.91186],[-1.3491,46.94446],[-1.33918,46.969],[-1.34972,47.02397],[-1.28524,47.02185],[-1.26727,47.06325],[-1.21582,47.02904],[-1.14453,47.01636],[-1.09989,47.03199],[-1.09431,47.0717],[-1.14081,47.08056],[-1.15568,47.10504],[-1.20838,47.10968],[-1.20652,47.12402],[-1.1563,47.15818],[-1.14143,47.1763],[-1.16498,47.24957],[-1.20032,47.26935],[-1.23379,47.26093],[-1.28339,47.32736],[-0.97157,47.35845],[-0.92694,47.37482],[-0.9009,47.39874],[-0.93376,47.43859],[-0.93686,47.47715],[-0.94863,47.50103],[-1.0317,47.51778],[-1.13275,47.5161],[-1.13585,47.55628],[-1.0317,47.55001],[-0.98459,47.58598],[-0.99265,47.6027],[-1.09121,47.6332],[-1.13151,47.63654],[-1.15196,47.69332],[-1.18668,47.73462],[-1.21086,47.7317],[-1.22635,47.73628],[-1.21643,47.75838],[-1.23007,47.78587],[-1.346,47.81086],[-1.38133,47.84415],[-1.49911,47.84166],[-1.49601,47.81752],[-1.63735,47.77463],[-1.67455,47.72544],[-1.83077,47.72419],[-1.98016,47.70751],[-2.08121,47.66578],[-2.10055,47.65141],[-2.11141,47.62873],[-2.09981,47.62005],[-2.1001,47.61123],[-2.11783,47.60126],[-2.12109,47.54674],[-2.23223,47.51499],[-2.24836,47.52384],[-2.31063,47.53021],[-2.50573,47.50812],[-2.63913,47.41678],[-2.54862,47.28575],[-2.41658,47.25336],[-2.29694,47.22769],[-2.19341,47.25546],[-2.19217,47.16914],[-2.22627,47.17124],[-2.2678,47.12656],[-2.07067,47.08521],[-1.95475,46.98084],[-1.89276,46.94234],[-1.73468,46.87246],[-1.54437,46.84873],[-1.48638,46.87691]]],"type":"Polygon"}}, {"properties":{"name":"Lyon Orthophoto 2018 (8cm)","id":"orthophoto_lyon_2018","url":"https://imagerie.data.grandlyon.com/geoserver/grandlyon/ows?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=ortho_2018&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":true,"text":"Métropole de Lyon","url":"https://data.grandlyon.com/portail/fr/jeux-de-donnees/orthophotographie-2018-de-la-metropole-de-lyon--format-ecw/info"},"type":"wms","category":"historicphoto","min_zoom":10,"max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[4.67728,45.9607],[4.66438,45.54676],[4.88308,45.54291],[4.8848,45.59698],[5.16666,45.59194],[5.17284,45.74596],[5.10796,45.74692],[5.11311,45.8814],[4.90677,45.88499],[4.90849,45.9213],[4.84394,45.92202],[4.84531,45.95831],[4.67728,45.9607]]],"type":"Polygon"}}, -{"properties":{"name":"Lyon Orthophoto 2012-03 10cm","id":"orthophoto_lyon_2012","url":"https://imagerie.data.grandlyon.com/geoserver/grandlyon/ows?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=ortho_2012&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":true,"text":"Grand Lyon Smart Data DSIT 2012","url":"https://data.grandlyon.com/portail/fr/jeux-de-donnees/orthophotographie-2012-metropole-lyon/info"},"type":"wms","category":"historicphoto"},"type":"Feature","geometry":{"coordinates":[[[4.69049,45.54652],[4.83131,45.54652],[4.83131,45.57131],[4.88344,45.57131],[4.88344,45.59745],[5.16622,45.59745],[5.16622,45.74533],[5.10793,45.74533],[5.10793,45.88145],[4.90698,45.88145],[4.90698,45.92107],[4.84377,45.92107],[4.84377,45.94011],[4.71543,45.94011],[4.71543,45.87018],[4.67458,45.87018],[4.67458,45.7178],[4.69567,45.7178],[4.69049,45.54652]]],"type":"Polygon"}}, -{"properties":{"name":"Lyon Orthophoto 2015 (8cm)","id":"orthophoto_lyon","url":"https://wms.openstreetmap.fr/tms/1.0.0/lyon/{zoom}/{x}/{y}","attribution":{"required":true,"text":"Métropole de Lyon DINSI 2015","url":"https://data.grandlyon.com/portail/fr/jeux-de-donnees/orthophotographie-2015-metropole-lyon-format-ecw/info"},"type":"tms","category":"historicphoto","min_zoom":2,"max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[4.66489,45.54688],[4.88253,45.54348],[4.88435,45.59745],[5.16623,45.59242],[5.17217,45.74532],[5.10793,45.74653],[5.11305,45.88145],[4.90698,45.88508],[4.90822,45.92106],[4.84377,45.92212],[4.84497,45.9581],[4.67729,45.96069],[4.66489,45.54688]]],"type":"Polygon"}}, -{"properties":{"name":"Lyon Orthophoto 2022 (5cm)","id":"orthophoto_lyon_2022","url":"https://imagerie.data.grandlyon.com/geoserver/grandlyon/ows?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=ortho_2022&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":true,"text":"CRAIG - Métropole du Grand Lyon 2022","url":"https://data.grandlyon.com/portail/fr/jeux-de-donnees/orthophotographie-2022-de-la-metropole-de-lyon/info"},"type":"wms","category":"photo","min_zoom":10,"max_zoom":22,"best":true},"type":"Feature","geometry":{"coordinates":[[[4.74178,45.95976],[4.67728,45.9607],[4.66542,45.5649],[4.69906,45.56443],[4.69932,45.56776],[4.72876,45.56737],[4.72876,45.56617],[4.73301,45.56611],[4.73305,45.56419],[4.75507,45.56347],[4.75593,45.55451],[4.7867,45.55403],[4.787,45.55719],[4.79331,45.55713],[4.79356,45.56299],[4.80622,45.56293],[4.80687,45.58071],[4.84536,45.58011],[4.84498,45.57119],[4.87064,45.57083],[4.87128,45.58864],[4.88403,45.58969],[4.88441,45.5975],[4.90986,45.59753],[4.91038,45.60612],[5.0096,45.60444],[5.00986,45.6132],[5.0514,45.61278],[5.05226,45.63043],[5.03938,45.63073],[5.03981,45.63967],[5.05251,45.63961],[5.05312,45.64855],[5.06316,45.64825],[5.0665,45.66631],[5.07989,45.66601],[5.11972,45.69467],[5.1574,45.6933],[5.15839,45.7235],[5.15191,45.72365],[5.15199,45.72764],[5.14577,45.72782],[5.14607,45.73683],[5.13328,45.73707],[5.13341,45.7419],[5.11916,45.74226],[5.11912,45.74399],[5.10551,45.74417],[5.10607,45.75555],[5.09539,45.75579],[5.09766,45.81663],[5.03337,45.81783],[5.0332,45.81089],[4.93025,45.81269],[4.93091,45.84803],[4.92546,45.84813],[4.9255,45.84878],[4.91862,45.84888],[4.919,45.86082],[4.91639,45.86088],[4.91698,45.87592],[4.90668,45.87609],[4.90688,45.88267],[4.89953,45.88286],[4.8996,45.88453],[4.89606,45.8846],[4.89608,45.88528],[4.8941,45.8853],[4.89426,45.89024],[4.89139,45.89032],[4.89168,45.90271],[4.88519,45.90283],[4.88519,45.90345],[4.88182,45.9035],[4.8818,45.90825],[4.8753,45.90837],[4.87535,45.91027],[4.8696,45.91042],[4.86964,45.91199],[4.86527,45.91209],[4.8653,45.91278],[4.85636,45.91292],[4.85651,45.91771],[4.8395,45.91814],[4.8395,45.92219],[4.79221,45.92295],[4.7928,45.94094],[4.74122,45.94174],[4.74178,45.95976]]],"type":"Polygon"}}, +{"properties":{"name":"Lyon Orthophoto 2012 (10cm)","id":"orthophoto_lyon_2012","url":"https://imagerie.data.grandlyon.com/geoserver/grandlyon/ows?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=ortho_2012&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":true,"text":"Grand Lyon Smart Data DSIT 2012","url":"https://data.grandlyon.com/portail/fr/jeux-de-donnees/orthophotographie-2012-metropole-lyon/info"},"type":"wms","category":"historicphoto"},"type":"Feature","geometry":{"coordinates":[[[4.69049,45.54652],[4.83131,45.54652],[4.83131,45.57131],[4.88344,45.57131],[4.88344,45.59745],[5.16622,45.59745],[5.16622,45.74533],[5.10793,45.74533],[5.10793,45.88145],[4.90698,45.88145],[4.90698,45.92107],[4.84377,45.92107],[4.84377,45.94011],[4.71543,45.94011],[4.71543,45.87018],[4.67458,45.87018],[4.67458,45.7178],[4.69567,45.7178],[4.69049,45.54652]]],"type":"Polygon"}}, +{"properties":{"name":"Lyon Orthophoto 2015 (8cm)","id":"orthophoto_lyon_2015","url":"https://wms.openstreetmap.fr/tms/1.0.0/lyon/{zoom}/{x}/{y}","attribution":{"required":true,"text":"Métropole de Lyon DINSI 2015","url":"https://data.grandlyon.com/portail/fr/jeux-de-donnees/orthophotographie-2015-metropole-lyon-format-ecw/info"},"type":"tms","category":"historicphoto","min_zoom":2,"max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[4.66489,45.54688],[4.88253,45.54348],[4.88435,45.59745],[5.16623,45.59242],[5.17217,45.74532],[5.10793,45.74653],[5.11305,45.88145],[4.90698,45.88508],[4.90822,45.92106],[4.84377,45.92212],[4.84497,45.9581],[4.67729,45.96069],[4.66489,45.54688]]],"type":"Polygon"}}, +{"properties":{"name":"Lyon Orthophoto 2022 (5cm)","id":"orthophoto_lyon_2022","url":"https://imagerie.data.grandlyon.com/geoserver/grandlyon/ows?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=ortho_2022&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":true,"text":"CRAIG - Métropole du Grand Lyon 2022","url":"https://data.grandlyon.com/portail/fr/jeux-de-donnees/orthophotographie-2022-de-la-metropole-de-lyon--format-ecw/info"},"type":"wms","category":"photo","min_zoom":10,"max_zoom":22,"best":true},"type":"Feature","geometry":{"coordinates":[[[4.74178,45.95976],[4.67728,45.9607],[4.66542,45.5649],[4.69906,45.56443],[4.69932,45.56776],[4.72876,45.56737],[4.72876,45.56617],[4.73301,45.56611],[4.73305,45.56419],[4.75507,45.56347],[4.75593,45.55451],[4.7867,45.55403],[4.787,45.55719],[4.79331,45.55713],[4.79356,45.56299],[4.80622,45.56293],[4.80687,45.58071],[4.84536,45.58011],[4.84498,45.57119],[4.87064,45.57083],[4.87128,45.58864],[4.88403,45.58969],[4.88441,45.5975],[4.90986,45.59753],[4.91038,45.60612],[5.0096,45.60444],[5.00986,45.6132],[5.0514,45.61278],[5.05226,45.63043],[5.03938,45.63073],[5.03981,45.63967],[5.05251,45.63961],[5.05312,45.64855],[5.06316,45.64825],[5.0665,45.66631],[5.07989,45.66601],[5.11972,45.69467],[5.1574,45.6933],[5.15839,45.7235],[5.15191,45.72365],[5.15199,45.72764],[5.14577,45.72782],[5.14607,45.73683],[5.13328,45.73707],[5.13341,45.7419],[5.11916,45.74226],[5.11912,45.74399],[5.10551,45.74417],[5.10607,45.75555],[5.09539,45.75579],[5.09766,45.81663],[5.03337,45.81783],[5.0332,45.81089],[4.93025,45.81269],[4.93091,45.84803],[4.92546,45.84813],[4.9255,45.84878],[4.91862,45.84888],[4.919,45.86082],[4.91639,45.86088],[4.91698,45.87592],[4.90668,45.87609],[4.90688,45.88267],[4.89953,45.88286],[4.8996,45.88453],[4.89606,45.8846],[4.89608,45.88528],[4.8941,45.8853],[4.89426,45.89024],[4.89139,45.89032],[4.89168,45.90271],[4.88519,45.90283],[4.88519,45.90345],[4.88182,45.9035],[4.8818,45.90825],[4.8753,45.90837],[4.87535,45.91027],[4.8696,45.91042],[4.86964,45.91199],[4.86527,45.91209],[4.8653,45.91278],[4.85636,45.91292],[4.85651,45.91771],[4.8395,45.91814],[4.8395,45.92219],[4.79221,45.92295],[4.7928,45.94094],[4.74122,45.94174],[4.74178,45.95976]]],"type":"Polygon"}}, {"properties":{"name":"Mulhouse - 2018","id":"Mulhouse_2018","url":"https://wms.openstreetmap.fr/tms/1.0.0/mulhouse_2018/{zoom}/{x}/{y}","attribution":{"required":true,"text":"Mulhouse Alsace Agglomération 2018","url":"https://data.mulhouse-alsace.fr/explore/dataset/m2a_orthophotographie-2018/information/"},"type":"tms","category":"photo","max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[7.53731,47.78041],[7.57439,47.84128],[7.57301,47.85602],[7.55035,47.86201],[7.53525,47.84588],[7.5016,47.85141],[7.45972,47.84174],[7.4556,47.83252],[7.4453,47.83114],[7.43088,47.84128],[7.41549,47.83831],[7.39174,47.84727],[7.38007,47.83851],[7.33475,47.83989],[7.34024,47.89563],[7.31758,47.91082],[7.28737,47.89977],[7.28462,47.88412],[7.24686,47.88228],[7.23724,47.87629],[7.23312,47.86431],[7.2139,47.86524],[7.183,47.85786],[7.17613,47.84819],[7.21115,47.82284],[7.18986,47.81085],[7.19948,47.78179],[7.16789,47.77025],[7.183,47.73517],[7.17682,47.72362],[7.19467,47.69451],[7.21802,47.68527],[7.24548,47.68527],[7.25166,47.69497],[7.2448,47.70376],[7.2551,47.71392],[7.27844,47.713],[7.27982,47.70237],[7.26814,47.69405],[7.26883,47.68434],[7.28531,47.67094],[7.30247,47.66308],[7.32376,47.65984],[7.33337,47.66724],[7.3732,47.65383],[7.39242,47.65475],[7.40822,47.6603],[7.39723,47.67648],[7.42744,47.68666],[7.47551,47.69821],[7.5222,47.69636],[7.55104,47.71531],[7.56134,47.73517],[7.53731,47.78041]]],"type":"Polygon"}}, {"properties":{"name":"Nancy - Orthophoto - 2016","id":"GrandNancy_Orthophotographie_2016","url":"https://wms.openstreetmap.fr/tms/1.0.0/nancy_2016/{zoom}/{x}/{y}","attribution":{"required":true,"text":"GrandNancy Orthophotographie 2016","url":"https://wiki.openstreetmap.org/wiki/Nancy/Orthophotographie"},"type":"tms","category":"photo","max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[6.0672,48.70184],[6.06377,48.65106],[6.15097,48.59977],[6.22101,48.59886],[6.24435,48.61566],[6.29723,48.61975],[6.30478,48.73536],[6.15028,48.7503],[6.0672,48.70184]]],"type":"Polygon"}}, {"properties":{"name":"Nancy - Orthophoto","id":"GrandNancy_Orthophotographie","url":"https://wms.openstreetmap.fr/tms/1.0.0/nancy/{zoom}/{x}/{y}","attribution":{"required":true,"text":"GrandNancy Orthophotographie 2016","url":"https://wiki.openstreetmap.org/wiki/Nancy/Orthophotographie"},"type":"tms","category":"photo","max_zoom":22,"best":true},"type":"Feature","geometry":{"coordinates":[[[6.0672,48.70184],[6.06377,48.65106],[6.15097,48.59977],[6.22101,48.59886],[6.24435,48.61566],[6.29723,48.61975],[6.30478,48.73536],[6.15028,48.7503],[6.0672,48.70184]]],"type":"Polygon"}}, @@ -517,7 +521,8 @@ {"properties":{"name":"Broward County Orthoimagery (2022)","id":"Broward_Ortho_2022","url":"https://gisweb-adapters.bcpa.net/arcgis/rest/services/BCPA_WEB_BASE_22/MapServer/export?f=image&format=jpg&layers=show,3&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Broward County Property Appraiser’s Office","url":"https://gisweb-adapters.bcpa.net/bcpawebmap_ex/bcpawebmap.aspx"},"type":"wms","category":"historicphoto","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-80.10267,25.97516],[-80.07117,26.33617],[-80.21877,26.33457],[-80.21867,26.34904],[-80.2674,26.34916],[-80.26732,26.36364],[-80.29687,26.36379],[-80.29766,26.18933],[-80.32984,26.18945],[-80.32993,26.17499],[-80.34598,26.17504],[-80.34602,26.16035],[-80.36222,26.16041],[-80.36224,26.14592],[-80.42654,26.14587],[-80.42648,26.16039],[-80.4589,26.1605],[-80.45599,25.95687],[-80.2948,25.95681],[-80.29473,25.97032],[-80.10267,25.97516]]],"type":"Polygon"}}, {"properties":{"name":"Broward County Orthoimagery (2023)","id":"Broward_Ortho_2023","url":"https://gisweb-adapters.bcpa.net/arcgis/rest/services/BCPA_WEB_BASE_23/MapServer/export?f=image&format=jpg&layers=show,3&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Broward County Property Appraiser’s Office","url":"https://gisweb-adapters.bcpa.net/bcpawebmap_ex/bcpawebmap.aspx"},"type":"wms","category":"photo","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-80.10267,25.97516],[-80.07117,26.33617],[-80.21877,26.33457],[-80.21867,26.34904],[-80.2674,26.34916],[-80.26732,26.36364],[-80.29687,26.36379],[-80.29766,26.18933],[-80.32984,26.18945],[-80.32993,26.17499],[-80.34598,26.17504],[-80.34602,26.16035],[-80.36222,26.16041],[-80.36224,26.14592],[-80.42654,26.14587],[-80.42648,26.16039],[-80.4589,26.1605],[-80.45599,25.95687],[-80.2948,25.95681],[-80.29473,25.97032],[-80.10267,25.97516]]],"type":"Polygon"}}, {"properties":{"name":"Charlotte County Orthoimagery (2020)","id":"Charlotte_Ortho_2020","url":"https://agis.charlottecountyfl.gov/arcgis/rest/services/Aerials/Raster2020/MapServer/export?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Charlotte County Geographic Information Systems","url":"https://www.charlottecountyfl.gov/gis/"},"type":"wms","category":"photo","max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[-82.26451,26.94701],[-82.38809,26.94605],[-82.26226,26.71081],[-82.06476,26.7683],[-81.55921,26.76777],[-81.55814,27.04285],[-82.2647,27.0433],[-82.26451,26.94701]]],"type":"Polygon"}}, -{"properties":{"name":"Charlotte County Orthoimagery (2022)","id":"Charlotte_Ortho_2022","url":"https://agis.charlottecountyfl.gov/arcgis/rest/services/Aerials/Aerials2022_Post_IAN/MapServer/export?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Charlotte County Geographic Information Systems","url":"https://www.charlottecountyfl.gov/gis/"},"type":"wms","category":"photo","max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[-82.26451,26.94701],[-82.38809,26.94605],[-82.26123,26.69942],[-82.06476,26.7683],[-82.00362,26.76846],[-82.00362,26.79597],[-81.98823,26.79592],[-81.98828,26.82343],[-81.97289,26.82343],[-81.97286,26.83721],[-81.95752,26.83719],[-81.95746,26.87845],[-81.94222,26.87854],[-81.94215,26.98851],[-81.98825,26.98844],[-81.98817,27.04348],[-82.2647,27.0433],[-82.26451,26.94701]]],"type":"Polygon"}}, +{"properties":{"name":"Charlotte County Orthoimagery (2022)","id":"Charlotte_Ortho_2022","url":"https://agis.charlottecountyfl.gov/arcgis/rest/services/Aerials/Aerials2022_Post_IAN/MapServer/export?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Charlotte County Geographic Information Systems","url":"https://www.charlottecountyfl.gov/gis/"},"type":"wms","category":"historicphoto","max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[-82.26451,26.94701],[-82.38809,26.94605],[-82.26123,26.69942],[-82.06476,26.7683],[-82.00362,26.76846],[-82.00362,26.79597],[-81.98823,26.79592],[-81.98828,26.82343],[-81.97289,26.82343],[-81.97286,26.83721],[-81.95752,26.83719],[-81.95746,26.87845],[-81.94222,26.87854],[-81.94215,26.98851],[-81.98825,26.98844],[-81.98817,27.04348],[-82.2647,27.0433],[-82.26451,26.94701]]],"type":"Polygon"}}, +{"properties":{"name":"Charlotte County Orthoimagery (2023)","id":"Charlotte_Ortho_2023","url":"https://agis.charlottecountyfl.gov/arcgis/rest/services/Aerials/Aerials2023/MapServer/export?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Charlotte County Geographic Information Systems","url":"https://www.charlottecountyfl.gov/gis/"},"type":"wms","category":"photo","max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[-82.26451,26.94701],[-82.38809,26.94605],[-82.26123,26.69942],[-82.06476,26.7683],[-82.00362,26.76846],[-82.00362,26.79597],[-81.98823,26.79592],[-81.98828,26.82343],[-81.97289,26.82343],[-81.97286,26.83721],[-81.95752,26.83719],[-81.95746,26.87845],[-81.94222,26.87854],[-81.94215,26.98851],[-81.98825,26.98844],[-81.98817,27.04348],[-82.2647,27.0433],[-82.26451,26.94701]]],"type":"Polygon"}}, {"properties":{"name":"Citrus County Orthoimagery (2020)","id":"Citrus_Ortho_2020","url":"https://gis.citruspa.org/arcgisweb/rest/services/Tyler/2020_Aerials/MapServer/export?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Citrus County Property Appraiser's Office","url":"https://www.citruspa.org/"},"type":"wms","category":"photo","max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[-82.78572,29.0214],[-82.7192,28.69176],[-82.42429,28.69321],[-82.42452,28.65186],[-82.25311,28.65241],[-82.25289,28.67976],[-82.23799,28.67987],[-82.23733,28.72122],[-82.22215,28.72122],[-82.22193,28.73484],[-82.20637,28.73517],[-82.20626,28.74873],[-82.17513,28.74873],[-82.17524,28.77641],[-82.15979,28.77652],[-82.15985,28.81754],[-82.1753,28.81748],[-82.17538,28.83132],[-82.19108,28.83135],[-82.19097,28.84508],[-82.2067,28.84502],[-82.20667,28.85881],[-82.22229,28.85883],[-82.22238,28.87248],[-82.23755,28.8724],[-82.23805,28.90013],[-82.26901,28.89957],[-82.26923,28.92714],[-82.28468,28.92659],[-82.28512,28.95449],[-82.30046,28.9546],[-82.30069,28.98216],[-82.31625,28.98205],[-82.31614,28.99595],[-82.34748,28.99595],[-82.34793,29.00973],[-82.37883,29.00929],[-82.37905,29.02329],[-82.3945,29.0234],[-82.39461,29.03697],[-82.42618,29.03708],[-82.42602,29.05086],[-82.45742,29.05058],[-82.45764,29.06487],[-82.50444,29.06442],[-82.50444,29.05053],[-82.58281,29.05008],[-82.58236,29.03624],[-82.73893,29.03574],[-82.7387,29.02196],[-82.78572,29.0214]]],"type":"Polygon"}}, {"properties":{"name":"Collier County Orthoimagery (2022)","id":"Collier_Ortho_2022","url":"https://ags2.colliercountyfl.gov/imagery/rest/services/Orthos2022/ImageServer/exportImage?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Collier County Information Technology GIS Team","url":"https://hub-collierbcc.opendata.arcgis.com/"},"type":"wms","category":"historicphoto","max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[-80.85707,26.31698],[-81.22402,26.31665],[-81.22445,26.53704],[-81.59119,26.536],[-81.59079,26.48124],[-81.71292,26.48063],[-81.71234,26.42575],[-81.89595,26.42477],[-81.76936,25.76545],[-80.85773,25.76874],[-80.85707,26.31698]]],"type":"Polygon"}}, {"properties":{"name":"Collier County Orthoimagery (Latest)","id":"Collier_Ortho_Latest","url":"https://ags2.colliercountyfl.gov/imagery/rest/services/CurrentOrthos/ImageServer/exportImage?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Collier County Information Technology GIS Team","url":"https://hub-collierbcc.opendata.arcgis.com/"},"type":"wms","category":"photo","max_zoom":22},"type":"Feature","geometry":{"coordinates":[[[-80.85707,26.31698],[-81.22402,26.31665],[-81.22445,26.53704],[-81.59119,26.536],[-81.59079,26.48124],[-81.71292,26.48063],[-81.71234,26.42575],[-81.89595,26.42477],[-81.76936,25.76545],[-80.85773,25.76874],[-80.85707,26.31698]]],"type":"Polygon"}}, @@ -550,17 +555,19 @@ {"properties":{"name":"City of Naples Orthoimagery (2020)","id":"Naples_FL_Ortho_2020","url":"https://g.naplesgov.com/arcgis/rest/services/Imagery/NAPLES_2020/ImageServer/exportImage?f=image&format=jpg&imageSR={wkid}&bboxSR={wkid}&bbox={bbox}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Collier County Property Appraiser’s Office, Naples GIS","url":"https://g.naplesgov.com/cityofnaplesgis2/"},"type":"wms","category":"historicphoto","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-81.89578,26.42447],[-81.52919,26.42631],[-81.5285,26.27909],[-81.51477,26.27918],[-81.51437,26.19255],[-81.52803,26.19245],[-81.5262,25.82107],[-81.83073,25.81984],[-81.83226,26.09502],[-81.8928,26.09488],[-81.89578,26.42447]]],"type":"Polygon"}}, {"properties":{"name":"Okaloosa County Orthoimagery (2019)","id":"Okaloosa_Ortho_2019","url":"https://ags.myokaloosa.com/arcgis/rest/services/imagery/Aerial2019/MapServer/WMTS/tile/1.0.0/imagery_Aerial2019/default/default028mm/{zoom}/{y}/{x}.jpg","attribution":{"required":false,"text":"Okaloosa County, FL GIS","url":"https://myokaloosa.com/gis_homepage"},"type":"tms","category":"historicphoto","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-86.79553,31.00315],[-86.79162,30.83611],[-86.80524,30.8358],[-86.80032,30.48768],[-86.8087,30.48752],[-86.80721,30.42335],[-86.81146,30.42328],[-86.81073,30.38239],[-86.38213,30.37511],[-86.37831,31.00762],[-86.79553,31.00315]]],"type":"Polygon"}}, {"properties":{"name":"Okaloosa County Orthoimagery (2022)","id":"Okaloosa_Ortho_2022","url":"https://ags.myokaloosa.com/arcgis/rest/services/imagery/Aerial2022_dynamic/MapServer/export?dpi=96&transparent=true&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&f=image&foo={proj}","attribution":{"required":false,"text":"Okaloosa County, FL GIS","url":"https://myokaloosa.com/gis_homepage"},"type":"wms","category":"photo","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-86.79553,31.00315],[-86.79162,30.83611],[-86.80524,30.8358],[-86.80032,30.48768],[-86.8087,30.48752],[-86.80721,30.42335],[-86.81146,30.42328],[-86.81073,30.38239],[-86.38213,30.37511],[-86.37831,31.00762],[-86.79553,31.00315]]],"type":"Polygon"}}, -{"properties":{"name":"Palm-Beach County Orthoimagery (2021)","id":"Palm-Beach_Ortho_2021","url":"https://maps.co.palm-beach.fl.us/arcgis/rest/services/Aerials/2021/MapServer/export?f=image&format=jpg&layers=show,0&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Palm Beach County","url":"http://maps.co.palm-beach.fl.us/cwgis/mygeonav.html"},"type":"wms","category":"historicphoto","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-80.6351,26.98808],[-80.63519,26.97425],[-80.61978,26.97425],[-80.62004,26.8917],[-80.71235,26.7819],[-80.75835,26.78199],[-80.7584,26.74074],[-80.85037,26.74085],[-80.85036,26.75462],[-80.86567,26.75464],[-80.86565,26.78213],[-80.89628,26.78218],[-80.89671,26.31446],[-80.0572,26.31134],[-80.02463,26.55876],[-80.0223,26.83382],[-80.06716,26.98546],[-80.6351,26.98808]]],"type":"Polygon"}}, -{"properties":{"name":"Palm-Beach County Orthoimagery (2022)","id":"Palm-Beach_Ortho_2022","url":"https://maps.co.palm-beach.fl.us/arcgis/rest/services/Aerials/2022/MapServer/export?f=image&format=jpg&layers=show,0&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Palm Beach County","url":"http://maps.co.palm-beach.fl.us/cwgis/mygeonav.html"},"type":"wms","category":"photo","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[[-80.38942,26.9872],[-80.39108,26.67082],[-80.36056,26.67048],[-80.36035,26.64297],[-80.34524,26.64297],[-80.34532,26.62938],[-80.33013,26.62931],[-80.33021,26.61555],[-80.31489,26.61542],[-80.31502,26.60174],[-80.2844,26.60156],[-80.28448,26.58782],[-80.26916,26.58776],[-80.26928,26.57398],[-80.25398,26.57394],[-80.25391,26.54662],[-80.23885,26.5462],[-80.23893,26.51881],[-80.22375,26.51874],[-80.22415,26.4637],[-80.23942,26.46376],[-80.23993,26.38124],[-80.25522,26.38129],[-80.25533,26.36757],[-80.30111,26.36783],[-80.30143,26.31279],[-80.05733,26.31191],[-80.02463,26.55876],[-80.0223,26.83382],[-80.06716,26.98546],[-80.38942,26.9872]]],[[[-80.63533,26.89178],[-80.63553,26.86419],[-80.65085,26.86423],[-80.6509,26.85056],[-80.66618,26.85058],[-80.66628,26.83679],[-80.68152,26.83679],[-80.6817,26.7956],[-80.63568,26.79553],[-80.63559,26.82308],[-80.62035,26.823],[-80.62025,26.83673],[-80.60494,26.83669],[-80.60473,26.89167],[-80.63533,26.89178]]],[[[-80.68185,26.74057],[-80.6819,26.71311],[-80.7125,26.71307],[-80.7128,26.68565],[-80.7432,26.68565],[-80.74337,26.65814],[-80.728,26.65814],[-80.728,26.64442],[-80.66681,26.64428],[-80.66672,26.65806],[-80.62082,26.65786],[-80.62069,26.69915],[-80.63604,26.69915],[-80.636,26.71295],[-80.6513,26.71299],[-80.65121,26.7405],[-80.68185,26.74057]]]],"type":"MultiPolygon"}}, +{"properties":{"name":"Palm Beach County Orthoimagery (2021)","id":"Palm-Beach_Ortho_2021","url":"https://maps.co.palm-beach.fl.us/arcgis/rest/services/Aerials/2021/MapServer/export?f=image&format=jpg&layers=show,0&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Palm Beach County","url":"http://maps.co.palm-beach.fl.us/cwgis/mygeonav.html"},"type":"wms","category":"historicphoto","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-80.6351,26.98808],[-80.63519,26.97425],[-80.61978,26.97425],[-80.62004,26.8917],[-80.71235,26.7819],[-80.75835,26.78199],[-80.7584,26.74074],[-80.85037,26.74085],[-80.85036,26.75462],[-80.86567,26.75464],[-80.86565,26.78213],[-80.89628,26.78218],[-80.89671,26.31446],[-80.0572,26.31134],[-80.02463,26.55876],[-80.0223,26.83382],[-80.06716,26.98546],[-80.6351,26.98808]]],"type":"Polygon"}}, +{"properties":{"name":"Palm Beach County Orthoimagery (2022)","id":"Palm-Beach_Ortho_2022","url":"https://maps.co.palm-beach.fl.us/arcgis/rest/services/Aerials/2022/MapServer/export?f=image&format=jpg&layers=show,0&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Palm Beach County","url":"http://maps.co.palm-beach.fl.us/cwgis/mygeonav.html"},"type":"wms","category":"historicphoto","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[[-80.38942,26.9872],[-80.39108,26.67082],[-80.36056,26.67048],[-80.36035,26.64297],[-80.34524,26.64297],[-80.34532,26.62938],[-80.33013,26.62931],[-80.33021,26.61555],[-80.31489,26.61542],[-80.31502,26.60174],[-80.2844,26.60156],[-80.28448,26.58782],[-80.26916,26.58776],[-80.26928,26.57398],[-80.25398,26.57394],[-80.25391,26.54662],[-80.23885,26.5462],[-80.23893,26.51881],[-80.22375,26.51874],[-80.22415,26.4637],[-80.23942,26.46376],[-80.23993,26.38124],[-80.25522,26.38129],[-80.25533,26.36757],[-80.30111,26.36783],[-80.30143,26.31279],[-80.05733,26.31191],[-80.02463,26.55876],[-80.0223,26.83382],[-80.06716,26.98546],[-80.38942,26.9872]]],[[[-80.63533,26.89178],[-80.63553,26.86419],[-80.65085,26.86423],[-80.6509,26.85056],[-80.66618,26.85058],[-80.66628,26.83679],[-80.68152,26.83679],[-80.6817,26.7956],[-80.63568,26.79553],[-80.63559,26.82308],[-80.62035,26.823],[-80.62025,26.83673],[-80.60494,26.83669],[-80.60473,26.89167],[-80.63533,26.89178]]],[[[-80.68185,26.74057],[-80.6819,26.71311],[-80.7125,26.71307],[-80.7128,26.68565],[-80.7432,26.68565],[-80.74337,26.65814],[-80.728,26.65814],[-80.728,26.64442],[-80.66681,26.64428],[-80.66672,26.65806],[-80.62082,26.65786],[-80.62069,26.69915],[-80.63604,26.69915],[-80.636,26.71295],[-80.6513,26.71299],[-80.65121,26.7405],[-80.68185,26.74057]]]],"type":"MultiPolygon"}}, +{"properties":{"name":"Palm Beach County Orthoimagery (2023)","id":"Palm-Beach_Ortho_2023","url":"https://maps.co.palm-beach.fl.us/arcgis/rest/services/Aerials/2023/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Palm Beach County","url":"http://maps.co.palm-beach.fl.us/cwgis/mygeonav.html"},"type":"tms","category":"photo","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[[-80.38942,26.9872],[-80.39108,26.67082],[-80.36056,26.67048],[-80.36035,26.64297],[-80.34524,26.64297],[-80.34532,26.62938],[-80.33013,26.62931],[-80.33021,26.61555],[-80.31489,26.61542],[-80.31502,26.60174],[-80.2844,26.60156],[-80.28448,26.58782],[-80.26916,26.58776],[-80.26928,26.57398],[-80.25398,26.57394],[-80.25391,26.54662],[-80.23885,26.5462],[-80.23893,26.51881],[-80.22375,26.51874],[-80.22415,26.4637],[-80.23942,26.46376],[-80.23993,26.38124],[-80.25522,26.38129],[-80.25533,26.36757],[-80.30111,26.36783],[-80.30143,26.31279],[-80.05733,26.31191],[-80.02463,26.55876],[-80.0223,26.83382],[-80.06716,26.98546],[-80.38942,26.9872]]],[[[-80.63533,26.89178],[-80.63553,26.86419],[-80.65085,26.86423],[-80.6509,26.85056],[-80.66618,26.85058],[-80.66628,26.83679],[-80.68152,26.83679],[-80.6817,26.7956],[-80.63568,26.79553],[-80.63559,26.82308],[-80.62035,26.823],[-80.62025,26.83673],[-80.60494,26.83669],[-80.60473,26.89167],[-80.63533,26.89178]]],[[[-80.68185,26.74057],[-80.6819,26.71311],[-80.7125,26.71307],[-80.7128,26.68565],[-80.7432,26.68565],[-80.74337,26.65814],[-80.728,26.65814],[-80.728,26.64442],[-80.66681,26.64428],[-80.66672,26.65806],[-80.62082,26.65786],[-80.62069,26.69915],[-80.63604,26.69915],[-80.636,26.71295],[-80.6513,26.71299],[-80.65121,26.7405],[-80.68185,26.74057]]]],"type":"MultiPolygon"}}, {"properties":{"name":"Pinellas County Orthoimagery (2023)","id":"Pinellas_Ortho_2023","url":"https://egis.pinellas.gov/gis/rest/services/Aerials/Aerials2023/ImageServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Pinellas County","url":"https://new-pinellas-egis.opendata.arcgis.com/"},"type":"tms","category":"photo","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-82.77539,27.56428],[-82.60568,27.57869],[-82.56087,27.86767],[-82.56115,27.93635],[-82.62383,27.96377],[-82.62299,27.97733],[-82.63902,28.00488],[-82.63998,28.18353],[-82.96373,28.18228],[-82.77539,27.56428]]],"type":"Polygon"}}, -{"properties":{"name":"Saint Johns County Natural Color Orthoimagery (2021)","id":"Saint_Johns_Ortho_2021","url":"https://www.gis.sjcfl.us/portal_sjcgis/rest/services/Imagery_2021_color/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Saint Johns County GIS","url":"https://www.sjcfl.us/GIS/index.aspx"},"type":"tms","category":"photo","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-81.60172,29.96152],[-81.55467,29.61383],[-81.31862,29.61478],[-81.31851,29.62861],[-81.30255,29.62866],[-81.30286,29.64208],[-81.25476,29.64302],[-81.25524,29.65464],[-81.20855,29.65617],[-81.3203,30.2611],[-81.44677,30.26085],[-81.44662,30.13673],[-81.50903,30.13727],[-81.50946,30.15077],[-81.58879,30.15028],[-81.58878,30.13628],[-81.65218,30.13561],[-81.68584,30.03107],[-81.60172,29.96152]]],"type":"Polygon"}}, -{"properties":{"name":"Saint Johns County Infrared Orthoimagery (2021)","id":"Saint_Johns_Ortho_CIR_2021","url":"https://www.gis.sjcfl.us/portal_sjcgis/rest/services/Imagery_2021_cir/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Saint Johns County GIS","url":"https://www.sjcfl.us/GIS/index.aspx"},"type":"tms","category":"photo","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-81.60172,29.96152],[-81.55467,29.61383],[-81.31862,29.61478],[-81.31851,29.62861],[-81.30255,29.62866],[-81.30286,29.64208],[-81.25476,29.64302],[-81.25524,29.65464],[-81.20855,29.65617],[-81.3203,30.2611],[-81.44677,30.26085],[-81.44662,30.13673],[-81.50903,30.13727],[-81.50946,30.15077],[-81.58879,30.15028],[-81.58878,30.13628],[-81.65218,30.13561],[-81.68584,30.03107],[-81.60172,29.96152]]],"type":"Polygon"}}, +{"properties":{"name":"Saint Johns County Basemap","id":"Saint_Johns_Basemap","url":"https://www.gis.sjcfl.us/portal_sjcgis/rest/services/Basemap/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Saint Johns County GIS","url":"https://www.sjcfl.us/GIS/index.aspx"},"type":"tms","category":"map","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-81.60172,29.96152],[-81.55467,29.61383],[-81.31862,29.61478],[-81.31851,29.62861],[-81.30255,29.62866],[-81.30286,29.64208],[-81.25476,29.64302],[-81.25524,29.65464],[-81.20855,29.65617],[-81.3203,30.2611],[-81.44677,30.26085],[-81.44662,30.13673],[-81.50903,30.13727],[-81.50946,30.15077],[-81.58879,30.15028],[-81.58878,30.13628],[-81.65218,30.13561],[-81.68584,30.03107],[-81.60172,29.96152]]],"type":"Polygon"}}, +{"properties":{"name":"Saint Johns County Natural Color Orthoimagery (2021)","id":"Saint_Johns_Ortho_2021","url":"https://www.gis.sjcfl.us/portal_sjcgis/rest/services/Imagery_2021_color/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Saint Johns County GIS","url":"https://www.sjcfl.us/GIS/index.aspx"},"type":"tms","category":"historicphoto","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-81.60172,29.96152],[-81.55467,29.61383],[-81.31862,29.61478],[-81.31851,29.62861],[-81.30255,29.62866],[-81.30286,29.64208],[-81.25476,29.64302],[-81.25524,29.65464],[-81.20855,29.65617],[-81.3203,30.2611],[-81.44677,30.26085],[-81.44662,30.13673],[-81.50903,30.13727],[-81.50946,30.15077],[-81.58879,30.15028],[-81.58878,30.13628],[-81.65218,30.13561],[-81.68584,30.03107],[-81.60172,29.96152]]],"type":"Polygon"}}, +{"properties":{"name":"Saint Johns County Infrared Orthoimagery (2021)","id":"Saint_Johns_Ortho_CIR_2021","url":"https://www.gis.sjcfl.us/portal_sjcgis/rest/services/Imagery_2021_cir/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Saint Johns County GIS","url":"https://www.sjcfl.us/GIS/index.aspx"},"type":"tms","category":"historicphoto","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-81.60172,29.96152],[-81.55467,29.61383],[-81.31862,29.61478],[-81.31851,29.62861],[-81.30255,29.62866],[-81.30286,29.64208],[-81.25476,29.64302],[-81.25524,29.65464],[-81.20855,29.65617],[-81.3203,30.2611],[-81.44677,30.26085],[-81.44662,30.13673],[-81.50903,30.13727],[-81.50946,30.15077],[-81.58879,30.15028],[-81.58878,30.13628],[-81.65218,30.13561],[-81.68584,30.03107],[-81.60172,29.96152]]],"type":"Polygon"}}, +{"properties":{"name":"Saint Johns County Infrared Orthoimagery (2023)","id":"Saint_Johns_Ortho_CIR_2023","url":"https://www.gis.sjcfl.us/portal_sjcgis/rest/services/Imagery_2023_cir/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Saint Johns County GIS","url":"https://www.sjcfl.us/GIS/index.aspx"},"type":"tms","category":"photo","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-81.60172,29.96152],[-81.55467,29.61383],[-81.31862,29.61478],[-81.31851,29.62861],[-81.30255,29.62866],[-81.30286,29.64208],[-81.25476,29.64302],[-81.25524,29.65464],[-81.20855,29.65617],[-81.3203,30.2611],[-81.44677,30.26085],[-81.44662,30.13673],[-81.50903,30.13727],[-81.50946,30.15077],[-81.58879,30.15028],[-81.58878,30.13628],[-81.65218,30.13561],[-81.68584,30.03107],[-81.60172,29.96152]]],"type":"Polygon"}}, {"properties":{"name":"Saint Lucie County Orthoimagery (2021)","id":"Saint_Lucie_Ortho_2021","url":"https://tiles.arcgis.com/tiles/UZU5YYWrSlE9YWnx/arcgis/rest/services/2021_Aerial_Imagery/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Saint Lucie County Property Appraiser’s Office","url":"https://www.paslc.gov/"},"type":"tms","category":"historicphoto","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-80.69494,27.5659],[-80.69595,27.19453],[-80.28066,27.19288],[-80.28031,27.24802],[-80.18807,27.24748],[-80.32276,27.56457],[-80.69494,27.5659]]],"type":"Polygon"}}, {"properties":{"name":"Saint Lucie County Orthoimagery (2023)","id":"Saint_Lucie_Ortho_2023","url":"https://tiles.arcgis.com/tiles/UZU5YYWrSlE9YWnx/arcgis/rest/services/2023_Aerial_Imagery/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Saint Lucie County Property Appraiser’s Office","url":"https://www.paslc.gov/"},"type":"tms","category":"photo","max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-80.69494,27.5659],[-80.69595,27.19453],[-80.28066,27.19288],[-80.28031,27.24802],[-80.18807,27.24748],[-80.32276,27.56457],[-80.69494,27.5659]]],"type":"Polygon"}}, {"properties":{"name":"Sarasota County Orthoimagery (2023)","id":"Sarasota_Ortho_2023","url":"https://ags3.scgov.net/agsfed/rest/services/ImageServices/SC2023WM/ImageServer/WMTS/tile/1.0.0/ImageServices_SC2023WM/default/default028mm/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Sarasota County GIS","url":"https://data-sarco.opendata.arcgis.com/"},"type":"tms","category":"photo","min_zoom":8,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-82.23475,27.40096],[-82.65082,27.39964],[-82.40258,26.93293],[-82.24912,26.93328],[-82.24933,27.02957],[-82.0343,27.02979],[-82.03436,27.22235],[-82.23438,27.22216],[-82.23475,27.40096]]],"type":"Polygon"}}, {"properties":{"name":"Seminole County Orthoimagery (2021)","id":"Seminole_Ortho_2021","url":"https://maps2.scpafl.org/arcgis/rest/services/Aerials2021/MapServer/export?f=image&format=jpg&layers=show,0&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Seminole County GIS","url":"https://maps2.scpafl.org/SCPAExternal/"},"type":"wms","category":"historicphoto","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-81.45946,28.72408],[-81.45972,28.63987],[-81.362,28.64017],[-81.36198,28.63692],[-81.33388,28.63696],[-81.33385,28.61057],[-80.98709,28.61293],[-80.98709,28.63693],[-81.00331,28.637],[-81.00331,28.6805],[-81.01988,28.68059],[-81.01986,28.69501],[-81.00333,28.69501],[-81.00333,28.70098],[-81.02097,28.70099],[-81.02097,28.72408],[-81.03636,28.72409],[-81.05676,28.7877],[-81.05677,28.80228],[-81.06738,28.80228],[-81.06739,28.81119],[-81.06944,28.81119],[-81.06945,28.82572],[-81.10248,28.82571],[-81.10248,28.82889],[-81.13554,28.82887],[-81.13554,28.81121],[-81.15207,28.81119],[-81.15203,28.79664],[-81.20161,28.79669],[-81.20164,28.81122],[-81.21814,28.81121],[-81.21818,28.8232],[-81.30074,28.82571],[-81.30079,28.84027],[-81.31726,28.84023],[-81.31731,28.85479],[-81.35032,28.85476],[-81.35031,28.87371],[-81.36304,28.87369],[-81.36301,28.87847],[-81.36708,28.87849],[-81.36708,28.87364],[-81.3834,28.8736],[-81.3834,28.86934],[-81.39999,28.86928],[-81.39995,28.85481],[-81.41649,28.85473],[-81.41644,28.82576],[-81.41981,28.82573],[-81.4197,28.79666],[-81.4165,28.79667],[-81.41645,28.75314],[-81.43302,28.75311],[-81.43302,28.72411],[-81.45946,28.72408]]],"type":"Polygon"}}, {"properties":{"name":"Seminole County Orthoimagery (2023)","id":"Seminole_Ortho_2023","url":"https://seminolearcgis.seminolecountyfl.gov:6443/arcgis/rest/services/Aerials2023/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Seminole County GIS","url":"https://maps2.scpafl.org/SCPAExternal/"},"type":"tms","category":"photo","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-81.45946,28.72408],[-81.45972,28.63987],[-81.362,28.64017],[-81.36198,28.63692],[-81.33388,28.63696],[-81.33385,28.61057],[-80.98709,28.61293],[-80.98709,28.63693],[-81.00331,28.637],[-81.00331,28.6805],[-81.01988,28.68059],[-81.01986,28.69501],[-81.00333,28.69501],[-81.00333,28.70098],[-81.02097,28.70099],[-81.02097,28.72408],[-81.03636,28.72409],[-81.05676,28.7877],[-81.05677,28.80228],[-81.06738,28.80228],[-81.06739,28.81119],[-81.06944,28.81119],[-81.06945,28.82572],[-81.10248,28.82571],[-81.10248,28.82889],[-81.13554,28.82887],[-81.13554,28.81121],[-81.15207,28.81119],[-81.15203,28.79664],[-81.20161,28.79669],[-81.20164,28.81122],[-81.21814,28.81121],[-81.21818,28.8232],[-81.30074,28.82571],[-81.30079,28.84027],[-81.31726,28.84023],[-81.31731,28.85479],[-81.35032,28.85476],[-81.35031,28.87371],[-81.36304,28.87369],[-81.36301,28.87847],[-81.36708,28.87849],[-81.36708,28.87364],[-81.3834,28.8736],[-81.3834,28.86934],[-81.39999,28.86928],[-81.39995,28.85481],[-81.41649,28.85473],[-81.41644,28.82576],[-81.41981,28.82573],[-81.4197,28.79666],[-81.4165,28.79667],[-81.41645,28.75314],[-81.43302,28.75311],[-81.43302,28.72411],[-81.45946,28.72408]]],"type":"Polygon"}}, -{"properties":{"name":"Sumter County Orthoimagery (2021)","id":"Sumter_Ortho_2021","url":"https://gis.sumtercountyfl.gov/sumtergis/rest/services/Imagery/Imagery2021_EV/MapServer/export?f=image&format=jpg&layers=&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Sumter County GIS, Sumter County Board of County Commissioners","url":"https://data2-sumtercountygis.opendata.arcgis.com/"},"type":"wms","category":"photo","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-81.9411,28.96893],[-81.94136,28.39095],[-81.95685,28.39099],[-81.95706,28.29501],[-82.00367,28.29516],[-82.00367,28.30922],[-82.0659,28.30914],[-82.0659,28.51516],[-82.11267,28.51508],[-82.11259,28.52888],[-82.12812,28.52896],[-82.1283,28.54276],[-82.14383,28.54268],[-82.14383,28.55633],[-82.19069,28.55618],[-82.19061,28.57013],[-82.22168,28.57005],[-82.22177,28.58377],[-82.23756,28.58384],[-82.23739,28.6112],[-82.25301,28.61127],[-82.25301,28.62499],[-82.26852,28.62504],[-82.26854,28.6387],[-82.28421,28.63874],[-82.28429,28.66611],[-82.26871,28.66623],[-82.26867,28.69364],[-82.25314,28.69375],[-82.25318,28.735],[-82.23764,28.735],[-82.23764,28.7487],[-82.22198,28.7487],[-82.22207,28.76247],[-82.19078,28.76254],[-82.19091,28.8176],[-82.20659,28.81757],[-82.2067,28.83133],[-82.22215,28.83125],[-82.22232,28.84494],[-82.23799,28.84494],[-82.23799,28.85881],[-82.25348,28.85869],[-82.25352,28.87249],[-82.2691,28.87252],[-82.26919,28.88632],[-82.28472,28.88628],[-82.28481,28.91367],[-82.3006,28.91378],[-82.30052,28.94124],[-82.31622,28.94127],[-82.31627,28.96854],[-81.9411,28.96893]]],"type":"Polygon"}}, {"properties":{"name":"Volusia County Orthoimagery (2021)","id":"Volusia_Ortho_2021","url":"https://maps2.vcgov.org/arcgis/rest/services/Aerial_2021/ImageServer/exportImage?f=image&format=jpg&imageSR={wkid}&bboxSR={wkid}&bbox={bbox}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Volusia County Property Appraiser’s Office","url":"https://volusiacountyfl.maps.arcgis.com/apps/webappviewer/index.html?id=b773f4bfcadd4d9aa8b99cb599e0aec6"},"type":"wms","category":"photo","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-80.72269,28.78991],[-81.0979,29.43646],[-81.12929,29.43639],[-81.1293,29.42265],[-81.16071,29.42261],[-81.16048,29.27136],[-81.4114,29.27083],[-81.41196,29.40834],[-81.44324,29.40824],[-81.4432,29.39444],[-81.45899,29.39448],[-81.45899,29.3806],[-81.50601,29.38052],[-81.50598,29.36677],[-81.5687,29.36652],[-81.56868,29.3527],[-81.59975,29.35278],[-81.58344,29.18769],[-81.56774,29.18774],[-81.56761,29.17405],[-81.53628,29.17408],[-81.53615,29.14665],[-81.52053,29.14669],[-81.52023,29.10556],[-81.50456,29.10553],[-81.50456,29.09158],[-81.47306,29.09173],[-81.47315,29.06435],[-81.45736,29.0645],[-81.45736,29.05077],[-81.44174,29.05077],[-81.44165,29.03711],[-81.42612,29.03704],[-81.42594,29.02338],[-81.41032,29.02338],[-81.41024,29.00957],[-81.3947,29.00964],[-81.39462,28.9959],[-81.37882,28.99605],[-81.37882,28.95483],[-81.3632,28.95483],[-81.36303,28.89977],[-81.37874,28.89954],[-81.37839,28.85851],[-81.36312,28.85858],[-81.36286,28.84452],[-81.34724,28.84475],[-81.34707,28.83106],[-81.23789,28.83122],[-81.23776,28.8176],[-81.22222,28.81753],[-81.22201,28.79],[-81.19085,28.79008],[-81.19094,28.77631],[-81.15944,28.77631],[-81.15961,28.79],[-81.12845,28.78985],[-81.1285,28.80388],[-81.11292,28.80395],[-81.11283,28.81772],[-81.09725,28.81768],[-81.09734,28.80392],[-81.08163,28.80392],[-81.08168,28.79004],[-81.06601,28.79012],[-81.05035,28.70768],[-81.0349,28.70753],[-81.03464,28.66627],[-81.01902,28.66642],[-81.01911,28.62514],[-81.00357,28.62506],[-81.00348,28.61135],[-80.95684,28.61135],[-80.95679,28.7903],[-80.72269,28.78991]]],"type":"Polygon"}}, {"properties":{"name":"Athens-Clarke County Imagery (2018)","id":"ACC_2018","url":"https://tiles.arcgis.com/tiles/xSEULKvB31odt3XQ/arcgis/rest/services/2018_Ortho_Imagery/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":true,"text":"Athens-Clarke County GIS","url":"https://data-athensclarke.opendata.arcgis.com/"},"type":"tms","category":"historicphoto","min_zoom":8,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-83.39172,33.8727],[-83.30933,33.83506],[-83.20633,33.86243],[-83.25577,33.96386],[-83.35739,34.02193],[-83.45078,33.99689],[-83.50433,33.92627],[-83.39172,33.8727]]],"type":"Polygon"}}, {"properties":{"name":"Maui County Orthoimagery (2023)","id":"Maui_2023","url":"https://tiles.arcgis.com/tiles/fsrDo0QMPlK9CkZD/arcgis/rest/services/Pictometry2023_MauiCounty282/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Maui County GIS","url":"https://mauicounty.maps.arcgis.com/home/index.html"},"type":"tms","category":"photo","min_zoom":2,"max_zoom":21,"best":true},"type":"Feature","geometry":{"coordinates":[[[-157.37227,21.28684],[-157.37227,20.45357],[-155.92272,20.45357],[-155.92272,21.28684],[-157.37227,21.28684]]],"type":"Polygon"}}, @@ -575,7 +582,8 @@ {"properties":{"name":"KyFromAbove Aerial Imagery (2012-2014)","id":"KYAPED_Phase_1","url":"https://kyraster.ky.gov/arcgis/services/ImageServices/Ky_KYAPED_Imagery_WGS84WM/ImageServer/WMSServer?LAYERS=0&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":false,"text":"KyFromAbove","url":"https://kyfromabove.ky.gov/"},"type":"wms","category":"historicphoto","min_zoom":3,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[[-89.55251,36.57723],[-89.55264,36.57718],[-89.57151,36.55257],[-89.54296,36.50496],[-89.53912,36.4981],[-89.4885,36.49755],[-89.4852,36.49745],[-89.46589,36.52995],[-89.46545,36.53616],[-89.4808,36.56969],[-89.48089,36.56977],[-89.5205,36.57943],[-89.52234,36.58018],[-89.55251,36.57723]]],[[[-89.36555,36.62506],[-89.37545,36.61572],[-89.37637,36.61387],[-89.41729,36.49903],[-89.34795,36.50308],[-89.30028,36.50715],[-88.06598,36.49774],[-88.05332,36.49712],[-88.0453,36.50408],[-88.03948,36.51041],[-88.03413,36.53112],[-88.03249,36.54066],[-88.0412,36.58412],[-88.04513,36.60294],[-88.06821,36.65975],[-88.07053,36.67812],[-87.85045,36.6651],[-87.85354,36.63507],[-87.69783,36.637],[-87.69385,36.63707],[-87.68285,36.70333],[-87.67503,36.7501],[-87.67186,36.8785],[-87.66,36.96624],[-87.72956,36.9999],[-87.73366,37.00209],[-87.7106,37.02156],[-87.68078,37.14928],[-87.66878,37.14975],[-87.64964,37.14692],[-87.52378,37.10601],[-87.52029,37.10516],[-87.48508,37.12661],[-87.37442,37.13381],[-87.35981,37.1499],[-87.35998,37.1567],[-87.34036,37.15745],[-87.33626,37.1548],[-87.3151,37.18731],[-87.31174,37.19144],[-87.34575,37.21178],[-87.34988,37.2149],[-87.34719,37.2231],[-87.349,37.22625],[-87.37141,37.24404],[-87.36402,37.24914],[-87.38553,37.25335],[-87.38852,37.25738],[-87.38414,37.2901],[-87.38602,37.29551],[-87.37801,37.29926],[-87.37221,37.29889],[-87.3793,37.30468],[-87.38004,37.31102],[-87.37377,37.31474],[-87.37091,37.31614],[-87.35457,37.3105],[-87.34586,37.30973],[-87.33775,37.33252],[-87.33726,37.33615],[-87.31243,37.36282],[-87.30045,37.37026],[-87.30749,37.37378],[-87.3093,37.37693],[-87.30595,37.38197],[-87.29575,37.39164],[-87.33332,37.41148],[-87.3528,37.42569],[-87.3548,37.48279],[-87.36243,37.4863],[-87.35148,37.51231],[-87.34924,37.51552],[-87.35952,37.53213],[-87.36652,37.53339],[-87.36037,37.54255],[-87.35931,37.54665],[-87.37499,37.56998],[-87.37986,37.57898],[-87.39521,37.58871],[-87.46038,37.59267],[-87.4803,37.59914],[-87.49965,37.62694],[-87.49557,37.64786],[-87.73424,37.63828],[-87.94307,37.46638],[-87.94375,37.46563],[-87.93037,37.41046],[-87.93019,37.4092],[-87.91391,37.41123],[-87.91313,37.41104],[-87.91564,37.40477],[-87.91545,37.40448],[-87.90281,37.39822],[-87.90195,37.39824],[-87.86945,37.40519],[-87.86876,37.40518],[-87.86164,37.40894],[-87.86131,37.40954],[-87.86595,37.41683],[-87.86561,37.41728],[-87.84737,37.42062],[-87.84638,37.42046],[-87.84426,37.39512],[-87.84426,37.39482],[-87.85904,37.39277],[-87.85964,37.39241],[-87.86,37.38071],[-87.86003,37.3802],[-87.84335,37.37468],[-87.84282,37.37469],[-87.81953,37.38776],[-87.81861,37.38832],[-87.81061,37.37686],[-87.81042,37.37645],[-87.80095,37.3794],[-87.80122,37.37898],[-88.05239,37.23751],[-88.05269,37.22934],[-88.07805,37.22699],[-88.08885,37.22178],[-88.08231,37.21647],[-88.08042,37.21198],[-88.08376,37.20828],[-88.08891,37.20681],[-88.08443,37.1942],[-88.08301,37.18652],[-88.09489,37.17947],[-88.10183,37.17932],[-88.10889,37.16647],[-88.1099,37.16237],[-88.13168,37.15736],[-88.13252,37.14872],[-88.14131,37.15216],[-88.14428,37.15436],[-88.16293,37.14307],[-88.16557,37.13575],[-88.1919,37.12927],[-88.1966,37.13144],[-88.18787,37.16156],[-88.19039,37.1674],[-88.21927,37.18309],[-88.21986,37.18353],[-88.20455,37.20746],[-88.20065,37.21162],[-88.20258,37.23289],[-88.20032,37.23431],[-88.21851,37.27335],[-88.29375,37.33559],[-88.29557,37.33782],[-88.30556,37.37206],[-88.29948,37.37991],[-88.31327,37.39228],[-88.31811,37.39761],[-88.36547,37.40166],[-88.37121,37.40273],[-88.40624,37.42479],[-88.40881,37.42522],[-88.46586,37.40055],[-88.47022,37.39625],[-88.48672,37.34015],[-88.48695,37.3396],[-88.51406,37.29246],[-88.51486,37.29069],[-88.50409,37.26514],[-88.50382,37.26485],[-88.5123,37.26295],[-88.51187,37.26201],[-88.45005,37.2062],[-88.44982,37.20599],[-88.42533,37.15421],[-88.42462,37.15173],[-88.44912,37.08875],[-88.44919,37.08776],[-88.45919,37.07523],[-88.46019,37.07447],[-88.48622,37.06659],[-88.48605,37.0648],[-88.56528,37.07521],[-88.56587,37.0752],[-88.62221,37.11775],[-88.62589,37.11946],[-88.68777,37.13938],[-88.69398,37.14115],[-88.73125,37.14368],[-88.73211,37.14396],[-88.78695,37.17858],[-88.79737,37.18485],[-88.83505,37.19649],[-88.86953,37.20971],[-88.9273,37.22636],[-88.93175,37.22759],[-88.98326,37.22868],[-89.00097,37.2244],[-89.07622,37.17513],[-89.08653,37.1656],[-89.10403,37.13197],[-89.11119,37.11905],[-89.15129,37.09049],[-89.1545,37.08891],[-89.17858,37.055],[-89.17938,37.05301],[-89.18248,37.03748],[-89.18251,37.03728],[-89.1736,37.01141],[-89.17112,37.00807],[-89.13844,36.98509],[-89.13301,36.982],[-89.1183,36.98188],[-89.11503,36.98033],[-89.09901,36.96139],[-89.09884,36.95785],[-89.13194,36.85744],[-89.13797,36.84735],[-89.17718,36.83578],[-89.17815,36.83459],[-89.17923,36.81291],[-89.17875,36.80993],[-89.17107,36.79812],[-89.16846,36.79557],[-89.13321,36.78823],[-89.12892,36.78768],[-89.11685,36.77561],[-89.11607,36.77242],[-89.12243,36.75484],[-89.12613,36.75173],[-89.15699,36.75597],[-89.16689,36.75963],[-89.19155,36.74722],[-89.19781,36.73941],[-89.20073,36.72014],[-89.19948,36.71605],[-89.17484,36.69396],[-89.16952,36.68888],[-89.16872,36.67189],[-89.15908,36.66635],[-89.18775,36.64111],[-89.19254,36.636],[-89.20261,36.60158],[-89.21356,36.58012],[-89.23184,36.56812],[-89.23654,36.56682],[-89.26806,36.56891],[-89.27171,36.57139],[-89.31943,36.62739],[-89.32472,36.63108],[-89.36486,36.62532],[-89.36555,36.62506]]],[[[-86.0832,38.00266],[-86.08252,37.80823],[-86.15172,37.79892],[-86.27537,37.5933],[-86.27184,37.58879],[-86.26139,37.58885],[-86.25214,37.59435],[-86.24634,37.59438],[-86.23343,37.58086],[-86.23221,37.57543],[-86.22002,37.57596],[-86.21075,37.57737],[-86.18677,37.56028],[-86.18035,37.55624],[-86.16999,37.56582],[-86.16419,37.56721],[-86.15428,37.56228],[-86.14613,37.5596],[-86.11425,37.56611],[-86.1131,37.56657],[-86.05621,37.4916],[-86.05151,37.48301],[-86.06584,37.46073],[-86.06756,37.458],[-86.05997,37.45123],[-86.05647,37.44853],[-86.05415,37.44899],[-86.05071,37.45354],[-86.00023,37.44696],[-85.8912,37.44011],[-85.89011,37.45144],[-85.89426,37.47002],[-85.88848,37.47366],[-85.87493,37.54533],[-85.83905,37.56901],[-85.82107,37.57404],[-85.80725,37.60173],[-85.7974,37.60855],[-85.80326,37.62032],[-85.80211,37.62305],[-85.73423,37.65674],[-85.68093,37.73207],[-85.65181,37.71806],[-85.6454,37.71308],[-85.63781,37.69632],[-85.63432,37.69406],[-85.60059,37.69908],[-85.59244,37.69364],[-85.6011,37.64604],[-85.60051,37.6397],[-85.58714,37.64469],[-85.58366,37.64424],[-85.57725,37.63926],[-85.57319,37.6379],[-85.57376,37.62838],[-85.56853,37.62295],[-85.57027,37.61751],[-85.57375,37.61705],[-85.57899,37.62611],[-85.58422,37.62566],[-85.5906,37.61613],[-85.59524,37.61341],[-85.61326,37.61612],[-85.615,37.61475],[-85.59464,37.59891],[-85.59464,37.59755],[-85.60219,37.59346],[-85.60683,37.59074],[-85.60564,37.57532],[-85.60505,37.56581],[-85.62302,37.55174],[-85.6265,37.54493],[-85.62417,37.5404],[-85.61778,37.54086],[-85.60735,37.54813],[-85.60212,37.54723],[-85.59341,37.54406],[-85.59514,37.53545],[-85.57134,37.5273],[-85.56495,37.52277],[-85.55393,37.53184],[-85.54697,37.53049],[-85.55105,37.5536],[-85.54408,37.56222],[-85.5348,37.56313],[-85.52667,37.56267],[-85.52144,37.55361],[-85.45053,37.68596],[-85.40147,37.73066],[-85.3873,37.7404],[-85.38629,37.74075],[-85.37373,37.73951],[-85.3733,37.73981],[-85.37495,37.76461],[-85.37454,37.7652],[-85.35546,37.75756],[-85.35345,37.75735],[-85.34591,37.76225],[-85.34548,37.76249],[-85.35624,37.78367],[-85.35601,37.78403],[-85.34029,37.78005],[-85.33859,37.77992],[-85.33649,37.78663],[-85.33574,37.78739],[-85.31214,37.79132],[-85.30818,37.79364],[-85.31532,37.80739],[-85.31527,37.80793],[-85.29908,37.83149],[-85.29844,37.83233],[-85.27861,37.83666],[-85.2776,37.83756],[-85.28027,37.85154],[-85.27953,37.85236],[-85.26827,37.84471],[-85.26784,37.84444],[-85.24567,37.84685],[-85.245,37.84689],[-85.25585,37.86503],[-85.25535,37.86583],[-85.24147,37.86229],[-85.24042,37.86243],[-85.23106,37.87044],[-85.23037,37.87061],[-85.22767,37.85492],[-85.22677,37.85501],[-85.21773,37.87142],[-85.21668,37.87141],[-85.21842,37.85492],[-85.21843,37.85427],[-85.20506,37.84608],[-85.20421,37.84624],[-85.20148,37.85319],[-85.19627,37.85603],[-85.20615,37.8633],[-85.19853,37.8728],[-85.1956,37.87642],[-85.19963,37.89003],[-85.199,37.90091],[-85.18916,37.88457],[-85.18683,37.88411],[-85.1868,37.8909],[-85.18446,37.89226],[-85.15766,37.88584],[-85.153,37.88583],[-85.15412,37.89535],[-85.15295,37.89762],[-85.14714,37.8908],[-85.13839,37.89168],[-85.13192,37.9039],[-85.12544,37.91566],[-85.10622,37.90926],[-85.10747,37.8934],[-85.09758,37.88974],[-85.09409,37.88792],[-85.03458,37.89224],[-85.0305,37.89177],[-85.03114,37.88316],[-85.03115,37.8818],[-85.01031,37.85951],[-85.00043,37.85448],[-85.0029,37.8341],[-85.00872,37.83503],[-85.01175,37.81736],[-85.00594,37.81643],[-85.02984,37.63114],[-85.03909,37.54505],[-85.01293,37.55084],[-85.00192,37.54807],[-85.00201,37.53312],[-84.89814,37.5322],[-84.74786,37.5853],[-84.75244,37.59213],[-84.73671,37.59656],[-84.73155,37.58973],[-84.71175,37.59505],[-84.71516,37.60232],[-84.69416,37.61035],[-84.68832,37.61258],[-84.69583,37.61716],[-84.69405,37.62032],[-84.65805,37.61826],[-84.65215,37.62548],[-84.65854,37.63503],[-84.65902,37.63541],[-84.66623,37.6334],[-84.66685,37.6341],[-84.65457,37.64052],[-84.65352,37.64113],[-84.65356,37.65231],[-84.6564,37.65442],[-84.67906,37.64593],[-84.68045,37.64496],[-84.67354,37.64198],[-84.67213,37.64074],[-84.6785,37.63672],[-84.68,37.6367],[-84.69459,37.64103],[-84.69491,37.64152],[-84.67925,37.65622],[-84.68039,37.65755],[-84.6929,37.66415],[-84.69376,37.66487],[-84.70234,37.65981],[-84.70524,37.66074],[-84.69935,37.66795],[-84.68009,37.67417],[-84.6823,37.68506],[-84.68343,37.68734],[-84.69628,37.68244],[-84.70036,37.68156],[-84.69848,37.69469],[-84.69845,37.69741],[-84.74265,37.69769],[-84.74322,37.69905],[-84.73286,37.70867],[-84.73258,37.70976],[-84.74578,37.71478],[-84.7442,37.71674],[-84.72678,37.71391],[-84.72444,37.71435],[-84.73072,37.72617],[-84.72836,37.72888],[-84.69934,37.72235],[-84.68998,37.72682],[-84.70564,37.73191],[-84.70737,37.73373],[-84.68813,37.73632],[-84.68865,37.74177],[-84.702,37.74548],[-84.70489,37.74777],[-84.69429,37.75903],[-84.69424,37.76356],[-84.71395,37.76793],[-84.71571,37.7696],[-84.70292,37.78664],[-84.70274,37.78751],[-84.71637,37.79979],[-84.71682,37.80026],[-84.71579,37.81518],[-84.71595,37.81553],[-84.67952,37.82987],[-84.6771,37.82838],[-84.67982,37.80132],[-84.6795,37.79834],[-84.66789,37.7834],[-84.66555,37.78298],[-84.64982,37.78901],[-84.64933,37.7907],[-84.65966,37.80897],[-84.65862,37.80952],[-84.61235,37.8017],[-84.61001,37.80071],[-84.60836,37.79271],[-84.60999,37.79165],[-84.64066,37.79291],[-84.64222,37.79201],[-84.64335,37.77918],[-84.64322,37.77846],[-84.60658,37.76066],[-84.60501,37.75974],[-84.61569,37.74642],[-84.61496,37.74584],[-84.60632,37.74281],[-84.60541,37.743],[-84.58968,37.75414],[-84.58728,37.75347],[-84.58816,37.73812],[-84.58776,37.73642],[-84.57019,37.72872],[-84.56928,37.72906],[-84.55705,37.74743],[-84.55591,37.74842],[-84.54004,37.75425],[-84.53923,37.75481],[-84.53656,37.76915],[-84.53602,37.76971],[-84.49641,37.78505],[-84.49565,37.78547],[-84.48348,37.80138],[-84.48291,37.80188],[-84.46464,37.79892],[-84.46412,37.79961],[-84.48413,37.83585],[-84.48467,37.83747],[-84.47809,37.85212],[-84.4773,37.85273],[-84.44594,37.84074],[-84.44411,37.84131],[-84.418,37.87157],[-84.41645,37.87212],[-84.39202,37.84668],[-84.39067,37.84596],[-84.37047,37.85387],[-84.36854,37.85455],[-84.38304,37.89289],[-84.38386,37.89473],[-84.37973,37.90109],[-84.37739,37.90174],[-84.34117,37.88224],[-84.34059,37.88272],[-84.33787,37.89163],[-84.33765,37.89193],[-84.34891,37.89836],[-84.34897,37.8986],[-84.33875,37.90712],[-84.33871,37.90733],[-84.34803,37.91488],[-84.34799,37.91512],[-84.33024,37.92249],[-84.32995,37.92392],[-84.33986,37.93097],[-84.33998,37.93131],[-84.33065,37.9368],[-84.3308,37.93723],[-84.33842,37.93906],[-84.33837,37.93921],[-84.31147,37.95185],[-84.31075,37.95217],[-84.32328,37.95589],[-84.32331,37.95603],[-84.31175,37.96302],[-84.31172,37.96332],[-84.32414,37.96579],[-84.32443,37.96645],[-84.31964,38.0023],[-84.28271,38.01912],[-84.29281,38.03333],[-84.29299,38.03361],[-84.28481,38.04006],[-84.28465,38.04043],[-84.28618,38.06663],[-84.28637,38.06701],[-84.08024,38.11516],[-84.02625,38.1526],[-83.99068,38.17484],[-83.9827,38.19287],[-83.98383,38.19515],[-83.97018,38.20314],[-83.96947,38.20948],[-83.98741,38.22013],[-83.98917,38.22015],[-83.9901,38.20293],[-83.99477,38.2039],[-84.00684,38.21538],[-84.0085,38.21993],[-84.04297,38.22488],[-84.06271,38.23463],[-84.06207,38.23689],[-84.05382,38.23906],[-84.05772,38.24953],[-84.05699,38.25722],[-84.06817,38.25509],[-84.07985,38.25704],[-84.07445,38.26378],[-84.07323,38.26603],[-84.07782,38.27107],[-84.08012,38.27336],[-84.08951,38.27301],[-84.09547,38.2681],[-84.09681,38.25905],[-84.10326,38.25912],[-84.10137,38.2659],[-84.10421,38.27046],[-84.11649,38.27242],[-84.11995,38.27517],[-84.10516,38.28271],[-84.1045,38.28633],[-84.11736,38.28829],[-84.12696,38.30789],[-84.13289,38.30478],[-84.16658,38.35501],[-84.19386,38.37162],[-84.10268,38.45943],[-84.10671,38.46884],[-84.10712,38.46916],[-84.13066,38.4746],[-84.13124,38.47465],[-84.131,38.48267],[-84.13163,38.48297],[-84.14244,38.47603],[-84.14319,38.47602],[-84.15141,38.48962],[-84.1521,38.48993],[-84.17199,38.4865],[-84.17319,38.48736],[-84.16006,38.50108],[-84.16124,38.50177],[-84.18289,38.49678],[-84.18345,38.49735],[-84.1794,38.52331],[-84.17804,38.53327],[-84.1643,38.54491],[-84.16177,38.55394],[-84.16816,38.559],[-84.20714,38.55125],[-84.20886,38.54992],[-84.21707,38.55729],[-84.21763,38.55813],[-84.20383,38.56432],[-84.20297,38.56516],[-84.20541,38.58206],[-84.20473,38.58794],[-84.19575,38.58834],[-84.1953,38.5883],[-84.20091,38.60377],[-84.2075,38.59749],[-84.23415,38.80739],[-84.23604,38.82232],[-84.23054,38.82744],[-84.23294,38.88603],[-84.23289,38.8871],[-84.2864,38.95228],[-84.28874,38.95567],[-84.30005,38.9935],[-84.30716,39.0073],[-84.33985,39.03437],[-84.34089,39.03501],[-84.42565,39.0527],[-84.42652,39.05322],[-84.43401,39.09569],[-84.43495,39.09977],[-84.45282,39.11925],[-84.45619,39.12054],[-84.4792,39.11807],[-84.48059,39.11742],[-84.50434,39.09592],[-84.50612,39.09511],[-84.54971,39.09972],[-84.55096,39.09949],[-84.57256,39.08215],[-84.57306,39.08191],[-84.61685,39.07323],[-84.61845,39.07327],[-84.65842,39.09552],[-84.65993,39.09614],[-84.68487,39.1003],[-84.68593,39.10077],[-84.71788,39.13641],[-84.71826,39.13682],[-84.75139,39.14753],[-84.75244,39.14726],[-84.7842,39.1179],[-84.7874,39.11544],[-84.83142,39.10085],[-84.83248,39.10036],[-84.89694,39.05747],[-84.89729,39.05747],[-84.83027,38.97417],[-84.83003,38.97305],[-84.83299,38.96042],[-84.83363,38.95943],[-84.8762,38.92254],[-84.87706,38.92147],[-84.87723,38.90954],[-84.87702,38.90901],[-84.8679,38.89921],[-84.86643,38.89871],[-84.8145,38.89546],[-84.81331,38.89523],[-84.78542,38.88075],[-84.78518,38.88001],[-84.78981,38.86206],[-84.79094,38.86057],[-84.82962,38.83112],[-84.82993,38.83037],[-84.81417,38.80034],[-84.81339,38.79851],[-84.81432,38.7847],[-84.8149,38.78428],[-84.88679,38.79463],[-84.88724,38.7948],[-84.93976,38.77602],[-84.94121,38.77565],[-84.99111,38.77842],[-84.99229,38.77815],[-85.10094,38.72662],[-85.10172,38.72651],[-85.13605,38.70062],[-85.1368,38.7001],[-85.17189,38.68836],[-85.17298,38.68807],[-85.21237,38.69501],[-85.21482,38.69625],[-85.2464,38.73147],[-85.24692,38.73194],[-85.2767,38.74135],[-85.27859,38.74159],[-85.36176,38.73051],[-85.3636,38.73037],[-85.41339,38.73683],[-85.41489,38.73677],[-85.43532,38.729],[-85.43609,38.7285],[-85.45169,38.71037],[-85.45184,38.7102],[-85.45673,38.68734],[-85.45665,38.68603],[-85.43897,38.65963],[-85.43854,38.65896],[-85.43832,38.60596],[-85.43824,38.60468],[-85.41528,38.55926],[-85.41489,38.55775],[-85.41777,38.53822],[-85.41777,38.53775],[-85.43188,38.52455],[-85.43291,38.52398],[-85.37901,38.51903],[-85.37859,38.51895],[-85.36594,38.50787],[-85.3657,38.5077],[-85.31944,38.49761],[-85.31592,38.49624],[-85.31416,38.49262],[-85.34598,38.45957],[-85.33133,38.44233],[-85.32496,38.4038],[-85.29568,38.37384],[-85.28281,38.35796],[-85.32459,38.30998],[-85.35159,38.30322],[-85.39057,38.30779],[-85.39323,38.30779],[-85.39558,38.30508],[-85.40906,38.30373],[-85.40791,38.29466],[-85.41025,38.29376],[-85.41318,38.29467],[-85.41669,38.30237],[-85.42255,38.30192],[-85.42666,38.30374],[-85.42491,38.29422],[-85.42725,38.29286],[-85.44718,38.30194],[-85.44894,38.3042],[-85.46127,38.28563],[-85.46713,38.28535],[-85.40596,38.26389],[-85.4051,38.26359],[-85.42905,38.11817],[-85.43035,38.10703],[-85.46195,38.09662],[-85.46954,38.09617],[-85.51222,38.01096],[-85.51689,38.01096],[-85.49995,37.99872],[-85.48886,37.99056],[-85.49995,37.98467],[-85.53381,37.96518],[-85.52563,37.95294],[-85.52972,37.94705],[-85.57987,37.91258],[-85.58395,37.91076],[-85.58042,37.88312],[-85.58683,37.87631],[-85.60548,37.87041],[-85.61364,37.86995],[-85.6107,37.85907],[-85.61828,37.85136],[-85.69802,37.81137],[-85.73414,37.81086],[-85.73833,37.81225],[-85.73833,37.84287],[-85.75207,37.84314],[-85.75241,37.87946],[-85.7737,37.87919],[-85.77541,37.97709],[-85.82382,37.9779],[-85.82399,37.99129],[-85.91154,37.99197],[-85.91171,38.00671],[-85.99891,37.99963],[-86.0305,37.99062],[-86.0317,37.96897],[-86.03703,37.95787],[-86.04904,37.95814],[-86.06569,37.97465],[-86.0832,38.00266]]]],"type":"MultiPolygon"}}, {"properties":{"name":"KyFromAbove Aerial Imagery (2018-2022)","id":"KYAPED_Phase_2","url":"https://kyraster.ky.gov/arcgis/services/ImageServices/Ky_KYAPED_Phase2_6IN_WGS84WM/ImageServer/WMSServer?LAYERS=0&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":false,"text":"KyFromAbove","url":"https://kyfromabove.ky.gov/"},"type":"wms","category":"photo","min_zoom":3,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[[-83.69079,36.58261],[-83.67433,36.60398],[-83.66336,36.60701],[-83.64681,36.62481],[-83.53182,36.66541],[-83.49345,36.67041],[-83.46347,36.66445],[-83.42102,36.66849],[-83.39584,36.67734],[-83.34788,36.7008],[-83.19272,36.74021],[-83.13511,36.74294],[-83.12551,36.76177],[-83.13247,36.76391],[-83.13505,36.78583],[-83.1233,36.78756],[-83.11155,36.80081],[-83.10429,36.8048],[-83.10273,36.81161],[-83.09907,36.81329],[-83.09937,36.82299],[-83.10225,36.82803],[-83.09901,36.83216],[-83.0911,36.83475],[-83.08804,36.83803],[-83.07512,36.84079],[-83.07587,36.85065],[-83.07271,36.85455],[-82.96652,36.86363],[-82.90734,36.87557],[-82.86855,36.89943],[-82.87869,36.90904],[-82.8589,36.92879],[-82.86334,36.94115],[-82.85957,36.95049],[-82.87449,36.96721],[-82.86718,36.97995],[-82.83834,36.98944],[-82.82882,37.00682],[-82.79135,37.0103],[-82.7598,37.0274],[-82.74841,37.02678],[-82.74367,37.04387],[-82.72287,37.04497],[-82.72663,37.07332],[-82.71747,37.07678],[-82.72419,37.08305],[-82.72676,37.11824],[-82.6547,37.15146],[-82.633,37.15476],[-82.61813,37.17024],[-82.50182,37.22621],[-82.43983,37.2474],[-82.34894,37.26799],[-82.34996,37.27216],[-82.3427,37.27402],[-82.34429,37.28125],[-82.32487,37.28335],[-82.31585,37.29493],[-82.1412,37.41613],[-82.06116,37.47223],[-82.04845,37.48241],[-82.01689,37.50386],[-81.96799,37.53804],[-81.96581,37.54067],[-81.96506,37.54339],[-81.97032,37.54676],[-81.97556,37.54603],[-81.98141,37.54202],[-81.98752,37.54301],[-81.98905,37.5428],[-81.98988,37.54212],[-81.99064,37.54049],[-81.99202,37.53879],[-81.99424,37.53771],[-81.99649,37.53823],[-81.99701,37.53948],[-81.9968,37.54081],[-81.99643,37.54174],[-81.99694,37.54308],[-81.99797,37.54335],[-82.00011,37.54252],[-82.00311,37.53851],[-82.00533,37.53501],[-82.00807,37.53311],[-82.01095,37.53279],[-82.01632,37.534],[-82.01687,37.53774],[-82.01792,37.53986],[-82.02021,37.54072],[-82.02684,37.53733],[-82.02976,37.53793],[-82.03394,37.5414],[-82.03678,37.54221],[-82.03787,37.54362],[-82.03865,37.54717],[-82.04016,37.5482],[-82.04376,37.5478],[-82.045,37.54579],[-82.04454,37.54163],[-82.04189,37.53487],[-82.04303,37.53057],[-82.04499,37.52798],[-82.04731,37.52804],[-82.04877,37.52928],[-82.04869,37.53397],[-82.05085,37.53564],[-82.05601,37.53672],[-82.0617,37.53589],[-82.06486,37.53721],[-82.06515,37.53891],[-82.06349,37.54269],[-82.06909,37.54951],[-82.07192,37.55097],[-82.07383,37.55556],[-82.08819,37.55571],[-82.09977,37.55279],[-82.10505,37.55269],[-82.10539,37.55433],[-82.10195,37.55842],[-82.10403,37.56011],[-82.11071,37.55873],[-82.1177,37.55918],[-82.12073,37.55621],[-82.12098,37.55437],[-82.12297,37.55156],[-82.12681,37.55109],[-82.13344,37.55261],[-82.13535,37.55752],[-82.13326,37.56039],[-82.13431,37.56334],[-82.14486,37.56681],[-82.14348,37.5708],[-82.12882,37.57234],[-82.12474,37.5763],[-82.13173,37.59287],[-82.14114,37.59504],[-82.14806,37.59033],[-82.15748,37.59238],[-82.15644,37.60538],[-82.15681,37.60959],[-82.16521,37.6076],[-82.16731,37.60781],[-82.16889,37.60868],[-82.16873,37.61023],[-82.1699,37.61316],[-82.16635,37.61685],[-82.1643,37.62019],[-82.1658,37.62048],[-82.16632,37.62109],[-82.16846,37.62176],[-82.17706,37.61789],[-82.18225,37.62235],[-82.18273,37.62531],[-82.18168,37.62721],[-82.17585,37.62987],[-82.17262,37.63217],[-82.17271,37.6341],[-82.17458,37.63561],[-82.17788,37.64114],[-82.175,37.64719],[-82.17702,37.64861],[-82.1859,37.6485],[-82.19108,37.64455],[-82.18775,37.63967],[-82.18572,37.62713],[-82.19228,37.62559],[-82.19928,37.62731],[-82.20556,37.62484],[-82.21445,37.62533],[-82.21602,37.6263],[-82.215,37.62778],[-82.22089,37.63464],[-82.21866,37.63684],[-82.21686,37.64166],[-82.22445,37.64475],[-82.22575,37.6528],[-82.24114,37.66179],[-82.25349,37.65638],[-82.25862,37.65666],[-82.26416,37.66063],[-82.27202,37.66388],[-82.27538,37.66879],[-82.27825,37.66986],[-82.28348,37.67632],[-82.28795,37.66803],[-82.29513,37.67063],[-82.295,37.67853],[-82.30271,37.67524],[-82.30495,37.67661],[-82.29594,37.68649],[-82.30322,37.69485],[-82.29633,37.70205],[-82.30089,37.70609],[-82.30693,37.70675],[-82.31133,37.71448],[-82.31138,37.71806],[-82.31617,37.72067],[-82.31944,37.73394],[-82.32688,37.73589],[-82.33428,37.74233],[-82.32774,37.74952],[-82.32169,37.75122],[-82.32099,37.75717],[-82.31745,37.76029],[-82.31169,37.76143],[-82.3123,37.76465],[-82.31718,37.76535],[-82.32572,37.76237],[-82.32924,37.76215],[-82.33435,37.76529],[-82.33365,37.76741],[-82.32307,37.77299],[-82.324,37.77504],[-82.32604,37.7758],[-82.33576,37.77393],[-82.33984,37.77628],[-82.33888,37.78236],[-82.34057,37.78591],[-82.34901,37.78674],[-82.35496,37.79315],[-82.36313,37.79437],[-82.36854,37.80108],[-82.37697,37.80207],[-82.37821,37.80382],[-82.37905,37.80886],[-82.38652,37.81807],[-82.38864,37.81767],[-82.39664,37.80855],[-82.40105,37.80856],[-82.40278,37.81247],[-82.39864,37.82174],[-82.39945,37.82954],[-82.409,37.83602],[-82.41309,37.84483],[-82.42,37.84574],[-82.42094,37.84756],[-82.41784,37.85107],[-82.41483,37.8561],[-82.42252,37.85917],[-82.42465,37.86094],[-82.42446,37.86305],[-82.42247,37.8641],[-82.40999,37.86571],[-82.40774,37.86713],[-82.40912,37.86861],[-82.41693,37.86913],[-82.41933,37.87248],[-82.41989,37.8846],[-82.4225,37.8865],[-82.428,37.88754],[-82.43311,37.89038],[-82.4341,37.89377],[-82.43809,37.90002],[-82.44762,37.90379],[-82.45164,37.90844],[-82.45978,37.90917],[-82.46345,37.91476],[-82.46756,37.91409],[-82.46874,37.91125],[-82.46801,37.90352],[-82.47123,37.89892],[-82.47466,37.89959],[-82.47667,37.90269],[-82.47442,37.90719],[-82.4756,37.91161],[-82.47905,37.91442],[-82.48218,37.91566],[-82.48715,37.91636],[-82.48853,37.91766],[-82.48822,37.91981],[-82.48054,37.9255],[-82.48166,37.92666],[-82.49681,37.92678],[-82.50229,37.93241],[-82.50258,37.93478],[-82.49951,37.93732],[-82.4911,37.93587],[-82.48968,37.93885],[-82.49416,37.9401],[-82.49771,37.94206],[-82.49798,37.94626],[-82.49507,37.94705],[-82.48758,37.94532],[-82.48111,37.94953],[-82.47168,37.95992],[-82.48499,37.96349],[-82.48503,37.97067],[-82.48232,37.97333],[-82.46901,37.97331],[-82.46471,37.97751],[-82.46489,37.98415],[-82.47262,37.98657],[-82.48293,37.98331],[-82.48491,37.98486],[-82.48642,37.98997],[-82.48608,37.99501],[-82.48827,37.99876],[-82.5001,37.99876],[-82.50865,38.00136],[-82.51357,37.99884],[-82.52002,38.00111],[-82.51549,38.006],[-82.51982,38.00789],[-82.52591,38.01798],[-82.52606,38.02627],[-82.53174,38.02904],[-82.53479,38.0318],[-82.53781,38.03535],[-82.53944,38.03873],[-82.53739,38.04508],[-82.54097,38.04727],[-82.54403,38.0522],[-82.54517,38.05519],[-82.54503,38.05887],[-82.54981,38.06347],[-82.54959,38.06839],[-82.55205,38.07102],[-82.55979,38.07252],[-82.56602,38.08094],[-82.57328,38.08148],[-82.58447,38.09066],[-82.58575,38.09479],[-82.58554,38.10653],[-82.58785,38.10894],[-82.59214,38.10995],[-82.59544,38.11257],[-82.59909,38.1169],[-82.60824,38.12075],[-82.61507,38.12],[-82.61945,38.12033],[-82.62131,38.12314],[-82.62212,38.13325],[-82.63683,38.13792],[-82.63862,38.14512],[-82.63878,38.15607],[-82.64514,38.16557],[-82.64265,38.17019],[-82.63953,38.17128],[-82.62739,38.17099],[-82.61942,38.16899],[-82.61267,38.1708],[-82.59996,38.19672],[-82.59852,38.21743],[-82.60915,38.22296],[-82.61303,38.23551],[-82.60866,38.24453],[-82.60482,38.24785],[-82.59331,38.24554],[-82.58491,38.24611],[-82.58117,38.2491],[-82.57481,38.26412],[-82.57464,38.27274],[-82.5797,38.28355],[-82.57958,38.29047],[-82.58353,38.29685],[-82.58042,38.30325],[-82.57313,38.31228],[-82.57184,38.31602],[-82.57322,38.31985],[-82.57568,38.32319],[-82.57688,38.32844],[-82.58494,38.33387],[-82.58947,38.34031],[-82.5964,38.34252],[-82.59879,38.34649],[-82.59764,38.36575],[-82.59433,38.372],[-82.59289,38.37576],[-82.59398,38.38041],[-82.60063,38.39053],[-82.59529,38.39869],[-82.59727,38.41033],[-82.59631,38.41801],[-82.61117,38.47165],[-82.65926,38.49435],[-82.69901,38.54244],[-82.73445,38.55813],[-82.78,38.55698],[-82.80279,38.56171],[-82.84487,38.58698],[-82.85608,38.6067],[-82.85998,38.66042],[-82.8812,38.68836],[-82.87425,38.70909],[-82.87353,38.74296],[-82.8926,38.75577],[-82.93408,38.74613],[-82.9691,38.72509],[-83.01796,38.72734],[-83.03037,38.72341],[-83.03792,38.70919],[-83.06106,38.6885],[-83.11184,38.67175],[-83.12449,38.63954],[-83.1477,38.61884],[-83.20603,38.61476],[-83.23751,38.6275],[-83.25861,38.62188],[-83.29212,38.59391],[-83.31665,38.60375],[-83.3269,38.63673],[-83.36863,38.65892],[-83.46197,38.66988],[-83.49081,38.69244],[-83.5311,38.70179],[-83.62774,38.67671],[-83.64669,38.63073],[-83.66827,38.62455],[-83.71719,38.64286],[-83.771,38.65327],[-83.78758,38.69931],[-83.84031,38.71442],[-83.84504,38.73796],[-83.86321,38.7585],[-83.92235,38.7683],[-83.95164,38.78456],[-83.96528,38.78624],[-84.03962,38.76914],[-84.08411,38.77016],[-84.14457,38.7896],[-84.20767,38.8004],[-84.228,38.81727],[-84.23744,38.8384],[-84.23294,38.88603],[-84.23289,38.8871],[-84.2864,38.95228],[-84.28874,38.95567],[-84.30005,38.9935],[-84.30716,39.0073],[-84.33985,39.03437],[-84.34089,39.03501],[-84.42565,39.0527],[-84.42652,39.05322],[-84.43401,39.09569],[-84.43495,39.09977],[-84.45282,39.11925],[-84.45619,39.12054],[-84.4792,39.11807],[-84.48059,39.11742],[-84.50434,39.09592],[-84.50612,39.09511],[-84.54971,39.09972],[-84.55096,39.09949],[-84.57256,39.08215],[-84.57306,39.08191],[-84.61685,39.07323],[-84.61845,39.07327],[-84.65842,39.09552],[-84.65993,39.09614],[-84.68487,39.1003],[-84.68593,39.10077],[-84.71788,39.13641],[-84.71826,39.13682],[-84.75139,39.14753],[-84.75244,39.14726],[-84.7842,39.1179],[-84.7874,39.11544],[-84.83142,39.10085],[-84.83248,39.10036],[-84.89694,39.05747],[-84.89729,39.05747],[-84.83027,38.97417],[-84.83003,38.97305],[-84.83299,38.96042],[-84.83363,38.95943],[-84.8762,38.92254],[-84.87706,38.92147],[-84.87723,38.90954],[-84.87702,38.90901],[-84.8679,38.89921],[-84.86643,38.89871],[-84.8145,38.89546],[-84.81331,38.89523],[-84.78542,38.88075],[-84.78518,38.88001],[-84.78981,38.86206],[-84.79094,38.86057],[-84.82962,38.83112],[-84.82993,38.83037],[-84.81417,38.80034],[-84.81339,38.79851],[-84.81432,38.7847],[-84.8149,38.78428],[-84.88679,38.79463],[-84.88724,38.7948],[-84.93976,38.77602],[-84.94121,38.77565],[-84.99111,38.77842],[-84.99229,38.77815],[-85.10094,38.72662],[-85.10172,38.72651],[-85.13605,38.70062],[-85.1368,38.7001],[-85.17189,38.68836],[-85.17298,38.68807],[-85.21237,38.69501],[-85.21482,38.69625],[-85.2464,38.73147],[-85.24692,38.73194],[-85.2767,38.74135],[-85.27859,38.74159],[-85.36176,38.73051],[-85.3636,38.73037],[-85.41339,38.73683],[-85.41489,38.73677],[-85.43532,38.729],[-85.43609,38.7285],[-85.45169,38.71037],[-85.45184,38.7102],[-85.45673,38.68734],[-85.45665,38.68603],[-85.43897,38.65963],[-85.43854,38.65896],[-85.43832,38.60596],[-85.43824,38.60468],[-85.41528,38.55926],[-85.41489,38.55775],[-85.41777,38.53822],[-85.41777,38.53775],[-85.43188,38.52455],[-85.43291,38.52398],[-85.46759,38.50802],[-85.48534,38.47536],[-85.51651,38.45602],[-85.58102,38.45038],[-85.61748,38.42559],[-85.64218,38.34231],[-85.67191,38.29922],[-85.75237,38.26101],[-85.78259,38.28633],[-85.81256,38.28285],[-85.83187,38.27071],[-85.83439,38.24867],[-85.84674,38.22249],[-85.8742,38.2077],[-85.89698,38.18329],[-85.90825,38.14643],[-85.90082,38.10483],[-85.92144,38.02204],[-85.94674,38.0039],[-85.99891,37.99963],[-86.0305,37.99062],[-86.0317,37.96897],[-86.03703,37.95787],[-86.04904,37.95814],[-86.06569,37.97465],[-86.0832,38.00266],[-86.10526,38.01108],[-86.13499,38.01429],[-86.17395,38.00779],[-86.22318,38.02719],[-86.23476,38.0364],[-86.26965,38.05557],[-86.2784,38.0719],[-86.2832,38.09757],[-86.27121,38.13587],[-86.29327,38.15831],[-86.31725,38.16727],[-86.34891,38.19526],[-86.36593,38.19762],[-86.37732,38.18735],[-86.37103,38.16411],[-86.3296,38.16067],[-86.31821,38.14634],[-86.33152,38.12729],[-86.38152,38.12549],[-86.39987,38.10266],[-86.44536,38.12595],[-86.46396,38.11808],[-86.46162,38.09946],[-86.43092,38.08672],[-86.4284,38.0702],[-86.44795,38.04886],[-86.52121,38.03914],[-86.52409,37.9765],[-86.50299,37.92998],[-86.5344,37.91343],[-86.58584,37.92175],[-86.59933,37.90652],[-86.59496,37.86403],[-86.62589,37.84367],[-86.65851,37.83913],[-86.6645,37.85579],[-86.64568,37.90557],[-86.67481,37.91466],[-86.72206,37.88816],[-86.74508,37.90217],[-86.77985,37.94719],[-86.79712,37.98841],[-86.81367,37.9971],[-86.84916,37.98784],[-86.91091,37.9365],[-86.9705,37.92866],[-87.00695,37.9192],[-87.04101,37.89479],[-87.03957,37.87113],[-87.06235,37.79878],[-87.09605,37.77916],[-87.13184,37.78058],[-87.13789,37.80617],[-87.16547,37.83761],[-87.21895,37.84391],[-87.29509,37.89318],[-87.34389,37.9086],[-87.40049,37.94057],[-87.44174,37.94114],[-87.50838,37.9045],[-87.52206,37.91325],[-87.53724,37.91882],[-87.54907,37.92401],[-87.55633,37.92803],[-87.56655,37.93649],[-87.57279,37.94837],[-87.57351,37.95173],[-87.57753,37.95168],[-87.57771,37.9678],[-87.59905,37.97139],[-87.60337,37.94984],[-87.61608,37.93603],[-87.6198,37.90879],[-87.58359,37.88466],[-87.57783,37.87207],[-87.60949,37.83155],[-87.64258,37.82369],[-87.67268,37.82682],[-87.68497,37.83884],[-87.68527,37.85655],[-87.66776,37.88788],[-87.68299,37.90094],[-87.72076,37.89053],[-87.74066,37.89346],[-87.76189,37.88977],[-87.78839,37.87416],[-87.83695,37.87643],[-87.88647,37.92438],[-87.90266,37.92449],[-87.93839,37.88627],[-87.90904,37.84833],[-87.89942,37.8091],[-87.92204,37.80074],[-87.95067,37.76792],[-87.99359,37.79118],[-88.04727,37.75139],[-88.13228,37.69126],[-88.15902,37.66098],[-88.15386,37.63117],[-88.12017,37.57132],[-88.07388,37.53796],[-88.05878,37.51019],[-88.06225,37.48802],[-88.0824,37.47147],[-88.28132,37.45148],[-88.32401,37.43225],[-88.34296,37.41263],[-88.36547,37.40166],[-88.37121,37.40273],[-88.40624,37.42479],[-88.40881,37.42522],[-88.46586,37.40055],[-88.47022,37.39625],[-88.48672,37.34015],[-88.48695,37.3396],[-88.51406,37.29246],[-88.51486,37.29069],[-88.50409,37.26514],[-88.50382,37.26485],[-88.5123,37.26295],[-88.51187,37.26201],[-88.45005,37.2062],[-88.44982,37.20599],[-88.42533,37.15421],[-88.42462,37.15173],[-88.44912,37.08875],[-88.44919,37.08776],[-88.45919,37.07523],[-88.46019,37.07447],[-88.48622,37.06659],[-88.48605,37.0648],[-88.56528,37.07521],[-88.56587,37.0752],[-88.62221,37.11775],[-88.62589,37.11946],[-88.68777,37.13938],[-88.69398,37.14115],[-88.73125,37.14368],[-88.73211,37.14396],[-88.78695,37.17858],[-88.79737,37.18485],[-88.83505,37.19649],[-88.86953,37.20971],[-88.9273,37.22636],[-88.93175,37.22759],[-88.98326,37.22868],[-89.00097,37.2244],[-89.07622,37.17513],[-89.08653,37.1656],[-89.10403,37.13197],[-89.11119,37.11905],[-89.15129,37.09049],[-89.1545,37.08891],[-89.17858,37.055],[-89.17938,37.05301],[-89.18248,37.03748],[-89.18251,37.03728],[-89.1736,37.01141],[-89.17112,37.00807],[-89.13844,36.98509],[-89.13301,36.982],[-89.1183,36.98188],[-89.11503,36.98033],[-89.09901,36.96139],[-89.09884,36.95785],[-89.13194,36.85744],[-89.13797,36.84735],[-89.17718,36.83578],[-89.17815,36.83459],[-89.17923,36.81291],[-89.17875,36.80993],[-89.17107,36.79812],[-89.16846,36.79557],[-89.13321,36.78823],[-89.12892,36.78768],[-89.11685,36.77561],[-89.11607,36.77242],[-89.12243,36.75484],[-89.12613,36.75173],[-89.15699,36.75597],[-89.16689,36.75963],[-89.19155,36.74722],[-89.19781,36.73941],[-89.20073,36.72014],[-89.19948,36.71605],[-89.17484,36.69396],[-89.16952,36.68888],[-89.16872,36.67189],[-89.15908,36.66635],[-89.18775,36.64111],[-89.19254,36.636],[-89.20261,36.60158],[-89.21356,36.58012],[-89.23184,36.56812],[-89.23654,36.56682],[-89.26806,36.56891],[-89.27171,36.57139],[-89.31943,36.62739],[-89.32472,36.63108],[-89.36486,36.62532],[-89.36555,36.62506],[-89.37545,36.61572],[-89.37637,36.61387],[-89.41729,36.49903],[-89.34795,36.50308],[-89.30028,36.50715],[-88.06598,36.49774],[-88.05332,36.49712],[-88.0453,36.50408],[-88.03948,36.51041],[-88.03413,36.53112],[-88.03249,36.54066],[-88.0412,36.58412],[-88.04513,36.60294],[-88.06821,36.65975],[-88.07053,36.67812],[-87.85045,36.6651],[-87.85355,36.63303],[-87.81577,36.63427],[-87.81886,36.73826],[-87.40945,36.74484],[-87.40717,36.64113],[-86.9982,36.64425],[-86.58995,36.65243],[-86.56427,36.63359],[-86.50781,36.65238],[-86.41587,36.65103],[-86.36545,36.64996],[-86.33208,36.64871],[-86.29245,36.64527],[-86.19791,36.63973],[-86.12894,36.63612],[-85.83242,36.62211],[-85.78445,36.62202],[-85.67737,36.61841],[-85.50117,36.615],[-85.33222,36.62327],[-85.3255,36.62472],[-85.3002,36.62443],[-85.29337,36.62635],[-85.19552,36.62539],[-85.11243,36.62299],[-84.99612,36.61789],[-84.93593,36.61173],[-84.82297,36.60403],[-84.54335,36.59594],[-84.44599,36.59671],[-84.0503,36.59093],[-83.82598,36.58477],[-83.78356,36.5839],[-83.69079,36.58261]]],[[[-89.55251,36.57723],[-89.55264,36.57718],[-89.57151,36.55257],[-89.54296,36.50496],[-89.53912,36.4981],[-89.4885,36.49755],[-89.4852,36.49745],[-89.46589,36.52995],[-89.46545,36.53616],[-89.4808,36.56969],[-89.48089,36.56977],[-89.5205,36.57943],[-89.52234,36.58018],[-89.55251,36.57723]]]],"type":"MultiPolygon"}}, {"properties":{"name":"MassGIS 2019 Orthos","id":"MassGIS-2019-Orthos","url":"https://tiles.arcgis.com/tiles/hGdibHYSPO59RG1h/arcgis/rest/services/USGS_Orthos_2019/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"MassGIS","url":"https://www.mass.gov/info-details/massgis-data-2019-aerial-imagery"},"type":"tms","category":"photo","min_zoom":7,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-72.13569,42.03025],[-72.0635,42.02735],[-71.80067,42.02351],[-71.79925,42.00807],[-71.52888,42.01499],[-71.49744,42.01725],[-71.38127,42.01885],[-71.38174,41.8932],[-71.33865,41.89865],[-71.34086,41.87872],[-71.33392,41.86229],[-71.3422,41.8448],[-71.3352,41.8355],[-71.3449,41.828],[-71.3472,41.8231],[-71.33914,41.80842],[-71.34156,41.79817],[-71.33407,41.79455],[-71.32885,41.7811],[-71.26139,41.7523],[-71.1954,41.67514],[-71.17588,41.67154],[-71.17605,41.6681],[-71.13291,41.6601],[-71.13569,41.6284],[-71.14047,41.62389],[-71.14059,41.6051],[-71.13131,41.59231],[-71.12047,41.49717],[-71.09996,41.43386],[-70.81959,41.23192],[-69.89537,41.21643],[-69.88713,42.0519],[-70.8759,42.35302],[-70.45842,42.67694],[-70.81567,42.87204],[-70.82963,42.86875],[-70.84776,42.86088],[-70.88566,42.88288],[-70.90348,42.88671],[-70.91465,42.88661],[-70.92973,42.88504],[-70.94967,42.87588],[-70.96702,42.86887],[-71.03128,42.85924],[-71.04483,42.84869],[-71.05395,42.83337],[-71.06442,42.80626],[-71.13277,42.82145],[-71.16667,42.80891],[-71.18617,42.79088],[-71.18181,42.73732],[-71.22391,42.74643],[-71.23732,42.74491],[-71.24598,42.74231],[-71.25526,42.73659],[-71.26787,42.72603],[-71.27894,42.71136],[-71.29464,42.69704],[-71.69663,42.70572],[-71.97286,42.71307],[-72.6872,42.73348],[-73.26496,42.74594],[-73.50814,42.08626],[-73.49688,42.04968],[-73.43281,42.05059],[-73.03678,42.03929],[-72.81378,42.03674],[-72.81705,41.99769],[-72.76673,42.00327],[-72.76572,42.02276],[-72.75868,42.02439],[-72.75714,42.03635],[-72.69933,42.03696],[-72.64019,42.03205],[-72.60797,42.03108],[-72.60717,42.02515],[-72.58216,42.02474],[-72.57278,42.03022],[-72.53156,42.03458],[-72.13569,42.03025]]],"type":"Polygon"}}, -{"properties":{"name":"MassGIS LIDAR Shaded Relief","id":"MassGIS-LIDAR-Shaded-Relief","url":"https://tiles.arcgis.com/tiles/hGdibHYSPO59RG1h/arcgis/rest/services/LiDAR_ShadedRelief/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"MassGIS","url":"https://www.mass.gov/info-details/massgis-data-lidar-terrain-data"},"type":"tms","category":"elevation","min_zoom":6,"max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-72.13569,42.03025],[-72.0635,42.02735],[-71.80067,42.02351],[-71.79925,42.00807],[-71.52888,42.01499],[-71.49744,42.01725],[-71.38127,42.01885],[-71.38174,41.8932],[-71.33865,41.89865],[-71.34086,41.87872],[-71.33392,41.86229],[-71.3422,41.8448],[-71.3352,41.8355],[-71.3449,41.828],[-71.3472,41.8231],[-71.33914,41.80842],[-71.34156,41.79817],[-71.33407,41.79455],[-71.32885,41.7811],[-71.26139,41.7523],[-71.1954,41.67514],[-71.17588,41.67154],[-71.17605,41.6681],[-71.13291,41.6601],[-71.13569,41.6284],[-71.14047,41.62389],[-71.14059,41.6051],[-71.13131,41.59231],[-71.12047,41.49717],[-71.09996,41.43386],[-70.81959,41.23192],[-69.89537,41.21643],[-69.88713,42.0519],[-70.8759,42.35302],[-70.45842,42.67694],[-70.81567,42.87204],[-70.82963,42.86875],[-70.84776,42.86088],[-70.88566,42.88288],[-70.90348,42.88671],[-70.91465,42.88661],[-70.92973,42.88504],[-70.94967,42.87588],[-70.96702,42.86887],[-71.03128,42.85924],[-71.04483,42.84869],[-71.05395,42.83337],[-71.06442,42.80626],[-71.13277,42.82145],[-71.16667,42.80891],[-71.18617,42.79088],[-71.18181,42.73732],[-71.22391,42.74643],[-71.23732,42.74491],[-71.24598,42.74231],[-71.25526,42.73659],[-71.26787,42.72603],[-71.27894,42.71136],[-71.29464,42.69704],[-71.69663,42.70572],[-71.97286,42.71307],[-72.6872,42.73348],[-73.26496,42.74594],[-73.50814,42.08626],[-73.49688,42.04968],[-73.43281,42.05059],[-73.03678,42.03929],[-72.81378,42.03674],[-72.81705,41.99769],[-72.76673,42.00327],[-72.76572,42.02276],[-72.75868,42.02439],[-72.75714,42.03635],[-72.69933,42.03696],[-72.64019,42.03205],[-72.60797,42.03108],[-72.60717,42.02515],[-72.58216,42.02474],[-72.57278,42.03022],[-72.53156,42.03458],[-72.13569,42.03025]]],"type":"Polygon"}}, +{"properties":{"name":"MassGIS LIDAR Shaded Relief","id":"MassGIS-LIDAR-Shaded-Relief","url":"https://tiles.arcgis.com/tiles/hGdibHYSPO59RG1h/arcgis/rest/services/LiDAR_ShadedRelief_18Nant_21EC/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"MassGIS","url":"https://www.mass.gov/info-details/massgis-data-lidar-terrain-data"},"type":"tms","category":"elevation","min_zoom":7,"max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-72.13569,42.03025],[-72.0635,42.02735],[-71.80067,42.02351],[-71.79925,42.00807],[-71.52888,42.01499],[-71.49744,42.01725],[-71.38127,42.01885],[-71.38174,41.8932],[-71.33865,41.89865],[-71.34086,41.87872],[-71.33392,41.86229],[-71.3422,41.8448],[-71.3352,41.8355],[-71.3449,41.828],[-71.3472,41.8231],[-71.33914,41.80842],[-71.34156,41.79817],[-71.33407,41.79455],[-71.32885,41.7811],[-71.26139,41.7523],[-71.1954,41.67514],[-71.17588,41.67154],[-71.17605,41.6681],[-71.13291,41.6601],[-71.13569,41.6284],[-71.14047,41.62389],[-71.14059,41.6051],[-71.13131,41.59231],[-71.12047,41.49717],[-71.09996,41.43386],[-70.81959,41.23192],[-69.89537,41.21643],[-69.88713,42.0519],[-70.8759,42.35302],[-70.45842,42.67694],[-70.81567,42.87204],[-70.82963,42.86875],[-70.84776,42.86088],[-70.88566,42.88288],[-70.90348,42.88671],[-70.91465,42.88661],[-70.92973,42.88504],[-70.94967,42.87588],[-70.96702,42.86887],[-71.03128,42.85924],[-71.04483,42.84869],[-71.05395,42.83337],[-71.06442,42.80626],[-71.13277,42.82145],[-71.16667,42.80891],[-71.18617,42.79088],[-71.18181,42.73732],[-71.22391,42.74643],[-71.23732,42.74491],[-71.24598,42.74231],[-71.25526,42.73659],[-71.26787,42.72603],[-71.27894,42.71136],[-71.29464,42.69704],[-71.69663,42.70572],[-71.97286,42.71307],[-72.6872,42.73348],[-73.26496,42.74594],[-73.50814,42.08626],[-73.49688,42.04968],[-73.43281,42.05059],[-73.03678,42.03929],[-72.81378,42.03674],[-72.81705,41.99769],[-72.76673,42.00327],[-72.76572,42.02276],[-72.75868,42.02439],[-72.75714,42.03635],[-72.69933,42.03696],[-72.64019,42.03205],[-72.60797,42.03108],[-72.60717,42.02515],[-72.58216,42.02474],[-72.57278,42.03022],[-72.53156,42.03458],[-72.13569,42.03025]]],"type":"Polygon"}}, +{"properties":{"name":"MassGIS LIDAR Slope","id":"MassGIS-LIDAR-Slope","url":"https://tiles.arcgis.com/tiles/hGdibHYSPO59RG1h/arcgis/rest/services/Slope_Grayscale_2013to2021/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"MassGIS","url":"https://www.mass.gov/info-details/massgis-data-lidar-terrain-data"},"type":"tms","category":"elevation","min_zoom":7,"max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-72.13569,42.03025],[-72.0635,42.02735],[-71.80067,42.02351],[-71.79925,42.00807],[-71.52888,42.01499],[-71.49744,42.01725],[-71.38127,42.01885],[-71.38174,41.8932],[-71.33865,41.89865],[-71.34086,41.87872],[-71.33392,41.86229],[-71.3422,41.8448],[-71.3352,41.8355],[-71.3449,41.828],[-71.3472,41.8231],[-71.33914,41.80842],[-71.34156,41.79817],[-71.33407,41.79455],[-71.32885,41.7811],[-71.26139,41.7523],[-71.1954,41.67514],[-71.17588,41.67154],[-71.17605,41.6681],[-71.13291,41.6601],[-71.13569,41.6284],[-71.14047,41.62389],[-71.14059,41.6051],[-71.13131,41.59231],[-71.12047,41.49717],[-71.09996,41.43386],[-70.81959,41.23192],[-69.89537,41.21643],[-69.88713,42.0519],[-70.8759,42.35302],[-70.45842,42.67694],[-70.81567,42.87204],[-70.82963,42.86875],[-70.84776,42.86088],[-70.88566,42.88288],[-70.90348,42.88671],[-70.91465,42.88661],[-70.92973,42.88504],[-70.94967,42.87588],[-70.96702,42.86887],[-71.03128,42.85924],[-71.04483,42.84869],[-71.05395,42.83337],[-71.06442,42.80626],[-71.13277,42.82145],[-71.16667,42.80891],[-71.18617,42.79088],[-71.18181,42.73732],[-71.22391,42.74643],[-71.23732,42.74491],[-71.24598,42.74231],[-71.25526,42.73659],[-71.26787,42.72603],[-71.27894,42.71136],[-71.29464,42.69704],[-71.69663,42.70572],[-71.97286,42.71307],[-72.6872,42.73348],[-73.26496,42.74594],[-73.50814,42.08626],[-73.49688,42.04968],[-73.43281,42.05059],[-73.03678,42.03929],[-72.81378,42.03674],[-72.81705,41.99769],[-72.76673,42.00327],[-72.76572,42.02276],[-72.75868,42.02439],[-72.75714,42.03635],[-72.69933,42.03696],[-72.64019,42.03205],[-72.60797,42.03108],[-72.60717,42.02515],[-72.58216,42.02474],[-72.57278,42.03022],[-72.53156,42.03458],[-72.13569,42.03025]]],"type":"Polygon"}}, {"properties":{"name":"MassGIS 2021 Aerial Imagery","id":"MassGIS_2021_Aerial","url":"https://tiles.arcgis.com/tiles/hGdibHYSPO59RG1h/arcgis/rest/services/orthos2021/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"MassGIS","url":"https://www.mass.gov/info-details/massgis-data-2021-aerial-imagery"},"type":"tms","category":"photo","max_zoom":20,"best":true},"type":"Feature","geometry":{"coordinates":[[[-72.13569,42.03025],[-72.0635,42.02735],[-71.80067,42.02351],[-71.79925,42.00807],[-71.52888,42.01499],[-71.49744,42.01725],[-71.38127,42.01885],[-71.38174,41.8932],[-71.33865,41.89865],[-71.34086,41.87872],[-71.33392,41.86229],[-71.3422,41.8448],[-71.3352,41.8355],[-71.3449,41.828],[-71.3472,41.8231],[-71.33914,41.80842],[-71.34156,41.79817],[-71.33407,41.79455],[-71.32885,41.7811],[-71.26139,41.7523],[-71.1954,41.67514],[-71.17588,41.67154],[-71.17605,41.6681],[-71.13291,41.6601],[-71.13569,41.6284],[-71.14047,41.62389],[-71.14059,41.6051],[-71.13131,41.59231],[-71.12047,41.49717],[-71.09996,41.43386],[-70.81959,41.23192],[-69.89537,41.21643],[-69.88713,42.0519],[-70.8759,42.35302],[-70.45842,42.67694],[-70.81567,42.87204],[-70.82963,42.86875],[-70.84776,42.86088],[-70.88566,42.88288],[-70.90348,42.88671],[-70.91465,42.88661],[-70.92973,42.88504],[-70.94967,42.87588],[-70.96702,42.86887],[-71.03128,42.85924],[-71.04483,42.84869],[-71.05395,42.83337],[-71.06442,42.80626],[-71.13277,42.82145],[-71.16667,42.80891],[-71.18617,42.79088],[-71.18181,42.73732],[-71.22391,42.74643],[-71.23732,42.74491],[-71.24598,42.74231],[-71.25526,42.73659],[-71.26787,42.72603],[-71.27894,42.71136],[-71.29464,42.69704],[-71.69663,42.70572],[-71.97286,42.71307],[-72.6872,42.73348],[-73.26496,42.74594],[-73.50814,42.08626],[-73.49688,42.04968],[-73.43281,42.05059],[-73.03678,42.03929],[-72.81378,42.03674],[-72.81705,41.99769],[-72.76673,42.00327],[-72.76572,42.02276],[-72.75868,42.02439],[-72.75714,42.03635],[-72.69933,42.03696],[-72.64019,42.03205],[-72.60797,42.03108],[-72.60717,42.02515],[-72.58216,42.02474],[-72.57278,42.03022],[-72.53156,42.03458],[-72.13569,42.03025]]],"type":"Polygon"}}, {"properties":{"name":"MassGIS Basemap","id":"MassGIS-basemap","url":"https://tiles.arcgis.com/tiles/hGdibHYSPO59RG1h/arcgis/rest/services/MassGISBasemap/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"MassGIS","url":"https://www.mass.gov/service-details/massgis-base-map"},"type":"tms","category":"map","min_zoom":7,"max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-72.13569,42.03025],[-72.0635,42.02735],[-71.80067,42.02351],[-71.79925,42.00807],[-71.52888,42.01499],[-71.49744,42.01725],[-71.38127,42.01885],[-71.38174,41.8932],[-71.33865,41.89865],[-71.34086,41.87872],[-71.33392,41.86229],[-71.3422,41.8448],[-71.3352,41.8355],[-71.3449,41.828],[-71.3472,41.8231],[-71.33914,41.80842],[-71.34156,41.79817],[-71.33407,41.79455],[-71.32885,41.7811],[-71.26139,41.7523],[-71.1954,41.67514],[-71.17588,41.67154],[-71.17605,41.6681],[-71.13291,41.6601],[-71.13569,41.6284],[-71.14047,41.62389],[-71.14059,41.6051],[-71.13131,41.59231],[-71.12047,41.49717],[-71.09996,41.43386],[-70.81959,41.23192],[-69.89537,41.21643],[-69.88713,42.0519],[-70.8759,42.35302],[-70.45842,42.67694],[-70.81567,42.87204],[-70.82963,42.86875],[-70.84776,42.86088],[-70.88566,42.88288],[-70.90348,42.88671],[-70.91465,42.88661],[-70.92973,42.88504],[-70.94967,42.87588],[-70.96702,42.86887],[-71.03128,42.85924],[-71.04483,42.84869],[-71.05395,42.83337],[-71.06442,42.80626],[-71.13277,42.82145],[-71.16667,42.80891],[-71.18617,42.79088],[-71.18181,42.73732],[-71.22391,42.74643],[-71.23732,42.74491],[-71.24598,42.74231],[-71.25526,42.73659],[-71.26787,42.72603],[-71.27894,42.71136],[-71.29464,42.69704],[-71.69663,42.70572],[-71.97286,42.71307],[-72.6872,42.73348],[-73.26496,42.74594],[-73.50814,42.08626],[-73.49688,42.04968],[-73.43281,42.05059],[-73.03678,42.03929],[-72.81378,42.03674],[-72.81705,41.99769],[-72.76673,42.00327],[-72.76572,42.02276],[-72.75868,42.02439],[-72.75714,42.03635],[-72.69933,42.03696],[-72.64019,42.03205],[-72.60797,42.03108],[-72.60717,42.02515],[-72.58216,42.02474],[-72.57278,42.03022],[-72.53156,42.03458],[-72.13569,42.03025]]],"type":"Polygon"}}, {"properties":{"name":"MD Latest 6 Inch Aerial Imagery","id":"geodata.md.gov-MD_SixInchImagery","url":"https://geodata.md.gov/imap/rest/services/Imagery/MD_SixInchImagery/ImageServer/exportImage?f=image&format=jpg&imageSR={wkid}&bboxSR={wkid}&bbox={bbox}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"DoIT, MD iMap, MDP","url":"https://imap.maryland.gov/Pages/imagery-products.aspx"},"type":"wms","category":"photo","max_zoom":20,"best":true},"type":"Feature","geometry":{"coordinates":[[[-76.68182,38.2309],[-76.23413,37.92037],[-75.86369,37.90953],[-75.77897,37.96538],[-75.73563,37.96247],[-75.65551,37.95295],[-75.64177,37.97654],[-75.63825,37.97935],[-75.6364,37.97962],[-75.63186,37.97539],[-75.62967,37.97583],[-75.62924,37.97712],[-75.63405,37.98334],[-75.63344,37.98581],[-75.63053,37.98801],[-75.62666,37.98882],[-75.62448,37.99413],[-75.23865,38.02692],[-75.187,38.09755],[-75.09773,38.30907],[-75.08151,38.32321],[-75.04984,38.40222],[-75.04844,38.4515],[-75.3717,38.45251],[-75.69099,38.46058],[-75.78644,39.6546],[-75.78839,39.72236],[-77.72441,39.72178],[-78.16762,39.72239],[-78.29428,39.72282],[-78.31703,39.723],[-78.35335,39.7227],[-78.38831,39.72287],[-78.61083,39.72298],[-78.71839,39.72311],[-78.82587,39.72306],[-79.04114,39.72298],[-79.4775,39.72141],[-79.48316,39.53264],[-79.48977,39.20526],[-79.47184,39.20113],[-79.42394,39.22334],[-79.35897,39.27313],[-79.28936,39.29711],[-79.21177,39.36217],[-79.16147,39.38739],[-79.11015,39.43129],[-79.08959,39.46048],[-79.06929,39.4734],[-79.03058,39.46323],[-78.9602,39.43526],[-78.87437,39.52238],[-78.81472,39.56223],[-78.77953,39.63716],[-78.77599,39.64415],[-78.76599,39.64681],[-78.76717,39.64291],[-78.7791,39.62509],[-78.77927,39.62103],[-78.76341,39.61814],[-78.74773,39.62526],[-78.73768,39.62156],[-78.73472,39.61494],[-78.7394,39.61025],[-78.74232,39.61002],[-78.74704,39.60774],[-78.75026,39.61048],[-78.76146,39.61131],[-78.7785,39.60582],[-78.77858,39.60053],[-78.76601,39.58509],[-78.75618,39.57999],[-78.74455,39.57897],[-78.74245,39.58022],[-78.73979,39.58581],[-78.73425,39.58552],[-78.73283,39.5751],[-78.72494,39.56229],[-78.70734,39.55538],[-78.68979,39.54548],[-78.67571,39.53992],[-78.65361,39.53344],[-78.60589,39.53158],[-78.56581,39.51881],[-78.52718,39.5202],[-78.47002,39.51411],[-78.46272,39.51854],[-78.45788,39.52344],[-78.45805,39.53463],[-78.45037,39.53767],[-78.43655,39.53807],[-78.41732,39.54562],[-78.39483,39.58241],[-78.39355,39.58568],[-78.39535,39.59101],[-78.42504,39.60913],[-78.43195,39.61835],[-78.42925,39.62225],[-78.42204,39.62341],[-78.38741,39.60823],[-78.37638,39.60595],[-78.37149,39.61005],[-78.37071,39.6129],[-78.382,39.62334],[-78.3805,39.62787],[-78.37423,39.62899],[-78.3593,39.62414],[-78.35277,39.62704],[-78.35119,39.63012],[-78.35621,39.63597],[-78.35651,39.63838],[-78.35282,39.63957],[-78.33887,39.63676],[-78.2842,39.61911],[-78.26566,39.61673],[-78.25201,39.63921],[-78.22283,39.66042],[-78.22235,39.6623],[-78.22575,39.66614],[-78.23201,39.67178],[-78.22793,39.67495],[-78.20236,39.67588],[-78.18283,39.69348],[-78.17674,39.69517],[-78.11331,39.68024],[-78.08979,39.67099],[-78.04816,39.64066],[-78.01177,39.60258],[-78.00688,39.6002],[-77.97375,39.59795],[-77.96474,39.60509],[-77.95753,39.60675],[-77.9528,39.60324],[-77.95289,39.58538],[-77.94336,39.58353],[-77.93624,39.58677],[-77.93384,39.60959],[-77.93946,39.61455],[-77.94281,39.61511],[-77.94259,39.61756],[-77.93834,39.61848],[-77.93405,39.61782],[-77.92937,39.61349],[-77.92641,39.60618],[-77.92336,39.6041],[-77.89564,39.59597],[-77.88337,39.59775],[-77.87938,39.6006],[-77.87981,39.60562],[-77.88487,39.61468],[-77.87397,39.61256],[-77.8662,39.60817],[-77.83822,39.60463],[-77.83058,39.59154],[-77.8371,39.56891],[-77.88423,39.56375],[-77.89092,39.55733],[-77.88774,39.55091],[-77.86714,39.5388],[-77.86697,39.51497],[-77.86131,39.51285],[-77.84303,39.51556],[-77.83848,39.52007],[-77.83702,39.53072],[-77.82637,39.5286],[-77.82432,39.52364],[-77.82749,39.51642],[-77.84629,39.50662],[-77.84912,39.50238],[-77.84706,39.49775],[-77.82749,39.49218],[-77.80088,39.48821],[-77.79003,39.4902],[-77.78479,39.49652],[-77.7814,39.49838],[-77.77406,39.49864],[-77.76943,39.49735],[-77.76698,39.49513],[-77.76707,39.49238],[-77.77033,39.49053],[-77.78827,39.48609],[-77.79797,39.48185],[-77.79951,39.47516],[-77.79719,39.47079],[-77.77908,39.46247],[-77.78067,39.46118],[-77.79844,39.46294],[-77.79998,39.45727],[-77.79616,39.45266],[-77.78595,39.44637],[-77.79028,39.44309],[-77.8011,39.4413],[-77.80535,39.43762],[-77.80088,39.43172],[-77.75454,39.42207],[-77.73668,39.38928],[-77.75316,39.384],[-77.75411,39.37896],[-77.74424,39.3631],[-77.74539,39.3564],[-77.74896,39.35089],[-77.7611,39.34518],[-77.76166,39.34014],[-77.75848,39.33476],[-77.75415,39.3329],[-77.72703,39.32155],[-77.71437,39.32268],[-77.70613,39.32198],[-77.69795,39.31914],[-77.69349,39.31883],[-77.68836,39.31986],[-77.68136,39.32553],[-77.67561,39.32573],[-77.67257,39.32304],[-77.66347,39.31723],[-77.64761,39.31194],[-77.63765,39.30885],[-77.63223,39.30873],[-77.62306,39.30581],[-77.60459,39.30403],[-77.59422,39.30277],[-77.58541,39.30425],[-77.57429,39.30721],[-77.56749,39.30742],[-77.56335,39.30571],[-77.55996,39.30166],[-77.55972,39.29601],[-77.56137,39.28841],[-77.55798,39.28401],[-77.55324,39.28145],[-77.54981,39.27628],[-77.54215,39.27059],[-77.53972,39.26808],[-77.53704,39.26485],[-77.53266,39.26256],[-77.52065,39.25866],[-77.52017,39.25774],[-77.51846,39.25689],[-77.51186,39.25385],[-77.50882,39.25272],[-77.50509,39.25208],[-77.50345,39.25206],[-77.49978,39.25145],[-77.49646,39.25146],[-77.48713,39.24748],[-77.48481,39.24616],[-77.48392,39.24494],[-77.48279,39.24408],[-77.4808,39.24154],[-77.47892,39.24023],[-77.4781,39.24045],[-77.47119,39.23518],[-77.45692,39.22745],[-77.45578,39.22369],[-77.45715,39.21977],[-77.47138,39.20891],[-77.47348,39.20363],[-77.47357,39.19332],[-77.4755,39.19049],[-77.47915,39.18743],[-77.48425,39.18523],[-77.50322,39.18141],[-77.50846,39.17925],[-77.51425,39.17242],[-77.52395,39.1487],[-77.52697,39.14666],[-77.52698,39.14344],[-77.52608,39.13532],[-77.52417,39.12883],[-77.5226,39.12525],[-77.52048,39.1218],[-77.5182,39.12019],[-77.51427,39.11871],[-77.50672,39.11764],[-77.48702,39.11223],[-77.48001,39.10757],[-77.47481,39.10147],[-77.47159,39.09476],[-77.46772,39.08884],[-77.46879,39.08658],[-77.46881,39.08574],[-77.46831,39.08497],[-77.46531,39.08156],[-77.46389,39.07957],[-77.46318,39.07854],[-77.46228,39.07631],[-77.46089,39.07513],[-77.45726,39.07359],[-77.45264,39.07255],[-77.44928,39.0724],[-77.44404,39.07149],[-77.44179,39.07144],[-77.43791,39.07096],[-77.43355,39.07014],[-77.42883,39.06885],[-77.42237,39.06758],[-77.41278,39.06656],[-77.40448,39.0655],[-77.3978,39.06546],[-77.3872,39.06383],[-77.33345,39.06458],[-77.33364,39.05958],[-77.32842,39.05798],[-77.32336,39.05607],[-77.32009,39.05545],[-77.31727,39.05403],[-77.31516,39.05251],[-77.31255,39.052],[-77.3058,39.05272],[-77.29118,39.04613],[-77.2739,39.03452],[-77.25103,39.02913],[-77.24582,39.02585],[-77.24286,39.02095],[-77.24519,39.0148],[-77.25346,39.00658],[-77.2535,38.99696],[-77.25236,38.99568],[-77.24987,38.99466],[-77.24822,38.99179],[-77.24536,38.98438],[-77.24438,38.98278],[-77.2391,38.98081],[-77.23511,38.97699],[-77.23191,38.98023],[-77.2293,38.98032],[-77.22419,38.97373],[-77.2193,38.9719],[-77.21133,38.97022],[-77.20535,38.9706],[-77.20028,38.96889],[-77.17394,38.96885],[-77.17025,38.96721],[-77.16608,38.96833],[-77.1595,38.96627],[-77.14899,38.96611],[-77.14273,38.96329],[-77.13647,38.95784],[-77.12904,38.94743],[-77.12718,38.94109],[-77.11953,38.93452],[-77.04097,38.99592],[-76.90966,38.89285],[-77.03944,38.79151],[-77.04128,38.70775],[-77.10995,38.68671],[-77.12488,38.67077],[-77.12042,38.63444],[-77.24024,38.58414],[-77.29912,38.46972],[-77.28762,38.39576],[-77.26152,38.35929],[-77.2229,38.34731],[-77.02635,38.42213],[-76.94,38.27053],[-76.74465,38.20527],[-76.68182,38.2309]]],"type":"Polygon"}}, @@ -586,7 +594,7 @@ {"properties":{"name":"Dakota County GIS 2019 Spring Leaf-Off 6-Inch","id":"DCGIS-County-Imagery-2019-Spring-Leaf-Off-6-Inch","url":"https://gisimg.co.dakota.mn.us/arcgis/services/AerialPhotography/2019AirPhotoLeafOff6Inch_Spring/ImageServer/WMSServer?LAYERS=2019AirPhotoLeafOff6Inch_Spring:default&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":false,"text":"Dakota County GIS","url":"https://dakotacounty.us"},"type":"wms","category":"photo","min_zoom":5,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-93.32967,44.79107],[-93.32964,44.63037],[-93.28189,44.63074],[-93.28169,44.47194],[-93.28176,44.47137],[-93.0395,44.47103],[-93.03924,44.51125],[-92.91932,44.51049],[-92.91899,44.54325],[-92.79268,44.54324],[-92.7926,44.62971],[-92.73207,44.62948],[-92.73122,44.71411],[-92.80342,44.74652],[-92.82767,44.75056],[-92.85209,44.74695],[-92.85959,44.75359],[-92.87724,44.77283],[-92.88149,44.77492],[-92.9049,44.77408],[-92.92808,44.78111],[-92.93969,44.77563],[-92.94843,44.76786],[-92.95859,44.76724],[-92.98604,44.77501],[-92.99291,44.77517],[-93.00306,44.77206],[-93.01685,44.77635],[-93.02153,44.79431],[-93.00523,44.81541],[-93.0119,44.83657],[-93.00859,44.85652],[-93.01041,44.86586],[-93.02074,44.89279],[-93.0309,44.8967],[-93.04083,44.90391],[-93.04445,44.91514],[-93.04725,44.9195],[-93.04724,44.92318],[-93.12863,44.92335],[-93.12882,44.91965],[-93.13257,44.91243],[-93.1641,44.89048],[-93.18289,44.8872],[-93.20075,44.86486],[-93.20325,44.85263],[-93.22179,44.83825],[-93.25188,44.81146],[-93.28177,44.80611],[-93.30453,44.7945],[-93.32645,44.79245],[-93.32961,44.79107],[-93.32967,44.79107]]],"type":"Polygon"}}, {"properties":{"name":"Hennepin County Orthoimagery (2020)","id":"Hennepin_Ortho_2020","url":"https://gis.hennepin.us/arcgis/services/Imagery/UTM_Aerial_2020/MapServer/WMSServer?FORMAT=image/jpeg&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=1&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":false,"text":"Hennepin County GIS","url":"https://gis-hennepin.hub.arcgis.com/"},"type":"wms","category":"historicphoto","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-93.20054,44.86147],[-93.1781,44.8865],[-93.17243,44.88651],[-93.17245,44.89458],[-93.18381,44.89456],[-93.18382,44.8986],[-93.18951,44.89859],[-93.18952,44.90666],[-93.19521,44.90665],[-93.19534,44.947],[-93.2018,44.94696],[-93.20189,44.95506],[-93.20673,44.95505],[-93.20702,45.03576],[-93.28101,45.03561],[-93.28115,45.06788],[-93.27548,45.06789],[-93.27566,45.10824],[-93.28136,45.10823],[-93.28137,45.10984],[-93.28426,45.10984],[-93.28427,45.11226],[-93.28708,45.11225],[-93.28711,45.11629],[-93.2928,45.11629],[-93.29289,45.13241],[-93.29858,45.1324],[-93.2986,45.13643],[-93.3043,45.13642],[-93.30491,45.14448],[-93.31005,45.14447],[-93.31007,45.14851],[-93.32147,45.14848],[-93.3215,45.15251],[-93.3272,45.1525],[-93.32723,45.15653],[-93.33863,45.1565],[-93.33866,45.16053],[-93.34436,45.16051],[-93.3444,45.16762],[-93.34599,45.16762],[-93.34599,45.16858],[-93.35011,45.16857],[-93.35014,45.1726],[-93.35585,45.17258],[-93.35586,45.17662],[-93.36158,45.17661],[-93.3616,45.18064],[-93.37301,45.18061],[-93.37304,45.18464],[-93.37874,45.18462],[-93.37877,45.18865],[-93.39018,45.18863],[-93.39021,45.19265],[-93.39591,45.19263],[-93.39594,45.19642],[-93.40762,45.1964],[-93.40767,45.20066],[-93.41309,45.20064],[-93.41315,45.20871],[-93.41886,45.20869],[-93.41892,45.21676],[-93.42463,45.21674],[-93.42466,45.22077],[-93.4532,45.22066],[-93.45323,45.2247],[-93.45894,45.22468],[-93.45898,45.22871],[-93.48753,45.22859],[-93.48756,45.23262],[-93.49327,45.2326],[-93.49334,45.24067],[-93.49905,45.24065],[-93.49909,45.24469],[-93.5048,45.24466],[-93.50484,45.24869],[-93.52768,45.24859],[-93.52761,45.24052],[-93.5219,45.24056],[-93.52187,45.23651],[-93.52757,45.23648],[-93.5275,45.22841],[-93.53321,45.22839],[-93.53325,45.23242],[-93.53896,45.2324],[-93.539,45.23643],[-93.54471,45.2364],[-93.54467,45.23237],[-93.55037,45.23234],[-93.55034,45.22831],[-93.56176,45.22825],[-93.56172,45.22422],[-93.57313,45.22417],[-93.5731,45.22012],[-93.59593,45.22001],[-93.59585,45.21194],[-93.60155,45.21191],[-93.6016,45.21594],[-93.61302,45.21588],[-93.61298,45.21185],[-93.64722,45.21166],[-93.64713,45.20359],[-93.65284,45.20355],[-93.65279,45.19952],[-93.6585,45.19949],[-93.65846,45.19545],[-93.66416,45.19542],[-93.66407,45.18735],[-93.65836,45.18739],[-93.65831,45.18335],[-93.65261,45.18338],[-93.65256,45.17935],[-93.64686,45.17938],[-93.64682,45.17535],[-93.64111,45.17538],[-93.64102,45.16731],[-93.64672,45.16728],[-93.64667,45.16324],[-93.65238,45.16321],[-93.65233,45.15917],[-93.65803,45.15914],[-93.65799,45.15511],[-93.6865,45.15494],[-93.68641,45.14687],[-93.69211,45.14683],[-93.69206,45.1428],[-93.69776,45.14276],[-93.69771,45.13873],[-93.70341,45.13869],[-93.70336,45.13466],[-93.70906,45.13462],[-93.70896,45.12655],[-93.71409,45.12652],[-93.71404,45.12245],[-93.72031,45.12241],[-93.72026,45.11841],[-93.72596,45.11838],[-93.72581,45.10627],[-93.7315,45.10623],[-93.7313,45.09009],[-93.73699,45.09006],[-93.73694,45.08602],[-93.74264,45.08599],[-93.74258,45.08195],[-93.75397,45.08188],[-93.75405,45.08591],[-93.76542,45.08584],[-93.7652,45.0697],[-93.7709,45.06966],[-93.76851,44.8903],[-93.60927,44.89126],[-93.60925,44.88905],[-93.52439,44.88947],[-93.52365,44.80364],[-93.43786,44.80979],[-93.34477,44.78525],[-93.24592,44.81373],[-93.20054,44.86147]]],"type":"Polygon"}}, {"properties":{"name":"Hennepin County Orthoimagery (2021)","id":"Hennepin_Ortho_2021","url":"https://gis.hennepin.us/arcgis/services/Imagery/UTM_Aerial_2021/MapServer/WMSServer?FORMAT=image/jpeg&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=1&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":false,"text":"Hennepin County GIS","url":"https://gis-hennepin.hub.arcgis.com/"},"type":"wms","category":"historicphoto","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-93.20054,44.86147],[-93.1781,44.8865],[-93.17243,44.88651],[-93.17245,44.89458],[-93.18381,44.89456],[-93.18382,44.8986],[-93.18951,44.89859],[-93.18952,44.90666],[-93.19521,44.90665],[-93.19534,44.947],[-93.2018,44.94696],[-93.20189,44.95506],[-93.20673,44.95505],[-93.20702,45.03576],[-93.28101,45.03561],[-93.28115,45.06788],[-93.27548,45.06789],[-93.27566,45.10824],[-93.28136,45.10823],[-93.28137,45.10984],[-93.28426,45.10984],[-93.28427,45.11226],[-93.28708,45.11225],[-93.28711,45.11629],[-93.2928,45.11629],[-93.29289,45.13241],[-93.29858,45.1324],[-93.2986,45.13643],[-93.3043,45.13642],[-93.30491,45.14448],[-93.31005,45.14447],[-93.31007,45.14851],[-93.32147,45.14848],[-93.3215,45.15251],[-93.3272,45.1525],[-93.32723,45.15653],[-93.33863,45.1565],[-93.33866,45.16053],[-93.34436,45.16051],[-93.3444,45.16762],[-93.34599,45.16762],[-93.34599,45.16858],[-93.35011,45.16857],[-93.35014,45.1726],[-93.35585,45.17258],[-93.35586,45.17662],[-93.36158,45.17661],[-93.3616,45.18064],[-93.37301,45.18061],[-93.37304,45.18464],[-93.37874,45.18462],[-93.37877,45.18865],[-93.39018,45.18863],[-93.39021,45.19265],[-93.39591,45.19263],[-93.39594,45.19642],[-93.40762,45.1964],[-93.40767,45.20066],[-93.41309,45.20064],[-93.41315,45.20871],[-93.41886,45.20869],[-93.41892,45.21676],[-93.42463,45.21674],[-93.42466,45.22077],[-93.4532,45.22066],[-93.45323,45.2247],[-93.45894,45.22468],[-93.45898,45.22871],[-93.48753,45.22859],[-93.48756,45.23262],[-93.49327,45.2326],[-93.49334,45.24067],[-93.49905,45.24065],[-93.49909,45.24469],[-93.5048,45.24466],[-93.50484,45.24869],[-93.52768,45.24859],[-93.52761,45.24052],[-93.5219,45.24056],[-93.52187,45.23651],[-93.52757,45.23648],[-93.5275,45.22841],[-93.53321,45.22839],[-93.53325,45.23242],[-93.53896,45.2324],[-93.539,45.23643],[-93.54471,45.2364],[-93.54467,45.23237],[-93.55037,45.23234],[-93.55034,45.22831],[-93.56176,45.22825],[-93.56172,45.22422],[-93.57313,45.22417],[-93.5731,45.22012],[-93.59593,45.22001],[-93.59585,45.21194],[-93.60155,45.21191],[-93.6016,45.21594],[-93.61302,45.21588],[-93.61298,45.21185],[-93.64722,45.21166],[-93.64713,45.20359],[-93.65284,45.20355],[-93.65279,45.19952],[-93.6585,45.19949],[-93.65846,45.19545],[-93.66416,45.19542],[-93.66407,45.18735],[-93.65836,45.18739],[-93.65831,45.18335],[-93.65261,45.18338],[-93.65256,45.17935],[-93.64686,45.17938],[-93.64682,45.17535],[-93.64111,45.17538],[-93.64102,45.16731],[-93.64672,45.16728],[-93.64667,45.16324],[-93.65238,45.16321],[-93.65233,45.15917],[-93.65803,45.15914],[-93.65799,45.15511],[-93.6865,45.15494],[-93.68641,45.14687],[-93.69211,45.14683],[-93.69206,45.1428],[-93.69776,45.14276],[-93.69771,45.13873],[-93.70341,45.13869],[-93.70336,45.13466],[-93.70906,45.13462],[-93.70896,45.12655],[-93.71409,45.12652],[-93.71404,45.12245],[-93.72031,45.12241],[-93.72026,45.11841],[-93.72596,45.11838],[-93.72581,45.10627],[-93.7315,45.10623],[-93.7313,45.09009],[-93.73699,45.09006],[-93.73694,45.08602],[-93.74264,45.08599],[-93.74258,45.08195],[-93.75397,45.08188],[-93.75405,45.08591],[-93.76542,45.08584],[-93.7652,45.0697],[-93.7709,45.06966],[-93.76851,44.8903],[-93.60927,44.89126],[-93.60925,44.88905],[-93.52439,44.88947],[-93.52365,44.80364],[-93.43786,44.80979],[-93.34477,44.78525],[-93.24592,44.81373],[-93.20054,44.86147]]],"type":"Polygon"}}, -{"properties":{"name":"Hennepin County Orthoimagery (2022)","id":"Hennepin_Ortho_2022","url":"https://gis.hennepin.us/arcgis/services/Imagery/UTM_Aerial_2022/MapServer/WMSServer?FORMAT=image/jpeg&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=1&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":false,"text":"Hennepin County GIS","url":"https://gis-hennepin.hub.arcgis.com/"},"type":"wms","category":"photo","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-93.20054,44.86147],[-93.1781,44.8865],[-93.17243,44.88651],[-93.17245,44.89458],[-93.18381,44.89456],[-93.18382,44.8986],[-93.18951,44.89859],[-93.18952,44.90666],[-93.19521,44.90665],[-93.19534,44.947],[-93.2018,44.94696],[-93.20189,44.95506],[-93.20673,44.95505],[-93.20702,45.03576],[-93.28101,45.03561],[-93.28115,45.06788],[-93.27548,45.06789],[-93.27566,45.10824],[-93.28136,45.10823],[-93.28137,45.10984],[-93.28426,45.10984],[-93.28427,45.11226],[-93.28708,45.11225],[-93.28711,45.11629],[-93.2928,45.11629],[-93.29289,45.13241],[-93.29858,45.1324],[-93.2986,45.13643],[-93.3043,45.13642],[-93.30491,45.14448],[-93.31005,45.14447],[-93.31007,45.14851],[-93.32147,45.14848],[-93.3215,45.15251],[-93.3272,45.1525],[-93.32723,45.15653],[-93.33863,45.1565],[-93.33866,45.16053],[-93.34436,45.16051],[-93.3444,45.16762],[-93.34599,45.16762],[-93.34599,45.16858],[-93.35011,45.16857],[-93.35014,45.1726],[-93.35585,45.17258],[-93.35586,45.17662],[-93.36158,45.17661],[-93.3616,45.18064],[-93.37301,45.18061],[-93.37304,45.18464],[-93.37874,45.18462],[-93.37877,45.18865],[-93.39018,45.18863],[-93.39021,45.19265],[-93.39591,45.19263],[-93.39594,45.19642],[-93.40762,45.1964],[-93.40767,45.20066],[-93.41309,45.20064],[-93.41315,45.20871],[-93.41886,45.20869],[-93.41892,45.21676],[-93.42463,45.21674],[-93.42466,45.22077],[-93.4532,45.22066],[-93.45323,45.2247],[-93.45894,45.22468],[-93.45898,45.22871],[-93.48753,45.22859],[-93.48756,45.23262],[-93.49327,45.2326],[-93.49334,45.24067],[-93.49905,45.24065],[-93.49909,45.24469],[-93.5048,45.24466],[-93.50484,45.24869],[-93.52768,45.24859],[-93.52761,45.24052],[-93.5219,45.24056],[-93.52187,45.23651],[-93.52757,45.23648],[-93.5275,45.22841],[-93.53321,45.22839],[-93.53325,45.23242],[-93.53896,45.2324],[-93.539,45.23643],[-93.54471,45.2364],[-93.54467,45.23237],[-93.55037,45.23234],[-93.55034,45.22831],[-93.56176,45.22825],[-93.56172,45.22422],[-93.57313,45.22417],[-93.5731,45.22012],[-93.59593,45.22001],[-93.59585,45.21194],[-93.60155,45.21191],[-93.6016,45.21594],[-93.61302,45.21588],[-93.61298,45.21185],[-93.64722,45.21166],[-93.64713,45.20359],[-93.65284,45.20355],[-93.65279,45.19952],[-93.6585,45.19949],[-93.65846,45.19545],[-93.66416,45.19542],[-93.66407,45.18735],[-93.65836,45.18739],[-93.65831,45.18335],[-93.65261,45.18338],[-93.65256,45.17935],[-93.64686,45.17938],[-93.64682,45.17535],[-93.64111,45.17538],[-93.64102,45.16731],[-93.64672,45.16728],[-93.64667,45.16324],[-93.65238,45.16321],[-93.65233,45.15917],[-93.65803,45.15914],[-93.65799,45.15511],[-93.6865,45.15494],[-93.68641,45.14687],[-93.69211,45.14683],[-93.69206,45.1428],[-93.69776,45.14276],[-93.69771,45.13873],[-93.70341,45.13869],[-93.70336,45.13466],[-93.70906,45.13462],[-93.70896,45.12655],[-93.71409,45.12652],[-93.71404,45.12245],[-93.72031,45.12241],[-93.72026,45.11841],[-93.72596,45.11838],[-93.72581,45.10627],[-93.7315,45.10623],[-93.7313,45.09009],[-93.73699,45.09006],[-93.73694,45.08602],[-93.74264,45.08599],[-93.74258,45.08195],[-93.75397,45.08188],[-93.75405,45.08591],[-93.76542,45.08584],[-93.7652,45.0697],[-93.7709,45.06966],[-93.76851,44.8903],[-93.60927,44.89126],[-93.60925,44.88905],[-93.52439,44.88947],[-93.52365,44.80364],[-93.43786,44.80979],[-93.34477,44.78525],[-93.24592,44.81373],[-93.20054,44.86147]]],"type":"Polygon"}}, +{"properties":{"name":"Hennepin County Orthoimagery (2022)","id":"Hennepin_Ortho_2022","url":"https://gis.hennepin.us/arcgis/services/Imagery/UTM_Aerial_2022/MapServer/WMSServer?FORMAT=image/jpeg&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=0&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":false,"text":"Hennepin County GIS","url":"https://gis-hennepin.hub.arcgis.com/"},"type":"wms","category":"photo","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-93.20054,44.86147],[-93.1781,44.8865],[-93.17243,44.88651],[-93.17245,44.89458],[-93.18381,44.89456],[-93.18382,44.8986],[-93.18951,44.89859],[-93.18952,44.90666],[-93.19521,44.90665],[-93.19534,44.947],[-93.2018,44.94696],[-93.20189,44.95506],[-93.20673,44.95505],[-93.20702,45.03576],[-93.28101,45.03561],[-93.28115,45.06788],[-93.27548,45.06789],[-93.27566,45.10824],[-93.28136,45.10823],[-93.28137,45.10984],[-93.28426,45.10984],[-93.28427,45.11226],[-93.28708,45.11225],[-93.28711,45.11629],[-93.2928,45.11629],[-93.29289,45.13241],[-93.29858,45.1324],[-93.2986,45.13643],[-93.3043,45.13642],[-93.30491,45.14448],[-93.31005,45.14447],[-93.31007,45.14851],[-93.32147,45.14848],[-93.3215,45.15251],[-93.3272,45.1525],[-93.32723,45.15653],[-93.33863,45.1565],[-93.33866,45.16053],[-93.34436,45.16051],[-93.3444,45.16762],[-93.34599,45.16762],[-93.34599,45.16858],[-93.35011,45.16857],[-93.35014,45.1726],[-93.35585,45.17258],[-93.35586,45.17662],[-93.36158,45.17661],[-93.3616,45.18064],[-93.37301,45.18061],[-93.37304,45.18464],[-93.37874,45.18462],[-93.37877,45.18865],[-93.39018,45.18863],[-93.39021,45.19265],[-93.39591,45.19263],[-93.39594,45.19642],[-93.40762,45.1964],[-93.40767,45.20066],[-93.41309,45.20064],[-93.41315,45.20871],[-93.41886,45.20869],[-93.41892,45.21676],[-93.42463,45.21674],[-93.42466,45.22077],[-93.4532,45.22066],[-93.45323,45.2247],[-93.45894,45.22468],[-93.45898,45.22871],[-93.48753,45.22859],[-93.48756,45.23262],[-93.49327,45.2326],[-93.49334,45.24067],[-93.49905,45.24065],[-93.49909,45.24469],[-93.5048,45.24466],[-93.50484,45.24869],[-93.52768,45.24859],[-93.52761,45.24052],[-93.5219,45.24056],[-93.52187,45.23651],[-93.52757,45.23648],[-93.5275,45.22841],[-93.53321,45.22839],[-93.53325,45.23242],[-93.53896,45.2324],[-93.539,45.23643],[-93.54471,45.2364],[-93.54467,45.23237],[-93.55037,45.23234],[-93.55034,45.22831],[-93.56176,45.22825],[-93.56172,45.22422],[-93.57313,45.22417],[-93.5731,45.22012],[-93.59593,45.22001],[-93.59585,45.21194],[-93.60155,45.21191],[-93.6016,45.21594],[-93.61302,45.21588],[-93.61298,45.21185],[-93.64722,45.21166],[-93.64713,45.20359],[-93.65284,45.20355],[-93.65279,45.19952],[-93.6585,45.19949],[-93.65846,45.19545],[-93.66416,45.19542],[-93.66407,45.18735],[-93.65836,45.18739],[-93.65831,45.18335],[-93.65261,45.18338],[-93.65256,45.17935],[-93.64686,45.17938],[-93.64682,45.17535],[-93.64111,45.17538],[-93.64102,45.16731],[-93.64672,45.16728],[-93.64667,45.16324],[-93.65238,45.16321],[-93.65233,45.15917],[-93.65803,45.15914],[-93.65799,45.15511],[-93.6865,45.15494],[-93.68641,45.14687],[-93.69211,45.14683],[-93.69206,45.1428],[-93.69776,45.14276],[-93.69771,45.13873],[-93.70341,45.13869],[-93.70336,45.13466],[-93.70906,45.13462],[-93.70896,45.12655],[-93.71409,45.12652],[-93.71404,45.12245],[-93.72031,45.12241],[-93.72026,45.11841],[-93.72596,45.11838],[-93.72581,45.10627],[-93.7315,45.10623],[-93.7313,45.09009],[-93.73699,45.09006],[-93.73694,45.08602],[-93.74264,45.08599],[-93.74258,45.08195],[-93.75397,45.08188],[-93.75405,45.08591],[-93.76542,45.08584],[-93.7652,45.0697],[-93.7709,45.06966],[-93.76851,44.8903],[-93.60927,44.89126],[-93.60925,44.88905],[-93.52439,44.88947],[-93.52365,44.80364],[-93.43786,44.80979],[-93.34477,44.78525],[-93.24592,44.81373],[-93.20054,44.86147]]],"type":"Polygon"}}, {"properties":{"name":"Minnesota Composite Image Service","id":"Minnesota-Composite-Image-Service","url":"https://imageserver.gisdata.mn.gov/cgi-bin/mncomp?LAYERS=mncomp&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":false,"text":"MnGeo","url":"https://www.mngeo.state.mn.us/chouse/wms/composite_image.html"},"type":"wms","category":"photo","min_zoom":3,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-97.17831,48.87603],[-97.1904,48.81522],[-97.15809,48.81035],[-97.18153,48.79821],[-97.08987,48.68402],[-97.17544,48.56222],[-97.13912,48.55541],[-97.16595,48.54825],[-97.12678,48.5202],[-97.16268,48.47779],[-97.12817,48.47437],[-97.15064,48.44082],[-97.12483,48.4419],[-97.14588,48.43116],[-97.12259,48.41793],[-97.15398,48.41814],[-97.13118,48.40729],[-97.16339,48.39287],[-97.13335,48.38221],[-97.15627,48.36559],[-97.11224,48.29648],[-97.14504,48.26877],[-97.1207,48.22476],[-97.12452,48.22318],[-97.1372,48.22696],[-97.14967,48.22316],[-97.1522,48.21964],[-97.13907,48.22196],[-97.12289,48.2165],[-97.11785,48.20974],[-97.13931,48.21676],[-97.12929,48.20839],[-97.14752,48.17058],[-97.12098,48.15956],[-97.14652,48.14223],[-97.07227,48.04808],[-97.02317,47.87399],[-96.97416,47.82335],[-96.99169,47.80842],[-96.93499,47.76706],[-96.93113,47.7154],[-96.85096,47.5983],[-96.87184,47.41882],[-96.8376,47.38899],[-96.85847,47.36769],[-96.829,47.32762],[-96.84488,47.19282],[-96.82192,47.18425],[-96.84147,47.15185],[-96.81257,47.03859],[-96.83956,47.00674],[-96.79177,46.92847],[-96.75327,46.92457],[-96.80255,46.81153],[-96.77546,46.76676],[-96.79821,46.62933],[-96.74722,46.58235],[-96.72201,46.43999],[-96.59961,46.33014],[-96.59269,46.17522],[-96.55452,46.08399],[-96.58118,45.82302],[-96.6626,45.73869],[-96.83866,45.64752],[-96.85776,45.60597],[-96.69255,45.41735],[-96.5218,45.37565],[-96.45314,45.30079],[-96.45306,43.50038],[-93.84857,43.49963],[-91.21772,43.50064],[-91.23187,43.58183],[-91.26899,43.61659],[-91.24414,43.77468],[-91.43253,43.99684],[-91.59208,44.03138],[-91.72156,44.13035],[-91.87517,44.20058],[-91.91863,44.32268],[-91.96682,44.36399],[-92.23086,44.4445],[-92.33612,44.55401],[-92.54807,44.5678],[-92.56944,44.60355],[-92.62148,44.61505],[-92.63211,44.64904],[-92.80786,44.75085],[-92.75065,44.93731],[-92.76207,45.02433],[-92.80313,45.06157],[-92.74092,45.11296],[-92.76188,45.28702],[-92.65043,45.39852],[-92.64651,45.44035],[-92.7456,45.55302],[-92.88376,45.57549],[-92.8692,45.71758],[-92.78463,45.7642],[-92.70771,45.89491],[-92.55194,45.95166],[-92.52519,45.98387],[-92.46936,45.97382],[-92.42856,46.02425],[-92.35177,46.01569],[-92.33292,46.06271],[-92.29404,46.07439],[-92.2913,46.66815],[-92.2071,46.65195],[-92.1761,46.68635],[-92.2047,46.70405],[-92.1463,46.71595],[-92.1166,46.74865],[-92.01631,46.70598],[-92.08868,46.79367],[-91.7799,46.94341],[-91.57352,47.09003],[-91.46563,47.13124],[-91.04681,47.45632],[-90.7776,47.60573],[-90.43711,47.73164],[-89.97463,47.83056],[-89.75663,47.9041],[-89.68041,47.96408],[-89.63918,47.95373],[-89.62363,47.99464],[-89.57027,47.98571],[-89.49176,48.00536],[-89.5698,47.99899],[-89.61071,48.01786],[-89.65088,48.00354],[-89.77537,48.02279],[-89.89721,47.98751],[-89.99383,48.02802],[-90.02334,48.08468],[-90.13579,48.11215],[-90.3743,48.09092],[-90.4674,48.10875],[-90.55671,48.09594],[-90.57969,48.12381],[-90.75159,48.091],[-90.79823,48.1369],[-90.77794,48.16385],[-90.83641,48.17704],[-90.8393,48.23957],[-90.88576,48.24596],[-91.08248,48.18116],[-91.26678,48.07884],[-91.42958,48.04866],[-91.48819,48.06839],[-91.56752,48.0438],[-91.55912,48.1086],[-91.63993,48.09712],[-91.68212,48.12251],[-91.71176,48.11466],[-91.71537,48.19951],[-91.86437,48.20696],[-91.89311,48.23799],[-91.95836,48.23314],[-91.95411,48.25227],[-92.00653,48.26542],[-92.00012,48.3211],[-92.05523,48.35937],[-92.26256,48.35492],[-92.30618,48.31625],[-92.26959,48.24819],[-92.36992,48.22029],[-92.46978,48.3521],[-92.45644,48.41409],[-92.50748,48.44799],[-92.65623,48.43648],[-92.71269,48.46299],[-92.69868,48.49485],[-92.62702,48.50328],[-92.63489,48.54256],[-92.72842,48.53938],[-92.95002,48.60835],[-92.9548,48.63154],[-93.17839,48.62301],[-93.25466,48.64282],[-93.46548,48.59164],[-93.46739,48.54646],[-93.79338,48.51632],[-93.84436,48.63022],[-94.22422,48.64947],[-94.29121,48.70782],[-94.41584,48.71098],[-94.45239,48.69241],[-94.64538,48.74403],[-94.69441,48.78945],[-94.68347,48.88413],[-94.81627,49.32141],[-94.9574,49.37021],[-95.05831,49.35326],[-95.1534,49.38449],[-95.1532,48.99888],[-97.22956,49.00046],[-97.23421,48.94739],[-97.17831,48.87603]]],"type":"Polygon"}}, {"properties":{"name":"City of Bozeman Aerial Photography (2021)","id":"Bozeman_MT_2021","url":"https://gisweb.bozeman.net/image/services/COB_20210417_WGS84_WebMercator/ImageServer/WMSServer?FORMAT=image/jpeg&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=0&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"required":false,"text":"City of Bozeman GIS Department","url":"https://public-bozeman.opendata.arcgis.com/"},"type":"wms","category":"photo","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-111.11853,45.75121],[-111.1187,45.73664],[-111.14471,45.73659],[-111.14494,45.71462],[-111.13456,45.71467],[-111.13509,45.68218],[-111.15272,45.68215],[-111.15272,45.67971],[-111.16263,45.6797],[-111.1626,45.67032],[-111.1552,45.67015],[-111.15509,45.6513],[-111.13548,45.65113],[-111.13531,45.63794],[-111.12515,45.63096],[-111.10951,45.63087],[-111.10948,45.6237],[-111.10432,45.62367],[-111.08871,45.61294],[-111.07852,45.61293],[-111.07677,45.61067],[-111.07671,45.5802],[-111.04551,45.59404],[-111.02002,45.59389],[-111.02004,45.60568],[-111.01056,45.60572],[-111.01058,45.62009],[-111.00105,45.62006],[-111.00103,45.6273],[-110.97011,45.62733],[-110.97007,45.6382],[-110.96011,45.63817],[-110.95998,45.65255],[-110.95475,45.65255],[-110.95404,45.69923],[-110.96973,45.69994],[-110.96998,45.7036],[-110.96986,45.73627],[-111.04063,45.73675],[-111.04051,45.74041],[-111.06631,45.74044],[-111.06635,45.74662],[-111.06888,45.75132],[-111.11853,45.75121]]],"type":"Polygon"}}, {"properties":{"name":"Mecklenburg County Orthoimagery (2021)","id":"Mecklenburg_NC_2021","url":"https://polaris3g.mecklenburgcountync.gov/polarisv/rest/services/aerial2021/MapServer/export?f=image&format=jpg&layers=&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Mecklenburg County GIS","url":"https://www.mecknc.gov/LUESA/GIS/Pages/GIS-Data-Center.aspx"},"type":"wms","category":"historicphoto","max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[-80.93764,35.5219],[-80.93729,35.50739],[-80.95503,35.50709],[-80.95361,35.44911],[-80.97136,35.4488],[-80.96995,35.39079],[-80.98767,35.3905],[-80.98624,35.33248],[-81.00395,35.33219],[-81.00323,35.30318],[-81.02092,35.30288],[-81.01733,35.15784],[-81.05265,35.15724],[-81.0512,35.09923],[-81.06884,35.09892],[-81.06737,35.04091],[-81.01446,35.0418],[-81.01482,35.0563],[-80.97954,35.05689],[-80.97989,35.07139],[-80.96225,35.07168],[-80.9626,35.08619],[-80.92731,35.08675],[-80.92697,35.07225],[-80.90932,35.07253],[-80.90897,35.05803],[-80.89134,35.0583],[-80.89066,35.02929],[-80.87303,35.02957],[-80.8727,35.01506],[-80.85507,35.01533],[-80.85474,35.00083],[-80.81948,35.00136],[-80.8198,35.01587],[-80.78454,35.0164],[-80.78486,35.0309],[-80.74959,35.03142],[-80.74989,35.04593],[-80.73226,35.04618],[-80.73257,35.06068],[-80.71492,35.06094],[-80.71523,35.07544],[-80.69758,35.07569],[-80.69789,35.0902],[-80.66259,35.09069],[-80.66288,35.1052],[-80.64523,35.10544],[-80.64553,35.11994],[-80.62787,35.12018],[-80.62816,35.13469],[-80.6105,35.13493],[-80.61079,35.14943],[-80.59313,35.14966],[-80.59341,35.16417],[-80.57575,35.1644],[-80.57602,35.17891],[-80.55836,35.17914],[-80.55864,35.19365],[-80.54096,35.19387],[-80.54151,35.22288],[-80.57687,35.22242],[-80.57714,35.23695],[-80.59483,35.2367],[-80.59511,35.25121],[-80.63048,35.25074],[-80.63077,35.26524],[-80.64846,35.265],[-80.64875,35.27951],[-80.66644,35.27927],[-80.66733,35.32278],[-80.68503,35.32254],[-80.68594,35.36606],[-80.72134,35.36556],[-80.72166,35.38006],[-80.73936,35.37981],[-80.74061,35.43783],[-80.75833,35.43757],[-80.75928,35.48108],[-80.77701,35.48082],[-80.77764,35.50984],[-80.81313,35.50931],[-80.81345,35.52381],[-80.93764,35.5219]]],"type":"Polygon"}}, diff --git a/src/assets/svg/Center.svelte b/src/assets/svg/Center.svelte new file mode 100644 index 0000000000..2eb0b3c114 --- /dev/null +++ b/src/assets/svg/Center.svelte @@ -0,0 +1,4 @@ + + \ No newline at end of file From a350410520f22d3e3b2239b465bd48ad3144cb0c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 3 Dec 2023 04:20:56 +0100 Subject: [PATCH 13/22] Favourites: tweaking overview; fix upload of properties --- assets/svg/center.svg.license | 2 +- public/css/index-tailwind-output.css | 8 ++-- .../Sources/FavouritesFeatureSource.ts | 2 +- src/UI/Favourites/FavouriteSummary.svelte | 19 ++++++++- .../TagRendering/TagRenderingQuestion.svelte | 4 +- src/UI/Studio/EditItemButton.svelte | 39 +++++++++++-------- 6 files changed, 49 insertions(+), 25 deletions(-) diff --git a/assets/svg/center.svg.license b/assets/svg/center.svg.license index 2452bee1e8..ed02883002 100644 --- a/assets/svg/center.svg.license +++ b/assets/svg/center.svg.license @@ -1,2 +1,2 @@ SPDX-FileCopyrightText: Pieter Vander Vennet -SPDX-License-Identifier: CC0 \ No newline at end of file +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index d66b0bb916..f4040f97d3 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -841,10 +841,6 @@ video { margin-right: 3rem; } -.mb-4 { - margin-bottom: 1rem; -} - .mt-4 { margin-top: 1rem; } @@ -881,6 +877,10 @@ video { margin-right: 0.25rem; } +.mb-4 { + margin-bottom: 1rem; +} + .ml-1 { margin-left: 0.25rem; } diff --git a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts index 01703987f6..1a4c37df23 100644 --- a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts @@ -109,7 +109,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { if (!key.startsWith(FavouritesFeatureSource.prefix + id)) { continue } - const propertyName = key.substring(minLength) + const propertyName = key.substring(minLength).replaceAll("__", ":") properties[propertyName] = prefs[key] } return properties diff --git a/src/UI/Favourites/FavouriteSummary.svelte b/src/UI/Favourites/FavouriteSummary.svelte index fc23cbbb9e..c890f5a59b 100644 --- a/src/UI/Favourites/FavouriteSummary.svelte +++ b/src/UI/Favourites/FavouriteSummary.svelte @@ -6,6 +6,7 @@ import { ImmutableStore } from "../../Logic/UIEventSource"; import { GeoOperations } from "../../Logic/GeoOperations"; import Center from "../../assets/svg/Center.svelte"; + import { Utils } from "../../Utils"; export let feature: Feature; let properties: Record = feature.properties; @@ -30,15 +31,31 @@ center() } + const coord = GeoOperations.centerpointCoordinates(feature) + const distance = state.mapProperties.location.stabilized(500).mapD(({lon, lat}) => { + let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat])) + + if(meters < 1000){ + return meters +"m" + } + + meters = Math.round(meters / 100) + const kmStr = ""+meters + + + return kmStr.substring(0, kmStr.length - 1)+"."+kmStr.substring(kmStr.length-1) +"km" + }) const titleIconBlacklist = ["osmlink","sharelink","favourite_title_icon"]

select()} class="cursor-pointer ml-1 m-0"> - +

+ {$distance} +
{#each favConfig.titleIcons as titleIconConfig} {#if (titleIconBlacklist.indexOf(titleIconConfig.id) < 0) && (titleIconConfig.condition?.matchesProperties(properties) ?? true) && (titleIconConfig.metacondition?.matchesProperties( { ...properties, ...state.userRelatedState.preferencesAsTags.data } ) ?? true) && titleIconConfig.IsKnown(properties)} diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index ab918974e8..037021e73a 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -140,7 +140,7 @@ console.log("SelectedTags is undefined, ignoring 'onSave'-event") return } - if (layer === undefined || layer?.source === null) { + if (layer === undefined || (layer?.source === null && layer.id !== "favourite")) { /** * This is a special, priviliged layer. * We simply apply the tags onto the records @@ -159,7 +159,7 @@ dispatch("saved", { config, applied: selectedTags }) const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, { - theme: state.layout.id, + theme: tags.data["_orig_theme"] ?? state.layout.id, changeType: "answer", }) freeformInput.setData(undefined) diff --git a/src/UI/Studio/EditItemButton.svelte b/src/UI/Studio/EditItemButton.svelte index 3d593f0a11..aaa59b397b 100644 --- a/src/UI/Studio/EditItemButton.svelte +++ b/src/UI/Studio/EditItemButton.svelte @@ -1,29 +1,30 @@ dispatch("layerSelected", info)}> @@ -32,6 +33,12 @@
{info.id} {#if info.owner && info.owner !== $selfId} - (made by {$displayName ?? info.owner}) + {#if displayName} + (made by {$displayName} + {#if window.location.host.startsWith("127.0.0.1")} + - {info.owner} + {/if} + ) + {/if} {/if} From 1a9a5b45c1fd82c49d70d2e1bef03fde0d3a089b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 3 Dec 2023 04:49:28 +0100 Subject: [PATCH 14/22] Fixes to favourites --- .../ImageProviders/ImageUploadManager.ts | 8 ++- src/UI/Popup/LinkableImage.svelte | 5 +- .../TagRendering/TagRenderingQuestion.svelte | 7 -- src/UI/ThemeViewGUI.svelte | 67 +------------------ 4 files changed, 8 insertions(+), 79 deletions(-) diff --git a/src/Logic/ImageProviders/ImageUploadManager.ts b/src/Logic/ImageProviders/ImageUploadManager.ts index 07a9ff3aea..17b3b8f56a 100644 --- a/src/Logic/ImageProviders/ImageUploadManager.ts +++ b/src/Logic/ImageProviders/ImageUploadManager.ts @@ -107,7 +107,8 @@ export class ImageUploadManager { title, description, file, - targetKey + targetKey, + tags.data["_orig_theme"] ) if (!isNaN(Number(featureId))) { // This is a map note @@ -126,7 +127,8 @@ export class ImageUploadManager { title: string, description: string, blob: File, - targetKey: string | undefined + targetKey: string | undefined, + theme?: string ): Promise { this.increaseCountFor(this._uploadStarted, featureId) const properties = this._featureProperties.getStore(featureId) @@ -148,7 +150,7 @@ export class ImageUploadManager { console.log("Uploading done, creating action for", featureId) key = targetKey ?? key const action = new LinkImageAction(featureId, key, value, properties, { - theme: this._layout.id, + theme: theme ?? this._layout.id, changeType: "add-image", }) this.increaseCountFor(this._uploadFinished, featureId) diff --git a/src/UI/Popup/LinkableImage.svelte b/src/UI/Popup/LinkableImage.svelte index d9290fbddb..a4776dc812 100644 --- a/src/UI/Popup/LinkableImage.svelte +++ b/src/UI/Popup/LinkableImage.svelte @@ -28,7 +28,6 @@ const t = Translations.t.image.nearby const c = [lon, lat] - console.log(">>>", image) let attributedImage = new AttributedImage({ url: image.thumbUrl ?? image.pictureUrl, provider: AllImageProviders.byName(image.provider), @@ -45,7 +44,7 @@ const url = image.osmTags[key] if (isLinked) { const action = new LinkImageAction(currentTags.id, key, url, tags, { - theme: state.layout.id, + theme: tags.data._orig_theme ?? state.layout.id, changeType: "link-image", }) state.changes.applyAction(action) @@ -54,7 +53,7 @@ const v = currentTags[k] if (v === url) { const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, { - theme: state.layout.id, + theme: tags.data._orig_theme ?? state.layout.id, changeType: "remove-image", }) state.changes.applyAction(action) diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index 423046fecc..3705ec2361 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -190,13 +190,6 @@ } } - let dispatch = createEventDispatcher<{ - saved: { - config: TagRenderingConfig - applied: TagsFilter - } - }>(); - let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false); let featureSwitchIsDebugging = diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index e45b9d3a1a..79e258195b 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -65,73 +65,8 @@ import Download from "../assets/svg/Download.svelte"; import Share from "../assets/svg/Share.svelte"; import Favourites from "./Favourites/Favourites.svelte"; - import { Store, UIEventSource } from "../Logic/UIEventSource" - import { Map as MlMap } from "maplibre-gl" - import MaplibreMap from "./Map/MaplibreMap.svelte" - import FeatureSwitchState from "../Logic/State/FeatureSwitchState" - import MapControlButton from "./Base/MapControlButton.svelte" - import ToSvelte from "./Base/ToSvelte.svelte" - import If from "./Base/If.svelte" - import { GeolocationControl } from "./BigComponents/GeolocationControl" - import type { Feature } from "geojson" - import SelectedElementView from "./BigComponents/SelectedElementView.svelte" - import LayerConfig from "../Models/ThemeConfig/LayerConfig" - import Filterview from "./BigComponents/Filterview.svelte" - import ThemeViewState from "../Models/ThemeViewState" - import type { MapProperties } from "../Models/MapProperties" - import Geosearch from "./BigComponents/Geosearch.svelte" - import Translations from "./i18n/Translations" - import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid" - import Tr from "./Base/Tr.svelte" - import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" - import FloatOver from "./Base/FloatOver.svelte" - import PrivacyPolicy from "./BigComponents/PrivacyPolicy" - import Constants from "../Models/Constants" - import TabbedGroup from "./Base/TabbedGroup.svelte" - import UserRelatedState from "../Logic/State/UserRelatedState" - import LoginToggle from "./Base/LoginToggle.svelte" - import LoginButton from "./Base/LoginButton.svelte" - import CopyrightPanel from "./BigComponents/CopyrightPanel" - import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte" - import ModalRight from "./Base/ModalRight.svelte" - import { Utils } from "../Utils" - import Hotkeys from "./Base/Hotkeys" - import { VariableUiElement } from "./Base/VariableUIElement" - import SvelteUIElement from "./Base/SvelteUIElement" - import OverlayToggle from "./BigComponents/OverlayToggle.svelte" - import LevelSelector from "./BigComponents/LevelSelector.svelte" - import ExtraLinkButton from "./BigComponents/ExtraLinkButton" - import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte" - import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte" - import type { RasterLayerPolygon } from "../Models/RasterLayers" - import { AvailableRasterLayers } from "../Models/RasterLayers" - import RasterLayerOverview from "./Map/RasterLayerOverview.svelte" - import IfHidden from "./Base/IfHidden.svelte" - import { onDestroy } from "svelte" - import MapillaryLink from "./BigComponents/MapillaryLink.svelte" - import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" - import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte" - import StateIndicator from "./BigComponents/StateIndicator.svelte" - import ShareScreen from "./BigComponents/ShareScreen.svelte" - import UploadingImageCounter from "./Image/UploadingImageCounter.svelte" - import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte" - import Cross from "../assets/svg/Cross.svelte" - import Summary from "./BigComponents/Summary.svelte" - import Mastodon from "../assets/svg/Mastodon.svelte" - import Bug from "../assets/svg/Bug.svelte" - import Liberapay from "../assets/svg/Liberapay.svelte" - import Min from "../assets/svg/Min.svelte" - import Plus from "../assets/svg/Plus.svelte" - import Filter from "../assets/svg/Filter.svelte" - import Add from "../assets/svg/Add.svelte" - import Statistics from "../assets/svg/Statistics.svelte" - import Community from "../assets/svg/Community.svelte" - import Download from "../assets/svg/Download.svelte" - import Share from "../assets/svg/Share.svelte" - import LanguagePicker from "./InputElement/LanguagePicker.svelte" - import OpenJosm from "./Base/OpenJosm.svelte" - export let state: ThemeViewState + export let state: ThemeViewState let layout = state.layout let maplibremap: UIEventSource = state.map From 35f8b9d8f21e3383ac51cfd9d8461767ac938b68 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 3 Dec 2023 20:01:11 +0100 Subject: [PATCH 15/22] Chore: translation sync --- assets/layers/icons/icons.json | 23 +++++++++------ assets/themes/climbing/climbing.json | 2 +- assets/themes/icecream/icecream.json | 2 +- assets/themes/sidewalks/sidewalks.json | 40 +++++++++++++------------- langs/en.json | 16 +++++++---- langs/layers/en.json | 12 -------- langs/layers/nl.json | 12 -------- 7 files changed, 46 insertions(+), 61 deletions(-) diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index f83be37859..69c52e2170 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -1,7 +1,7 @@ { "id": "icons", "description": { - "en": "A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI or as icon next to the title", + "en": "A layer acting as library for icon-tagrenderings, especially to show as badge next to a POI", "de": "Eine Ebene, die als Bibliothek für Symbol-Tag-Renderings dient, insbesondere um als Abzeichen neben einem POI angezeigt zu werden", "ca": "Una capa que actua com a biblioteca per a les icones d'etiquetes, especialment per mostrar-se com a insígnia al costat d'un PDI", "cs": "Vrstva sloužící jako knihovna pro ikony-značky, zejména pro zobrazení jako odznak vedle bodu zájmu" @@ -14,7 +14,8 @@ { "id": "wikipedialink", "labels": [ - "defaults", "in_favourite" + "defaults", + "in_favourite" ], "render": "Wikipedia", "condition": { @@ -69,7 +70,8 @@ { "id": "phonelink", "labels": [ - "defaults", "in_favourite" + "defaults", + "in_favourite" ], "render": "phone", "mappings": [ @@ -89,7 +91,8 @@ { "id": "emaillink", "labels": [ - "defaults", "in_favourite" + "defaults", + "in_favourite" ], "render": "email", "mappings": [ @@ -109,7 +112,8 @@ { "id": "websitelink", "labels": [ - "defaults", "in_favourite" + "defaults", + "in_favourite" ], "render": "website", "condition": "website~*" @@ -117,7 +121,8 @@ { "id": "smokingicon", "labels": [ - "defaults", "in_favourite" + "defaults", + "in_favourite" ], "mappings": [ { @@ -146,7 +151,7 @@ "defaults" ], "render": { - "*":"{favourite_icon()}" + "*": "{favourite_icon()}" } }, { @@ -171,7 +176,8 @@ { "id": "dogicon", "labels": [ - "defaults", "in_favourite" + "defaults", + "in_favourite" ], "mappings": [ { @@ -209,7 +215,6 @@ "condition": "_favourite=yes", "icon": "circle:white;heart:red", "metacondition": "__showTimeSensitiveIcons!=no" - } ] } diff --git a/assets/themes/climbing/climbing.json b/assets/themes/climbing/climbing.json index bee0d87cf4..82a23b6be1 100644 --- a/assets/themes/climbing/climbing.json +++ b/assets/themes/climbing/climbing.json @@ -74,7 +74,7 @@ "condition": "climbing:length~*" }, { - "id":"climbing_bolts", + "id": "climbing_bolts", "mappings": [ { "if": "__bolts_max~*", diff --git a/assets/themes/icecream/icecream.json b/assets/themes/icecream/icecream.json index 6f9ebe19f8..33a9e80936 100644 --- a/assets/themes/icecream/icecream.json +++ b/assets/themes/icecream/icecream.json @@ -2,7 +2,7 @@ "id": "icecream", "title": { "en": "Icecream", - "de": "Eiscreme", + "de": "Eiscreme", "cs": "Zmrzlina" }, "description": { diff --git a/assets/themes/sidewalks/sidewalks.json b/assets/themes/sidewalks/sidewalks.json index 03b439468d..459225a4a7 100644 --- a/assets/themes/sidewalks/sidewalks.json +++ b/assets/themes/sidewalks/sidewalks.json @@ -166,31 +166,31 @@ { "if": "sidewalk:left|right=yes", "then": { - "en": "Yes, there is a sidewalk on this side of the road", - "de": "Ja, es gibt einen Bürgersteig auf dieser Straßenseite", - "da": "Ja, der er et fortov på denne side af vejen", - "nl": "Ja, er is een stoep aan deze kant van de weg", - "fr": "Oui, il y a un trottoir de ce côté de la route", - "ca": "Sí, hi ha una vorera a aquest costat del carrer", - "es": "Sí, hay una acera en este lado de la calle", - "cs": "Ano, na této straně silnice je chodník", - "it": "Sì, c'è un marciapiede su questo lato della strada", - "pl": "Tak, jest chodnik z boku drogi" + "en": "There is a sidewalk on this side of the road", + "de": "Es gibt einen Bürgersteig auf dieser Straßenseite", + "da": "Der er et fortov på denne side af vejen", + "nl": "Er is een stoep aan deze kant van de weg", + "fr": "Il y a un trottoir de ce côté de la route", + "ca": "Hi ha una vorera a aquest costat del carrer", + "es": "Hay una acera en este lado de la calle", + "cs": "Na této straně silnice je chodník", + "it": "C'è un marciapiede su questo lato della strada", + "pl": "Jest chodnik z boku drogi" } }, { "if": "sidewalk:left|right=no", "then": { - "en": "No, there is no sidewalk to walk on", - "de": "Nein, es gibt keinen Bürgersteig für Fußgänger", - "da": "Nej, der er ikke noget fortov at gå på", - "nl": "Nee, er is geen stoep om op te lopen", - "fr": "Non, il n'y a pas de trottoir où marcher", - "ca": "No, no hi ha vorera per la que caminar", - "es": "No, no hay acera por la que caminar", - "cs": "Ne, není tu žádný chodník", - "it": "No, non c'è un marciapiede su cui camminare", - "pl": "Nie, nie ma chodnika, którym można chodzić" + "en": "There is no sidewalk to walk on", + "de": "Es gibt keinen Bürgersteig für Fußgänger", + "da": "Der er ikke noget fortov at gå på", + "nl": "Er is geen stoep om op te lopen", + "fr": "Il n'y a pas de trottoir où marcher", + "ca": "No hi ha vorera per la que caminar", + "es": "No hay acera por la que caminar", + "cs": "Není tu žádný chodník", + "it": "Non c'è un marciapiede su cui camminare", + "pl": "Nie ma chodnika, którym można chodzić" } }, { diff --git a/langs/en.json b/langs/en.json index 26a2588cdc..2f7c43e9aa 100644 --- a/langs/en.json +++ b/langs/en.json @@ -48,8 +48,7 @@ "favourite": { "loginNeeded": "

Log in

A personal layout is only available for OpenStreetMap users", "panelIntro": "

Your personal theme

Activate your favourite layers from all the official themes", - "reload": "Reload the data", - + "reload": "Reload the data" }, "favouritePoi": { "button": { @@ -57,10 +56,14 @@ "markAsFavouriteTitle": "Mark this location as favourite location", "markDescription": "Add this location to a personal list of your favourites", "unmark": "Remove from your personal list of favourites", - "unmarkNotDeleted": "This point will not be deleted and still be visible on the appropriate map for you and others", - "tab":"Your favourites", - "title":"Your favourite locations" - } + "unmarkNotDeleted": "This point will not be deleted and still be visible on the appropriate map for you and others" + }, + "downloadGeojson": "Download your favourites as geojson", + "downloadGpx": "Download your favourites as GPX", + "intro": "You marked {length} locations as a favourite location.", + "introPrivacy": "This list is only visible to you", + "tab": "Your favourites", + "title": "Your favourite locations" }, "flyer": { "aerial": "This map uses a different background, namely aerial imagery by Agentschap Informatie Vlaanderen", @@ -416,6 +419,7 @@ "key": "Key combination", "openLayersPanel": "Opens the layers and filters panel", "selectAerial": "Set the background to aerial or satellite imagery. Toggles between the two best, available layers", + "selectFavourites": "Open the favourites page", "selectItem": "Select the POI which is closest to the map center (crosshair). Only when in keyboard navigation is used", "selectMap": "Set the background to a map from external sources. Toggles between the two best, available layers", "selectMapnik": "Set the background layer to OpenStreetMap-carto", diff --git a/langs/layers/en.json b/langs/layers/en.json index ea077cf00e..70040a239f 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -4729,18 +4729,6 @@ "render": "Extinguishers" } }, - "favourite": { - "description": "A generic map layer which shows locations that a contributor marked as favourite", - "name": "Favourites", - "tagRenderings": { - "Explanation": { - "render": "You marked this location as a personal favourite. As such, it is shown on every map you load." - } - }, - "title": { - "render": "Favourite location" - } - }, "filters": { "filter": { "0": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index cc093e2a54..e1ac846efa 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -4382,18 +4382,6 @@ "render": "Brandblussers" } }, - "favourite": { - "description": "Een laag met persoonlijke favourieten", - "name": "Favorieten", - "tagRenderings": { - "Explanation": { - "render": "Je hebt deze locatie als persoonlijke favoriet aangeduid en worden op alle MapComplete-kaarten getoond." - } - }, - "title": { - "render": "Favoriete locatie" - } - }, "filters": { "filter": { "0": { From 56a23deb2db177f67f01932a06028c743cc58603 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 3 Dec 2023 20:03:47 +0100 Subject: [PATCH 16/22] Favourites: improve overview, update all features from OSM when loading --- public/css/index-tailwind-output.css | 34 +++- .../Actors/SelectedElementTagsUpdater.ts | 48 +++-- .../Sources/FavouritesFeatureSource.ts | 119 +++++++----- src/Logic/GeoOperations.ts | 170 ++++-------------- src/Logic/State/UserRelatedState.ts | 7 - src/Models/ThemeViewState.ts | 21 ++- src/UI/Base/ToSvelte.svelte | 2 +- src/UI/Favourites/FavouriteSummary.svelte | 9 +- src/UI/Favourites/Favourites.svelte | 45 ++++- src/index.css | 10 ++ 10 files changed, 231 insertions(+), 234 deletions(-) diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 32fb419c0d..13d12ea52a 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -841,10 +841,6 @@ video { margin-right: 3rem; } -.mb-4 { - margin-bottom: 1rem; -} - .mt-4 { margin-top: 1rem; } @@ -881,6 +877,10 @@ video { margin-right: 0.25rem; } +.mb-4 { + margin-bottom: 1rem; +} + .ml-1 { margin-left: 0.25rem; } @@ -1289,6 +1289,10 @@ video { appearance: none; } +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + .grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); } @@ -1441,6 +1445,18 @@ video { align-self: center; } +.justify-self-start { + justify-self: start; +} + +.justify-self-end { + justify-self: end; +} + +.justify-self-center { + justify-self: center; +} + .overflow-auto { overflow: auto; } @@ -2335,6 +2351,16 @@ button.disabled:hover, .button.disabled:hover { color: unset; } +button.link { + border: none; + text-decoration: underline; + background-color: unset; +} + +button.link:hover { + color:unset; +} + .interactive button.disabled svg path, .interactive .button.disabled svg path { fill: var(--interactive-foreground) !important; } diff --git a/src/Logic/Actors/SelectedElementTagsUpdater.ts b/src/Logic/Actors/SelectedElementTagsUpdater.ts index 91078a53b5..8bc510c56b 100644 --- a/src/Logic/Actors/SelectedElementTagsUpdater.ts +++ b/src/Logic/Actors/SelectedElementTagsUpdater.ts @@ -6,13 +6,21 @@ import { Changes } from "../Osm/Changes" import { OsmConnection } from "../Osm/OsmConnection" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import SimpleMetaTagger from "../SimpleMetaTagger" -import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" import { Feature } from "geojson" import { OsmTags } from "../../Models/OsmFeature" import OsmObjectDownloader from "../Osm/OsmObjectDownloader" import { IndexedFeatureSource } from "../FeatureSource/FeatureSource" import { Utils } from "../../Utils" +interface TagsUpdaterState { + selectedElement: UIEventSource + featureProperties: { getStore: (id: string) => UIEventSource> } + changes: Changes + osmConnection: OsmConnection + layout: LayoutConfig + osmObjectDownloader: OsmObjectDownloader + indexedFeatures: IndexedFeatureSource +} export default class SelectedElementTagsUpdater { private static readonly metatags = new Set([ "timestamp", @@ -23,38 +31,18 @@ export default class SelectedElementTagsUpdater { "id", ]) - private readonly state: { - selectedElement: UIEventSource - featureProperties: FeaturePropertiesStore - changes: Changes - osmConnection: OsmConnection - layout: LayoutConfig - osmObjectDownloader: OsmObjectDownloader - indexedFeatures: IndexedFeatureSource - } - - constructor(state: { - selectedElement: UIEventSource - featureProperties: FeaturePropertiesStore - indexedFeatures: IndexedFeatureSource - changes: Changes - osmConnection: OsmConnection - layout: LayoutConfig - osmObjectDownloader: OsmObjectDownloader - }) { - this.state = state + constructor(state: TagsUpdaterState) { state.osmConnection.isLoggedIn.addCallbackAndRun((isLoggedIn) => { if (!isLoggedIn && !Utils.runningFromConsole) { return } - this.installCallback() + this.installCallback(state) // We only have to do this once... return true }) } - private installCallback() { - const state = this.state + private installCallback(state: TagsUpdaterState) { state.selectedElement.addCallbackAndRunD(async (s) => { let id = s.properties?.id if (!id) { @@ -94,7 +82,7 @@ export default class SelectedElementTagsUpdater { oldFeature.geometry = newGeometry state.featureProperties.getStore(id)?.ping() } - this.applyUpdate(latestTags, id) + SelectedElementTagsUpdater.applyUpdate(latestTags, id, state) console.log("Updated", id) } catch (e) { @@ -102,8 +90,7 @@ export default class SelectedElementTagsUpdater { } }) } - private applyUpdate(latestTags: OsmTags, id: string) { - const state = this.state + public static applyUpdate(latestTags: OsmTags, id: string, state: TagsUpdaterState) { try { const leftRightSensitive = state.layout.isLeftRightSensitive() @@ -162,11 +149,16 @@ export default class SelectedElementTagsUpdater { } if (somethingChanged) { - console.log("Detected upstream changes to the object when opening it, updating...") + console.log( + "Detected upstream changes to the object " + + id + + " when opening it, updating..." + ) currentTagsSource.ping() } else { console.debug("Fetched latest tags for ", id, "but detected no changes") } + return currentTags } catch (e) { console.error("Updating the tags of selected element ", id, "failed due to", e) } diff --git a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts index 1a4c37df23..1f2764062b 100644 --- a/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/FavouritesFeatureSource.ts @@ -4,9 +4,10 @@ import { Store, Stores, UIEventSource } from "../../UIEventSource" import { OsmConnection } from "../../Osm/OsmConnection" import { OsmId } from "../../../Models/OsmFeature" import { GeoOperations } from "../../GeoOperations" -import FeaturePropertiesStore from "../Actors/FeaturePropertiesStore" import { IndexedFeatureSource } from "../FeatureSource" -import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" +import OsmObjectDownloader from "../../Osm/OsmObjectDownloader" +import { SpecialVisualizationState } from "../../../UI/SpecialVisualization" +import SelectedElementTagsUpdater from "../../Actors/SelectedElementTagsUpdater" /** * Generates the favourites from the preferences and marks them as favourite @@ -21,14 +22,9 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { */ public readonly allFavourites: Store - constructor( - connection: OsmConnection, - indexedSource: FeaturePropertiesStore, - allFeatures: IndexedFeatureSource, - layout: LayoutConfig - ) { + constructor(state: SpecialVisualizationState) { const features: Store = Stores.ListStabilized( - connection.preferencesHandler.preferences.map((prefs) => { + state.osmConnection.preferencesHandler.preferences.map((prefs) => { const feats: Feature[] = [] const allIds = new Set() for (const key in prefs) { @@ -53,24 +49,49 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { const featuresWithoutAlreadyPresent = features.map((features) => features.filter( - (feat) => !layout.layers.some((l) => l.id === feat.properties._orig_layer) + (feat) => !state.layout.layers.some((l) => l.id === feat.properties._orig_layer) ) ) super(featuresWithoutAlreadyPresent) this.allFavourites = features - this._osmConnection = connection + this._osmConnection = state.osmConnection this._detectedIds = Stores.ListStabilized( features.map((feats) => feats.map((f) => f.properties.id)) ) + let allFeatures = state.indexedFeatures this._detectedIds.addCallbackAndRunD((detected) => - this.markFeatures(detected, indexedSource, allFeatures) + this.markFeatures(detected, state.featureProperties, allFeatures) ) // We use the indexedFeatureSource as signal to update allFeatures.features.map((_) => - this.markFeatures(this._detectedIds.data, indexedSource, allFeatures) + this.markFeatures(this._detectedIds.data, state.featureProperties, allFeatures) ) + + this.allFavourites.addCallbackD((features) => { + for (const feature of features) { + this.updateFeature(feature, state.osmObjectDownloader, state) + } + + return true + }) + } + + private async updateFeature( + feature: Feature, + osmObjectDownloader: OsmObjectDownloader, + state: SpecialVisualizationState + ) { + const id = feature.properties.id + const upstream = await osmObjectDownloader.DownloadObjectAsync(id) + if (upstream === "deleted") { + this.removeFavourite(feature) + return + } + console.log("Updating metadata due to favourite of", id) + const latestTags = SelectedElementTagsUpdater.applyUpdate(upstream.tags, id, state) + this.updatePropertiesOfFavourite(latestTags) } private static ExtractFavourite(key: string, prefs: Record): Feature { @@ -115,6 +136,37 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { return properties } + /** + * Sets all the (normal) properties as the feature is updated + */ + private updatePropertiesOfFavourite(properties: Record) { + const id = properties?.id?.replace("/", "-") + if (!id) { + return + } + console.log("Updating store for", id) + for (const key in properties) { + const pref = this._osmConnection.GetPreference( + "favourite-" + id + "-property-" + key.replaceAll(":", "__") + ) + const v = properties[key] + if (v === "" || !v) { + continue + } + pref.setData("" + v) + } + } + + public removeFavourite(feature: Feature, tags?: UIEventSource>) { + const id = feature.properties.id.replace("/", "-") + const pref = this._osmConnection.GetPreference("favourite-" + id) + this._osmConnection.preferencesHandler.removeAllWithPrefix("mapcomplete-favourite-" + id) + if (tags) { + delete tags.data._favourite + tags.ping() + } + } + public markAsFavourite( feature: Feature, layer: string, @@ -123,42 +175,25 @@ export default class FavouritesFeatureSource extends StaticFeatureSource { isFavourite: boolean = true ) { { + if (!isFavourite) { + this.removeFavourite(feature, tags) + return + } const id = tags.data.id.replace("/", "-") const pref = this._osmConnection.GetPreference("favourite-" + id) - if (isFavourite) { - const center = GeoOperations.centerpointCoordinates(feature) - pref.setData(JSON.stringify(center)) - - this._osmConnection.GetPreference("favourite-" + id + "-layer").setData(layer) - this._osmConnection.GetPreference("favourite-" + id + "-theme").setData(theme) - for (const key in tags.data) { - const pref = this._osmConnection.GetPreference( - "favourite-" + id + "-property-" + key.replaceAll(":", "__") - ) - const v = tags.data[key] - if (v === "" || !v) { - continue - } - pref.setData("" + v) - } - } else { - this._osmConnection.preferencesHandler.removeAllWithPrefix( - "mapcomplete-favourite-" + id - ) - } - } - if (isFavourite) { - tags.data._favourite = "yes" - tags.ping() - } else { - delete tags.data._favourite - tags.ping() + const center = GeoOperations.centerpointCoordinates(feature) + pref.setData(JSON.stringify(center)) + this._osmConnection.GetPreference("favourite-" + id + "-layer").setData(layer) + this._osmConnection.GetPreference("favourite-" + id + "-theme").setData(theme) + this.updatePropertiesOfFavourite(tags.data) } + tags.data._favourite = "yes" + tags.ping() } private markFeatures( detected: string[], - featureProperties: FeaturePropertiesStore, + featureProperties: { getStore(id: string): UIEventSource> }, allFeatures: IndexedFeatureSource ) { const feature = allFeatures.features.data diff --git a/src/Logic/GeoOperations.ts b/src/Logic/GeoOperations.ts index e03cc13b1b..61128d1b06 100644 --- a/src/Logic/GeoOperations.ts +++ b/src/Logic/GeoOperations.ts @@ -501,147 +501,43 @@ export class GeoOperations { ) } - public static IdentifieCommonSegments(coordinatess: [number, number][][]): { - originalIndex: number - segmentShardWith: number[] - coordinates: [] - }[] { - // An edge. Note that the edge might be reversed to fix the sorting condition: start[0] < end[0] && (start[0] != end[0] || start[0] < end[1]) - type edge = { - start: [number, number] - end: [number, number] - intermediate: [number, number][] - members: { index: number; isReversed: boolean }[] + /** + * Given a list of points, convert into a GPX-list, e.g. for favourites + * @param locations + * @param title + */ + public static toGpxPoints( + locations: Feature[], + title?: string + ) { + title = title?.trim() + if (title === undefined || title === "") { + title = "Created with MapComplete" } - - // The strategy: - // 1. Index _all_ edges from _every_ linestring. Index them by starting key, gather which relations run over them - // 2. Join these edges back together - as long as their membership groups are the same - // 3. Convert to results - - const allEdgesByKey = new Map() - - for (let index = 0; index < coordinatess.length; index++) { - const coordinates = coordinatess[index] - for (let i = 0; i < coordinates.length - 1; i++) { - const c0 = coordinates[i] - const c1 = coordinates[i + 1] - const isReversed = c0[0] > c1[0] || (c0[0] == c1[0] && c0[1] > c1[1]) - - let key: string - if (isReversed) { - key = "" + c1 + ";" + c0 - } else { - key = "" + c0 + ";" + c1 + title = Utils.EncodeXmlValue(title) + const trackPoints: string[] = [] + for (const l of locations) { + let trkpt = ` ` + for (const key in l.properties) { + const keyCleaned = key.replaceAll(":", "__") + trkpt += ` <${keyCleaned}>${l.properties[key]}\n` + if (key === "website") { + trkpt += ` ${l.properties[key]}\n` } - const member = { index, isReversed } - if (allEdgesByKey.has(key)) { - allEdgesByKey.get(key).members.push(member) - continue - } - - let edge: edge - if (!isReversed) { - edge = { - start: c0, - end: c1, - members: [member], - intermediate: [], - } - } else { - edge = { - start: c1, - end: c0, - members: [member], - intermediate: [], - } - } - allEdgesByKey.set(key, edge) } + trkpt += " \n" + trackPoints.push(trkpt) } - - // Lets merge them back together! - - let didMergeSomething = false - let allMergedEdges = Array.from(allEdgesByKey.values()) - const allEdgesByStartPoint = new Map() - for (const edge of allMergedEdges) { - edge.members.sort((m0, m1) => m0.index - m1.index) - - const kstart = edge.start + "" - if (!allEdgesByStartPoint.has(kstart)) { - allEdgesByStartPoint.set(kstart, []) - } - allEdgesByStartPoint.get(kstart).push(edge) - } - - function membersAreCompatible(first: edge, second: edge): boolean { - // There must be an exact match between the members - if (first.members === second.members) { - return true - } - - if (first.members.length !== second.members.length) { - return false - } - - // Members are sorted and have the same length, so we can check quickly - for (let i = 0; i < first.members.length; i++) { - const m0 = first.members[i] - const m1 = second.members[i] - if (m0.index !== m1.index || m0.isReversed !== m1.isReversed) { - return false - } - } - - // Allrigth, they are the same, lets mark this permanently - second.members = first.members - return true - } - - do { - didMergeSomething = false - // We use 'allMergedEdges' as our running list - const consumed = new Set() - for (const edge of allMergedEdges) { - // Can we make this edge longer at the end? - if (consumed.has(edge)) { - continue - } - - console.log("Considering edge", edge) - const matchingEndEdges = allEdgesByStartPoint.get(edge.end + "") - console.log("Matchign endpoints:", matchingEndEdges) - if (matchingEndEdges === undefined) { - continue - } - - for (let i = 0; i < matchingEndEdges.length; i++) { - const endEdge = matchingEndEdges[i] - - if (consumed.has(endEdge)) { - continue - } - - if (!membersAreCompatible(edge, endEdge)) { - continue - } - - // We can make the segment longer! - didMergeSomething = true - console.log("Merging ", edge, "with ", endEdge) - edge.intermediate.push(edge.end) - edge.end = endEdge.end - consumed.add(endEdge) - matchingEndEdges.splice(i, 1) - break - } - } - - allMergedEdges = allMergedEdges.filter((edge) => !consumed.has(edge)) - } while (didMergeSomething) - - return [] + const header = + '' + return ( + header + + "\n" + + title + + "\n\n" + + trackPoints.join("\n") + + "\n" + ) } /** diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index bbc7c9f8ce..2fbbdc3697 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -397,13 +397,6 @@ export default class UserRelatedState { } if (tags[key + "-combined-0"]) { // A combined value exists - console.log( - "Trying to get a long preference for ", - key, - "with length value", - tags[key], - "as -combined-0 exists" - ) this.osmConnection.GetLongPreference(key, "").setData(tags[key]) } else { this.osmConnection diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index fbf17de776..1db7f7ac5e 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -244,12 +244,6 @@ export default class ThemeViewState implements SpecialVisualizationState { this.dataIsLoading = layoutSource.isLoading this.indexedFeatures = layoutSource this.featureProperties = new FeaturePropertiesStore(layoutSource) - this.favourites = new FavouritesFeatureSource( - this.osmConnection, - this.featureProperties, - layoutSource, - layout - ) this.changes = new Changes( { @@ -333,10 +327,10 @@ export default class ThemeViewState implements SpecialVisualizationState { return sorted }) - const lastClick = (this.lastClickObject = new LastClickFeatureSource( + this.lastClickObject = new LastClickFeatureSource( this.mapProperties.lastClickLocation, this.layout - )) + ) this.osmObjectDownloader = new OsmObjectDownloader( this.osmConnection.Backend(), @@ -359,6 +353,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.osmConnection, this.changes ) + this.favourites = new FavouritesFeatureSource(this) this.initActors() this.drawSpecialLayers() @@ -472,6 +467,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.selectedLayer.setData(layer) this.selectedElement.setData(toSelect) } + private initHotkeys() { Hotkeys.RegisterHotkey( { nomod: "Escape", onUp: true }, @@ -483,6 +479,15 @@ export default class ThemeViewState implements SpecialVisualizationState { } ) + Hotkeys.RegisterHotkey( + { nomod: "f" }, + Translations.t.hotkeyDocumentation.selectFavourites, + () => { + this.guistate.menuViewTab.setData("favourites") + this.guistate.menuIsOpened.setData(true) + } + ) + this.mapProperties.lastKeyNavigation.addCallbackAndRunD((_) => { Hotkeys.RegisterHotkey( { diff --git a/src/UI/Base/ToSvelte.svelte b/src/UI/Base/ToSvelte.svelte index 01386bf903..88428eb2dc 100644 --- a/src/UI/Base/ToSvelte.svelte +++ b/src/UI/Base/ToSvelte.svelte @@ -9,7 +9,7 @@ const uiElem = typeof construct === "function" ? construct() : construct html = uiElem?.ConstructElement() if (html !== undefined) { - elem.replaceWith(html) + elem?.replaceWith(html) } }) diff --git a/src/UI/Favourites/FavouriteSummary.svelte b/src/UI/Favourites/FavouriteSummary.svelte index c890f5a59b..7cc402381a 100644 --- a/src/UI/Favourites/FavouriteSummary.svelte +++ b/src/UI/Favourites/FavouriteSummary.svelte @@ -49,14 +49,14 @@ -
-

select()} class="cursor-pointer ml-1 m-0"> +
+

+ {$distance} -
+
diff --git a/src/UI/Favourites/Favourites.svelte b/src/UI/Favourites/Favourites.svelte index 790dc9a7bd..f4452ef5bc 100644 --- a/src/UI/Favourites/Favourites.svelte +++ b/src/UI/Favourites/Favourites.svelte @@ -1,19 +1,58 @@ -
- You marked {$favourites.length} locations as a favourite location. +
console.log("Got keypress", e)}> + + - This list is only visible to you {#each $favourites as feature (feature.properties.id)} {/each} + +
+ + +
diff --git a/src/index.css b/src/index.css index 84cf251b0f..c9f89ccb78 100644 --- a/src/index.css +++ b/src/index.css @@ -280,6 +280,16 @@ button.disabled:hover, .button.disabled:hover { color: unset; } +button.link { + border: none; + text-decoration: underline; + background-color: unset; +} + +button.link:hover { + color:unset; +} + .interactive button.disabled svg path, .interactive .button.disabled svg path { fill: var(--interactive-foreground) !important;; } From d9d330e91221fac741120c7e2cb0b394d0871406 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 4 Dec 2023 03:32:25 +0100 Subject: [PATCH 17/22] Themes: add currently open badge --- assets/layers/icons/icons.json | 8 ++ public/css/index-tailwind-output.css | 24 ++++-- src/UI/Favourites/FavouriteSummary.svelte | 74 ++++++++++--------- src/UI/OpeningHours/NextChangeViz.svelte | 50 +++++++++++++ src/UI/OpeningHours/OpeningHours.ts | 43 ++++++++++- .../OpeningHours/OpeningHoursVisualization.ts | 55 ++++---------- src/UI/SpecialVisualizations.ts | 41 ++++++++++ 7 files changed, 210 insertions(+), 85 deletions(-) create mode 100644 src/UI/OpeningHours/NextChangeViz.svelte diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 69c52e2170..9c5fc0b788 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -67,6 +67,14 @@ ], "metacondition": "__showTimeSensitiveIcons!=no" }, + {"id":"open_until", + "labels": ["defaults","in_favourite"], + "#":"Titleicon showing 'open until 17:00'", + "icon": { + "class": "w-20 mx-1 flex items-center" + }, + "render": "{opening_hours_state()}" + }, { "id": "phonelink", "labels": [ diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 13d12ea52a..ae394847be 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -745,6 +745,10 @@ video { top: 2.5rem; } +.left-1\/4 { + left: 25%; +} + .isolate { isolation: isolate; } @@ -1088,6 +1092,10 @@ video { height: 2.75rem; } +.h-5 { + height: 1.25rem; +} + .h-48 { height: 12rem; } @@ -1198,6 +1206,14 @@ video { width: 50%; } +.w-14 { + width: 3.5rem; +} + +.w-5 { + width: 1.25rem; +} + .w-10 { width: 2.5rem; } @@ -1289,8 +1305,8 @@ video { appearance: none; } -.grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); } .grid-cols-1 { @@ -1453,10 +1469,6 @@ video { justify-self: end; } -.justify-self-center { - justify-self: center; -} - .overflow-auto { overflow: auto; } diff --git a/src/UI/Favourites/FavouriteSummary.svelte b/src/UI/Favourites/FavouriteSummary.svelte index 7cc402381a..112c3dd69a 100644 --- a/src/UI/Favourites/FavouriteSummary.svelte +++ b/src/UI/Favourites/FavouriteSummary.svelte @@ -6,7 +6,6 @@ import { ImmutableStore } from "../../Logic/UIEventSource"; import { GeoOperations } from "../../Logic/GeoOperations"; import Center from "../../assets/svg/Center.svelte"; - import { Utils } from "../../Utils"; export let feature: Feature; let properties: Record = feature.properties; @@ -16,49 +15,49 @@ const favLayer = state.layerState.filteredLayers.get("favourite"); const favConfig = favLayer.layerDef; const titleConfig = favConfig.title; - - function center(){ - const [lon, lat] = GeoOperations.centerpointCoordinates(feature) + + function center() { + const [lon, lat] = GeoOperations.centerpointCoordinates(feature); state.mapProperties.location.setData( - {lon, lat} - ) - state.guistate.menuIsOpened.setData(false) + { lon, lat } + ); + state.guistate.menuIsOpened.setData(false); } - - function select(){ - state.selectedLayer.setData(favConfig) - state.selectedElement.setData(feature) - center() + + function select() { + state.selectedLayer.setData(favConfig); + state.selectedElement.setData(feature); + center(); } - - const coord = GeoOperations.centerpointCoordinates(feature) - const distance = state.mapProperties.location.stabilized(500).mapD(({lon, lat}) => { - let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat])) - - if(meters < 1000){ - return meters +"m" + + const coord = GeoOperations.centerpointCoordinates(feature); + const distance = state.mapProperties.location.stabilized(500).mapD(({ lon, lat }) => { + let meters = Math.round(GeoOperations.distanceBetween(coord, [lon, lat])); + + if (meters < 1000) { + return meters + "m"; } - - meters = Math.round(meters / 100) - const kmStr = ""+meters - - - return kmStr.substring(0, kmStr.length - 1)+"."+kmStr.substring(kmStr.length-1) +"km" - }) - const titleIconBlacklist = ["osmlink","sharelink","favourite_title_icon"] + + meters = Math.round(meters / 100); + const kmStr = "" + meters; + + + return kmStr.substring(0, kmStr.length - 1) + "." + kmStr.substring(kmStr.length - 1) + "km"; + }); + const titleIconBlacklist = ["osmlink", "sharelink", "favourite_title_icon"]; -
- - {$distance} - + diff --git a/src/UI/OpeningHours/NextChangeViz.svelte b/src/UI/OpeningHours/NextChangeViz.svelte new file mode 100644 index 0000000000..cdafa89037 --- /dev/null +++ b/src/UI/OpeningHours/NextChangeViz.svelte @@ -0,0 +1,50 @@ + + +{#if $currentState !== undefined} +
+ {#if $currentState === true} + + + {:else if $currentState === false} + + + {/if} + + {#if $nextChange !== undefined} + + {$nextChange} + + {/if} + +
+ +{/if} diff --git a/src/UI/OpeningHours/OpeningHours.ts b/src/UI/OpeningHours/OpeningHours.ts index cd62fdd794..400f44c56f 100644 --- a/src/UI/OpeningHours/OpeningHours.ts +++ b/src/UI/OpeningHours/OpeningHours.ts @@ -1,5 +1,6 @@ import { Utils } from "../../Utils" import opening_hours from "opening_hours" +import { Store } from "../../Logic/UIEventSource" export interface OpeningHour { weekday: number // 0 is monday, 1 is tuesday, ... @@ -494,10 +495,48 @@ This list will be sorted return [changeHours, changeHourText] } + public static CreateOhObjectStore( + tags: Store>, + key: string = "opening_hours", + prefixToIgnore?: string, + postfixToIgnore?: string + ): Store { + prefixToIgnore ??= "" + postfixToIgnore ??= "" + const country = tags.map((tags) => tags._country) + return tags + .mapD((tags) => { + const value: string = tags[key] + if (value === undefined) { + return undefined + } + if ( + (prefixToIgnore || postfixToIgnore) && + value.startsWith(prefixToIgnore) && + value.endsWith(postfixToIgnore) + ) { + return value + .substring(prefixToIgnore.length, value.length - postfixToIgnore.length) + .trim() + } + return value + }) + .mapD( + (ohtext) => { + try { + return OH.CreateOhObject(tags.data, ohtext, country.data) + } catch (e) { + return "error" + } + }, + [country] + ) + } public static CreateOhObject( tags: Record & { _lat: number; _lon: number; _country?: string }, - textToParse: string + textToParse: string, + country?: string ) { // noinspection JSPotentiallyInvalidConstructorUsage return new opening_hours( @@ -506,7 +545,7 @@ This list will be sorted lat: tags._lat, lon: tags._lon, address: { - country_code: tags._country?.toLowerCase(), + country_code: country.toLowerCase(), state: undefined, }, }, diff --git a/src/UI/OpeningHours/OpeningHoursVisualization.ts b/src/UI/OpeningHours/OpeningHoursVisualization.ts index f51c225dff..560a2276d5 100644 --- a/src/UI/OpeningHours/OpeningHoursVisualization.ts +++ b/src/UI/OpeningHours/OpeningHoursVisualization.ts @@ -3,7 +3,6 @@ import Combine from "../Base/Combine" import { FixedUiElement } from "../Base/FixedUiElement" import { OH } from "./OpeningHours" import Translations from "../i18n/Translations" -import Constants from "../../Models/Constants" import BaseUIElement from "../BaseUIElement" import Toggle from "../Input/Toggle" import { VariableUiElement } from "../Base/VariableUIElement" @@ -30,48 +29,20 @@ export default class OpeningHoursVisualization extends Toggle { prefix = "", postfix = "" ) { - const country = tags.map((tags) => tags._country) + const openingHoursStore = OH.CreateOhObjectStore(tags, key, prefix, postfix) const ohTable = new VariableUiElement( - tags - .map((tags) => { - const value: string = tags[key] - if (value === undefined) { - return undefined - } - if (value.startsWith(prefix) && value.endsWith(postfix)) { - return value.substring(prefix.length, value.length - postfix.length).trim() - } - return value - }) // This mapping will absorb all other changes to tags in order to prevent regeneration - .map( - (ohtext) => { - if (ohtext === undefined) { - return new FixedUiElement( - "No opening hours defined with key " + key - ).SetClass("alert") - } - try { - return OpeningHoursVisualization.CreateFullVisualisation( - OH.CreateOhObject(tags.data, ohtext) - ) - } catch (e) { - console.warn(e, e.stack) - return new Combine([ - Translations.t.general.opening_hours.error_loading, - new Toggle( - new FixedUiElement(e).SetClass("subtle"), - undefined, - state?.osmConnection?.userDetails.map( - (userdetails) => - userdetails.csCount >= - Constants.userJourney.tagsVisibleAndWikiLinked - ) - ), - ]) - } - }, - [country] - ) + openingHoursStore.map((opening_hours_obj) => { + if (opening_hours_obj === undefined) { + return new FixedUiElement("No opening hours defined with key " + key).SetClass( + "alert" + ) + } + + if (opening_hours_obj === "error") { + return Translations.t.general.opening_hours.error_loading + } + return OpeningHoursVisualization.CreateFullVisualisation(opening_hours_obj) + }) ) super( diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index ce09eebe4b..7bfdf0b937 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -81,6 +81,7 @@ import LogoutButton from "./Base/LogoutButton.svelte" import OpenJosm from "./Base/OpenJosm.svelte" import MarkAsFavourite from "./Popup/MarkAsFavourite.svelte" import MarkAsFavouriteMini from "./Popup/MarkAsFavouriteMini.svelte" +import NextChangeViz from "./OpeningHours/NextChangeViz.svelte" class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -827,6 +828,46 @@ export default class SpecialVisualizations { ) }, }, + { + funcName: "opening_hours_state", + docs: "A small element, showing if the POI is currently open and when the next change is", + args: [ + { + name: "key", + defaultValue: "opening_hours", + doc: "The tagkey from which the opening hours are read.", + }, + { + name: "prefix", + defaultValue: "", + doc: "Remove this string from the start of the value before parsing. __Note: use `&LPARENs` to indicate `(` if needed__", + }, + { + name: "postfix", + defaultValue: "", + doc: "Remove this string from the end of the value before parsing. __Note: use `&RPARENs` to indicate `)` if needed__", + }, + ], + needsUrls: [], + constr( + state: SpecialVisualizationState, + tags: UIEventSource>, + args: string[], + feature: Feature, + layer: LayerConfig + ): BaseUIElement { + const keyToUse = args[0] + const prefix = args[1] + const postfix = args[2] + return new SvelteUIElement(NextChangeViz, { + state, + keyToUse, + tags, + prefix, + postfix, + }) + }, + }, { funcName: "canonical", needsUrls: [], From 2156c423905957cbac47a5c46fed6c728a1479b0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 4 Dec 2023 16:08:47 +0100 Subject: [PATCH 18/22] Chore: formatting --- assets/layers/icons/icons.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/assets/layers/icons/icons.json b/assets/layers/icons/icons.json index 9c5fc0b788..4e8c386074 100644 --- a/assets/layers/icons/icons.json +++ b/assets/layers/icons/icons.json @@ -67,9 +67,13 @@ ], "metacondition": "__showTimeSensitiveIcons!=no" }, - {"id":"open_until", - "labels": ["defaults","in_favourite"], - "#":"Titleicon showing 'open until 17:00'", + { + "id": "open_until", + "labels": [ + "defaults", + "in_favourite" + ], + "#": "Titleicon showing 'open until 17:00'", "icon": { "class": "w-20 mx-1 flex items-center" }, From baf858bc214a0847397b99e3f65f40cd4c6cdb25 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 4 Dec 2023 16:09:30 +0100 Subject: [PATCH 19/22] Refactoring: remove slot name; fix logout button --- src/UI/Base/LoginButton.svelte | 2 +- src/UI/Base/LogoutButton.svelte | 2 +- src/UI/Favourites/FavouriteSummary.svelte | 2 ++ src/UI/Favourites/Favourites.svelte | 10 ++++++++++ src/UI/Popup/AddNewPoint/AddNewPoint.svelte | 2 +- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/UI/Base/LoginButton.svelte b/src/UI/Base/LoginButton.svelte index 504d487132..7381f7b781 100644 --- a/src/UI/Base/LoginButton.svelte +++ b/src/UI/Base/LoginButton.svelte @@ -11,7 +11,7 @@ diff --git a/src/UI/Base/LogoutButton.svelte b/src/UI/Base/LogoutButton.svelte index 93435f9eb6..996fea08a8 100644 --- a/src/UI/Base/LogoutButton.svelte +++ b/src/UI/Base/LogoutButton.svelte @@ -9,7 +9,7 @@
+ diff --git a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte index ef2616257f..d28c573faf 100644 --- a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -161,7 +161,7 @@ 2. What do we want to add? 3. Are all elements of this category visible? (i.e. there are no filters possibly hiding this, is the data still loading, ...) --> - +
{#if $zoom < Constants.minZoomLevelToAddNewPoint} From 3135b83598d7070d14d31ec745a7c0af9a3469ef Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 4 Dec 2023 16:10:05 +0100 Subject: [PATCH 20/22] Update tag totals, sort title tags by popularity to give the most precise title --- langs/en.json | 1 + scripts/generateFavouritesLayer.ts | 29 +- scripts/generateStats.ts | 11 +- src/Logic/Tags/RegexTag.ts | 2 +- src/Logic/Tags/TagUtils.ts | 21 + src/Models/ThemeConfig/TagRenderingConfig.ts | 23 +- src/assets/key_totals.json | 611 ++++++++++++------- 7 files changed, 485 insertions(+), 213 deletions(-) diff --git a/langs/en.json b/langs/en.json index 2f7c43e9aa..b722dd1d4a 100644 --- a/langs/en.json +++ b/langs/en.json @@ -62,6 +62,7 @@ "downloadGpx": "Download your favourites as GPX", "intro": "You marked {length} locations as a favourite location.", "introPrivacy": "This list is only visible to you", + "loginToSeeList": "Login to see the list of locations you marked as favourite", "tab": "Your favourites", "title": "Your favourite locations" }, diff --git a/scripts/generateFavouritesLayer.ts b/scripts/generateFavouritesLayer.ts index a60e9f22c9..5c48e0ef00 100644 --- a/scripts/generateFavouritesLayer.ts +++ b/scripts/generateFavouritesLayer.ts @@ -13,7 +13,7 @@ import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson" import { TagUtils } from "../src/Logic/Tags/TagUtils" import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" -import icons from "../src/assets/generated/layers/icons.json" + export class GenerateFavouritesLayer extends Script { private readonly layers: LayerConfigJson[] = [] @@ -40,6 +40,19 @@ export class GenerateFavouritesLayer extends Script { } } + private sortMappings(mappings: MappingConfigJson[]): MappingConfigJson[] { + const sortedMappings: MappingConfigJson[] = [...mappings] + sortedMappings.sort((a, b) => { + const aTag = TagUtils.Tag(a.if) + const bTag = TagUtils.Tag(b.if) + const aPop = TagUtils.GetPopularity(aTag) + const bPop = TagUtils.GetPopularity(bTag) + console.log("Comparing", a.if, "with", b.if, { aPop, bPop }) + return aPop - bPop + }) + + return sortedMappings + } private addTagRenderings(proto: LayerConfigJson) { const blacklistedIds = new Set([ "images", @@ -96,7 +109,7 @@ export class GenerateFavouritesLayer extends Script { continue } newTr.condition = { - and: Utils.NoNull([(newTr.condition, layerConfig.source["osmTags"])]), + and: Utils.NoNull([newTr.condition, layerConfig.source["osmTags"]]), } generatedTagRenderings.push(newTr) blacklistedIds.add(newTr.id) @@ -181,7 +194,7 @@ export class GenerateFavouritesLayer extends Script { } private addTitle(proto: LayerConfigJson) { - const mappings: MappingConfigJson[] = [] + let mappings: MappingConfigJson[] = [] for (const layer of this.layers) { const t = layer.title const tags: TagConfigJson = layer.source["osmTags"] @@ -238,6 +251,8 @@ export class GenerateFavouritesLayer extends Script { } } + mappings = this.sortMappings(mappings) + if (proto.title["mappings"]) { mappings.unshift(...proto.title["mappings"]) } @@ -248,6 +263,14 @@ export class GenerateFavouritesLayer extends Script { }) } + for (const mapping of mappings) { + const opt = TagUtils.optimzeJson(mapping.if) + if (typeof opt === "boolean") { + continue + } + mapping.if = opt + } + proto.title = { mappings, } diff --git a/scripts/generateStats.ts b/scripts/generateStats.ts index 1dd5ced370..2931cbaf79 100644 --- a/scripts/generateStats.ts +++ b/scripts/generateStats.ts @@ -4,6 +4,8 @@ import { TagUtils } from "../src/Logic/Tags/TagUtils" import { Utils } from "../src/Utils" import { writeFileSync } from "fs" import ScriptUtils from "./ScriptUtils" +import TagRenderingConfig from "../src/Models/ThemeConfig/TagRenderingConfig" +import { And } from "../src/Logic/Tags/And" /* Downloads stats on osmSource-tags and keys from tagInfo */ @@ -21,7 +23,12 @@ async function main(includeTags = true) { continue } - const sources = TagUtils.Tag(layer.source["osmTags"]) + const sourcesList = [TagUtils.Tag(layer.source["osmTags"])] + if (layer?.title) { + sourcesList.push(...new TagRenderingConfig(layer.title).usedTags()) + } + + const sources = new And(sourcesList) const allKeys = sources.usedKeys() for (const key of allKeys) { if (!keysAndTags.has(key)) { @@ -68,6 +75,8 @@ async function main(includeTags = true) { "./src/assets/key_totals.json", JSON.stringify( { + "#": "Generated with generateStats.ts", + date: new Date().toISOString(), keys: Utils.MapToObj(keyTotal, (t) => t), tags: Utils.MapToObj(tagTotal, (v) => Utils.MapToObj(v, (t) => t)), }, diff --git a/src/Logic/Tags/RegexTag.ts b/src/Logic/Tags/RegexTag.ts index d133c3663d..436a9175f9 100644 --- a/src/Logic/Tags/RegexTag.ts +++ b/src/Logic/Tags/RegexTag.ts @@ -298,7 +298,7 @@ export class RegexTag extends TagsFilter { if (typeof this.key === "string") { return [this.key] } - throw "Key cannot be determined as it is a regex" + return [] } usedTags(): { key: string; value: string }[] { diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index 0a8de68d69..50bf9a93b8 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -869,6 +869,27 @@ export class TagUtils { return TagUtils.keyCounts.keys[key] } + public static GetPopularity(tag: TagsFilter): number | undefined { + if (tag instanceof And) { + return Math.min(...Utils.NoNull(tag.and.map((t) => TagUtils.GetPopularity(t)))) + } + if (tag instanceof Or) { + return Math.max(...Utils.NoNull(tag.or.map((t) => TagUtils.GetPopularity(t)))) + } + if (tag instanceof Tag) { + return TagUtils.GetCount(tag.key, tag.value) + } + if (tag instanceof RegexTag) { + const key = tag.key + if (key instanceof RegExp || tag.invert || tag.isNegative()) { + return undefined + } + return TagUtils.GetCount(key) + } + + return undefined + } + private static order(a: TagsFilter, b: TagsFilter, usePopularity: boolean): number { const rta = a instanceof RegexTag const rtb = b instanceof RegexTag diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index 424141bd48..17c8404893 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -16,10 +16,10 @@ import { } from "./Json/QuestionableTagRenderingConfigJson" import { FixedUiElement } from "../../UI/Base/FixedUiElement" import { Paragraph } from "../../UI/Base/Paragraph" -import Svg from "../../Svg" import Validators, { ValidatorType } from "../../UI/InputElement/Validators" import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" import Constants from "../Constants" +import { RegexTag } from "../../Logic/Tags/RegexTag" export interface Icon {} @@ -800,4 +800,25 @@ export default class TagRenderingConfig { labels, ]).SetClass("flex flex-col") } + + public usedTags(): TagsFilter[] { + const tags: TagsFilter[] = [] + tags.push( + this.metacondition, + this.condition, + this.freeform?.key ? new RegexTag(this.freeform?.key, /.*/) : undefined, + this.invalidValues + ) + for (const m of this.mappings ?? []) { + tags.push(m.if) + tags.push(m.priorityIf) + tags.push(...(m.addExtraTags ?? [])) + if (typeof m.hideInAnswer !== "boolean") { + tags.push(m.hideInAnswer) + } + tags.push(m.ifnot) + } + + return Utils.NoNull(tags) + } } diff --git a/src/assets/key_totals.json b/src/assets/key_totals.json index d85e04329a..0091b9a672 100644 --- a/src/assets/key_totals.json +++ b/src/assets/key_totals.json @@ -1,229 +1,426 @@ { + "#": "Generated with generateStats.ts", + "date": "2023-12-04T14:42:01.299Z", "keys": { - "addr:street": 117211930, - "addr:housenumber": 125040768, - "emergency": 1939478, - "barrier": 18424246, - "tourism": 2683525, - "amenity": 20541353, - "bench": 894256, - "rental": 8838, - "bicycle_rental": 7447, - "vending": 206755, - "service:bicycle:rental": 3570, - "pub": 316, - "theme": 426, - "service:bicycle:.*": 0, - "service:bicycle:cleaning": 807, - "shop": 5062252, - "service:bicycle:retail": 9162, - "network": 2181336, - "sport": 2194801, - "service:bicycle:repair": 11381, - "association": 369, - "ngo": 42, - "leisure": 7368076, - "club": 38429, - "disused:amenity": 40880, - "planned:amenity": 205, - "tileId": 0, - "construction:amenity": 1206, - "cycleway": 906487, - "highway": 218189453, - "bicycle": 6218071, - "cyclestreet": 8185, - "camera:direction": 40676, - "direction": 1896015, - "access": 16030036, - "entrance": 2954076, - "name:etymology": 24485, - "memorial": 132172, - "indoor": 353116, - "name:etymology:wikidata": 285224, - "landuse": 35524214, - "name": 88330405, - "protect_class": 73801, - "information": 831513, - "man_made": 5116088, - "boundary": 2142378, - "tower:type": 451658, - "playground": 109175, - "route": 939184, - "surveillance:type": 116760, - "natural": 52353504, - "building": 500469053 + "FIXME": 119237, + "access": 20023328, + "addr:housenumber": 146524978, + "addr:street": 137485111, + "advertising": 158347, + "amenity": 25340913, + "area": 1803451, + "association": 757, + "barrier": 23634152, + "bench": 1300789, + "bicycle": 7507086, + "bicycle_rental": 26948, + "boundary": 2366033, + "brand": 2317628, + "building": 585543589, + "camera:direction": 61201, + "climbing": 9051, + "club": 53046, + "construction:amenity": 1943, + "conveying": 27311, + "craft": 296376, + "crossing": 8736722, + "cyclestreet": 12505, + "cycleway": 1016837, + "direction": 2978834, + "disused:amenity": 63413, + "dog": 95086, + "door": 280843, + "drinking_water": 136067, + "emergency": 2542692, + "entrance": 3769592, + "fixme": 1746318, + "footway": 7540651, + "generator:source": 2387982, + "healthcare": 790125, + "highway": 249307936, + "indoor": 562051, + "information": 1073014, + "isced:2011:level": 27, + "isced:level:2011": 74, + "landuse": 41730047, + "leisure": 8955744, + "man_made": 6799900, + "memorial": 209327, + "motorcar": 621864, + "name": 98684655, + "name:etymology": 56375, + "name:etymology:wikidata": 1174439, + "name:nl": 80468, + "natural": 64176097, + "ngo": 57, + "office": 1092855, + "parking_space": 600707, + "planned:amenity": 237, + "playground": 182188, + "post_office": 16379, + "protect_class": 83815, + "pub": 324, + "public_transport": 5111577, + "railway": 7068070, + "recycling_type": 385569, + "ref": 18607577, + "rental": 13611, + "route": 1075802, + "service:bicycle:cleaning": 1179, + "service:bicycle:pump": 14053, + "service:bicycle:pump:operational_status": 344, + "service:bicycle:rental": 4599, + "service:bicycle:repair": 15470, + "service:bicycle:retail": 11467, + "service:bicycle:tools": 6227, + "shelter": 1647743, + "shop": 5860878, + "species": 1656206, + "species:wikidata": 107778, + "sport": 2580042, + "subject": 40076, + "surface:colour": 17851, + "surveillance:type": 171923, + "theme": 906, + "toilets": 90842, + "tourism": 3211694, + "tower:type": 596349, + "type": 11757856, + "vending": 252016 }, "tags": { - "emergency": { - "defibrillator": 51273, - "ambulance_station": 11047, - "fire_extinguisher": 7355, - "fire_hydrant": 1598739 - }, - "barrier": { - "cycle_barrier": 104166, - "bollard": 502220, - "wall": 3535056 - }, - "tourism": { - "artwork": 187470, - "map": 51, - "viewpoint": 191765 + "advertising": { + "billboard": 76420, + "board": 15040, + "column": 21212, + "flag": 4264, + "poster_box": 22932, + "screen": 1352, + "sculpture": 145, + "sign": 6172, + "tarp": 407, + "totem": 7097, + "wall_painting": 132 }, "amenity": { - "bench": 1736979, - "bicycle_library": 36, - "bicycle_rental": 49082, - "vending_machine": 201871, - "bar": 199662, - "pub": 174979, - "cafe": 467521, - "restaurant": 1211671, - "bicycle_wash": 44, - "bike_wash": 0, - "bicycle_repair_station": 9247, - "bicycle_parking": 435959, - "binoculars": 479, - "biergarten": 10309, - "charging_station": 65402, - "drinking_water": 250463, - "fast_food": 460079, - "fire_station": 122200, - "parking": 4255206, - "public_bookcase": 13120, - "toilets": 350648, - "recycling": 333925, - "waste_basket": 550357, - "waste_disposal": 156765 - }, - "bench": { - "stand_up_bench": 87, - "yes": 524993 - }, - "service:bicycle:rental": { - "yes": 3054 - }, - "pub": { - "cycling": 9, - "bicycle": 0 - }, - "theme": { - "cycling": 8, - "bicycle": 16 - }, - "service:bicycle:cleaning": { - "yes": 607, - "diy": 0 - }, - "shop": { - "bicycle": 46488, - "sports": 37024 - }, - "sport": { - "cycling": 6045, - "bicycle": 96 + "animal_shelter": 6056, + "atm": 207899, + "bank": 389470, + "bar": 219208, + "bench": 2313183, + "bicycle_library": 46, + "bicycle_parking": 616881, + "bicycle_rental": 63710, + "bicycle_repair_station": 14026, + "bicycle_wash": 79, + "biergarten": 10323, + "bike_wash": 1, + "binoculars": 1109, + "cafe": 530066, + "car_rental": 26726, + "charging_station": 111996, + "childcare": 50390, + "clinic": 179739, + "clock": 25274, + "college": 64379, + "dentist": 122076, + "doctors": 166850, + "drinking_water": 294750, + "fast_food": 533335, + "fire_station": 131842, + "hospital": 204756, + "ice_cream": 48853, + "kindergarten": 294441, + "nightclub": 22779, + "parcel_locker": 44270, + "parking": 5158899, + "parking_space": 2292063, + "pharmacy": 383181, + "post_box": 370286, + "post_office": 198908, + "pub": 185475, + "public_bookcase": 21608, + "reception_desk": 2426, + "recycling": 417512, + "restaurant": 1346895, + "school": 1286594, + "shelter": 494594, + "shower": 27029, + "ticket_validator": 7730, + "toilets": 417991, + "university": 54299, + "vending_machine": 247257, + "veterinary": 52813, + "waste_basket": 759718, + "waste_disposal": 219245 }, "association": { - "cycling": 5, - "bicycle": 20 + "bicycle": 47, + "cycling": 5 }, - "ngo": { - "cycling": 0, - "bicycle": 0 + "barrier": { + "bollard": 668017, + "cycle_barrier": 122201, + "kerb": 1178769, + "retaining_wall": 472454, + "wall": 4448788 }, - "leisure": { - "bird_hide": 5669, - "nature_reserve": 117016, - "picnic_table": 206322, - "pitch": 1990293, - "playground": 705102 - }, - "club": { - "cycling": 3, - "bicycle": 49 - }, - "disused:amenity": { - "charging_station": 164 - }, - "planned:amenity": { - "charging_station": 115 - }, - "construction:amenity": { - "charging_station": 221 - }, - "cycleway": { - "lane": 314576, - "track": 86541, - "shared_lane": 60824 - }, - "highway": { - "residential": 61321708, - "crossing": 6119521, - "cycleway": 1423789, - "traffic_signals": 1512639, - "tertiary": 7051727, - "unclassified": 15756878, - "secondary": 4486617, - "primary": 3110552, - "footway": 16496620, - "path": 11438303, - "steps": 1327396, - "corridor": 27051, - "pedestrian": 685989, - "bridleway": 102280, - "track": 22670967, - "living_street": 1519108, - "street_lamp": 2811705 + "bench": { + "stand_up_bench": 212, + "yes": 778144 }, "bicycle": { - "designated": 1110839 - }, - "cyclestreet": { - "yes": 8164 - }, - "access": { - "public": 6222, - "yes": 1363526 - }, - "memorial": { - "ghost_bike": 503 - }, - "indoor": { - "door": 9722 - }, - "landuse": { - "grass": 4898559, - "village_green": 104681 - }, - "name": { - "Park Oude God": 1 - }, - "information": { - "board": 242007, - "map": 85912, - "office": 24139, - "visitor_centre": 285 - }, - "man_made": { - "surveillance": 148172, - "watermill": 9699 + "designated": 1499247, + "no": 1614544, + "yes": 3753651 }, "boundary": { - "protected_area": 97075 + "protected_area": 111282 }, - "tower:type": { - "observation": 19654 + "climbing": { + "area": 191, + "crag": 2873, + "route": 1040, + "site": 14 }, - "playground": { - "forest": 56 + "club": { + "bicycle": 60, + "climbing": 1, + "cycling": 7 }, - "surveillance:type": { - "camera": 112963, - "ALPR": 2522, - "ANPR": 3 + "construction:amenity": { + "charging_station": 259 + }, + "conveying": { + "yes": 12153 + }, + "craft": { + "key_cutter": 3711, + "shoe_repair": 64 + }, + "crossing": { + "traffic_signals": 1408141 + }, + "cyclestreet": { + "yes": 12480 + }, + "cycleway": { + "lane": 300810, + "shared_lane": 71051, + "track": 77166 + }, + "disused:amenity": { + "charging_station": 289, + "drinking_water": 2758 + }, + "dog": { + "unleashed": 727 + }, + "drinking_water": { + "yes": 74561 + }, + "emergency": { + "ambulance_station": 13020, + "defibrillator": 80699, + "fire_extinguisher": 11605, + "fire_hydrant": 1928477 + }, + "footway": { + "crossing": 3111184 + }, + "generator:source": { + "wind": 390537 + }, + "healthcare": { + "physiotherapist": 17548 + }, + "highway": { + "bridleway": 107507, + "bus_stop": 3459595, + "corridor": 46847, + "crossing": 8505991, + "cycleway": 1693405, + "elevator": 39221, + "footway": 21573091, + "living_street": 1753722, + "motorway": 1182914, + "motorway_link": 829035, + "path": 13690001, + "pedestrian": 767066, + "primary": 3462637, + "primary_link": 433106, + "residential": 65553821, + "secondary": 5008689, + "secondary_link": 340521, + "service": 54202864, + "speed_camera": 61915, + "speed_display": 2621, + "steps": 1618344, + "street_lamp": 3879570, + "tertiary": 7809143, + "tertiary_link": 245867, + "track": 25718176, + "traffic_signals": 1709993, + "trunk": 1679773, + "trunk_link": 519826, + "unclassified": 16914480 + }, + "indoor": { + "area": 25332, + "corridor": 17609, + "door": 19157, + "level": 4253, + "room": 157006, + "wall": 32366 + }, + "information": { + "board": 321201, + "guidepost": 520873, + "map": 108166, + "office": 27749, + "route_marker": 59596, + "visitor_centre": 523 + }, + "isced:level:2011": { + "early_childhood": 0 + }, + "landuse": { + "village_green": 102589 + }, + "leisure": { + "bird_hide": 6607, + "dog_park": 21993, + "fitness_centre": 72920, + "fitness_station": 62923, + "hackerspace": 1537, + "nature_reserve": 129575, + "park": 1168747, + "picnic_table": 302582, + "pitch": 2307262, + "playground": 821692, + "sports_centre": 231823, + "track": 124600 + }, + "man_made": { + "surveillance": 205953 + }, + "memorial": { + "ghost_bike": 748, + "plaque": 45536 + }, + "motorcar": { + "no": 270350, + "yes": 190966 }, "natural": { - "tree": 18245059 + "cliff": 761375, + "rock": 229114, + "stone": 52141, + "tree": 23309774 + }, + "ngo": { + "bicycle": 0, + "cycling": 0 + }, + "office": { + "government": 250353 + }, + "parking_space": { + "disabled": 161162 + }, + "planned:amenity": { + "charging_station": 72 + }, + "playground": { + "forest": 77 + }, + "post_office": { + "post_partner": 7560 + }, + "pub": { + "bicycle": 0, + "cycling": 12 + }, + "public_transport": { + "platform": 3254387 + }, + "railway": { + "platform": 167408 + }, + "recycling_type": { + "centre": 29508, + "container": 355016 + }, + "route": { + "bus": 272174 + }, + "service:bicycle:cleaning": { + "diy": 4, + "yes": 909 + }, + "service:bicycle:pump": { + "no": 1548, + "yes": 12452 + }, + "service:bicycle:pump:operational_status": { + "broken": 122 + }, + "service:bicycle:rental": { + "yes": 3902 + }, + "service:bicycle:repair": { + "yes": 15134 + }, + "service:bicycle:tools": { + "no": 354, + "yes": 5872 + }, + "shelter": { + "yes": 884942 + }, + "shop": { + "bicycle": 51336, + "bicycle_rental": 1, + "rental": 5206, + "sports": 40802 + }, + "sport": { + "bicycle": 114, + "climbing": 29028, + "cycling": 8225 + }, + "surface:colour": { + "rainbow": 217 + }, + "surveillance:type": { + "ALPR": 4424, + "ANPR": 3, + "camera": 165247 + }, + "theme": { + "bicycle": 16, + "cycling": 7 + }, + "toilets": { + "yes": 70811 + }, + "tourism": { + "artwork": 245861, + "hotel": 407208, + "map": 51, + "viewpoint": 219932 + }, + "tower:type": { + "observation": 23057 + }, + "type": { + "route": 1005677 + }, + "vending": { + "elongated_coin": 816, + "parcel_pickup;parcel_mail_in": 522, + "parking_tickets": 70753, + "public_transport_tickets": 26895 } } } \ No newline at end of file From 58be4653291f098419445eef8e2904807dc32367 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 4 Dec 2023 16:18:33 +0100 Subject: [PATCH 21/22] Docs: add usertest --- .../2023-12-4 User Test Favourites.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Docs/UserTests/2023-12-4 User Test Favourites.md diff --git a/Docs/UserTests/2023-12-4 User Test Favourites.md b/Docs/UserTests/2023-12-4 User Test Favourites.md new file mode 100644 index 0000000000..27231f9f29 --- /dev/null +++ b/Docs/UserTests/2023-12-4 User Test Favourites.md @@ -0,0 +1,29 @@ + +## Task + +Add a (specified) feature as favourite +Find and use the list of favourites +Determine information from this list +Open the popup from this list + +## Background info + +User has used mapcomplete before + +## Results + +The user is asked to mark a specified bicycle shop as favourite. They find the big button to mark as favourite at the bottom. + +When asked to select another feature, they choose a bicycle pump. When hinted that 'they can add this in a different way', they immediately select the heart title icon. + +When asked to open the list of favourites, they open the 'hamburger'-menu. After a bit of looking, they spot the 'Your favourites'-button. + +They are a bit confused. The specified bicycle shop is advertised as `building or wall`. + +The bicycle pump is shown correctly, the icons are clear. When asked to open the popup for one of them, they click directly on the link. + +## Surfaced issues + +Due to the way the title is generated, wrong titles appeared: all titles from all layers are mixed and used as title, if the tags match. As such, the title `building or wall` appeared, as it happened to be on top and the bicycle shop had a `building~*` tag. + +This was resolved by sorting those titles by popularity. The least occuring tags/titles are placed first, so that the most specific title is shown. This might, in some cases, still result in differing titles (e.g. if something is e.g. both a shop and a café), but this should be exceptional. From d3895800f3d2451f0087e41c325a257605be788a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 5 Dec 2023 00:04:29 +0100 Subject: [PATCH 22/22] Favourites: some fixes to rendering the title --- .../climbing_opportunity.json | 3 +- scripts/generateFavouritesLayer.ts | 12 +++++-- src/Logic/Tags/RegexTag.ts | 33 +++++++++++++++++-- src/Logic/Tags/TagUtils.ts | 4 +-- .../TagRendering/TagRenderingAnswer.svelte | 1 + 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/assets/layers/climbing_opportunity/climbing_opportunity.json b/assets/layers/climbing_opportunity/climbing_opportunity.json index 89fd5d6795..11d7727910 100644 --- a/assets/layers/climbing_opportunity/climbing_opportunity.json +++ b/assets/layers/climbing_opportunity/climbing_opportunity.json @@ -29,7 +29,8 @@ "natural=stone" ] }, - "climbing=" + "climbing=", + "sport!=climbing" ] } }, diff --git a/scripts/generateFavouritesLayer.ts b/scripts/generateFavouritesLayer.ts index 5c48e0ef00..29b25d17d2 100644 --- a/scripts/generateFavouritesLayer.ts +++ b/scripts/generateFavouritesLayer.ts @@ -1,6 +1,6 @@ import Script from "./Script" import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson" -import { readFileSync, writeFileSync } from "fs" +import { existsSync, readFileSync, writeFileSync } from "fs" import { AllSharedLayers } from "../src/Customizations/AllSharedLayers" import { AllKnownLayoutsLazy } from "../src/Customizations/AllKnownLayouts" import { Utils } from "../src/Utils" @@ -47,7 +47,6 @@ export class GenerateFavouritesLayer extends Script { const bTag = TagUtils.Tag(b.if) const aPop = TagUtils.GetPopularity(aTag) const bPop = TagUtils.GetPopularity(bTag) - console.log("Comparing", a.if, "with", b.if, { aPop, bPop }) return aPop - bPop }) @@ -282,7 +281,14 @@ export class GenerateFavouritesLayer extends Script { this.addTagRenderings(proto) this.addTitle(proto) this.addTitleIcons(proto) - writeFileSync("./assets/layers/favourite/favourite.json", JSON.stringify(proto, null, " ")) + const targetContent = JSON.stringify(proto, null, " ") + const path = "./assets/layers/favourite/favourite.json" + if (existsSync(path)) { + if (readFileSync(path, "utf8") === targetContent) { + return // No need to actually write the file, it is identical + } + } + writeFileSync(path, targetContent) } private readLayer(path: string): LayerConfigJson { diff --git a/src/Logic/Tags/RegexTag.ts b/src/Logic/Tags/RegexTag.ts index 436a9175f9..f024f82517 100644 --- a/src/Logic/Tags/RegexTag.ts +++ b/src/Logic/Tags/RegexTag.ts @@ -12,6 +12,9 @@ export class RegexTag extends TagsFilter { super() this.key = key this.value = value + if (this.value instanceof RegExp && ("" + this.value).startsWith("^(^(")) { + throw "Detected a duplicate start marker ^(^( in a regextag:" + this.value + } this.invert = invert this.matchesEmpty = RegexTag.doesMatch("", this.value) } @@ -42,11 +45,21 @@ export class RegexTag extends TagsFilter { return possibleRegex.test(fromTag) } - private static source(r: string | RegExp) { + private static source(r: string | RegExp, includeStartMarker: boolean = true) { if (typeof r === "string") { return r } - return r.source + if (r === undefined) { + return undefined + } + const src = r.source + if (includeStartMarker) { + return src + } + if (src.startsWith("^(") && src.endsWith(")$")) { + return src.substring(2, src.length - 2) + } + return src } /** @@ -83,8 +96,22 @@ export class RegexTag extends TagsFilter { } } + /** + * import { TagUtils } from "./TagUtils"; + * + * const t = TagUtils.Tag("a~b") + * t.asJson() // => "a~b" + * + * const t = TagUtils.Tag("a=") + * t.asJson() // => "a=" + */ asJson(): TagConfigJson { - return this.asHumanString() + const v = RegexTag.source(this.value, false) + if (typeof this.key === "string") { + const oper = typeof this.value === "string" ? "=" : "~" + return `${this.key}${this.invert ? "!" : ""}${oper}${v}` + } + return `${this.key.source}${this.invert ? "!" : ""}~~${v}` } isUsableAsAnswer(): boolean { diff --git a/src/Logic/Tags/TagUtils.ts b/src/Logic/Tags/TagUtils.ts index 50bf9a93b8..1893399cec 100644 --- a/src/Logic/Tags/TagUtils.ts +++ b/src/Logic/Tags/TagUtils.ts @@ -871,10 +871,10 @@ export class TagUtils { public static GetPopularity(tag: TagsFilter): number | undefined { if (tag instanceof And) { - return Math.min(...Utils.NoNull(tag.and.map((t) => TagUtils.GetPopularity(t)))) + return Math.min(...Utils.NoNull(tag.and.map((t) => TagUtils.GetPopularity(t)))) - 1 } if (tag instanceof Or) { - return Math.max(...Utils.NoNull(tag.or.map((t) => TagUtils.GetPopularity(t)))) + return Math.max(...Utils.NoNull(tag.or.map((t) => TagUtils.GetPopularity(t)))) + 1 } if (tag instanceof Tag) { return TagUtils.GetCount(tag.key, tag.value) diff --git a/src/UI/Popup/TagRendering/TagRenderingAnswer.svelte b/src/UI/Popup/TagRendering/TagRenderingAnswer.svelte index 243413fafa..62fdee263b 100644 --- a/src/UI/Popup/TagRendering/TagRenderingAnswer.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingAnswer.svelte @@ -26,6 +26,7 @@ onDestroy( tags.addCallbackAndRun((tags) => { _tags = tags + console.log("Getting render value for", _tags,config) trs = Utils.NoNull(config?.GetRenderValues(_tags)) }) )