From 6c0d54d7694292f3c7038ba336489d6de5231060 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 25 Jan 2025 02:06:29 +0100 Subject: [PATCH 01/59] Fix: fix wikimedia attribution, fix #2332 --- src/Logic/ImageProviders/WikimediaImageProvider.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Logic/ImageProviders/WikimediaImageProvider.ts b/src/Logic/ImageProviders/WikimediaImageProvider.ts index bf1e7a3677..3ccc96f150 100644 --- a/src/Logic/ImageProviders/WikimediaImageProvider.ts +++ b/src/Logic/ImageProviders/WikimediaImageProvider.ts @@ -138,7 +138,12 @@ export class WikimediaImageProvider extends ImageProvider { query: { pages: { title: string; imageinfo: { extmetadata }[] }[] } }>(url, 365 * 24 * 60 * 60) const licenseInfo = new LicenseInfo() - const pageInfo = data.query.pages.at(-1) + const pages = data.query.pages + /*jup, a literal "-1" in an object, not a list!*/ + let pageInfo = pages["-1"] + if (Array.isArray(pages)) { + pageInfo = pages.at(-1) + } if (pageInfo === undefined) { return undefined } From 2f7e35a16e9896216d3fc4fce5d93d4272dc6219 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 25 Jan 2025 18:37:09 +0100 Subject: [PATCH 02/59] Fix: don't check if someone is logged in for a taghint --- src/UI/Popup/TagHint.svelte | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/UI/Popup/TagHint.svelte b/src/UI/Popup/TagHint.svelte index e7eb543dc1..583fb76d94 100644 --- a/src/UI/Popup/TagHint.svelte +++ b/src/UI/Popup/TagHint.svelte @@ -11,19 +11,15 @@ * Depending on the options, it'll link through to the wiki or might be completely hidden */ export let tags: TagsFilter - export let state: SpecialVisualizationState - export let currentProperties: Record = {} /** * If given, this function will be called to embed the given tags hint into this translation */ export let embedIn: ((string: string) => Translation) | undefined = undefined - const userDetails = state?.osmConnection?.userDetails let tagsExplanation = "" $: tagsExplanation = tags?.asHumanString(true, false, currentProperties) -{#if !userDetails}
{#if tags === undefined} @@ -33,4 +29,3 @@ {/if}
-{/if} From 59dc9012c06765ba53c4652566b226c5128c141e Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 26 Jan 2025 01:42:41 +0100 Subject: [PATCH 03/59] UX: don't show the gray 'image loading' for wikimedia; assume there is none --- src/Logic/ImageProviders/AllImageProviders.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Logic/ImageProviders/AllImageProviders.ts b/src/Logic/ImageProviders/AllImageProviders.ts index 3c9ba228aa..76f96737c9 100644 --- a/src/Logic/ImageProviders/AllImageProviders.ts +++ b/src/Logic/ImageProviders/AllImageProviders.ts @@ -25,7 +25,7 @@ export default class AllImageProviders { "Category:" ]) - private static ImageAttributionSource: ImageProvider[] = [ + private static imageAttributionSources: ImageProvider[] = [ Imgur.singleton, Mapillary.singleton, WikidataImageProvider.singleton, @@ -34,10 +34,10 @@ export default class AllImageProviders { AllImageProviders.genericImageProvider ] public static apiUrls: string[] = [].concat( - ...AllImageProviders.ImageAttributionSource.map((src) => src.apiUrls()) + ...AllImageProviders.imageAttributionSources.map((src) => src.apiUrls()) ) public static defaultKeys = [].concat( - AllImageProviders.ImageAttributionSource.map((provider) => provider.defaultKeyPrefixes) + AllImageProviders.imageAttributionSources.map((provider) => provider.defaultKeyPrefixes) ) private static providersByName = { imgur: Imgur.singleton, @@ -52,7 +52,7 @@ export default class AllImageProviders { } public static async selectBestProvider(key: string, value: string): Promise { - for (const imageProvider of AllImageProviders.ImageAttributionSource) { + for (const imageProvider of AllImageProviders.imageAttributionSources) { try { const extracted = await Promise.all(await imageProvider.ExtractUrls(key, value)) if (extracted?.length > 0) { @@ -78,7 +78,13 @@ export default class AllImageProviders { public static estimateNumberOfImages(tags: Record, prefixes: string[] = undefined): number { let count = 0 - const allPrefixes = Utils.Dedup(prefixes ?? [].concat(...AllImageProviders.ImageAttributionSource.map(s => s.defaultKeyPrefixes))) + const sources = [Imgur.singleton, + Mapillary.singleton, + WikidataImageProvider.singleton, + WikimediaImageProvider.singleton, + Panoramax.singleton, + AllImageProviders.genericImageProvider] + const allPrefixes = Utils.Dedup(prefixes ?? [].concat(...sources.map(s => s.defaultKeyPrefixes))) for (const prefix of allPrefixes) { for (const k in tags) { if (k === prefix || k.startsWith(prefix + ":")) { @@ -108,7 +114,7 @@ export default class AllImageProviders { const source = new UIEventSource([]) const allSources: Store[] = [] - for (const imageProvider of AllImageProviders.ImageAttributionSource) { + for (const imageProvider of AllImageProviders.imageAttributionSources) { /* By default, 'GetRelevantUrls' uses the defaultKeyPrefixes. However, we override them if a custom image tag is set, e.g. 'image:menu' From 640fe62440f3a3fa68a8b41d588814b6b38572c8 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 26 Jan 2025 01:42:55 +0100 Subject: [PATCH 04/59] Chore: remove obsolete import --- src/UI/Image/ImageCarousel.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/UI/Image/ImageCarousel.svelte b/src/UI/Image/ImageCarousel.svelte index c071c1d1a0..22b3d2d884 100644 --- a/src/UI/Image/ImageCarousel.svelte +++ b/src/UI/Image/ImageCarousel.svelte @@ -3,7 +3,6 @@ import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import type { SpecialVisualizationState } from "../SpecialVisualization" import DeletableImage from "./DeletableImage.svelte" - import Loading from "../Base/Loading.svelte" import LoadingPlaceholder from "../Base/LoadingPlaceholder.svelte" export let images: Store From 87ab165e58472c4c88cf44c45f823706f1969c93 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 26 Jan 2025 03:32:03 +0100 Subject: [PATCH 05/59] Fix: actually don't show grey loading placeholder for wikidata/wikipedia, add tests --- src/Logic/ImageProviders/AllImageProviders.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Logic/ImageProviders/AllImageProviders.ts b/src/Logic/ImageProviders/AllImageProviders.ts index 76f96737c9..a9a5d416b1 100644 --- a/src/Logic/ImageProviders/AllImageProviders.ts +++ b/src/Logic/ImageProviders/AllImageProviders.ts @@ -73,6 +73,8 @@ export default class AllImageProviders { * Will simply count all image tags * * AllImageProviders.estimateNumberOfImages({image:"abc", "mapillary": "123", "panoramax:0": "xyz"}) // => 3 + * AllImageProviders.estimateNumberOfImages({wikidata:"Q123", "wikipedia": "nl:xyz"}) // => 0 + * * */ public static estimateNumberOfImages(tags: Record, prefixes: string[] = undefined): number { @@ -80,8 +82,6 @@ export default class AllImageProviders { const sources = [Imgur.singleton, Mapillary.singleton, - WikidataImageProvider.singleton, - WikimediaImageProvider.singleton, Panoramax.singleton, AllImageProviders.genericImageProvider] const allPrefixes = Utils.Dedup(prefixes ?? [].concat(...sources.map(s => s.defaultKeyPrefixes))) From 6d68ca7988bc6db0982ebae35efd90b5c829f435 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 26 Jan 2025 03:32:51 +0100 Subject: [PATCH 06/59] Themes(education): move 'kindergarten' into 'school', add 'school:orientation' (only in Belgium), add more differentiated icons --- assets/layers/childcare/childcare.json | 157 +++++++++++ .../childcare.svg | 0 .../childcare.svg.license | 0 .../kindergarten.svg.license | 0 assets/layers/childcare/license_info.json | 12 + .../kindergarten_childcare.json | 243 ------------------ .../kindergarten_childcare/license_info.json | 25 -- .../kindergarten.svg | 0 assets/layers/school/kindergarten.svg.license | 2 + assets/layers/school/license_info.json | 37 +++ assets/layers/school/school.json | 242 +++++++++++++---- assets/layers/school/school_academic.svg | 74 ++++++ .../layers/school/school_academic.svg.license | 2 + assets/layers/school/school_primary.svg | 153 +++++++++++ .../layers/school/school_primary.svg.license | 2 + assets/themes/education/education.json | 9 +- langs/layers/ca.json | 69 ++--- langs/layers/cs.json | 98 +++---- langs/layers/de.json | 99 +++---- langs/layers/en.json | 146 ++++++----- langs/layers/es.json | 98 +++---- langs/layers/fr.json | 22 +- langs/layers/nl.json | 182 ++++++++----- langs/layers/pl.json | 85 +++--- langs/layers/ru.json | 14 +- langs/themes/nl.json | 114 +++++++- 26 files changed, 1147 insertions(+), 738 deletions(-) create mode 100644 assets/layers/childcare/childcare.json rename assets/layers/{kindergarten_childcare => childcare}/childcare.svg (100%) rename assets/layers/{kindergarten_childcare => childcare}/childcare.svg.license (100%) rename assets/layers/{kindergarten_childcare => childcare}/kindergarten.svg.license (100%) create mode 100644 assets/layers/childcare/license_info.json delete mode 100644 assets/layers/kindergarten_childcare/kindergarten_childcare.json delete mode 100644 assets/layers/kindergarten_childcare/license_info.json rename assets/layers/{kindergarten_childcare => school}/kindergarten.svg (100%) create mode 100644 assets/layers/school/kindergarten.svg.license create mode 100644 assets/layers/school/school_academic.svg create mode 100644 assets/layers/school/school_academic.svg.license create mode 100644 assets/layers/school/school_primary.svg create mode 100644 assets/layers/school/school_primary.svg.license diff --git a/assets/layers/childcare/childcare.json b/assets/layers/childcare/childcare.json new file mode 100644 index 0000000000..4502ec88df --- /dev/null +++ b/assets/layers/childcare/childcare.json @@ -0,0 +1,157 @@ +{ + "id": "childcare", + "name": { + "en": "Childcare", + "nl": "Kinderopvang", + "de": "Kinderkrippen", + "ca": "Guarderies d'infants" + }, + "description": "Shows kindergartens and preschools. Both are grouped in one layer, as they are regularly confused with each other", + "source": { + "osmTags": "amenity=childcare" + }, + "minzoom": 12, + "title": { + "mappings": [ + { + "if": "amenity=childcare", + "then": { + "en": "Childcare {name}", + "nl": "Kinderopvang {name}", + "de": "Kinderkrippe {name}", + "pl": "Żłobek {name}", + "cs": "Péče o děti {name}", + "es": "Guardería {name}" + } + } + ] + }, + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "label": { + "mappings": [ + { + "if": "name~*", + "then": "
{name}
" + } + ] + }, + "marker": [ + { + "icon": "circle", + "color": "white" + }, + { + "icon": { + "mappings": [ + { + "if": "amenity=childcare", + "then": "./assets/layers/childcare/childcare.svg" + } + ] + } + } + ] + } + ], + "lineRendering": [ + { + "color": "#62fc6c", + "width": 1 + } + ], + "presets": [ + { + "title": { + "en": "a childcare", + "nl": "een kinderopvang", + "de": "eine Kinderkrippe", + "ca": "una guarderia", + "pl": "żłobek", + "cs": "péče o děti", + "es": "una guardería" + }, + "description": "A childcare (also known as a nursery or daycare) is a facility which looks after small kids, but does not offer them an education program.", + "tags": [ + "amenity=childcare" + ] + } + ], + "tagRenderings": [ + { + "id": "name", + "question": { + "en": "What is the name of this facility?", + "de": "Wie lautet der Name dieser Einrichtung?", + "nl": "Wat is de naam van deze faciliteit?", + "ca": "Com s'anomena aquesta instal·lació?", + "pl": "Jaka jest nazwa tej placówki?", + "cs": "Jak se toto zařízení jmenuje?", + "es": "¿Cuál es el nombre de este centro?" + }, + "render": { + "en": "This facility is named {name}", + "de": "Diese Einrichtung hat den Namen {name}", + "ca": "Aquesta instal·lació s'anomena {name}", + "nl": "Deze faciliteit heet {name}", + "pl": "Ta placówka nazywa się {name}", + "cs": "Toto zařízení se jmenuje {name}", + "es": "Este centro se llama {name}" + }, + "freeform": { + "key": "name" + } + }, + "website", + "email", + "phone", + { + "builtin": "opening_hours", + "override": { + "question": { + "en": "When is this childcare opened?", + "nl": "Wanneer is deze kinderopvang geopend?", + "de": "Wann ist diese Kinderbetreuung geöffnet?", + "pl": "W jakich godzinach ten żłobek jest otwarty?", + "cs": "Kdy je tato péče o děti otevřena?", + "es": "¿Cuándo abre esta guardería?" + }, + "condition": "amenity=childcare" + } + }, + { + "id": "capacity", + "question": { + "en": "How much kids (at most) can be enrolled here?", + "nl": "Hoeveel kinderen kunnen hier terecht?", + "de": "Wie viele Kinder können hier maximal angemeldet werden?", + "ca": "Quants nens (com a màxim) es poden inscriure aquí?", + "pl": "Jak wiele dzieci (maksymalnie) może być tutaj zapisanych?", + "cs": "Kolik dětí (maximálně) zde může být zapsáno?", + "es": "¿Cuántos niños (como máximo) pueden inscribirse aquí?" + }, + "render": { + "en": "This facility has room for {capacity} kids", + "nl": "Hier kunnen {capacity} kinderen terecht", + "de": "Diese Einrichtung bietet Platz für {capacity} Kinder", + "ca": "Aquesta instal·lació té espai per a {capacity} nens", + "pl": "Ta placówka ma miejsce na {capacity} dzieci", + "cs": "Toto zařízení má prostor pro {capacity} dětí", + "es": "Este centro tiene capacidad para {capacity} niños" + }, + "freeform": { + "key": "capacity", + "type": "pnat" + } + } + ], + "deletion": true, + "allowMove": { + "enableRelocation": true, + "enableImproveAccuracy": true + } +} diff --git a/assets/layers/kindergarten_childcare/childcare.svg b/assets/layers/childcare/childcare.svg similarity index 100% rename from assets/layers/kindergarten_childcare/childcare.svg rename to assets/layers/childcare/childcare.svg diff --git a/assets/layers/kindergarten_childcare/childcare.svg.license b/assets/layers/childcare/childcare.svg.license similarity index 100% rename from assets/layers/kindergarten_childcare/childcare.svg.license rename to assets/layers/childcare/childcare.svg.license diff --git a/assets/layers/kindergarten_childcare/kindergarten.svg.license b/assets/layers/childcare/kindergarten.svg.license similarity index 100% rename from assets/layers/kindergarten_childcare/kindergarten.svg.license rename to assets/layers/childcare/kindergarten.svg.license diff --git a/assets/layers/childcare/license_info.json b/assets/layers/childcare/license_info.json new file mode 100644 index 0000000000..a60a3105ac --- /dev/null +++ b/assets/layers/childcare/license_info.json @@ -0,0 +1,12 @@ +[ + { + "path": "childcare.svg", + "license": "CC-BY-4.0", + "authors": [ + "Diego Naive" + ], + "sources": [ + "https://thenounproject.com/icon/child-care-332981/" + ] + } +] \ No newline at end of file diff --git a/assets/layers/kindergarten_childcare/kindergarten_childcare.json b/assets/layers/kindergarten_childcare/kindergarten_childcare.json deleted file mode 100644 index 45d8c7baa5..0000000000 --- a/assets/layers/kindergarten_childcare/kindergarten_childcare.json +++ /dev/null @@ -1,243 +0,0 @@ -{ - "id": "kindergarten_childcare", - "name": { - "en": "Kindergartens and childcare", - "nl": "Kleuterscholen en kinderopvang", - "de": "Kindergärten und Kinderkrippen", - "ca": "Llars d'infants i guarderies", - "pl": "Przedszkola i żłobki", - "cs": "Mateřské školky a péče o děti", - "es": "Jardines de infancia y guarderías" - }, - "description": "Shows kindergartens and preschools. Both are grouped in one layer, as they are regularly confused with each other", - "source": { - "osmTags": { - "or": [ - "amenity=childcare", - "amenity=kindergarten", - "isced:level:2011=early_childhood" - ] - } - }, - "minzoom": 12, - "title": { - "mappings": [ - { - "if": "amenity=kindergarten", - "then": { - "en": "Kindergarten {name}", - "nl": "Kleuterschool {name}", - "de": "Kindergarten {name}", - "pl": "Przedszkole {name}", - "cs": "Mateřská škola {name}", - "es": "Jardín de infancia {name}" - } - }, - { - "if": "amenity=childcare", - "then": { - "en": "Childcare {name}", - "nl": "Kinderopvang {name}", - "de": "Kinderkrippe {name}", - "pl": "Żłobek {name}", - "cs": "Péče o děti {name}", - "es": "Guardería {name}" - } - } - ] - }, - "pointRendering": [ - { - "location": [ - "point", - "centroid" - ], - "label": { - "mappings": [ - { - "if": "name~*", - "then": "
{name}
" - } - ] - }, - "marker": [ - { - "icon": "circle", - "color": "white" - }, - { - "icon": { - "mappings": [ - { - "if": "amenity=kindergarten", - "then": "./assets/layers/kindergarten_childcare/kindergarten.svg" - }, - { - "if": "amenity=childcare", - "then": "./assets/layers/kindergarten_childcare/childcare.svg" - } - ] - } - } - ] - } - ], - "lineRendering": [ - { - "color": "#62fc6c", - "width": 1 - } - ], - "presets": [ - { - "title": { - "en": "a kindergarten", - "nl": "een kleuterschool", - "de": "einen Kindergarten", - "ru": "детский сад", - "ca": "una llar d'infants", - "pl": "przedszkole", - "cs": "mateřská školka", - "es": "un jardín de infancia" - }, - "description": "A kindergarten (also known as preschool) is a school where small kids receive early education.", - "tags": [ - "amenity=kindergarten", - "isced:level=0", - "isced:2011:level=early_childhood" - ] - }, - { - "title": { - "en": "a childcare", - "nl": "een kinderopvang", - "de": "eine Kinderkrippe", - "ca": "una guarderia", - "pl": "żłobek", - "cs": "péče o děti", - "es": "una guardería" - }, - "description": "A childcare (also known as a nursery or daycare) is a facility which looks after small kids, but does not offer them an education program.", - "tags": [ - "amenity=kindergarten" - ] - } - ], - "tagRenderings": [ - { - "id": "childcare-type", - "question": { - "en": "What type of facility is this?", - "nl": "Wat voor faciliteit is dit?", - "de": "Um welche Art von Einrichtung handelt es sich?", - "pl": "Jaki to rodzaj placówki?", - "ca": "Quin tipus d'instal·lació és aquesta?", - "cs": "O jaký typ zařízení se jedná?", - "es": "¿Qué tipo de centro es este?" - }, - "mappings": [ - { - "if": "amenity=kindergarten", - "then": { - "en": "This is a kindergarten (also known as preschool) where small kids receive early education.", - "nl": "Dit is een kleuterschool waar kindjes (voorbereidend) onderwijs krijgen.", - "de": "Dies ist ein Kindergarten (auch bekannt als Vorschule), in dem kleine Kinder eine Früherziehung erhalten.", - "ca": "Aquesta és una llar d'infants (també coneguda com a preescolar) on els nens petits reben educació primerenca.", - "pl": "To jest przedszkole, gdzie małe dzieci otrzymują wczesną edukację.", - "cs": "Jedná se o mateřskou školu (známou také jako předškolní zařízení), kde se malým dětem dostává raného vzdělání.", - "es": "Se trata de un jardín de infancia (también conocido como preescolar) donde los niños pequeños reciben educación temprana." - }, - "addExtraTags": [ - "isced:level=0", - "isced:2011:level=early_childhood" - ] - }, - { - "if": "amenity=childcare", - "then": { - "en": "This is a childcare facility, such as a nursery or daycare where small kids are looked after. They do not offer an education and are ofter run as private businesses", - "nl": "Dit is een kinderopvang (ook een creche of onthaalmoeder genoemd) waar er voor kleine kinderen gezorgd wordt. Onderwijs is niet de hoofdfocus.", - "de": "Dies ist eine Kinderbetreuungseinrichtung, z. B. ein Kinderkrippe oder eine Tagesmutter, in der Kleinkinder betreut werden. Sie bieten keine Ausbildung an und werden oft als Privatunternehmen geführt", - "cs": "Jedná se o zařízení péče o děti, jako jsou jesle nebo školka, kde se starají o malé děti. Neposkytují vzdělání a jsou často provozovány jako soukromé podniky", - "es": "Se trata de un centro de cuidado infantil, como una guardería o una sala de juegos donde se cuida a los niños pequeños. No ofrecen educación y suelen ser empresas privadas" - }, - "addExtraTags": [ - "isced:level=", - "isced:2011:level=" - ] - } - ] - }, - { - "id": "name", - "question": { - "en": "What is the name of this facility?", - "de": "Wie lautet der Name dieser Einrichtung?", - "nl": "Wat is de naam van deze faciliteit?", - "ca": "Com s'anomena aquesta instal·lació?", - "pl": "Jaka jest nazwa tej placówki?", - "cs": "Jak se toto zařízení jmenuje?", - "es": "¿Cuál es el nombre de este centro?" - }, - "render": { - "en": "This facility is named {name}", - "de": "Diese Einrichtung hat den Namen {name}", - "ca": "Aquesta instal·lació s'anomena {name}", - "nl": "Deze faciliteit heet {name}", - "pl": "Ta placówka nazywa się {name}", - "cs": "Toto zařízení se jmenuje {name}", - "es": "Este centro se llama {name}" - }, - "freeform": { - "key": "name" - } - }, - "website", - "email", - "phone", - { - "builtin": "opening_hours", - "override": { - "question": { - "en": "When is this childcare opened?", - "nl": "Wanneer is deze kinderopvang geopend?", - "de": "Wann ist diese Kinderbetreuung geöffnet?", - "pl": "W jakich godzinach ten żłobek jest otwarty?", - "cs": "Kdy je tato péče o děti otevřena?", - "es": "¿Cuándo abre esta guardería?" - }, - "condition": "amenity=childcare" - } - }, - { - "id": "capacity", - "question": { - "en": "How much kids (at most) can be enrolled here?", - "nl": "Hoeveel kinderen kunnen hier terecht?", - "de": "Wie viele Kinder können hier maximal angemeldet werden?", - "ca": "Quants nens (com a màxim) es poden inscriure aquí?", - "pl": "Jak wiele dzieci (maksymalnie) może być tutaj zapisanych?", - "cs": "Kolik dětí (maximálně) zde může být zapsáno?", - "es": "¿Cuántos niños (como máximo) pueden inscribirse aquí?" - }, - "render": { - "en": "This facility has room for {capacity} kids", - "nl": "Hier kunnen {capacity} kinderen terecht", - "de": "Diese Einrichtung bietet Platz für {capacity} Kinder", - "ca": "Aquesta instal·lació té espai per a {capacity} nens", - "pl": "Ta placówka ma miejsce na {capacity} dzieci", - "cs": "Toto zařízení má prostor pro {capacity} dětí", - "es": "Este centro tiene capacidad para {capacity} niños" - }, - "freeform": { - "key": "capacity", - "type": "pnat" - } - } - ], - "deletion": true, - "allowMove": { - "enableRelocation": true, - "enableImproveAccuracy": true - } -} diff --git a/assets/layers/kindergarten_childcare/license_info.json b/assets/layers/kindergarten_childcare/license_info.json deleted file mode 100644 index 9da793cbef..0000000000 --- a/assets/layers/kindergarten_childcare/license_info.json +++ /dev/null @@ -1,25 +0,0 @@ -[ - { - "path": "childcare.svg", - "license": "CC-BY-4.0", - "authors": [ - "Diego Naive" - ], - "sources": [ - "https://thenounproject.com/icon/child-care-332981/" - ] - }, - { - "path": "kindergarten.svg", - "license": "CC-BY-4.0", - "authors": [ - "Diego Naive", - "VideoPlasty", - "Pietervdvn" - ], - "sources": [ - "https://thenounproject.com/icon/child-care-332981/", - "https://commons.wikimedia.org/wiki/File:Blackboard_Flat_Icon_Vector.svg" - ] - } -] \ No newline at end of file diff --git a/assets/layers/kindergarten_childcare/kindergarten.svg b/assets/layers/school/kindergarten.svg similarity index 100% rename from assets/layers/kindergarten_childcare/kindergarten.svg rename to assets/layers/school/kindergarten.svg diff --git a/assets/layers/school/kindergarten.svg.license b/assets/layers/school/kindergarten.svg.license new file mode 100644 index 0000000000..5f03c70c71 --- /dev/null +++ b/assets/layers/school/kindergarten.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Diego Naive; VideoPlasty; Pietervdvn +SPDX-License-Identifier: CC-BY-4.0 \ No newline at end of file diff --git a/assets/layers/school/license_info.json b/assets/layers/school/license_info.json index 595580ece0..32e1284963 100644 --- a/assets/layers/school/license_info.json +++ b/assets/layers/school/license_info.json @@ -9,6 +9,19 @@ "https://labs.mapbox.com/maki-icons/" ] }, + { + "path": "kindergarten.svg", + "license": "CC-BY-4.0", + "authors": [ + "Diego Naive", + "VideoPlasty", + "Pietervdvn" + ], + "sources": [ + "https://thenounproject.com/icon/child-care-332981/", + "https://commons.wikimedia.org/wiki/File:Blackboard_Flat_Icon_Vector.svg" + ] + }, { "path": "school.svg", "license": "CC0-1.0", @@ -19,5 +32,29 @@ "https://github.com/ideditor/temaki", "https://ideditor.github.io/temaki/docs/" ] + }, + { + "path": "school_academic.svg", + "license": "CC0-1.0", + "authors": [ + "Temaki", + "Pietervdvn" + ], + "sources": [ + "https://github.com/ideditor/temaki", + "https://ideditor.github.io/temaki/docs/" + ] + }, + { + "path": "school_primary.svg", + "license": "CC0-1.0", + "authors": [ + "Temaki", + "Pietervdvn" + ], + "sources": [ + "https://github.com/ideditor/temaki", + "https://ideditor.github.io/temaki/docs/" + ] } ] \ No newline at end of file diff --git a/assets/layers/school/school.json b/assets/layers/school/school.json index 7b83fb6a77..c461de29e7 100644 --- a/assets/layers/school/school.json +++ b/assets/layers/school/school.json @@ -11,7 +11,12 @@ }, "description": "Schools giving primary and secondary education and post-secondary, non-tertiary education. Note that this level of education does not imply an age of the pupiles", "source": { - "osmTags": "amenity=school" + "osmTags": { + "or": [ + "amenity=school", + "amenity=kindergarten" + ] + } }, "calculatedTags": [ "_enclosing=enclosingFeatures(feat)('school').map(f => f.feat.properties.id)", @@ -58,7 +63,32 @@ "color": "white" }, { - "icon": "./assets/layers/school/school.svg" + "icon": { + "render": "./assets/layers/school/school.svg", + "mappings": [ + { + "if": { + "or": [ + "amenity=kindergarten", + "school=kindergarten" + ] + }, + "then": "./assets/layers/school/kindergarten.svg" + }, + { + "if": "school:orientation=academic", + "then": "./assets/layers/school/school_academic.svg" + }, + { + "if": "school~.*secondary.*", + "then": "./assets/layers/school/school.svg" + }, + { + "if": "school~.*primary.*", + "then": "./assets/layers/school/school_primary.svg" + } + ] + } } ] } @@ -70,6 +100,25 @@ } ], "presets": [ + { + "title": { + "en": "a primary or secondary school", + "nl": "een lagere of middelbare school", + "de": "eine Grundschule oder weiterführende Schule", + "ru": "детский сад", + "ca": "una escola de primària o secundària", + "pl": "przedszkole", + "cs": "základní nebo střední škola", + "es": "una escuela primaria o secundaria", + "fr": "une école primaire ou secondaire" + }, + "description": "A kindergarten (also known as preschool) is a school where small kids receive early education.", + "tags": [ + "amenity=kindergarten", + "school=kindergarten", + "isced:level=0" + ] + }, { "tags": [ "amenity=school", @@ -156,10 +205,10 @@ "cs": "Jaký stupeň vzdělání se na této škole poskytuje?", "es": "¿Qué nivel de educación se imparte en esta escuela?" }, - "filter": true, "mappings": [ { "if": "school=kindergarten", + "alsoShowIf": "amenity=kindergarten", "then": { "en": "This is a school with a kindergarten section where young kids receive some education which prepares reading and writing.", "nl": "Dit is een school die ook een kleuterschool bevat", @@ -172,6 +221,9 @@ }, { "if": "school=primary", + "addExtraTags": [ + "amenity=school" + ], "then": { "en": "This is a school where one learns primary skills such as basic literacy and numerical skills.
Pupils typically enroll from 6 years old till 12 years old
", "nl": "Dit is een lagere school", @@ -184,6 +236,9 @@ }, { "if": "school=secondary", + "addExtraTags": [ + "amenity=school" + ], "then": { "en": "This is a secondary school which offers all grades", "nl": "Dit is een middelbare school die alle schooljaren aanbiedt (dus van het eerste tot en met het zesde middelbaar)", @@ -196,6 +251,9 @@ }, { "if": "school=lower_secondary", + "addExtraTags": [ + "amenity=school" + ], "then": { "en": "This is a secondary school which does not have all grades, but offers first and second grade", "nl": "Dit is een middelbare school die niet alle schooljaren aanbiedt, maar wel het eerste en tweede middelbaar", @@ -208,6 +266,9 @@ }, { "if": "school=middle_secondary", + "addExtraTags": [ + "amenity=school" + ], "then": { "en": "This is a secondary school which does not have all grades, but offers third and fourth grade", "nl": "Dit is een middelbare school die niet alle schooljaren aanbiedt, maar wel het derde en vierde middelbaar", @@ -220,6 +281,9 @@ }, { "if": "school=upper_secondary", + "addExtraTags": [ + "amenity=school" + ], "then": { "en": "This is a secondary school which does not have all grades, but offers fifth and sixth grade", "nl": "Dit is een middelbare school die niet alle schooljaren aanbiedt, maar wel het vijfde en zesde middelbaar", @@ -232,6 +296,9 @@ }, { "if": "school=post_secondary", + "addExtraTags": [ + "amenity=school" + ], "then": { "en": "This school offers post-secondary education (e.g. a seventh or eight specialisation year)", "nl": "Deze school biedt post-secundair onderwijs (bijvoorbeeld specialisatiejaren)", @@ -245,6 +312,43 @@ ], "multiAnswer": true }, + { + "id": "orientation_belgium", + "question": { + "en": "What does this school train pupils for?", + "nl": "Waarop wordt een leerling voorbereid?" + }, + "condition": { + "or": [ + "school~i~(.+;)?upper_secondary(;.+)?", + "school~i~(.+;)?secondary(;.+)?" + ] + }, + "multiAnswer": true, + "mappings": [ + { + "if": "school:orientation=academic", + "then": { + "en": "Prepares for an academic study at university", + "nl": "Doorstroomfinaliteit: een opleiding hier bereidt voor op universitaire studies" + } + }, + { + "if": "school:orientation=professional", + "then": { + "en": "Prepares for a professional study at a college", + "nl": "Dubbele finaliteit: een opleiding hier bereidt voor op verdere studie (bv. aan een hogeschool of secundair-na-secundair) of op de arbeidsmarkt" + } + }, + { + "if": "school:orientation=vocational", + "then": { + "en": "Prepares for a job", + "nl": "Arbeidsfinaliteit: een opleiding hier bereidt voor op de arbeidsmarkt" + } + } + ] + }, { "id": "gender", "question": { @@ -410,7 +514,6 @@ }, { "id": "target-audience", - "condition": "school:for~*", "question": { "en": "Does this school target students with a special need? Which structural facilities does this school have?", "nl": "Richt deze school zich op leerlingen met een speciale zorgbehoefte? Welke structurele faciliteiten heeft deze school voor leerlingen met een extra zorgbehoefte?", @@ -435,21 +538,9 @@ "inline": true }, "mappings": [ - { - "if": "school:for=", - "then": { - "en": "This is a school where students study skills at their age-adequate level.
There are little or no special facilities to cater for students with special needs or facilities are ad-hoc
", - "nl": "Deze school richt zich op studenten zonder extra zorgbehoefte.
Il y a peu ou pas d'adaptations spéciales pour aider les étudiants ayant des besoins particuliers, ou les installations sont ad-hoc
Remark: isn't it contradictory to say there's no special facility + the facilities are ad-hoc?", - "ca": "Aquesta és una escola on els estudiants estudien habilitats al nivell adequat per a la seva edat.
Hi ha poques o cap instal·lació especial per atendre els estudiants amb necessitats especials o les instal·lacions són ad-hoc
", - "cs": "Jedná se o školu, kde studenti studují dovednosti na úrovni odpovídající jejich věku.
Existuje málo nebo žádná speciální zařízení pro studenty se speciálními potřebami nebo zařízení jsou ad hoc
", - "es": "Esta es una escuela donde los alumnos estudian habilidades en su nivel adecuado para su edad.
Hay pocas o ninguna instalación especial para atender a alumnos con necesidades especiales o las instalaciones son improvisadas
" - }, - "hideInAnswer": true - }, { "if": "school:for=mainstream", + "alsoShowIf": "school:for=", "then": { "en": "This is a school for students without special needs
This includes students who can follow the courses with small, ad hoc measurements
", "nl": "Deze school richt zich op studenten zonder extra zorgbehoefte
Esto incluye alumnos que pueden seguir los cursos con pequeñas medidas improvisadas
" } }, - { - "if": "school:for=adults", - "then": { - "en": "This is a school where adults are taught skills on the level as specified.", - "nl": "Deze school richt zich op volwassenen", - "de": "Dies ist eine Schule, in der Erwachsene auf dem angegebenen Niveau unterrichtet werden.", - "fr": "C'est un établissement où des adultes sont formés au niveau mentionné.", - "ca": "Aquesta és una escola on els adults reben competències al nivell especificat.", - "cs": "Jedná se o školu, kde se dospělí učí dovednostem na stanovené úrovni.", - "es": "Esta es una escuela donde se enseñan habilidades a adultos en el nivel especificado." - } - }, - { - "if": "school:for=autism", - "then": { - "en": "This is a school for students with autism", - "nl": "Deze school richt zich op studenten in het autisme-spectrum", - "de": "Dies ist eine Schule für Schüler mit Autismus", - "fr": "C'est un établissement scolaire pour les étudiants ayant un trouble du spectre de l’autisme (TSA)", - "ca": "Aquesta és una escola per a estudiants amb autisme", - "pl": "To jest szkoła dla uczniów z autyzmem", - "cs": "Jedná se o školu pro studenty s autismem", - "es": "Esta es una escuela para alumnos con autismo" - } - }, { "if": "school:for=learning_disabilities", "then": { @@ -606,7 +672,97 @@ } ], "filter": [ - "pedagogy" + "pedagogy", + { + "id": "level", + "options": [ + { + "question": { + "en": "All levels of education" + } + }, + { + "question": { + "en": "Has a kindergarten", + "nl": "Kleuterafdeling" + }, + "osmTags": { + "or": [ + "school~i~(.+;)?kindergarten(;.+)?", + "amenity=kindergarten" + ] + } + }, + { + "question": { + "en": "Primary school", + "nl": "Lagere school" + }, + "osmTags": "school~i~(.+;)?primary(;.+)?" + }, + { + "question": { + "en": "Secondary school with all grades", + "nl": "Secundaire school met alle graden" + }, + "osmTags": "school~i~(.+;)?secondary(;.+)?" + }, + { + "question": { + "en": "Has first grade", + "nl": "Heeft eerste graad" + }, + "osmTags": { + "or": [ + "school~i~(.+;)?lower_secondary(;.+)?", + "school~i~(.+;)?secondary(;.+)?" + ] + } + }, + { + "question": { + "en": "Has second grade", + "nl": "Heeft tweede graad" + }, + "osmTags": { + "or": [ + "school~i~(.+;)?middle_secondary(;.+)?", + "school~i~(.+;)?secondary(;.+)?" + ] + } + }, + { + "question": { + "en": "Has third grade", + "nl": "Heeft derde graad" + }, + "osmTags": { + "or": [ + "school~i~(.+;)?upper_secondary(;.+)?", + "school~i~(.+;)?secondary(;.+)?" + ] + } + }, + { + "question": { + "en": "This school offers post-secondary education (e.g. a seventh or eight specialisation year)", + "nl": "Heeft specialisatiejaar" + }, + "osmTags": "school~i~.+;)?post_secondary(;.+" + }, + { + "question": { + "en": "Unknown school level" + }, + "osmTags": { + "and": [ + "school=", + "amenity!=kindergarten" + ] + } + } + ] + } ], "allowMove": { "enableImproveAccuracy": true, diff --git a/assets/layers/school/school_academic.svg b/assets/layers/school/school_academic.svg new file mode 100644 index 0000000000..83bf866dfd --- /dev/null +++ b/assets/layers/school/school_academic.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + diff --git a/assets/layers/school/school_academic.svg.license b/assets/layers/school/school_academic.svg.license new file mode 100644 index 0000000000..1456fdbd68 --- /dev/null +++ b/assets/layers/school/school_academic.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Temaki; Pietervdvn +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/layers/school/school_primary.svg b/assets/layers/school/school_primary.svg new file mode 100644 index 0000000000..5e712ffdc6 --- /dev/null +++ b/assets/layers/school/school_primary.svg @@ -0,0 +1,153 @@ + + diff --git a/assets/layers/school/school_primary.svg.license b/assets/layers/school/school_primary.svg.license new file mode 100644 index 0000000000..1456fdbd68 --- /dev/null +++ b/assets/layers/school/school_primary.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Temaki; Pietervdvn +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/themes/education/education.json b/assets/themes/education/education.json index 6a03421b9f..5573a045ca 100644 --- a/assets/themes/education/education.json +++ b/assets/themes/education/education.json @@ -36,9 +36,8 @@ }, "icon": "./assets/layers/school/college.svg", "layers": [ - "tertiary_education", + "childcare", "school", - "kindergarten_childcare" - ], - "#layers:note": "kindergarten_childcare must be _below_ school, as it can 'catch' primary schools which do have an integrated preschool" -} \ No newline at end of file + "tertiary_education" + ] +} diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 00e8634299..771d597f2e 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -2150,6 +2150,24 @@ } } }, + "childcare": { + "name": "Guarderies d'infants", + "presets": { + "0": { + "title": "una guarderia" + } + }, + "tagRenderings": { + "capacity": { + "question": "Quants nens (com a màxim) es poden inscriure aquí?", + "render": "Aquesta instal·lació té espai per a {capacity} nens" + }, + "name": { + "question": "Com s'anomena aquesta instal·lació?", + "render": "Aquesta instal·lació s'anomena {name}" + } + } + }, "climbing": { "description": "Una capa fictícia que conté renderització d'etiquetes compartides entre les capes d'escalada", "tagRenderings": { @@ -4865,35 +4883,6 @@ "render": "Vorada" } }, - "kindergarten_childcare": { - "name": "Llars d'infants i guarderies", - "presets": { - "0": { - "title": "una llar d'infants" - }, - "1": { - "title": "una guarderia" - } - }, - "tagRenderings": { - "capacity": { - "question": "Quants nens (com a màxim) es poden inscriure aquí?", - "render": "Aquesta instal·lació té espai per a {capacity} nens" - }, - "childcare-type": { - "mappings": { - "0": { - "then": "Aquesta és una llar d'infants (també coneguda com a preescolar) on els nens petits reben educació primerenca." - } - }, - "question": "Quin tipus d'instal·lació és aquesta?" - }, - "name": { - "question": "Com s'anomena aquesta instal·lació?", - "render": "Aquesta instal·lació s'anomena {name}" - } - } - }, "last_click": { "pointRendering": { "0": { @@ -6976,6 +6965,9 @@ "presets": { "0": { "title": "una escola de primària o secundària" + }, + "1": { + "title": "una escola de primària o secundària" } }, "tagRenderings": { @@ -7043,30 +7035,21 @@ "target-audience": { "mappings": { "0": { - "then": "Aquesta és una escola on els estudiants estudien habilitats al nivell adequat per a la seva edat.
Hi ha poques o cap instal·lació especial per atendre els estudiants amb necessitats especials o les instal·lacions són ad-hoc
" - }, - "1": { "then": "Aquesta és una escola per a estudiants sense necessitats especials
Açò inclou alumnes que poden seguir les classes amb petites mesures
" }, - "2": { - "then": "Aquesta és una escola on els adults reben competències al nivell especificat." - }, - "3": { - "then": "Aquesta és una escola per a estudiants amb autisme" - }, - "4": { + "1": { "then": "Aquesta és una escola per a estudiants amb dificultats de l'aprenentatge" }, - "5": { + "2": { "then": "Aquesta és una escola per a estudiants cecs o estudiants amb deficiències visuals" }, - "6": { + "3": { "then": "Aquesta és una escola per a estudiants sords o amb dificultats auditives" }, - "7": { + "4": { "then": "Aquesta és una escola per a estudiants amb discapacitats" }, - "8": { + "5": { "then": "Aquesta és una escola per a estudiants amb necessitats especials" } }, diff --git a/langs/layers/cs.json b/langs/layers/cs.json index a08e559d7b..915c4e972e 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -2588,6 +2588,35 @@ } } }, + "childcare": { + "presets": { + "0": { + "title": "péče o děti" + } + }, + "tagRenderings": { + "capacity": { + "question": "Kolik dětí (maximálně) zde může být zapsáno?", + "render": "Toto zařízení má prostor pro {capacity} dětí" + }, + "name": { + "question": "Jak se toto zařízení jmenuje?", + "render": "Toto zařízení se jmenuje {name}" + }, + "opening_hours": { + "override": { + "question": "Kdy je tato péče o děti otevřena?" + } + } + }, + "title": { + "mappings": { + "0": { + "then": "Péče o děti {name}" + } + } + } + }, "cinema": { "name": "Kino", "tagRenderings": { @@ -5750,53 +5779,6 @@ "render": "Obrubník" } }, - "kindergarten_childcare": { - "name": "Mateřské školky a péče o děti", - "presets": { - "0": { - "title": "mateřská školka" - }, - "1": { - "title": "péče o děti" - } - }, - "tagRenderings": { - "capacity": { - "question": "Kolik dětí (maximálně) zde může být zapsáno?", - "render": "Toto zařízení má prostor pro {capacity} dětí" - }, - "childcare-type": { - "mappings": { - "0": { - "then": "Jedná se o mateřskou školu (známou také jako předškolní zařízení), kde se malým dětem dostává raného vzdělání." - }, - "1": { - "then": "Jedná se o zařízení péče o děti, jako jsou jesle nebo školka, kde se starají o malé děti. Neposkytují vzdělání a jsou často provozovány jako soukromé podniky" - } - }, - "question": "O jaký typ zařízení se jedná?" - }, - "name": { - "question": "Jak se toto zařízení jmenuje?", - "render": "Toto zařízení se jmenuje {name}" - }, - "opening_hours": { - "override": { - "question": "Kdy je tato péče o děti otevřena?" - } - } - }, - "title": { - "mappings": { - "0": { - "then": "Mateřská škola {name}" - }, - "1": { - "then": "Péče o děti {name}" - } - } - } - }, "last_click": { "pointRendering": { "0": { @@ -7965,6 +7947,9 @@ "presets": { "0": { "title": "základní nebo střední škola" + }, + "1": { + "title": "základní nebo střední škola" } }, "tagRenderings": { @@ -8065,30 +8050,21 @@ "target-audience": { "mappings": { "0": { - "then": "Jedná se o školu, kde studenti studují dovednosti na úrovni odpovídající jejich věku.
Existuje málo nebo žádná speciální zařízení pro studenty se speciálními potřebami nebo zařízení jsou ad hoc
" - }, - "1": { "then": "Toto je škola pro studenty bez speciálních potřeb
To zahrnuje studenty, kteří mohou absolvovat kurzy s malými, ad hoc měřeními
" }, - "2": { - "then": "Jedná se o školu, kde se dospělí učí dovednostem na stanovené úrovni." - }, - "3": { - "then": "Jedná se o školu pro studenty s autismem" - }, - "4": { + "1": { "then": "Jedná se o školu pro žáky s poruchami učení" }, - "5": { + "2": { "then": "Jedná se o školu pro nevidomé studenty nebo studenty se zrakovým postižením" }, - "6": { + "3": { "then": "Jedná se o školu pro neslyšící studenty nebo studenty se sluchovým postižením" }, - "7": { + "4": { "then": "Jedná se o školu pro studenty se zdravotním postižením" }, - "8": { + "5": { "then": "Jedná se o školu pro žáky se speciálními potřebami" } }, diff --git a/langs/layers/de.json b/langs/layers/de.json index fc5d6a000f..f829711d44 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -2874,6 +2874,36 @@ "render": "Ladestation" } }, + "childcare": { + "name": "Kinderkrippen", + "presets": { + "0": { + "title": "eine Kinderkrippe" + } + }, + "tagRenderings": { + "capacity": { + "question": "Wie viele Kinder können hier maximal angemeldet werden?", + "render": "Diese Einrichtung bietet Platz für {capacity} Kinder" + }, + "name": { + "question": "Wie lautet der Name dieser Einrichtung?", + "render": "Diese Einrichtung hat den Namen {name}" + }, + "opening_hours": { + "override": { + "question": "Wann ist diese Kinderbetreuung geöffnet?" + } + } + }, + "title": { + "mappings": { + "0": { + "then": "Kinderkrippe {name}" + } + } + } + }, "cinema": { "description": "Ein Ort, an dem Filme gezeigt werden, die der Öffentlichkeit gegen Gebühr zugänglich sind.", "name": "Kino", @@ -6573,53 +6603,6 @@ "render": "Bordstein" } }, - "kindergarten_childcare": { - "name": "Kindergärten und Kinderkrippen", - "presets": { - "0": { - "title": "einen Kindergarten" - }, - "1": { - "title": "eine Kinderkrippe" - } - }, - "tagRenderings": { - "capacity": { - "question": "Wie viele Kinder können hier maximal angemeldet werden?", - "render": "Diese Einrichtung bietet Platz für {capacity} Kinder" - }, - "childcare-type": { - "mappings": { - "0": { - "then": "Dies ist ein Kindergarten (auch bekannt als Vorschule), in dem kleine Kinder eine Früherziehung erhalten." - }, - "1": { - "then": "Dies ist eine Kinderbetreuungseinrichtung, z. B. ein Kinderkrippe oder eine Tagesmutter, in der Kleinkinder betreut werden. Sie bieten keine Ausbildung an und werden oft als Privatunternehmen geführt" - } - }, - "question": "Um welche Art von Einrichtung handelt es sich?" - }, - "name": { - "question": "Wie lautet der Name dieser Einrichtung?", - "render": "Diese Einrichtung hat den Namen {name}" - }, - "opening_hours": { - "override": { - "question": "Wann ist diese Kinderbetreuung geöffnet?" - } - } - }, - "title": { - "mappings": { - "0": { - "then": "Kindergarten {name}" - }, - "1": { - "then": "Kinderkrippe {name}" - } - } - } - }, "last_click": { "pointRendering": { "0": { @@ -9456,6 +9439,9 @@ "presets": { "0": { "title": "eine Grundschule oder weiterführende Schule" + }, + "1": { + "title": "eine Grundschule oder weiterführende Schule" } }, "tagRenderings": { @@ -9556,30 +9542,21 @@ "target-audience": { "mappings": { "0": { - "then": "Es handelt sich um eine Schule, in der die Schüler Fähigkeiten auf ihrem altersgemäßen Niveau erlernen.
Es gibt wenig oder keine speziellen Einrichtungen für Schüler mit besonderen Bedürfnissen oder die Einrichtungen sind ad-hoc
" - }, - "1": { "then": "Dies ist eine Schule für Schüler ohne besondere Bedürfnisse
Dazu gehören auch Schüler, die den Kursen mit kleinen Ad-hoc-Maßnahmen folgen können
" }, - "2": { - "then": "Dies ist eine Schule, in der Erwachsene auf dem angegebenen Niveau unterrichtet werden." - }, - "3": { - "then": "Dies ist eine Schule für Schüler mit Autismus" - }, - "4": { + "1": { "then": "Dies ist eine Schule für Schüler mit Lernschwierigkeiten" }, - "5": { + "2": { "then": "Dies ist eine Schule für blinde oder sehbehinderte Schüler" }, - "6": { + "3": { "then": "Dies ist eine Schule für gehörlose oder hörgeschädigte Schüler" }, - "7": { + "4": { "then": "Dies ist eine Schule für Schüler mit Behinderungen" }, - "8": { + "5": { "then": "Dies ist eine Schule für Schüler mit besonderen Bedürfnissen" } }, diff --git a/langs/layers/en.json b/langs/layers/en.json index ac91e90a73..1e7167df1e 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -2874,6 +2874,36 @@ "render": "Charging station" } }, + "childcare": { + "name": "Childcare", + "presets": { + "0": { + "title": "a childcare" + } + }, + "tagRenderings": { + "capacity": { + "question": "How much kids (at most) can be enrolled here?", + "render": "This facility has room for {capacity} kids" + }, + "name": { + "question": "What is the name of this facility?", + "render": "This facility is named {name}" + }, + "opening_hours": { + "override": { + "question": "When is this childcare opened?" + } + } + }, + "title": { + "mappings": { + "0": { + "then": "Childcare {name}" + } + } + } + }, "cinema": { "description": "A place showing movies (films), generally open to the public for a fee. Commonly referred to as a movie theater in the US.", "name": "Cinema", @@ -6604,53 +6634,6 @@ "render": "Kerb" } }, - "kindergarten_childcare": { - "name": "Kindergartens and childcare", - "presets": { - "0": { - "title": "a kindergarten" - }, - "1": { - "title": "a childcare" - } - }, - "tagRenderings": { - "capacity": { - "question": "How much kids (at most) can be enrolled here?", - "render": "This facility has room for {capacity} kids" - }, - "childcare-type": { - "mappings": { - "0": { - "then": "This is a kindergarten (also known as preschool) where small kids receive early education." - }, - "1": { - "then": "This is a childcare facility, such as a nursery or daycare where small kids are looked after. They do not offer an education and are ofter run as private businesses" - } - }, - "question": "What type of facility is this?" - }, - "name": { - "question": "What is the name of this facility?", - "render": "This facility is named {name}" - }, - "opening_hours": { - "override": { - "question": "When is this childcare opened?" - } - } - }, - "title": { - "mappings": { - "0": { - "then": "Kindergarten {name}" - }, - "1": { - "then": "Childcare {name}" - } - } - } - }, "last_click": { "pointRendering": { "0": { @@ -9483,10 +9466,46 @@ } }, "school": { + "filter": { + "1": { + "options": { + "0": { + "question": "All levels of education" + }, + "1": { + "question": "Has a kindergarten" + }, + "2": { + "question": "Primary school" + }, + "3": { + "question": "Secondary school with all grades" + }, + "4": { + "question": "Has first grade" + }, + "5": { + "question": "Has second grade" + }, + "6": { + "question": "Has third grade" + }, + "7": { + "question": "This school offers post-secondary education (e.g. a seventh or eight specialisation year)" + }, + "8": { + "question": "Unknown school level" + } + } + } + }, "name": "Primary and secondary schools", "presets": { "0": { "title": "a primary or secondary school" + }, + "1": { + "title": "a primary or secondary school" } }, "tagRenderings": { @@ -9537,6 +9556,20 @@ }, "question": "Which genders can enroll at this school?" }, + "orientation_belgium": { + "mappings": { + "0": { + "then": "Prepares for an academic study at university" + }, + "1": { + "then": "Prepares for a professional study at a college" + }, + "2": { + "then": "Prepares for a job" + } + }, + "question": "What does this school train pupils for?" + }, "pedagogy": { "mappings": { "0": { @@ -9587,30 +9620,21 @@ "target-audience": { "mappings": { "0": { - "then": "This is a school where students study skills at their age-adequate level.
There are little or no special facilities to cater for students with special needs or facilities are ad-hoc
" - }, - "1": { "then": "This is a school for students without special needs
This includes students who can follow the courses with small, ad hoc measurements
" }, - "2": { - "then": "This is a school where adults are taught skills on the level as specified." - }, - "3": { - "then": "This is a school for students with autism" - }, - "4": { + "1": { "then": "This is a school for students with learning disabilities" }, - "5": { + "2": { "then": "This is a school for blind students or students with sight impairments" }, - "6": { + "3": { "then": "This is a school for deaf students or students with hearing impairments" }, - "7": { + "4": { "then": "This is a school for students with disabilities" }, - "8": { + "5": { "then": "This is a school for students with special needs" } }, diff --git a/langs/layers/es.json b/langs/layers/es.json index db6281c6a8..4e006e807a 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -2815,6 +2815,35 @@ "render": "Punto de carga" } }, + "childcare": { + "presets": { + "0": { + "title": "una guardería" + } + }, + "tagRenderings": { + "capacity": { + "question": "¿Cuántos niños (como máximo) pueden inscribirse aquí?", + "render": "Este centro tiene capacidad para {capacity} niños" + }, + "name": { + "question": "¿Cuál es el nombre de este centro?", + "render": "Este centro se llama {name}" + }, + "opening_hours": { + "override": { + "question": "¿Cuándo abre esta guardería?" + } + } + }, + "title": { + "mappings": { + "0": { + "then": "Guardería {name}" + } + } + } + }, "cinema": { "description": "Un lugar donde se proyectan películas (cine), generalmente abierto al público por una tarifa Comúnmente conocido como cine en Estados Unidos.", "name": "Cine", @@ -6416,53 +6445,6 @@ "render": "Bordillo" } }, - "kindergarten_childcare": { - "name": "Jardines de infancia y guarderías", - "presets": { - "0": { - "title": "un jardín de infancia" - }, - "1": { - "title": "una guardería" - } - }, - "tagRenderings": { - "capacity": { - "question": "¿Cuántos niños (como máximo) pueden inscribirse aquí?", - "render": "Este centro tiene capacidad para {capacity} niños" - }, - "childcare-type": { - "mappings": { - "0": { - "then": "Se trata de un jardín de infancia (también conocido como preescolar) donde los niños pequeños reciben educación temprana." - }, - "1": { - "then": "Se trata de un centro de cuidado infantil, como una guardería o una sala de juegos donde se cuida a los niños pequeños. No ofrecen educación y suelen ser empresas privadas" - } - }, - "question": "¿Qué tipo de centro es este?" - }, - "name": { - "question": "¿Cuál es el nombre de este centro?", - "render": "Este centro se llama {name}" - }, - "opening_hours": { - "override": { - "question": "¿Cuándo abre esta guardería?" - } - } - }, - "title": { - "mappings": { - "0": { - "then": "Jardín de infancia {name}" - }, - "1": { - "then": "Guardería {name}" - } - } - } - }, "last_click": { "pointRendering": { "0": { @@ -9269,6 +9251,9 @@ "presets": { "0": { "title": "una escuela primaria o secundaria" + }, + "1": { + "title": "una escuela primaria o secundaria" } }, "tagRenderings": { @@ -9369,30 +9354,21 @@ "target-audience": { "mappings": { "0": { - "then": "Esta es una escuela donde los alumnos estudian habilidades en su nivel adecuado para su edad.
Hay pocas o ninguna instalación especial para atender a alumnos con necesidades especiales o las instalaciones son improvisadas
" - }, - "1": { "then": "Esta es una escuela para alumnos sin necesidades especiales
Esto incluye alumnos que pueden seguir los cursos con pequeñas medidas improvisadas
" }, - "2": { - "then": "Esta es una escuela donde se enseñan habilidades a adultos en el nivel especificado." - }, - "3": { - "then": "Esta es una escuela para alumnos con autismo" - }, - "4": { + "1": { "then": "Esta es una escuela para alumnos con dificultades de aprendizaje" }, - "5": { + "2": { "then": "Esta es una escuela para alumnos ciegos o con discapacidad visual" }, - "6": { + "3": { "then": "Esta es una escuela para alumnos sordos o con discapacidad auditiva" }, - "7": { + "4": { "then": "Esta es una escuela para alumnos con discapacidad" }, - "8": { + "5": { "then": "Esta es una escuela para alumnos con necesidades especiales" } }, diff --git a/langs/layers/fr.json b/langs/layers/fr.json index 863e132847..14599fc6ff 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -5693,6 +5693,9 @@ "presets": { "0": { "title": "une école primaire ou secondaire" + }, + "1": { + "title": "une école primaire ou secondaire" } }, "tagRenderings": { @@ -5760,30 +5763,21 @@ "target-audience": { "mappings": { "0": { - "then": "C'est un établissement scolaire où les étudiants acquièrent des compétences à un niveau adapté à leur âge.
Il y a peu ou pas d'adaptations spéciales pour aider les étudiants ayant des besoins particuliers, ou les installations sont ad-hoc
Remark: isn't it contradictory to say there's no special facility + the facilities are ad-hoc?" - }, - "1": { "then": "C'est un établissement scolaire pour étudiants sans besoins particuliers.
Sont inclus les étudiants qui peuvent suivre les cours avec de petites adaptations.
" }, - "2": { - "then": "C'est un établissement où des adultes sont formés au niveau mentionné." - }, - "3": { - "then": "C'est un établissement scolaire pour les étudiants ayant un trouble du spectre de l’autisme (TSA)" - }, - "4": { + "1": { "then": "C'est un établissement scolaire pour les étudiants ayant des troubles d’apprentissage" }, - "5": { + "2": { "then": "C'est un établissement scolaire pour les étudiants aveugles ou malvoyants" }, - "6": { + "3": { "then": "C'est un établissement scolaire pour les étudiants sourds ou malentendants" }, - "7": { + "4": { "then": "C'est un établissement scolaire pour les étudiants en situation de handicap" }, - "8": { + "5": { "then": "C'est un établissement scolaire pour les étudiants ayant des besoins particuliers" } }, diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 347a5ca909..95b55731d2 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -2049,6 +2049,9 @@ }, "title": { "mappings": { + "0": { + "then": "{name}" + }, "1": { "then": "Vogelkijkhut {name}" }, @@ -2816,6 +2819,36 @@ "render": "Oplaadpunt" } }, + "childcare": { + "name": "Kinderopvang", + "presets": { + "0": { + "title": "een kinderopvang" + } + }, + "tagRenderings": { + "capacity": { + "question": "Hoeveel kinderen kunnen hier terecht?", + "render": "Hier kunnen {capacity} kinderen terecht" + }, + "name": { + "question": "Wat is de naam van deze faciliteit?", + "render": "Deze faciliteit heet {name}" + }, + "opening_hours": { + "override": { + "question": "Wanneer is deze kinderopvang geopend?" + } + } + }, + "title": { + "mappings": { + "0": { + "then": "Kinderopvang {name}" + } + } + } + }, "cinema": { "description": "Een plaats die films toont, meestal open voor het publiek tegen een vergoeding.", "name": "Bioscoop", @@ -5915,53 +5948,6 @@ "render": "Stoeprand" } }, - "kindergarten_childcare": { - "name": "Kleuterscholen en kinderopvang", - "presets": { - "0": { - "title": "een kleuterschool" - }, - "1": { - "title": "een kinderopvang" - } - }, - "tagRenderings": { - "capacity": { - "question": "Hoeveel kinderen kunnen hier terecht?", - "render": "Hier kunnen {capacity} kinderen terecht" - }, - "childcare-type": { - "mappings": { - "0": { - "then": "Dit is een kleuterschool waar kindjes (voorbereidend) onderwijs krijgen." - }, - "1": { - "then": "Dit is een kinderopvang (ook een creche of onthaalmoeder genoemd) waar er voor kleine kinderen gezorgd wordt. Onderwijs is niet de hoofdfocus." - } - }, - "question": "Wat voor faciliteit is dit?" - }, - "name": { - "question": "Wat is de naam van deze faciliteit?", - "render": "Deze faciliteit heet {name}" - }, - "opening_hours": { - "override": { - "question": "Wanneer is deze kinderopvang geopend?" - } - } - }, - "title": { - "mappings": { - "0": { - "then": "Kleuterschool {name}" - }, - "1": { - "then": "Kinderopvang {name}" - } - } - } - }, "last_click": { "pointRendering": { "0": { @@ -6373,6 +6359,11 @@ } }, "title": { + "mappings": { + "0": { + "then": "{name}" + } + }, "render": "Natuurgebied" } }, @@ -6904,6 +6895,21 @@ "render": "Picknicktafel" } }, + "play_forest": { + "description": "Een speelbos is een vrij toegankelijke zone in een bos", + "name": "Speelbossen", + "title": { + "mappings": { + "0": { + "then": "{name}" + }, + "1": { + "then": "Speelbos {name}" + } + }, + "render": "Speelbos" + } + }, "playground": { "deletion": { "nonDeleteMappings": { @@ -8082,10 +8088,40 @@ } }, "school": { + "filter": { + "1": { + "options": { + "1": { + "question": "Kleuterafdeling" + }, + "2": { + "question": "Lagere school" + }, + "3": { + "question": "Secundaire school met alle graden" + }, + "4": { + "question": "Heeft eerste graad" + }, + "5": { + "question": "Heeft tweede graad" + }, + "6": { + "question": "Heeft derde graad" + }, + "7": { + "question": "Heeft specialisatiejaar" + } + } + } + }, "name": "Lagere en middelbare scholen", "presets": { "0": { "title": "een lagere of middelbare school" + }, + "1": { + "title": "een lagere of middelbare school" } }, "tagRenderings": { @@ -8136,6 +8172,20 @@ }, "question": "Mogen jongens en meisjes les volgen op deze school?" }, + "orientation_belgium": { + "mappings": { + "0": { + "then": "Doorstroomfinaliteit: een opleiding hier bereidt voor op universitaire studies" + }, + "1": { + "then": "Dubbele finaliteit: een opleiding hier bereidt voor op verdere studie (bv. aan een hogeschool of secundair-na-secundair) of op de arbeidsmarkt" + }, + "2": { + "then": "Arbeidsfinaliteit: een opleiding hier bereidt voor op de arbeidsmarkt" + } + }, + "question": "Waarop wordt een leerling voorbereid?" + }, "school-language": { "render": { "special": { @@ -8151,30 +8201,21 @@ "target-audience": { "mappings": { "0": { - "then": "Deze school richt zich op studenten zonder extra zorgbehoefte.
Dit omvat leerlingen waarbij kleine, ad-hoc maatregelen volstaan om de lessen te volgen.
" }, - "2": { - "then": "Deze school richt zich op volwassenen" - }, - "3": { - "then": "Deze school richt zich op studenten in het autisme-spectrum" - }, - "4": { + "1": { "then": "Deze school richt zich op leerlingen met een leerprobleem" }, - "5": { + "2": { "then": "Deze school richt zich op blinde en slechtziende studenten" }, - "6": { + "3": { "then": "Deze school richt zich op dove en hardhorende studenten" }, - "7": { + "4": { "then": "Deze school richt zich op studenten met een beperking" }, - "8": { + "5": { "then": "Deze school richt zich op studenten met extra zorgbehoeften" } }, @@ -8470,6 +8511,9 @@ }, "title": { "mappings": { + "0": { + "then": "{name}" + }, "1": { "then": "Voetpad" }, @@ -10635,13 +10679,25 @@ } }, "village_green": { - "description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)" + "description": "Een laag die dorpsgroen toont (gemeenschapsgroen, maar niet echt een park)", + "name": "Speelweide", + "title": { + "mappings": { + "0": { + "then": "{name}" + } + }, + "render": "Speelweide" + } }, "visitor_information_centre": { "description": "Een bezoekerscentrum biedt informatie over een specifieke attractie of bezienswaardigheid waar het is gevestigd.", "name": "Bezoekerscentrum", "title": { "mappings": { + "0": { + "then": "{name:nl}" + }, "1": { "then": "{name}" } @@ -10871,4 +10927,4 @@ "render": "windturbine" } } -} +} \ No newline at end of file diff --git a/langs/layers/pl.json b/langs/layers/pl.json index c5671dfa7d..a9408413dd 100644 --- a/langs/layers/pl.json +++ b/langs/layers/pl.json @@ -1247,6 +1247,35 @@ } } }, + "childcare": { + "presets": { + "0": { + "title": "żłobek" + } + }, + "tagRenderings": { + "capacity": { + "question": "Jak wiele dzieci (maksymalnie) może być tutaj zapisanych?", + "render": "Ta placówka ma miejsce na {capacity} dzieci" + }, + "name": { + "question": "Jaka jest nazwa tej placówki?", + "render": "Ta placówka nazywa się {name}" + }, + "opening_hours": { + "override": { + "question": "W jakich godzinach ten żłobek jest otwarty?" + } + } + }, + "title": { + "mappings": { + "0": { + "then": "Żłobek {name}" + } + } + } + }, "climbing_area": { "tagRenderings": { "Rock type (crag/rock/cliff only)": { @@ -2377,50 +2406,6 @@ "render": "Krawężnik" } }, - "kindergarten_childcare": { - "name": "Przedszkola i żłobki", - "presets": { - "0": { - "title": "przedszkole" - }, - "1": { - "title": "żłobek" - } - }, - "tagRenderings": { - "capacity": { - "question": "Jak wiele dzieci (maksymalnie) może być tutaj zapisanych?", - "render": "Ta placówka ma miejsce na {capacity} dzieci" - }, - "childcare-type": { - "mappings": { - "0": { - "then": "To jest przedszkole, gdzie małe dzieci otrzymują wczesną edukację." - } - }, - "question": "Jaki to rodzaj placówki?" - }, - "name": { - "question": "Jaka jest nazwa tej placówki?", - "render": "Ta placówka nazywa się {name}" - }, - "opening_hours": { - "override": { - "question": "W jakich godzinach ten żłobek jest otwarty?" - } - } - }, - "title": { - "mappings": { - "0": { - "then": "Przedszkole {name}" - }, - "1": { - "then": "Żłobek {name}" - } - } - } - }, "last_click": { "pointRendering": { "0": { @@ -3466,6 +3451,11 @@ } }, "school": { + "presets": { + "0": { + "title": "przedszkole" + } + }, "tagRenderings": { "school-language": { "render": { @@ -3482,15 +3472,12 @@ "target-audience": { "mappings": { "3": { - "then": "To jest szkoła dla uczniów z autyzmem" - }, - "6": { "then": "To jest szkoła dla uczniów głuchych i słabosłyszących" }, - "7": { + "4": { "then": "To jest szkoła dla uczniów z niepełnosprawnościami" }, - "8": { + "5": { "then": "To jest szkoła dla uczniów z specjalnymi potrzebami" } } diff --git a/langs/layers/ru.json b/langs/layers/ru.json index f535b0dcb9..bf3110103f 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -1284,13 +1284,6 @@ "render": "Бордюр" } }, - "kindergarten_childcare": { - "presets": { - "0": { - "title": "детский сад" - } - } - }, "map": { "name": "Карты", "presets": { @@ -1637,6 +1630,13 @@ } } }, + "school": { + "presets": { + "0": { + "title": "детский сад" + } + } + }, "shops": { "description": "Магазин", "name": "Магазин", diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 8170fe8912..fc4637708f 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -653,8 +653,37 @@ "building type": { "question": "Wat voor soort gebouw is dit?" }, + "grb-fixme": { + "mappings": { + "0": { + "then": "Geen fixme" + } + }, + "question": "Wat zegt de fixme?", + "render": "De fixme is {fixme}" + }, + "grb-housenumber": { + "mappings": { + "0": { + "then": "Geen huisnummer" + } + }, + "question": "Wat is het huisnummer?", + "render": "Het huisnummer is {addr:housenumber}" + }, + "grb-min-level": { + "question": "Hoeveel verdiepingen ontbreken?", + "render": "Dit gebouw begint maar op de {building:min_level} verdieping" + }, "grb-reference": { "render": "Werd geïmporteerd vanuit GRB, het referentienummer is {source:geometry:ref}" + }, + "grb-street": { + "question": "Wat is de straat?", + "render": "De straat is {addr:street}" + }, + "grb-unit": { + "render": "De wooneenheid-aanduiding is {addr:unit} " } } }, @@ -671,8 +700,35 @@ } } } + }, + "5": { + "override": { + "tagRenderings+": { + "0": { + "mappings": { + "0": { + "then": "Geen omliggend OSM-gebouw gevonden" + } + } + }, + "3": { + "mappings": { + "0": { + "then": "Geen omliggend OSM-gebouw gevonden. Een omliggend gebouw is nodig om dit punt als adres punt toe te voegen.
Importeer eerst de gebouwen. Vernieuw dan de pagina om losse adressen toe te voegen
" + } + }, + "render": { + "special": { + "text": "Voeg dit adres als een nieuw adrespunt toe" + } + } + } + } + } } - } + }, + "shortDescription": "Grb import helper tool", + "title": "GRB import helper" }, "guideposts": { "description": "Wegwijzers (ook wel handwijzer genoemd) zijn vaak te vinden langs officiële wandel-, fiets-, ski- of paardrijroutes om de richtingen naar verschillende bestemmingen aan te geven. Vaak zijn ze vernoemd naar een regio of plaats en geven ze de hoogte aan.\n\nDe positie van een wegwijzer kan door een wandelaar/fietser/renner/skiër worden gebruikt als bevestiging van de huidige positie, vooral als ze een gedrukte kaart zonder GPS-ontvanger gebruiken. ", @@ -1100,6 +1156,11 @@ }, "title": "Dierenartsen, hondenloopzones en andere huisdiervriendelijke plaatsen" }, + "play_forests": { + "description": "Een speelbos is een zone in een bos die vrij toegankelijk is voor spelende kinderen. Deze wordt in bossen van het Agentschap Natuur en bos altijd aangeduid met het overeenkomstige bord.", + "shortDescription": "Deze kaart toont speelbossen", + "title": "Speelbossen" + }, "playgrounds": { "description": "Op deze kaart vind je speeltuinen en kan je zelf meer informatie en foto's toevoegen", "shortDescription": "Een kaart met speeltuinen", @@ -1173,6 +1234,47 @@ "description": "Alles om te skiën", "title": "Skipistes en kabelbanen" }, + "speelplekken": { + "description": "

Welkom bij de Groendoener!

De Zuidrand dat is spelen, ravotten, chillen, wandelen,… in het groen. Meer dan 200 grote en kleine speelplekken liggen er in parken, in bossen en op pleintjes te wachten om ontdekt te worden. De verschillende speelplekken werden getest én goedgekeurd door kinder- en jongerenreporters uit de Zuidrand. Met leuke challenges dagen de reporters jou uit om ook op ontdekking te gaan. Klik op een speelplek op de kaart, bekijk het filmpje en ga op verkenning!

Het project groendoener kadert binnen het strategisch project Beleefbare Open Ruimte in de Antwerpse Zuidrand en is een samenwerking tussen het departement Leefmilieu van provincie Antwerpen, Sportpret vzw, een OpenStreetMap-België Consultent en Createlli vzw. Het project kwam tot stand met steun van Departement Omgeving van de Vlaamse Overheid.
", + "layers": { + "6": { + "name": "Wandelroutes van provincie Antwerpen", + "tagRenderings": { + "walk-description": { + "render": "

Korte beschrijving:

{description}" + }, + "walk-length": { + "render": "Deze wandeling is {_length:km}km lang" + }, + "walk-operator": { + "question": "Wie beheert deze wandeling en plaatst dus de signalisatiebordjes?" + }, + "walk-operator-email": { + "question": "Naar wie kan men emailen bij problemen rond signalisatie?", + "render": "Bij problemen met signalisatie kan men emailen naar {operator:email}" + }, + "walk-type": { + "mappings": { + "0": { + "then": "Dit is een internationale wandelroute" + }, + "1": { + "then": "Dit is een nationale wandelroute" + }, + "2": { + "then": "Dit is een regionale wandelroute" + }, + "3": { + "then": "Dit is een lokale wandelroute" + } + } + } + } + } + }, + "shortDescription": "Speelplekken in de Antwerpse Zuidrand", + "title": "Welkom bij de groendoener!" + }, "sport_pitches": { "description": "Een sportveld is een ingerichte plaats met infrastructuur om een sport te beoefenen", "shortDescription": "Deze kaart toont sportvelden", @@ -1293,6 +1395,10 @@ }, "title": "Straatverlichting" }, + "street_lighting_assen": { + "description": "Op deze kaart vind je alles over straatlantaarns + een dataset van Assen", + "title": "Straatverlichting - Assen" + }, "surveillance": { "description": "Op deze open kaart kan je bewakingscamera's vinden.", "shortDescription": "Bewakingscameras en dergelijke", @@ -1406,9 +1512,13 @@ "description": "Kaart met afvalbakken en recyclingfaciliteiten.", "title": "Afval" }, + "waste_assen": { + "description": "Kaart met afvalbakken en recyclingfaciliteiten + een dataset voor Assen.", + "title": "Afval - Assen" + }, "waste_basket": { "description": "Op deze kaart vind je afvalbakken bij jou in de buurt. Als er een afvalbak ontbreekt op deze kaart, kun je deze zelf toevoegen", "shortDescription": "Een kaart met vuilnisbakken", "title": "Vuilnisbakken" } -} +} \ No newline at end of file From 88ca77ab3823c832b33b24f3d84d251fc62e85b6 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 26 Jan 2025 17:20:03 +0100 Subject: [PATCH 07/59] Fix build: move preset down --- assets/layers/school/school.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/assets/layers/school/school.json b/assets/layers/school/school.json index c461de29e7..06bf0df757 100644 --- a/assets/layers/school/school.json +++ b/assets/layers/school/school.json @@ -100,6 +100,21 @@ } ], "presets": [ + { + "tags": [ + "amenity=school", + "fixme=Added with MapComplete, the precise geometry should still be drawn" + ], + "title": { + "en": "a primary or secondary school", + "nl": "een lagere of middelbare school", + "de": "eine Grundschule oder weiterführende Schule", + "fr": "une école primaire ou secondaire", + "ca": "una escola de primària o secundària", + "cs": "základní nebo střední škola", + "es": "una escuela primaria o secundaria" + } + }, { "title": { "en": "a primary or secondary school", @@ -118,21 +133,6 @@ "school=kindergarten", "isced:level=0" ] - }, - { - "tags": [ - "amenity=school", - "fixme=Added with MapComplete, the precise geometry should still be drawn" - ], - "title": { - "en": "a primary or secondary school", - "nl": "een lagere of middelbare school", - "de": "eine Grundschule oder weiterführende Schule", - "fr": "une école primaire ou secondaire", - "ca": "una escola de primària o secundària", - "cs": "základní nebo střední škola", - "es": "una escuela primaria o secundaria" - } } ], "tagRenderings": [ From 9836c299986564e2d8ed29e72fa52652d8a1a921 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 26 Jan 2025 17:30:41 +0100 Subject: [PATCH 08/59] Fix build: fix translations --- assets/layers/school/school.json | 11 ++--------- langs/layers/ca.json | 3 --- langs/layers/cs.json | 3 --- langs/layers/de.json | 3 --- langs/layers/en.json | 2 +- langs/layers/es.json | 3 --- langs/layers/fr.json | 3 --- langs/layers/nl.json | 2 +- langs/layers/pl.json | 5 ----- langs/layers/ru.json | 7 ------- 10 files changed, 4 insertions(+), 38 deletions(-) diff --git a/assets/layers/school/school.json b/assets/layers/school/school.json index 06bf0df757..23adb084d5 100644 --- a/assets/layers/school/school.json +++ b/assets/layers/school/school.json @@ -117,15 +117,8 @@ }, { "title": { - "en": "a primary or secondary school", - "nl": "een lagere of middelbare school", - "de": "eine Grundschule oder weiterführende Schule", - "ru": "детский сад", - "ca": "una escola de primària o secundària", - "pl": "przedszkole", - "cs": "základní nebo střední škola", - "es": "una escuela primaria o secundaria", - "fr": "une école primaire ou secondaire" + "en": "a kindergarten", + "nl": "een kleuterschool" }, "description": "A kindergarten (also known as preschool) is a school where small kids receive early education.", "tags": [ diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 771d597f2e..e86719019a 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -6965,9 +6965,6 @@ "presets": { "0": { "title": "una escola de primària o secundària" - }, - "1": { - "title": "una escola de primària o secundària" } }, "tagRenderings": { diff --git a/langs/layers/cs.json b/langs/layers/cs.json index 915c4e972e..1e5103f83e 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -7947,9 +7947,6 @@ "presets": { "0": { "title": "základní nebo střední škola" - }, - "1": { - "title": "základní nebo střední škola" } }, "tagRenderings": { diff --git a/langs/layers/de.json b/langs/layers/de.json index f829711d44..5e447db3b2 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -9439,9 +9439,6 @@ "presets": { "0": { "title": "eine Grundschule oder weiterführende Schule" - }, - "1": { - "title": "eine Grundschule oder weiterführende Schule" } }, "tagRenderings": { diff --git a/langs/layers/en.json b/langs/layers/en.json index 1e7167df1e..a5a0ee4850 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -9505,7 +9505,7 @@ "title": "a primary or secondary school" }, "1": { - "title": "a primary or secondary school" + "title": "a kindergarten" } }, "tagRenderings": { diff --git a/langs/layers/es.json b/langs/layers/es.json index 4e006e807a..68aeb5f9b2 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -9251,9 +9251,6 @@ "presets": { "0": { "title": "una escuela primaria o secundaria" - }, - "1": { - "title": "una escuela primaria o secundaria" } }, "tagRenderings": { diff --git a/langs/layers/fr.json b/langs/layers/fr.json index 14599fc6ff..e9349d50b3 100644 --- a/langs/layers/fr.json +++ b/langs/layers/fr.json @@ -5693,9 +5693,6 @@ "presets": { "0": { "title": "une école primaire ou secondaire" - }, - "1": { - "title": "une école primaire ou secondaire" } }, "tagRenderings": { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 95b55731d2..51ccfb3067 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -8121,7 +8121,7 @@ "title": "een lagere of middelbare school" }, "1": { - "title": "een lagere of middelbare school" + "title": "een kleuterschool" } }, "tagRenderings": { diff --git a/langs/layers/pl.json b/langs/layers/pl.json index a9408413dd..5361d29b30 100644 --- a/langs/layers/pl.json +++ b/langs/layers/pl.json @@ -3451,11 +3451,6 @@ } }, "school": { - "presets": { - "0": { - "title": "przedszkole" - } - }, "tagRenderings": { "school-language": { "render": { diff --git a/langs/layers/ru.json b/langs/layers/ru.json index bf3110103f..eb95a110be 100644 --- a/langs/layers/ru.json +++ b/langs/layers/ru.json @@ -1630,13 +1630,6 @@ } } }, - "school": { - "presets": { - "0": { - "title": "детский сад" - } - } - }, "shops": { "description": "Магазин", "name": "Магазин", From 419495f26d91a26a4e3b304b8d247d3aca748e49 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 26 Jan 2025 18:36:50 +0100 Subject: [PATCH 09/59] UX: move debug information in dropdown --- assets/layers/usersettings/usersettings.json | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/assets/layers/usersettings/usersettings.json b/assets/layers/usersettings/usersettings.json index ed8d50057e..099c5d149a 100644 --- a/assets/layers/usersettings/usersettings.json +++ b/assets/layers/usersettings/usersettings.json @@ -1464,13 +1464,57 @@ ], "#force-save-button": "yes" }, + { + "id": "gps_accordeon", + "render": { + "special": { + "type": "group", + "header": "debug-gps-title", + "labels": "debug-gps" + } + } + }, + { + "id": "debug-gps-title", + "labels": [ + "hidden" + ], + "render": { + "en": "GPS and gyroscope data" + } + }, { "id": "debug-gps", + "labels": [ + "hidden" + ], "condition": "mapcomplete-show_debug=yes", "render": "{gps_all_tags()}" }, + { + "id": "debug_accordeon", + "render": { + "special": { + "type": "group", + "header": "debug_accordeon_title", + "labels": "debug" + } + } + }, + { + "id": "debug_accordeon_title", + "labels": [ + "hidden" + ], + "render": { + "en": "Debug information" + } + }, { "id": "debug", + "labels": [ + "hidden" + ], "condition": "mapcomplete-show_debug=yes", "render": "{all_tags()}" } From 7bddaa7d4c0c9ce95d57beb24c3b317f2f008035 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 02:32:19 +0100 Subject: [PATCH 10/59] Scripts(community_index): create script to update community index files, create weekly data maintenance script --- .forgejo/workflows/update_community_index.yml | 31 ++++ package.json | 1 + scripts/downloadCommunityIndex.ts | 116 +++++++++++++ scripts/slice.ts | 56 ++----- .../Actors/SaveFeatureSourceToLocalStorage.ts | 2 +- src/Logic/GeoOperations.ts | 157 +++++++++++------- src/Logic/Web/CommunityIndex.ts | 33 ++++ src/Models/TileRange.ts | 12 ++ .../BigComponents/CommunityIndexView.svelte | 15 +- src/UI/BigComponents/ContactLink.svelte | 11 +- test/Logic/GeoOperations.spec.ts | 119 ++++++++++--- 11 files changed, 412 insertions(+), 141 deletions(-) create mode 100644 .forgejo/workflows/update_community_index.yml create mode 100644 scripts/downloadCommunityIndex.ts create mode 100644 src/Logic/Web/CommunityIndex.ts diff --git a/.forgejo/workflows/update_community_index.yml b/.forgejo/workflows/update_community_index.yml new file mode 100644 index 0000000000..77b0e8f8cf --- /dev/null +++ b/.forgejo/workflows/update_community_index.yml @@ -0,0 +1,31 @@ +name: Weekly data updates +on: + schedule: + -cron: "* * * * 1" + +jobs: + deploy_on_hetzner_single: + runs-on: [ ubuntu-latest, hetzner-access ] + + runs-on: [ ubuntu-latest, hetzner-access ] + steps: + - uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: install deps + run: npm ci + shell: bash + + - name: update community index files + shell: bash + run: | + mkdir community-index + npm run download:community-index -- community-index/ + zip community-index.zip community-index/* + scp community-index.zip hetzner:data/ diff --git a/package.json b/package.json index 4ff48086b5..5e868596a8 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "download:editor-layer-index": "vite-node scripts/downloadEli.ts", "download:stats": "vite-node scripts/GenerateSeries.ts", "download:images": "vite-node scripts/generateImageAnalysis.ts -- ~/data/imgur-image-backup/", + "download:community-index": "vite-node scripts/downloadCommunityIndex.ts -- /tmp/test", "weblate:add-upstream": "git remote add weblate https://translate.mapcomplete.org/git/mapcomplete/core/ ; git remote update weblate", "weblate:fix": "npm run weblate:add-upstream && git merge weblate/master && git rebase origin/master && git push origin master", "lint": "npm run lint:prettier && npm run lint:eslint && npm run lint:themes", diff --git a/scripts/downloadCommunityIndex.ts b/scripts/downloadCommunityIndex.ts new file mode 100644 index 0000000000..fe4d4dcc02 --- /dev/null +++ b/scripts/downloadCommunityIndex.ts @@ -0,0 +1,116 @@ +import Script from "./Script" +import { CommunityResource } from "../src/Logic/Web/CommunityIndex" +import { Utils } from "../src/Utils" +import { FeatureCollection, MultiPolygon, Polygon } from "geojson" +import { writeFileSync } from "fs" +import { GeoOperations } from "../src/Logic/GeoOperations" +import { Tiles } from "../src/Models/TileRange" +import ScriptUtils from "./ScriptUtils" + +class DownloadCommunityIndex extends Script { + + constructor() { + super("Updates the community index") + } + + printHelp() { + console.log("Arguments are:\noutputdirectory") + } + + private static targetZoomlevel: number = 6 + private static upstreamUrl: string = "https://raw.githubusercontent.com/osmlab/osm-community-index/main/dist/" + + /** + * Prunes away unnecessary fields from a CommunityResource + * @private + */ + private static stripResource(r: Readonly): CommunityResource { + return { + id: r.id, + languageCodes: r.languageCodes, + account: r.account, + type: r.type, + resolved: { + name: r.resolved.name, + description: r.resolved.description, + url: r.resolved.url + } + } + } + + private static stripResourcesObj(resources: Readonly>>) { + const stripped: Record = {} + for (const k in resources) { + stripped[k] = DownloadCommunityIndex.stripResource(resources[k]) + } + return stripped + } + + public static async update(targetDirectory: string) { + const data = await Utils.downloadJson, + nameEn: string, + id: string + }>>(DownloadCommunityIndex.upstreamUrl + "completeFeatureCollection.json" + ) + const features = data.features + const global = features.find( + f => f.id === "Q2" + ) + const globalProperties = DownloadCommunityIndex.stripResourcesObj(global.properties.resources) + writeFileSync(targetDirectory + "/global.json", JSON.stringify(globalProperties), "utf8") + console.log("Written global properties") + + const types = new Set() + for (const f of features) { + const res = f.properties.resources + for (const k in res) { + types.add(res[k].type) + } + } + for (const type of types) { + const url = `${DownloadCommunityIndex.upstreamUrl}img/${type}.svg` + await ScriptUtils.DownloadFileTo(url, `${targetDirectory}/${type}.svg`) + } + const local = features.filter(f => f.id !== "Q2") + const spread = GeoOperations.spreadIntoBboxes(local, DownloadCommunityIndex.targetZoomlevel) + let written = 0 + let skipped = 0 + writeFileSync(targetDirectory + "local.geojson", JSON.stringify({ type: "FeatureCollection", features: local })) + for (const tileIndex of spread.keys()) { + const features = spread.get(tileIndex) + const clipped = GeoOperations.clipAllInBox(features, tileIndex) + if (clipped.length === 0) { + skipped++ + features.push(Tiles.asGeojson(tileIndex)) + writeFileSync(`${targetDirectory + tileIndex}_skipped.geojson`, JSON.stringify({ + type: "FeatureCollection", features + })) + continue + } + const [z, x, y] = Tiles.tile_from_index(tileIndex) + const path = `${targetDirectory}/tile_${z}_${x}_${y}.geojson` + clipped.forEach((f) => { + delete f.bbox + }) + writeFileSync(path, JSON.stringify({ type: "FeatureCollection", features: clipped }), "utf8") + written++ + console.log(`Written tile ${path}`) + } + console.log(`Created ${written} tiles, skipped ${skipped}`) + } + + + async main(args: string[]): Promise { + const path = args[0] + if (!path) { + this.printHelp() + return + } + + await DownloadCommunityIndex.update(path) + + } +} + +new DownloadCommunityIndex().run() diff --git a/scripts/slice.ts b/scripts/slice.ts index 77b7b65c9a..a4a17831a0 100644 --- a/scripts/slice.ts +++ b/scripts/slice.ts @@ -1,13 +1,12 @@ import * as fs from "fs" -import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" import * as readline from "readline" import ScriptUtils from "./ScriptUtils" import { Utils } from "../Utils" import Script from "./Script" -import { BBox } from "../Logic/BBox" import { GeoOperations } from "../Logic/GeoOperations" import { Tiles } from "../Models/TileRange" import { Feature } from "geojson" +import { features } from "monaco-editor/esm/metadata" /** * This script slices a big newline-delimeted geojson file into tiled geojson @@ -96,34 +95,15 @@ class Slice extends Script { features: Feature[], tileIndex: number, outputDirectory: string, - doSlice: boolean, + doClip: boolean, handled: number, maxNumberOfTiles: number - ) { + ): boolean { + if (doClip) { + features = GeoOperations.clipAllInBox(features, tileIndex) + } const [z, x, y] = Tiles.tile_from_index(tileIndex) const path = `${outputDirectory}/tile_${z}_${x}_${y}.geojson` - const box = BBox.fromTileIndex(tileIndex) - if (doSlice) { - features = Utils.NoNull( - features.map((f) => { - const bbox = box.asGeoJson({}) - const properties = { - ...f.properties, - id: (f.properties?.id ?? "") + "_" + z + "_" + x + "_" + y, - } - - if (GeoOperations.completelyWithin(bbox, f)) { - bbox.properties = properties - return bbox - } - const intersection = GeoOperations.intersect(f, box.asGeoJson({})) - if (intersection) { - intersection.properties = properties - } - return intersection - }) - ) - } features.forEach((f) => { delete f.bbox }) @@ -177,7 +157,7 @@ class Slice extends Script { } console.log("Using directory ", outputDirectory) - let allFeatures: any[] + let allFeatures: Feature[] if (inputFile.endsWith(".geojson")) { console.log("Detected geojson") allFeatures = await this.readFeaturesFromGeoJson(inputFile) @@ -202,18 +182,16 @@ class Slice extends Script { } const maxNumberOfTiles = Math.pow(2, zoomlevel) * Math.pow(2, zoomlevel) let handled = 0 - StaticFeatureSource.fromGeojson(allFeatures).features.addCallbackAndRun((feats) => { - GeoOperations.slice(zoomlevel, feats).forEach((tileData, tileIndex) => { - handled = handled + 1 - this.handleTileData( - tileData, - tileIndex, - outputDirectory, - doSlice, - handled, - maxNumberOfTiles - ) - }) + GeoOperations.slice(zoomlevel, features).forEach((tileData, tileIndex) => { + handled = handled + 1 + this.handleTileData( + tileData, + tileIndex, + outputDirectory, + doSlice, + handled, + maxNumberOfTiles + ) }) } } diff --git a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts index 6e2510b727..59cf8b51bd 100644 --- a/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts +++ b/src/Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage.ts @@ -75,7 +75,7 @@ export default class SaveFeatureSourceToLocalStorage { this.storage = storage const singleTileSavers: Map = new Map() features.features.addCallbackAndRunD((features) => { - const sliced = GeoOperations.slice(zoomlevel, features) + const sliced = GeoOperations.spreadIntoBboxes(features, zoomlevel) sliced.forEach((features, tileIndex) => { let tileSaver = singleTileSavers.get(tileIndex) diff --git a/src/Logic/GeoOperations.ts b/src/Logic/GeoOperations.ts index 63fe2ccb76..a6faf3d8b8 100644 --- a/src/Logic/GeoOperations.ts +++ b/src/Logic/GeoOperations.ts @@ -1,6 +1,6 @@ import { BBox } from "./BBox" import * as turf from "@turf/turf" -import { AllGeoJSON, booleanWithin, Coord } from "@turf/turf" +import { AllGeoJSON, booleanWithin, Coord, Polygon } from "@turf/turf" import { Feature, FeatureCollection, @@ -9,13 +9,13 @@ import { MultiLineString, MultiPolygon, Point, - Polygon, - Position, + Position } from "geojson" import { Tiles } from "../Models/TileRange" import { Utils } from "../Utils" import { NearestPointOnLine } from "@turf/nearest-point-on-line" -;("use strict") + +("use strict") export class GeoOperations { private static readonly _earthRadius = 6378137 @@ -29,7 +29,7 @@ export class GeoOperations { "behind", "sharp_left", "left", - "slight_left", + "slight_left" ] as const private static reverseBearing = { N: 0, @@ -47,7 +47,7 @@ export class GeoOperations { W: 270, WNW: 292.5, NW: 315, - NNW: 337.5, + NNW: 337.5 } /** @@ -61,8 +61,8 @@ export class GeoOperations { } public static intersect( - f0: Feature, - f1: Feature + f0: Readonly>, + f1: Readonly> ): Feature | null { return turf.intersect(f0, f1) } @@ -309,7 +309,7 @@ export class GeoOperations { bufferSizeInMeter: number ): Feature | FeatureCollection { return turf.buffer(feature, bufferSizeInMeter / 1000, { - units: "kilometers", + units: "kilometers" }) } @@ -325,9 +325,9 @@ export class GeoOperations { [lon0, lat], [lon0, lat0], [lon, lat0], - [lon, lat], - ], - }, + [lon, lat] + ] + } } } @@ -368,9 +368,9 @@ export class GeoOperations { type: "Feature", geometry: { type: "LineString", - coordinates: way.geometry.coordinates[0], + coordinates: way.geometry.coordinates[0] }, - properties: way.properties, + properties: way.properties } } if (way.geometry.type === "MultiPolygon") { @@ -378,9 +378,9 @@ export class GeoOperations { type: "Feature", geometry: { type: "MultiLineString", - coordinates: way.geometry.coordinates[0], + coordinates: way.geometry.coordinates[0] }, - properties: way.properties, + properties: way.properties } } if (way.geometry.type === "LineString") { @@ -512,6 +512,8 @@ export class GeoOperations { /** * Given a list of features, will construct a map of slippy map tile-indices. * Features of which the BBOX overlaps with the corresponding slippy map tile are added to the corresponding array + * + * Also @see clipAllInBox * @param features * @param zoomlevel */ @@ -535,6 +537,33 @@ export class GeoOperations { return perBbox } + /** + * Given a list of features, returns a new list of features so that the features are clipped into the given tile-index. + * Note: IDs are rewritten + * Also @see spreadIntoBBoxes + */ + public static clipAllInBox(features: ReadonlyArray>, tileIndex: number): Feature[] { + const bbox = Tiles.asGeojson(tileIndex) + const newFeatures: Feature[] = [] + for (const f of features) { + const intersectionParts = GeoOperations.clipWith(f, bbox) + for (let i = 0; i < intersectionParts.length; i++) { + const intersectionPart = intersectionParts[i] + let id = (f.properties?.id ?? "") + "_" + tileIndex + if (i > 0) { + id += "_part_" + i + } + const properties = { + ...f.properties, + id + } + intersectionPart.properties = properties + newFeatures.push(intersectionPart) + } + } + return Utils.NoNull(newFeatures) + } + public static toGpx( locations: | Feature @@ -558,8 +587,8 @@ export class GeoOperations { properties: {}, geometry: { type: "Point", - coordinates: p, - }, + coordinates: p + } } ) } @@ -575,7 +604,7 @@ export class GeoOperations { trackPoints.push(trkpt) } const header = - '' + "" return ( header + "\n" + @@ -614,7 +643,7 @@ export class GeoOperations { trackPoints.push(trkpt) } const header = - '' + "" return ( header + "\n" + @@ -640,7 +669,7 @@ export class GeoOperations { const copy = { ...feature, - geometry: { ...feature.geometry }, + geometry: { ...feature.geometry } } let coordinates: [number, number][] if (feature.geometry.type === "LineString") { @@ -698,8 +727,8 @@ export class GeoOperations { type: "Feature", geometry: { type: "LineString", - coordinates: [a, b], - }, + coordinates: [a, b] + } }, distanceMeter, { units: "meters" } @@ -736,17 +765,26 @@ export class GeoOperations { * GeoOperations.completelyWithin(park, pond) // => false */ static completelyWithin( - feature: Feature, - possiblyEnclosingFeature: Feature + feature: Readonly, + possiblyEnclosingFeature: Readonly> ): boolean { + if (feature.geometry.type === "MultiPolygon") { + const polygons = feature.geometry.coordinates.map(coordinates => + >{ + type: "Feature", geometry: { + type: "Polygon", coordinates + } + }) + return !polygons.some(polygon => !booleanWithin(polygon, possiblyEnclosingFeature)) + } return booleanWithin(feature, possiblyEnclosingFeature) } /** * Create an intersection between two features. - * One or multiple new feature is returned based on 'toSplit', which'll have a geometry that is completely withing boundary + * One or multiple new feature are returned based on 'toSplit', which'll have a geometry that is completely withing boundary */ - public static clipWith(toSplit: Feature, boundary: Feature): Feature[] { + public static clipWith(toSplit: Readonly, boundary: Readonly>): Feature[] { if (toSplit.geometry.type === "Point") { const p = >toSplit if (GeoOperations.inside(<[number, number]>p.geometry.coordinates, boundary)) { @@ -757,9 +795,9 @@ export class GeoOperations { } if (toSplit.geometry.type === "LineString") { - const splitup = turf.lineSplit(>toSplit, boundary) - const kept = [] - for (const f of splitup.features) { + const splitup: Feature[] = turf.lineSplit(>toSplit, boundary).features + const kept: Feature[] = [] + for (const f of splitup) { if (!GeoOperations.inside(GeoOperations.centerpointCoordinates(f), boundary)) { continue } @@ -787,7 +825,24 @@ export class GeoOperations { return kept } if (toSplit.geometry.type === "Polygon" || toSplit.geometry.type == "MultiPolygon") { + const splitup = turf.intersect(>toSplit, boundary) + if (splitup === null) { + // No intersection found. + // Either: the boundary is contained fully in 'toSplit', 'toSplit' is contained fully in 'boundary' or they are unrelated at all + if (GeoOperations.completelyWithin(toSplit, boundary)) { + return [toSplit] + } + if (GeoOperations.completelyWithin(boundary, >toSplit)) { + return [{ + type: "Feature", + properties: { ...toSplit.properties }, + geometry: boundary.geometry, + bbox: boundary.bbox + }] + } + return [] + } splitup.properties = { ...toSplit.properties } return [splitup] } @@ -864,32 +919,6 @@ export class GeoOperations { } } - /** - * Constructs all tiles where features overlap with and puts those features in them. - * Long features (e.g. lines or polygons) which overlap with multiple tiles are referenced in each tile they overlap with - * @param zoomlevel - * @param features - */ - public static slice(zoomlevel: number, features: Feature[]): Map { - const tiles = new Map() - - for (const feature of features) { - const bbox = BBox.get(feature) - Tiles.MapRange(Tiles.tileRangeFrom(bbox, zoomlevel), (x, y) => { - const i = Tiles.tile_index(zoomlevel, x, y) - - let tiledata = tiles.get(i) - if (tiledata === undefined) { - tiledata = [] - tiles.set(i, tiledata) - } - tiledata.push(feature) - }) - } - - return tiles - } - /** * Creates a linestring object based on the outer ring of the given polygon * @@ -905,8 +934,8 @@ export class GeoOperations { properties: p.properties, geometry: { type: "LineString", - coordinates: p.geometry.coordinates[0], - }, + coordinates: p.geometry.coordinates[0] + } } } @@ -934,7 +963,7 @@ export class GeoOperations { console.debug("SPlitting way", feature.properties.id) result.push({ ...feature, - geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) }, + geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) } }) coors = coors.slice(0, i + 1) break @@ -943,7 +972,7 @@ export class GeoOperations { } result.push({ ...feature, - geometry: { ...feature.geometry, coordinates: coors }, + geometry: { ...feature.geometry, coordinates: coors } }) } } @@ -1117,8 +1146,8 @@ export class GeoOperations { properties: multiLineStringFeature.properties, geometry: { type: "LineString", - coordinates: coors[0], - }, + coordinates: coors[0] + } } } return { @@ -1126,8 +1155,8 @@ export class GeoOperations { properties: multiLineStringFeature.properties, geometry: { type: "MultiLineString", - coordinates: coors, - }, + coordinates: coors + } } } diff --git a/src/Logic/Web/CommunityIndex.ts b/src/Logic/Web/CommunityIndex.ts new file mode 100644 index 0000000000..bb0dc14760 --- /dev/null +++ b/src/Logic/Web/CommunityIndex.ts @@ -0,0 +1,33 @@ +/** + * Various tools and types to work with the community index (https://openstreetmap.community/; https://github.com/osmlab/osm-community-index) + */ + + +export interface CommunityResource { + /** + * A unique identifier for the resource + * "pattern": "^[-_.A-Za-z0-9]+$" + */ + id: string, + /** + * Type of community resource (thus: platform) + */ + type: string, + /** + * included and excluded locations for this item + * See location-conflation documentation for compatible values: https://github.com/rapideditor/location-conflation#readme + */ + locationSet?, + + /** Array of ISO-639-1 (2 letter) or ISO-639-3 (3 letter) codes in lowercase + * */ + languageCodes?: string[] + /** + * Resource account string, required for some resource types + */ + account?: string + + resolved?: { url: string, name: string, description: string } & Record + +} + diff --git a/src/Models/TileRange.ts b/src/Models/TileRange.ts index 6b70c438e8..31890210da 100644 --- a/src/Models/TileRange.ts +++ b/src/Models/TileRange.ts @@ -1,4 +1,5 @@ import { BBox } from "../Logic/BBox" +import { Feature, Polygon } from "geojson" export interface TileRange { xstart: number @@ -80,6 +81,17 @@ export class Tiles { return [z, x, index % factor] } + static asGeojson(index: number): Feature; + static asGeojson(x: number, y: number, z: number): Feature; + static asGeojson(zIndex: number, x?: number, y?: number): Feature { + let z = zIndex + if (x === undefined) { + [z, x, y] = Tiles.tile_from_index(zIndex) + } + const bounds = Tiles.tile_bounds_lon_lat(z, x, y) + return new BBox(bounds).asGeoJson() + } + /** * Return x, y of the tile containing (lat, lon) on the given zoom level */ diff --git a/src/UI/BigComponents/CommunityIndexView.svelte b/src/UI/BigComponents/CommunityIndexView.svelte index 5ef59b4f39..d1dfc3659d 100644 --- a/src/UI/BigComponents/CommunityIndexView.svelte +++ b/src/UI/BigComponents/CommunityIndexView.svelte @@ -6,8 +6,10 @@ import ContactLink from "./ContactLink.svelte" import { GeoOperations } from "../../Logic/GeoOperations" import Translations from "../i18n/Translations" - import ToSvelte from "../Base/ToSvelte.svelte" import type { Feature, Geometry, GeometryCollection } from "@turf/turf" + import type { FeatureCollection, Polygon } from "geojson" + import type { CommunityResource } from "../../Logic/Web/CommunityIndex" + import Tr from "../Base/Tr.svelte" export let location: Store<{ lat: number; lon: number }> const tileToFetch: Store = location.mapD((l) => { @@ -20,7 +22,10 @@ >([]) tileToFetch.addCallbackAndRun(async (url) => { - const data = await Utils.downloadJsonCached(url, 24 * 60 * 60) + const data = await Utils.downloadJsonCached + }>>(url, 24 * 60 * 60) if (data === undefined) { return } @@ -29,15 +34,13 @@ const filteredResources = resources.map( (features) => - features.filter((f) => { - return GeoOperations.inside([location.data.lon, location.data.lat], f) - }), + features.filter((f) => GeoOperations.inside([location.data.lon, location.data.lat], f)), [location] )
- + {#each $filteredResources as feature} {/each} diff --git a/src/UI/BigComponents/ContactLink.svelte b/src/UI/BigComponents/ContactLink.svelte index ca37679f8d..123d5f82a7 100644 --- a/src/UI/BigComponents/ContactLink.svelte +++ b/src/UI/BigComponents/ContactLink.svelte @@ -3,23 +3,18 @@ // The _properties_ of a community feature import Locale from "../i18n/Locale.js" import Translations from "../i18n/Translations" - import ToSvelte from "../Base/ToSvelte.svelte" import * as native from "../../assets/language_native.json" import { TypedTranslation } from "../i18n/Translation" import Tr from "../Base/Tr.svelte" + import type { CommunityResource } from "../../Logic/Web/CommunityIndex" const availableTranslationTyped: TypedTranslation<{ native: string }> = Translations.t.communityIndex.available const availableTranslation = availableTranslationTyped.OnEveryLanguage((s, ln) => s.replace("{native}", native[ln] ?? ln) ) - export let country: { resources; nameEn: string } - let resources: { - id: string - resolved: Record - languageCodes: string[] - type: string - }[] = [] + export let country: { resources: Record; nameEn: string } + let resources: CommunityResource[] = [] $: resources = Array.from(Object.values(country?.resources ?? {})) const language = Locale.language diff --git a/test/Logic/GeoOperations.spec.ts b/test/Logic/GeoOperations.spec.ts index f069603655..0bc249be1d 100644 --- a/test/Logic/GeoOperations.spec.ts +++ b/test/Logic/GeoOperations.spec.ts @@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest" describe("GeoOperations", () => { describe("calculateOverlap", () => { it("should not give too much overlap (regression test)", () => { - const polyGrb = { + const polyGrb: Feature = { type: "Feature", properties: { osm_id: "25189153", @@ -37,7 +37,7 @@ describe("GeoOperations", () => { "_now:date": "2021-12-05", "_now:datetime": "2021-12-05 21:51:40", "_loaded:date": "2021-12-05", - "_loaded:datetime": "2021-12-05 21:51:40", + "_loaded:datetime": "2021-12-05 21:51:40" }, geometry: { type: "Polygon", @@ -50,21 +50,21 @@ describe("GeoOperations", () => { [3.24329779999996, 50.837435399999855], [3.2431881000000504, 50.83740090000025], [3.243152699999997, 50.83738980000017], - [3.2431059999999974, 50.83730270000021], - ], - ], + [3.2431059999999974, 50.83730270000021] + ] + ] }, id: "https://betadata.grbosm.site/grb?bbox=360935.6475626023,6592540.815539878,361088.52161917265,6592693.689596449/37", _lon: 3.2432137000000116, _lat: 50.83736194999996, bbox: { + minLat: 50.83728850000007, maxLat: 50.837435399999855, maxLon: 3.2433214000000254, - minLat: 50.83728850000007, - minLon: 3.2431059999999974, - }, + minLon: 3.2431059999999974 + } } - const polyHouse = { + const polyHouse: Feature = { type: "Feature", id: "way/594963177", properties: { @@ -95,7 +95,7 @@ describe("GeoOperations", () => { "_loaded:date": "2021-12-05", "_loaded:datetime": "2021-12-05 21:51:39", _surface: "93.32785810484549", - "_surface:ha": "0", + "_surface:ha": "0" }, geometry: { type: "Polygon", @@ -108,9 +108,9 @@ describe("GeoOperations", () => { [3.2431691, 50.8374252], [3.2430936, 50.837401], [3.243046, 50.8374112], - [3.2429993, 50.8373243], - ], - ], + [3.2429993, 50.8373243] + ] + ] }, _lon: 3.2430937, _lat: 50.83736395, @@ -118,8 +118,8 @@ describe("GeoOperations", () => { maxLat: 50.8374252, maxLon: 3.2431881, minLat: 50.8373027, - minLon: 3.2429993, - }, + minLon: 3.2429993 + } } const p0 = turf.polygon(polyGrb.geometry.coordinates) @@ -145,11 +145,11 @@ describe("GeoOperations", () => { [3.218560377159008, 51.21499687768525], [3.2207456783268356, 51.21499687768525], [3.2207456783268356, 51.21600586532159], - [3.218560377159008, 51.21600586532159], - ], + [3.218560377159008, 51.21600586532159] + ] ], - type: "Polygon", - }, + type: "Polygon" + } } const line: Feature = { type: "Feature", @@ -157,10 +157,10 @@ describe("GeoOperations", () => { geometry: { coordinates: [ [3.218405371672816, 51.21499091846559], - [3.2208408127450525, 51.21560173433727], + [3.2208408127450525, 51.21560173433727] ], - type: "LineString", - }, + type: "LineString" + } } const result = GeoOperations.clipWith(line, bbox) expect(result.length).to.equal(1) @@ -168,10 +168,83 @@ describe("GeoOperations", () => { const clippedLine = (>result[0]).geometry.coordinates const expCoordinates = [ [3.2185604, 51.215029800031594], - [3.2207457, 51.21557787977764], + [3.2207457, 51.21557787977764] ] expect(clippedLine).to.deep.equal(expCoordinates) }) + it("clipWith should contain the full feature if it is fully contained", () => { + const bbox: Feature = { + type: "Feature", + properties: {}, + geometry: { + coordinates: [ + [ + [ + 2.1541744759711037, + 51.73994420687188 + ], + [ + 2.1541744759711037, + 50.31129074222787 + ], + [ + 4.53247037641421, + 50.31129074222787 + ], + [ + 4.53247037641421, + 51.73994420687188 + ], + [ + 2.1541744759711037, + 51.73994420687188 + ] + ] + ], + type: "Polygon" + } + } + const content: Feature = { + "type": "Feature", + "properties": {}, + "geometry": { + "coordinates": [ + [ + [ + 2.8900597545854225, + 50.9035099487991 + ], + [ + 3.4872999807053873, + 50.74856284865993 + ], + [ + 3.9512276563531543, + 50.947206170675486 + ], + [ + 3.897902636163167, + 51.25526892606362 + ], + [ + 3.188679867646016, + 51.24525576870511 + ], [ + 2.8900597545854225, + 50.9035099487991 + ] + ] + ], + "type": "Polygon" + } + } + const clipped = GeoOperations.clipWith(content, bbox) + expect(clipped.length).to.equal(1) + + const clippedReverse = GeoOperations.clipWith(bbox, content) + expect(clippedReverse.length).to.equal(1) + } + ) }) }) From 851e7e4dee840223971a45fdf9d66502c90f4776 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 02:34:50 +0100 Subject: [PATCH 11/59] Scripts: attempt to force a run --- .forgejo/workflows/update_community_index.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/update_community_index.yml b/.forgejo/workflows/update_community_index.yml index 77b0e8f8cf..3247c4b325 100644 --- a/.forgejo/workflows/update_community_index.yml +++ b/.forgejo/workflows/update_community_index.yml @@ -1,7 +1,10 @@ name: Weekly data updates on: + push: + branches-ignore: + - build/* schedule: - -cron: "* * * * 1" + - cron: "* * * * 1" jobs: deploy_on_hetzner_single: From 6bdb5b2d5b6f299b21ec7149fe0a70b17e7313b5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 02:37:47 +0100 Subject: [PATCH 12/59] Scripts: attempt to force a run --- .forgejo/workflows/update_community_index.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.forgejo/workflows/update_community_index.yml b/.forgejo/workflows/update_community_index.yml index 3247c4b325..3e5d07cc1f 100644 --- a/.forgejo/workflows/update_community_index.yml +++ b/.forgejo/workflows/update_community_index.yml @@ -8,8 +8,6 @@ on: jobs: deploy_on_hetzner_single: - runs-on: [ ubuntu-latest, hetzner-access ] - runs-on: [ ubuntu-latest, hetzner-access ] steps: - uses: actions/checkout@v3 From b4b35aa699880553e893a6b1ba9f7209f7229357 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 02:41:46 +0100 Subject: [PATCH 13/59] Scripts: attempt to fix a run --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5e868596a8..d94904d85c 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "download:editor-layer-index": "vite-node scripts/downloadEli.ts", "download:stats": "vite-node scripts/GenerateSeries.ts", "download:images": "vite-node scripts/generateImageAnalysis.ts -- ~/data/imgur-image-backup/", - "download:community-index": "vite-node scripts/downloadCommunityIndex.ts -- /tmp/test", + "download:community-index": "vite-node scripts/downloadCommunityIndex.ts ", "weblate:add-upstream": "git remote add weblate https://translate.mapcomplete.org/git/mapcomplete/core/ ; git remote update weblate", "weblate:fix": "npm run weblate:add-upstream && git merge weblate/master && git rebase origin/master && git push origin master", "lint": "npm run lint:prettier && npm run lint:eslint && npm run lint:themes", From 7a6cd34d0ddb3a2832d53401b413c3c7b048c1df Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 02:43:11 +0100 Subject: [PATCH 14/59] Actions: remove unnecessary 'name' from config files --- .forgejo/workflows/deploy_hosted.yml | 1 - .forgejo/workflows/deploy_single_theme.yml | 1 - .forgejo/workflows/update_community_index.yml | 1 - 3 files changed, 3 deletions(-) diff --git a/.forgejo/workflows/deploy_hosted.yml b/.forgejo/workflows/deploy_hosted.yml index 53d2137dcb..7a9e1fa6b8 100644 --- a/.forgejo/workflows/deploy_hosted.yml +++ b/.forgejo/workflows/deploy_hosted.yml @@ -1,4 +1,3 @@ -name: Deploy develop on dev.mapcomplete.org on: push: branches-ignore: diff --git a/.forgejo/workflows/deploy_single_theme.yml b/.forgejo/workflows/deploy_single_theme.yml index 2d7d662fbe..5858007ece 100644 --- a/.forgejo/workflows/deploy_single_theme.yml +++ b/.forgejo/workflows/deploy_single_theme.yml @@ -1,4 +1,3 @@ -name: Deploy develop on theme.mapcomplete.org on: push: branches: diff --git a/.forgejo/workflows/update_community_index.yml b/.forgejo/workflows/update_community_index.yml index 3e5d07cc1f..661e23d519 100644 --- a/.forgejo/workflows/update_community_index.yml +++ b/.forgejo/workflows/update_community_index.yml @@ -1,4 +1,3 @@ -name: Weekly data updates on: push: branches-ignore: From 17f839b02f59095f62abd32f85c4c2ad08599cfd Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 02:48:37 +0100 Subject: [PATCH 15/59] Actions: attempt to fix script --- ...update_community_index.yml => daily_data_maintenance.yml} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename .forgejo/workflows/{update_community_index.yml => daily_data_maintenance.yml} (86%) diff --git a/.forgejo/workflows/update_community_index.yml b/.forgejo/workflows/daily_data_maintenance.yml similarity index 86% rename from .forgejo/workflows/update_community_index.yml rename to .forgejo/workflows/daily_data_maintenance.yml index 661e23d519..adbcbd94ac 100644 --- a/.forgejo/workflows/update_community_index.yml +++ b/.forgejo/workflows/daily_data_maintenance.yml @@ -3,7 +3,7 @@ on: branches-ignore: - build/* schedule: - - cron: "* * * * 1" + - cron: "0 2 * * *" jobs: deploy_on_hetzner_single: @@ -26,6 +26,7 @@ jobs: shell: bash run: | mkdir community-index - npm run download:community-index -- community-index/ + npm run download:community-index -- -- community-index/ zip community-index.zip community-index/* scp community-index.zip hetzner:data/ + From 90aa2f1fc15cf2fbcde36ca6874621e192c22db4 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 02:56:13 +0100 Subject: [PATCH 16/59] Config(hetzner): update caddyfile --- Docs/ServerConfig/hetzner/Caddyfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Docs/ServerConfig/hetzner/Caddyfile b/Docs/ServerConfig/hetzner/Caddyfile index bedaba838f..33c4f55d87 100644 --- a/Docs/ServerConfig/hetzner/Caddyfile +++ b/Docs/ServerConfig/hetzner/Caddyfile @@ -64,6 +64,15 @@ countrycoder.mapcomplete.org { } } +data.mapcomplete.org { + root * data/ + file_server + header { + +Permissions-Policy "interest-cohort=()" + +Access-Control-Allow-Origin * + } +} + report.mapcomplete.org { reverse_proxy http://127.0.0.1:2348 } From 30604304eb5a6a83445ac26b0a9ed452414d172a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 03:14:19 +0100 Subject: [PATCH 17/59] Scripts: fix overview-support for osm-community, switch server --- .forgejo/workflows/daily_data_maintenance.yml | 9 ++++++--- .../osm_community_index/osm_community_index.json | 2 +- scripts/downloadCommunityIndex.ts | 13 ++++++++++++- .../TiledFeatureSource/DynamicGeoJsonTileSource.ts | 8 ++++---- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.forgejo/workflows/daily_data_maintenance.yml b/.forgejo/workflows/daily_data_maintenance.yml index adbcbd94ac..6e2b3922a0 100644 --- a/.forgejo/workflows/daily_data_maintenance.yml +++ b/.forgejo/workflows/daily_data_maintenance.yml @@ -22,11 +22,14 @@ jobs: run: npm ci shell: bash - - name: update community index files + - name: create community index files + shell: bash + run: npm run download:community-index -- -- community-index/ + + - name: upload community index shell: bash run: | - mkdir community-index - npm run download:community-index -- -- community-index/ zip community-index.zip community-index/* scp community-index.zip hetzner:data/ + ssh hetzner "cd data && rm -rf community-index/ && unzip community-index.zip && rm community-index.zip" diff --git a/assets/layers/osm_community_index/osm_community_index.json b/assets/layers/osm_community_index/osm_community_index.json index 0f50782567..df2bf5a5ba 100644 --- a/assets/layers/osm_community_index/osm_community_index.json +++ b/assets/layers/osm_community_index/osm_community_index.json @@ -19,7 +19,7 @@ "es": "Una capa que muestra las comunidades de OpenStreetMap" }, "source": { - "geoJson": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/community_index/tile_{z}_{x}_{y}.geojson", + "geoJson": "https://data.mapcomplete.org/community-index/tile_{z}_{x}_{y}.geojson", "geoJsonZoomLevel": 6, "osmTags": "resources~*" }, diff --git a/scripts/downloadCommunityIndex.ts b/scripts/downloadCommunityIndex.ts index fe4d4dcc02..6643b23de5 100644 --- a/scripts/downloadCommunityIndex.ts +++ b/scripts/downloadCommunityIndex.ts @@ -2,7 +2,7 @@ import Script from "./Script" import { CommunityResource } from "../src/Logic/Web/CommunityIndex" import { Utils } from "../src/Utils" import { FeatureCollection, MultiPolygon, Polygon } from "geojson" -import { writeFileSync } from "fs" +import { existsSync, mkdirSync, writeFileSync } from "fs" import { GeoOperations } from "../src/Logic/GeoOperations" import { Tiles } from "../src/Models/TileRange" import ScriptUtils from "./ScriptUtils" @@ -53,6 +53,9 @@ class DownloadCommunityIndex extends Script { id: string }>>(DownloadCommunityIndex.upstreamUrl + "completeFeatureCollection.json" ) + if (!existsSync(targetDirectory)) { + mkdirSync(targetDirectory) + } const features = data.features const global = features.find( f => f.id === "Q2" @@ -76,6 +79,7 @@ class DownloadCommunityIndex extends Script { const spread = GeoOperations.spreadIntoBboxes(local, DownloadCommunityIndex.targetZoomlevel) let written = 0 let skipped = 0 + const writtenTilesOverview: Record = {} writeFileSync(targetDirectory + "local.geojson", JSON.stringify({ type: "FeatureCollection", features: local })) for (const tileIndex of spread.keys()) { const features = spread.get(tileIndex) @@ -95,9 +99,16 @@ class DownloadCommunityIndex extends Script { }) writeFileSync(path, JSON.stringify({ type: "FeatureCollection", features: clipped }), "utf8") written++ + let yList = writtenTilesOverview[x] + if (!yList) { + yList = [] + writtenTilesOverview[x] = yList + } + yList.push(y) console.log(`Written tile ${path}`) } console.log(`Created ${written} tiles, skipped ${skipped}`) + writeFileSync(targetDirectory + "/tiles_6_overview.json", JSON.stringify(writtenTilesOverview), "utf8") } diff --git a/src/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts index 7cb3428563..848d8734b0 100644 --- a/src/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts +++ b/src/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts @@ -6,7 +6,7 @@ import { BBox } from "../../BBox" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" export default class DynamicGeoJsonTileSource extends UpdatableDynamicTileSource { - private static whitelistCache = new Map() + private static whitelistCache = new Map>>() constructor( layer: LayerConfig, @@ -27,7 +27,7 @@ export default class DynamicGeoJsonTileSource extends UpdatableDynamicTileSource } console.log("Creating a dynamic geojson source for", layer.source.geojsonSource) - let whitelist = undefined + let whitelist: Map> = undefined if (source.geojsonSource.indexOf("{x}_{y}.geojson") > 0) { const whitelistUrl = source.geojsonSource .replace("{z}", "" + source.geojsonZoomLevel) @@ -37,8 +37,8 @@ export default class DynamicGeoJsonTileSource extends UpdatableDynamicTileSource if (DynamicGeoJsonTileSource.whitelistCache.has(whitelistUrl)) { whitelist = DynamicGeoJsonTileSource.whitelistCache.get(whitelistUrl) } else { - Utils.downloadJsonCached(whitelistUrl, 1000 * 60 * 60) - .then((json) => { + Utils.downloadJsonCached>(whitelistUrl, 1000 * 60 * 60) + .then(json => { const data = new Map>() for (const x in json) { if (x === "zoom") { From 7f5b797b7db338d310aa5ec7d73532386ea2bf0e Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 03:20:07 +0100 Subject: [PATCH 18/59] Scripts: fix typo --- scripts/downloadCommunityIndex.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/downloadCommunityIndex.ts b/scripts/downloadCommunityIndex.ts index 6643b23de5..edc0c9c20d 100644 --- a/scripts/downloadCommunityIndex.ts +++ b/scripts/downloadCommunityIndex.ts @@ -108,7 +108,8 @@ class DownloadCommunityIndex extends Script { console.log(`Written tile ${path}`) } console.log(`Created ${written} tiles, skipped ${skipped}`) - writeFileSync(targetDirectory + "/tiles_6_overview.json", JSON.stringify(writtenTilesOverview), "utf8") + writeFileSync(targetDirectory + "/tile_6_overview.json", JSON.stringify(writtenTilesOverview), "utf8") + console.log("Created overview file") } From 3c6606c90be13c9d6d1eda4c0e0dbbb7ae1f5d48 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 27 Jan 2025 03:47:50 +0100 Subject: [PATCH 19/59] UI: update community index view to point to the selfhosted server --- package.json | 1 + scripts/downloadCommunityIndex.ts | 6 +- src/Models/Constants.ts | 2 + src/UI/Base/Popup.svelte | 2 +- .../BigComponents/CommunityIndexView.svelte | 27 +- src/UI/BigComponents/ContactLink.svelte | 8 +- .../community_index_global_resources.json | 355 ------------------ 7 files changed, 33 insertions(+), 368 deletions(-) delete mode 100644 src/assets/community_index_global_resources.json diff --git a/package.json b/package.json index d94904d85c..502f86bd0e 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "https://overpass.openstreetmap.ru/cgi/interpreter": "Broken as of 2024-09-05, might be a glitch" }, "country_coder_host": "https://countrycoder.mapcomplete.org", + "community_index_host": "https://data.mapcomplete.org/community-index/", "nominatimEndpoint": "https://geocoding.geofabrik.de/b75350b1cfc34962ac49824fe5b582dc/", "#photonEndpoint": "`api/` or `reverse/` will be appended by the code", "photonEndpoint": "https://photon.komoot.io/", diff --git a/scripts/downloadCommunityIndex.ts b/scripts/downloadCommunityIndex.ts index edc0c9c20d..f8702d4151 100644 --- a/scripts/downloadCommunityIndex.ts +++ b/scripts/downloadCommunityIndex.ts @@ -1,5 +1,5 @@ import Script from "./Script" -import { CommunityResource } from "../src/Logic/Web/CommunityIndex" +import { CommunityResource } from "../src/Logic/Web/CommunityIndex"downlaodComm import { Utils } from "../src/Utils" import { FeatureCollection, MultiPolygon, Polygon } from "geojson" import { existsSync, mkdirSync, writeFileSync } from "fs" @@ -41,6 +41,10 @@ class DownloadCommunityIndex extends Script { private static stripResourcesObj(resources: Readonly>>) { const stripped: Record = {} for (const k in resources) { + if(k === "twitter" || k === "facebook" || k === "x"){ + // These channels are toxic nowadays - we simply omit them + continue + } stripped[k] = DownloadCommunityIndex.stripResource(resources[k]) } return stripped diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index 3f6f5c9431..1831b894b6 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -134,6 +134,8 @@ export default class Constants { public static readonly mapillary_client_token_v4 = Constants.config.api_keys.mapillary_v4 public static defaultOverpassUrls = Constants.config.default_overpass_urls public static countryCoderEndpoint: string = Constants.config.country_coder_host + public static communityIndexHost: string = Constants.config.community_index_host + public static osmAuthConfig: AuthConfig = Constants.config.oauth_credentials public static nominatimEndpoint: string = Constants.config.nominatimEndpoint public static photonEndpoint: string = Constants.config.photonEndpoint diff --git a/src/UI/Base/Popup.svelte b/src/UI/Base/Popup.svelte index 728d92448e..b644bcbae4 100644 --- a/src/UI/Base/Popup.svelte +++ b/src/UI/Base/Popup.svelte @@ -9,7 +9,7 @@ export let fullscreen: boolean = false export let bodyPadding = "p-4 md:p-5 " export let shown: UIEventSource - export let dismissable = false + export let dismissable = true /** * Default: 50 */ diff --git a/src/UI/BigComponents/CommunityIndexView.svelte b/src/UI/BigComponents/CommunityIndexView.svelte index d1dfc3659d..a76450e6b8 100644 --- a/src/UI/BigComponents/CommunityIndexView.svelte +++ b/src/UI/BigComponents/CommunityIndexView.svelte @@ -2,7 +2,6 @@ import { Store, UIEventSource } from "../../Logic/UIEventSource" import { Tiles } from "../../Models/TileRange" import { Utils } from "../../Utils" - import global_community from "../../assets/community_index_global_resources.json" import ContactLink from "./ContactLink.svelte" import { GeoOperations } from "../../Logic/GeoOperations" import Translations from "../i18n/Translations" @@ -10,11 +9,13 @@ import type { FeatureCollection, Polygon } from "geojson" import type { CommunityResource } from "../../Logic/Web/CommunityIndex" import Tr from "../Base/Tr.svelte" + import Constants from "../../Models/Constants" + import Loading from "../Base/Loading.svelte" export let location: Store<{ lat: number; lon: number }> const tileToFetch: Store = location.mapD((l) => { const t = Tiles.embedded_tile(l.lat, l.lon, 6) - return `https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/community_index/tile_${t.z}_${t.x}_${t.y}.geojson` + return Utils.SubstituteKeys(Constants.communityIndexHost + "tile_{z}_{x}_{y}.geojson", t) }) const t = Translations.t.communityIndex const resources = new UIEventSource< @@ -32,17 +33,29 @@ resources.setData(data.features) }) - const filteredResources = resources.map( + const filteredResources = resources.mapD( (features) => features.filter((f) => GeoOperations.inside([location.data.lon, location.data.lat], f)), [location] ) + + const globalResources: UIEventSource> = UIEventSource.FromPromise(Utils.downloadJsonCached>( + Constants.communityIndexHost + "/global.json", 24 * 60 * 60 * 1000 + ))
- {#each $filteredResources as feature} - - {/each} - + {#if $filteredResources === undefined} + + {:else} + {#each $filteredResources as feature} + + {/each} + {/if} + {#if $globalResources === undefined} + + {:else} + + {/if}
diff --git a/src/UI/BigComponents/ContactLink.svelte b/src/UI/BigComponents/ContactLink.svelte index 123d5f82a7..f1c8de96a4 100644 --- a/src/UI/BigComponents/ContactLink.svelte +++ b/src/UI/BigComponents/ContactLink.svelte @@ -7,6 +7,7 @@ import { TypedTranslation } from "../i18n/Translation" import Tr from "../Base/Tr.svelte" import type { CommunityResource } from "../../Logic/Web/CommunityIndex" + import Constants from "../../Models/Constants" const availableTranslationTyped: TypedTranslation<{ native: string }> = Translations.t.communityIndex.available @@ -16,7 +17,6 @@ export let country: { resources: Record; nameEn: string } let resources: CommunityResource[] = [] $: resources = Array.from(Object.values(country?.resources ?? {})) - const language = Locale.language @@ -28,16 +28,16 @@