From c98891d4bd67556b2e327fe239d09f92d4dcc56a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 15 Jan 2024 15:23:40 +0100 Subject: [PATCH 01/57] Server: add first docs and experiments --- Docs/SettingUpPSQL.md | 11 ++++++++++ src/UI/Test.svelte | 47 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 Docs/SettingUpPSQL.md diff --git a/Docs/SettingUpPSQL.md b/Docs/SettingUpPSQL.md new file mode 100644 index 000000000..67c2ddeb2 --- /dev/null +++ b/Docs/SettingUpPSQL.md @@ -0,0 +1,11 @@ +sudo docker run --name some-postgis -e POSTGRES_PASSWORD=none -e POSTGRES_USER=user -d -p 5444:5432 -v /home/pietervdvn/data/pgsql/:/var/lib/postgresql/data postgis/postgis + +-> Via PGAdmin een database maken en: +1) Postgis activeren (rechtsklikken > Create > extension) +2) HStore activeren + +Installeer osm2pgsql (hint: compile from source is painless) + +pg_tileserv kan hier gedownload worden: https://github.com/CrunchyData/pg_tileserv + +DATABASE_URL=postgresql://user:none@localhost:5444/osm-poi ./pg_tileserv diff --git a/src/UI/Test.svelte b/src/UI/Test.svelte index c4d5d1823..91b4aa306 100644 --- a/src/UI/Test.svelte +++ b/src/UI/Test.svelte @@ -1,7 +1,50 @@ - +
+ +
From fb088059a5bddde88d0e7b6212720b4a0419e7fe Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 17 Jan 2024 02:30:13 +0100 Subject: [PATCH 02/57] Layerserver: improve docs, add stub of script that generates configuration file --- Docs/SettingUpPSQL.md | 43 +++++++- scripts/osm2pgsql/generateLayerFile.ts | 136 +++++++++++++++++++++++++ src/UI/Test.svelte | 20 ++++ 3 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 scripts/osm2pgsql/generateLayerFile.ts diff --git a/Docs/SettingUpPSQL.md b/Docs/SettingUpPSQL.md index 67c2ddeb2..9c9e5e6f3 100644 --- a/Docs/SettingUpPSQL.md +++ b/Docs/SettingUpPSQL.md @@ -1,11 +1,44 @@ -sudo docker run --name some-postgis -e POSTGRES_PASSWORD=none -e POSTGRES_USER=user -d -p 5444:5432 -v /home/pietervdvn/data/pgsql/:/var/lib/postgresql/data postgis/postgis +# Setting up a synced OSM-server for quick layer access --> Via PGAdmin een database maken en: -1) Postgis activeren (rechtsklikken > Create > extension) -2) HStore activeren +## Setting up the SQL-server: -Installeer osm2pgsql (hint: compile from source is painless) +`sudo docker run --name some-postgis -e POSTGRES_PASSWORD=password -e POSTGRES_USER=user -d -p 5444:5432 -v /home/pietervdvn/data/pgsql/:/var/lib/postgresql/data postgis/postgis` + +Then, connect to this databank with PGAdmin, create a database within it. +Then activate following extensions for this database (right click > Create > Extension): + +- Postgis activeren (rechtsklikken > Create > extension) +- HStore activeren + +Install osm2pgsql (hint: compile from source is painless) pg_tileserv kan hier gedownload worden: https://github.com/CrunchyData/pg_tileserv DATABASE_URL=postgresql://user:none@localhost:5444/osm-poi ./pg_tileserv + +## Create export scripts for every layer + +Use scripts/osm2pgsl + +## Importing data + +To seed the database: + +```` +osm2pgsql -O flex -S drinking_water.lua -s --flat-nodes=import-help-file -d postgresql://user:none@localhost:5444/osm-poi andorra-latest.osm.pbf +```` + +## Deploying a tile server + +```` +export DATABASE_URL=postgresql://user:none@localhost:5444/osm-poi +./pg_tileserv +```` + +Tiles are available at: +```` +map.addSource("drinking_water", { +"type": "vector", +"tiles": ["http://127.0.0.2:7800/public.drinking_water/{z}/{x}/{y}.pbf"] // http://127.0.0.2:7800/public.drinking_water.json", +}) +```` diff --git a/scripts/osm2pgsql/generateLayerFile.ts b/scripts/osm2pgsql/generateLayerFile.ts new file mode 100644 index 000000000..670918a89 --- /dev/null +++ b/scripts/osm2pgsql/generateLayerFile.ts @@ -0,0 +1,136 @@ +import LayerConfig from "../../src/Models/ThemeConfig/LayerConfig" +import { TagsFilter } from "../../src/Logic/Tags/TagsFilter" +import { Tag } from "../../src/Logic/Tags/Tag" +import { And } from "../../src/Logic/Tags/And" +import Script from "../Script" +import { AllSharedLayers } from "../../src/Customizations/AllSharedLayers" +import fs from "fs" +import { Or } from "../../src/Logic/Tags/Or" +import { RegexTag } from "../../src/Logic/Tags/RegexTag" + +class LuaSnippets{ + /** + * The main piece of code that calls `process_poi` + */ + static tail = [ + "function osm2pgsql.process_node(object)", + " process_poi(object, object:as_point())", + "end", + "", + "function osm2pgsql.process_way(object)", + " if object.is_closed then", + " process_poi(object, object:as_polygon():centroid())", + " end", + "end", + ""].join("\n") + + public static combine(calls: string[]): string{ + return [ + `function process_poi(object, geom)`, + ...calls.map(c => " "+c+"(object, geom)"), + `end`, + ].join("\n") + } +} +class GenerateLayerLua { + private readonly _layer: LayerConfig + + constructor(layer: LayerConfig) { + this._layer = layer + } + public functionName(){ + const l = this._layer + return `process_poi_${l.id}` + } + + public generateFunction(): string { + const l = this._layer + return [ + `local pois_${l.id} = osm2pgsql.define_table({`, + ` name = '${l.id}',`, + " ids = { type = 'any', type_column = 'osm_type', id_column = 'osm_id' },", + " columns = {", + " { column = 'tags', type = 'jsonb' },", + " { column = 'geom', type = 'point', not_null = true },", + " }" + + "})", + "", + "", + `function ${this.functionName()}(object, geom)`, + " local matches_filter = " + this.toLuaFilter(l.source.osmTags), + " if( not matches_filter) then", + " return", + " end", + " local a = {", + " geom = geom,", + " tags = object.tags", + " }", + " ", + ` pois_${l.id}:insert(a)`, + "end", + "" + ].join("\n") + } + + + private toLuaFilter(tag: TagsFilter, useParens: boolean = false): string { + if (tag instanceof Tag) { + return `object.tags["${tag.key}"] == "${tag.value}"` + } + if (tag instanceof And) { + const expr = tag.and.map(t => this.toLuaFilter(t, true)).join(" and ") + if (useParens) { + return "(" + expr + ")" + } + return expr + } + if (tag instanceof Or) { + const expr = tag.or.map(t => this.toLuaFilter(t, true)).join(" or ") + if (useParens) { + return "(" + expr + ")" + } + return expr + } + if (tag instanceof RegexTag) { + if(typeof tag.value === "string" && tag.invert){ + return `object.tags["${tag.key}"] ~= "${tag.value}"` + } + + let expr = `not string.find(object.tags["${tag.key}"], "${tag.value}")` + if (!tag.invert) { + expr = "not " + expr + } + if (useParens) { + expr = "(" + expr + ")" + } + return expr + } + let msg = "Could not handle" + tag.asHumanString(false, false, {}) + console.error(msg) + throw msg + } +} + +class GenerateLayerFile extends Script { + constructor() { + super("Generates a .lua-file to use with osm2pgsql") + } + + async main(args: string[]) { + let dw = AllSharedLayers.sharedLayers.get("drinking_water") + let t = AllSharedLayers.sharedLayers.get("toilet") + + const generators = [dw, t].map(l => new GenerateLayerLua(l)) + + const script = [ + ...generators.map(g => g.generateFunction()), + LuaSnippets.combine(generators.map(g => g.functionName())), + LuaSnippets.tail + ].join("\n\n\n") + const path = "build_db.lua" + fs.writeFileSync(path,script, "utf-8") + console.log("Written", path) + } +} + +new GenerateLayerFile().run() diff --git a/src/UI/Test.svelte b/src/UI/Test.svelte index 91b4aa306..5e13292b1 100644 --- a/src/UI/Test.svelte +++ b/src/UI/Test.svelte @@ -36,6 +36,26 @@ }, ) + map.addSource("toilet", { + "type": "vector", + "tiles": ["http://127.0.0.2:7800/public.toilet/{z}/{x}/{y}.pbf"] // http://127.0.0.2:7800/public.drinking_water.json", + }) + + map.addLayer( + { + "id": "toilet_layer", + "type": "circle", + "source": "toilet", + "source-layer": "public.toilet", + "paint": { + "circle-radius": 5, + "circle-color": "#0000ff", + "circle-stroke-width": 2, + "circle-stroke-color": "#000000", + }, + }, + ) + map.on('click', 'drinking_water_layer', (e) => { // Copy coordinates array. console.log(e) From 35228daa8fa2511ecb0407111431a4390006f782 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 19 Jan 2024 02:00:05 +0100 Subject: [PATCH 03/57] Remove obsolete code --- src/Logic/FeatureSource/Sources/GeoJsonSource.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Logic/FeatureSource/Sources/GeoJsonSource.ts b/src/Logic/FeatureSource/Sources/GeoJsonSource.ts index 998f88310..22bd6979a 100644 --- a/src/Logic/FeatureSource/Sources/GeoJsonSource.ts +++ b/src/Logic/FeatureSource/Sources/GeoJsonSource.ts @@ -103,7 +103,6 @@ export default class GeoJsonSource implements FeatureSource { const time = new Date() const newFeatures: Feature[] = [] let i = 0 - let skipped = 0 for (const feature of json.features) { if (feature.geometry.type === "Point") { // See https://github.com/maproulette/maproulette-backend/issues/242 @@ -131,16 +130,9 @@ export default class GeoJsonSource implements FeatureSource { i++ } if (self.seenids.has(props.id)) { - skipped++ continue } self.seenids.add(props.id) - - let freshness: Date = time - if (feature.properties["_last_edit:timestamp"] !== undefined) { - freshness = new Date(props["_last_edit:timestamp"]) - } - newFeatures.push(feature) } From ef2f1487c6dee52f52e81319b7f9f3bf9094bcce Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 22 Jan 2024 01:42:05 +0100 Subject: [PATCH 04/57] LayerServer: first version which can use a local MVT-server --- Docs/SettingUpPSQL.md | 4 +- assets/layers/shower/shower.json | 2 +- assets/layers/toilet/toilet.json | 2 +- assets/themes/toilets/toilets.json | 2 +- package-lock.json | 386 ++++++++++++++++++ package.json | 4 + scripts/osm2pgsql/generateLayerFile.ts | 16 +- scripts/osm2pgsql/tilecountServer.ts | 44 ++ .../FeatureSource/Sources/LayoutSource.ts | 22 +- src/Logic/FeatureSource/Sources/MvtSource.ts | 378 +++++++++++++++++ .../DynamicMvtTileSource.ts | 39 ++ .../TiledFeatureSource/DynamicTileSource.ts | 9 +- .../LocalStorageFeatureSource.ts | 4 +- src/Models/Constants.ts | 5 + .../ThemeConfig/Conversion/PrepareTheme.ts | 1 - src/UI/Map/ShowDataLayer.ts | 1 + src/UI/Test.svelte | 172 +++++--- 17 files changed, 1009 insertions(+), 82 deletions(-) create mode 100644 scripts/osm2pgsql/tilecountServer.ts create mode 100644 src/Logic/FeatureSource/Sources/MvtSource.ts create mode 100644 src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts diff --git a/Docs/SettingUpPSQL.md b/Docs/SettingUpPSQL.md index 9c9e5e6f3..c83ddb662 100644 --- a/Docs/SettingUpPSQL.md +++ b/Docs/SettingUpPSQL.md @@ -25,13 +25,13 @@ Use scripts/osm2pgsl To seed the database: ```` -osm2pgsql -O flex -S drinking_water.lua -s --flat-nodes=import-help-file -d postgresql://user:none@localhost:5444/osm-poi andorra-latest.osm.pbf +osm2pgsql -O flex -E 4326 -S build_db.lua -s --flat-nodes=import-help-file -d postgresql://user:password@localhost:5444/osm-poi andorra-latest.osm.pbf ```` ## Deploying a tile server ```` -export DATABASE_URL=postgresql://user:none@localhost:5444/osm-poi +export DATABASE_URL=postgresql://user:password@localhost:5444/osm-poi ./pg_tileserv ```` diff --git a/assets/layers/shower/shower.json b/assets/layers/shower/shower.json index 759004606..50fb25ed9 100644 --- a/assets/layers/shower/shower.json +++ b/assets/layers/shower/shower.json @@ -17,7 +17,7 @@ "source": { "osmTags": "amenity=shower" }, - "minzoom": 12, + "minzoom": 8, "title": { "render": { "en": "Shower", diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index 068ba43aa..2d58241b6 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -26,7 +26,7 @@ "source": { "osmTags": "amenity=toilets" }, - "minzoom": 12, + "minzoom": 9, "title": { "render": { "en": "Toilet", diff --git a/assets/themes/toilets/toilets.json b/assets/themes/toilets/toilets.json index 3879b0385..87afd0068 100644 --- a/assets/themes/toilets/toilets.json +++ b/assets/themes/toilets/toilets.json @@ -44,4 +44,4 @@ "shower" ], "widenFactor": 3 -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 92b0f86d2..2fa881903 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@turf/length": "^6.5.0", "@turf/turf": "^6.5.0", "@types/dompurify": "^3.0.2", + "@types/pg": "^8.10.9", "@types/qrcode-generator": "^1.0.6", "@types/showdown": "^2.0.0", "chart.js": "^3.8.0", @@ -50,6 +51,8 @@ "osmtogeojson": "^3.0.0-beta.5", "panzoom": "^9.4.3", "papaparse": "^5.3.1", + "pbf": "^3.2.1", + "pg": "^8.11.3", "pic4carto": "^2.1.15", "prompt-sync": "^4.2.0", "qrcode-generator": "^1.4.4", @@ -4223,6 +4226,68 @@ "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.3.tgz", "integrity": "sha512-hw6bDMjvm+QTvEC+pRLpnTknQXoPu8Fnf+A+zX9HB7j/7RfYajFSbdukabo3adPwvvEHhIMafQl0R0Tpej7clQ==" }, + "node_modules/@types/pg": { + "version": "8.10.9", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.10.9.tgz", + "integrity": "sha512-UksbANNE/f8w0wOMxVKKIrLCbEMV+oM1uKejmwXr39olg4xqcfBDbXxObJAt6XxHbDa4XTKOlUEcEltXDX+XLQ==", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, + "node_modules/@types/pg/node_modules/pg-types": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.1.tgz", + "integrity": "sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==", + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.0.1", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/pg/node_modules/postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/pg/node_modules/postgres-date": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.0.1.tgz", + "integrity": "sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "engines": { + "node": ">=12" + } + }, "node_modules/@types/prompt-sync": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.2.0.tgz", @@ -5277,6 +5342,14 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/bytewise": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", @@ -9606,6 +9679,11 @@ "node": ">= 0.4" } }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -9845,6 +9923,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "node_modules/panzoom": { "version": "9.4.3", "resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.3.tgz", @@ -9954,6 +10037,97 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, + "node_modules/pg": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", + "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.6.2", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-pool": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", + "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/pic4carto": { "version": "2.1.15", "resolved": "https://registry.npmjs.org/pic4carto/-/pic4carto-2.1.15.tgz", @@ -10138,6 +10312,46 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-range": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.3.tgz", + "integrity": "sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==" + }, "node_modules/potpack": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", @@ -11461,6 +11675,14 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -16835,6 +17057,55 @@ "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.3.tgz", "integrity": "sha512-hw6bDMjvm+QTvEC+pRLpnTknQXoPu8Fnf+A+zX9HB7j/7RfYajFSbdukabo3adPwvvEHhIMafQl0R0Tpej7clQ==" }, + "@types/pg": { + "version": "8.10.9", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.10.9.tgz", + "integrity": "sha512-UksbANNE/f8w0wOMxVKKIrLCbEMV+oM1uKejmwXr39olg4xqcfBDbXxObJAt6XxHbDa4XTKOlUEcEltXDX+XLQ==", + "requires": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + }, + "dependencies": { + "pg-types": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.1.tgz", + "integrity": "sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==", + "requires": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.0.1", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + } + }, + "postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==" + }, + "postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "requires": { + "obuf": "~1.1.2" + } + }, + "postgres-date": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.0.1.tgz", + "integrity": "sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==" + }, + "postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==" + } + } + }, "@types/prompt-sync": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.2.0.tgz", @@ -17608,6 +17879,11 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "bytewise": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", @@ -20874,6 +21150,11 @@ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -21049,6 +21330,11 @@ "p-limit": "^3.0.2" } }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "panzoom": { "version": "9.4.3", "resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.3.tgz", @@ -21134,6 +21420,73 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, + "pg": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", + "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-cloudflare": "^1.1.1", + "pg-connection-string": "^2.6.2", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==" + }, + "pg-pool": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", + "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "requires": {} + }, + "pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + } + }, "pic4carto": { "version": "2.1.15", "resolved": "https://registry.npmjs.org/pic4carto/-/pic4carto-2.1.15.tgz", @@ -21238,6 +21591,34 @@ "util-deprecate": "^1.0.2" } }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "postgres-range": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.3.tgz", + "integrity": "sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==" + }, "potpack": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz", @@ -22195,6 +22576,11 @@ } } }, + "split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, "sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", diff --git a/package.json b/package.json index 71f0b38be..5f961226b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg", "url": "https://www.openstreetmap.org" }, + "mvt_layer_server": "http://127.0.0.1:7800/public.{layer}/{z}/{x}/{y}.pbf", "disabled:oauth_credentials": { "##": "DEV", "#": "This client-id is registered by 'MapComplete' on https://master.apis.dev.openstreetmap.org/", @@ -118,6 +119,7 @@ "@turf/length": "^6.5.0", "@turf/turf": "^6.5.0", "@types/dompurify": "^3.0.2", + "@types/pg": "^8.10.9", "@types/qrcode-generator": "^1.0.6", "@types/showdown": "^2.0.0", "chart.js": "^3.8.0", @@ -148,6 +150,8 @@ "osmtogeojson": "^3.0.0-beta.5", "panzoom": "^9.4.3", "papaparse": "^5.3.1", + "pbf": "^3.2.1", + "pg": "^8.11.3", "pic4carto": "^2.1.15", "prompt-sync": "^4.2.0", "qrcode-generator": "^1.4.4", diff --git a/scripts/osm2pgsql/generateLayerFile.ts b/scripts/osm2pgsql/generateLayerFile.ts index 670918a89..5867b8d9f 100644 --- a/scripts/osm2pgsql/generateLayerFile.ts +++ b/scripts/osm2pgsql/generateLayerFile.ts @@ -7,6 +7,7 @@ import { AllSharedLayers } from "../../src/Customizations/AllSharedLayers" import fs from "fs" import { Or } from "../../src/Logic/Tags/Or" import { RegexTag } from "../../src/Logic/Tags/RegexTag" +import { Utils } from "../../src/Utils" class LuaSnippets{ /** @@ -40,18 +41,24 @@ class GenerateLayerLua { } public functionName(){ const l = this._layer + if(!l.source?.osmTags){ + return undefined + } return `process_poi_${l.id}` } public generateFunction(): string { const l = this._layer + if(!l.source?.osmTags){ + return undefined + } return [ `local pois_${l.id} = osm2pgsql.define_table({`, ` name = '${l.id}',`, " ids = { type = 'any', type_column = 'osm_type', id_column = 'osm_id' },", " columns = {", " { column = 'tags', type = 'jsonb' },", - " { column = 'geom', type = 'point', not_null = true },", + " { column = 'geom', type = 'point', projection = 4326, not_null = true },", " }" + "})", "", @@ -117,14 +124,13 @@ class GenerateLayerFile extends Script { } async main(args: string[]) { - let dw = AllSharedLayers.sharedLayers.get("drinking_water") - let t = AllSharedLayers.sharedLayers.get("toilet") + const layerNames = Array.from(AllSharedLayers.sharedLayers.values()) - const generators = [dw, t].map(l => new GenerateLayerLua(l)) + const generators = layerNames.map(l => new GenerateLayerLua(l)) const script = [ ...generators.map(g => g.generateFunction()), - LuaSnippets.combine(generators.map(g => g.functionName())), + LuaSnippets.combine(Utils.NoNull(generators.map(g => g.functionName()))), LuaSnippets.tail ].join("\n\n\n") const path = "build_db.lua" diff --git a/scripts/osm2pgsql/tilecountServer.ts b/scripts/osm2pgsql/tilecountServer.ts new file mode 100644 index 000000000..5c33c72b7 --- /dev/null +++ b/scripts/osm2pgsql/tilecountServer.ts @@ -0,0 +1,44 @@ +import { BBox } from "../../src/Logic/BBox" +import { Client } from "pg" + +/** + * Connects with a Postgis database, gives back how much items there are within the given BBOX + */ +export default class TilecountServer { + private readonly _client: Client + private isConnected = false + + constructor(connectionString: string) { + this._client = new Client(connectionString) + } + + async getCount(layer: string, bbox: BBox = undefined): Promise { + if (!this.isConnected) { + await this._client.connect() + this.isConnected = true + } + + let query = "SELECT COUNT(*) FROM " + layer + + if(bbox){ + query += ` WHERE ST_MakeEnvelope (${bbox.minLon}, ${bbox.minLat}, ${bbox.maxLon}, ${bbox.maxLat}, 4326) ~ geom` + } +console.log(query) + const result = await this._client.query(query) + return result.rows[0].count + } + + disconnect() { + this._client.end() + } +} + +const tcs = new TilecountServer("postgresql://user:none@localhost:5444/osm-poi") +console.log(">>>", await tcs.getCount("drinking_water", new BBox([ + [1.5052013991654007, + 42.57480750272123, + ], [ + 1.6663677350703097, + 42.499856652770745, + ]]))) +tcs.disconnect() diff --git a/src/Logic/FeatureSource/Sources/LayoutSource.ts b/src/Logic/FeatureSource/Sources/LayoutSource.ts index fa430f561..a46d81384 100644 --- a/src/Logic/FeatureSource/Sources/LayoutSource.ts +++ b/src/Logic/FeatureSource/Sources/LayoutSource.ts @@ -11,6 +11,9 @@ import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSo import { BBox } from "../../BBox" import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource" import FullNodeDatabaseSource from "../TiledFeatureSource/FullNodeDatabaseSource" +import { Features } from "@rgossiaux/svelte-headlessui/types" +import DynamicMvtileSource from "../TiledFeatureSource/DynamicMvtTileSource" +import { layouts } from "chart.js" /** * This source will fetch the needed data from various sources for the given layout. @@ -44,14 +47,18 @@ export default class LayoutSource extends FeatureSourceMerger { maxAge: l.maxAgeOfCache, }) ) + console.log(mapProperties) + const mvtSources: FeatureSource[] = osmLayers.map(l => LayoutSource.setupMvtSource(l, mapProperties, isDisplayed(l.id))) + + /* const overpassSource = LayoutSource.setupOverpass( backend, osmLayers, bounds, zoom, featureSwitches - ) + )//*/ const osmApiSource = LayoutSource.setupOsmApiSource( osmLayers, @@ -61,22 +68,27 @@ export default class LayoutSource extends FeatureSourceMerger { featureSwitches, fullNodeDatabaseSource ) + const geojsonSources: FeatureSource[] = geojsonlayers.map((l) => LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id)) ) - super(overpassSource, osmApiSource, ...geojsonSources, ...fromCache) + + super(osmApiSource, ...geojsonSources, ...fromCache, ...mvtSources) const self = this function setIsLoading() { - const loading = overpassSource?.runningQuery?.data || osmApiSource?.isRunning?.data - self._isLoading.setData(loading) + // const loading = overpassSource?.runningQuery?.data || osmApiSource?.isRunning?.data + // self._isLoading.setData(loading) } - overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading()) + // overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading()) osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading()) } + private static setupMvtSource(layer: LayerConfig, mapProperties: { zoom: Store; bounds: Store }, isActive?: Store): FeatureSource{ + return new DynamicMvtileSource(layer, mapProperties, { isActive }) + } private static setupGeojsonSource( layer: LayerConfig, mapProperties: { zoom: Store; bounds: Store }, diff --git a/src/Logic/FeatureSource/Sources/MvtSource.ts b/src/Logic/FeatureSource/Sources/MvtSource.ts new file mode 100644 index 000000000..8c93eee9c --- /dev/null +++ b/src/Logic/FeatureSource/Sources/MvtSource.ts @@ -0,0 +1,378 @@ +import { Feature, Geometry } from "geojson" +import { Store, UIEventSource } from "../../UIEventSource" +import { FeatureSource } from "../FeatureSource" +import Pbf from "pbf" +import * as pbfCompile from "pbf/compile" +import * as PbfSchema from "protocol-buffers-schema" + +type Coords = [number, number][] + +class MvtFeatureBuilder { + private static readonly geom_types = ["Unknown", "Point", "LineString", "Polygon"] as const + private readonly _size: number + private readonly _x0: number + private readonly _y0: number + + constructor(extent: number, x: number, y: number, z: number) { + this._size = extent * Math.pow(2, z) + this._x0 = extent * x + this._y0 = extent * y + } + + public toGeoJson(geometry, typeIndex, properties): Feature { + let coords: [number, number] | Coords | Coords[] = this.encodeGeometry(geometry) + switch (typeIndex) { + case 1: + const points = [] + for (let i = 0; i < coords.length; i++) { + points[i] = coords[i][0] + } + coords = points + this.project(coords) + break + + case 2: + for (let i = 0; i < coords.length; i++) { + this.project(coords[i]) + } + break + + case 3: + let classified = this.classifyRings(coords) + for (let i = 0; i < coords.length; i++) { + for (let j = 0; j < coords[i].length; j++) { + this.project(classified[i][j]) + } + } + break + } + + let type: string = MvtFeatureBuilder.geom_types[typeIndex] + if (coords.length === 1) { + coords = coords[0] + } else { + type = "Multi" + type + } + + return { + type: "Feature", + geometry: { + type: type, + coordinates: coords, + }, + properties, + } + } + + private encodeGeometry(geometry: number[]) { + let cX = 0 + let cY = 0 + let coordss: Coords[] = [] + let currentRing: Coords = [] + for (let i = 0; i < geometry.length; i++) { + let commandInteger = geometry[i] + let commandId = commandInteger & 0x7 + let commandCount = commandInteger >> 3 + /* + Command Id Parameters Parameter Count + MoveTo 1 dX, dY 2 + LineTo 2 dX, dY 2 + ClosePath 7 No parameters 0 + */ + if (commandId === 1) { + // MoveTo means: we start a new ring + if (currentRing.length !== 0) { + coordss.push(currentRing) + currentRing = [] + } + } + if (commandId === 1 || commandId === 2){ + for (let j = 0; j < commandCount; j++) { + const dx = geometry[i + j * 2 + 1] + cX += ((dx >> 1) ^ (-(dx & 1))) + const dy = geometry[i + j * 2 + 2] + cY += ((dy >> 1) ^ (-(dy & 1))) + currentRing.push([cX, cY]) + } + i = commandCount * 2 + } + if(commandId === 7){ + currentRing.push([...currentRing[0]]) + } + + } + if (currentRing.length > 0) { + coordss.push(currentRing) + } + return coordss + } + + private signedArea(ring: Coords): number { + let sum = 0 + const len = ring.length + // J is basically (i - 1) % len + let j = len - 1 + let p1 + let p2 + for (let i = 0; i < len; i++) { + p1 = ring[i] + p2 = ring[j] + sum += (p2.x - p1.x) * (p1.y + p2.y) + j = i + } + return sum + } + + private classifyRings(rings: Coords[]): Coords[][] { + const len = rings.length + + if (len <= 1) return [rings] + + const polygons = [] + let polygon + // CounterClockWise + let ccw: boolean + + for (let i = 0; i < len; i++) { + const area = this.signedArea(rings[i]) + if (area === 0) continue + + if (ccw === undefined) { + ccw = area < 0 + } + if (ccw === (area < 0)) { + if (polygon) { + polygons.push(polygon) + } + polygon = [rings[i]] + + } else { + polygon.push(rings[i]) + } + } + if (polygon) { + polygons.push(polygon) + } + + return polygons + } + + /** + * Inline replacement of the location by projecting + * @param line + * @private + */ + private project(line: [number, number][]) { + const y0 = this._y0 + const x0 = this._x0 + const size = this._size + for (let i = 0; i < line.length; i++) { + let p = line[i] + let y2 = 180 - (p[1] + y0) * 360 / size + line[i] = [ + (p[0] + x0) * 360 / size - 180, + 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90, + ] + } + } +} + +export default class MvtSource implements FeatureSource { + + private static readonly schemaSpec = ` + package vector_tile; + +option optimize_for = LITE_RUNTIME; + +message Tile { + + // GeomType is described in section 4.3.4 of the specification + enum GeomType { + UNKNOWN = 0; + POINT = 1; + LINESTRING = 2; + POLYGON = 3; + } + + // Variant type encoding + // The use of values is described in section 4.1 of the specification + message Value { + // Exactly one of these values must be present in a valid message + optional string string_value = 1; + optional float float_value = 2; + optional double double_value = 3; + optional int64 int_value = 4; + optional uint64 uint_value = 5; + optional sint64 sint_value = 6; + optional bool bool_value = 7; + + extensions 8 to max; + } + + // Features are described in section 4.2 of the specification + message Feature { + optional uint64 id = 1 [ default = 0 ]; + + // Tags of this feature are encoded as repeated pairs of + // integers. + // A detailed description of tags is located in sections + // 4.2 and 4.4 of the specification + repeated uint32 tags = 2 [ packed = true ]; + + // The type of geometry stored in this feature. + optional GeomType type = 3 [ default = UNKNOWN ]; + + // Contains a stream of commands and parameters (vertices). + // A detailed description on geometry encoding is located in + // section 4.3 of the specification. + repeated uint32 geometry = 4 [ packed = true ]; + } + + // Layers are described in section 4.1 of the specification + message Layer { + // Any compliant implementation must first read the version + // number encoded in this message and choose the correct + // implementation for this version number before proceeding to + // decode other parts of this message. + required uint32 version = 15 [ default = 1 ]; + + required string name = 1; + + // The actual features in this tile. + repeated Feature features = 2; + + // Dictionary encoding for keys + repeated string keys = 3; + + // Dictionary encoding for values + repeated Value values = 4; + + // Although this is an "optional" field it is required by the specification. + // See https://github.com/mapbox/vector-tile-spec/issues/47 + optional uint32 extent = 5 [ default = 4096 ]; + + extensions 16 to max; + } + + repeated Layer layers = 3; + + extensions 16 to 8191; +} +` + private static readonly tile_schema = pbfCompile(PbfSchema.parse(MvtSource.schemaSpec)).Tile + + + private readonly _url: string + private readonly _layerName: string + private readonly _features: UIEventSource[]> = new UIEventSource[]>([]) + public readonly features: Store[]> = this._features + private readonly x: number + private readonly y: number + private readonly z: number + + constructor(url: string, x: number, y: number, z: number, layerName?: string) { + this._url = url + this._layerName = layerName + this.x = x + this.y = y + this.z = z + this.downloadSync() + } + + private getValue(v: { + // Exactly one of these values must be present in a valid message + string_value?: string, + float_value?: number, + double_value?: number, + int_value?: number, + uint_value?: number, + sint_value?: number, + bool_value?: boolean + }): string | number | undefined | boolean { + if (v.string_value !== "") { + return v.string_value + } + if (v.double_value !== 0) { + return v.double_value + } + if (v.float_value !== 0) { + return v.float_value + } + if (v.int_value !== 0) { + return v.int_value + } + if (v.uint_value !== 0) { + return v.uint_value + } + if (v.sint_value !== 0) { + return v.sint_value + } + if (v.bool_value !== false) { + return v.bool_value + } + return undefined + + } + + private downloadSync(){ + this.download().then(d => { + if(d.length === 0){ + return + } + return this._features.setData(d) + }).catch(e => {console.error(e)}) + } + private async download(): Promise { + const result = await fetch(this._url) + const buffer = await result.arrayBuffer() + const data = MvtSource.tile_schema.read(new Pbf(buffer)) + const layers = data.layers + let layer = data.layers[0] + if (layers.length > 1) { + if (!this._layerName) { + throw "Multiple layers in the downloaded tile, but no layername is given to choose from" + } + layer = layers.find(l => l.name === this._layerName) + } + if(!layer){ + return [] + } + const builder = new MvtFeatureBuilder(layer.extent, this.x, this.y, this.z) + const features: Feature[] = [] + + for (const feature of layer.features) { + const properties = this.inflateProperties(feature.tags, layer.keys, layer.values) + features.push(builder.toGeoJson(feature.geometry, feature.type, properties)) + } + + return features + } + + + private inflateProperties(tags: number[], keys: string[], values: { string_value: string }[]) { + const properties = {} + for (let i = 0; i < tags.length; i += 2) { + properties[keys[tags[i]]] = this.getValue(values[tags[i + 1]]) + } + let type: string + switch (properties["osm_type"]) { + case "N": + type = "node" + break + case "W": + type = "way" + break + case "R": + type = "relation" + break + } + properties["id"] = type + "/" + properties["osm_id"] + delete properties["osm_id"] + delete properties["osm_type"] + + return properties + } + +} diff --git a/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts new file mode 100644 index 000000000..ac8713332 --- /dev/null +++ b/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts @@ -0,0 +1,39 @@ +import { Store } from "../../UIEventSource" +import DynamicTileSource from "./DynamicTileSource" +import { Utils } from "../../../Utils" +import { BBox } from "../../BBox" +import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" +import MvtSource from "../Sources/MvtSource" +import { Tiles } from "../../../Models/TileRange" +import Constants from "../../../Models/Constants" + +export default class DynamicMvtileSource extends DynamicTileSource { + + constructor( + layer: LayerConfig, + mapProperties: { + zoom: Store + bounds: Store + }, + options?: { + isActive?: Store + }, + ) { + super( + mapProperties.zoom, + layer.minzoom, + (zxy) => { + const [z, x, y] = Tiles.tile_from_index(zxy) + const url = Utils.SubstituteKeys(Constants.VectorTileServer, + { + z, x, y, layer: layer.id, + }) + return new MvtSource(url, x, y, z) + }, + mapProperties, + { + isActive: options?.isActive, + }, + ) + } +} diff --git a/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts index e8e69ee2a..3bb5affd9 100644 --- a/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts +++ b/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts @@ -10,9 +10,9 @@ import FeatureSourceMerger from "../Sources/FeatureSourceMerger" */ export default class DynamicTileSource extends FeatureSourceMerger { constructor( - zoomlevel: number, + zoomlevel: Store, minzoom: number, - constructSource: (tileIndex) => FeatureSource, + constructSource: (tileIndex: number) => FeatureSource, mapProperties: { bounds: Store zoom: Store @@ -34,8 +34,9 @@ export default class DynamicTileSource extends FeatureSourceMerger { if (mapProperties.zoom.data < minzoom) { return undefined } + const z = Math.round(zoomlevel.data) const tileRange = Tiles.TileRangeBetween( - zoomlevel, + z, bounds.getNorth(), bounds.getEast(), bounds.getSouth(), @@ -49,7 +50,7 @@ export default class DynamicTileSource extends FeatureSourceMerger { } const needed = Tiles.MapRange(tileRange, (x, y) => - Tiles.tile_index(zoomlevel, x, y) + Tiles.tile_index(z, x, y) ).filter((i) => !loadedTiles.has(i)) if (needed.length === 0) { return undefined diff --git a/src/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts index eac9d0859..11ce41080 100644 --- a/src/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts +++ b/src/Logic/FeatureSource/TiledFeatureSource/LocalStorageFeatureSource.ts @@ -1,5 +1,5 @@ import DynamicTileSource from "./DynamicTileSource" -import { Store } from "../../UIEventSource" +import { ImmutableStore, Store } from "../../UIEventSource" import { BBox } from "../../BBox" import TileLocalStorage from "../Actors/TileLocalStorage" import { Feature } from "geojson" @@ -27,7 +27,7 @@ export default class LocalStorageFeatureSource extends DynamicTileSource { options?.maxAge ?? 24 * 60 * 60 ) super( - zoomlevel, + new ImmutableStore(zoomlevel), layer.minzoom, (tileIndex) => new StaticFeatureSource( diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index 3551d6125..0d76b4fc5 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -154,6 +154,11 @@ export default class Constants { ] as const public static readonly defaultPinIcons: string[] = Constants._defaultPinIcons + /** + * The location that the MVT-layer is hosted. + * This is a MapLibre/MapBox vector tile server which hosts vector tiles for every (official) layer + */ + public static VectorTileServer: string | undefined = Constants.config.mvt_layer_server private static isRetina(): boolean { if (Utils.runningFromConsole) { diff --git a/src/Models/ThemeConfig/Conversion/PrepareTheme.ts b/src/Models/ThemeConfig/Conversion/PrepareTheme.ts index c5a613564..d4259c342 100644 --- a/src/Models/ThemeConfig/Conversion/PrepareTheme.ts +++ b/src/Models/ThemeConfig/Conversion/PrepareTheme.ts @@ -178,7 +178,6 @@ class AddDefaultLayers extends DesugaringStep { if (v === undefined) { const msg = `Default layer ${layerName} not found. ${state.sharedLayers.size} layers are available` if (layerName === "favourite") { - context.warn(msg) continue } context.err(msg) diff --git a/src/UI/Map/ShowDataLayer.ts b/src/UI/Map/ShowDataLayer.ts index 75c7213b0..64a3cfe94 100644 --- a/src/UI/Map/ShowDataLayer.ts +++ b/src/UI/Map/ShowDataLayer.ts @@ -303,6 +303,7 @@ class LineRenderingLayer { type: "FeatureCollection", features, }, + cluster: true, promoteId: "id", }) const linelayer = this._layername + "_line" diff --git a/src/UI/Test.svelte b/src/UI/Test.svelte index 5e13292b1..70f864f1b 100644 --- a/src/UI/Test.svelte +++ b/src/UI/Test.svelte @@ -1,70 +1,122 @@
- +
From 1d6c9ec1efc86d982071e6b6da932277e83594b4 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 23 Jan 2024 22:03:02 +0100 Subject: [PATCH 05/57] LayerServer: make all layer ids unique --- assets/themes/bag/bag.json | 22 +++---- assets/themes/cyclenodes/cyclenodes.json | 5 +- assets/themes/cyclestreets/cyclestreets.json | 2 +- assets/themes/grb/grb.json | 14 ++--- assets/themes/healthcare/healthcare.json | 2 +- .../kerbs_and_crossings.json | 3 +- assets/themes/onwheels/onwheels.json | 1 + assets/themes/openlovemap/openlovemap.json | 2 +- assets/themes/transit/transit.json | 1 + assets/themes/uk_addresses/uk_addresses.json | 2 +- assets/themes/walkingnodes/walkingnodes.json | 5 +- ...eLayerFile.ts => generateBuildDbScript.ts} | 61 +++++++++++++------ 12 files changed, 74 insertions(+), 46 deletions(-) rename scripts/osm2pgsql/{generateLayerFile.ts => generateBuildDbScript.ts} (70%) diff --git a/assets/themes/bag/bag.json b/assets/themes/bag/bag.json index 1aa324bc3..05ddd4e4a 100644 --- a/assets/themes/bag/bag.json +++ b/assets/themes/bag/bag.json @@ -45,7 +45,7 @@ "hideFromOverview": true, "layers": [ { - "id": "osm:buildings", + "id": "osm_buildings", "name": "OSM Buildings", "title": "OSM Building", "description": "Layer showing buildings that are in OpenStreetMap", @@ -147,7 +147,7 @@ ] }, { - "id": "osm:adresses", + "id": "osm_adresses", "name": "OSM Adresses", "title": "OSM Adress", "description": "Layer showing adresses that are in OpenStreetMap", @@ -185,7 +185,7 @@ ] }, { - "id": "bag:pand", + "id": "bag_pand", "name": "BAG Buildings", "title": "BAG Building", "description": { @@ -207,7 +207,7 @@ }, "minzoom": 18, "calculatedTags": [ - "_overlaps_with_buildings=overlapWith(feat)('osm:buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)", + "_overlaps_with_buildings=overlapWith(feat)('osm_buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)", "_overlaps_with=feat(get)('_overlaps_with_buildings').find(f => f.overlap > 1 /* square meter */ )", "_overlaps_with_properties=feat(get)('_overlaps_with')?.feat?.properties", "_overlap_percentage=Math.round(100 * (feat(get)('_overlaps_with')?.overlap / feat(get)('_overlaps_with_properties')['_surface:strict']))", @@ -228,7 +228,7 @@ "render": { "special": { "type": "import_way_button", - "targetLayer": "osm:buildings", + "targetLayer": "osm_buildings", "tags": "building=$_bag_obj:building; ref:bag=$_bag_obj:ref:bag; source=BAG; source:date=$_bag_obj:source:date; start_date=$_bag_obj:start_date", "text": { "*": "Upload this building to OpenStreetMap" @@ -258,7 +258,7 @@ }, { "if": "_overlaps_with!=", - "then": "{conflate_button(osm:buildings, building=$_bag_obj:building; ref:bag=$_bag_obj:ref:bag; source=BAG; source:date=$_bag_obj:source:date; start_date=$_bag_obj:start_date, Replace the geometry in OpenStreetMap, , _osm_obj:id)}" + "then": "{conflate_button(osm_buildings, building=$_bag_obj:building; ref:bag=$_bag_obj:ref:bag; source=BAG; source:date=$_bag_obj:source:date; start_date=$_bag_obj:start_date, Replace the geometry in OpenStreetMap, , _osm_obj:id)}" }, { "if": { @@ -268,7 +268,7 @@ "_bag_obj:in_construction=true" ] }, - "then": "{import_way_button(osm:buildings, building=$_bag_obj:building; construction=$_bag_obj:construction; ref:bag=$_bag_obj:ref:bag; source=BAG; source:date=$_bag_obj:source:date; start_date=$_bag_obj:start_date, Upload this building to OpenStreetMap)}" + "then": "{import_way_button(osm_buildings, building=$_bag_obj:building; construction=$_bag_obj:construction; ref:bag=$_bag_obj:ref:bag; source=BAG; source:date=$_bag_obj:source:date; start_date=$_bag_obj:start_date, Upload this building to OpenStreetMap)}" } ] }, @@ -348,7 +348,7 @@ }, { "id": "Overlapping building", - "render": "
The overlapping osm:buildings is a {_osm_obj:building} and covers {_overlap_percentage}% of the BAG building.
The BAG-building covers {_reverse_overlap_percentage}% of the OSM building

BAG geometry:

{minimap(21, id):height:10rem;border-radius:1rem;overflow:hidden}

OSM geometry:

{minimap(21,_osm_obj:id):height:10rem;border-radius:1rem;overflow:hidden}
", + "render": "
The overlapping osm_buildings is a {_osm_obj:building} and covers {_overlap_percentage}% of the BAG building.
The BAG-building covers {_reverse_overlap_percentage}% of the OSM building

BAG geometry:

{minimap(21, id):height:10rem;border-radius:1rem;overflow:hidden}

OSM geometry:

{minimap(21,_osm_obj:id):height:10rem;border-radius:1rem;overflow:hidden}
", "condition": "_overlaps_with!=" }, { @@ -386,7 +386,7 @@ ] }, { - "id": "bag:verblijfsobject", + "id": "bag_verblijfsobject", "name": "BAG Addresses", "title": "BAG Address", "description": "Address information from the BAG register", @@ -398,7 +398,7 @@ }, "minzoom": 18, "calculatedTags": [ - "_closed_osm_addr:=closest(feat)('osm:adresses').properties", + "_closed_osm_addr:=closest(feat)('osm_adresses').properties", "_bag_obj:addr:housenumber=`${feat.properties.huisnummer}${feat.properties.huisletter}${(feat.properties.toevoeging != '') ? '-' : ''}${feat.properties.toevoeging}`", "_bag_obj:ref:bag=Number(feat.properties.identificatie)", "_bag_obj:source:date=new Date().toISOString().split('T')[0]", @@ -411,7 +411,7 @@ "tagRenderings": [ { "id": "Import button", - "render": "{import_button(osm:adresses, addr:city=$woonplaats; addr:housenumber=$_bag_obj:addr:housenumber; addr:postcode=$postcode; addr:street=$openbare_ruimte; ref:bag=$_bag_obj:ref:bag; source=BAG; source:date=$_bag_obj:source:date, Upload this adress to OpenStreetMap)}", + "render": "{import_button(osm_adresses, addr:city=$woonplaats; addr:housenumber=$_bag_obj:addr:housenumber; addr:postcode=$postcode; addr:street=$openbare_ruimte; ref:bag=$_bag_obj:ref:bag; source=BAG; source:date=$_bag_obj:source:date, Upload this adress to OpenStreetMap)}", "condition": "_imported_osm_object_found=false" }, { diff --git a/assets/themes/cyclenodes/cyclenodes.json b/assets/themes/cyclenodes/cyclenodes.json index 9e2d3ba35..8fdd4cab1 100644 --- a/assets/themes/cyclenodes/cyclenodes.json +++ b/assets/themes/cyclenodes/cyclenodes.json @@ -32,7 +32,7 @@ "hideFromOverview": true, "layers": [ { - "id": "node2node", + "id": "node2node_bicycle", "name": { "en": "Node to node links", "de": "Knotenpunktverbindungen", @@ -126,7 +126,7 @@ "pointRendering": null }, { - "id": "node", + "id": "node_bicycle", "name": { "en": "Nodes", "de": "Knotenpunkte", @@ -321,6 +321,7 @@ ], "override": { "minzoom": 16, + "id": "bicycle_guidepost", "source": { "osmTags": { "and": [ diff --git a/assets/themes/cyclestreets/cyclestreets.json b/assets/themes/cyclestreets/cyclestreets.json index 7b8ce2fc8..affd01ce7 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -290,7 +290,7 @@ ] }, { - "id": "all_streets", + "id": "not_cyclestreets", "name": { "nl": "Alle straten", "en": "All streets", diff --git a/assets/themes/grb/grb.json b/assets/themes/grb/grb.json index c4c955660..c2b64f77c 100644 --- a/assets/themes/grb/grb.json +++ b/assets/themes/grb/grb.json @@ -29,7 +29,7 @@ "hideFromOverview": true, "layers": [ { - "id": "osm-buildings", + "id": "osm_buildings_no_points", "name": "All OSM-buildings", "source": { "osmTags": { @@ -296,7 +296,7 @@ "name": "GRB geometries", "title": "GRB outline", "calculatedTags": [ - "_overlaps_with_buildings=overlapWith(feat)('osm-buildings').filter(f => f.feat.properties.id.indexOf('-') < 0) ?? []", + "_overlaps_with_buildings=overlapWith(feat)('osm_buildings_no_points').filter(f => f.feat.properties.id.indexOf('-') < 0) ?? []", "_overlaps_with=get(feat)('_overlaps_with_buildings').find(f => f.overlap > 1 /* square meter */ )", "_osm_obj:source:ref=get(feat)('_overlaps_with')?.feat?.properties['source:geometry:ref']", "_osm_obj:id=get(feat)('_overlaps_with')?.feat?.properties?.id", @@ -319,7 +319,7 @@ "tagRenderings": [ { "id": "Import-button", - "render": "{import_way_button(osm-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap,,_is_part_of_building=true,1,_moveable=true)}", + "render": "{import_way_button(osm_buildings_no_points,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap,,_is_part_of_building=true,1,_moveable=true)}", "mappings": [ { "#": "Failsafe", @@ -371,7 +371,7 @@ "addr:housenumber!:={_osm_obj:addr:housenumber}" ] }, - "then": "{conflate_button(osm-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,_osm_obj:id)}" + "then": "{conflate_button(osm_buildings_no_points,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,_osm_obj:id)}" }, { "if": { @@ -380,7 +380,7 @@ "_reverse_overlap_percentage>50" ] }, - "then": "{conflate_button(osm-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Replace the geometry in OpenStreetMap,,_osm_obj:id)}" + "then": "{conflate_button(osm_buildings_no_points,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Replace the geometry in OpenStreetMap,,_osm_obj:id)}" } ] }, @@ -612,7 +612,7 @@ "builtin": "crab_address", "override": { "calculatedTags+": [ - "_embedded_in=overlapWith(feat)('osm-buildings').filter(b => /* Do not match newly created objects */ b.feat.properties.id.indexOf('-') < 0)[0]?.feat?.properties ?? {}", + "_embedded_in=overlapWith(feat)('osm_buildings_no_points').filter(b => /* Do not match newly created objects */ b.feat.properties.id.indexOf('-') < 0)[0]?.feat?.properties ?? {}", "_embedding_nr=get(feat)('_embedded_in')['addr:housenumber']+(get(feat)('_embedded_in')['addr:unit'] ?? '')", "_embedding_street=get(feat)('_embedded_in')['addr:street']", "_embedding_id=get(feat)('_embedded_in').id", @@ -709,7 +709,7 @@ "text": { "nl": "Voeg dit adres als een nieuw adrespunt toe" }, - "snap_onto_layers": "osm-buildings" + "snap_onto_layers": "osm_buildings_no_points" } }, "mappings": [ diff --git a/assets/themes/healthcare/healthcare.json b/assets/themes/healthcare/healthcare.json index 51c577f0c..e06e7f305 100644 --- a/assets/themes/healthcare/healthcare.json +++ b/assets/themes/healthcare/healthcare.json @@ -39,7 +39,7 @@ { "builtin": "shops", "override": { - "id": "medical-shops", + "id": "medical_shops", "minzoom": 13, "=filter": [ "open_now", diff --git a/assets/themes/kerbs_and_crossings/kerbs_and_crossings.json b/assets/themes/kerbs_and_crossings/kerbs_and_crossings.json index 108cfd410..ce049f837 100644 --- a/assets/themes/kerbs_and_crossings/kerbs_and_crossings.json +++ b/assets/themes/kerbs_and_crossings/kerbs_and_crossings.json @@ -46,6 +46,7 @@ { "builtin": "crossings", "override": { + "id": "crossings_no_traffic_lights", "=presets": [ { "title": { @@ -87,4 +88,4 @@ }, "kerbs" ] -} \ No newline at end of file +} diff --git a/assets/themes/onwheels/onwheels.json b/assets/themes/onwheels/onwheels.json index 116f0a487..8577f595f 100644 --- a/assets/themes/onwheels/onwheels.json +++ b/assets/themes/onwheels/onwheels.json @@ -258,6 +258,7 @@ { "builtin": "parking_spaces", "override": { + "id": "parking_spaces_disabled", "source": { "osmTags": "parking_space=disabled" }, diff --git a/assets/themes/openlovemap/openlovemap.json b/assets/themes/openlovemap/openlovemap.json index 2fd24944b..1c9bf94c1 100644 --- a/assets/themes/openlovemap/openlovemap.json +++ b/assets/themes/openlovemap/openlovemap.json @@ -14,7 +14,7 @@ { "builtin": "shops", "override": { - "id": "erotic-shop", + "id": "erotic_shop", "source": { "osmTags": "shop=erotic" }, diff --git a/assets/themes/transit/transit.json b/assets/themes/transit/transit.json index 84c4a6abb..5676d6802 100644 --- a/assets/themes/transit/transit.json +++ b/assets/themes/transit/transit.json @@ -53,6 +53,7 @@ "builtin": "shelter", "override": { "minzoom": 18, + "id": "pt_shelter", "source": { "osmTags": { "and": [ diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index 0a05b9665..97bf12a81 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -185,7 +185,7 @@ ] }, { - "id": "address", + "id": "uk_address", "name": { "en": "Known addresses in OSM" }, diff --git a/assets/themes/walkingnodes/walkingnodes.json b/assets/themes/walkingnodes/walkingnodes.json index e20ffb727..78c89216c 100644 --- a/assets/themes/walkingnodes/walkingnodes.json +++ b/assets/themes/walkingnodes/walkingnodes.json @@ -21,7 +21,7 @@ "hideFromOverview": true, "layers": [ { - "id": "node2node", + "id": "node2node_hiking", "name": { "en": "Node to node links", "de": "Knotenpunktverbindungen", @@ -120,7 +120,7 @@ ] }, { - "id": "node", + "id": "node_hiking", "name": { "en": "Nodes", "de": "Knotenpunkte", @@ -279,6 +279,7 @@ ], "override": { "minzoom": 16, + "id": "guidepost_hiking", "source": { "osmTags": { "and": [ diff --git a/scripts/osm2pgsql/generateLayerFile.ts b/scripts/osm2pgsql/generateBuildDbScript.ts similarity index 70% rename from scripts/osm2pgsql/generateLayerFile.ts rename to scripts/osm2pgsql/generateBuildDbScript.ts index 5867b8d9f..a2c9363f2 100644 --- a/scripts/osm2pgsql/generateLayerFile.ts +++ b/scripts/osm2pgsql/generateBuildDbScript.ts @@ -9,7 +9,7 @@ import { Or } from "../../src/Logic/Tags/Or" import { RegexTag } from "../../src/Logic/Tags/RegexTag" import { Utils } from "../../src/Utils" -class LuaSnippets{ +class LuaSnippets { /** * The main piece of code that calls `process_poi` */ @@ -25,23 +25,25 @@ class LuaSnippets{ "end", ""].join("\n") - public static combine(calls: string[]): string{ + public static combine(calls: string[]): string { return [ `function process_poi(object, geom)`, - ...calls.map(c => " "+c+"(object, geom)"), + ...calls.map(c => " " + c + "(object, geom)"), `end`, ].join("\n") } } + class GenerateLayerLua { private readonly _layer: LayerConfig constructor(layer: LayerConfig) { this._layer = layer } - public functionName(){ + + public functionName() { const l = this._layer - if(!l.source?.osmTags){ + if (!l.source?.osmTags) { return undefined } return `process_poi_${l.id}` @@ -49,7 +51,7 @@ class GenerateLayerLua { public generateFunction(): string { const l = this._layer - if(!l.source?.osmTags){ + if (!l.source?.osmTags) { return undefined } return [ @@ -75,10 +77,38 @@ class GenerateLayerLua { " ", ` pois_${l.id}:insert(a)`, "end", - "" + "", ].join("\n") } + private regexTagToLua(tag: RegexTag) { + if (typeof tag.value === "string" && tag.invert) { + return `object.tags["${tag.key}"] ~= "${tag.value}"` + } + + if ("" + tag.value === "/.+/is" && !tag.invert) { + return `object.tags["${tag.key}"] ~= nil` + } + + if ("" + tag.value === "/.+/is" && tag.invert) { + return `object.tags["${tag.key}"] == nil` + } + + if (tag.matchesEmpty && !tag.invert) { + return `object.tags["${tag.key}"] == nil or object.tags["${tag.key}"] == ""` + } + + + if (tag.matchesEmpty && tag.invert) { + return `object.tags["${tag.key}"] ~= nil or object.tags["${tag.key}"] ~= ""` + } + + if (tag.invert) { + return `object.tags["${tag.key}"] == nil or not string.find(object.tags["${tag.key}"], "${tag.value}")` + } + + return `(object.tags["${tag.key}"] ~= nil and string.find(object.tags["${tag.key}"], "${tag.value}"))` + } private toLuaFilter(tag: TagsFilter, useParens: boolean = false): string { if (tag instanceof Tag) { @@ -99,14 +129,7 @@ class GenerateLayerLua { return expr } if (tag instanceof RegexTag) { - if(typeof tag.value === "string" && tag.invert){ - return `object.tags["${tag.key}"] ~= "${tag.value}"` - } - - let expr = `not string.find(object.tags["${tag.key}"], "${tag.value}")` - if (!tag.invert) { - expr = "not " + expr - } + let expr = this.regexTagToLua(tag) if (useParens) { expr = "(" + expr + ")" } @@ -124,17 +147,17 @@ class GenerateLayerFile extends Script { } async main(args: string[]) { - const layerNames = Array.from(AllSharedLayers.sharedLayers.values()) + const layers = Array.from(AllSharedLayers.sharedLayers.values()) - const generators = layerNames.map(l => new GenerateLayerLua(l)) + const generators = layers.filter(l => l.source.geojsonSource === undefined).map(l => new GenerateLayerLua(l)) const script = [ ...generators.map(g => g.generateFunction()), LuaSnippets.combine(Utils.NoNull(generators.map(g => g.functionName()))), - LuaSnippets.tail + LuaSnippets.tail, ].join("\n\n\n") const path = "build_db.lua" - fs.writeFileSync(path,script, "utf-8") + fs.writeFileSync(path, script, "utf-8") console.log("Written", path) } } From ee38cdb9d7e4fb050b6bcd57c8638b0205cf0b01 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 23 Jan 2024 22:03:22 +0100 Subject: [PATCH 06/57] LayerServer: improve script, add unique and valid id check to layers --- Docs/SettingUpPSQL.md | 23 +- scripts/Script.ts | 9 +- scripts/generateLayerOverview.ts | 69 ++-- scripts/osm2pgsql/generateBuildDbScript.ts | 52 ++- .../ThemeConfig/Conversion/Validation.ts | 359 +++++++++++------- 5 files changed, 313 insertions(+), 199 deletions(-) diff --git a/Docs/SettingUpPSQL.md b/Docs/SettingUpPSQL.md index c83ddb662..dfa9989c6 100644 --- a/Docs/SettingUpPSQL.md +++ b/Docs/SettingUpPSQL.md @@ -10,26 +10,37 @@ Then activate following extensions for this database (right click > Create > Ext - Postgis activeren (rechtsklikken > Create > extension) - HStore activeren -Install osm2pgsql (hint: compile from source is painless) +Increase the max number of connections. osm2pgsql needs connection one per table (and a few more), and since we are making one table per layer in MapComplete, this amounts to a lot. -pg_tileserv kan hier gedownload worden: https://github.com/CrunchyData/pg_tileserv - -DATABASE_URL=postgresql://user:none@localhost:5444/osm-poi ./pg_tileserv +- Open PGAdmin, open the PGSQL-tool (CLI-button at the top) +- Run `max_connections = 2000;` and `show config_file;` to get the config file location (in docker). This is probably `/var/lib/postgresql/data/postgresql.conf` +- In a terminal, run `sudo docker exec -i bash` (run `sudo docker ps` to get the container id) +- `sed -i "s/max_connections = 100/max_connections = 5000/" /var/lib/postgresql/data/postgresql.conf` +- Validate with `cat /var/lib/postgresql/data/postgresql.conf | grep "max_connections"` +- `sudo docker restart ` ## Create export scripts for every layer -Use scripts/osm2pgsl +Use `vite-node ./scripts/osm2pgsql/generateBuildDbScript.ts` ## Importing data +Install osm2pgsql (hint: compile from source is painless) To seed the database: ```` -osm2pgsql -O flex -E 4326 -S build_db.lua -s --flat-nodes=import-help-file -d postgresql://user:password@localhost:5444/osm-poi andorra-latest.osm.pbf +osm2pgsql -O flex -E 4326 -S build_db.lua -s --flat-nodes=import-help-file -d postgresql://user:password@localhost:5444/osm-poi .osm.pbf ```` +Storing properties to table '"public"."osm2pgsql_properties" takes about 25 minutes with planet.osm + +Belgium (~555mb) takes 15m +World (80GB) should take 15m*160 = 2400m = 40hr + ## Deploying a tile server +pg_tileserv kan hier gedownload worden: https://github.com/CrunchyData/pg_tileserv + ```` export DATABASE_URL=postgresql://user:password@localhost:5444/osm-poi ./pg_tileserv diff --git a/scripts/Script.ts b/scripts/Script.ts index 48a727b22..25b0b14e7 100644 --- a/scripts/Script.ts +++ b/scripts/Script.ts @@ -13,8 +13,15 @@ export default abstract class Script { ScriptUtils.fixUtils() const args = [...process.argv] args.splice(0, 2) + const start = new Date() this.main(args) - .then((_) => console.log("All done")) + .then((_) =>{ + const end = new Date() + const millisNeeded = end.getTime() - start.getTime() + + const green = (s) => "\x1b[92m" + s + "\x1b[0m" + console.log(green("All done! (" + millisNeeded + " ms)")) + }) .catch((e) => console.log("ERROR:", e)) } diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 39e8c6424..f60f96d10 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -9,7 +9,7 @@ import { DoesImageExist, PrevalidateTheme, ValidateLayer, - ValidateThemeAndLayers, + ValidateThemeAndLayers, ValidateThemeEnsemble, } from "../src/Models/ThemeConfig/Conversion/Validation" import { Translation } from "../src/UI/i18n/Translation" import { PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer" @@ -25,6 +25,8 @@ import LayerConfig from "../src/Models/ThemeConfig/LayerConfig" import PointRenderingConfig from "../src/Models/ThemeConfig/PointRenderingConfig" import { ConversionContext } from "../src/Models/ThemeConfig/Conversion/ConversionContext" import { GenerateFavouritesLayer } from "./generateFavouritesLayer" +import LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig" +import { TagsFilter } from "../src/Logic/Tags/TagsFilter" // This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files. // It spits out an overview of those to be used to load them @@ -123,6 +125,7 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye } } + class LayerOverviewUtils extends Script { public static readonly layerPath = "./src/assets/generated/layers/" public static readonly themePath = "./src/assets/generated/themes/" @@ -355,7 +358,6 @@ class LayerOverviewUtils extends Script { const layerWhitelist = new Set(args.find(a => a.startsWith("--layers=")) ?.substring("--layers=".length)?.split(",") ?? []) - const start = new Date() const forceReload = args.some((a) => a == "--force") const licensePaths = new Set() @@ -382,17 +384,21 @@ class LayerOverviewUtils extends Script { sharedLayers, recompiledThemes, forceReload, - themeWhitelist + themeWhitelist, ) - if (recompiledThemes.length > 0){ + new ValidateThemeEnsemble().convertStrict( + Array.from(sharedThemes.values()).map(th => new LayoutConfig(th, true))) + + + if (recompiledThemes.length > 0) { writeFileSync( "./src/assets/generated/known_layers.json", JSON.stringify({ layers: Array.from(sharedLayers.values()).filter((l) => l.id !== "favourite"), }), ) - } + } const mcChangesPath = "./assets/themes/mapcomplete-changes/mapcomplete-changes.json" if ( @@ -437,7 +443,7 @@ class LayerOverviewUtils extends Script { ) } - if(recompiledThemes.length > 0) { + if (recompiledThemes.length > 0) { writeFileSync( "./src/assets/generated/known_themes.json", JSON.stringify({ @@ -446,17 +452,10 @@ class LayerOverviewUtils extends Script { ) } - const end = new Date() - const millisNeeded = end.getTime() - start.getTime() if (AllSharedLayers.getSharedLayersConfigs().size == 0) { console.error( - "This was a bootstrapping-run. Run generate layeroverview again!(" + - millisNeeded + - " ms)", + "This was a bootstrapping-run. Run generate layeroverview again!" ) - } else { - const green = (s) => "\x1b[92m" + s + "\x1b[0m" - console.log(green("All done! (" + millisNeeded + " ms)")) } } @@ -482,7 +481,7 @@ class LayerOverviewUtils extends Script { private buildLayerIndex( doesImageExist: DoesImageExist, forceReload: boolean, - whitelist: Set + whitelist: Set, ): Map { // First, we expand and validate all builtin layers. These are written to src/assets/generated/layers // At the same time, an index of available layers is built. @@ -500,9 +499,9 @@ class LayerOverviewUtils extends Script { const recompiledLayers: string[] = [] let warningCount = 0 for (const sharedLayerPath of ScriptUtils.getLayerPaths()) { - if(whitelist.size > 0){ - const idByPath = sharedLayerPath.split("/").at(-1).split(".")[0] - if(Constants.priviliged_layers.indexOf( idByPath) < 0 && !whitelist.has(idByPath)){ + if (whitelist.size > 0) { + const idByPath = sharedLayerPath.split("/").at(-1).split(".")[0] + if (Constants.priviliged_layers.indexOf(idByPath) < 0 && !whitelist.has(idByPath)) { continue } } @@ -672,7 +671,7 @@ class LayerOverviewUtils extends Script { sharedLayers: Map, recompiledThemes: string[], forceReload: boolean, - whitelist: Set + whitelist: Set, ): Map { console.log(" ---------- VALIDATING BUILTIN THEMES ---------") const themeFiles = ScriptUtils.getThemeFiles() @@ -710,7 +709,7 @@ class LayerOverviewUtils extends Script { const themeInfo = themeFiles[i] const themePath = themeInfo.path let themeFile = themeInfo.parsed - if(whitelist.size > 0 && !whitelist.has(themeFile.id)){ + if (whitelist.size > 0 && !whitelist.has(themeFile.id)) { continue } @@ -795,21 +794,21 @@ class LayerOverviewUtils extends Script { } } - if(whitelist.size == 0){ - this.writeSmallOverview( - Array.from(fixed.values()).map((t) => { - return { - ...t, - hideFromOverview: t.hideFromOverview ?? false, - shortDescription: - t.shortDescription ?? - new Translation(t.description) - .FirstSentence() - .OnEveryLanguage((s) => parse_html(s).textContent).translations, - mustHaveLanguage: t.mustHaveLanguage?.length > 0, - } - }), - ) + if (whitelist.size == 0) { + this.writeSmallOverview( + Array.from(fixed.values()).map((t) => { + return { + ...t, + hideFromOverview: t.hideFromOverview ?? false, + shortDescription: + t.shortDescription ?? + new Translation(t.description) + .FirstSentence() + .OnEveryLanguage((s) => parse_html(s).textContent).translations, + mustHaveLanguage: t.mustHaveLanguage?.length > 0, + } + }), + ) } console.log( diff --git a/scripts/osm2pgsql/generateBuildDbScript.ts b/scripts/osm2pgsql/generateBuildDbScript.ts index a2c9363f2..0fce50b06 100644 --- a/scripts/osm2pgsql/generateBuildDbScript.ts +++ b/scripts/osm2pgsql/generateBuildDbScript.ts @@ -1,13 +1,13 @@ -import LayerConfig from "../../src/Models/ThemeConfig/LayerConfig" import { TagsFilter } from "../../src/Logic/Tags/TagsFilter" import { Tag } from "../../src/Logic/Tags/Tag" import { And } from "../../src/Logic/Tags/And" import Script from "../Script" -import { AllSharedLayers } from "../../src/Customizations/AllSharedLayers" import fs from "fs" import { Or } from "../../src/Logic/Tags/Or" import { RegexTag } from "../../src/Logic/Tags/RegexTag" import { Utils } from "../../src/Utils" +import { ValidateThemeEnsemble } from "../../src/Models/ThemeConfig/Conversion/Validation" +import { AllKnownLayouts } from "../../src/Customizations/AllKnownLayouts" class LuaSnippets { /** @@ -35,28 +35,31 @@ class LuaSnippets { } class GenerateLayerLua { - private readonly _layer: LayerConfig + private readonly _id: string + private readonly _tags: TagsFilter + private readonly _foundInThemes: string[] - constructor(layer: LayerConfig) { - this._layer = layer + constructor(id: string, tags: TagsFilter, foundInThemes: string[] = []) { + this._tags = tags + this._id = id + this._foundInThemes = foundInThemes } public functionName() { - const l = this._layer - if (!l.source?.osmTags) { + if (!this._tags) { return undefined } - return `process_poi_${l.id}` + return `process_poi_${this._id}` } public generateFunction(): string { - const l = this._layer - if (!l.source?.osmTags) { + if (!this._tags) { return undefined } return [ - `local pois_${l.id} = osm2pgsql.define_table({`, - ` name = '${l.id}',`, + `local pois_${this._id} = osm2pgsql.define_table({`, + this._foundInThemes ? "-- used in themes: " + this._foundInThemes.join(", ") : "", + ` name = '${this._id}',`, " ids = { type = 'any', type_column = 'osm_type', id_column = 'osm_id' },", " columns = {", " { column = 'tags', type = 'jsonb' },", @@ -66,7 +69,7 @@ class GenerateLayerLua { "", "", `function ${this.functionName()}(object, geom)`, - " local matches_filter = " + this.toLuaFilter(l.source.osmTags), + " local matches_filter = " + this.toLuaFilter(this._tags), " if( not matches_filter) then", " return", " end", @@ -75,7 +78,7 @@ class GenerateLayerLua { " tags = object.tags", " }", " ", - ` pois_${l.id}:insert(a)`, + ` pois_${this._id}:insert(a)`, "end", "", ].join("\n") @@ -86,6 +89,8 @@ class GenerateLayerLua { return `object.tags["${tag.key}"] ~= "${tag.value}"` } + const v = ( tag.value).source.replace(/\\\//g, "/") + if ("" + tag.value === "/.+/is" && !tag.invert) { return `object.tags["${tag.key}"] ~= nil` } @@ -104,10 +109,10 @@ class GenerateLayerLua { } if (tag.invert) { - return `object.tags["${tag.key}"] == nil or not string.find(object.tags["${tag.key}"], "${tag.value}")` + return `object.tags["${tag.key}"] == nil or not string.find(object.tags["${tag.key}"], "${v}")` } - return `(object.tags["${tag.key}"] ~= nil and string.find(object.tags["${tag.key}"], "${tag.value}"))` + return `(object.tags["${tag.key}"] ~= nil and string.find(object.tags["${tag.key}"], "${v}"))` } private toLuaFilter(tag: TagsFilter, useParens: boolean = false): string { @@ -141,15 +146,21 @@ class GenerateLayerLua { } } -class GenerateLayerFile extends Script { +class GenerateBuildDbScript extends Script { constructor() { super("Generates a .lua-file to use with osm2pgsql") } async main(args: string[]) { - const layers = Array.from(AllSharedLayers.sharedLayers.values()) + const allNeededLayers = new ValidateThemeEnsemble().convertStrict( + AllKnownLayouts.allKnownLayouts.values(), + ) - const generators = layers.filter(l => l.source.geojsonSource === undefined).map(l => new GenerateLayerLua(l)) + const generators: GenerateLayerLua[] = [] + + allNeededLayers.forEach(({ tags, foundInTheme }, layerId) => { + generators.push(new GenerateLayerLua(layerId, tags, foundInTheme)) + }) const script = [ ...generators.map(g => g.generateFunction()), @@ -159,7 +170,8 @@ class GenerateLayerFile extends Script { const path = "build_db.lua" fs.writeFileSync(path, script, "utf-8") console.log("Written", path) + console.log(allNeededLayers.size+" layers will be created. Make sure to set 'max_connections' to at least "+(10 + allNeededLayers.size) ) } } -new GenerateLayerFile().run() +new GenerateBuildDbScript().run() diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index ed4c79098..6380df2e5 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -21,9 +21,7 @@ import PresetConfig from "../PresetConfig" import { TagsFilter } from "../../../Logic/Tags/TagsFilter" import { Translatable } from "../Json/Translatable" import { ConversionContext } from "./ConversionContext" -import * as eli from "../../../assets/editor-layer-index.json" import { AvailableRasterLayers } from "../../RasterLayers" -import Back from "../../../assets/svg/Back.svelte" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" class ValidateLanguageCompleteness extends DesugaringStep { @@ -33,7 +31,7 @@ class ValidateLanguageCompleteness extends DesugaringStep { super( "Checks that the given object is fully translated in the specified languages", [], - "ValidateLanguageCompleteness" + "ValidateLanguageCompleteness", ) this._languages = languages ?? ["en"] } @@ -47,18 +45,18 @@ class ValidateLanguageCompleteness extends DesugaringStep { .filter( (t) => t.tr.translations[neededLanguage] === undefined && - t.tr.translations["*"] === undefined + t.tr.translations["*"] === undefined, ) .forEach((missing) => { context .enter(missing.context.split(".")) .err( `The theme ${obj.id} should be translation-complete for ` + - neededLanguage + - ", but it lacks a translation for " + - missing.context + - ".\n\tThe known translation is " + - missing.tr.textFor("en") + neededLanguage + + ", but it lacks a translation for " + + missing.context + + ".\n\tThe known translation is " + + missing.tr.textFor("en"), ) }) } @@ -75,7 +73,7 @@ export class DoesImageExist extends DesugaringStep { constructor( knownImagePaths: Set, checkExistsSync: (path: string) => boolean = undefined, - ignore?: Set + ignore?: Set, ) { super("Checks if an image exists", [], "DoesImageExist") this._ignore = ignore @@ -111,15 +109,15 @@ export class DoesImageExist extends DesugaringStep { if (!this._knownImagePaths.has(image)) { if (this.doesPathExist === undefined) { context.err( - `Image with path ${image} not found or not attributed; it is used in ${context}` + `Image with path ${image} not found or not attributed; it is used in ${context}`, ) } else if (!this.doesPathExist(image)) { context.err( - `Image with path ${image} does not exist.\n Check for typo's and missing directories in the path.` + `Image with path ${image} does not exist.\n Check for typo's and missing directories in the path.`, ) } else { context.err( - `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info` + `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`, ) } } @@ -143,7 +141,7 @@ export class ValidateTheme extends DesugaringStep { doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, - sharedTagRenderings?: Set + sharedTagRenderings?: Set, ) { super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme") this._validateImage = doesImageExist @@ -162,15 +160,15 @@ export class ValidateTheme extends DesugaringStep { if (json["units"] !== undefined) { context.err( "The theme " + - json.id + - " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " + json.id + + " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ", ) } if (json["roamingRenderings"] !== undefined) { context.err( "Theme " + - json.id + - " contains an old 'roamingRenderings'. Use an 'overrideAll' instead" + json.id + + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead", ) } } @@ -178,7 +176,7 @@ export class ValidateTheme extends DesugaringStep { if (!json.title) { context.enter("title").err(`The theme ${json.id} does not have a title defined.`) } - if(!json.icon){ + if (!json.icon) { context.enter("icon").err("A theme should have an icon") } if (this._isBuiltin && this._extractImages !== undefined) { @@ -188,10 +186,10 @@ export class ValidateTheme extends DesugaringStep { for (const remoteImage of remoteImages) { context.err( "Found a remote image: " + - remoteImage.path + - " in theme " + - json.id + - ", please download it." + remoteImage.path + + " in theme " + + json.id + + ", please download it.", ) } for (const image of images) { @@ -207,17 +205,17 @@ export class ValidateTheme extends DesugaringStep { const filename = this._path.substring( this._path.lastIndexOf("/") + 1, - this._path.length - 5 + this._path.length - 5, ) if (theme.id !== filename) { context.err( "Theme ids should be the same as the name.json, but we got id: " + - theme.id + - " and filename " + - filename + - " (" + - this._path + - ")" + theme.id + + " and filename " + + filename + + " (" + + this._path + + ")", ) } this._validateImage.convert(theme.icon, context.enter("icon")) @@ -225,13 +223,13 @@ export class ValidateTheme extends DesugaringStep { const dups = Utils.Duplicates(json.layers.map((layer) => layer["id"])) if (dups.length > 0) { context.err( - `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}` + `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`, ) } if (json["mustHaveLanguage"] !== undefined) { new ValidateLanguageCompleteness(...json["mustHaveLanguage"]).convert( theme, - context + context, ) } if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) { @@ -239,7 +237,7 @@ export class ValidateTheme extends DesugaringStep { const targetLanguage = theme.title.SupportedLanguages()[0] if (targetLanguage !== "en") { context.err( - `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key` + `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`, ) } @@ -282,6 +280,13 @@ export class ValidateTheme extends DesugaringStep { } } + for (let i = 0; i < theme.layers.length; i++) { + const layer = theme.layers[i] + if (!layer.id.match("[a-z][a-z0-9_]*")) { + context.enters("layers", i, "id").err("Invalid ID:" + layer.id + "should match [a-z][a-z0-9_]*") + } + } + return json } } @@ -291,7 +296,7 @@ export class ValidateThemeAndLayers extends Fuse { doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, - sharedTagRenderings?: Set + sharedTagRenderings?: Set, ) { super( "Validates a theme and the contained layers", @@ -301,10 +306,10 @@ export class ValidateThemeAndLayers extends Fuse { new Each( new Bypass( (layer) => Constants.added_by_default.indexOf(layer.id) < 0, - new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true) - ) - ) - ) + new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true), + ), + ), + ), ) } } @@ -314,7 +319,7 @@ class OverrideShadowingCheck extends DesugaringStep { super( "Checks that an 'overrideAll' does not override a single override", [], - "OverrideShadowingCheck" + "OverrideShadowingCheck", ) } @@ -363,6 +368,22 @@ class MiscThemeChecks extends DesugaringStep { if (json.socialImage === "") { context.warn("Social image for theme " + json.id + " is the emtpy string") } + { + for (let i = 0; i < json.layers.length; i++) { + const l = json.layers[i] + if (l["override"]?.["source"] === undefined) { + continue + } + if (l["override"]?.["source"]?.["geoJson"]) { + continue // We don't care about external data as we won't cache it anyway + } + if (l["override"]["id"] !== undefined) { + continue + } + context.enters("layers", i).err("A layer which changes the source-tags must also change the ID") + } + } + return json } } @@ -372,7 +393,7 @@ export class PrevalidateTheme extends Fuse { super( "Various consistency checks on the raw JSON", new MiscThemeChecks(), - new OverrideShadowingCheck() + new OverrideShadowingCheck(), ) } } @@ -382,7 +403,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep ["_abc"] */ private static extractCalculatedTagNames( - layerConfig?: LayerConfigJson | { calculatedTags: string[] } + layerConfig?: LayerConfigJson | { calculatedTags: string[] }, ) { return ( layerConfig?.calculatedTags?.map((ct) => { @@ -617,16 +638,16 @@ export class DetectShadowedMappings extends DesugaringStep\` instead. The images found are ${images.join( - ", " - )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged` + ", ", + )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`, ) } else { ctx.info( `Ignored image ${images.join( - ", " - )} in 'then'-clause of a mapping as this check has been disabled` + ", ", + )} in 'then'-clause of a mapping as this check has been disabled`, ) for (const image of images) { @@ -721,7 +742,7 @@ class ValidatePossibleLinks extends DesugaringStep does have `rel='noopener'` set", [], - "ValidatePossibleLinks" + "ValidatePossibleLinks", ) } @@ -751,21 +772,21 @@ class ValidatePossibleLinks extends DesugaringStep, - context: ConversionContext + context: ConversionContext, ): string | Record { if (typeof json === "string") { if (this.isTabnabbingProne(json)) { context.err( "The string " + - json + - " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping" + json + + " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping", ) } } else { for (const k in json) { if (this.isTabnabbingProne(json[k])) { context.err( - `The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping` + `The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`, ) } } @@ -783,7 +804,7 @@ class CheckTranslation extends DesugaringStep { super( "Checks that a translation is valid and internally consistent", ["*"], - "CheckTranslation" + "CheckTranslation", ) this._allowUndefined = allowUndefined } @@ -829,17 +850,17 @@ class MiscTagRenderingChecks extends DesugaringStep { convert( json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson, - context: ConversionContext + context: ConversionContext, ): TagRenderingConfigJson { if (json["special"] !== undefined) { context.err( - 'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`' + "Detected `special` on the top level. Did you mean `{\"render\":{ \"special\": ... }}`", ) } if (Object.keys(json).length === 1 && typeof json["render"] === "string") { context.warn( - `use the content directly instead of {render: ${JSON.stringify(json["render"])}}` + `use the content directly instead of {render: ${JSON.stringify(json["render"])}}`, ) } @@ -851,7 +872,7 @@ class MiscTagRenderingChecks extends DesugaringStep { const mapping = json.mappings[i] CheckTranslation.noUndefined.convert( mapping.then, - context.enters("mappings", i, "then") + context.enters("mappings", i, "then"), ) if (!mapping.if) { context.enters("mappings", i).err("No `if` is defined") @@ -862,18 +883,18 @@ class MiscTagRenderingChecks extends DesugaringStep { context .enters("mappings", i, "then") .warn( - "A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' without the question, resulting in a weird phrasing in the information box" + "A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' without the question, resulting in a weird phrasing in the information box", ) } } } if (json["group"]) { - context.err('Groups are deprecated, use `"label": ["' + json["group"] + '"]` instead') + context.err("Groups are deprecated, use `\"label\": [\"" + json["group"] + "\"]` instead") } if (json["question"] && json.freeform?.key === undefined && json.mappings === undefined) { context.err( - "A question is defined, but no mappings nor freeform (key) are. Add at least one of them" + "A question is defined, but no mappings nor freeform (key) are. Add at least one of them", ) } if (json["question"] && !json.freeform && (json.mappings?.length ?? 0) == 1) { @@ -883,7 +904,7 @@ class MiscTagRenderingChecks extends DesugaringStep { context .enter("questionHint") .err( - "A questionHint is defined, but no question is given. As such, the questionHint will never be shown" + "A questionHint is defined, but no question is given. As such, the questionHint will never be shown", ) } @@ -893,10 +914,10 @@ class MiscTagRenderingChecks extends DesugaringStep { .enter("render") .err( "This tagRendering allows to set a value to key " + - json.freeform.key + - ", but does not define a `render`. Please, add a value here which contains `{" + - json.freeform.key + - "}`" + json.freeform.key + + ", but does not define a `render`. Please, add a value here which contains `{" + + json.freeform.key + + "}`", ) } else { const render = new Translation(json.render) @@ -927,7 +948,7 @@ class MiscTagRenderingChecks extends DesugaringStep { const keyFirstArg = ["canonical", "fediverse_link", "translated"] if ( keyFirstArg.some( - (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0 + (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0, ) ) { continue @@ -950,7 +971,7 @@ class MiscTagRenderingChecks extends DesugaringStep { context .enter("render") .err( - `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!` + `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!`, ) } } @@ -958,8 +979,8 @@ class MiscTagRenderingChecks extends DesugaringStep { if (json.render && json["question"] && json.freeform === undefined) { context.err( `Detected a tagrendering which takes input without freeform key in ${context}; the question is ${new Translation( - json["question"] - ).textFor("en")}` + json["question"], + ).textFor("en")}`, ) } @@ -970,9 +991,9 @@ class MiscTagRenderingChecks extends DesugaringStep { .enters("freeform", "type") .err( "Unknown type: " + - freeformType + - "; try one of " + - Validators.availableTypes.join(", ") + freeformType + + "; try one of " + + Validators.availableTypes.join(", "), ) } } @@ -1004,7 +1025,7 @@ export class ValidateTagRenderings extends Fuse { new On("question", new ValidatePossibleLinks()), new On("questionHint", new ValidatePossibleLinks()), new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))), - new MiscTagRenderingChecks() + new MiscTagRenderingChecks(), ) } } @@ -1034,8 +1055,9 @@ export class PrevalidateLayer extends DesugaringStep { if (json.id?.toLowerCase() !== json.id) { context.enter("id").err(`The id of a layer should be lowercase: ${json.id}`) } - if (json.id?.match(/[a-z0-9-_]/) == null) { - context.enter("id").err(`The id of a layer should match [a-z0-9-_]*: ${json.id}`) + const layerRegex = /[a-zA-Z][a-zA-Z_0-9]+/ + if (json.id.match(layerRegex) === null) { + context.enter("id").err("Invalid ID. A layer ID should match " + layerRegex.source) } } @@ -1043,7 +1065,7 @@ export class PrevalidateLayer extends DesugaringStep { context .enter("source") .err( - "No source section is defined; please define one as data is not loaded otherwise" + "No source section is defined; please define one as data is not loaded otherwise", ) } else { if (json.source === "special" || json.source === "special:library") { @@ -1051,7 +1073,7 @@ export class PrevalidateLayer extends DesugaringStep { context .enters("source", "osmTags") .err( - "No osmTags defined in the source section - these should always be present, even for geojson layer" + "No osmTags defined in the source section - these should always be present, even for geojson layer", ) } else { const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags") @@ -1060,7 +1082,7 @@ export class PrevalidateLayer extends DesugaringStep { .enters("source", "osmTags") .err( "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" + - osmTags.asHumanString(false, false, {}) + osmTags.asHumanString(false, false, {}), ) } } @@ -1086,10 +1108,10 @@ export class PrevalidateLayer extends DesugaringStep { .enter("syncSelection") .err( "Invalid sync-selection: must be one of " + - LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") + - " but got '" + - json.syncSelection + - "'" + LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") + + " but got '" + + json.syncSelection + + "'", ) } if (json["pointRenderings"]?.length > 0) { @@ -1107,7 +1129,7 @@ export class PrevalidateLayer extends DesugaringStep { context.enter("pointRendering").err("There are no pointRenderings at all...") } - json.pointRendering?.forEach((pr,i) => this._validatePointRendering.convert(pr, context.enters("pointeRendering", i))) + json.pointRendering?.forEach((pr, i) => this._validatePointRendering.convert(pr, context.enters("pointeRendering", i))) if (json["mapRendering"]) { context.enter("mapRendering").err("This layer has a legacy 'mapRendering'") @@ -1123,8 +1145,8 @@ export class PrevalidateLayer extends DesugaringStep { if (!Constants.priviliged_layers.find((x) => x == json.id)) { context.err( "Layer " + - json.id + - " uses 'special' as source.osmTags. However, this layer is not a priviliged layer" + json.id + + " uses 'special' as source.osmTags. However, this layer is not a priviliged layer", ) } } @@ -1139,19 +1161,19 @@ export class PrevalidateLayer extends DesugaringStep { context .enter("title") .err( - "This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error." + "This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error.", ) } if (json.title === null) { context.info( - "Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." + "Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set.", ) } { // Check for multiple, identical builtin questions - usability for studio users const duplicates = Utils.Duplicates( - json.tagRenderings.filter((tr) => typeof tr === "string") + json.tagRenderings.filter((tr) => typeof tr === "string"), ) for (let i = 0; i < json.tagRenderings.length; i++) { const tagRendering = json.tagRenderings[i] @@ -1181,7 +1203,7 @@ export class PrevalidateLayer extends DesugaringStep { { // duplicate ids in tagrenderings check const duplicates = Utils.NoNull( - Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))) + Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))), ) if (duplicates.length > 0) { // It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list @@ -1219,8 +1241,8 @@ export class PrevalidateLayer extends DesugaringStep { if (json["overpassTags"] !== undefined) { context.err( "Layer " + - json.id + - 'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": }\' instead of "overpassTags": (note: this isn\'t your fault, the custom theme generator still spits out the old format)' + json.id + + "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": }' instead of \"overpassTags\": (note: this isn't your fault, the custom theme generator still spits out the old format)", ) } const forbiddenTopLevel = [ @@ -1240,7 +1262,7 @@ export class PrevalidateLayer extends DesugaringStep { } if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { context.err( - "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'" + "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'", ) } @@ -1257,9 +1279,9 @@ export class PrevalidateLayer extends DesugaringStep { if (this._path != undefined && this._path.indexOf(expected) < 0) { context.err( "Layer is in an incorrect place. The path is " + - this._path + - ", but expected " + - expected + this._path + + ", but expected " + + expected, ) } } @@ -1277,13 +1299,13 @@ export class PrevalidateLayer extends DesugaringStep { .enter(["tagRenderings", ...emptyIndexes]) .err( `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join( - "," - )}])` + ",", + )}])`, ) } const duplicateIds = Utils.Duplicates( - (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions") + (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions"), ) if (duplicateIds.length > 0 && !Utils.runningFromConsole) { context @@ -1307,7 +1329,7 @@ export class PrevalidateLayer extends DesugaringStep { if (json.tagRenderings !== undefined) { new On( "tagRenderings", - new Each(new ValidateTagRenderings(json, this._doesImageExist)) + new Each(new ValidateTagRenderings(json, this._doesImageExist)), ).convert(json, context) } @@ -1334,7 +1356,7 @@ export class PrevalidateLayer extends DesugaringStep { context .enters("pointRendering", i, "marker", indexM, "icon", "condition") .err( - "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead." + "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead.", ) } } @@ -1372,9 +1394,9 @@ export class PrevalidateLayer extends DesugaringStep { .enters("presets", i, "tags") .err( "This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + - tags.asHumanString(false, false, {}) + - "\n The required tags are: " + - baseTags.asHumanString(false, false, {}) + tags.asHumanString(false, false, {}) + + "\n The required tags are: " + + baseTags.asHumanString(false, false, {}), ) } } @@ -1391,7 +1413,7 @@ export class ValidateLayerConfig extends DesugaringStep { isBuiltin: boolean, doesImageExist: DoesImageExist, studioValidations: boolean = false, - skipDefaultLayers: boolean = false + skipDefaultLayers: boolean = false, ) { super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig") this.validator = new ValidateLayer( @@ -1399,7 +1421,7 @@ export class ValidateLayerConfig extends DesugaringStep { isBuiltin, doesImageExist, studioValidations, - skipDefaultLayers + skipDefaultLayers, ) } @@ -1428,12 +1450,12 @@ class ValidatePointRendering extends DesugaringStep { } if (json.marker && !Array.isArray(json.marker)) { context.enter("marker").err( - "The marker in a pointRendering should be an array" + "The marker in a pointRendering should be an array", ) } if (json.location.length == 0) { - context.enter("location").err ( - "A pointRendering should have at least one 'location' to defined where it should be rendered. " + context.enter("location").err( + "A pointRendering should have at least one 'location' to defined where it should be rendered. ", ) } return json @@ -1441,41 +1463,44 @@ class ValidatePointRendering extends DesugaringStep { } } + export class ValidateLayer extends Conversion< LayerConfigJson, { parsed: LayerConfig; raw: LayerConfigJson } > { private readonly _skipDefaultLayers: boolean private readonly _prevalidation: PrevalidateLayer + constructor( path: string, isBuiltin: boolean, doesImageExist: DoesImageExist, studioValidations: boolean = false, - skipDefaultLayers: boolean = false + skipDefaultLayers: boolean = false, ) { super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") this._prevalidation = new PrevalidateLayer( path, isBuiltin, doesImageExist, - studioValidations + studioValidations, ) this._skipDefaultLayers = skipDefaultLayers } convert( json: LayerConfigJson, - context: ConversionContext + context: ConversionContext, ): { parsed: LayerConfig; raw: LayerConfigJson } { context = context.inOperation(this.name) if (typeof json === "string") { context.err( - `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed` + `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed`, ) return undefined } + if (this._skipDefaultLayers && Constants.added_by_default.indexOf(json.id) >= 0) { return { parsed: undefined, raw: json } } @@ -1502,7 +1527,7 @@ export class ValidateLayer extends Conversion< context .enters("calculatedTags", i) .err( - `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}` + `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}`, ) } } @@ -1553,8 +1578,8 @@ export class ValidateFilter extends DesugaringStep { .enters("fields", i) .err( `Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from( - Validators.availableTypes - ).join(",")}` + Validators.availableTypes, + ).join(",")}`, ) } } @@ -1571,13 +1596,13 @@ export class DetectDuplicateFilters extends DesugaringStep<{ super( "Tries to detect layers where a shared filter can be used (or where similar filters occur)", [], - "DetectDuplicateFilters" + "DetectDuplicateFilters", ) } convert( json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] }, - context: ConversionContext + context: ConversionContext, ): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } { const { layers, themes } = json const perOsmTag = new Map< @@ -1641,7 +1666,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{ filter: FilterConfigJson }[] >, - layout?: LayoutConfigJson | undefined + layout?: LayoutConfigJson | undefined, ): void { if (layer.filter === undefined || layer.filter === null) { return @@ -1681,7 +1706,7 @@ export class DetectDuplicatePresets extends DesugaringStep { super( "Detects mappings which have identical (english) names or identical mappings.", ["presets"], - "DetectDuplicatePresets" + "DetectDuplicatePresets", ) } @@ -1692,13 +1717,13 @@ export class DetectDuplicatePresets extends DesugaringStep { if (new Set(enNames).size != enNames.length) { const dups = Utils.Duplicates(enNames) const layersWithDup = json.layers.filter((l) => - l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0) + l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0), ) const layerIds = layersWithDup.map((l) => l.id) context.err( `This themes has multiple presets which are named:${dups}, namely layers ${layerIds.join( - ", " - )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets` + ", ", + )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets`, ) } @@ -1713,17 +1738,17 @@ export class DetectDuplicatePresets extends DesugaringStep { Utils.SameObject(presetATags, presetBTags) && Utils.sameList( presetA.preciseInput.snapToLayers, - presetB.preciseInput.snapToLayers + presetB.preciseInput.snapToLayers, ) ) { context.err( `This themes has multiple presets with the same tags: ${presetATags.asHumanString( false, false, - {} + {}, )}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[ j - ].title.textFor("en")}'` + ].title.textFor("en")}'`, ) } } @@ -1732,3 +1757,63 @@ export class DetectDuplicatePresets extends DesugaringStep { return json } } + +export class ValidateThemeEnsemble extends Conversion> { + constructor() { + super("Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes", [], "ValidateThemeEnsemble") + } + + convert(json: LayoutConfig[], context: ConversionContext): Map { + + + const idToSource = new Map() + + for (const theme of json) { + for (const layer of theme.layers) { + if (typeof layer.source === "string") { + continue + } + if (Constants.priviliged_layers.indexOf(layer.id) >= 0) { + continue + } + if (!layer.source) { + console.log(theme, layer, layer.source) + context.enters(theme.id, "layers", "source", layer.id).err("No source defined") + continue + } + if (layer.source.geojsonSource) { + continue + } + const id = layer.id + const tags = layer.source.osmTags + if (!idToSource.has(id)) { + idToSource.set(id, { tags, foundInTheme: [theme.id] }) + continue + } + + const oldTags = idToSource.get(id).tags + const oldTheme = idToSource.get(id).foundInTheme + if (oldTags.shadows(tags) && tags.shadows(oldTags)) { + // All is good, all is well + oldTheme.push(theme.id) + continue + } + context.err(["The layer with id '" + id + "' is found in multiple themes with different tag definitions:", + "\t In theme " + oldTheme + ":\t" + oldTags.asHumanString(false, false, {}), + "\tIn theme " + theme.id + ":\t" + tags.asHumanString(false, false, {}), + + + ].join("\n")) + } + } + + + return idToSource + } +} From ee3e000cd166b931faaaf0faa0b0d23feba13b37 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 26 Jan 2024 18:18:07 +0100 Subject: [PATCH 07/57] Add polygon merging --- Docs/SettingUpPSQL.md | 2 +- package.json | 2 +- scripts/osm2pgsql/generateBuildDbScript.ts | 274 +++++++++++------- src/Logic/FeatureSource/FeatureSource.ts | 6 + .../Sources/FeatureSourceMerger.ts | 32 +- src/Logic/FeatureSource/Sources/MvtSource.ts | 191 +++++++----- .../DynamicGeoJsonTileSource.ts | 4 +- .../DynamicMvtTileSource.ts | 61 +++- .../TiledFeatureSource/DynamicTileSource.ts | 99 ++++++- src/UI/Map/ShowDataLayer.ts | 1 - src/UI/Test.svelte | 93 +----- 11 files changed, 460 insertions(+), 305 deletions(-) diff --git a/Docs/SettingUpPSQL.md b/Docs/SettingUpPSQL.md index dfa9989c6..e3a4ae13b 100644 --- a/Docs/SettingUpPSQL.md +++ b/Docs/SettingUpPSQL.md @@ -29,7 +29,7 @@ Install osm2pgsql (hint: compile from source is painless) To seed the database: ```` -osm2pgsql -O flex -E 4326 -S build_db.lua -s --flat-nodes=import-help-file -d postgresql://user:password@localhost:5444/osm-poi .osm.pbf +osm2pgsql -O flex -S build_db.lua -s --flat-nodes=import-help-file -d postgresql://user:password@localhost:5444/osm-poi .osm.pbf ```` Storing properties to table '"public"."osm2pgsql_properties" takes about 25 minutes with planet.osm diff --git a/package.json b/package.json index 5f961226b..921eaee85 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg", "url": "https://www.openstreetmap.org" }, - "mvt_layer_server": "http://127.0.0.1:7800/public.{layer}/{z}/{x}/{y}.pbf", + "mvt_layer_server": "http://127.0.0.1:7800/public.{type}_{layer}/{z}/{x}/{y}.pbf", "disabled:oauth_credentials": { "##": "DEV", "#": "This client-id is registered by 'MapComplete' on https://master.apis.dev.openstreetmap.org/", diff --git a/scripts/osm2pgsql/generateBuildDbScript.ts b/scripts/osm2pgsql/generateBuildDbScript.ts index 0fce50b06..4559f4683 100644 --- a/scripts/osm2pgsql/generateBuildDbScript.ts +++ b/scripts/osm2pgsql/generateBuildDbScript.ts @@ -5,91 +5,57 @@ import Script from "../Script" import fs from "fs" import { Or } from "../../src/Logic/Tags/Or" import { RegexTag } from "../../src/Logic/Tags/RegexTag" -import { Utils } from "../../src/Utils" import { ValidateThemeEnsemble } from "../../src/Models/ThemeConfig/Conversion/Validation" import { AllKnownLayouts } from "../../src/Customizations/AllKnownLayouts" class LuaSnippets { - /** - * The main piece of code that calls `process_poi` - */ - static tail = [ - "function osm2pgsql.process_node(object)", - " process_poi(object, object:as_point())", + + public static helpers = [ + "function countTbl(tbl)\n" + + " local c = 0\n" + + " for n in pairs(tbl) do \n" + + " c = c + 1 \n" + + " end\n" + + " return c\n" + "end", - "", - "function osm2pgsql.process_way(object)", - " if object.is_closed then", - " process_poi(object, object:as_polygon():centroid())", - " end", - "end", - ""].join("\n") + ].join("\n") - public static combine(calls: string[]): string { - return [ - `function process_poi(object, geom)`, - ...calls.map(c => " " + c + "(object, geom)"), - `end`, - ].join("\n") - } -} - -class GenerateLayerLua { - private readonly _id: string - private readonly _tags: TagsFilter - private readonly _foundInThemes: string[] - - constructor(id: string, tags: TagsFilter, foundInThemes: string[] = []) { - this._tags = tags - this._id = id - this._foundInThemes = foundInThemes - } - - public functionName() { - if (!this._tags) { - return undefined + public static toLuaFilter(tag: TagsFilter, useParens: boolean = false): string { + if (tag instanceof Tag) { + return `object.tags["${tag.key}"] == "${tag.value}"` } - return `process_poi_${this._id}` - } - - public generateFunction(): string { - if (!this._tags) { - return undefined + if (tag instanceof And) { + const expr = tag.and.map(t => this.toLuaFilter(t, true)).join(" and ") + if (useParens) { + return "(" + expr + ")" + } + return expr } - return [ - `local pois_${this._id} = osm2pgsql.define_table({`, - this._foundInThemes ? "-- used in themes: " + this._foundInThemes.join(", ") : "", - ` name = '${this._id}',`, - " ids = { type = 'any', type_column = 'osm_type', id_column = 'osm_id' },", - " columns = {", - " { column = 'tags', type = 'jsonb' },", - " { column = 'geom', type = 'point', projection = 4326, not_null = true },", - " }" + - "})", - "", - "", - `function ${this.functionName()}(object, geom)`, - " local matches_filter = " + this.toLuaFilter(this._tags), - " if( not matches_filter) then", - " return", - " end", - " local a = {", - " geom = geom,", - " tags = object.tags", - " }", - " ", - ` pois_${this._id}:insert(a)`, - "end", - "", - ].join("\n") + if (tag instanceof Or) { + const expr = tag.or.map(t => this.toLuaFilter(t, true)).join(" or ") + if (useParens) { + return "(" + expr + ")" + } + return expr + } + if (tag instanceof RegexTag) { + let expr = LuaSnippets.regexTagToLua(tag) + if (useParens) { + expr = "(" + expr + ")" + } + return expr + } + let msg = "Could not handle" + tag.asHumanString(false, false, {}) + console.error(msg) + throw msg } - private regexTagToLua(tag: RegexTag) { + private static regexTagToLua(tag: RegexTag) { if (typeof tag.value === "string" && tag.invert) { return `object.tags["${tag.key}"] ~= "${tag.value}"` } - const v = ( tag.value).source.replace(/\\\//g, "/") + const v = (tag.value).source.replace(/\\\//g, "/") if ("" + tag.value === "/.+/is" && !tag.invert) { return `object.tags["${tag.key}"] ~= nil` @@ -115,35 +81,58 @@ class GenerateLayerLua { return `(object.tags["${tag.key}"] ~= nil and string.find(object.tags["${tag.key}"], "${v}"))` } - private toLuaFilter(tag: TagsFilter, useParens: boolean = false): string { - if (tag instanceof Tag) { - return `object.tags["${tag.key}"] == "${tag.value}"` - } - if (tag instanceof And) { - const expr = tag.and.map(t => this.toLuaFilter(t, true)).join(" and ") - if (useParens) { - return "(" + expr + ")" - } - return expr - } - if (tag instanceof Or) { - const expr = tag.or.map(t => this.toLuaFilter(t, true)).join(" or ") - if (useParens) { - return "(" + expr + ")" - } - return expr - } - if (tag instanceof RegexTag) { - let expr = this.regexTagToLua(tag) - if (useParens) { - expr = "(" + expr + ")" - } - return expr - } - let msg = "Could not handle" + tag.asHumanString(false, false, {}) - console.error(msg) - throw msg +} + +class GenerateLayerLua { + private readonly _id: string + private readonly _tags: TagsFilter + private readonly _foundInThemes: string[] + + constructor(id: string, tags: TagsFilter, foundInThemes: string[] = []) { + this._tags = tags + this._id = id + this._foundInThemes = foundInThemes } + + public generateTables(): string { + if (!this._tags) { + return undefined + } + return [ + `db_tables.pois_${this._id} = osm2pgsql.define_table({`, + this._foundInThemes ? "-- used in themes: " + this._foundInThemes.join(", ") : "", + ` name = 'pois_${this._id}',`, + " ids = { type = 'any', type_column = 'osm_type', id_column = 'osm_id' },", + " columns = {", + " { column = 'tags', type = 'jsonb' },", + " { column = 'geom', type = 'point', projection = 4326, not_null = true },", + " }", + "})", + "", + `db_tables.lines_${this._id} = osm2pgsql.define_table({`, + this._foundInThemes ? "-- used in themes: " + this._foundInThemes.join(", ") : "", + ` name = 'lines_${this._id}',`, + " ids = { type = 'any', type_column = 'osm_type', id_column = 'osm_id' },", + " columns = {", + " { column = 'tags', type = 'jsonb' },", + " { column = 'geom', type = 'linestring', projection = 4326, not_null = true },", + " }", + "})", + + `db_tables.polygons_${this._id} = osm2pgsql.define_table({`, + this._foundInThemes ? "-- used in themes: " + this._foundInThemes.join(", ") : "", + ` name = 'polygons_${this._id}',`, + " ids = { type = 'any', type_column = 'osm_type', id_column = 'osm_id' },", + " columns = {", + " { column = 'tags', type = 'jsonb' },", + " { column = 'geom', type = 'polygon', projection = 4326, not_null = true },", + " }", + "})", + "", + ].join("\n") + } + + } class GenerateBuildDbScript extends Script { @@ -163,14 +152,93 @@ class GenerateBuildDbScript extends Script { }) const script = [ - ...generators.map(g => g.generateFunction()), - LuaSnippets.combine(Utils.NoNull(generators.map(g => g.functionName()))), - LuaSnippets.tail, + "local db_tables = {}", + LuaSnippets.helpers, + ...generators.map(g => g.generateTables()), + this.generateProcessPoi(allNeededLayers), + this.generateProcessWay(allNeededLayers), ].join("\n\n\n") const path = "build_db.lua" fs.writeFileSync(path, script, "utf-8") console.log("Written", path) - console.log(allNeededLayers.size+" layers will be created. Make sure to set 'max_connections' to at least "+(10 + allNeededLayers.size) ) + console.log(allNeededLayers.size + " layers will be created with 3 tables each. Make sure to set 'max_connections' to at least " + (10 + 3 * allNeededLayers.size)) + } + + private earlyAbort() { + return [" if countTbl(object.tags) == 0 then", + " return", + " end", + ""].join("\n") + } + + private generateProcessPoi(allNeededLayers: Map) { + const body: string[] = [] + allNeededLayers.forEach(({ tags }, layerId) => { + body.push( + this.insertInto(tags, layerId, "pois_").join("\n"), + ) + }) + + return [ + "function osm2pgsql.process_node(object)", + this.earlyAbort(), + " local geom = object:as_point()", + " local matches_filter = false", + body.join("\n"), + "end", + ].join("\n") + } + + /** + * If matches_filter + * @param tags + * @param layerId + * @param tableprefix + * @private + */ + private insertInto(tags: TagsFilter, layerId: string, tableprefix: "pois_" | "lines_" | "polygons_") { + const filter = LuaSnippets.toLuaFilter(tags) + return [ + " matches_filter = " + filter, + " if matches_filter then", + " db_tables." + tableprefix + layerId + ":insert({", + " geom = geom,", + " tags = object.tags", + " })", + " end", + ] + } + + private generateProcessWay(allNeededLayers: Map) { + const bodyLines: string[] = [] + allNeededLayers.forEach(({ tags }, layerId) => { + bodyLines.push(this.insertInto(tags, layerId, "lines_").join("\n")) + }) + + const bodyPolygons: string[] = [] + allNeededLayers.forEach(({ tags }, layerId) => { + bodyPolygons.push(this.insertInto(tags, layerId, "polygons_").join("\n")) + }) + + return [ + "function process_polygon(object, geom)", + " local matches_filter", + ...bodyPolygons, + "end", + "function process_linestring(object, geom)", + " local matches_filter", + ...bodyLines, + "end", + "", + "function osm2pgsql.process_way(object)", + this.earlyAbort(), + " if object.is_closed then", + " process_polygon(object, object:as_polygon())", + " else", + " process_linestring(object, object:as_linestring())", + " end", + "end", + ].join("\n") } } diff --git a/src/Logic/FeatureSource/FeatureSource.ts b/src/Logic/FeatureSource/FeatureSource.ts index 3b9798dbb..bd5ecbfc2 100644 --- a/src/Logic/FeatureSource/FeatureSource.ts +++ b/src/Logic/FeatureSource/FeatureSource.ts @@ -16,6 +16,12 @@ export interface FeatureSourceForLayer extends Feat readonly layer: FilteredLayer } +export interface FeatureSourceForTile extends FeatureSource { + readonly x: number + readonly y: number + readonly z: number + +} /** * A feature source which is aware of the indexes it contains */ diff --git a/src/Logic/FeatureSource/Sources/FeatureSourceMerger.ts b/src/Logic/FeatureSource/Sources/FeatureSourceMerger.ts index 951b688c5..7ea4d1635 100644 --- a/src/Logic/FeatureSource/Sources/FeatureSourceMerger.ts +++ b/src/Logic/FeatureSource/Sources/FeatureSourceMerger.ts @@ -2,43 +2,49 @@ import { Store, UIEventSource } from "../../UIEventSource" import { FeatureSource, IndexedFeatureSource } from "../FeatureSource" import { Feature } from "geojson" import { Utils } from "../../../Utils" +import DynamicTileSource from "../TiledFeatureSource/DynamicTileSource" /** - * + * The featureSourceMerger receives complete geometries from various sources. + * If multiple sources contain the same object (as determined by 'id'), only one copy of them is retained */ -export default class FeatureSourceMerger implements IndexedFeatureSource { +export default class FeatureSourceMerger implements IndexedFeatureSource { public features: UIEventSource = new UIEventSource([]) public readonly featuresById: Store> - private readonly _featuresById: UIEventSource> - private readonly _sources: FeatureSource[] = [] + protected readonly _featuresById: UIEventSource> + private readonly _sources: Src[] = [] /** * Merges features from different featureSources. * In case that multiple features have the same id, the latest `_version_number` will be used. Otherwise, we will take the last one */ - constructor(...sources: FeatureSource[]) { + constructor(...sources: Src[]) { this._featuresById = new UIEventSource>(new Map()) this.featuresById = this._featuresById const self = this sources = Utils.NoNull(sources) for (let source of sources) { source.features.addCallback(() => { - self.addData(sources.map((s) => s.features.data)) + self.addDataFromSources(sources) }) } - this.addData(sources.map((s) => s.features.data)) + this.addDataFromSources(sources) this._sources = sources } - public addSource(source: FeatureSource) { + public addSource(source: Src) { if (!source) { return } this._sources.push(source) source.features.addCallbackAndRun(() => { - this.addData(this._sources.map((s) => s.features.data)) + this.addDataFromSources(this._sources) }) } + protected addDataFromSources(sources: Src[]){ + this.addData(sources.map(s => s.features.data)) + } + protected addData(sources: Feature[][]) { sources = Utils.NoNull(sources) let somethingChanged = false @@ -56,7 +62,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource { const id = f.properties.id unseen.delete(id) if (!all.has(id)) { - // This is a new feature + // This is a new, previously unseen feature somethingChanged = true all.set(id, f) continue @@ -81,10 +87,8 @@ export default class FeatureSourceMerger implements IndexedFeatureSource { return } - const newList = [] - all.forEach((value) => { - newList.push(value) - }) + const newList = Array.from(all.values()) + this.features.setData(newList) this._featuresById.setData(all) } diff --git a/src/Logic/FeatureSource/Sources/MvtSource.ts b/src/Logic/FeatureSource/Sources/MvtSource.ts index 8c93eee9c..3aeb34400 100644 --- a/src/Logic/FeatureSource/Sources/MvtSource.ts +++ b/src/Logic/FeatureSource/Sources/MvtSource.ts @@ -1,6 +1,6 @@ import { Feature, Geometry } from "geojson" import { Store, UIEventSource } from "../../UIEventSource" -import { FeatureSource } from "../FeatureSource" +import { FeatureSourceForTile } from "../FeatureSource" import Pbf from "pbf" import * as pbfCompile from "pbf/compile" import * as PbfSchema from "protocol-buffers-schema" @@ -19,8 +19,67 @@ class MvtFeatureBuilder { this._y0 = extent * y } - public toGeoJson(geometry, typeIndex, properties): Feature { - let coords: [number, number] | Coords | Coords[] = this.encodeGeometry(geometry) + private static signedArea(ring: Coords): number { + let sum = 0 + const len = ring.length + // J is basically (i - 1) % len + let j = len - 1 + let p1 + let p2 + for (let i = 0; i < len; i++) { + p1 = ring[i] + p2 = ring[j] + sum += (p2.x - p1.x) * (p1.y + p2.y) + j = i + } + return sum + } + + /** + * + * const rings = [ [ [ 3.208361864089966, 51.186908820014736 ], [ 3.2084155082702637, 51.18689537073311 ], [ 3.208436965942383, 51.186888646090836 ], [ 3.2084155082702637, 51.18686174751187 ], [ 3.2084155082702637, 51.18685502286465 ], [ 3.2083725929260254, 51.18686847215807 ], [ 3.2083404064178467, 51.18687519680333 ], [ 3.208361864089966, 51.186908820014736 ] ] ] + * MvtFeatureBuilder.classifyRings(rings) // => [rings] + */ + private static classifyRings(rings: Coords[]): Coords[][] { + if (rings.length <= 0) { + throw "Now rings in polygon found" + } + if (rings.length == 1) { + return [rings] + } + + const polygons: Coords[][] = [] + let currentPolygon: Coords[] + + for (let i = 0; i < rings.length; i++) { + let ring = rings[i] + const area = this.signedArea(ring) + if (area === 0) { + // Weird, degenerate ring + continue + } + const ccw = area < 0 + + if (ccw === (area < 0)) { + if (currentPolygon) { + polygons.push(currentPolygon) + } + currentPolygon = [ring] + + } else { + currentPolygon.push(ring) + } + } + if (currentPolygon) { + polygons.push(currentPolygon) + } + + return polygons + } + + public toGeoJson(geometry: number[], typeIndex: 1 | 2 | 3, properties: any): Feature { + let coords: Coords[] = this.encodeGeometry(geometry) + let classified = undefined switch (typeIndex) { case 1: const points = [] @@ -38,9 +97,9 @@ class MvtFeatureBuilder { break case 3: - let classified = this.classifyRings(coords) - for (let i = 0; i < coords.length; i++) { - for (let j = 0; j < coords[i].length; j++) { + classified = MvtFeatureBuilder.classifyRings(coords) + for (let i = 0; i < classified.length; i++) { + for (let j = 0; j < classified[i].length; j++) { this.project(classified[i][j]) } } @@ -48,9 +107,11 @@ class MvtFeatureBuilder { } let type: string = MvtFeatureBuilder.geom_types[typeIndex] + let polygonCoords: Coords | Coords[] | Coords[][] if (coords.length === 1) { - coords = coords[0] + polygonCoords = (classified ?? coords)[0] } else { + polygonCoords = classified ?? coords type = "Multi" + type } @@ -58,13 +119,22 @@ class MvtFeatureBuilder { type: "Feature", geometry: { type: type, - coordinates: coords, + coordinates: polygonCoords, }, properties, } } - private encodeGeometry(geometry: number[]) { + /** + * + * const geometry = [9,233,8704,130,438,1455,270,653,248,423,368,493,362,381,330,267,408,301,406,221,402,157,1078,429,1002,449,1036,577,800,545,1586,1165,164,79,40] + * const builder = new MvtFeatureBuilder(4096, 66705, 43755, 17) + * const expected = [[3.2106759399175644,51.213658395282124],[3.2108227908611298,51.21396418776169],[3.2109133154153824,51.21410154168976],[3.210996463894844,51.214190590500664],[3.211119845509529,51.214294340548975],[3.211241215467453,51.2143745681588],[3.2113518565893173,51.21443085341426],[3.211488649249077,51.21449427925393],[3.2116247713565826,51.214540903490956],[3.211759552359581,51.21457408647774],[3.2121209800243378,51.214664394485254],[3.212456926703453,51.21475890267553],[3.2128042727708817,51.214880292910834],[3.213072493672371,51.214994962285544],[3.2136042416095734,51.21523984134939],[3.2136592268943787,51.21525664260963],[3.213672637939453,51.21525664260963]] + * builder.project(builder.encodeGeometry(geometry)[0]) // => expected + * @param geometry + * @private + */ + private encodeGeometry(geometry: number[]): Coords[] { let cX = 0 let cY = 0 let coordss: Coords[] = [] @@ -86,7 +156,7 @@ class MvtFeatureBuilder { currentRing = [] } } - if (commandId === 1 || commandId === 2){ + if (commandId === 1 || commandId === 2) { for (let j = 0; j < commandCount; j++) { const dx = geometry[i + j * 2 + 1] cX += ((dx >> 1) ^ (-(dx & 1))) @@ -94,10 +164,11 @@ class MvtFeatureBuilder { cY += ((dy >> 1) ^ (-(dy & 1))) currentRing.push([cX, cY]) } - i = commandCount * 2 + i += commandCount * 2 } - if(commandId === 7){ + if (commandId === 7) { currentRing.push([...currentRing[0]]) + i++ } } @@ -107,62 +178,12 @@ class MvtFeatureBuilder { return coordss } - private signedArea(ring: Coords): number { - let sum = 0 - const len = ring.length - // J is basically (i - 1) % len - let j = len - 1 - let p1 - let p2 - for (let i = 0; i < len; i++) { - p1 = ring[i] - p2 = ring[j] - sum += (p2.x - p1.x) * (p1.y + p2.y) - j = i - } - return sum - } - - private classifyRings(rings: Coords[]): Coords[][] { - const len = rings.length - - if (len <= 1) return [rings] - - const polygons = [] - let polygon - // CounterClockWise - let ccw: boolean - - for (let i = 0; i < len; i++) { - const area = this.signedArea(rings[i]) - if (area === 0) continue - - if (ccw === undefined) { - ccw = area < 0 - } - if (ccw === (area < 0)) { - if (polygon) { - polygons.push(polygon) - } - polygon = [rings[i]] - - } else { - polygon.push(rings[i]) - } - } - if (polygon) { - polygons.push(polygon) - } - - return polygons - } - /** * Inline replacement of the location by projecting - * @param line - * @private + * @param line the line which will be rewritten inline + * @return line */ - private project(line: [number, number][]) { + private project(line: Coords) { const y0 = this._y0 const x0 = this._x0 const size = this._size @@ -174,12 +195,13 @@ class MvtFeatureBuilder { 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90, ] } + return line } } -export default class MvtSource implements FeatureSource { +export default class MvtSource implements FeatureSourceForTile { - private static readonly schemaSpec = ` + private static readonly schemaSpec21 = ` package vector_tile; option optimize_for = LITE_RUNTIME; @@ -259,26 +281,30 @@ message Tile { extensions 16 to 8191; } ` - private static readonly tile_schema = pbfCompile(PbfSchema.parse(MvtSource.schemaSpec)).Tile - - + private static readonly tile_schema = (pbfCompile.default ?? pbfCompile)(PbfSchema.parse(MvtSource.schemaSpec21)).Tile + public readonly features: Store[]> private readonly _url: string private readonly _layerName: string private readonly _features: UIEventSource[]> = new UIEventSource[]>([]) - public readonly features: Store[]> = this._features - private readonly x: number - private readonly y: number - private readonly z: number + public readonly x: number + public readonly y: number + public readonly z: number - constructor(url: string, x: number, y: number, z: number, layerName?: string) { + constructor(url: string, x: number, y: number, z: number, layerName?: string, isActive?: Store) { this._url = url this._layerName = layerName this.x = x this.y = y this.z = z this.downloadSync() + this.features = this._features.map(fs => { + if (fs === undefined || isActive?.data === false) { + return [] + } + return fs + }, [isActive]) } private getValue(v: { @@ -316,16 +342,23 @@ message Tile { } - private downloadSync(){ + private downloadSync() { this.download().then(d => { - if(d.length === 0){ + if (d.length === 0) { return } return this._features.setData(d) - }).catch(e => {console.error(e)}) + }).catch(e => { + console.error(e) + }) } + private async download(): Promise { const result = await fetch(this._url) + if (result.status !== 200) { + console.error("Could not download tile " + this._url) + return [] + } const buffer = await result.arrayBuffer() const data = MvtSource.tile_schema.read(new Pbf(buffer)) const layers = data.layers @@ -336,7 +369,7 @@ message Tile { } layer = layers.find(l => l.name === this._layerName) } - if(!layer){ + if (!layer) { return [] } const builder = new MvtFeatureBuilder(layer.extent, this.x, this.y, this.z) diff --git a/src/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts index 88455e9bb..010af52ff 100644 --- a/src/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts +++ b/src/Logic/FeatureSource/TiledFeatureSource/DynamicGeoJsonTileSource.ts @@ -1,4 +1,4 @@ -import { Store } from "../../UIEventSource" +import { ImmutableStore, Store } from "../../UIEventSource" import DynamicTileSource from "./DynamicTileSource" import { Utils } from "../../../Utils" import GeoJsonSource from "../Sources/GeoJsonSource" @@ -65,7 +65,7 @@ export default class DynamicGeoJsonTileSource extends DynamicTileSource { const blackList = new Set() super( - source.geojsonZoomLevel, + new ImmutableStore(source.geojsonZoomLevel), layer.minzoom, (zxy) => { if (whitelist !== undefined) { diff --git a/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts index ac8713332..3b7476768 100644 --- a/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts +++ b/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts @@ -1,13 +1,45 @@ import { Store } from "../../UIEventSource" -import DynamicTileSource from "./DynamicTileSource" +import DynamicTileSource, { PolygonSourceMerger } from "./DynamicTileSource" import { Utils } from "../../../Utils" import { BBox } from "../../BBox" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import MvtSource from "../Sources/MvtSource" import { Tiles } from "../../../Models/TileRange" import Constants from "../../../Models/Constants" +import FeatureSourceMerger from "../Sources/FeatureSourceMerger" -export default class DynamicMvtileSource extends DynamicTileSource { + +class PolygonMvtSource extends PolygonSourceMerger{ + constructor( layer: LayerConfig, + mapProperties: { + zoom: Store + bounds: Store + }, + options?: { + isActive?: Store + }) { + const roundedZoom = mapProperties.zoom.mapD(z => Math.min(Math.floor(z/2)*2, 14)) + super( + roundedZoom, + layer.minzoom, + (zxy) => { + const [z, x, y] = Tiles.tile_from_index(zxy) + const url = Utils.SubstituteKeys(Constants.VectorTileServer, + { + z, x, y, layer: layer.id, + type: "polygons", + }) + return new MvtSource(url, x, y, z) + }, + mapProperties, + { + isActive: options?.isActive, + }) + } +} + + +class PointMvtSource extends DynamicTileSource { constructor( layer: LayerConfig, @@ -19,14 +51,16 @@ export default class DynamicMvtileSource extends DynamicTileSource { isActive?: Store }, ) { + const roundedZoom = mapProperties.zoom.mapD(z => Math.min(Math.floor(z/2)*2, 14)) super( - mapProperties.zoom, + roundedZoom, layer.minzoom, (zxy) => { const [z, x, y] = Tiles.tile_from_index(zxy) const url = Utils.SubstituteKeys(Constants.VectorTileServer, { z, x, y, layer: layer.id, + type: "pois", }) return new MvtSource(url, x, y, z) }, @@ -37,3 +71,24 @@ export default class DynamicMvtileSource extends DynamicTileSource { ) } } + +export default class DynamicMvtileSource extends FeatureSourceMerger { + + constructor( + layer: LayerConfig, + mapProperties: { + zoom: Store + bounds: Store + }, + options?: { + isActive?: Store + }, + ) { + const roundedZoom = mapProperties.zoom.mapD(z => Math.floor(z)) + super( + new PointMvtSource(layer, mapProperties, options), + new PolygonMvtSource(layer, mapProperties, options) + + ) + } +} diff --git a/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts index 3bb5affd9..f905ae893 100644 --- a/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts +++ b/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts @@ -1,25 +1,37 @@ import { Store, Stores } from "../../UIEventSource" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" -import { FeatureSource } from "../FeatureSource" +import { FeatureSource, FeatureSourceForTile } from "../FeatureSource" import FeatureSourceMerger from "../Sources/FeatureSourceMerger" +import { Feature } from "geojson" +import { Utils } from "../../../Utils" +import { GeoOperations } from "../../GeoOperations" + /*** * A tiled source which dynamically loads the required tiles at a fixed zoom level. * A single featureSource will be initialized for every tile in view; which will later be merged into this featureSource */ -export default class DynamicTileSource extends FeatureSourceMerger { +export default class DynamicTileSource extends FeatureSourceMerger { + /** + * + * @param zoomlevel If {z} is specified in the source, the 'zoomlevel' will be used as zoomlevel to download from + * @param minzoom Only activate this feature source if zoomed in further then this + * @param constructSource + * @param mapProperties + * @param options + */ constructor( zoomlevel: Store, minzoom: number, - constructSource: (tileIndex: number) => FeatureSource, + constructSource: (tileIndex: number) => Src, mapProperties: { bounds: Store zoom: Store }, options?: { isActive?: Store - } + }, ) { super() const loadedTiles = new Set() @@ -34,32 +46,32 @@ export default class DynamicTileSource extends FeatureSourceMerger { if (mapProperties.zoom.data < minzoom) { return undefined } - const z = Math.round(zoomlevel.data) + const z = Math.floor(zoomlevel.data) const tileRange = Tiles.TileRangeBetween( z, bounds.getNorth(), bounds.getEast(), bounds.getSouth(), - bounds.getWest() + bounds.getWest(), ) if (tileRange.total > 500) { console.warn( - "Got a really big tilerange, bounds and location might be out of sync" + "Got a really big tilerange, bounds and location might be out of sync", ) return undefined } const needed = Tiles.MapRange(tileRange, (x, y) => - Tiles.tile_index(z, x, y) + Tiles.tile_index(z, x, y), ).filter((i) => !loadedTiles.has(i)) if (needed.length === 0) { return undefined } return needed }, - [options?.isActive, mapProperties.zoom] + [options?.isActive, mapProperties.zoom], ) - .stabilized(250) + .stabilized(250), ) neededTiles.addCallbackAndRunD((neededIndexes) => { @@ -70,3 +82,70 @@ export default class DynamicTileSource extends FeatureSourceMerger { }) } } + + +/** + * The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together. + * This is used to reconstruct polygons of vector tiles + */ +export class PolygonSourceMerger extends DynamicTileSource { + constructor( + zoomlevel: Store, + minzoom: number, + constructSource: (tileIndex: number) => FeatureSourceForTile, + mapProperties: { + bounds: Store + zoom: Store + }, + options?: { + isActive?: Store + }, + ) { + super(zoomlevel, minzoom, constructSource, mapProperties, options) + } + + protected addDataFromSources(sources: FeatureSourceForTile[]) { + sources = Utils.NoNull(sources) + const all: Map = new Map() + const zooms: Map = new Map() + + for (const source of sources) { + let z = source.z + for (const f of source.features.data) { + const id = f.properties.id + if(id.endsWith("146616907")){ + console.log("Horeca totaal") + } + if (!all.has(id)) { + // No other parts of this polygon have been seen before, simply add it + all.set(id, f) + zooms.set(id, z) + continue + } + + // A part of this object has been seen before, eventually from a different zoom level + const oldV = all.get(id) + const oldZ = zooms.get(id) + if (oldZ > z) { + // The store contains more detailed information, so we ignore this part which has a lower accuraccy + continue + } + if (oldZ < z) { + // The old value has worse accuracy then what we receive now, we throw it away + all.set(id, f) + zooms.set(id, z) + continue + } + const merged = GeoOperations.union(f, oldV) + merged.properties = oldV.properties + all.set(id, merged) + zooms.set(id, z) + } + } + + const newList = Array.from(all.values()) + this.features.setData(newList) + this._featuresById.setData(all) + } + +} diff --git a/src/UI/Map/ShowDataLayer.ts b/src/UI/Map/ShowDataLayer.ts index 64a3cfe94..75c7213b0 100644 --- a/src/UI/Map/ShowDataLayer.ts +++ b/src/UI/Map/ShowDataLayer.ts @@ -303,7 +303,6 @@ class LineRenderingLayer { type: "FeatureCollection", features, }, - cluster: true, promoteId: "id", }) const linelayer = this._layername + "_line" diff --git a/src/UI/Test.svelte b/src/UI/Test.svelte index 70f864f1b..4e278c4b1 100644 --- a/src/UI/Test.svelte +++ b/src/UI/Test.svelte @@ -4,20 +4,18 @@ import MaplibreMap from "./Map/MaplibreMap.svelte" import { Map as MlMap } from "maplibre-gl" import { MapLibreAdaptor } from "./Map/MapLibreAdaptor" - import Constants from "../Models/Constants" - import toilet from "../assets/generated/layers/toilet.json" + import shops from "../assets/generated/layers/shops.json" import LayerConfig from "../Models/ThemeConfig/LayerConfig" import DynamicMvtileSource from "../Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource" import ShowDataLayer from "./Map/ShowDataLayer" - const tl = new LayerConfig(toilet) + const tl = new LayerConfig(shops) let map: UIEventSource = new UIEventSource(undefined) let adaptor = new MapLibreAdaptor(map) const src = new DynamicMvtileSource(tl, adaptor) - src.features.addCallbackAndRun(f => console.log(">>> Features are", f)) new ShowDataLayer(map, { layer: tl, features: src @@ -27,94 +25,7 @@ lat: 51.2095, lon: 3.2260, }) adaptor.zoom.setData(13) - const loadedIcons = new Set() - async function loadImage(map: MlMap, url: string, name: string): Promise { - return new Promise((resolve, reject) => { - if (loadedIcons.has(name)) { - return new Promise((resolve, reject) => resolve()) - } - loadedIcons.add(name) - if (Constants.defaultPinIcons.indexOf(url) >= 0) { - url = "./assets/svg/" + url + ".svg" - } - map.loadImage( - url, - (error, image) => { - if (error) { - reject(error) - } - map.addImage(name, image) - resolve() - }) - }) - } - - map.addCallbackAndRunD(map => { - map.on("load", async () => { - console.log("Onload") - await loadImage(map, "https://upload.wikimedia.org/wikipedia/commons/7/7c/201408_cat.png", "cat") - - /* - map.addSource("drinking_water", { - "type": "vector", - "tiles": ["http://127.0.0.2:7800/public.drinking_water/{z}/{x}/{y}.pbf"], // http://127.0.0.2:7800/public.drinking_water.json", - }) - - map.addLayer( - { - "id": "drinking_water_layer", - "type": "circle", - "source": "drinking_water", - "source-layer": "public.drinking_water", - "paint": { - "circle-radius": 5, - "circle-color": "#ff00ff", - "circle-stroke-width": 2, - "circle-stroke-color": "#000000", - }, - }, - )*/ - /* - map.addSource("toilet", { - "type": "vector", - "tiles": ["http://127.0.0.2:7800/public.toilet/{z}/{x}/{y}.pbf"], // http://127.0.0.2:7800/public.drinking_water.json", - }) - - map.addLayer( - { - "id": "toilet_layer", - "type": "circle", - "source": "toilet", - "source-layer": "public.toilet", - "paint": { - "circle-radius": 5, - "circle-color": "#0000ff", - "circle-stroke-width": 2, - "circle-stroke-color": "#000000", - }, - }, - ) - map.addLayer({ - "id": "points", - "type": "symbol", - "source": "toilet", - "source-layer": "public.toilet", - "layout": { - "icon-overlap": "always", - "icon-image": "cat", - "icon-size": 0.05, - }, - })*/ - - - map.on("click", "drinking_water_layer", (e) => { -// Copy coordinates array. - console.log(e) - console.warn(">>>", e.features[0]) - }) - }) - })
From 5b318236bfece8eb7a43d4e67ff676bcf3f54fc8 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 2 Feb 2024 20:04:39 +0100 Subject: [PATCH 08/57] LayerServer: fix some bugs in build_db-script, decode MVT-tilesource for more features --- scripts/osm2pgsql/generateBuildDbScript.ts | 37 +++++- .../FeatureSource/Sources/LayoutSource.ts | 8 +- .../DynamicMvtTileSource.ts | 36 ++++- .../TiledFeatureSource/DynamicTileSource.ts | 70 +--------- .../TiledFeatureSource/LineSourceMerger.ts | 80 +++++++++++ .../TiledFeatureSource/PolygonSourceMerger.ts | 73 +++++++++++ src/Logic/GeoOperations.ts | 124 ++++++++++++++---- src/Logic/Osm/OsmObject.ts | 2 +- 8 files changed, 327 insertions(+), 103 deletions(-) create mode 100644 src/Logic/FeatureSource/TiledFeatureSource/LineSourceMerger.ts create mode 100644 src/Logic/FeatureSource/TiledFeatureSource/PolygonSourceMerger.ts diff --git a/scripts/osm2pgsql/generateBuildDbScript.ts b/scripts/osm2pgsql/generateBuildDbScript.ts index 4559f4683..5f6eb28b7 100644 --- a/scripts/osm2pgsql/generateBuildDbScript.ts +++ b/scripts/osm2pgsql/generateBuildDbScript.ts @@ -7,6 +7,7 @@ import { Or } from "../../src/Logic/Tags/Or" import { RegexTag } from "../../src/Logic/Tags/RegexTag" import { ValidateThemeEnsemble } from "../../src/Models/ThemeConfig/Conversion/Validation" import { AllKnownLayouts } from "../../src/Customizations/AllKnownLayouts" +import { OsmObject } from "../../src/Logic/Osm/OsmObject" class LuaSnippets { @@ -20,6 +21,33 @@ class LuaSnippets { "end", ].join("\n") + public static isPolygonFeature(): { blacklist: TagsFilter, whitelisted: TagsFilter } { + const dict = OsmObject.polygonFeatures + const or: TagsFilter[] = [] + const blacklisted : TagsFilter[] = [] + dict.forEach(({ values, blacklist }, k) => { + if(blacklist){ + if(values === undefined){ + blacklisted.push(new RegexTag(k, /.+/is)) + return + } + values.forEach(v => { + blacklisted.push(new RegexTag(k, v)) + }) + return + } + if (values === undefined || values === null) { + or.push(new RegexTag(k, /.+/is)) + return + } + values.forEach(v => { + or.push(new RegexTag(k, v)) + }) + }) + console.log("Polygon features are:", or.map(t => t.asHumanString(false, false, {}))) + return { blacklist: new Or(blacklisted), whitelisted: new Or(or) } + } + public static toLuaFilter(tag: TagsFilter, useParens: boolean = false): string { if (tag instanceof Tag) { return `object.tags["${tag.key}"] == "${tag.value}"` @@ -55,6 +83,10 @@ class LuaSnippets { return `object.tags["${tag.key}"] ~= "${tag.value}"` } + if (typeof tag.value === "string" && !tag.invert) { + return `object.tags["${tag.key}"] == "${tag.value}"` + } + const v = (tag.value).source.replace(/\\\//g, "/") if ("" + tag.value === "/.+/is" && !tag.invert) { @@ -220,6 +252,7 @@ class GenerateBuildDbScript extends Script { bodyPolygons.push(this.insertInto(tags, layerId, "polygons_").join("\n")) }) + const isPolygon = LuaSnippets.isPolygonFeature() return [ "function process_polygon(object, geom)", " local matches_filter", @@ -232,7 +265,9 @@ class GenerateBuildDbScript extends Script { "", "function osm2pgsql.process_way(object)", this.earlyAbort(), - " if object.is_closed then", + " local object_is_line = not object.is_closed or "+LuaSnippets.toLuaFilter(isPolygon.blacklist), + ` local object_is_area = object.is_closed and (object.tags["area"] == "yes" or (not object_is_line and ${LuaSnippets.toLuaFilter(isPolygon.whitelisted, true)}))`, + " if object_is_area then", " process_polygon(object, object:as_polygon())", " else", " process_linestring(object, object:as_linestring())", diff --git a/src/Logic/FeatureSource/Sources/LayoutSource.ts b/src/Logic/FeatureSource/Sources/LayoutSource.ts index a46d81384..9361217ce 100644 --- a/src/Logic/FeatureSource/Sources/LayoutSource.ts +++ b/src/Logic/FeatureSource/Sources/LayoutSource.ts @@ -59,7 +59,7 @@ export default class LayoutSource extends FeatureSourceMerger { zoom, featureSwitches )//*/ - +/* const osmApiSource = LayoutSource.setupOsmApiSource( osmLayers, bounds, @@ -67,14 +67,14 @@ export default class LayoutSource extends FeatureSourceMerger { backend, featureSwitches, fullNodeDatabaseSource - ) + )*/ const geojsonSources: FeatureSource[] = geojsonlayers.map((l) => LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id)) ) - super(osmApiSource, ...geojsonSources, ...fromCache, ...mvtSources) + super(...geojsonSources, ...fromCache, ...mvtSources) const self = this function setIsLoading() { @@ -83,7 +83,7 @@ export default class LayoutSource extends FeatureSourceMerger { } // overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading()) - osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading()) + // osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading()) } private static setupMvtSource(layer: LayerConfig, mapProperties: { zoom: Store; bounds: Store }, isActive?: Store): FeatureSource{ diff --git a/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts index 3b7476768..ed379bfb1 100644 --- a/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts +++ b/src/Logic/FeatureSource/TiledFeatureSource/DynamicMvtTileSource.ts @@ -1,5 +1,5 @@ import { Store } from "../../UIEventSource" -import DynamicTileSource, { PolygonSourceMerger } from "./DynamicTileSource" +import DynamicTileSource from "./DynamicTileSource" import { Utils } from "../../../Utils" import { BBox } from "../../BBox" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" @@ -7,6 +7,8 @@ import MvtSource from "../Sources/MvtSource" import { Tiles } from "../../../Models/TileRange" import Constants from "../../../Models/Constants" import FeatureSourceMerger from "../Sources/FeatureSourceMerger" +import { LineSourceMerger } from "./LineSourceMerger" +import { PolygonSourceMerger } from "./PolygonSourceMerger" class PolygonMvtSource extends PolygonSourceMerger{ @@ -39,6 +41,36 @@ class PolygonMvtSource extends PolygonSourceMerger{ } +class LineMvtSource extends LineSourceMerger{ + constructor( layer: LayerConfig, + mapProperties: { + zoom: Store + bounds: Store + }, + options?: { + isActive?: Store + }) { + const roundedZoom = mapProperties.zoom.mapD(z => Math.min(Math.floor(z/2)*2, 14)) + super( + roundedZoom, + layer.minzoom, + (zxy) => { + const [z, x, y] = Tiles.tile_from_index(zxy) + const url = Utils.SubstituteKeys(Constants.VectorTileServer, + { + z, x, y, layer: layer.id, + type: "lines", + }) + return new MvtSource(url, x, y, z) + }, + mapProperties, + { + isActive: options?.isActive, + }) + } +} + + class PointMvtSource extends DynamicTileSource { constructor( @@ -84,9 +116,9 @@ export default class DynamicMvtileSource extends FeatureSourceMerger { isActive?: Store }, ) { - const roundedZoom = mapProperties.zoom.mapD(z => Math.floor(z)) super( new PointMvtSource(layer, mapProperties, options), + new LineMvtSource(layer, mapProperties, options), new PolygonMvtSource(layer, mapProperties, options) ) diff --git a/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts index f905ae893..efd109fd5 100644 --- a/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts +++ b/src/Logic/FeatureSource/TiledFeatureSource/DynamicTileSource.ts @@ -1,11 +1,8 @@ import { Store, Stores } from "../../UIEventSource" import { Tiles } from "../../../Models/TileRange" import { BBox } from "../../BBox" -import { FeatureSource, FeatureSourceForTile } from "../FeatureSource" +import { FeatureSource } from "../FeatureSource" import FeatureSourceMerger from "../Sources/FeatureSourceMerger" -import { Feature } from "geojson" -import { Utils } from "../../../Utils" -import { GeoOperations } from "../../GeoOperations" /*** @@ -84,68 +81,3 @@ export default class DynamicTileSource { - constructor( - zoomlevel: Store, - minzoom: number, - constructSource: (tileIndex: number) => FeatureSourceForTile, - mapProperties: { - bounds: Store - zoom: Store - }, - options?: { - isActive?: Store - }, - ) { - super(zoomlevel, minzoom, constructSource, mapProperties, options) - } - - protected addDataFromSources(sources: FeatureSourceForTile[]) { - sources = Utils.NoNull(sources) - const all: Map = new Map() - const zooms: Map = new Map() - - for (const source of sources) { - let z = source.z - for (const f of source.features.data) { - const id = f.properties.id - if(id.endsWith("146616907")){ - console.log("Horeca totaal") - } - if (!all.has(id)) { - // No other parts of this polygon have been seen before, simply add it - all.set(id, f) - zooms.set(id, z) - continue - } - - // A part of this object has been seen before, eventually from a different zoom level - const oldV = all.get(id) - const oldZ = zooms.get(id) - if (oldZ > z) { - // The store contains more detailed information, so we ignore this part which has a lower accuraccy - continue - } - if (oldZ < z) { - // The old value has worse accuracy then what we receive now, we throw it away - all.set(id, f) - zooms.set(id, z) - continue - } - const merged = GeoOperations.union(f, oldV) - merged.properties = oldV.properties - all.set(id, merged) - zooms.set(id, z) - } - } - - const newList = Array.from(all.values()) - this.features.setData(newList) - this._featuresById.setData(all) - } - -} diff --git a/src/Logic/FeatureSource/TiledFeatureSource/LineSourceMerger.ts b/src/Logic/FeatureSource/TiledFeatureSource/LineSourceMerger.ts new file mode 100644 index 000000000..8a1cb3909 --- /dev/null +++ b/src/Logic/FeatureSource/TiledFeatureSource/LineSourceMerger.ts @@ -0,0 +1,80 @@ +import { FeatureSourceForTile } from "../FeatureSource" +import { Store } from "../../UIEventSource" +import { BBox } from "../../BBox" +import { Utils } from "../../../Utils" +import { Feature, LineString, MultiLineString, Position } from "geojson" +import { Tiles } from "../../../Models/TileRange" +import { GeoOperations } from "../../GeoOperations" +import DynamicTileSource from "./DynamicTileSource" + +/** + * The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together. + * This is used to reconstruct polygons of vector tiles + */ +export class LineSourceMerger extends DynamicTileSource { + private readonly _zoomlevel: Store + + constructor( + zoomlevel: Store, + minzoom: number, + constructSource: (tileIndex: number) => FeatureSourceForTile, + mapProperties: { + bounds: Store + zoom: Store + }, + options?: { + isActive?: Store + }, + ) { + super(zoomlevel, minzoom, constructSource, mapProperties, options) + this._zoomlevel = zoomlevel + } + + protected addDataFromSources(sources: FeatureSourceForTile[]) { + sources = Utils.NoNull(sources) + const all: Map> = new Map() + const currentZoom = this._zoomlevel?.data ?? 0 + for (const source of sources) { + if(source.z != currentZoom){ + continue + } + const bboxCoors = Tiles.tile_bounds_lon_lat(source.z, source.x, source.y) + const bboxGeo = new BBox(bboxCoors).asGeoJson({}) + for (const f of source.features.data) { + const id = f.properties.id + const coordinates : Position[][] = [] + if(f.geometry.type === "LineString"){ + coordinates.push(f.geometry.coordinates) + }else if(f.geometry.type === "MultiLineString"){ + coordinates.push(...f.geometry.coordinates) + }else { + console.error("Invalid geometry type:", f.geometry.type) + continue + } + const oldV = all.get(id) + if(!oldV){ + + all.set(id, { + type: "Feature", + properties: f.properties, + geometry:{ + type:"MultiLineString", + coordinates + } + }) + continue + } + oldV.geometry.coordinates.push(...coordinates) + } + } + + const keys = Array.from(all.keys()) + for (const key of keys) { + all.set(key, GeoOperations.attemptLinearize(>all.get(key))) + } + const newList = Array.from(all.values()) + this.features.setData(newList) + this._featuresById.setData(all) + } + +} diff --git a/src/Logic/FeatureSource/TiledFeatureSource/PolygonSourceMerger.ts b/src/Logic/FeatureSource/TiledFeatureSource/PolygonSourceMerger.ts new file mode 100644 index 000000000..47327d872 --- /dev/null +++ b/src/Logic/FeatureSource/TiledFeatureSource/PolygonSourceMerger.ts @@ -0,0 +1,73 @@ +import { FeatureSourceForTile } from "../FeatureSource" +import { Store } from "../../UIEventSource" +import { BBox } from "../../BBox" +import { Utils } from "../../../Utils" +import { Feature } from "geojson" +import { GeoOperations } from "../../GeoOperations" +import DynamicTileSource from "./DynamicTileSource" + +/** + * The PolygonSourceMerger receives various small pieces of bigger polygons and stitches them together. + * This is used to reconstruct polygons of vector tiles + */ +export class PolygonSourceMerger extends DynamicTileSource { + constructor( + zoomlevel: Store, + minzoom: number, + constructSource: (tileIndex: number) => FeatureSourceForTile, + mapProperties: { + bounds: Store + zoom: Store + }, + options?: { + isActive?: Store + }, + ) { + super(zoomlevel, minzoom, constructSource, mapProperties, options) + } + + protected addDataFromSources(sources: FeatureSourceForTile[]) { + sources = Utils.NoNull(sources) + const all: Map = new Map() + const zooms: Map = new Map() + + for (const source of sources) { + let z = source.z + for (const f of source.features.data) { + const id = f.properties.id + if (id.endsWith("146616907")) { + console.log("Horeca totaal") + } + if (!all.has(id)) { + // No other parts of this polygon have been seen before, simply add it + all.set(id, f) + zooms.set(id, z) + continue + } + + // A part of this object has been seen before, eventually from a different zoom level + const oldV = all.get(id) + const oldZ = zooms.get(id) + if (oldZ > z) { + // The store contains more detailed information, so we ignore this part which has a lower accuraccy + continue + } + if (oldZ < z) { + // The old value has worse accuracy then what we receive now, we throw it away + all.set(id, f) + zooms.set(id, z) + continue + } + const merged = GeoOperations.union(f, oldV) + merged.properties = oldV.properties + all.set(id, merged) + zooms.set(id, z) + } + } + + const newList = Array.from(all.values()) + this.features.setData(newList) + this._featuresById.setData(all) + } + +} diff --git a/src/Logic/GeoOperations.ts b/src/Logic/GeoOperations.ts index c90869b83..7567a8bca 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, Lines } from "@turf/turf" import { Feature, FeatureCollection, @@ -156,7 +156,7 @@ export class GeoOperations { const intersection = GeoOperations.calculateIntersection( feature, otherFeature, - featureBBox + featureBBox, ) if (intersection === null) { continue @@ -195,7 +195,7 @@ export class GeoOperations { console.error( "Could not correctly calculate the overlap of ", feature, - ": unsupported type" + ": unsupported type", ) return result } @@ -224,7 +224,7 @@ export class GeoOperations { */ public static inside( pointCoordinate: [number, number] | Feature, - feature: Feature + feature: Feature, ): boolean { // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html @@ -302,7 +302,7 @@ export class GeoOperations { */ public static nearestPoint( way: Feature, - point: [number, number] + point: [number, number], ): Feature< Point, { @@ -324,11 +324,11 @@ export class GeoOperations { public static forceLineString(way: Feature): Feature public static forceLineString( - way: Feature + way: Feature, ): Feature public static forceLineString( - way: Feature + way: Feature, ): Feature { if (way.geometry.type === "Polygon") { way = { ...way } @@ -448,7 +448,7 @@ export class GeoOperations { */ public static LineIntersections( feature: Feature, - otherFeature: Feature + otherFeature: Feature, ): [number, number][] { return turf .lineIntersect(feature, otherFeature) @@ -485,7 +485,7 @@ export class GeoOperations { locations: | Feature | Feature[], - title?: string + title?: string, ) { title = title?.trim() if (title === undefined || title === "") { @@ -506,7 +506,7 @@ export class GeoOperations { type: "Point", coordinates: p, }, - } + }, ) } for (const l of locationsWithMeta) { @@ -521,7 +521,7 @@ export class GeoOperations { trackPoints.push(trkpt) } const header = - '' + "" return ( header + "\n" + @@ -539,7 +539,7 @@ export class GeoOperations { */ public static toGpxPoints( locations: Feature[], - title?: string + title?: string, ) { title = title?.trim() if (title === undefined || title === "") { @@ -560,7 +560,7 @@ export class GeoOperations { trackPoints.push(trkpt) } const header = - '' + "" return ( header + "\n" + @@ -648,7 +648,7 @@ export class GeoOperations { }, }, distanceMeter, - { units: "meters" } + { units: "meters" }, ).geometry.coordinates } @@ -683,7 +683,7 @@ export class GeoOperations { */ static completelyWithin( feature: Feature, - possiblyEnclosingFeature: Feature + possiblyEnclosingFeature: Feature, ): boolean { return booleanWithin(feature, possiblyEnclosingFeature) } @@ -714,6 +714,23 @@ export class GeoOperations { } return kept } + + if (toSplit.geometry.type === "MultiLineString") { + const lines: Feature[][] = toSplit.geometry.coordinates.map(coordinates => + turf.lineSplit( {type: "LineString", coordinates}, boundary).features ) + const splitted: Feature[] = [].concat(...lines) + const kept: Feature[] = [] + for (const f of splitted) { + console.log("Checking", f) + if (!GeoOperations.inside(GeoOperations.centerpointCoordinates(f), boundary)) { + continue + } + f.properties = { ...toSplit.properties } + kept.push(f) + } + console.log(">>>", {lines, splitted, kept}) + return kept + } if (toSplit.geometry.type === "Polygon" || toSplit.geometry.type == "MultiPolygon") { const splitup = turf.intersect(>toSplit, boundary) splitup.properties = { ...toSplit.properties } @@ -739,7 +756,7 @@ export class GeoOperations { */ public static featureToCoordinateWithRenderingType( feature: Feature, - location: "point" | "centroid" | "start" | "end" | "projected_centerpoint" | string + location: "point" | "centroid" | "start" | "end" | "projected_centerpoint" | string, ): [number, number] | undefined { switch (location) { case "point": @@ -760,7 +777,7 @@ export class GeoOperations { const centerpoint = GeoOperations.centerpointCoordinates(feature) const projected = GeoOperations.nearestPoint( >feature, - centerpoint + centerpoint, ) return <[number, number]>projected.geometry.coordinates } @@ -937,7 +954,7 @@ export class GeoOperations { * GeoOperations.bearingToHuman(46) // => "NE" */ public static bearingToHuman( - bearing: number + bearing: number, ): "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW" { while (bearing < 0) { bearing += 360 @@ -956,7 +973,7 @@ export class GeoOperations { * GeoOperations.bearingToHuman(46) // => "NE" */ public static bearingToHumanRelative( - bearing: number + bearing: number, ): | "straight" | "slight_right" @@ -975,18 +992,73 @@ export class GeoOperations { return GeoOperations.directionsRelative[segment] } + /** + * const coors = [[[3.217198532946432,51.218067],[3.216807134449482,51.21849812105347],[3.2164304037883706,51.2189272]],[[3.2176208,51.21760169669458],[3.217198560167068,51.218067]]] + * const f = {geometry: {coordinates: coors}} + * const merged = GeoOperations.attemptLinearize(f) + * merged.geometry.coordinates // => [[3.2176208,51.21760169669458],[3.217198532946432,51.218067], [3.216807134449482,51.21849812105347],[3.2164304037883706,51.2189272]] + */ + static attemptLinearize(multiLineStringFeature: Feature): Feature { + const coors = multiLineStringFeature.geometry.coordinates + if(coors.length === 0) { + console.error(multiLineStringFeature.geometry) + throw "Error: got degenerate multilinestring" + } + outer: for (let i = coors.length - 1; i >= 0; i--) { + // We try to match the first element of 'i' with another, earlier list `j` + // If a match is found with `j`, j is extended and `i` is scrapped + const iFirst = coors[i][0] + for (let j = 0; j < coors.length; j++) { + if (i == j) { + continue + } + + const jLast = coors[j].at(-1) + if (!(Math.abs(iFirst[0] - jLast[0]) < 0.000001 && Math.abs(iFirst[1] - jLast[1]) < 0.0000001)) { + continue + } + coors[j].splice(coors.length - 1, 1) + coors[j].push(...coors[i]) + coors.splice(i, 1) + continue outer + } + } + if(coors.length === 0) { + throw "No more coordinates found" + } + + if (coors.length === 1) { + return { + type: "Feature", + properties: multiLineStringFeature.properties, + geometry: { + type: "LineString", + coordinates: coors[0], + }, + } + } + return { + type: "Feature", + properties: multiLineStringFeature.properties, + geometry: { + type: "MultiLineString", + coordinates: coors, + }, + } + } + /** * Helper function which does the heavy lifting for 'inside' */ private static pointInPolygonCoordinates( x: number, y: number, - coordinates: [number, number][][] + coordinates: [number, number][][], ): boolean { const inside = GeoOperations.pointWithinRing( x, y, - /*This is the outer ring of the polygon */ coordinates[0] + /*This is the outer ring of the polygon */ coordinates[0], ) if (!inside) { return false @@ -995,7 +1067,7 @@ export class GeoOperations { const inHole = GeoOperations.pointWithinRing( x, y, - coordinates[i] /* These are inner rings, aka holes*/ + coordinates[i], /* These are inner rings, aka holes*/ ) if (inHole) { return false @@ -1033,7 +1105,7 @@ export class GeoOperations { feature, otherFeature, featureBBox: BBox, - otherFeatureBBox?: BBox + otherFeatureBBox?: BBox, ): number { if (feature.geometry.type === "LineString") { otherFeatureBBox = otherFeatureBBox ?? BBox.get(otherFeature) @@ -1082,7 +1154,7 @@ export class GeoOperations { let intersection = turf.lineSlice( turf.point(intersectionPointsArray[0]), turf.point(intersectionPointsArray[1]), - feature + feature, ) if (intersection == null) { @@ -1103,7 +1175,7 @@ export class GeoOperations { otherFeature, feature, otherFeatureBBox, - featureBBox + featureBBox, ) } @@ -1123,7 +1195,7 @@ export class GeoOperations { console.log("Applying fallback intersection...") const intersection = turf.intersect( turf.truncate(feature), - turf.truncate(otherFeature) + turf.truncate(otherFeature), ) if (intersection == null) { return null diff --git a/src/Logic/Osm/OsmObject.ts b/src/Logic/Osm/OsmObject.ts index f0e0215cd..d743b58c9 100644 --- a/src/Logic/Osm/OsmObject.ts +++ b/src/Logic/Osm/OsmObject.ts @@ -7,7 +7,7 @@ import { Feature, LineString, Polygon } from "geojson" export abstract class OsmObject { private static defaultBackend = "https://api.openstreetmap.org/" protected static backendURL = OsmObject.defaultBackend - private static polygonFeatures = OsmObject.constructPolygonFeatures() + public static polygonFeatures = OsmObject.constructPolygonFeatures() type: "node" | "way" | "relation" id: number /** From 74fb4bd5d1c9a8c33e68d47c48663e736ba79661 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 15 Feb 2024 17:39:59 +0100 Subject: [PATCH 09/57] Add summary layer --- Docs/SettingUpPSQL.md | 11 + assets/layers/ice_cream/ice_cream.json | 1 + assets/layers/last_click/last_click.json | 2 +- assets/layers/summary/summary.json | 25 ++ assets/layers/toilet/toilet.json | 145 +++++++++-- assets/themes/shops/shops.json | 4 +- scripts/osm2pgsql/tilecountServer.ts | 232 ++++++++++++++++-- .../Sources/LastClickFeatureSource.ts | 20 +- .../TiledFeatureSource/SummaryTileSource.ts | 85 +++++++ src/Logic/UIEventSource.ts | 5 +- src/Models/Constants.ts | 4 +- src/Models/ThemeConfig/LayerConfig.ts | 5 +- src/Models/ThemeConfig/LayoutConfig.ts | 8 + .../ThemeConfig/PointRenderingConfig.ts | 3 +- src/Models/ThemeViewState.ts | 59 +++-- src/UI/DownloadFlow/DownloadButton.svelte | 1 - src/UI/Popup/AddNewPoint/AddNewPoint.svelte | 1 - src/UI/SpecialVisualization.ts | 1 - src/UI/ThemeViewGUI.svelte | 9 +- 19 files changed, 533 insertions(+), 88 deletions(-) create mode 100644 assets/layers/summary/summary.json create mode 100644 src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts diff --git a/Docs/SettingUpPSQL.md b/Docs/SettingUpPSQL.md index e3a4ae13b..1b7ed9aa9 100644 --- a/Docs/SettingUpPSQL.md +++ b/Docs/SettingUpPSQL.md @@ -36,6 +36,17 @@ Storing properties to table '"public"."osm2pgsql_properties" takes about 25 minu Belgium (~555mb) takes 15m World (80GB) should take 15m*160 = 2400m = 40hr +73G Jan 23 00:22 planet-240115.osm.pbf: 2024-02-10 16:45:11 osm2pgsql took 871615s (242h 6m 55s; 10 days) overall on lain.local with RAID5 on 4 HDD disks, database is over 1Terrabyte (!) + +Server specs + +Lenovo thinkserver RD350, Intel Xeon E5-2600, 2Rx4 PC3 + 11 watt powered off, 73 watt idle, ~100 watt when importing + +HP ProLiant DL360 G7 (1U): 2Rx4 DDR3-memory (PC3) + Intel Xeon X56** + + ## Deploying a tile server diff --git a/assets/layers/ice_cream/ice_cream.json b/assets/layers/ice_cream/ice_cream.json index b6d6576a9..8b5ea0568 100644 --- a/assets/layers/ice_cream/ice_cream.json +++ b/assets/layers/ice_cream/ice_cream.json @@ -4,6 +4,7 @@ "en": "Ice cream parlors", "de": "Eisdielen" }, + "minzoom": 14, "description": { "en": "A place where ice cream is sold over the counter", "de": "Ein Ort, an dem Eiscreme an der Theke verkauft wird" diff --git a/assets/layers/last_click/last_click.json b/assets/layers/last_click/last_click.json index c99a4ce5b..335731bda 100644 --- a/assets/layers/last_click/last_click.json +++ b/assets/layers/last_click/last_click.json @@ -1,7 +1,7 @@ { "id": "last_click", "name": null, - "description": "This layer defines how to render the 'last click'-location. By default, it will show a marker with the possibility to add a new point (if there are some presets) and/or to add a new note (if the 'note' layer attribute is set). If none are possible, this layer won't show up", + "description": "This 'layer' is not really a layer, but contains part of the code how the popup to 'add a new marker' is displayed", "source": "special", "isShown": { "or": [ diff --git a/assets/layers/summary/summary.json b/assets/layers/summary/summary.json new file mode 100644 index 000000000..ecffca76a --- /dev/null +++ b/assets/layers/summary/summary.json @@ -0,0 +1,25 @@ +{ + "id": "summary", + "description": "Special layer which shows `count`", + "source": "special", + "name": "CLusters", + "title": { + "render": {"en": "Summary"} + }, + "tagRenderings": [ + "all_tags" + ], + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "iconSize": "25,25", + "label": { + "render": "{total}" + }, + "labelCssClasses": "bg-white w-6 h-6 text-lg rounded-full" + } + ] +} diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index 2d58241b6..192a60ab7 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -26,7 +26,7 @@ "source": { "osmTags": "amenity=toilets" }, - "minzoom": 9, + "minzoom": 10, "title": { "render": { "en": "Toilet", @@ -548,6 +548,115 @@ } ] }, + { + "condition": "toilets:position!=urinal", + "id": "gender_segregated", + "question": { + "en": "Are these toilets gender-segregated?", + "nl": "Zijn deze toiletten gescheiden op basis van geslacht?" + }, + "questionHint": { + "en": "Are there separate stalls or separate areas for men and women and are they signposted as such?", + "nl": "Is er een aparte ruimte voor mannen en vrouwen en zijn deze ruimtes ook expliciet aangegeven?" + }, + "mappings": [ + { + "if": "gender_segregated=yes", + "then": { + "en": "There is a separate, signposted area for men and women", + "nl": "Er zijn aparte ruimtes of toiletten voor mannen en vrouwen" + } + }, + { + "if": "gender_segregated=no", + "then": { + "en": "There is no separate, signposted area for men and women", + "nl": "Mannen en vrouwen gebruiken dezelfde ruimtes en toiletten" + } + } + ] + }, + { + "id": "menstrual_products", + "question": { + "en": "Are free, menstrual products distributed here?", + "nl": "Zijn er gratis menstruatieproducten beschikbaar?" + }, + "questionHint": { + "en": "This is only about menstrual products that are free of charge. If e.g. a vending machine is available which charges for menstrual products, ignore it for this question.", + "nl": "Dit gaat enkel over menstruatieproducten die gratis geschikbaar zijn. Indien er bv. een verkoopautomaat met menstruatieproducten is, negeer deze dan" + }, + "mappings": [ + { + "if": "toilets:menstrual_products=yes", + "then": { + "en": "Free menstrual products are available to all visitors of these toilets", + "nl": "Er zijn gratis menstruatieprocten beschikbaar voor alle bezoekers van deze toiletten" + } + }, + { + "if": "toilets:menstrual_products=limited", + "then": { + "en": "Free menstrual products are available to some visitors of these toilets", + "nl": "De gratis menstruatieproducten zijn enkel beschikbaar in een deel van de toiletten" + }, + "hideInAnswer": "gender_segregated=yes" + }, + { + "if": "toilets:menstrual_products=no", + "alsoShowIf": "toilets:menstrual_products=", + "then": { + "en": "No free menstrual products are available here", + "nl": "Er zijn geen gratis menstruatieproducten beschikbaar" + } + } + ] + }, + { + "id": "menstrual_products_location", + "question": { + "en": "Where are the free menstrual products located?", + "nl": "Waar bevinden de gratis menstruatieproducten zich?" + }, + "condition": { + "or": [ + "toilets:menstrual_products=limited", + "toilets:menstrual_products:location~*" + ] + }, + "render": { + "en": "The menstrual products are located in {toilets:menstrual_products:location}", + "nl": "De menstruatieproducten bevinden zich in {toilets:menstrual_products:location}" + }, + "freeform": { + "key": "toilets:menstrual_products:location", + "inline": true + }, + "mappings": [ + { + "then": { + "en": "The free, menstrual products are located in the toilet for women", + "nl": "De gratis menstruatieproducten bevinden zich in het vrouwentoilet" + }, + "if": "toilets:menstrual_products:location=female_toilet", + "alsoShowIf": "toilets:menstrual_products:location=" + }, + { + "then": { + "en": "The free, menstrual products are located in the toilet for men", + "nl": "De gratis menstruatieproducten bevinden zich in het mannentoilet" + }, + "if": "toilets:menstrual_products:location=male_toilet" + }, + { + "if": "toilets:menstrual_products:location=wheelchair_toilet", + "then": { + "en": "The free, menstrual products are located in the toilet for wheelchair users", + "nl": "De gratis menstruatieproducten bevinden zich in het rolstoeltoegankelijke toilet" + } + } + ] + }, { "id": "toilets-changing-table", "labels": [ @@ -576,7 +685,8 @@ "ca": "Hi ha un canviador per a nadons", "cs": "Přebalovací pult je k dispozici" }, - "if": "changing_table=yes" + "if": "changing_table=yes", + "icon": "./assets/layers/toilet/baby.svg" }, { "if": "changing_table=no", @@ -610,10 +720,10 @@ "cs": "Kde je umístěn přebalovací pult?" }, "render": { - "en": "The changing table is located at {changing_table:location}", - "de": "Die Wickeltabelle befindet sich in {changing_table:location}", + "en": "A changing table is located at {changing_table:location}", + "de": "Ein Wickeltisch befindet sich in {changing_table:location}", "fr": "Emplacement de la table à langer : {changing_table:location}", - "nl": "De luiertafel bevindt zich in {changing_table:location}", + "nl": "Er bevindt zich een luiertafel in {changing_table:location}", "it": "Il fasciatoio si trova presso {changing_table:location}", "es": "El cambiador está en {changing_table:location}", "da": "Puslebordet er placeret på {changing_table:location}", @@ -632,10 +742,10 @@ "mappings": [ { "then": { - "en": "The changing table is in the toilet for women. ", - "de": "Der Wickeltisch befindet sich in der Damentoilette. ", + "en": "A changing table is in the toilet for women", + "de": "Ein Wickeltisch ist in der Damentoilette vorhanden", "fr": "La table à langer est dans les toilettes pour femmes. ", - "nl": "De luiertafel bevindt zich in de vrouwentoiletten ", + "nl": "Er bevindt zich een luiertafel in de vrouwentoiletten ", "it": "Il fasciatoio è nei servizi igienici femminili. ", "da": "Puslebordet er på toilettet til kvinder. ", "ca": "El canviador està al lavabo per a dones. ", @@ -645,10 +755,10 @@ }, { "then": { - "en": "The changing table is in the toilet for men. ", - "de": "Der Wickeltisch befindet sich in der Herrentoilette. ", + "en": "A changing table is in the toilet for men", + "de": "Ein Wickeltisch ist in der Herrentoilette vorhanden", "fr": "La table à langer est dans les toilettes pour hommes. ", - "nl": "De luiertafel bevindt zich in de herentoiletten ", + "nl": "Er bevindt zich een luiertafel in de herentoiletten ", "it": "Il fasciatoio è nei servizi igienici maschili. ", "ca": "El canviador està al lavabo per a homes. ", "cs": "Přebalovací pult je na pánské toaletě. " @@ -658,10 +768,10 @@ { "if": "changing_table:location=wheelchair_toilet", "then": { - "en": "The changing table is in the toilet for wheelchair users. ", - "de": "Der Wickeltisch befindet sich in der Toilette für Rollstuhlfahrer. ", + "en": "A changing table is in the toilet for wheelchair users", + "de": "Ein Wickeltisch ist in der barrierefreien Toilette vorhanden", "fr": "La table à langer est dans les toilettes pour personnes à mobilité réduite. ", - "nl": "De luiertafel bevindt zich in de rolstoeltoegankelijke toilet ", + "nl": "Er bevindt zich een luiertafel in de rolstoeltoegankelijke toilet ", "it": "Il fasciatoio è nei servizi igienici per persone in sedia a rotelle. ", "da": "Puslebordet er på toilettet for kørestolsbrugere. ", "ca": "El canviador està al lavabo per a usuaris de cadira de rodes. ", @@ -671,10 +781,10 @@ { "if": "changing_table:location=dedicated_room", "then": { - "en": "The changing table is in a dedicated room. ", - "de": "Der Wickeltisch befindet sich in einem eigenen Raum. ", + "en": "A changing table is in a dedicated room", + "de": "Ein Wickeltisch befindet sich in einem eigenen Raum", "fr": "La table à langer est dans un espace dédié. ", - "nl": "De luiertafel bevindt zich in een daartoe voorziene kamer ", + "nl": "Er bevindt zich een luiertafel in een daartoe voorziene kamer ", "it": "Il fasciatoio è in una stanza dedicata. ", "es": "El cambiador está en una habitación dedicada ", "da": "Vuggestuen står i et særligt rum. ", @@ -683,6 +793,7 @@ } } ], + "multiAnswer": true, "id": "toilet-changing_table:location" }, { diff --git a/assets/themes/shops/shops.json b/assets/themes/shops/shops.json index 0024d9837..abe44dc0e 100644 --- a/assets/themes/shops/shops.json +++ b/assets/themes/shops/shops.json @@ -54,5 +54,7 @@ "pharmacy", "ice_cream" ], - "widenFactor": 3 + "overideAll": { + "minzoom": 16 + } } diff --git a/scripts/osm2pgsql/tilecountServer.ts b/scripts/osm2pgsql/tilecountServer.ts index 5c33c72b7..8980e9939 100644 --- a/scripts/osm2pgsql/tilecountServer.ts +++ b/scripts/osm2pgsql/tilecountServer.ts @@ -1,44 +1,240 @@ -import { BBox } from "../../src/Logic/BBox" import { Client } from "pg" +import http from "node:http" +import { Tiles } from "../../src/Models/TileRange" + +/** + * Just the OSM2PGSL default database + */ +interface PoiDatabaseMeta { + attributes + current_timestamp + db_format + flat_node_file + import_timestamp + output + prefix + replication_base_url + replication_sequence_number + replication_timestamp + style + updatable + version +} /** * Connects with a Postgis database, gives back how much items there are within the given BBOX */ -export default class TilecountServer { +class OsmPoiDatabase { + private static readonly prefixes: ReadonlyArray = ["pois", "lines", "polygons"] private readonly _client: Client private isConnected = false + private supportedLayers: string[] = undefined + private metaCache: PoiDatabaseMeta = undefined + private metaCacheDate: Date = undefined constructor(connectionString: string) { this._client = new Client(connectionString) } - async getCount(layer: string, bbox: BBox = undefined): Promise { + async getCount( + layer: string, + bbox: [[number, number], [number, number]] = undefined + ): Promise { if (!this.isConnected) { await this._client.connect() this.isConnected = true } - let query = "SELECT COUNT(*) FROM " + layer + let total = 0 - if(bbox){ - query += ` WHERE ST_MakeEnvelope (${bbox.minLon}, ${bbox.minLat}, ${bbox.maxLon}, ${bbox.maxLat}, 4326) ~ geom` + for (const prefix of OsmPoiDatabase.prefixes) { + let query = "SELECT COUNT(*) FROM " + prefix + "_" + layer + + if (bbox) { + query += ` WHERE ST_MakeEnvelope (${bbox[0][0]}, ${bbox[0][1]}, ${bbox[1][0]}, ${bbox[1][1]}, 4326) ~ geom` + } + console.log("Query:", query) + const result = await this._client.query(query) + total += Number(result.rows[0].count) } -console.log(query) - const result = await this._client.query(query) - return result.rows[0].count + return total } disconnect() { this._client.end() } + + async getLayers(): Promise { + if (this.supportedLayers !== undefined) { + return this.supportedLayers + } + const result = await this._client.query( + "SELECT table_name \n" + + "FROM information_schema.tables \n" + + "WHERE table_schema = 'public' AND table_name LIKE 'lines_%';" + ) + const layers = result.rows.map((r) => r.table_name.substring("lines_".length)) + this.supportedLayers = layers + return layers + } + + async getMeta(): Promise { + const now = new Date() + if (this.metaCache !== undefined) { + const diffSec = (this.metaCacheDate.getTime() - now.getTime()) / 1000 + if (diffSec < 120) { + return this.metaCache + } + } + const result = await this._client.query("SELECT * FROM public.osm2pgsql_properties") + const meta = {} + for (const { property, value } of result.rows) { + meta[property] = value + } + this.metaCacheDate = now + this.metaCache = meta + return this.metaCache + } } -const tcs = new TilecountServer("postgresql://user:none@localhost:5444/osm-poi") -console.log(">>>", await tcs.getCount("drinking_water", new BBox([ - [1.5052013991654007, - 42.57480750272123, - ], [ - 1.6663677350703097, - 42.499856652770745, - ]]))) -tcs.disconnect() +class Server { + constructor( + port: number, + handle: { + mustMatch: string | RegExp + mimetype: string + handle: (path: string) => Promise + }[] + ) { + handle.push({ + mustMatch: "", + mimetype: "text/html", + handle: async () => { + return `Supported endpoints are
    ${handle + .filter((h) => h.mustMatch !== "") + .map((h) => { + let l = h.mustMatch + if (typeof h.mustMatch === "string") { + l = `${l}` + } + return "
  • " + l + "
  • " + }) + .join("")}
` + }, + }) + http.createServer(async (req: http.IncomingMessage, res) => { + try { + console.log( + req.method + " " + req.url, + "from:", + req.headers.origin, + new Date().toISOString() + ) + + const url = new URL(`http://127.0.0.1/` + req.url) + let path = url.pathname + while (path.startsWith("/")) { + path = path.substring(1) + } + const handler = handle.find((h) => { + if (typeof h.mustMatch === "string") { + return h.mustMatch === path + } + if (path.match(h.mustMatch)) { + return true + } + }) + + if (handler === undefined || handler === null) { + res.writeHead(404, { "Content-Type": "text/html" }) + res.write("

Not found...

") + res.end() + return + } + + res.setHeader( + "Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept" + ) + res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*") + if (req.method === "OPTIONS") { + res.setHeader( + "Access-Control-Allow-Methods", + "POST, GET, OPTIONS, DELETE, UPDATE" + ) + res.writeHead(204, { "Content-Type": handler.mimetype }) + res.end() + return + } + if (req.method === "POST" || req.method === "UPDATE") { + return + } + + if (req.method === "DELETE") { + return + } + + try { + const result = await handler.handle(path) + res.writeHead(200, { "Content-Type": handler.mimetype }) + res.write(result) + res.end() + } catch (e) { + console.error("Could not handle request:", e) + res.writeHead(500) + res.write(e) + res.end() + } + } catch (e) { + console.error("FATAL:", e) + res.end() + } + }).listen(port) + console.log( + "Server is running on port " + port, + ". Supported endpoints are: " + handle.map((h) => h.mustMatch).join(", ") + ) + } +} + +const connectionString = "postgresql://user:password@localhost:5444/osm-poi" +const tcs = new OsmPoiDatabase(connectionString) +const server = new Server(2345, [ + { + mustMatch: "status.json", + mimetype: "application/json", + handle: async (path: string) => { + const layers = await tcs.getLayers() + const meta = await tcs.getMeta() + return JSON.stringify({ meta, layers }) + }, + }, + { + mustMatch: /[a-zA-Z0-9+]+\/[0-9]+\/[0-9]+\/[0-9]+\.json/, + mimetype: "application/json", // "application/vnd.geo+json", + async handle(path) { + console.log("Path is:", path, path.split(".")[0]) + const [layers, z, x, y] = path.split(".")[0].split("/") + + let sum = 0 + let properties: Record = {} + for (const layer of layers.split("+")) { + const count = await tcs.getCount( + layer, + Tiles.tile_bounds_lon_lat(Number(z), Number(x), Number(y)) + ) + properties[layer] = count + sum += count + } + + return JSON.stringify({ ...properties, total: sum }) + }, + }, +]) +console.log( + ">>>", + await tcs.getCount("drinking_water", [ + [3.194358020772171, 51.228073636083394], + [3.2839964396059145, 51.172701162680994], + ]) +) diff --git a/src/Logic/FeatureSource/Sources/LastClickFeatureSource.ts b/src/Logic/FeatureSource/Sources/LastClickFeatureSource.ts index c95857d70..3fe9b79e9 100644 --- a/src/Logic/FeatureSource/Sources/LastClickFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/LastClickFeatureSource.ts @@ -11,16 +11,15 @@ import { OsmTags } from "../../../Models/OsmFeature" * Highly specialized feature source. * Based on a lon/lat UIEVentSource, will generate the corresponding feature with the correct properties */ -export class LastClickFeatureSource implements WritableFeatureSource { - public readonly features: UIEventSource = new UIEventSource([]) - public readonly hasNoteLayer: boolean +export class LastClickFeatureSource { public readonly renderings: string[] - public readonly hasPresets: boolean private i: number = 0 + private readonly hasPresets: boolean + private readonly hasNoteLayer: boolean - constructor(location: Store<{ lon: number; lat: number }>, layout: LayoutConfig) { - this.hasNoteLayer = layout.layers.some((l) => l.id === "note") - this.hasPresets = layout.layers.some((l) => l.presets?.length > 0) + constructor(layout: LayoutConfig) { + this.hasNoteLayer = layout.hasNoteLayer() + this.hasPresets = layout.hasPresets() const allPresets: BaseUIElement[] = [] for (const layer of layout.layers) for (let i = 0; i < (layer.presets ?? []).length; i++) { @@ -43,16 +42,11 @@ export class LastClickFeatureSource implements WritableFeatureSource { Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML ) ) - - location.addCallbackAndRunD(({ lon, lat }) => { - this.features.setData([this.createFeature(lon, lat)]) - }) } public createFeature(lon: number, lat: number): Feature { const properties: OsmTags = { - lastclick: "yes", - id: "last_click_" + this.i, + id: "new_point_dialog", has_note_layer: this.hasNoteLayer ? "yes" : "no", has_presets: this.hasPresets ? "yes" : "no", renderings: this.renderings.join(""), diff --git a/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts b/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts new file mode 100644 index 000000000..2a7105fca --- /dev/null +++ b/src/Logic/FeatureSource/TiledFeatureSource/SummaryTileSource.ts @@ -0,0 +1,85 @@ +import DynamicTileSource from "./DynamicTileSource" +import { Store, UIEventSource } from "../../UIEventSource" +import { BBox } from "../../BBox" +import StaticFeatureSource from "../Sources/StaticFeatureSource" +import { Feature, Point } from "geojson" +import { Utils } from "../../../Utils" +import { Tiles } from "../../../Models/TileRange" + +/** + * Provides features summarizing the total amount of features at a given location + */ +export class SummaryTileSource extends DynamicTileSource { + private static readonly empty = [] + constructor( + cacheserver: string, + layers: string[], + zoomRounded: Store, + mapProperties: { + bounds: Store + zoom: Store + }, + options?: { + isActive?: Store + } + ) { + const layersSummed = layers.join("+") + super( + zoomRounded, + 0, // minzoom + (tileIndex) => { + const [z, x, y] = Tiles.tile_from_index(tileIndex) + const coordinates = Tiles.centerPointOf(z, x, y) + + const count = UIEventSource.FromPromiseWithErr( + Utils.downloadJson(`${cacheserver}/${layersSummed}/${z}/${x}/${y}.json`) + ) + const features: Store[]> = count.mapD((count) => { + if (count["error"] !== undefined) { + console.error( + "Could not download count for tile", + z, + x, + y, + "due to", + count["error"] + ) + return SummaryTileSource.empty + } + const counts = count["success"] + if (counts === undefined || counts["total"] === 0) { + return SummaryTileSource.empty + } + return [ + { + type: "Feature", + properties: { + id: "summary_" + tileIndex, + summary: "yes", + ...counts, + layers: layersSummed, + }, + geometry: { + type: "Point", + coordinates, + }, + }, + ] + }) + return new StaticFeatureSource( + features.map( + (f) => { + if (z !== zoomRounded.data) { + return SummaryTileSource.empty + } + return f + }, + [zoomRounded] + ) + ) + }, + mapProperties, + options + ) + } +} diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index ed48130ff..48bc9c6d9 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -638,8 +638,9 @@ export class UIEventSource extends Store implements Writable { promise: Promise ): UIEventSource<{ success: T } | { error: any } | undefined> { const src = new UIEventSource<{ success: T } | { error: any }>(undefined) - promise?.then((d) => src.setData({ success: d })) - promise?.catch((err) => src.setData({ error: err })) + promise + ?.then((d) => src.setData({ success: d })) + ?.catch((err) => src.setData({ error: err })) return src } diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index 0d76b4fc5..31181c5e6 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -24,6 +24,7 @@ export default class Constants { "range", "last_click", "favourite", + "summary", ] as const /** * Special layers which are not included in a theme by default @@ -36,7 +37,7 @@ export default class Constants { "import_candidate", "usersettings", "icons", - "filters" + "filters", ] as const /** * Layer IDs of layers which have special properties through built-in hooks @@ -151,7 +152,6 @@ export default class Constants { "mastodon", "party", "addSmall", - ] as const public static readonly defaultPinIcons: string[] = Constants._defaultPinIcons /** diff --git a/src/Models/ThemeConfig/LayerConfig.ts b/src/Models/ThemeConfig/LayerConfig.ts index 7b2feacd0..0f8b5facc 100644 --- a/src/Models/ThemeConfig/LayerConfig.ts +++ b/src/Models/ThemeConfig/LayerConfig.ts @@ -45,7 +45,6 @@ export default class LayerConfig extends WithContextLoader { public readonly isShown: TagsFilter public minzoom: number public minzoomVisible: number - public readonly maxzoom: number public readonly title?: TagRenderingConfig public readonly titleIcons: TagRenderingConfig[] public readonly mapRendering: PointRenderingConfig[] @@ -464,9 +463,7 @@ export default class LayerConfig extends WithContextLoader { return [ new Combine([ new Link( - Utils.runningFromConsole - ? "" - : Svg.statistics_svg().SetClass("w-4 h-4 mr-2"), + "", "https://taginfo.openstreetmap.org/keys/" + values.key + "#values", true ), diff --git a/src/Models/ThemeConfig/LayoutConfig.ts b/src/Models/ThemeConfig/LayoutConfig.ts index c9859b2c5..194089aeb 100644 --- a/src/Models/ThemeConfig/LayoutConfig.ts +++ b/src/Models/ThemeConfig/LayoutConfig.ts @@ -245,6 +245,14 @@ export default class LayoutConfig implements LayoutInformation { return this.layers.some((l) => l.isLeftRightSensitive()) } + public hasNoteLayer() { + return this.layers.some((l) => l.id === "note") + } + + public hasPresets() { + return this.layers.some((l) => l.presets?.length > 0) + } + public missingTranslations(extraInspection: any): { untranslated: Map total: number diff --git a/src/Models/ThemeConfig/PointRenderingConfig.ts b/src/Models/ThemeConfig/PointRenderingConfig.ts index a522c45a7..5f7974723 100644 --- a/src/Models/ThemeConfig/PointRenderingConfig.ts +++ b/src/Models/ThemeConfig/PointRenderingConfig.ts @@ -79,7 +79,6 @@ export default class PointRenderingConfig extends WithContextLoader { } }) - this.marker = (json.marker ?? []).map((m) => new IconConfig(m)) if (json.css !== undefined) { this.cssDef = this.tr("css", undefined) @@ -307,7 +306,7 @@ export default class PointRenderingConfig extends WithContextLoader { const label = self.label ?.GetRenderValue(tags) ?.Subs(tags) - ?.SetClass("block center absolute text-center marker-label") + ?.SetClass("flex items-center justify-center absolute marker-label") ?.SetClass(cssClassesLabel) if (cssLabel) { label.SetStyle(cssLabel) diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 011cf335a..83602f151 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -62,7 +62,9 @@ import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFe import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider" import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl" import Zoomcontrol from "../UI/Zoomcontrol" - +import { SummaryTileSource } from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" +import summaryLayer from "../assets/generated/layers/summary.json" +import { LayerConfigJson } from "./ThemeConfig/Json/LayerConfigJson" /** * * The themeviewState contains all the state needed for the themeViewGUI. @@ -140,7 +142,6 @@ export default class ThemeViewState implements SpecialVisualizationState { * Triggered by navigating the map with arrows or by pressing 'space' or 'enter' */ public readonly visualFeedback: UIEventSource = new UIEventSource(false) - private readonly newPointDialog: FilteredLayer constructor(layout: LayoutConfig) { Utils.initDomPurify() @@ -309,7 +310,6 @@ export default class ThemeViewState implements SpecialVisualizationState { fs.layer.layerDef.maxAgeOfCache ) }) - this.newPointDialog = this.layerState.filteredLayers.get("last_click") this.floors = this.featuresInView.features.stabilized(500).map((features) => { if (!features) { @@ -343,10 +343,7 @@ export default class ThemeViewState implements SpecialVisualizationState { return sorted }) - this.lastClickObject = new LastClickFeatureSource( - this.mapProperties.lastClickLocation, - this.layout - ) + this.lastClickObject = new LastClickFeatureSource(this.layout) this.osmObjectDownloader = new OsmObjectDownloader( this.osmConnection.Backend(), @@ -446,7 +443,6 @@ export default class ThemeViewState implements SpecialVisualizationState { const feature = this.lastClickObject.createFeature(lon, lat) this.featureProperties.trackFeature(feature) this.selectedElement.setData(feature) - this.selectedLayer.setData(this.newPointDialog.layerDef) } /** @@ -472,16 +468,6 @@ export default class ThemeViewState implements SpecialVisualizationState { this.userRelatedState.markLayoutAsVisited(this.layout) - this.selectedElement.addCallbackAndRunD((feature) => { - // As soon as we have a selected element, we clear the selected element - // This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature - // The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear - if (feature.properties.id === "last_click") { - return - } - this.lastClickObject.features.setData([]) - }) - this.selectedElement.addCallback((selected) => { if (selected === undefined) { Zoomcontrol.resetzoom() @@ -656,6 +642,19 @@ export default class ThemeViewState implements SpecialVisualizationState { }) } + private setupSummaryLayer() { + const layers = this.layout.layers.filter( + (l) => + Constants.priviliged_layers.indexOf(l.id) < 0 && + l.source.geojsonSource === undefined + ) + return new SummaryTileSource( + "http://127.0.0.1:2345", + layers.map((l) => l.id), + this.mapProperties.zoom.map((z) => Math.max(Math.ceil(z) + 1, 0)), + this.mapProperties + ) + } /** * Add the special layers to the map */ @@ -683,6 +682,7 @@ export default class ThemeViewState implements SpecialVisualizationState { ), current_view: this.currentView, favourite: this.favourites, + summary: this.setupSummaryLayer(), } this.closestFeatures.registerSource(specialLayers.favourite, "favourite") @@ -720,15 +720,16 @@ export default class ThemeViewState implements SpecialVisualizationState { rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) } - // enumarate all 'normal' layers and match them with the appropriate 'special' layer - if applicable + // enumerate all 'normal' layers and match them with the appropriate 'special' layer - if applicable this.layerState.filteredLayers.forEach((flayer) => { const id = flayer.layerDef.id const features: FeatureSource = specialLayers[id] if (features === undefined) { return } - if (id === "favourite") { - console.log("Matching special layer", id, flayer) + if (id === "summary") { + console.log("Skipping summary!") + return } this.featureProperties.trackFeatureSource(features) @@ -741,6 +742,20 @@ export default class ThemeViewState implements SpecialVisualizationState { selectedLayer: this.selectedLayer, }) }) + + const maxzoom = Math.min( + ...this.layout.layers + .filter((l) => Constants.priviliged_layers.indexOf(l.id) < 0) + .map((l) => l.minzoom) + ) + console.log("Maxzoom is", maxzoom) + new ShowDataLayer(this.map, { + features: specialLayers.summary, + layer: new LayerConfig(summaryLayer, "summaryLayer"), + doShowLayer: this.mapProperties.zoom.map((z) => z < maxzoom), + selectedLayer: this.selectedLayer, + selectedElement: this.selectedElement, + }) } /** @@ -761,8 +776,6 @@ export default class ThemeViewState implements SpecialVisualizationState { this.selectedElement.addCallback((selected) => { if (selected === undefined) { - // We did _unselect_ an item - we always remove the lastclick-object - this.lastClickObject.features.setData([]) this.selectedLayer.setData(undefined) this.focusOnMap() } diff --git a/src/UI/DownloadFlow/DownloadButton.svelte b/src/UI/DownloadFlow/DownloadButton.svelte index 229b74b9c..a8d0b1c72 100644 --- a/src/UI/DownloadFlow/DownloadButton.svelte +++ b/src/UI/DownloadFlow/DownloadButton.svelte @@ -35,7 +35,6 @@ async function clicked() { isExporting = true const gpsLayer = state.layerState.filteredLayers.get("gps_location") - state.lastClickObject.features.setData([]) state.userRelatedState.preferencesAsTags.data["__showTimeSensitiveIcons"] = "no" state.userRelatedState.preferencesAsTags.ping() const gpsIsDisplayed = gpsLayer.isDisplayed.data diff --git a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte index d28c573fa..b1da84275 100644 --- a/src/UI/Popup/AddNewPoint/AddNewPoint.svelte +++ b/src/UI/Popup/AddNewPoint/AddNewPoint.svelte @@ -89,7 +89,6 @@ state.selectedElement.setData(undefined) // When aborted, we force the contributors to place the pin _again_ // This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map - state.lastClickObject.features.setData([]) preciseInputIsTapped = false } diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 2799a2e80..ab61da599 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -84,7 +84,6 @@ export interface SpecialVisualizationState { readonly preferencesAsTags: UIEventSource> readonly language: UIEventSource } - readonly lastClickObject: WritableFeatureSource readonly availableLayers: Store diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index 26e4f6e98..f6ee40867 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -96,6 +96,11 @@ if (element.properties.id.startsWith("current_view")) { return currentViewLayer } + console.log(">>> selected:", element) + if(element.properties.id === "new_point_dialog"){ + console.log(">>> searching last_click layer", layout) + return layout.layers.find(l => l.id === "last_click") + } if(element.properties.id === "location_track"){ return layout.layers.find(l => l.id === "gps_track") } @@ -259,7 +264,7 @@
- {#if state.lastClickObject.hasPresets || state.lastClickObject.hasNoteLayer} + {#if state.layout.hasPresets() || state.layout.hasNoteLayer()}
- {Object.keys(perRegion).join(";")} +
Basic properties
@@ -74,16 +74,15 @@
Configuration file
-
+
Below, you'll find the raw configuration file in `.json`-format. This is mostly for debugging purposes, but you can also edit the file directly if you want.
+
- -
From 8e803cdd0346aba4eafab590cf5002c9f4bbdba6 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 11:55:32 +0100 Subject: [PATCH 38/57] Split of server file --- scripts/osm2pgsql/tilecountServer.ts | 113 +-------------------------- scripts/server.ts | 112 ++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 112 deletions(-) create mode 100644 scripts/server.ts diff --git a/scripts/osm2pgsql/tilecountServer.ts b/scripts/osm2pgsql/tilecountServer.ts index 607adfb3a..6f92f6e8e 100644 --- a/scripts/osm2pgsql/tilecountServer.ts +++ b/scripts/osm2pgsql/tilecountServer.ts @@ -1,6 +1,6 @@ import { Client } from "pg" -import http from "node:http" import { Tiles } from "../../src/Models/TileRange" +import { Server } from "../server" /** * Just the OSM2PGSL default database @@ -160,117 +160,6 @@ class CachedSqlCount { } } -class Server { - constructor( - port: number, - options: { - ignorePathPrefix?: string[] - }, - handle: { - mustMatch: string | RegExp - mimetype: string - handle: (path: string) => Promise - }[] - ) { - handle.push({ - mustMatch: "", - mimetype: "text/html", - handle: async () => { - return `Supported endpoints are
    ${handle - .filter((h) => h.mustMatch !== "") - .map((h) => { - let l = h.mustMatch - if (typeof h.mustMatch === "string") { - l = `${l}` - } - return "
  • " + l + "
  • " - }) - .join("")}
` - }, - }) - http.createServer(async (req: http.IncomingMessage, res) => { - try { - console.log( - req.method + " " + req.url, - "from:", - req.headers.origin, - new Date().toISOString() - ) - - const url = new URL(`http://127.0.0.1/` + req.url) - let path = url.pathname - while (path.startsWith("/")) { - path = path.substring(1) - } - if (options?.ignorePathPrefix) { - for (const toIgnore of options.ignorePathPrefix) { - if (path.startsWith(toIgnore)) { - path = path.substring(toIgnore.length + 1) - break - } - } - } - const handler = handle.find((h) => { - if (typeof h.mustMatch === "string") { - return h.mustMatch === path - } - if (path.match(h.mustMatch)) { - return true - } - }) - - if (handler === undefined || handler === null) { - res.writeHead(404, { "Content-Type": "text/html" }) - res.write("

Not found...

") - res.end() - return - } - - res.setHeader( - "Access-Control-Allow-Headers", - "Origin, X-Requested-With, Content-Type, Accept" - ) - res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*") - if (req.method === "OPTIONS") { - res.setHeader( - "Access-Control-Allow-Methods", - "POST, GET, OPTIONS, DELETE, UPDATE" - ) - res.writeHead(204, { "Content-Type": handler.mimetype }) - res.end() - return - } - if (req.method === "POST" || req.method === "UPDATE") { - return - } - - if (req.method === "DELETE") { - return - } - - try { - const result = await handler.handle(path) - res.writeHead(200, { "Content-Type": handler.mimetype }) - res.write(result) - res.end() - } catch (e) { - console.error("Could not handle request:", e) - res.writeHead(500) - res.write(e) - res.end() - } - } catch (e) { - console.error("FATAL:", e) - res.end() - } - }).listen(port) - console.log( - "Server is running on port " + port, - ". Supported endpoints are: " + handle.map((h) => h.mustMatch).join(", ") - ) - } -} - const connectionString = "postgresql://user:password@localhost:5444/osm-poi" const tcs = new OsmPoiDatabase(connectionString) const withCache = new CachedSqlCount(tcs, 60 * 60 * 24) diff --git a/scripts/server.ts b/scripts/server.ts new file mode 100644 index 000000000..8c036721c --- /dev/null +++ b/scripts/server.ts @@ -0,0 +1,112 @@ +import http from "node:http" + +export class Server { + constructor( + port: number, + options: { + ignorePathPrefix?: string[] + }, + handle: { + mustMatch: string | RegExp + mimetype: string + handle: (path: string) => Promise + }[] + ) { + handle.push({ + mustMatch: "", + mimetype: "text/html", + handle: async () => { + return `Supported endpoints are
    ${handle + .filter((h) => h.mustMatch !== "") + .map((h) => { + let l = h.mustMatch + if (typeof h.mustMatch === "string") { + l = `${l}` + } + return "
  • " + l + "
  • " + }) + .join("")}
` + }, + }) + http.createServer(async (req: http.IncomingMessage, res) => { + try { + console.log( + req.method + " " + req.url, + "from:", + req.headers.origin, + new Date().toISOString() + ) + + const url = new URL(`http://127.0.0.1/` + req.url) + let path = url.pathname + while (path.startsWith("/")) { + path = path.substring(1) + } + if (options?.ignorePathPrefix) { + for (const toIgnore of options.ignorePathPrefix) { + if (path.startsWith(toIgnore)) { + path = path.substring(toIgnore.length + 1) + break + } + } + } + const handler = handle.find((h) => { + if (typeof h.mustMatch === "string") { + return h.mustMatch === path + } + if (path.match(h.mustMatch)) { + return true + } + }) + + if (handler === undefined || handler === null) { + res.writeHead(404, { "Content-Type": "text/html" }) + res.write("

Not found...

") + res.end() + return + } + + res.setHeader( + "Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept" + ) + res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*") + if (req.method === "OPTIONS") { + res.setHeader( + "Access-Control-Allow-Methods", + "POST, GET, OPTIONS, DELETE, UPDATE" + ) + res.writeHead(204, { "Content-Type": handler.mimetype }) + res.end() + return + } + if (req.method === "POST" || req.method === "UPDATE") { + return + } + + if (req.method === "DELETE") { + return + } + + try { + const result = await handler.handle(path) + res.writeHead(200, { "Content-Type": handler.mimetype }) + res.write(result) + res.end() + } catch (e) { + console.error("Could not handle request:", e) + res.writeHead(500) + res.write(e) + res.end() + } + } catch (e) { + console.error("FATAL:", e) + res.end() + } + }).listen(port) + console.log( + "Server is running on port " + port, + ". Supported endpoints are: " + handle.map((h) => h.mustMatch).join(", ") + ) + } +} From 62c494d709c2715dcae171dd6b0777ae0f6079d9 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 10:42:33 +0100 Subject: [PATCH 39/57] Version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8eeef3fea..69b394dcf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.38.0", + "version": "0.38.1", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", From 50156ee3f5a2901bb6d0e73a20030a27885854c9 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 11:57:31 +0100 Subject: [PATCH 40/57] Cherrypick server file --- scripts/server.ts | 112 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 scripts/server.ts diff --git a/scripts/server.ts b/scripts/server.ts new file mode 100644 index 000000000..8c036721c --- /dev/null +++ b/scripts/server.ts @@ -0,0 +1,112 @@ +import http from "node:http" + +export class Server { + constructor( + port: number, + options: { + ignorePathPrefix?: string[] + }, + handle: { + mustMatch: string | RegExp + mimetype: string + handle: (path: string) => Promise + }[] + ) { + handle.push({ + mustMatch: "", + mimetype: "text/html", + handle: async () => { + return `Supported endpoints are
    ${handle + .filter((h) => h.mustMatch !== "") + .map((h) => { + let l = h.mustMatch + if (typeof h.mustMatch === "string") { + l = `${l}` + } + return "
  • " + l + "
  • " + }) + .join("")}
` + }, + }) + http.createServer(async (req: http.IncomingMessage, res) => { + try { + console.log( + req.method + " " + req.url, + "from:", + req.headers.origin, + new Date().toISOString() + ) + + const url = new URL(`http://127.0.0.1/` + req.url) + let path = url.pathname + while (path.startsWith("/")) { + path = path.substring(1) + } + if (options?.ignorePathPrefix) { + for (const toIgnore of options.ignorePathPrefix) { + if (path.startsWith(toIgnore)) { + path = path.substring(toIgnore.length + 1) + break + } + } + } + const handler = handle.find((h) => { + if (typeof h.mustMatch === "string") { + return h.mustMatch === path + } + if (path.match(h.mustMatch)) { + return true + } + }) + + if (handler === undefined || handler === null) { + res.writeHead(404, { "Content-Type": "text/html" }) + res.write("

Not found...

") + res.end() + return + } + + res.setHeader( + "Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept" + ) + res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*") + if (req.method === "OPTIONS") { + res.setHeader( + "Access-Control-Allow-Methods", + "POST, GET, OPTIONS, DELETE, UPDATE" + ) + res.writeHead(204, { "Content-Type": handler.mimetype }) + res.end() + return + } + if (req.method === "POST" || req.method === "UPDATE") { + return + } + + if (req.method === "DELETE") { + return + } + + try { + const result = await handler.handle(path) + res.writeHead(200, { "Content-Type": handler.mimetype }) + res.write(result) + res.end() + } catch (e) { + console.error("Could not handle request:", e) + res.writeHead(500) + res.write(e) + res.end() + } + } catch (e) { + console.error("FATAL:", e) + res.end() + } + }).listen(port) + console.log( + "Server is running on port " + port, + ". Supported endpoints are: " + handle.map((h) => h.mustMatch).join(", ") + ) + } +} From 2af6af7630556c29704c8103af579b69f3b930e9 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 14:59:05 +0100 Subject: [PATCH 41/57] Add JSON-LD proxy server --- scripts/server.ts | 4 ++-- scripts/serverLdScrape.ts | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 scripts/serverLdScrape.ts diff --git a/scripts/server.ts b/scripts/server.ts index 8c036721c..8bed5bc18 100644 --- a/scripts/server.ts +++ b/scripts/server.ts @@ -9,7 +9,7 @@ export class Server { handle: { mustMatch: string | RegExp mimetype: string - handle: (path: string) => Promise + handle: (path: string, queryParams: URLSearchParams) => Promise }[] ) { handle.push({ @@ -89,7 +89,7 @@ export class Server { } try { - const result = await handler.handle(path) + const result = await handler.handle(path, url.searchParams) res.writeHead(200, { "Content-Type": handler.mimetype }) res.write(result) res.end() diff --git a/scripts/serverLdScrape.ts b/scripts/serverLdScrape.ts new file mode 100644 index 000000000..31d66b0e8 --- /dev/null +++ b/scripts/serverLdScrape.ts @@ -0,0 +1,43 @@ +import Script from "../scripts/Script" +import { Server } from "../scripts/server" +import { Utils } from "../src/Utils" +import parse from "node-html-parser" +class ServerLdScrape extends Script { + constructor() { + super("Starts a server which fetches a webpage and returns embedded LD+JSON") + } + async main(args: string[]): Promise { + const port = Number(args[0] ?? 2346) + new Server(port, {}, [ + { + mustMatch: "extractgraph", + mimetype: "application/ld+json", + async handle(content, searchParams: URLSearchParams) { + const url = searchParams.get("url") + const dloaded = await Utils.download(url, { + "User-Agent": + "MapComplete/openstreetmap scraper; pietervdvn@posteo.net; https://github.com/pietervdvn/MapComplete", + }) + const parsed = parse(dloaded) + const scripts = Array.from(parsed.getElementsByTagName("script")) + const snippets = [] + for (const script of scripts) { + const tp = script.attributes["type"] + if (tp !== "application/ld+json") { + continue + } + try { + snippets.push(JSON.parse(script.textContent)) + } catch (e) { + console.error(e) + } + } + + return JSON.stringify(snippets) + }, + }, + ]) + } +} + +new ServerLdScrape().run() From 801de2c65c01acefe0f0cff4f921a8721ff1699e Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 15:00:06 +0100 Subject: [PATCH 42/57] Add server command --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e0b3b8339..082d8f3d5 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "housekeeping": "git pull && npx update-browserslist-db@latest && npm run weblate-fix-heavy && npm run generate && npm run generate:docs && npm run generate:contributor-list && vite-node scripts/fetchLanguages.ts && npm run format && git add assets/ langs/ Docs/ **/*.ts Docs/* src/* && git commit -m 'chore: automated housekeeping...'", "reuse-compliance": "reuse lint", "summary-server": "vite-node scripts/osm2pgsql/tilecountServer.ts", + "ldjson-server": "vite-node scripts/serverLdScrape.ts", "backup:images": "vite-node scripts/generateImageAnalysis.ts -- ~/data/imgur-image-backup/" }, "keywords": [ From 9c939ca23c66633bfa446fe3497bcde6c7f5e65f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 15:07:08 +0100 Subject: [PATCH 43/57] Fix merge conflict --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 082d8f3d5..bab229bc4 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,6 @@ { "name": "mapcomplete", -<<<<<<< HEAD "version": "0.40.0", -======= - "version": "0.38.1", ->>>>>>> develop "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", From 6b703581de20a8784e801219a88bf86889e00f40 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 15:08:38 +0100 Subject: [PATCH 44/57] Update caddyfile --- Docs/ServerConfig/cache/Caddyfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Docs/ServerConfig/cache/Caddyfile b/Docs/ServerConfig/cache/Caddyfile index b5103b970..7a878bcdc 100644 --- a/Docs/ServerConfig/cache/Caddyfile +++ b/Docs/ServerConfig/cache/Caddyfile @@ -6,4 +6,8 @@ cache.mapcomplete.org { reverse_proxy /* { to http://127.0.0.1:7800 } + + reverse_proxy /extractgraph { + to http://127.0.0.1:2346 + } } From f971d7fd418007f3189505bdb4752eaa8f11b6a7 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 15:09:52 +0100 Subject: [PATCH 45/57] Update caddyfile --- Docs/ServerConfig/cache/Caddyfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Docs/ServerConfig/cache/Caddyfile b/Docs/ServerConfig/cache/Caddyfile index 7a878bcdc..b818fae82 100644 --- a/Docs/ServerConfig/cache/Caddyfile +++ b/Docs/ServerConfig/cache/Caddyfile @@ -3,11 +3,12 @@ cache.mapcomplete.org { to http://127.0.0.1:2345 } - reverse_proxy /* { - to http://127.0.0.1:7800 - } - reverse_proxy /extractgraph { to http://127.0.0.1:2346 } + + reverse_proxy /* { + to http://127.0.0.1:7800 + } + } From a124b7f2345f9059a18118091bf8baa6a8e10d6c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 15:21:04 +0100 Subject: [PATCH 46/57] Add caching --- scripts/serverLdScrape.ts | 15 +++++++++++++-- src/Utils.ts | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/scripts/serverLdScrape.ts b/scripts/serverLdScrape.ts index 31d66b0e8..1157ef1c2 100644 --- a/scripts/serverLdScrape.ts +++ b/scripts/serverLdScrape.ts @@ -8,15 +8,26 @@ class ServerLdScrape extends Script { } async main(args: string[]): Promise { const port = Number(args[0] ?? 2346) + + const cache: Record = {} + new Server(port, {}, [ { mustMatch: "extractgraph", mimetype: "application/ld+json", async handle(content, searchParams: URLSearchParams) { const url = searchParams.get("url") + if (cache[url] !== undefined) { + const { date, contents } = cache[url] + // In seconds + const tdiff = (new Date().getTime() - date.getTime()) / 1000 + if (tdiff < 24 * 60 * 60) { + return contents + } + } const dloaded = await Utils.download(url, { "User-Agent": - "MapComplete/openstreetmap scraper; pietervdvn@posteo.net; https://github.com/pietervdvn/MapComplete", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36", // MapComplete/openstreetmap scraper; pietervdvn@posteo.net; https://github.com/pietervdvn/MapComplete", }) const parsed = parse(dloaded) const scripts = Array.from(parsed.getElementsByTagName("script")) @@ -32,7 +43,7 @@ class ServerLdScrape extends Script { console.error(e) } } - + cache[url] = { contents: snippets, date: new Date() } return JSON.stringify(snippets) }, }, diff --git a/src/Utils.ts b/src/Utils.ts index 3c673f2d9..4f099ed2e 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -980,7 +980,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be resolve({ error: "rate limited", url, statuscode: xhr.status }) } else { resolve({ - error: "other error: " + xhr.statusText, + error: "other error: " + xhr.statusText + ", " + xhr.responseText, url, statuscode: xhr.status, }) From 98d441a2602c360136db9f9cc883d2ef734137e5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 22 Feb 2024 20:02:20 +0100 Subject: [PATCH 47/57] Themes: don't count food for fritures --- assets/themes/fritures/fritures.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/themes/fritures/fritures.json b/assets/themes/fritures/fritures.json index 4e7df1f96..05b5b8949 100644 --- a/assets/themes/fritures/fritures.json +++ b/assets/themes/fritures/fritures.json @@ -97,7 +97,7 @@ "minzoom": 18, "filter": null, "name": null, - "doCount": false + "isCounted": false } } ], From c300d901284dfbf05c5044da46e12ec8ca4469d1 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 23 Feb 2024 02:55:52 +0100 Subject: [PATCH 48/57] Themes: fix compilation --- assets/themes/ghostsigns/ghostsigns.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/assets/themes/ghostsigns/ghostsigns.json b/assets/themes/ghostsigns/ghostsigns.json index 21577bb77..e252029e2 100644 --- a/assets/themes/ghostsigns/ghostsigns.json +++ b/assets/themes/ghostsigns/ghostsigns.json @@ -140,7 +140,12 @@ "source": { "osmTags": "advertising=wall_painting" }, + "id": "advertising_wall_paintings", "minzoom": 18, + "name": { + "en": "All advertentie wall paintings", + "nl": "Alle adverterende muurschilderingen" + }, "+tagRenderings": [ { "id": "historic", @@ -176,4 +181,4 @@ } } ] -} \ No newline at end of file +} From 497fa4ccdf681b1134eb0e6dd4cca24c0000d8d5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 23 Feb 2024 11:35:58 +0100 Subject: [PATCH 49/57] Fix await which causes hanging map in fallback mode --- .../Sources/OverpassFeatureSource.ts | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts b/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts index 9ff8f74cf..af8fdbbf9 100644 --- a/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts +++ b/src/Logic/FeatureSource/Sources/OverpassFeatureSource.ts @@ -1,5 +1,5 @@ import { Feature } from "geojson" -import { FeatureSource, UpdatableFeatureSource } from "../FeatureSource" +import { UpdatableFeatureSource } from "../FeatureSource" import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import { Or } from "../../Tags/Or" @@ -59,49 +59,6 @@ export default class OverpassFeatureSource implements UpdatableFeatureSource { }) } - /** - * Creates the 'Overpass'-object for the given layers - * @param interpreterUrl - * @param layersToDownload - * @constructor - * @private - */ - private GetFilter(interpreterUrl: string, layersToDownload: LayerConfig[]): Overpass { - let filters: TagsFilter[] = layersToDownload.map((layer) => layer.source.osmTags) - filters = Utils.NoNull(filters) - if (filters.length === 0) { - return undefined - } - return new Overpass(new Or(filters), [], interpreterUrl, this.state.overpassTimeout) - } - - /** - * - * @private - */ - private async updateAsyncIfNeeded(): Promise { - if (!this._isActive?.data) { - return - } - if (this.runningQuery.data) { - console.log("Still running a query, not updating") - return undefined - } - - if (this.timeout.data > 0) { - console.log("Still in timeout - not updating") - return undefined - } - const requestedBounds = this.state.bounds.data - if ( - this._lastQueryBBox !== undefined && - requestedBounds.isContainedIn(this._lastQueryBBox) - ) { - return undefined - } - await this.updateAsync() - } - /** * Download the relevant data from overpass. Attempt to use a different server; only downloads the relevant layers * @private @@ -166,7 +123,7 @@ export default class OverpassFeatureSource implements UpdatableFeatureSource { return undefined } this.runningQuery.setData(true) - data = await overpass.queryGeoJson(bounds)[0] + data = (await overpass.queryGeoJson(bounds))[0] } catch (e) { self.retries.data++ self.retries.ping() @@ -207,4 +164,47 @@ export default class OverpassFeatureSource implements UpdatableFeatureSource { self.runningQuery.setData(false) } } + + /** + * Creates the 'Overpass'-object for the given layers + * @param interpreterUrl + * @param layersToDownload + * @constructor + * @private + */ + private GetFilter(interpreterUrl: string, layersToDownload: LayerConfig[]): Overpass { + let filters: TagsFilter[] = layersToDownload.map((layer) => layer.source.osmTags) + filters = Utils.NoNull(filters) + if (filters.length === 0) { + return undefined + } + return new Overpass(new Or(filters), [], interpreterUrl, this.state.overpassTimeout) + } + + /** + * + * @private + */ + private async updateAsyncIfNeeded(): Promise { + if (!this._isActive?.data) { + return + } + if (this.runningQuery.data) { + console.log("Still running a query, not updating") + return undefined + } + + if (this.timeout.data > 0) { + console.log("Still in timeout - not updating") + return undefined + } + const requestedBounds = this.state.bounds.data + if ( + this._lastQueryBBox !== undefined && + requestedBounds.isContainedIn(this._lastQueryBBox) + ) { + return undefined + } + await this.updateAsync() + } } From 20a462fdf5683587e50b42171c109e87eb193422 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 23 Feb 2024 11:44:56 +0100 Subject: [PATCH 50/57] Themes: fix some isCounted-typos --- assets/themes/cyclestreets/cyclestreets.json | 2 +- assets/themes/ghostsigns/ghostsigns.json | 3 +- .../ThemeConfig/Conversion/Validation.ts | 291 +++++++++--------- 3 files changed, 152 insertions(+), 144 deletions(-) diff --git a/assets/themes/cyclestreets/cyclestreets.json b/assets/themes/cyclestreets/cyclestreets.json index a33829f1e..da41a8deb 100644 --- a/assets/themes/cyclestreets/cyclestreets.json +++ b/assets/themes/cyclestreets/cyclestreets.json @@ -404,7 +404,7 @@ "width": "5" } ], - "doCount": false + "isCounted": false } ], "overrideAll": { diff --git a/assets/themes/ghostsigns/ghostsigns.json b/assets/themes/ghostsigns/ghostsigns.json index e252029e2..e95b54369 100644 --- a/assets/themes/ghostsigns/ghostsigns.json +++ b/assets/themes/ghostsigns/ghostsigns.json @@ -177,7 +177,8 @@ { "iconSize": "20,20" } - ] + ], + "isCounted": false } } ] diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index 4e3b0d602..14f9c519c 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -13,7 +13,10 @@ import { And } from "../../../Logic/Tags/And" import Translations from "../../../UI/i18n/Translations" import FilterConfigJson from "../Json/FilterConfigJson" import DeleteConfig from "../DeleteConfig" -import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" +import { + MappingConfigJson, + QuestionableTagRenderingConfigJson, +} from "../Json/QuestionableTagRenderingConfigJson" import Validators from "../../../UI/InputElement/Validators" import TagRenderingConfig from "../TagRenderingConfig" import { parse as parse_html } from "node-html-parser" @@ -31,7 +34,7 @@ class ValidateLanguageCompleteness extends DesugaringStep { super( "Checks that the given object is fully translated in the specified languages", [], - "ValidateLanguageCompleteness", + "ValidateLanguageCompleteness" ) this._languages = languages ?? ["en"] } @@ -45,18 +48,18 @@ class ValidateLanguageCompleteness extends DesugaringStep { .filter( (t) => t.tr.translations[neededLanguage] === undefined && - t.tr.translations["*"] === undefined, + t.tr.translations["*"] === undefined ) .forEach((missing) => { context .enter(missing.context.split(".")) .err( `The theme ${obj.id} should be translation-complete for ` + - neededLanguage + - ", but it lacks a translation for " + - missing.context + - ".\n\tThe known translation is " + - missing.tr.textFor("en"), + neededLanguage + + ", but it lacks a translation for " + + missing.context + + ".\n\tThe known translation is " + + missing.tr.textFor("en") ) }) } @@ -73,7 +76,7 @@ export class DoesImageExist extends DesugaringStep { constructor( knownImagePaths: Set, checkExistsSync: (path: string) => boolean = undefined, - ignore?: Set, + ignore?: Set ) { super("Checks if an image exists", [], "DoesImageExist") this._ignore = ignore @@ -109,15 +112,15 @@ export class DoesImageExist extends DesugaringStep { if (!this._knownImagePaths.has(image)) { if (this.doesPathExist === undefined) { context.err( - `Image with path ${image} not found or not attributed; it is used in ${context}`, + `Image with path ${image} not found or not attributed; it is used in ${context}` ) } else if (!this.doesPathExist(image)) { context.err( - `Image with path ${image} does not exist.\n Check for typo's and missing directories in the path.`, + `Image with path ${image} does not exist.\n Check for typo's and missing directories in the path.` ) } else { context.err( - `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`, + `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info` ) } } @@ -141,7 +144,7 @@ export class ValidateTheme extends DesugaringStep { doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, - sharedTagRenderings?: Set, + sharedTagRenderings?: Set ) { super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme") this._validateImage = doesImageExist @@ -160,15 +163,15 @@ export class ValidateTheme extends DesugaringStep { if (json["units"] !== undefined) { context.err( "The theme " + - json.id + - " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ", + json.id + + " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " ) } if (json["roamingRenderings"] !== undefined) { context.err( "Theme " + - json.id + - " contains an old 'roamingRenderings'. Use an 'overrideAll' instead", + json.id + + " contains an old 'roamingRenderings'. Use an 'overrideAll' instead" ) } } @@ -186,10 +189,10 @@ export class ValidateTheme extends DesugaringStep { for (const remoteImage of remoteImages) { context.err( "Found a remote image: " + - remoteImage.path + - " in theme " + - json.id + - ", please download it.", + remoteImage.path + + " in theme " + + json.id + + ", please download it." ) } for (const image of images) { @@ -205,17 +208,17 @@ export class ValidateTheme extends DesugaringStep { const filename = this._path.substring( this._path.lastIndexOf("/") + 1, - this._path.length - 5, + this._path.length - 5 ) if (theme.id !== filename) { context.err( "Theme ids should be the same as the name.json, but we got id: " + - theme.id + - " and filename " + - filename + - " (" + - this._path + - ")", + theme.id + + " and filename " + + filename + + " (" + + this._path + + ")" ) } this._validateImage.convert(theme.icon, context.enter("icon")) @@ -223,13 +226,13 @@ export class ValidateTheme extends DesugaringStep { const dups = Utils.Duplicates(json.layers.map((layer) => layer["id"])) if (dups.length > 0) { context.err( - `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`, + `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}` ) } if (json["mustHaveLanguage"] !== undefined) { new ValidateLanguageCompleteness(...json["mustHaveLanguage"]).convert( theme, - context, + context ) } if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) { @@ -237,7 +240,7 @@ export class ValidateTheme extends DesugaringStep { const targetLanguage = theme.title.SupportedLanguages()[0] if (targetLanguage !== "en") { context.err( - `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`, + `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key` ) } @@ -298,7 +301,7 @@ export class ValidateThemeAndLayers extends Fuse { doesImageExist: DoesImageExist, path: string, isBuiltin: boolean, - sharedTagRenderings?: Set, + sharedTagRenderings?: Set ) { super( "Validates a theme and the contained layers", @@ -308,10 +311,10 @@ export class ValidateThemeAndLayers extends Fuse { new Each( new Bypass( (layer) => Constants.added_by_default.indexOf(layer.id) < 0, - new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true), - ), - ), - ), + new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true) + ) + ) + ) ) } } @@ -321,7 +324,7 @@ class OverrideShadowingCheck extends DesugaringStep { super( "Checks that an 'overrideAll' does not override a single override", [], - "OverrideShadowingCheck", + "OverrideShadowingCheck" ) } @@ -395,7 +398,7 @@ class MiscThemeChecks extends DesugaringStep { context .enter("overideAll") .err( - "'overrideAll' is spelled with _two_ `r`s. You only wrote a single one of them.", + "'overrideAll' is spelled with _two_ `r`s. You only wrote a single one of them." ) } return json @@ -407,7 +410,7 @@ export class PrevalidateTheme extends Fuse { super( "Various consistency checks on the raw JSON", new MiscThemeChecks(), - new OverrideShadowingCheck(), + new OverrideShadowingCheck() ) } } @@ -417,7 +420,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep ["_abc"] */ private static extractCalculatedTagNames( - layerConfig?: LayerConfigJson | { calculatedTags: string[] }, + layerConfig?: LayerConfigJson | { calculatedTags: string[] } ) { return ( layerConfig?.calculatedTags?.map((ct) => { @@ -652,16 +655,16 @@ export class DetectShadowedMappings extends DesugaringStep\` instead. The images found are ${images.join( - ", ", - )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`, + ", " + )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged` ) } else { ctx.info( `Ignored image ${images.join( - ", ", - )} in 'then'-clause of a mapping as this check has been disabled`, + ", " + )} in 'then'-clause of a mapping as this check has been disabled` ) for (const image of images) { @@ -756,7 +759,7 @@ class ValidatePossibleLinks extends DesugaringStep does have `rel='noopener'` set", [], - "ValidatePossibleLinks", + "ValidatePossibleLinks" ) } @@ -786,21 +789,21 @@ class ValidatePossibleLinks extends DesugaringStep, - context: ConversionContext, + context: ConversionContext ): string | Record { if (typeof json === "string") { if (this.isTabnabbingProne(json)) { context.err( "The string " + - json + - " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping", + json + + " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping" ) } } else { for (const k in json) { if (this.isTabnabbingProne(json[k])) { context.err( - `The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`, + `The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping` ) } } @@ -818,7 +821,7 @@ class CheckTranslation extends DesugaringStep { super( "Checks that a translation is valid and internally consistent", ["*"], - "CheckTranslation", + "CheckTranslation" ) this._allowUndefined = allowUndefined } @@ -864,17 +867,17 @@ class MiscTagRenderingChecks extends DesugaringStep { convert( json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson, - context: ConversionContext, + context: ConversionContext ): TagRenderingConfigJson { if (json["special"] !== undefined) { context.err( - "Detected `special` on the top level. Did you mean `{\"render\":{ \"special\": ... }}`", + 'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`' ) } if (Object.keys(json).length === 1 && typeof json["render"] === "string") { context.warn( - `use the content directly instead of {render: ${JSON.stringify(json["render"])}}`, + `use the content directly instead of {render: ${JSON.stringify(json["render"])}}` ) } @@ -886,7 +889,7 @@ class MiscTagRenderingChecks extends DesugaringStep { const mapping: MappingConfigJson = json.mappings[i] CheckTranslation.noUndefined.convert( mapping.then, - context.enters("mappings", i, "then"), + context.enters("mappings", i, "then") ) if (!mapping.if) { console.log( @@ -895,7 +898,7 @@ class MiscTagRenderingChecks extends DesugaringStep { "if", mapping.if, context.path.join("."), - mapping.then, + mapping.then ) context.enters("mappings", i, "if").err("No `if` is defined") } @@ -905,7 +908,7 @@ class MiscTagRenderingChecks extends DesugaringStep { context .enters("mappings", i, "addExtraTags", j) .err( - "Detected a 'null' or 'undefined' value. Either specify a tag or delete this item", + "Detected a 'null' or 'undefined' value. Either specify a tag or delete this item" ) } } @@ -916,18 +919,18 @@ class MiscTagRenderingChecks extends DesugaringStep { context .enters("mappings", i, "then") .warn( - "A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' without the question, resulting in a weird phrasing in the information box", + "A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' without the question, resulting in a weird phrasing in the information box" ) } } } if (json["group"]) { - context.err("Groups are deprecated, use `\"label\": [\"" + json["group"] + "\"]` instead") + context.err('Groups are deprecated, use `"label": ["' + json["group"] + '"]` instead') } if (json["question"] && json.freeform?.key === undefined && json.mappings === undefined) { context.err( - "A question is defined, but no mappings nor freeform (key) are. Add at least one of them", + "A question is defined, but no mappings nor freeform (key) are. Add at least one of them" ) } if (json["question"] && !json.freeform && (json.mappings?.length ?? 0) == 1) { @@ -937,7 +940,7 @@ class MiscTagRenderingChecks extends DesugaringStep { context .enter("questionHint") .err( - "A questionHint is defined, but no question is given. As such, the questionHint will never be shown", + "A questionHint is defined, but no question is given. As such, the questionHint will never be shown" ) } @@ -945,7 +948,7 @@ class MiscTagRenderingChecks extends DesugaringStep { context .enters("icon", "size") .err( - "size is not a valid attribute. Did you mean 'class'? Class can be one of `small`, `medium` or `large`", + "size is not a valid attribute. Did you mean 'class'? Class can be one of `small`, `medium` or `large`" ) } @@ -955,10 +958,10 @@ class MiscTagRenderingChecks extends DesugaringStep { .enter("render") .err( "This tagRendering allows to set a value to key " + - json.freeform.key + - ", but does not define a `render`. Please, add a value here which contains `{" + - json.freeform.key + - "}`", + json.freeform.key + + ", but does not define a `render`. Please, add a value here which contains `{" + + json.freeform.key + + "}`" ) } else { const render = new Translation(json.render) @@ -989,7 +992,7 @@ class MiscTagRenderingChecks extends DesugaringStep { const keyFirstArg = ["canonical", "fediverse_link", "translated"] if ( keyFirstArg.some( - (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0, + (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0 ) ) { continue @@ -1012,7 +1015,7 @@ class MiscTagRenderingChecks extends DesugaringStep { context .enter("render") .err( - `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!`, + `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!` ) } } @@ -1020,8 +1023,8 @@ class MiscTagRenderingChecks extends DesugaringStep { if (json.render && json["question"] && json.freeform === undefined) { context.err( `Detected a tagrendering which takes input without freeform key in ${context}; the question is ${new Translation( - json["question"], - ).textFor("en")}`, + json["question"] + ).textFor("en")}` ) } @@ -1032,9 +1035,9 @@ class MiscTagRenderingChecks extends DesugaringStep { .enters("freeform", "type") .err( "Unknown type: " + - freeformType + - "; try one of " + - Validators.availableTypes.join(", "), + freeformType + + "; try one of " + + Validators.availableTypes.join(", ") ) } } @@ -1070,7 +1073,7 @@ export class ValidateTagRenderings extends Fuse { new On("question", new ValidatePossibleLinks()), new On("questionHint", new ValidatePossibleLinks()), new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))), - new MiscTagRenderingChecks(), + new MiscTagRenderingChecks() ) } } @@ -1089,7 +1092,7 @@ export class PrevalidateLayer extends DesugaringStep { path: string, isBuiltin: boolean, doesImageExist: DoesImageExist, - studioValidations: boolean, + studioValidations: boolean ) { super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer") this._path = path @@ -1115,7 +1118,7 @@ export class PrevalidateLayer extends DesugaringStep { context .enter("source") .err( - "No source section is defined; please define one as data is not loaded otherwise", + "No source section is defined; please define one as data is not loaded otherwise" ) } else { if (json.source === "special" || json.source === "special:library") { @@ -1123,7 +1126,7 @@ export class PrevalidateLayer extends DesugaringStep { context .enters("source", "osmTags") .err( - "No osmTags defined in the source section - these should always be present, even for geojson layer", + "No osmTags defined in the source section - these should always be present, even for geojson layer" ) } else { const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags") @@ -1132,7 +1135,7 @@ export class PrevalidateLayer extends DesugaringStep { .enters("source", "osmTags") .err( "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" + - osmTags.asHumanString(false, false, {}), + osmTags.asHumanString(false, false, {}) ) } } @@ -1158,10 +1161,10 @@ export class PrevalidateLayer extends DesugaringStep { .enter("syncSelection") .err( "Invalid sync-selection: must be one of " + - LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") + - " but got '" + - json.syncSelection + - "'", + LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") + + " but got '" + + json.syncSelection + + "'" ) } if (json["pointRenderings"]?.length > 0) { @@ -1180,7 +1183,7 @@ export class PrevalidateLayer extends DesugaringStep { } json.pointRendering?.forEach((pr, i) => - this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)), + this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)) ) if (json["mapRendering"]) { @@ -1197,8 +1200,8 @@ export class PrevalidateLayer extends DesugaringStep { if (!Constants.priviliged_layers.find((x) => x == json.id)) { context.err( "Layer " + - json.id + - " uses 'special' as source.osmTags. However, this layer is not a priviliged layer", + json.id + + " uses 'special' as source.osmTags. However, this layer is not a priviliged layer" ) } } @@ -1213,19 +1216,19 @@ export class PrevalidateLayer extends DesugaringStep { context .enter("title") .err( - "This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error.", + "This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error." ) } if (json.title === null) { context.info( - "Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set.", + "Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." ) } { // Check for multiple, identical builtin questions - usability for studio users const duplicates = Utils.Duplicates( - json.tagRenderings.filter((tr) => typeof tr === "string"), + json.tagRenderings.filter((tr) => typeof tr === "string") ) for (let i = 0; i < json.tagRenderings.length; i++) { const tagRendering = json.tagRenderings[i] @@ -1255,7 +1258,7 @@ export class PrevalidateLayer extends DesugaringStep { { // duplicate ids in tagrenderings check const duplicates = Utils.NoNull( - Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))), + Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))) ) if (duplicates.length > 0) { // It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list @@ -1293,8 +1296,8 @@ export class PrevalidateLayer extends DesugaringStep { if (json["overpassTags"] !== undefined) { context.err( "Layer " + - json.id + - "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": }' instead of \"overpassTags\": (note: this isn't your fault, the custom theme generator still spits out the old format)", + json.id + + 'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": }\' instead of "overpassTags": (note: this isn\'t your fault, the custom theme generator still spits out the old format)' ) } const forbiddenTopLevel = [ @@ -1314,7 +1317,7 @@ export class PrevalidateLayer extends DesugaringStep { } if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { context.err( - "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'", + "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'" ) } @@ -1331,9 +1334,9 @@ export class PrevalidateLayer extends DesugaringStep { if (this._path != undefined && this._path.indexOf(expected) < 0) { context.err( "Layer is in an incorrect place. The path is " + - this._path + - ", but expected " + - expected, + this._path + + ", but expected " + + expected ) } } @@ -1351,13 +1354,13 @@ export class PrevalidateLayer extends DesugaringStep { .enter(["tagRenderings", ...emptyIndexes]) .err( `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join( - ",", - )}])`, + "," + )}])` ) } const duplicateIds = Utils.Duplicates( - (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions"), + (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions") ) if (duplicateIds.length > 0 && !Utils.runningFromConsole) { context @@ -1381,7 +1384,7 @@ export class PrevalidateLayer extends DesugaringStep { if (json.tagRenderings !== undefined) { new On( "tagRenderings", - new Each(new ValidateTagRenderings(json, this._doesImageExist)), + new Each(new ValidateTagRenderings(json, this._doesImageExist)) ).convert(json, context) } @@ -1408,7 +1411,7 @@ export class PrevalidateLayer extends DesugaringStep { context .enters("pointRendering", i, "marker", indexM, "icon", "condition") .err( - "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead.", + "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead." ) } } @@ -1446,9 +1449,9 @@ export class PrevalidateLayer extends DesugaringStep { .enters("presets", i, "tags") .err( "This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + - tags.asHumanString(false, false, {}) + - "\n The required tags are: " + - baseTags.asHumanString(false, false, {}), + tags.asHumanString(false, false, {}) + + "\n The required tags are: " + + baseTags.asHumanString(false, false, {}) ) } } @@ -1465,7 +1468,7 @@ export class ValidateLayerConfig extends DesugaringStep { isBuiltin: boolean, doesImageExist: DoesImageExist, studioValidations: boolean = false, - skipDefaultLayers: boolean = false, + skipDefaultLayers: boolean = false ) { super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig") this.validator = new ValidateLayer( @@ -1473,7 +1476,7 @@ export class ValidateLayerConfig extends DesugaringStep { isBuiltin, doesImageExist, studioValidations, - skipDefaultLayers, + skipDefaultLayers ) } @@ -1501,7 +1504,7 @@ class ValidatePointRendering extends DesugaringStep { context .enter("markers") .err( - `Detected a field 'markerS' in pointRendering. It is written as a singular case`, + `Detected a field 'markerS' in pointRendering. It is written as a singular case` ) } if (json.marker && !Array.isArray(json.marker)) { @@ -1511,7 +1514,7 @@ class ValidatePointRendering extends DesugaringStep { context .enter("location") .err( - "A pointRendering should have at least one 'location' to defined where it should be rendered. ", + "A pointRendering should have at least one 'location' to defined where it should be rendered. " ) } return json @@ -1530,26 +1533,26 @@ export class ValidateLayer extends Conversion< isBuiltin: boolean, doesImageExist: DoesImageExist, studioValidations: boolean = false, - skipDefaultLayers: boolean = false, + skipDefaultLayers: boolean = false ) { super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer") this._prevalidation = new PrevalidateLayer( path, isBuiltin, doesImageExist, - studioValidations, + studioValidations ) this._skipDefaultLayers = skipDefaultLayers } convert( json: LayerConfigJson, - context: ConversionContext, + context: ConversionContext ): { parsed: LayerConfig; raw: LayerConfigJson } { context = context.inOperation(this.name) if (typeof json === "string") { context.err( - `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed`, + `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed` ) return undefined } @@ -1580,7 +1583,7 @@ export class ValidateLayer extends Conversion< context .enters("calculatedTags", i) .err( - `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}`, + `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n ${code}` ) } } @@ -1608,6 +1611,10 @@ export class ValidateLayer extends Conversion< } } + if (json["doCount"]) { + context.enters("doCount").err("Use `isCounted` instead of `doCount`") + } + return { raw: json, parsed: layerConfig } } } @@ -1631,8 +1638,8 @@ export class ValidateFilter extends DesugaringStep { .enters("fields", i) .err( `Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from( - Validators.availableTypes, - ).join(",")}`, + Validators.availableTypes + ).join(",")}` ) } } @@ -1649,13 +1656,13 @@ export class DetectDuplicateFilters extends DesugaringStep<{ super( "Tries to detect layers where a shared filter can be used (or where similar filters occur)", [], - "DetectDuplicateFilters", + "DetectDuplicateFilters" ) } convert( json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] }, - context: ConversionContext, + context: ConversionContext ): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } { const { layers, themes } = json const perOsmTag = new Map< @@ -1719,7 +1726,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{ filter: FilterConfigJson }[] >, - layout?: LayoutConfigJson | undefined, + layout?: LayoutConfigJson | undefined ): void { if (layer.filter === undefined || layer.filter === null) { return @@ -1759,7 +1766,7 @@ export class DetectDuplicatePresets extends DesugaringStep { super( "Detects mappings which have identical (english) names or identical mappings.", ["presets"], - "DetectDuplicatePresets", + "DetectDuplicatePresets" ) } @@ -1770,13 +1777,13 @@ export class DetectDuplicatePresets extends DesugaringStep { if (new Set(enNames).size != enNames.length) { const dups = Utils.Duplicates(enNames) const layersWithDup = json.layers.filter((l) => - l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0), + l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0) ) const layerIds = layersWithDup.map((l) => l.id) context.err( `This themes has multiple presets which are named:${dups}, namely layers ${layerIds.join( - ", ", - )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets`, + ", " + )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets` ) } @@ -1791,17 +1798,17 @@ export class DetectDuplicatePresets extends DesugaringStep { Utils.SameObject(presetATags, presetBTags) && Utils.sameList( presetA.preciseInput.snapToLayers, - presetB.preciseInput.snapToLayers, + presetB.preciseInput.snapToLayers ) ) { context.err( `This themes has multiple presets with the same tags: ${presetATags.asHumanString( false, false, - {}, + {} )}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[ j - ].title.textFor("en")}'`, + ].title.textFor("en")}'` ) } } @@ -1825,13 +1832,13 @@ export class ValidateThemeEnsemble extends Conversion< super( "Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes", [], - "ValidateThemeEnsemble", + "ValidateThemeEnsemble" ) } convert( json: LayoutConfig[], - context: ConversionContext, + context: ConversionContext ): Map< string, { @@ -1874,11 +1881,11 @@ export class ValidateThemeEnsemble extends Conversion< context.err( [ "The layer with id '" + - id + - "' is found in multiple themes with different tag definitions:", + id + + "' is found in multiple themes with different tag definitions:", "\t In theme " + oldTheme + ":\t" + oldTags.asHumanString(false, false, {}), "\tIn theme " + theme.id + ":\t" + tags.asHumanString(false, false, {}), - ].join("\n"), + ].join("\n") ) } } From f3db4b34c9e9e78a40c03cfb46839c0243650b80 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 23 Feb 2024 12:05:08 +0100 Subject: [PATCH 51/57] Remove generate cache scripts --- .../item_with_image/item_with_image.json | 12 +- assets/layers/summary/summary.json | 4 +- assets/themes/ghostbikes/ghostbikes.json | 1 - .../vending_machine/vending_machine.json | 4 +- langs/layers/ca.json | 9 + langs/layers/cs.json | 9 + langs/layers/de.json | 9 + langs/layers/en.json | 262 + langs/layers/es.json | 9 + langs/layers/nl.json | 27 + langs/themes/en.json | 95 + langs/themes/nl.json | 9 + package.json | 2 - scripts/generateCache.ts | 571 -- scripts/postal_code_tools/genPostal.sh | 5 - src/assets/editor-layer-index.json | 3 +- test/scripts/GenerateCache.spec.ts | 7644 ----------------- 17 files changed, 444 insertions(+), 8231 deletions(-) delete mode 100644 scripts/generateCache.ts delete mode 100755 scripts/postal_code_tools/genPostal.sh delete mode 100644 test/scripts/GenerateCache.spec.ts diff --git a/assets/layers/item_with_image/item_with_image.json b/assets/layers/item_with_image/item_with_image.json index 27c6ac1b3..8209d2cd1 100644 --- a/assets/layers/item_with_image/item_with_image.json +++ b/assets/layers/item_with_image/item_with_image.json @@ -22,10 +22,14 @@ "render": { "en": "POI with image" }, - "mappings": [{ - "if": "name~*", - "then": {"*": "name"} - }] + "mappings": [ + { + "if": "name~*", + "then": { + "*": "name" + } + } + ] }, "name": { "en": "Items with at least one image" diff --git a/assets/layers/summary/summary.json b/assets/layers/summary/summary.json index 2e57a5e70..deccd5e6b 100644 --- a/assets/layers/summary/summary.json +++ b/assets/layers/summary/summary.json @@ -3,7 +3,9 @@ "description": "Special layer which shows `count`", "source": "special", "title": { - "render": {"en": "Summary"} + "render": { + "en": "Summary" + } }, "tagRenderings": [ "all_tags" diff --git a/assets/themes/ghostbikes/ghostbikes.json b/assets/themes/ghostbikes/ghostbikes.json index 24aee6c49..841f11480 100644 --- a/assets/themes/ghostbikes/ghostbikes.json +++ b/assets/themes/ghostbikes/ghostbikes.json @@ -44,5 +44,4 @@ "ghost_bike" ], "widenFactor": 5 - } diff --git a/assets/themes/vending_machine/vending_machine.json b/assets/themes/vending_machine/vending_machine.json index 1847ae711..6e562630d 100644 --- a/assets/themes/vending_machine/vending_machine.json +++ b/assets/themes/vending_machine/vending_machine.json @@ -58,7 +58,7 @@ { "builtin": "elongated_coin", "override": { - "doCount": false, + "doCount": false, "name": null, "minzoom": 18 } @@ -66,7 +66,7 @@ { "builtin": "ticket_machine", "override": { - "doCount": false, + "doCount": false, "name": null, "minzoom": 18 } diff --git a/langs/layers/ca.json b/langs/layers/ca.json index 73cdc5531..bd8aa1b30 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -7100,6 +7100,15 @@ } } }, + "souvenir_note": { + "tagRenderings": { + "designs": { + "freeform": { + "placeholder": "Nombre de dissenys (p. e. 5)" + } + } + } + }, "speed_camera": { "description": "Capa que mostra càmeres de velocitat", "name": "Càmera de velocitat", diff --git a/langs/layers/cs.json b/langs/layers/cs.json index f2eddfbec..874ef3089 100644 --- a/langs/layers/cs.json +++ b/langs/layers/cs.json @@ -7396,6 +7396,15 @@ } } }, + "souvenir_note": { + "tagRenderings": { + "designs": { + "freeform": { + "placeholder": "Počet vzorů (např. 5)" + } + } + } + }, "speed_camera": { "description": "Vrstva zobrazující rychlostní radary", "name": "Rychlostní radar", diff --git a/langs/layers/de.json b/langs/layers/de.json index d6be3ee6d..5c2f8a7f4 100644 --- a/langs/layers/de.json +++ b/langs/layers/de.json @@ -8563,6 +8563,15 @@ "render": "Geschwindigkeitsreduzierte Straße" } }, + "souvenir_note": { + "tagRenderings": { + "designs": { + "freeform": { + "placeholder": "Motivanzahl (z.B. 5)" + } + } + } + }, "speed_camera": { "description": "Ebene mit Blitzern", "name": "Blitzer", diff --git a/langs/layers/en.json b/langs/layers/en.json index 09f6d317b..4d05671c4 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -5658,6 +5658,12 @@ "render": "Information board" } }, + "item_with_image": { + "name": "Items with at least one image", + "title": { + "render": "POI with image" + } + }, "kerbs": { "description": "A layer showing kerbs.", "filter": { @@ -6993,6 +6999,101 @@ "render": "Playground" } }, + "playground_equipment": { + "description": "Layer showing playground equipment", + "name": "Playground equipment", + "presets": { + "0": { + "description": "An exact type is asked later", + "title": "a playground device" + } + }, + "tagRenderings": { + "type": { + "freeform": { + "placeholder": "Type of device" + }, + "mappings": { + "0": { + "then": "This is a swing" + }, + "1": { + "then": "This is a structure consisting of several connected playground devices" + }, + "2": { + "then": "This is a slide" + }, + "3": { + "then": "This is a sand pit" + }, + "4": { + "then": "This is a spring rider" + }, + "5": { + "then": "This is a climbing frame" + }, + "6": { + "then": "This is a seesaw" + }, + "7": { + "then": "This is a playhouse" + }, + "8": { + "then": "This is a roundabout" + }, + "9": { + "then": "This is a basket swing" + }, + "10": { + "then": "This is a zip wire" + }, + "11": { + "then": "This is a horizontal bar" + }, + "12": { + "then": "This is a hopscotch" + }, + "13": { + "then": "This is a splash pad" + }, + "14": { + "then": "This is a climbing wall" + }, + "15": { + "then": "This is a map" + }, + "16": { + "then": "This is a bridge (either as a standalone device or as part of a larger structure)" + }, + "17": { + "then": "This is a bouncy cushion" + }, + "18": { + "then": "This is an activity panel" + }, + "19": { + "then": "This is a teen shelter" + }, + "20": { + "then": "This is a funnel used to play with funnel ball" + }, + "21": { + "then": "This is a spinning circle" + } + }, + "question": "What kind of device is this?", + "render": "This is a {playground}" + }, + "wheelchair-access": { + "override": { + "question": "Is this device accessible by wheelchair?" + } + } + }, + "title": { + "render": "Playground device" + } + }, "postboxes": { "description": "The layer showing postboxes.", "name": "Postboxes", @@ -7007,6 +7108,43 @@ }, "postoffices": { "description": "A layer showing post offices.", + "filter": { + "1": { + "options": { + "0": { + "question": "Offers letter posting" + } + } + }, + "2": { + "options": { + "0": { + "question": "Offers parcel posting" + } + } + }, + "3": { + "options": { + "0": { + "question": "Offers pickup of missed parcels" + } + } + }, + "4": { + "options": { + "0": { + "question": "Accepts pickup of parcels sent here" + } + } + }, + "5": { + "options": { + "0": { + "question": "Sells stamps" + } + } + } + }, "name": "Post offices", "presets": { "0": { @@ -8563,6 +8701,125 @@ "render": "Slow road" } }, + "souvenir_coin": { + "description": "Layer showing machines selling souvenir coins", + "name": "Souvenir Coin Machines", + "presets": { + "0": { + "description": "Add a machine selling souvenir coins", + "title": "a souvenir coin machine" + } + }, + "tagRenderings": { + "charge": { + "freeform": { + "placeholder": "Cost (e.g. 2 EUR)" + }, + "mappings": { + "0": { + "then": "A souvenir coin costs 2 euro" + } + }, + "question": "How much does a souvenir coin cost?", + "render": "A souvenir coins costs {charge}" + }, + "designs": { + "override": { + "mappings": { + "0": { + "then": "This machine has one design available" + }, + "1": { + "then": "This machine has two designs available" + }, + "2": { + "then": "This machine has three designs available" + }, + "3": { + "then": "This machine has four designs available" + } + }, + "render": "This machine has {coin:design_count} designs available" + } + }, + "indoor": { + "mappings": { + "0": { + "then": "This machine is located indoors." + }, + "1": { + "then": "This machine is located outdoors." + } + }, + "question": "Is this machine located indoors?" + } + }, + "title": { + "render": "Souvenir Coin Machine" + } + }, + "souvenir_note": { + "description": "Layer showing machines selling souvenir banknotes", + "name": "Souvenir Banknote Machines", + "presets": { + "0": { + "description": "Add a machine selling souvenir banknotes", + "title": "a souvenir banknote machine" + } + }, + "tagRenderings": { + "charge": { + "freeform": { + "placeholder": "Cost (e.g. 2 EUR)" + }, + "mappings": { + "0": { + "then": "A souvenir note costs 2 euro" + }, + "1": { + "then": "A souvenir note costs 3 euro" + } + }, + "question": "How much does a souvenir note cost?", + "render": "A souvenir note costs {charge}" + }, + "designs": { + "freeform": { + "placeholder": "Number of designs (e.g. 5)" + }, + "mappings": { + "0": { + "then": "This machine has one design available." + }, + "1": { + "then": "This machine has two designs available." + }, + "2": { + "then": "This machine has three designs available." + }, + "3": { + "then": "This machine has four designs available." + } + }, + "question": "How many designs are available?", + "render": "This machine has {note:design_count} designs available." + }, + "indoor": { + "mappings": { + "0": { + "then": "This machine is located indoors." + }, + "1": { + "then": "This machine is located outdoors." + } + }, + "question": "Is this machine located indoors?" + } + }, + "title": { + "render": "Souvenir Banknote Machine" + } + }, "speed_camera": { "description": "Layer showing speed cameras", "name": "Speed Camera", @@ -9063,6 +9320,11 @@ "render": "Stripclub" } }, + "summary": { + "title": { + "render": "Summary" + } + }, "surveillance_camera": { "description": "This layer shows surveillance cameras and allows a contributor to update information and add new cameras", "name": "Surveillance camera's", diff --git a/langs/layers/es.json b/langs/layers/es.json index 1297516d2..cb4b1ebab 100644 --- a/langs/layers/es.json +++ b/langs/layers/es.json @@ -4023,6 +4023,15 @@ } } }, + "souvenir_note": { + "tagRenderings": { + "designs": { + "freeform": { + "placeholder": "Número de diseños (por ejemplo, 5)" + } + } + } + }, "speed_camera": { "description": "Capa con cámaras de velocidad", "name": "Cámara de velocidad", diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 4e838823d..ffd5547aa 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -6065,6 +6065,33 @@ "render": "Speeltuin" } }, + "playground_equipment": { + "tagRenderings": { + "type": { + "mappings": { + "0": { + "then": "Dit is een schommel" + }, + "3": { + "then": "Dit is een zandbak" + }, + "4": { + "then": "Dit is een veertoestel" + }, + "5": { + "then": "Dit is een klimrek" + }, + "6": { + "then": "Dit is een wipwap" + }, + "11": { + "then": "Dit is een rekstok" + } + }, + "question": "Wat voor speeltoestel is dit?" + } + } + }, "postboxes": { "description": "Deze laag toont brievenbussen.", "name": "Brievenbussen", diff --git a/langs/themes/en.json b/langs/themes/en.json index be7c82c18..060fbe7c3 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -810,6 +810,71 @@ "description": "A ghost bike is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location.

On this map, one can see all the ghost bikes which are known by OpenStreetMap. Is a ghost bike missing? Everyone can add or update information here - you only need to have a (free) OpenStreetMap account.

There exists an automated account on Mastodon which posts a monthly overview of ghost bikes worldwide

", "title": "Ghost bikes" }, + "ghostsigns": { + "description": "A map showing disused signs on buildings", + "layers": { + "0": { + "description": "Layer showing disused signs on buildings", + "name": "Ghost Signs", + "presets": { + "0": { + "title": "a ghost sign" + } + }, + "tagRenderings": { + "brand": { + "freeform": { + "placeholder": "Business name" + }, + "question": "For what business was this sign made?", + "render": "This sign was made for: {brand}" + }, + "historic": { + "mappings": { + "0": { + "then": "This is a ghost sign" + }, + "1": { + "then": "This is not a ghost sign, answering this will hide the sign from the map" + } + }, + "question": "Is this a ghost sign?", + "questionHint": "Is this sign for a business that no longer exists or no longer being maintained?" + }, + "inscription": { + "freeform": { + "placeholder": "Text on the sign" + }, + "question": "What is the text on the sign?", + "render": "The text on the sign is: {inscription}" + } + }, + "title": { + "render": "Ghost Sign" + } + }, + "1": { + "override": { + "+tagRenderings": { + "0": { + "mappings": { + "0": { + "then": "This is a ghost sign" + }, + "1": { + "then": "This is not a ghost sign" + } + }, + "question": "Is this a ghost sign?", + "questionHint": "Is this sign for a business that no longer exists or no longer being maintained?" + } + }, + "name": "All advertentie wall paintings" + } + } + }, + "title": "Ghost Signs" + }, "grb": { "description": "This theme is an attempt to help automating the GRB import.", "layers": { @@ -886,6 +951,10 @@ "description": "On this map, publicly accessible indoor places are shown", "title": "Indoors" }, + "items_with_image": { + "description": "A map showing all items on OSM which have an image. This theme is a very bad fit for MapComplete as someone is not able to directly add a picture. However, this theme is mostly here to include this all into the database, which'll allow this to quickly fetch images nearby for other features", + "title": "All items with images" + }, "kerbs_and_crossings": { "description": "A map showing kerbs and crossings.", "layers": { @@ -1262,6 +1331,32 @@ }, "postboxes": { "description": "On this map you can find and add data of post offices and post boxes. You can use this map to find where you can mail your next postcard! :)
Spotted an error or is a post box missing? You can edit this map with a free OpenStreetMap account.", + "layers": { + "3": { + "override": { + "+tagRenderings": { + "0": { + "mappings": { + "0": { + "then": "This shop is a post partner" + }, + "1": { + "then": "This shop is not a post partner" + } + }, + "question": "Is this shop a post partner?" + } + }, + "=presets": { + "0": { + "description": "If a shop is not yet on the map and is a post partner, you can add it here.", + "title": "a missing shop that is a post partner" + } + }, + "description": "Add a new post partner to the map in an existing shop" + } + } + }, "shortDescription": "A map showing postboxes and post offices", "title": "Postbox and Post Office Map" }, diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 43e12c89e..5af27d779 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -721,6 +721,15 @@ "description": "Een Witte Fiets of Spookfiets is een aandenken aan een fietser die bij een verkeersongeval om het leven kwam. Het gaat om een fiets die volledig wit is geschilderd en in de buurt van het ongeval werd geinstalleerd.

Op deze kaart zie je alle witte fietsen die door OpenStreetMap gekend zijn. Ontbreekt er een Witte Fiets of wens je informatie aan te passen? Meld je dan aan met een (gratis) OpenStreetMap account.", "title": "Witte Fietsen" }, + "ghostsigns": { + "layers": { + "1": { + "override": { + "name": "Alle adverterende muurschilderingen" + } + } + } + }, "grb": { "description": "Dit thema helpt het GRB importeren.", "layers": { diff --git a/package.json b/package.json index bab229bc4..8eec40b88 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,6 @@ "reset:translations": "vite-node scripts/generateTranslations.ts -- --ignore-weblate", "generate:layouts": "vite-node scripts/generateLayouts.ts", "generate:docs": "rm -rf Docs/Themes/* && rm -rf Docs/Layers/* && rm -rf Docs/TagInfo && mkdir Docs/TagInfo && vite-node scripts/generateDocs.ts && vite-node scripts/generateTaginfoProjectFiles.ts", - "generate:cache:speelplekken": "npm run generate:layeroverview && vite-node scripts/generateCache.ts -- speelplekken 14 ../MapComplete-data/speelplekken_cache/ 51.20 4.35 51.09 4.56", - "generate:cache:natuurpunt": "npm run generate:layeroverview && vite-node scripts/generateCache.ts -- natuurpunt 12 ../MapComplete-data/natuurpunt_cache/ 50.40 2.1 51.54 6.4 --generate-point-overview nature_reserve,visitor_information_centre", "generate:layeroverview": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts", "velopark": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --force --themes=velopark --layers=bike_parking,maproulette_challenge", "generate:mapcomplete-changes-theme": "export NODE_OPTIONS=\"--max-old-space-size=8192\" && vite-node scripts/generateLayerOverview.ts -- --generate-change-map", diff --git a/scripts/generateCache.ts b/scripts/generateCache.ts deleted file mode 100644 index 44ac75577..000000000 --- a/scripts/generateCache.ts +++ /dev/null @@ -1,571 +0,0 @@ -/** - * Generates a collection of geojson files based on an overpass query for a given theme - */ -import { Utils } from "../src/Utils" -import { Overpass } from "../src/Logic/Osm/Overpass" -import { existsSync, readFileSync, writeFileSync } from "fs" -import { TagsFilter } from "../src/Logic/Tags/TagsFilter" -import { Or } from "../src/Logic/Tags/Or" -import { AllKnownLayouts } from "../src/Customizations/AllKnownLayouts" -import * as OsmToGeoJson from "osmtogeojson" -import MetaTagging from "../src/Logic/MetaTagging" -import { UIEventSource } from "../src/Logic/UIEventSource" -import { TileRange, Tiles } from "../src/Models/TileRange" -import LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig" -import ScriptUtils from "./ScriptUtils" -import PerLayerFeatureSourceSplitter from "../src/Logic/FeatureSource/PerLayerFeatureSourceSplitter" -import FilteredLayer from "../src/Models/FilteredLayer" -import StaticFeatureSource from "../src/Logic/FeatureSource/Sources/StaticFeatureSource" -import Constants from "../src/Models/Constants" -import { GeoOperations } from "../src/Logic/GeoOperations" -import SimpleMetaTaggers, { ReferencingWaysMetaTagger } from "../src/Logic/SimpleMetaTagger" -import FilteringFeatureSource from "../src/Logic/FeatureSource/Sources/FilteringFeatureSource" -import { Feature } from "geojson" -import { BBox } from "../src/Logic/BBox" -import { FeatureSource } from "../src/Logic/FeatureSource/FeatureSource" -import OsmObjectDownloader from "../src/Logic/Osm/OsmObjectDownloader" -import FeaturePropertiesStore from "../src/Logic/FeatureSource/Actors/FeaturePropertiesStore" - -ScriptUtils.fixUtils() - -function createOverpassObject(theme: LayoutConfig, backend: string) { - let filters: TagsFilter[] = [] - let extraScripts: string[] = [] - for (const layer of theme.layers) { - if (typeof layer === "string") { - throw "A layer was not expanded!" - } - if (layer.doNotDownload) { - continue - } - if (!layer.source) { - continue - } - if (layer.source.geojsonSource) { - // This layer defines a geoJson-source - // SHould it be cached? - if (layer.source.isOsmCacheLayer !== true) { - continue - } - } - - filters.push(layer.source.osmTags) - } - filters = Utils.NoNull(filters) - extraScripts = Utils.NoNull(extraScripts) - if (filters.length + extraScripts.length === 0) { - throw "Nothing to download! The theme doesn't declare anything to download" - } - return new Overpass(new Or(filters), extraScripts, backend, new UIEventSource(60)) -} - -function rawJsonName(targetDir: string, x: number, y: number, z: number): string { - return targetDir + "_" + z + "_" + x + "_" + y + ".json" -} - -function geoJsonName(targetDir: string, x: number, y: number, z: number): string { - return targetDir + "_" + z + "_" + x + "_" + y + ".geojson" -} - -/// Downloads the given tilerange from overpass and saves them to disk -async function downloadRaw( - targetdir: string, - r: TileRange, - theme: LayoutConfig -): Promise<{ failed: number; skipped: number }> { - let downloaded = 0 - let failed = 0 - let skipped = 0 - const startTime = new Date().getTime() - for (let x = r.xstart; x <= r.xend; x++) { - for (let y = r.ystart; y <= r.yend; y++) { - downloaded++ - const filename = rawJsonName(targetdir, x, y, r.zoomlevel) - if (existsSync(filename)) { - console.log("Already exists (not downloading again): ", filename) - skipped++ - continue - } - const runningSeconds = (new Date().getTime() - startTime) / 1000 - const resting = failed + (r.total - downloaded) - const perTile = runningSeconds / (downloaded - skipped) - const estimated = Math.floor(resting * perTile) - console.log( - "total: ", - downloaded, - "/", - r.total, - "failed: ", - failed, - "skipped: ", - skipped, - "running time: ", - Utils.toHumanTime(runningSeconds) + "s", - "estimated left: ", - Utils.toHumanTime(estimated), - "(" + Math.floor(perTile) + "s/tile)" - ) - - const boundsArr = Tiles.tile_bounds(r.zoomlevel, x, y) - const bounds = { - north: Math.max(boundsArr[0][0], boundsArr[1][0]), - south: Math.min(boundsArr[0][0], boundsArr[1][0]), - east: Math.max(boundsArr[0][1], boundsArr[1][1]), - west: Math.min(boundsArr[0][1], boundsArr[1][1]), - } - const overpass = createOverpassObject( - theme, - Constants.defaultOverpassUrls[failed % Constants.defaultOverpassUrls.length] - ) - const url = overpass.buildQuery( - "[bbox:" + - bounds.south + - "," + - bounds.west + - "," + - bounds.north + - "," + - bounds.east + - "]" - ) - - try { - const json = await Utils.downloadJson(url) - if ((json.remark ?? "").startsWith("runtime error")) { - console.error("Got a runtime error: ", json.remark) - failed++ - } else if (json.elements.length === 0) { - console.log("Got an empty response! Writing anyway") - } - - console.log( - "Got the response - writing ", - json.elements.length, - " elements to ", - filename - ) - writeFileSync(filename, JSON.stringify(json, null, " ")) - } catch (err) { - console.log(url) - console.log( - "Could not download - probably hit the rate limit; waiting a bit. (" + err + ")" - ) - failed++ - await ScriptUtils.sleep(1) - } - } - } - - return { failed: failed, skipped: skipped } -} - -/* - * Downloads extra geojson sources and returns the features. - * Extra geojson layers should not be tiled - */ -async function downloadExtraData(theme: LayoutConfig) /* : any[] */ { - const allFeatures: any[] = [] - for (const layer of theme.layers) { - if (!layer.source?.geojsonSource) { - continue - } - const source = layer.source.geojsonSource - if (layer.source.isOsmCacheLayer !== undefined && layer.source.isOsmCacheLayer !== false) { - // Cached layers are not considered here - continue - } - if (source.startsWith("https://api.openstreetmap.org/api/0.6/notes.json")) { - // We ignore map notes - continue - } - console.log("Downloading extra data: ", source) - await Utils.downloadJson(source).then((json) => allFeatures.push(...json.features)) - } - return allFeatures -} - -function loadAllTiles( - targetdir: string, - r: TileRange, - theme: LayoutConfig, - extraFeatures: any[] -): FeatureSource { - let allFeatures = [...extraFeatures] - let processed = 0 - for (let x = r.xstart; x <= r.xend; x++) { - for (let y = r.ystart; y <= r.yend; y++) { - processed++ - const filename = rawJsonName(targetdir, x, y, r.zoomlevel) - console.log(" Loading and processing", processed, "/", r.total, filename) - if (!existsSync(filename)) { - console.error("Not found - and not downloaded. Run this script again!: " + filename) - continue - } - - // We read the raw OSM-file and convert it to a geojson - const rawOsm = JSON.parse(readFileSync(filename, { encoding: "utf8" })) - - // Create and save the geojson file - which is the main chunk of the data - const geojson = OsmToGeoJson.default(rawOsm) - console.log(" which as", geojson.features.length, "features") - - allFeatures.push(...geojson.features) - } - } - return StaticFeatureSource.fromGeojson(allFeatures) -} - -/** - * Load all the tiles into memory from disk - */ -async function sliceToTiles( - allFeatures: FeatureSource, - theme: LayoutConfig, - targetdir: string, - pointsOnlyLayers: string[], - clip: boolean, - targetzoomLevel: number = 9 -) { - const skippedLayers = new Set() - - const indexedFeatures: Map = new Map() - let indexisBuilt = false - const osmObjectDownloader = new OsmObjectDownloader() - - function buildIndex() { - for (const f of allFeatures.features.data) { - indexedFeatures.set(f.properties.id, f) - } - indexisBuilt = true - } - - function getFeatureById(id) { - if (!indexisBuilt) { - buildIndex() - } - return indexedFeatures.get(id) - } - - const flayers: FilteredLayer[] = theme.layers.map((l) => new FilteredLayer(l)) - const perLayer = new PerLayerFeatureSourceSplitter(flayers, allFeatures) - for (const [layerId, source] of perLayer.perLayer) { - const layer = flayers.find((flayer) => flayer.layerDef.id === layerId).layerDef - const targetZoomLevel = layer.source.geojsonZoomLevel ?? targetzoomLevel - - if (layer.source.geojsonSource && layer.source.isOsmCacheLayer !== true) { - console.log("Skipping layer ", layerId, ": not a caching layer") - skippedLayers.add(layer.id) - continue - } - const flayer: FilteredLayer = new FilteredLayer(layer) - console.log( - "Handling layer ", - layerId, - "which has", - source.features.data.length, - "features" - ) - if (source.features.data.length === 0) { - continue - } - const featureProperties: FeaturePropertiesStore = new FeaturePropertiesStore(source) - - MetaTagging.addMetatags( - source.features.data, - { - getFeaturesWithin: (_) => { - return [allFeatures.features.data] - }, - getFeatureById: getFeatureById, - }, - layer, - theme, - osmObjectDownloader, - featureProperties, - { - includeDates: false, - includeNonDates: true, - evaluateStrict: true, - } - ) - - while (SimpleMetaTaggers.country.runningTasks.size > 0) { - console.log( - "Still waiting for ", - SimpleMetaTaggers.country.runningTasks.size, - " features which don't have a country yet" - ) - await ScriptUtils.sleep(250) - } - - const createdTiles = [] - // At this point, we have all the features of the entire area. - // However, we want to export them per tile of a fixed size, so we use a dynamicTileSOurce to split it up - const features = source.features.data - const perBbox = GeoOperations.spreadIntoBboxes(features, targetZoomLevel) - - for (let [tileIndex, features] of perBbox) { - const bbox = BBox.fromTileIndex(tileIndex).asGeoJson({}) - console.log("Got tile:", tileIndex, layer.id) - if (features.length === 0) { - continue - } - const filteredTile = new FilteringFeatureSource( - flayer, - new StaticFeatureSource(features) - ) - console.log( - "Tile " + - layer.id + - "." + - tileIndex + - " contains " + - filteredTile.features.data.length + - " features after filtering (" + - features.length + - ") features before" - ) - if (filteredTile.features.data.length === 0) { - continue - } - - let strictlyCalculated = 0 - let featureCount = 0 - - for (const feature of features) { - // Some cleanup - - if (layer.calculatedTags !== undefined) { - // Evaluate all the calculated tags strictly - const calculatedTagKeys = layer.calculatedTags.map((ct) => ct[0]) - featureCount++ - const props = feature.properties - for (const calculatedTagKey of calculatedTagKeys) { - const strict = props[calculatedTagKey] - - if (props.hasOwnProperty(calculatedTagKey)) { - delete props[calculatedTagKey] - } - - props[calculatedTagKey] = strict - strictlyCalculated++ - if (strictlyCalculated % 100 === 0) { - console.log( - "Strictly calculated ", - strictlyCalculated, - "values for tile", - tileIndex, - ": now at ", - featureCount, - "/", - filteredTile.features.data.length, - "examle value: ", - strict - ) - } - } - } - delete feature["bbox"] - } - - if (clip) { - console.log("Clipping features") - features = [].concat( - ...features.map((f: Feature) => GeoOperations.clipWith(f, bbox)) - ) - } - // Lets save this tile! - const [z, x, y] = Tiles.tile_from_index(tileIndex) - // console.log("Writing tile ", z, x, y, layerId) - const targetPath = geoJsonName(targetdir + "_" + layerId, x, y, z) - createdTiles.push(tileIndex) - // This is the geojson file containing all features for this tile - writeFileSync( - targetPath, - JSON.stringify( - { - type: "FeatureCollection", - features, - }, - null, - " " - ) - ) - console.log("Written tile", targetPath, "with", filteredTile.features.data.length) - } - - // All the tiles are written at this point - // Only thing left to do is to create the index - const path = targetdir + "_" + layerId + "_" + targetZoomLevel + "_overview.json" - const perX = {} - createdTiles - .map((i) => Tiles.tile_from_index(i)) - .forEach(([z, x, y]) => { - const key = "" + x - if (perX[key] === undefined) { - perX[key] = [] - } - perX[key].push(y) - }) - console.log("Written overview: ", path, "with ", createdTiles.length, "tiles") - writeFileSync(path, JSON.stringify(perX)) - - // And, if needed, to create a points-only layer - if (pointsOnlyLayers.indexOf(layer.id) >= 0) { - const filtered = new FilteringFeatureSource(flayer, source) - const features = filtered.features.data - - const points = features.map((feature) => GeoOperations.centerpoint(feature)) - console.log("Writing points overview for ", layerId) - const targetPath = targetdir + "_" + layerId + "_points.geojson" - // This is the geojson file containing all features for this tile - writeFileSync( - targetPath, - JSON.stringify( - { - type: "FeatureCollection", - features: points, - }, - null, - " " - ) - ) - } - } - - const skipped = Array.from(skippedLayers) - if (skipped.length > 0) { - console.warn( - "Did not save any cache files for layers " + - skipped.join(", ") + - " as these didn't set the flag `isOsmCache` to true" - ) - } -} - -export async function main(args: string[]) { - console.log("Cache builder started with args ", args.join(" ")) - ReferencingWaysMetaTagger.enabled = false - if (args.length < 6) { - console.error( - "Expected arguments are: theme zoomlevel targetdirectory lat0 lon0 lat1 lon1 [--generate-point-overview layer-name,layer-name,...] [--force-zoom-level z] [--clip]" + - "--force-zoom-level causes non-cached-layers to be donwnloaded\n" + - "--clip will erase parts of the feature falling outside of the bounding box" - ) - return - } - const themeName = args[0] - const zoomlevel = Number(args[1]) - console.log( - "Target zoomlevel for the tiles is", - zoomlevel, - "; this can be overridden by the individual layers" - ) - - const targetdir = args[2] + "/" + themeName - if (!existsSync(args[2])) { - console.log("Directory not found") - throw `The directory ${args[2]} does not exist` - } - - const lat0 = Number(args[3]) - const lon0 = Number(args[4]) - const lat1 = Number(args[5]) - const lon1 = Number(args[6]) - const clip = args.indexOf("--clip") >= 0 - - if (isNaN(lat0)) { - throw "The first number (a latitude) is not a valid number" - } - - if (isNaN(lon0)) { - throw "The second number (a longitude) is not a valid number" - } - if (isNaN(lat1)) { - throw "The third number (a latitude) is not a valid number" - } - - if (isNaN(lon1)) { - throw "The fourth number (a longitude) is not a valid number" - } - - const tileRange = Tiles.TileRangeBetween(zoomlevel, lat0, lon0, lat1, lon1) - - if (isNaN(tileRange.total)) { - throw "Something has gone wrong: tilerange is NAN" - } - - if (tileRange.total === 0) { - console.log("Tilerange has zero tiles - this is probably an error") - return - } - - const theme = AllKnownLayouts.allKnownLayouts.get(themeName) - if (theme === undefined) { - const keys = Array.from(AllKnownLayouts.allKnownLayouts.keys()) - console.error("The theme " + themeName + " was not found; try one of ", keys) - return - } - - theme.layers = theme.layers.filter( - (l) => - Constants.priviliged_layers.indexOf(l.id) < 0 && !l.id.startsWith("note_import_") - ) - console.log("Layers to download:", theme.layers.map((l) => l.id).join(", ")) - - let generatePointLayersFor = [] - if (args[7] == "--generate-point-overview") { - if (args[8] === undefined) { - throw "--generate-point-overview needs a list of layers to generate the overview for (or * for all)" - } else if (args[8] === "*") { - generatePointLayersFor = theme.layers.map((l) => l.id) - } else { - generatePointLayersFor = args[8].split(",") - } - console.log( - "Also generating a point overview for layers ", - generatePointLayersFor.join(",") - ) - } - { - const index = args.indexOf("--force-zoom-level") - if (index >= 0) { - const forcedZoomLevel = Number(args[index + 1]) - for (const layer of theme.layers) { - layer.source.geojsonSource = "https://127.0.0.1/cache_{layer}_{z}_{x}_{y}.geojson" - layer.source.isOsmCacheLayer = true - layer.source.geojsonZoomLevel = forcedZoomLevel - } - } - } - - let failed = 0 - do { - try { - const cachingResult = await downloadRaw(targetdir, tileRange, theme) - failed = cachingResult.failed - if (failed > 0) { - await ScriptUtils.sleep(30000) - } - } catch (e) { - console.error(e) - return - } - } while (failed > 0) - - const extraFeatures = await downloadExtraData(theme) - const allFeaturesSource = loadAllTiles(targetdir, tileRange, theme, extraFeatures) - await sliceToTiles(allFeaturesSource, theme, targetdir, generatePointLayersFor, clip, zoomlevel) -} - -let args = [...process.argv] -if (!args[1]?.endsWith("test/TestAll.ts")) { - args.splice(0, 2) - try { - main(args) - .then(() => console.log("All done!")) - .catch((e) => console.error("Error building cache:", e)) - } catch (e) { - console.error("Error building cache:", e) - } -} diff --git a/scripts/postal_code_tools/genPostal.sh b/scripts/postal_code_tools/genPostal.sh deleted file mode 100755 index 1784daba4..000000000 --- a/scripts/postal_code_tools/genPostal.sh +++ /dev/null @@ -1,5 +0,0 @@ -#! /bin/bash - -# npm run generate:layeroverview -cd ../.. -ts-node scripts/generateCache.ts postal_codes 8 /home/pietervdvn/Downloads/postal_codes 49.69606181911566 2.373046875 51.754240074033525 6.459960937499999 --generate-point-overview '*' --force-zoom-level 1 \ No newline at end of file diff --git a/src/assets/editor-layer-index.json b/src/assets/editor-layer-index.json index d674eaf58..1163fb5c8 100644 --- a/src/assets/editor-layer-index.json +++ b/src/assets/editor-layer-index.json @@ -655,6 +655,7 @@ {"properties":{"name":"Greene County Orthoimagery (2022)","id":"Greene_OH_2022","url":"https://gis.greenecountyohio.gov/webgis2/rest/services/Aerials/Aerial_2022_Summer/MapServer/export?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Greene County, State of Ohio","url":"https://www.greenecountyohio.gov/"},"type":"wms","category":"historicphoto","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-84.06204,39.85653],[-84.06161,39.84285],[-84.09735,39.84244],[-84.09652,39.80807],[-84.10545,39.80796],[-84.10369,39.73247],[-84.11258,39.73232],[-84.11066,39.64998],[-84.11956,39.64987],[-84.11781,39.57439],[-84.07344,39.575],[-84.07331,39.56812],[-83.98463,39.56928],[-83.98449,39.56243],[-83.91357,39.56331],[-83.91348,39.55645],[-83.79816,39.55778],[-83.79803,39.55092],[-83.70942,39.55188],[-83.70928,39.54499],[-83.66497,39.54548],[-83.66565,39.58664],[-83.65678,39.58673],[-83.65801,39.66223],[-83.64915,39.66231],[-83.65042,39.73781],[-83.64149,39.73789],[-83.64215,39.77907],[-83.69556,39.7785],[-83.69568,39.7854],[-83.73124,39.78501],[-83.73132,39.79188],[-83.75818,39.79169],[-83.75813,39.7985],[-83.82043,39.79775],[-83.82102,39.82521],[-83.8565,39.82479],[-83.85655,39.83164],[-83.93679,39.83069],[-83.93727,39.85129],[-84.02625,39.85016],[-84.02643,39.85702],[-84.06204,39.85653]]],"type":"Polygon"}}, {"properties":{"name":"Putnam County Orthoimagery (2023)","id":"Licking_OH_2023","url":"https://apps.lickingcounty.gov/arcgis/rest/services/Basemaps/Imagery2023/MapServer/export?f=image&format=jpg&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Licking County, State of Ohio","url":"https://lickingcounty.gov/"},"type":"wms","category":"photo","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-82.47752,40.24575],[-82.47607,40.2645],[-82.75072,40.27696],[-82.77362,39.97606],[-82.77941,39.97638],[-82.78238,39.93973],[-82.47333,39.92498],[-82.46314,39.93058],[-82.41846,39.92745],[-82.41897,39.92258],[-82.30121,39.91614],[-82.23414,39.9133],[-82.23164,39.95129],[-82.19885,39.95022],[-82.18284,40.23866],[-82.28095,40.24019],[-82.28094,40.2393],[-82.32857,40.23967],[-82.47752,40.24575]]],"type":"Polygon"}}, {"properties":{"name":"Mercer County Orthoimagery (2021)","id":"Mercer_OH_2021","url":"https://gis.mercercountyohio.org/arcgis/rest/services/aerials/2021Aerials/ImageServer/WMTS/tile/1.0.0/aerials_2021Aerials/default/default028mm/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Mercer County, State of Ohio","url":"https://www.mercercountyohio.org/"},"type":"tms","category":"photo","max_zoom":23},"type":"Feature","geometry":{"coordinates":[[[-84.80424,40.73458],[-84.80284,40.69342],[-84.81186,40.69324],[-84.804,40.46686],[-84.81308,40.46674],[-84.80896,40.34988],[-84.71927,40.35184],[-84.71892,40.34491],[-84.42307,40.35047],[-84.42324,40.35727],[-84.44126,40.35714],[-84.44143,40.36381],[-84.45019,40.36355],[-84.45482,40.50767],[-84.44538,40.50806],[-84.45259,40.73477],[-84.7681,40.72801],[-84.76828,40.73549],[-84.80424,40.73458]]],"type":"Polygon"}}, +{"properties":{"name":"Miami County Orthoimagery (2023)","id":"Miami_OH_2023","url":"https://tiles.arcgis.com/tiles/nKtC7LOvnaEyn6u8/arcgis/rest/services/Miami_Aerial_2023/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":false,"text":"Miami County, State of Ohio","url":"https://www.co.miami.oh.us/"},"type":"tms","category":"photo","max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[-84.42878,39.91291],[-84.17028,39.91669],[-84.16969,39.88947],[-84.16083,39.88947],[-84.16062,39.88267],[-84.12498,39.88313],[-84.12484,39.87631],[-84.04463,39.87734],[-84.0458,39.92545],[-84.03683,39.92554],[-84.03851,40.00101],[-84.0295,40.00116],[-84.0314,40.08344],[-84.02244,40.08361],[-84.02417,40.15903],[-84.0152,40.15916],[-84.01573,40.18657],[-84.05152,40.18617],[-84.05172,40.19301],[-84.1501,40.19178],[-84.15016,40.1985],[-84.21292,40.19764],[-84.21305,40.20454],[-84.43671,40.20111],[-84.43642,40.18733],[-84.44533,40.18725],[-84.44515,40.18033],[-84.43621,40.18047],[-84.42878,39.91291]]],"type":"Polygon"}}, {"properties":{"name":"Montgomery County Orthoimagery (2022)","id":"Montgomery_OH_2022","url":"https://gis.mcohio.org/arcgis/rest/services/AUDGIS_MrSID/MapServer/export?f=image&format=jpg&layers=show:0&bbox={bbox}&bboxSR={wkid}&imageSR={wkid}&size={width},{height}&foo={proj}","attribution":{"required":false,"text":"Montgomery County, State of Ohio","url":"https://www.mcohio.org/"},"type":"wms","category":"photo","max_zoom":21},"type":"Feature","geometry":{"coordinates":[[[-84.48184,39.58592],[-84.36654,39.58782],[-84.3575,39.58113],[-84.35686,39.55709],[-84.34356,39.5573],[-84.33985,39.58481],[-84.25116,39.58618],[-84.10894,39.5745],[-84.10981,39.61224],[-84.07863,39.61276],[-84.07897,39.62969],[-84.09699,39.62962],[-84.09682,39.63286],[-84.10532,39.6328],[-84.10635,39.66049],[-84.10189,39.66042],[-84.10343,39.71531],[-84.09871,39.71537],[-84.09966,39.75313],[-84.09081,39.75313],[-84.0918,39.79436],[-84.08283,39.79452],[-84.08343,39.8151],[-84.08781,39.81506],[-84.08785,39.81839],[-84.09236,39.81846],[-84.09249,39.82864],[-84.08807,39.82868],[-84.08828,39.83563],[-84.07043,39.83593],[-84.07039,39.83237],[-84.0526,39.83263],[-84.05291,39.8464],[-84.04845,39.84647],[-84.04932,39.88417],[-84.07146,39.88379],[-84.12764,39.90026],[-84.16098,39.89976],[-84.16184,39.92725],[-84.49139,39.92218],[-84.48184,39.58592]]],"type":"Polygon"}}, {"properties":{"name":"Ohio Statewide Imagery Program","id":"OSIP","url":"https://geo1.oit.ohio.gov/arcgis/services/OSIP/OSIPIII_MostCurrent/ImageServer/WMSServer?LAYERS=0&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":false,"text":"Ohio Statewide Imagery Program","url":"https://das.ohio.gov/technology-and-strategy/ogrip/projects/osip"},"type":"wms","category":"photo","min_zoom":8,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-83.1356,41.75081],[-83.13438,41.64959],[-83.44737,41.76038],[-84.82898,41.70637],[-84.84471,39.08477],[-84.74086,39.11164],[-84.64015,39.05545],[-84.49223,39.07255],[-84.34117,38.99922],[-84.22157,38.7812],[-84.08624,38.75421],[-83.9635,38.76403],[-83.74635,38.63385],[-83.65193,38.61172],[-83.54178,38.69283],[-83.46625,38.64614],[-83.39387,38.64368],[-83.30575,38.58466],[-83.14524,38.59942],[-83.00676,38.71002],[-82.91235,38.73212],[-82.8494,38.56006],[-82.73925,38.53545],[-82.57875,38.39745],[-82.29865,38.43198],[-82.26718,38.57236],[-82.16017,38.58712],[-82.16647,38.72475],[-82.19794,38.78856],[-82.12555,38.8278],[-82.11611,38.92336],[-82.02799,38.99922],[-81.9084,38.93315],[-81.95875,38.89397],[-81.9021,38.85477],[-81.73216,38.9258],[-81.72586,39.19461],[-81.54333,39.26288],[-81.515,39.35054],[-81.45521,39.38704],[-81.38912,39.31159],[-81.19399,39.37974],[-80.85095,39.625],[-80.58973,40.2812],[-80.58344,40.49933],[-80.6275,40.59021],[-80.51105,40.62127],[-80.50161,41.99939],[-81.10587,41.84484],[-81.39541,41.7369],[-81.72901,41.52048],[-82.0217,41.53462],[-82.49063,41.40492],[-82.68575,41.50634],[-82.64799,41.62408],[-82.75184,41.63584],[-82.76758,41.73925],[-82.8494,41.7463],[-82.89032,41.69462],[-82.89032,41.54404],[-82.95011,41.54404],[-83.0796,41.62936],[-83.08101,41.7511],[-83.1356,41.75081]]],"type":"Polygon"}}, {"properties":{"name":"Ohio Statewide Imagery Program 1-Foot","id":"OSIP_1ft","url":"https://geo1.oit.ohio.gov/arcgis/services/OSIP/osip_best_avail_1ft/ImageServer/WMSServer?LAYERS=0&STYLES=&CRS={proj}&BBOX={bbox}&FORMAT=image/jpeg&WIDTH={width}&HEIGHT={height}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap","attribution":{"required":false,"text":"Ohio Statewide Imagery Program","url":"https://das.ohio.gov/technology-and-strategy/ogrip/projects/osip"},"type":"wms","category":"photo","min_zoom":8,"max_zoom":20},"type":"Feature","geometry":{"coordinates":[[[-83.1356,41.75081],[-83.13438,41.64959],[-83.44737,41.76038],[-84.82898,41.70637],[-84.84471,39.08477],[-84.74086,39.11164],[-84.64015,39.05545],[-84.49223,39.07255],[-84.34117,38.99922],[-84.22157,38.7812],[-84.08624,38.75421],[-83.9635,38.76403],[-83.74635,38.63385],[-83.65193,38.61172],[-83.54178,38.69283],[-83.46625,38.64614],[-83.39387,38.64368],[-83.30575,38.58466],[-83.14524,38.59942],[-83.00676,38.71002],[-82.91235,38.73212],[-82.8494,38.56006],[-82.73925,38.53545],[-82.57875,38.39745],[-82.29865,38.43198],[-82.26718,38.57236],[-82.16017,38.58712],[-82.16647,38.72475],[-82.19794,38.78856],[-82.12555,38.8278],[-82.11611,38.92336],[-82.02799,38.99922],[-81.9084,38.93315],[-81.95875,38.89397],[-81.9021,38.85477],[-81.73216,38.9258],[-81.72586,39.19461],[-81.54333,39.26288],[-81.515,39.35054],[-81.45521,39.38704],[-81.38912,39.31159],[-81.19399,39.37974],[-80.85095,39.625],[-80.58973,40.2812],[-80.58344,40.49933],[-80.6275,40.59021],[-80.51105,40.62127],[-80.50161,41.99939],[-81.10587,41.84484],[-81.39541,41.7369],[-81.72901,41.52048],[-82.0217,41.53462],[-82.49063,41.40492],[-82.68575,41.50634],[-82.64799,41.62408],[-82.75184,41.63584],[-82.76758,41.73925],[-82.8494,41.7463],[-82.89032,41.69462],[-82.89032,41.54404],[-82.95011,41.54404],[-83.0796,41.62936],[-83.08101,41.7511],[-83.1356,41.75081]]],"type":"Polygon"}}, @@ -754,7 +755,7 @@ {"properties":{"name":"Rio Mosaico 2019","id":"rio2019","url":"https://pgeo3.rio.rj.gov.br/arcgis/services/Imagens/Mosaico_2019/ImageServer/WMSServer?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=0&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"text":"Instituto Pereira Passos - Prefeitura da Cidade do Rio de Janeiro","url":"https://www.rio.rj.gov.br/web/ipp"},"type":"wms","category":"photo","min_zoom":4,"best":true},"type":"Feature","geometry":{"coordinates":[[[-43.59375,-23.09163],[-43.49213,-23.09163],[-43.49043,-23.05158],[-43.42965,-23.04994],[-43.42958,-23.03186],[-43.42655,-23.02912],[-43.35517,-23.02925],[-43.35275,-23.0322],[-43.35273,-23.04931],[-43.32336,-23.05052],[-43.32046,-23.07079],[-43.27378,-23.07079],[-43.27156,-23.05116],[-43.24219,-23.04994],[-43.24219,-23.02902],[-43.23053,-23.02917],[-43.22783,-23.03186],[-43.22776,-23.09163],[-43.11654,-23.09163],[-43.11722,-23.03416],[-43.14502,-23.03414],[-43.1481,-23.03106],[-43.14754,-22.98957],[-43.11653,-22.98732],[-43.11722,-22.90906],[-43.14502,-22.90904],[-43.14805,-22.90629],[-43.14923,-22.86457],[-43.14741,-22.82414],[-43.14433,-22.82106],[-43.08564,-22.82104],[-43.08564,-22.72164],[-43.16528,-22.72227],[-43.1653,-22.73907],[-43.16696,-22.7417],[-43.19618,-22.74253],[-43.1962,-22.76033],[-43.19771,-22.76284],[-43.25866,-22.76343],[-43.25873,-22.78092],[-43.26176,-22.78367],[-43.41453,-22.78622],[-43.41511,-22.80369],[-43.42776,-22.80537],[-43.45873,-22.80481],[-43.46052,-22.80212],[-43.46123,-22.78622],[-43.55392,-22.78749],[-43.5541,-22.79474],[-43.55793,-22.8019],[-43.5704,-22.80269],[-43.57864,-22.81028],[-43.58017,-22.81603],[-43.59306,-22.81851],[-43.59375,-22.84646],[-43.72215,-22.84891],[-43.73108,-22.85144],[-43.73374,-22.85711],[-43.75373,-22.85651],[-43.75466,-22.86261],[-43.75861,-22.86767],[-43.78738,-22.87297],[-43.78944,-22.87424],[-43.78951,-22.8854],[-43.79156,-22.8879],[-43.8121,-22.88817],[-43.8121,-22.93182],[-43.80039,-22.93188],[-43.78897,-22.93578],[-43.78326,-22.94066],[-43.77767,-22.94095],[-43.76014,-22.95055],[-43.75786,-22.98804],[-43.72977,-22.98767],[-43.72766,-22.99051],[-43.72696,-23.00888],[-43.69775,-23.00989],[-43.69606,-23.04994],[-43.68379,-23.0512],[-43.68033,-23.06129],[-43.68026,-23.07079],[-43.60544,-23.07086],[-43.60269,-23.07389],[-43.60267,-23.091],[-43.59375,-23.09163]]],"type":"Polygon"}}, {"properties":{"name":"Rio Mosaico 2022","id":"rio2022","url":"https://pgeo3.rio.rj.gov.br/arcgis/services/Imagens/Mosaico_2022/ImageServer/WMSServer?FORMAT=image/png&TRANSPARENT=TRUE&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap&LAYERS=0&STYLES=&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}","attribution":{"text":"Instituto Pereira Passos - Prefeitura da Cidade do Rio de Janeiro","url":"https://www.rio.rj.gov.br/web/ipp"},"type":"wms","category":"photo","min_zoom":4},"type":"Feature","geometry":{"coordinates":[[[-43.59375,-23.09163],[-43.49213,-23.09163],[-43.49043,-23.05158],[-43.42965,-23.04994],[-43.42958,-23.03186],[-43.42655,-23.02912],[-43.35517,-23.02925],[-43.35275,-23.0322],[-43.35273,-23.04931],[-43.32336,-23.05052],[-43.32046,-23.07079],[-43.27378,-23.07079],[-43.27156,-23.05116],[-43.24219,-23.04994],[-43.24219,-23.02902],[-43.23053,-23.02917],[-43.22783,-23.03186],[-43.22776,-23.09163],[-43.11654,-23.09163],[-43.11722,-23.03416],[-43.14502,-23.03414],[-43.1481,-23.03106],[-43.14754,-22.98957],[-43.11653,-22.98732],[-43.11722,-22.90906],[-43.14502,-22.90904],[-43.14805,-22.90629],[-43.14923,-22.86457],[-43.14741,-22.82414],[-43.14433,-22.82106],[-43.08564,-22.82104],[-43.08564,-22.72164],[-43.16528,-22.72227],[-43.1653,-22.73907],[-43.16696,-22.7417],[-43.19618,-22.74253],[-43.1962,-22.76033],[-43.19771,-22.76284],[-43.25866,-22.76343],[-43.25873,-22.78092],[-43.26176,-22.78367],[-43.41453,-22.78622],[-43.41511,-22.80369],[-43.42776,-22.80537],[-43.45873,-22.80481],[-43.46052,-22.80212],[-43.46123,-22.78622],[-43.55392,-22.78749],[-43.5541,-22.79474],[-43.55793,-22.8019],[-43.5704,-22.80269],[-43.57864,-22.81028],[-43.58017,-22.81603],[-43.59306,-22.81851],[-43.59375,-22.84646],[-43.72215,-22.84891],[-43.73108,-22.85144],[-43.73374,-22.85711],[-43.75373,-22.85651],[-43.75466,-22.86261],[-43.75861,-22.86767],[-43.78738,-22.87297],[-43.78944,-22.87424],[-43.78951,-22.8854],[-43.79156,-22.8879],[-43.8121,-22.88817],[-43.8121,-22.93182],[-43.80039,-22.93188],[-43.78897,-22.93578],[-43.78326,-22.94066],[-43.77767,-22.94095],[-43.76014,-22.95055],[-43.75786,-22.98804],[-43.72977,-22.98767],[-43.72766,-22.99051],[-43.72696,-23.00888],[-43.69775,-23.00989],[-43.69606,-23.04994],[-43.68379,-23.0512],[-43.68033,-23.06129],[-43.68026,-23.07079],[-43.60544,-23.07086],[-43.60269,-23.07389],[-43.60267,-23.091],[-43.59375,-23.09163]]],"type":"Polygon"}}, {"properties":{"name":"Jaraguá do Sul Ortomosaico 2020","id":"jaragua-do-sul-2020","url":"https://www.jaraguadosul.sc.gov.br/geo/ortomosaico2020/{zoom}/{x}/{y}.png","attribution":{"text":"Prefeitura de Jaraguá do Sul, SC","url":"https://sistemas.jaraguadosul.sc.gov.br/index.php?class=GeoWelcomeView"},"type":"tms","category":"photo","max_zoom":19},"type":"Feature","geometry":{"coordinates":[[[-49.25368,-26.26563],[-49.17549,-26.31065],[-49.16931,-26.35804],[-49.19403,-26.38449],[-49.19266,-26.42016],[-49.21051,-26.43676],[-49.21806,-26.47733],[-49.22562,-26.48471],[-49.24621,-26.48901],[-49.29634,-26.54185],[-49.30595,-26.58054],[-49.28106,-26.61953],[-49.23798,-26.61922],[-49.20433,-26.62966],[-49.17824,-26.61615],[-49.1645,-26.65237],[-49.13292,-26.64316],[-49.10408,-26.61063],[-49.10133,-26.58177],[-49.0876,-26.57993],[-49.08554,-26.55168],[-49.0567,-26.54615],[-49.05121,-26.51912],[-49.03404,-26.52219],[-49.01756,-26.51298],[-49.01138,-26.48287],[-49.02511,-26.45643],[-49.09515,-26.39863],[-49.10545,-26.39371],[-49.10477,-26.36972],[-49.13635,-26.33219],[-49.13841,-26.30265],[-49.16725,-26.26571],[-49.16725,-26.21336],[-49.19128,-26.21274],[-49.23454,-26.23061],[-49.23386,-26.25524],[-49.25368,-26.26563]]],"type":"Polygon"}}, -{"properties":{"name":"Bing Maps Aerial","id":"Bing","url":"https://ecn.t1.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=14296&pr=odbl&n=f","type":"bing","category":"photo","min_zoom":1,"max_zoom":22},"type":"Feature","geometry":null}, +{"properties":{"name":"Bing Maps Aerial","id":"Bing","url":"https://ecn.t1.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=14307&pr=odbl&n=f","type":"bing","category":"photo","min_zoom":1,"max_zoom":22},"type":"Feature","geometry":null}, {"properties":{"name":"CyclOSM","id":"cyclosm","url":"https://{switch:a,b,c}.tile-cyclosm.openstreetmap.fr/cyclosm/{zoom}/{x}/{y}.png","attribution":{"text":"Rendering: CyclOSM (hosted by OpenStreetMap France) © Map data OpenStreetMap contributors","url":"https://www.cyclosm.org/"},"type":"tms","category":"osmbasedmap","max_zoom":20},"type":"Feature","geometry":null}, {"properties":{"name":"Esri World Imagery","id":"EsriWorldImagery","url":"https://{switch:services,server}.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":true,"text":"Terms & Feedback","url":"https://wiki.openstreetmap.org/wiki/Esri"},"type":"tms","category":"photo","max_zoom":22,"default":true},"type":"Feature","geometry":null}, {"properties":{"name":"Esri World Imagery (Clarity) Beta","id":"EsriWorldImageryClarity","url":"https://clarity.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/MapServer/tile/{zoom}/{y}/{x}","attribution":{"required":true,"text":"Terms & Feedback","url":"https://wiki.openstreetmap.org/wiki/Esri"},"type":"tms","category":"photo","max_zoom":22,"default":true},"type":"Feature","geometry":null}, diff --git a/test/scripts/GenerateCache.spec.ts b/test/scripts/GenerateCache.spec.ts deleted file mode 100644 index 8e851f475..000000000 --- a/test/scripts/GenerateCache.spec.ts +++ /dev/null @@ -1,7644 +0,0 @@ -import { Utils } from "../../src/Utils" -import { existsSync, mkdirSync, readFileSync, unlinkSync } from "fs" -import ScriptUtils from "../../scripts/ScriptUtils" -import { main } from "../../scripts/generateCache" -import { describe, expect, it } from "vitest" - -function initDownloads(query: string) { - const d = { - version: 0.6, - generator: "Overpass API 0.7.57 93a4d346", - osm3s: { - timestamp_osm_base: "2022-02-13T23:54:06Z", - copyright: - "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.", - }, - elements: [ - { - type: "node", - id: 518224450, - lat: 51.1548065, - lon: 3.1880118, - tags: { access: "yes", amenity: "parking", fee: "no", parking: "street_side" }, - }, - { - type: "node", - id: 665418924, - lat: 51.1575547, - lon: 3.20522, - tags: { amenity: "parking" }, - }, - { - type: "node", - id: 1168727903, - lat: 51.1299141, - lon: 3.1776123, - tags: { - amenity: "drinking_water", - mapillary: - "https://www.mapillary.com/app/?lat=51.129853685131906&lng=3.177603984688602&z=17&pKey=SEyKzIMUeKssni1ZLVe-9A&focus=photo&dateTo=2017-04-02&dateFrom=2017-04-01&x=0.5168826751181941&y=0.6114877557873634&zoom=0", - }, - }, - { - type: "node", - id: 1168728245, - lat: 51.1290938, - lon: 3.1767502, - tags: { - amenity: "drinking_water", - mapillary: - "https://www.mapillary.com/app/?lat=51.129104406662464&lng=3.176675795895676&z=17&pKey=vSP3D_hWv3XCBtH75GnYUQ&focus=photo&dateTo=2017-04-02&dateFrom=2017-04-01", - }, - }, - { - type: "node", - id: 1725842653, - lat: 51.153364, - lon: 3.2352655, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 1744641290, - lat: 51.1389321, - lon: 3.2385407, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 1746891135, - lat: 51.1598841, - lon: 3.2361425, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 1810326078, - lat: 51.1550855, - lon: 3.2349358, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 1810326092, - lat: 51.1552302, - lon: 3.234968, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 2325437742, - lat: 51.1770052, - lon: 3.1967794, - tags: { - board_type: "board", - information: "board", - name: "Tillegembos", - tourism: "information", - }, - }, - { - type: "node", - id: 2325437743, - lat: 51.1787363, - lon: 3.1949036, - tags: { - board_type: "board", - information: "board", - name: "Tillegembos", - tourism: "information", - }, - }, - { - type: "node", - id: 2325437813, - lat: 51.1733102, - lon: 3.1895672, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437839, - lat: 51.1763436, - lon: 3.1984985, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437848, - lat: 51.1770966, - lon: 3.1963507, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437862, - lat: 51.1773439, - lon: 3.1948779, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437867, - lat: 51.1775994, - lon: 3.1888088, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437873, - lat: 51.1778384, - lon: 3.1913802, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2732486257, - lat: 51.129741, - lon: 3.1907419, - tags: { - board_type: "nature", - information: "board", - name: "Doeveren", - tourism: "information", - }, - }, - { - type: "node", - id: 3774054068, - lat: 51.1586662, - lon: 3.2271102, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 4769106605, - lat: 51.138264, - lon: 3.1798655, - tags: { backrest: "yes", leisure: "picnic_table" }, - }, - { - type: "node", - id: 4912238707, - lat: 51.1448634, - lon: 3.2455986, - tags: { - access: "yes", - amenity: "parking", - fee: "no", - name: "Oostkamp", - parking: "Carpool", - }, - }, - { - type: "node", - id: 5637212235, - lat: 51.1305439, - lon: 3.1866873, - tags: { - board_type: "nature", - image: "https://i.imgur.com/HehOQL9.jpg", - information: "board", - name: "Welkom Doeveren", - tourism: "information", - }, - }, - { - type: "node", - id: 5637224573, - lat: 51.1281084, - lon: 3.1881726, - tags: { - board_type: "nature", - information: "board", - name: "Welkom Doeveren", - tourism: "information", - }, - }, - { - type: "node", - id: 5637230107, - lat: 51.1280884, - lon: 3.1889798, - tags: { information: "board", tourism: "information" }, - }, - { - type: "node", - id: 5637743026, - lat: 51.1295973, - lon: 3.1751122, - tags: { - information: "board", - name: "Doeveren Wandelroute", - tourism: "information", - }, - }, - { - type: "node", - id: 5716130103, - lat: 51.1767183, - lon: 3.1947867, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 5745783208, - lat: 51.1782581, - lon: 3.2410111, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 5745807545, - lat: 51.1784037, - lon: 3.2369439, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 5745807551, - lat: 51.1783278, - lon: 3.236678, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6535241426, - lat: 51.1693142, - lon: 3.1673093, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6535241427, - lat: 51.169265, - lon: 3.1673159, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6535241428, - lat: 51.1692199, - lon: 3.1673224, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6535241430, - lat: 51.1685726, - lon: 3.1678225, - tags: { bench: "yes", leisure: "picnic_table", material: "wood" }, - }, - { - type: "node", - id: 6536026827, - lat: 51.1703142, - lon: 3.1691109, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6536026828, - lat: 51.1702795, - lon: 3.1691552, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6712112244, - lat: 51.1595064, - lon: 3.2021482, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7304050040, - lat: 51.1560908, - lon: 3.1748919, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7304050041, - lat: 51.1560141, - lon: 3.1749533, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7304050042, - lat: 51.156032, - lon: 3.1749379, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7439979218, - lat: 51.1780402, - lon: 3.2178666, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 7439979219, - lat: 51.1780508, - lon: 3.2179033, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 7529262982, - lat: 51.1585566, - lon: 3.1715528, - tags: { board_type: "map", information: "board", tourism: "information" }, - }, - { - type: "node", - id: 7529262984, - lat: 51.1585786, - lon: 3.1715385, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7554879668, - lat: 51.1573713, - lon: 3.2043731, - tags: { - access: "yes", - amenity: "toilets", - fee: "no", - "toilets:disposal": "flush", - unisex: "yes", - wheelchair: "yes", - }, - }, - { - type: "node", - id: 7554879669, - lat: 51.1594855, - lon: 3.2021507, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7556988723, - lat: 51.1330234, - lon: 3.1839944, - tags: { amenity: "bench", material: "wood" }, - }, - { - type: "node", - id: 7575825326, - lat: 51.1386553, - lon: 3.1797358, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7575825327, - lat: 51.1382456, - lon: 3.1797422, - tags: { information: "board", tourism: "information" }, - }, - { - type: "node", - id: 8109498958, - lat: 51.1332267, - lon: 3.2341272, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 8109498959, - lat: 51.1335011, - lon: 3.2343954, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 8198894646, - lat: 51.125688, - lon: 3.1856217, - tags: { - image: "https://i.imgur.com/O5kX20u.jpg", - information: "board", - tourism: "information", - }, - }, - { - type: "node", - id: 8199012519, - lat: 51.1262245, - lon: 3.1802429, - tags: { - image: "https://i.imgur.com/tomw9p5.jpg", - information: "board", - tourism: "information", - }, - }, - { - type: "node", - id: 8199244816, - lat: 51.1252874, - lon: 3.1837622, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 8199301617, - lat: 51.1256827, - lon: 3.1853543, - tags: { amenity: "bench", backrest: "no" }, - }, - { - type: "node", - id: 8255488518, - lat: 51.1406698, - lon: 3.235178, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 9316104741, - lat: 51.1330984, - lon: 3.2335257, - tags: { information: "board", tourism: "information" }, - }, - { - type: "node", - id: 9442532340, - lat: 51.1763651, - lon: 3.1947952, - tags: { - amenity: "bench", - backrest: "yes", - image: "https://i.imgur.com/eZ0Loii.jpg", - }, - }, - { - type: "way", - id: 15242261, - nodes: [ - 150996092, 150996093, 6754312552, 6754312553, 6754312550, 6754312551, 150996094, - 150996095, 150996097, 150996098, 6754312560, 6754312559, 6754312558, 150996099, - 6754312557, 150996100, 6754312555, 6754312556, 150996101, 6754312554, 150996092, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 16514228, - nodes: [170464837, 170464839, 170464840, 170464841, 170464837], - tags: { - access: "yes", - amenity: "parking", - fee: "no", - maxstay: "4 hours", - parking: "surface", - }, - }, - { - type: "way", - id: 76706071, - nodes: [ - 903903386, 1038557094, 1038557233, 1038557143, 1038557075, 903903387, - 1038557195, 903903388, 903903390, 903904576, 903903386, - ], - tags: { access: "permissive", amenity: "parking" }, - }, - { - type: "way", - id: 89601157, - nodes: [ - 1038557083, 1038557078, 1038557104, 1038557072, 1038557108, 1038557230, - 1038557227, 1038557102, 1038557137, 1038575040, 1038557191, 1038557014, - 6960473080, 1038557035, 1038557012, 1038557083, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 89604999, - nodes: [ - 1038583404, 1038583491, 1038583375, 1038583483, 1038583479, 1038583398, - 1038583459, 1038583456, 1038583446, 1038583441, 1038583425, 1038583501, - 1038583451, 1038583463, 1038583476, 1038583404, - ], - tags: { - access: "yes", - amenity: "parking", - capacity: "57", - carpool: "yes", - description: "carpoolparking", - fee: "no", - name: "Loppem", - parking: "surface", - }, - }, - { - type: "way", - id: 92035679, - nodes: [ - 1069177920, 6853179264, 1069177925, 1069177919, 6853179269, 6853179268, - 6853179267, 6853179215, 6853179213, 6853179214, 1069178133, 1069177984, - 6853179230, 6853179228, 6853179229, 6853179224, 6853179225, 6853179227, - 6853179226, 6853179216, 6853179220, 6853179219, 6853179218, 6853179217, - 6853179223, 6853179221, 6853179222, 1069177967, 1069177852, 6853179211, - 6853179212, 6853179210, 6853179327, 6853179208, 6853179209, 6853179203, - 1069177976, 6853179207, 6853179206, 6853179205, 6853179204, 6853179202, - 1069177849, 6852012579, 6852012578, 6852012580, 6852012577, 6852012581, - 6852012582, 6852012583, 1069177845, 1759437085, 1519342743, 1519342742, - 1069178166, 1069177853, 1069177915, 6853179235, 6853179234, 6853179236, - 1069177933, 6853179237, 6853179238, 1069178021, 6853179246, 6853179244, - 6853179245, 6853179240, 6853179243, 6853179241, 6853179242, 6853179239, - 6853179248, 6853179249, 6853179250, 6853179247, 1069177873, 6853179262, - 6853179263, 6853179260, 6853179261, 6853179256, 6853179259, 6853179257, - 6853179258, 1069177920, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 101248451, - nodes: [1168728158, 1168728325, 1168728159, 5637669355, 1168728109, 1168728158], - tags: { access: "private", amenity: "parking", name: "Parking Merkenveld" }, - }, - { - type: "way", - id: 101248462, - nodes: [1168727876, 1168728288, 1168728412, 1168728208, 1168727876], - tags: { - amenity: "toilets", - building: "yes", - "source:geometry:date": "2019-03-14", - "source:geometry:ref": "Gbg/6588148", - }, - }, - { - type: "way", - id: 131622387, - nodes: [1448421093, 1448421099, 1448421091, 1448421081, 1448421093], - tags: { amenity: "parking", name: "Tudor - Zeeweg", parking: "surface" }, - }, - { - type: "way", - id: 145691934, - nodes: [1590642859, 1590642860, 1590642858, 1590642849, 1590642859], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 145691937, - nodes: [1590642829, 1590642828, 1590642830, 1590642832, 1590642829], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 158901716, - nodes: [ - 1710245713, 1710245718, 1710245707, 1710245705, 1710245703, 1710245715, - 1710245711, 1710245709, 1710245701, 1710245713, - ], - tags: { - access: "yes", - amenity: "parking", - capacity: "14", - fee: "no", - parking: "surface", - }, - }, - { - type: "way", - id: 158904558, - nodes: [ - 1710262742, 1710262745, 1710262735, 1710262733, 1710262732, 1710262743, - 1710262741, 1710262739, 1710262744, 1710262737, 1710262736, 1710262731, - 1710262738, 1710262742, - ], - tags: { - access: "yes", - alt_name: "Schoolparking", - amenity: "parking", - fee: "no", - parking: "surface", - }, - }, - { - type: "way", - id: 158906028, - nodes: [ - 1710276259, 1710276251, 1810330766, 1710276255, 1710276261, 1710276240, - 1710276232, 1710276257, 1710276243, 1710276253, 1810347217, 1710276242, - 1710276259, - ], - tags: { - access: "yes", - amenity: "parking", - fee: "no", - name: "Parking Sportcentrum De Valkaart", - parking: "surface", - }, - }, - { - type: "way", - id: 160825858, - nodes: [ - 1728421375, 1728421374, 1728421379, 1728421377, 1728421376, 1728421378, - 1728421375, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 162602213, - nodes: [ - 1920143232, 7393009684, 7393048385, 1744641293, 1523513488, 1744641292, - 1920143232, - ], - tags: { amenity: "parking", capacity: "15" }, - }, - { - type: "way", - id: 165489167, - nodes: [ - 4912197370, 4912197365, 4912197373, 4912197364, 4912197372, 1770289505, - 4912197362, 4912197371, 4912197374, 4912197363, 4912197368, 4912197366, - 4912197369, 4912197367, 4912197370, - ], - tags: { - access: "yes", - amenity: "parking", - fee: "no", - name: "Bad Neuheimplein", - parking: "surface", - }, - }, - { - type: "way", - id: 168291852, - nodes: [ - 1795793399, 4979389763, 1795793409, 1795793395, 1795793393, 1795793397, - 1795793407, 1795793406, 1795793408, 1795793405, 1795793399, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 169875513, - nodes: [1810345951, 1810345955, 1810345947, 1810345944, 1810345951], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 170015605, - nodes: [1811673425, 1811673421, 1811673418, 1811673423, 1811673425], - tags: { - access: "private", - amenity: "parking", - name: "gheeraert E40", - operator: "Gheeraert", - }, - }, - { - type: "way", - id: 170018487, - nodes: [1811699779, 1811699778, 1811699776, 1811699777, 1811699779], - tags: { access: "private", amenity: "parking", name: "Gheeraert vooraan" }, - }, - { - type: "way", - id: 170559194, - nodes: [1817319304, 1817319302, 1817319297, 1817319301, 1817319304], - tags: { access: "private", amenity: "parking", name: "Gheeraert laadkade" }, - }, - { - type: "way", - id: 170559195, - nodes: [1817319299, 1817319303, 1817319300, 1817319292, 1817319299], - tags: { - access: "private", - amenity: "parking", - name: "Gheeraert spoorweg trailers", - }, - }, - { - type: "way", - id: 170559196, - nodes: [1817319293, 1817319289, 1817319291, 1817319296, 1817319293], - tags: { access: "private", amenity: "parking", name: "Gheeraert spoorweg trucks" }, - }, - { - type: "way", - id: 170559197, - nodes: [1817319294, 1817319298, 1817319295, 1817319290, 1817319294], - tags: { access: "private", amenity: "parking", name: "Gheeraert zijkant" }, - }, - { - type: "way", - id: 170559292, - nodes: [1817320496, 1817320494, 1817320493, 1817320495, 1817320496], - tags: { access: "private", amenity: "parking", name: "Gheeraert vooraan gebouw" }, - }, - { - type: "way", - id: 170559832, - nodes: [ - 1817324515, 1817324509, 1817324491, 6397031888, 4044172008, 4044172003, - 4044171997, 4044171962, 4044171957, 4044171976, 1817324489, 1817324488, - 3550860268, 1817324505, 3550860269, 1817324513, 1817324515, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 223674368, - nodes: [ - 2325437844, 8191691971, 8191691973, 8191691972, 2325437840, 8191691970, - 414025563, 2325437844, - ], - tags: { amenity: "parking", name: "Tillegembos", parking: "surface" }, - }, - { - type: "way", - id: 237214948, - nodes: [2451574741, 2451574742, 2451574744, 1015583939, 2451574741], - tags: { access: "private", amenity: "parking" }, - }, - { - type: "way", - id: 237214949, - nodes: [2451574748, 2451574746, 2451574747, 2451574749, 2451574748], - tags: { access: "private", amenity: "parking" }, - }, - { - type: "way", - id: 237214950, - nodes: [2451569392, 1015567837, 2451574745, 2451574743, 2451578121, 2451569392], - tags: { access: "private", amenity: "parking" }, - }, - { - type: "way", - id: 325909586, - nodes: [ - 3325315243, 111759500, 3325315247, 3325315232, 3325315230, 3325315226, - 1169056712, 3325315243, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 326834162, - nodes: [ - 3335137807, 3335137692, 3335137795, 9163493632, 3335137802, 3335137812, - 9163486855, 9163486856, 3335137823, 3335137807, - ], - tags: { - access: "customers", - amenity: "parking", - operator: "Best Western Weinebrugge", - parking: "surface", - }, - }, - { - type: "way", - id: 327849054, - nodes: [ - 3346575929, 3346575873, 3346575876, 3346575843, 3346575845, 3346575891, - 3346575901, 3346575923, 3346575928, 3346575950, 3346575929, - ], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 327849055, - nodes: [ - 3346575945, 3346575946, 3346575943, 3346575933, 3346575925, 3346575917, - 3346575903, 3346575908, 3346575889, 3346575886, 3346575945, - ], - tags: { access: "customers", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 327849056, - nodes: [ - 3346575865, 3346575853, 3346575855, 3346575846, 3346575840, 3346575858, - 3346575865, - ], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 374677860, - nodes: [3780611504, 3780611502, 3780611500, 3780611503, 3780611504], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 374677861, - nodes: [3780611495, 3780611493, 3780611492, 3780611494, 3780611495], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 374677862, - nodes: [3780611498, 3780611497, 3780611496, 3780611499, 3780611501, 3780611498], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 389912371, - nodes: [3930713440, 3930713451, 3930713447, 3930713437, 3930713440], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 389912372, - nodes: [ - 3930713454, 3930713442, 3930713435, 3930713429, 5826811614, 3930713426, - 3930713430, 6982605752, 6982605754, 3930713438, 6982605769, 6982605766, - 6982605767, 6982605762, 6982605764, 3930713446, 3930713454, - ], - tags: { access: "customers", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 401995684, - nodes: [4044171963, 4044171954, 4044171924, 4044171936, 4044171941, 4044171963], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 500067820, - nodes: [ - 4912203166, 4912203165, 4912203164, 4912203163, 4912203162, 4912203161, - 4912203160, 4912203166, - ], - tags: { access: "yes", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 500067821, - nodes: [4912203170, 4912203169, 4912203168, 4912203167, 4912203170], - tags: { access: "yes", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 500067822, - nodes: [4912203174, 4912203173, 4912203172, 4912203171, 4912203174], - tags: { access: "yes", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 500067823, - nodes: [4912203179, 4912203178, 4912203177, 4912203176, 4912203179], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 500069613, - nodes: [ - 4912214695, 4912214685, 4912214694, 4912214693, 4912214692, 4912214680, - 4912214695, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 500071452, - nodes: [ - 4912225068, 4912225062, 4912225063, 4912225053, 4912225064, 4912214694, - 4912214685, 4912225068, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 500071455, - nodes: [ - 4912225070, 4912214681, 4912214686, 4912225052, 4912225051, 4912225067, - 4912225062, 4912225068, 4912225070, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 500071458, - nodes: [ - 4912214695, 4912214680, 4912225069, 4912225050, 1525460846, 4912214681, - 4912225070, 4912214695, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 533276307, - nodes: [5173881316, 5173881317, 5173881318, 7054196467, 5173881316], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 533276308, - nodes: [5173881320, 5173881621, 5173881622, 5173881623, 5173881320], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 533276309, - nodes: [5173881624, 5173881625, 5173881626, 5173881627, 5173881624], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 579848581, - nodes: [ - 4043782112, 5825400688, 5552466020, 6997096756, 6997096759, 6997096758, - 5552466018, 5552466013, 5552466014, 6997076567, 4043782112, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 584455569, - nodes: [5586765933, 5586765934, 5586765935, 5586765936, 5586765933], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 585977870, - nodes: [ - 5587844460, 5599314384, 5599314385, 1659850846, 6870850178, 5587844462, - 5587844461, 6870863414, 5587844460, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 587014342, - nodes: [ - 5607796820, 5607798725, 5607798721, 5607798722, 5607796819, 5607798723, - 5607796820, - ], - tags: { - access: "permissive", - amenity: "parking", - park_ride: "no", - parking: "surface", - surface: "paved", - }, - }, - { - type: "way", - id: 590167103, - nodes: [5635001277, 5635001274, 5635001275, 5635001276, 5635001277], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167113, - nodes: [ - 5635001312, 5635001306, 7767137237, 7767137235, 7767137236, 7767137234, - 5635001312, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167134, - nodes: [5635001374, 5635001373, 5635001372, 5635001371, 5635001374], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167135, - nodes: [5635001378, 5635001377, 5635001376, 5635001375, 5635001378], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167136, - nodes: [5635001382, 5635001381, 5635001380, 5635001379, 5635001382], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167137, - nodes: [ - 5635001386, 5882873336, 5882873337, 5882873338, 5882873335, 6593340582, - 6593340583, 5882873334, 6593340584, 5635001385, 5635001384, 5635001383, - 5635001386, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167147, - nodes: [5635001417, 5635001414, 5635001415, 5635001416, 5635001417], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 601406079, - nodes: [5716136617, 5716136618, 5716136619, 5716136620, 5716136617], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 632813009, - nodes: [ - 5974489618, 1810326044, 1810326087, 5974489617, 5972179348, 5972179347, - 5972179346, 5972179345, 5972179344, 5972179343, 5972179331, 5974489616, - 5974489615, 5974489614, 5974489618, - ], - tags: { amenity: "parking", name: "Gemeenteplein", parking: "surface" }, - }, - { - type: "way", - id: 668043297, - nodes: [6255587424, 6255587425, 6255587426, 6255587427, 6255587424], - tags: { access: "private", amenity: "parking" }, - }, - { - type: "way", - id: 670104236, - nodes: [6275462768, 6275462769, 6275462770, 6275462771, 6275462768], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 670104238, - nodes: [6275462772, 6275462989, 6275462773, 6275462774, 6275462775, 6275462772], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 670104239, - nodes: [6275462776, 6275462777, 6275462778, 6275462779, 6275462776], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 670104241, - nodes: [6275462780, 6275462781, 6275462782, 6275462783, 6275462780], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 670104242, - nodes: [6275462784, 6275462985, 6275462988, 6275462986, 6275462987, 6275462784], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 671840055, - nodes: [ - 6291339827, 6291339828, 6291339816, 6291339815, 6291339822, 6291339821, - 6291339829, 6291339830, 6291339827, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 695825223, - nodes: [ - 1519476746, 6533893620, 6533893621, 6533893622, 1519476797, 1519476620, - 1519476746, - ], - tags: { access: "yes", amenity: "parking" }, - }, - { - type: "way", - id: 695825224, - nodes: [ - 1519476744, 6533893624, 6533893625, 6533893626, 1519476698, 1519476635, - 1519476744, - ], - tags: { access: "yes", amenity: "parking" }, - }, - { - type: "way", - id: 696040917, - nodes: [6536026850, 6536026851, 6536026852, 6536026853, 6536026850], - tags: { amenity: "parking", name: "Kasteel Tudor" }, - }, - { - type: "way", - id: 696043218, - nodes: [6536038234, 6536038235, 6536038236, 6536038237, 6536038234], - tags: { access: "customers", amenity: "parking" }, - }, - { - type: "way", - id: 700675991, - nodes: [6579962064, 6579962065, 6579962066, 6579962068, 6579962067, 6579962064], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 705278707, - nodes: [ - 6625037999, 6625038000, 6625038001, 6625038002, 6625038003, 6004375826, - 6625038005, 6625038006, 6625037999, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 719482833, - nodes: [6754312544, 6754312543, 6754312542, 6754312541, 6754312544], - tags: { access: "yes", amenity: "parking", capacity: "5" }, - }, - { - type: "way", - id: 719482834, - nodes: [6754312565, 6754312564, 6754312563, 6754312562, 6754312565], - tags: { access: "yes", amenity: "parking", capacity: "12" }, - }, - { - type: "way", - id: 737054013, - nodes: [5826811496, 5826811497, 5826811494, 5826811495, 5826811496], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 737054014, - nodes: [5826810676, 5826810673, 5826810674, 5826810675, 5826810676], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 737054015, - nodes: [5826810681, 5826810678, 5826810679, 5826810680, 5826810681], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 737093410, - nodes: [5826811559, 5826811536, 5826811535, 5826811561, 5826811560, 5826811559], - tags: { - access: "yes", - amenity: "parking", - capacity: "4", - fee: "no", - parking: "surface", - }, - }, - { - type: "way", - id: 737093411, - nodes: [5826811551, 5826811547, 5826811548, 5826811549, 5826811550, 5826811551], - tags: { - access: "yes", - amenity: "parking", - capacity: "4", - fee: "no", - parking: "surface", - }, - }, - { - type: "way", - id: 739652949, - nodes: [6925536542, 6925536541, 6925536540, 6925536539, 6925536542], - tags: { amenity: "parking", capacity: "6", parking: "surface" }, - }, - { - type: "way", - id: 741675236, - nodes: [ - 6943148207, 6943148206, 6943148205, 6943148204, 1637742821, 6943148203, - 6943148202, 6943148201, 6943148200, 6943148199, 6943148198, 6943148197, - 6943148207, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 742295526, - nodes: [6949357909, 6949357908, 6949357907, 6949357906, 6949357909], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 742295527, - nodes: [6949357913, 6949357912, 6949357911, 6949357910, 6949357913], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 742295528, - nodes: [6949357917, 6949357916, 6949357915, 6949357914, 6949357917], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 742295529, - nodes: [6949357921, 6949357920, 6949357919, 6949357918, 6949357921], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 746170866, - nodes: [ - 6982906547, 6982906546, 6982906545, 6982906544, 6982906543, 6982906542, - 6982906547, - ], - tags: { access: "customers", amenity: "parking" }, - }, - { - type: "way", - id: 747880657, - nodes: [ - 3325315397, 6997076566, 6997076565, 6997076563, 6997076562, 6997076564, - 3325315395, 3325315397, - ], - tags: { access: "customers", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 763977465, - nodes: [7137343680, 7137343681, 7137343682, 7137343683, 7137343680], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 763977466, - nodes: [7137343684, 7137383185, 7137383186, 7137383187, 7137343684], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 803821090, - nodes: [7519058290, 7519058289, 7519058288, 7519058287, 7519058290], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 803821091, - nodes: [7519058294, 7519058293, 7519058292, 7519058291, 7519058294], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 803821092, - nodes: [7519058298, 7519058297, 7519058296, 7519058295, 7519058298], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 803821093, - nodes: [7519058302, 7519058301, 7519058300, 7519058299, 7519058302], - tags: { access: "private", amenity: "parking", capacity: "6", parking: "surface" }, - }, - { - type: "way", - id: 804963962, - nodes: [ - 7529417225, 7529417226, 7529417227, 7529417228, 7529417229, 7529417230, - 7529417232, 7529417225, - ], - tags: { - access: "customers", - amenity: "parking", - fee: "no", - operator: "’t Kiekekot", - parking: "surface", - surface: "unpaved", - }, - }, - { - type: "way", - id: 806875503, - nodes: [4042671969, 7545532512, 7545532514, 7545532513, 3359977305, 4042671969], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 806963547, - nodes: [7546193222, 7546193221, 7546193220, 7546193219, 7546193222], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 865357121, - nodes: [8065883228, 8065883227, 8065883226, 8065883225, 8065883228], - tags: { amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 865357122, - nodes: [8065883233, 8065883230, 8065883229, 8065883232, 8065883233], - tags: { amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 879281221, - nodes: [ - 8179735269, 8179735268, 8179735267, 8179735266, 8179735265, 8179735264, - 8179735224, 8179735269, - ], - tags: { access: "customers", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 881770201, - nodes: [8200275847, 8200275848, 8200275844, 8200275853, 8200275849, 8200275847], - tags: { amenity: "parking", parking: "surface", surface: "grass" }, - }, - { - type: "way", - id: 978360549, - nodes: [5761770202, 5761770204, 9052878229, 9052878228, 5761770202], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 1009692722, - nodes: [ - 9316118540, 9316118536, 9316118531, 9316118535, 9316118534, 9316118533, - 9316118530, 9316118532, 3311835478, 9316118540, - ], - tags: { amenity: "parking", parking: "street_side" }, - }, - { - type: "relation", - id: 8188853, - members: [ - { type: "way", ref: 577572397, role: "outer" }, - { type: "way", ref: 577572399, role: "outer" }, - ], - tags: { - access: "guided", - curator: "Luc Maene;Geert De Clercq", - email: "lucmaene@hotmail.com;geert.de.clercq1@pandora.be", - landuse: "meadow", - leisure: "nature_reserve", - name: "De Wulgenbroeken", - natural: "wetland", - operator: "Natuurpunt Brugge", - type: "multipolygon", - website: "https://natuurpuntbrugge.be/wulgenbroeken/", - wetland: "wet_meadow", - wikidata: "Q60061498", - wikipedia: "nl:Wulgenbroeken", - }, - }, - { - type: "relation", - id: 11163488, - members: [ - { type: "way", ref: 810604915, role: "outer" }, - { type: "way", ref: 989393316, role: "outer" }, - { type: "way", ref: 389026405, role: "inner" }, - { type: "way", ref: 810607458, role: "outer" }, - ], - tags: { - access: "yes", - curator: "Kris Lesage", - description: - "Wat Doeveren zo uniek maakt, zijn zijn kleine heidegebiedjes met soorten die erg verschillen van de Kempense heide. Doeveren en omstreken was vroeger één groot heidegebied, maar bestaat nu grotendeels uit bossen.", - dog: "leashed", - email: "doeveren@natuurpuntzedelgem.be", - image: "https://i.imgur.com/NEAsQZG.jpg", - "image:0": "https://i.imgur.com/Dq71hyQ.jpg", - "image:1": "https://i.imgur.com/mAIiT4f.jpg", - "image:2": "https://i.imgur.com/dELZU97.jpg", - "image:3": "https://i.imgur.com/Bso57JC.jpg", - "image:4": "https://i.imgur.com/9DtcfXo.jpg", - "image:5": "https://i.imgur.com/0R6eBfk.jpg", - "image:6": "https://i.imgur.com/b0JpvbR.jpg", - leisure: "nature_reserve", - name: "Doeveren", - operator: "Natuurpunt Zedelgem", - phone: "+32 486 25 25 30", - type: "multipolygon", - website: "https://www.natuurpuntzedelgem.be/gebieden/doeveren/", - wikidata: "Q56395754", - wikipedia: "nl:Doeveren (natuurgebied)", - }, - }, - { - type: "relation", - id: 11790117, - members: [ - { type: "way", ref: 863373849, role: "outer" }, - { type: "way", ref: 777280458, role: "outer" }, - ], - tags: { - access: "no", - "description:0": "In gebruik als waterbuffering", - leisure: "nature_reserve", - name: "Kerkebeek", - operator: "Natuurpunt Brugge", - type: "multipolygon", - }, - }, - { - type: "node", - id: 518224450, - lat: 51.1548065, - lon: 3.1880118, - timestamp: "2021-08-14T21:49:28Z", - version: 5, - changeset: 109683837, - user: "effem", - uid: 16437, - tags: { access: "yes", amenity: "parking", fee: "no", parking: "street_side" }, - }, - { - type: "node", - id: 665418924, - lat: 51.1575547, - lon: 3.20522, - timestamp: "2012-05-12T20:13:39Z", - version: 2, - changeset: 11580224, - user: "martino260", - uid: 655442, - tags: { amenity: "parking" }, - }, - { - type: "node", - id: 1168727903, - lat: 51.1299141, - lon: 3.1776123, - timestamp: "2017-04-03T08:34:05Z", - version: 2, - changeset: 47403889, - user: "philippec", - uid: 76884, - tags: { - amenity: "drinking_water", - mapillary: - "https://www.mapillary.com/app/?lat=51.129853685131906&lng=3.177603984688602&z=17&pKey=SEyKzIMUeKssni1ZLVe-9A&focus=photo&dateTo=2017-04-02&dateFrom=2017-04-01&x=0.5168826751181941&y=0.6114877557873634&zoom=0", - }, - }, - { - type: "node", - id: 1168728245, - lat: 51.1290938, - lon: 3.1767502, - timestamp: "2019-10-07T11:06:57Z", - version: 3, - changeset: 75370316, - user: "Hopperpop", - uid: 3664604, - tags: { - amenity: "drinking_water", - mapillary: - "https://www.mapillary.com/app/?lat=51.129104406662464&lng=3.176675795895676&z=17&pKey=vSP3D_hWv3XCBtH75GnYUQ&focus=photo&dateTo=2017-04-02&dateFrom=2017-04-01", - }, - }, - { - type: "node", - id: 1725842653, - lat: 51.153364, - lon: 3.2352655, - timestamp: "2012-07-02T17:33:00Z", - version: 2, - changeset: 12090625, - user: "martino260", - uid: 655442, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 1744641290, - lat: 51.1389321, - lon: 3.2385407, - timestamp: "2012-09-18T13:29:52Z", - version: 3, - changeset: 13156159, - user: "martino260", - uid: 655442, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 1746891135, - lat: 51.1598841, - lon: 3.2361425, - timestamp: "2012-05-09T18:22:11Z", - version: 1, - changeset: 11551825, - user: "martino260", - uid: 655442, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 1810326078, - lat: 51.1550855, - lon: 3.2349358, - timestamp: "2012-07-02T19:50:15Z", - version: 1, - changeset: 12093439, - user: "martino260", - uid: 655442, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 1810326092, - lat: 51.1552302, - lon: 3.234968, - timestamp: "2012-07-02T19:50:16Z", - version: 1, - changeset: 12093439, - user: "martino260", - uid: 655442, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 2325437742, - lat: 51.1770052, - lon: 3.1967794, - timestamp: "2013-05-30T12:19:08Z", - version: 1, - changeset: 16350909, - user: "peeweeke", - uid: 494726, - tags: { - board_type: "board", - information: "board", - name: "Tillegembos", - tourism: "information", - }, - }, - { - type: "node", - id: 2325437743, - lat: 51.1787363, - lon: 3.1949036, - timestamp: "2013-05-30T12:19:08Z", - version: 1, - changeset: 16350909, - user: "peeweeke", - uid: 494726, - tags: { - board_type: "board", - information: "board", - name: "Tillegembos", - tourism: "information", - }, - }, - { - type: "node", - id: 2325437813, - lat: 51.1733102, - lon: 3.1895672, - timestamp: "2013-05-30T12:19:09Z", - version: 1, - changeset: 16350909, - user: "peeweeke", - uid: 494726, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437839, - lat: 51.1763436, - lon: 3.1984985, - timestamp: "2013-05-30T12:19:10Z", - version: 1, - changeset: 16350909, - user: "peeweeke", - uid: 494726, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437848, - lat: 51.1770966, - lon: 3.1963507, - timestamp: "2013-05-30T12:19:10Z", - version: 1, - changeset: 16350909, - user: "peeweeke", - uid: 494726, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437862, - lat: 51.1773439, - lon: 3.1948779, - timestamp: "2013-05-30T12:19:10Z", - version: 1, - changeset: 16350909, - user: "peeweeke", - uid: 494726, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437867, - lat: 51.1775994, - lon: 3.1888088, - timestamp: "2013-05-30T12:19:11Z", - version: 1, - changeset: 16350909, - user: "peeweeke", - uid: 494726, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2325437873, - lat: 51.1778384, - lon: 3.1913802, - timestamp: "2013-05-30T12:19:11Z", - version: 1, - changeset: 16350909, - user: "peeweeke", - uid: 494726, - tags: { amenity: "bench", backrest: "yes", material: "wood" }, - }, - { - type: "node", - id: 2732486257, - lat: 51.129741, - lon: 3.1907419, - timestamp: "2014-03-21T21:15:28Z", - version: 1, - changeset: 21234491, - user: "meannder", - uid: 149496, - tags: { - board_type: "nature", - information: "board", - name: "Doeveren", - tourism: "information", - }, - }, - { - type: "node", - id: 3774054068, - lat: 51.1586662, - lon: 3.2271102, - timestamp: "2015-10-05T20:34:04Z", - version: 1, - changeset: 34456387, - user: "TripleBee", - uid: 497177, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 4769106605, - lat: 51.138264, - lon: 3.1798655, - timestamp: "2020-05-31T19:49:45Z", - version: 3, - changeset: 86019474, - user: "Hopperpop", - uid: 3664604, - tags: { backrest: "yes", leisure: "picnic_table" }, - }, - { - type: "node", - id: 4912238707, - lat: 51.1448634, - lon: 3.2455986, - timestamp: "2017-06-13T08:12:04Z", - version: 1, - changeset: 49491753, - user: "Jakka", - uid: 2403313, - tags: { - access: "yes", - amenity: "parking", - fee: "no", - name: "Oostkamp", - parking: "Carpool", - }, - }, - { - type: "node", - id: 5637212235, - lat: 51.1305439, - lon: 3.1866873, - timestamp: "2021-11-22T11:54:45Z", - version: 4, - changeset: 114095475, - user: "L'imaginaire", - uid: 654234, - tags: { - board_type: "nature", - image: "https://i.imgur.com/HehOQL9.jpg", - information: "board", - name: "Welkom Doeveren", - tourism: "information", - }, - }, - { - type: "node", - id: 5637224573, - lat: 51.1281084, - lon: 3.1881726, - timestamp: "2020-06-01T22:39:30Z", - version: 2, - changeset: 86065716, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { - board_type: "nature", - information: "board", - name: "Welkom Doeveren", - tourism: "information", - }, - }, - { - type: "node", - id: 5637230107, - lat: 51.1280884, - lon: 3.1889798, - timestamp: "2018-05-23T11:55:01Z", - version: 1, - changeset: 59208628, - user: "Jakka", - uid: 2403313, - tags: { information: "board", tourism: "information" }, - }, - { - type: "node", - id: 5637743026, - lat: 51.1295973, - lon: 3.1751122, - timestamp: "2021-10-08T08:53:14Z", - version: 2, - changeset: 112251989, - user: "DieterWesttoer", - uid: 13062237, - tags: { - information: "board", - name: "Doeveren Wandelroute", - tourism: "information", - }, - }, - { - type: "node", - id: 5716130103, - lat: 51.1767183, - lon: 3.1947867, - timestamp: "2018-06-24T22:04:21Z", - version: 1, - changeset: 60130942, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 5745783208, - lat: 51.1782581, - lon: 3.2410111, - timestamp: "2018-07-07T18:42:23Z", - version: 1, - changeset: 60494990, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 5745807545, - lat: 51.1784037, - lon: 3.2369439, - timestamp: "2018-07-07T18:58:25Z", - version: 1, - changeset: 60495307, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 5745807551, - lat: 51.1783278, - lon: 3.236678, - timestamp: "2018-07-07T18:58:25Z", - version: 1, - changeset: 60495307, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6535241426, - lat: 51.1693142, - lon: 3.1673093, - timestamp: "2019-06-09T13:50:19Z", - version: 1, - changeset: 71071874, - user: "Hopperpop", - uid: 3664604, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6535241427, - lat: 51.169265, - lon: 3.1673159, - timestamp: "2019-06-09T13:50:19Z", - version: 1, - changeset: 71071874, - user: "Hopperpop", - uid: 3664604, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6535241428, - lat: 51.1692199, - lon: 3.1673224, - timestamp: "2019-06-09T13:50:19Z", - version: 1, - changeset: 71071874, - user: "Hopperpop", - uid: 3664604, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6535241430, - lat: 51.1685726, - lon: 3.1678225, - timestamp: "2019-06-09T13:50:19Z", - version: 1, - changeset: 71071874, - user: "Hopperpop", - uid: 3664604, - tags: { bench: "yes", leisure: "picnic_table", material: "wood" }, - }, - { - type: "node", - id: 6536026827, - lat: 51.1703142, - lon: 3.1691109, - timestamp: "2019-06-09T22:54:45Z", - version: 1, - changeset: 71082671, - user: "Hopperpop", - uid: 3664604, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6536026828, - lat: 51.1702795, - lon: 3.1691552, - timestamp: "2019-06-09T22:54:45Z", - version: 1, - changeset: 71082671, - user: "Hopperpop", - uid: 3664604, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6712112244, - lat: 51.1595064, - lon: 3.2021482, - timestamp: "2020-05-24T21:35:50Z", - version: 2, - changeset: 85695537, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7304050040, - lat: 51.1560908, - lon: 3.1748919, - timestamp: "2020-03-17T19:11:00Z", - version: 1, - changeset: 82315744, - user: "Hopperpop", - uid: 3664604, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7304050041, - lat: 51.1560141, - lon: 3.1749533, - timestamp: "2020-03-17T19:11:00Z", - version: 1, - changeset: 82315744, - user: "Hopperpop", - uid: 3664604, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7304050042, - lat: 51.156032, - lon: 3.1749379, - timestamp: "2020-03-17T19:11:00Z", - version: 1, - changeset: 82315744, - user: "Hopperpop", - uid: 3664604, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7439979218, - lat: 51.1780402, - lon: 3.2178666, - timestamp: "2020-04-24T00:56:14Z", - version: 1, - changeset: 84027933, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 7439979219, - lat: 51.1780508, - lon: 3.2179033, - timestamp: "2020-04-24T00:56:14Z", - version: 1, - changeset: 84027933, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 7529262982, - lat: 51.1585566, - lon: 3.1715528, - timestamp: "2020-05-17T16:12:04Z", - version: 1, - changeset: 85340264, - user: "Hopperpop", - uid: 3664604, - tags: { board_type: "map", information: "board", tourism: "information" }, - }, - { - type: "node", - id: 7529262984, - lat: 51.1585786, - lon: 3.1715385, - timestamp: "2020-05-17T16:12:04Z", - version: 1, - changeset: 85340264, - user: "Hopperpop", - uid: 3664604, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7554879668, - lat: 51.1573713, - lon: 3.2043731, - timestamp: "2020-05-24T21:35:50Z", - version: 1, - changeset: 85695537, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { - access: "yes", - amenity: "toilets", - fee: "no", - "toilets:disposal": "flush", - unisex: "yes", - wheelchair: "yes", - }, - }, - { - type: "node", - id: 7554879669, - lat: 51.1594855, - lon: 3.2021507, - timestamp: "2020-05-24T21:35:50Z", - version: 1, - changeset: 85695537, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7556988723, - lat: 51.1330234, - lon: 3.1839944, - timestamp: "2020-05-25T19:19:56Z", - version: 1, - changeset: 85730259, - user: "Hopperpop", - uid: 3664604, - tags: { amenity: "bench", material: "wood" }, - }, - { - type: "node", - id: 7575825326, - lat: 51.1386553, - lon: 3.1797358, - timestamp: "2020-05-31T19:49:45Z", - version: 1, - changeset: 86019474, - user: "Hopperpop", - uid: 3664604, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7575825327, - lat: 51.1382456, - lon: 3.1797422, - timestamp: "2020-05-31T19:49:45Z", - version: 1, - changeset: 86019474, - user: "Hopperpop", - uid: 3664604, - tags: { information: "board", tourism: "information" }, - }, - { - type: "node", - id: 8109498958, - lat: 51.1332267, - lon: 3.2341272, - timestamp: "2020-11-11T20:42:45Z", - version: 1, - changeset: 93951029, - user: "L'imaginaire", - uid: 654234, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 8109498959, - lat: 51.1335011, - lon: 3.2343954, - timestamp: "2020-11-11T20:42:45Z", - version: 1, - changeset: 93951029, - user: "L'imaginaire", - uid: 654234, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 8198894646, - lat: 51.125688, - lon: 3.1856217, - timestamp: "2020-12-06T23:39:34Z", - version: 3, - changeset: 95384686, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { - image: "https://i.imgur.com/O5kX20u.jpg", - information: "board", - tourism: "information", - }, - }, - { - type: "node", - id: 8199012519, - lat: 51.1262245, - lon: 3.1802429, - timestamp: "2020-12-07T00:12:14Z", - version: 3, - changeset: 95385124, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { - image: "https://i.imgur.com/tomw9p5.jpg", - information: "board", - tourism: "information", - }, - }, - { - type: "node", - id: 8199244816, - lat: 51.1252874, - lon: 3.1837622, - timestamp: "2020-12-06T17:14:52Z", - version: 1, - changeset: 95374174, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 8199301617, - lat: 51.1256827, - lon: 3.1853543, - timestamp: "2020-12-06T17:14:52Z", - version: 1, - changeset: 95374174, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench", backrest: "no" }, - }, - { - type: "node", - id: 8255488518, - lat: 51.1406698, - lon: 3.235178, - timestamp: "2020-12-23T18:20:35Z", - version: 1, - changeset: 96342158, - user: "L'imaginaire", - uid: 654234, - tags: { amenity: "bench", backrest: "yes" }, - }, - { - type: "node", - id: 9316104741, - lat: 51.1330984, - lon: 3.2335257, - timestamp: "2021-12-06T18:31:00Z", - version: 1, - changeset: 114629890, - user: "L'imaginaire", - uid: 654234, - tags: { information: "board", tourism: "information" }, - }, - { - type: "node", - id: 9442532340, - lat: 51.1763651, - lon: 3.1947952, - timestamp: "2022-01-23T16:26:28Z", - version: 2, - changeset: 116506336, - user: "L'imaginaire", - uid: 654234, - tags: { - amenity: "bench", - backrest: "yes", - image: "https://i.imgur.com/eZ0Loii.jpg", - }, - }, - { - type: "way", - id: 15242261, - timestamp: "2020-04-05T07:08:45Z", - version: 8, - changeset: 83089516, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 150996092, 150996093, 6754312552, 6754312553, 6754312550, 6754312551, 150996094, - 150996095, 150996097, 150996098, 6754312560, 6754312559, 6754312558, 150996099, - 6754312557, 150996100, 6754312555, 6754312556, 150996101, 6754312554, 150996092, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 16514228, - timestamp: "2017-06-13T07:14:23Z", - version: 9, - changeset: 49490318, - user: "Jakka", - uid: 2403313, - nodes: [170464837, 170464839, 170464840, 170464841, 170464837], - tags: { - access: "yes", - amenity: "parking", - fee: "no", - maxstay: "4 hours", - parking: "surface", - }, - }, - { - type: "way", - id: 76706071, - timestamp: "2017-06-17T07:51:31Z", - version: 4, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [ - 903903386, 1038557094, 1038557233, 1038557143, 1038557075, 903903387, - 1038557195, 903903388, 903903390, 903904576, 903903386, - ], - tags: { access: "permissive", amenity: "parking" }, - }, - { - type: "way", - id: 89601157, - timestamp: "2019-11-09T18:40:50Z", - version: 3, - changeset: 76851428, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 1038557083, 1038557078, 1038557104, 1038557072, 1038557108, 1038557230, - 1038557227, 1038557102, 1038557137, 1038575040, 1038557191, 1038557014, - 6960473080, 1038557035, 1038557012, 1038557083, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 89604999, - timestamp: "2020-01-16T22:01:28Z", - version: 5, - changeset: 79667667, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 1038583404, 1038583491, 1038583375, 1038583483, 1038583479, 1038583398, - 1038583459, 1038583456, 1038583446, 1038583441, 1038583425, 1038583501, - 1038583451, 1038583463, 1038583476, 1038583404, - ], - tags: { - access: "yes", - amenity: "parking", - capacity: "57", - carpool: "yes", - description: "carpoolparking", - fee: "no", - name: "Loppem", - parking: "surface", - }, - }, - { - type: "way", - id: 92035679, - timestamp: "2019-10-05T08:05:33Z", - version: 7, - changeset: 75311122, - user: "skyman81", - uid: 955688, - nodes: [ - 1069177920, 6853179264, 1069177925, 1069177919, 6853179269, 6853179268, - 6853179267, 6853179215, 6853179213, 6853179214, 1069178133, 1069177984, - 6853179230, 6853179228, 6853179229, 6853179224, 6853179225, 6853179227, - 6853179226, 6853179216, 6853179220, 6853179219, 6853179218, 6853179217, - 6853179223, 6853179221, 6853179222, 1069177967, 1069177852, 6853179211, - 6853179212, 6853179210, 6853179327, 6853179208, 6853179209, 6853179203, - 1069177976, 6853179207, 6853179206, 6853179205, 6853179204, 6853179202, - 1069177849, 6852012579, 6852012578, 6852012580, 6852012577, 6852012581, - 6852012582, 6852012583, 1069177845, 1759437085, 1519342743, 1519342742, - 1069178166, 1069177853, 1069177915, 6853179235, 6853179234, 6853179236, - 1069177933, 6853179237, 6853179238, 1069178021, 6853179246, 6853179244, - 6853179245, 6853179240, 6853179243, 6853179241, 6853179242, 6853179239, - 6853179248, 6853179249, 6853179250, 6853179247, 1069177873, 6853179262, - 6853179263, 6853179260, 6853179261, 6853179256, 6853179259, 6853179257, - 6853179258, 1069177920, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 101248451, - timestamp: "2018-05-23T14:18:27Z", - version: 2, - changeset: 59213416, - user: "Jakka", - uid: 2403313, - nodes: [1168728158, 1168728325, 1168728159, 5637669355, 1168728109, 1168728158], - tags: { access: "private", amenity: "parking", name: "Parking Merkenveld" }, - }, - { - type: "way", - id: 101248462, - timestamp: "2020-05-25T13:53:02Z", - version: 3, - changeset: 85720081, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [1168727876, 1168728288, 1168728412, 1168728208, 1168727876], - tags: { - amenity: "toilets", - building: "yes", - "source:geometry:date": "2019-03-14", - "source:geometry:ref": "Gbg/6588148", - }, - }, - { - type: "way", - id: 131622387, - timestamp: "2015-01-09T12:06:51Z", - version: 2, - changeset: 28017707, - user: "TripleBee", - uid: 497177, - nodes: [1448421093, 1448421099, 1448421091, 1448421081, 1448421093], - tags: { amenity: "parking", name: "Tudor - Zeeweg", parking: "surface" }, - }, - { - type: "way", - id: 145691934, - timestamp: "2012-01-15T12:43:37Z", - version: 1, - changeset: 10397429, - user: "Sanderd17", - uid: 253266, - nodes: [1590642859, 1590642860, 1590642858, 1590642849, 1590642859], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 145691937, - timestamp: "2012-01-15T12:43:37Z", - version: 1, - changeset: 10397429, - user: "Sanderd17", - uid: 253266, - nodes: [1590642829, 1590642828, 1590642830, 1590642832, 1590642829], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 158901716, - timestamp: "2017-06-13T07:12:20Z", - version: 2, - changeset: 49490264, - user: "Jakka", - uid: 2403313, - nodes: [ - 1710245713, 1710245718, 1710245707, 1710245705, 1710245703, 1710245715, - 1710245711, 1710245709, 1710245701, 1710245713, - ], - tags: { - access: "yes", - amenity: "parking", - capacity: "14", - fee: "no", - parking: "surface", - }, - }, - { - type: "way", - id: 158904558, - timestamp: "2020-12-22T22:39:41Z", - version: 4, - changeset: 96283379, - user: "M!dgard", - uid: 763799, - nodes: [ - 1710262742, 1710262745, 1710262735, 1710262733, 1710262732, 1710262743, - 1710262741, 1710262739, 1710262744, 1710262737, 1710262736, 1710262731, - 1710262738, 1710262742, - ], - tags: { - access: "yes", - alt_name: "Schoolparking", - amenity: "parking", - fee: "no", - parking: "surface", - }, - }, - { - type: "way", - id: 158906028, - timestamp: "2017-06-13T07:27:19Z", - version: 5, - changeset: 49490646, - user: "Jakka", - uid: 2403313, - nodes: [ - 1710276259, 1710276251, 1810330766, 1710276255, 1710276261, 1710276240, - 1710276232, 1710276257, 1710276243, 1710276253, 1810347217, 1710276242, - 1710276259, - ], - tags: { - access: "yes", - amenity: "parking", - fee: "no", - name: "Parking Sportcentrum De Valkaart", - parking: "surface", - }, - }, - { - type: "way", - id: 160825858, - timestamp: "2012-04-23T20:35:52Z", - version: 1, - changeset: 11399632, - user: "martino260", - uid: 655442, - nodes: [ - 1728421375, 1728421374, 1728421379, 1728421377, 1728421376, 1728421378, - 1728421375, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 162602213, - timestamp: "2020-04-11T18:12:16Z", - version: 8, - changeset: 83407731, - user: "JanFi", - uid: 672253, - nodes: [ - 1920143232, 7393009684, 7393048385, 1744641293, 1523513488, 1744641292, - 1920143232, - ], - tags: { amenity: "parking", capacity: "15" }, - }, - { - type: "way", - id: 165489167, - timestamp: "2020-10-12T19:06:29Z", - version: 3, - changeset: 92371840, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 4912197370, 4912197365, 4912197373, 4912197364, 4912197372, 1770289505, - 4912197362, 4912197371, 4912197374, 4912197363, 4912197368, 4912197366, - 4912197369, 4912197367, 4912197370, - ], - tags: { - access: "yes", - amenity: "parking", - fee: "no", - name: "Bad Neuheimplein", - parking: "surface", - }, - }, - { - type: "way", - id: 168291852, - timestamp: "2017-07-19T11:17:33Z", - version: 3, - changeset: 50402298, - user: "martino260", - uid: 655442, - nodes: [ - 1795793399, 4979389763, 1795793409, 1795793395, 1795793393, 1795793397, - 1795793407, 1795793406, 1795793408, 1795793405, 1795793399, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 169875513, - timestamp: "2017-06-13T07:26:09Z", - version: 2, - changeset: 49490613, - user: "Jakka", - uid: 2403313, - nodes: [1810345951, 1810345955, 1810345947, 1810345944, 1810345951], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 170015605, - timestamp: "2017-06-17T07:51:33Z", - version: 4, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [1811673425, 1811673421, 1811673418, 1811673423, 1811673425], - tags: { - access: "private", - amenity: "parking", - name: "gheeraert E40", - operator: "Gheeraert", - }, - }, - { - type: "way", - id: 170018487, - timestamp: "2017-06-17T07:51:33Z", - version: 3, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [1811699779, 1811699778, 1811699776, 1811699777, 1811699779], - tags: { access: "private", amenity: "parking", name: "Gheeraert vooraan" }, - }, - { - type: "way", - id: 170559194, - timestamp: "2017-06-17T07:51:33Z", - version: 2, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [1817319304, 1817319302, 1817319297, 1817319301, 1817319304], - tags: { access: "private", amenity: "parking", name: "Gheeraert laadkade" }, - }, - { - type: "way", - id: 170559195, - timestamp: "2017-06-17T07:51:33Z", - version: 2, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [1817319299, 1817319303, 1817319300, 1817319292, 1817319299], - tags: { - access: "private", - amenity: "parking", - name: "Gheeraert spoorweg trailers", - }, - }, - { - type: "way", - id: 170559196, - timestamp: "2017-06-17T07:51:33Z", - version: 2, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [1817319293, 1817319289, 1817319291, 1817319296, 1817319293], - tags: { access: "private", amenity: "parking", name: "Gheeraert spoorweg trucks" }, - }, - { - type: "way", - id: 170559197, - timestamp: "2017-06-17T07:51:33Z", - version: 2, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [1817319294, 1817319298, 1817319295, 1817319290, 1817319294], - tags: { access: "private", amenity: "parking", name: "Gheeraert zijkant" }, - }, - { - type: "way", - id: 170559292, - timestamp: "2017-06-17T07:51:33Z", - version: 2, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [1817320496, 1817320494, 1817320493, 1817320495, 1817320496], - tags: { access: "private", amenity: "parking", name: "Gheeraert vooraan gebouw" }, - }, - { - type: "way", - id: 170559832, - timestamp: "2020-10-07T10:51:41Z", - version: 6, - changeset: 92105788, - user: "effem", - uid: 16437, - nodes: [ - 1817324515, 1817324509, 1817324491, 6397031888, 4044172008, 4044172003, - 4044171997, 4044171962, 4044171957, 4044171976, 1817324489, 1817324488, - 3550860268, 1817324505, 3550860269, 1817324513, 1817324515, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 223674368, - timestamp: "2020-12-03T18:47:28Z", - version: 3, - changeset: 95244226, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 2325437844, 8191691971, 8191691973, 8191691972, 2325437840, 8191691970, - 414025563, 2325437844, - ], - tags: { amenity: "parking", name: "Tillegembos", parking: "surface" }, - }, - { - type: "way", - id: 237214948, - timestamp: "2017-06-17T07:51:35Z", - version: 2, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [2451574741, 2451574742, 2451574744, 1015583939, 2451574741], - tags: { access: "private", amenity: "parking" }, - }, - { - type: "way", - id: 237214949, - timestamp: "2017-06-17T07:51:35Z", - version: 2, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [2451574748, 2451574746, 2451574747, 2451574749, 2451574748], - tags: { access: "private", amenity: "parking" }, - }, - { - type: "way", - id: 237214950, - timestamp: "2017-06-17T07:51:35Z", - version: 3, - changeset: 49608752, - user: "rowers2", - uid: 2445224, - nodes: [2451569392, 1015567837, 2451574745, 2451574743, 2451578121, 2451569392], - tags: { access: "private", amenity: "parking" }, - }, - { - type: "way", - id: 325909586, - timestamp: "2016-01-05T18:51:49Z", - version: 2, - changeset: 36387330, - user: "JanFi", - uid: 672253, - nodes: [ - 3325315243, 111759500, 3325315247, 3325315232, 3325315230, 3325315226, - 1169056712, 3325315243, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 326834162, - timestamp: "2021-10-10T21:49:11Z", - version: 4, - changeset: 112350014, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [ - 3335137807, 3335137692, 3335137795, 9163493632, 3335137802, 3335137812, - 9163486855, 9163486856, 3335137823, 3335137807, - ], - tags: { - access: "customers", - amenity: "parking", - operator: "Best Western Weinebrugge", - parking: "surface", - }, - }, - { - type: "way", - id: 327849054, - timestamp: "2015-02-12T20:26:22Z", - version: 1, - changeset: 28807613, - user: "escada", - uid: 436365, - nodes: [ - 3346575929, 3346575873, 3346575876, 3346575843, 3346575845, 3346575891, - 3346575901, 3346575923, 3346575928, 3346575950, 3346575929, - ], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 327849055, - timestamp: "2016-10-08T21:24:46Z", - version: 2, - changeset: 42742859, - user: "maggot27", - uid: 118021, - nodes: [ - 3346575945, 3346575946, 3346575943, 3346575933, 3346575925, 3346575917, - 3346575903, 3346575908, 3346575889, 3346575886, 3346575945, - ], - tags: { access: "customers", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 327849056, - timestamp: "2015-02-12T20:26:22Z", - version: 1, - changeset: 28807613, - user: "escada", - uid: 436365, - nodes: [ - 3346575865, 3346575853, 3346575855, 3346575846, 3346575840, 3346575858, - 3346575865, - ], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 374677860, - timestamp: "2015-10-10T09:08:20Z", - version: 1, - changeset: 34545536, - user: "Jakka", - uid: 2403313, - nodes: [3780611504, 3780611502, 3780611500, 3780611503, 3780611504], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 374677861, - timestamp: "2015-10-10T09:08:20Z", - version: 1, - changeset: 34545536, - user: "Jakka", - uid: 2403313, - nodes: [3780611495, 3780611493, 3780611492, 3780611494, 3780611495], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 374677862, - timestamp: "2015-10-10T09:08:20Z", - version: 1, - changeset: 34545536, - user: "Jakka", - uid: 2403313, - nodes: [3780611498, 3780611497, 3780611496, 3780611499, 3780611501, 3780611498], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 389912371, - timestamp: "2016-01-06T16:51:45Z", - version: 1, - changeset: 36407948, - user: "Spectrokid", - uid: 19775, - nodes: [3930713440, 3930713451, 3930713447, 3930713437, 3930713440], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 389912372, - timestamp: "2019-11-16T13:17:09Z", - version: 4, - changeset: 77164376, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 3930713454, 3930713442, 3930713435, 3930713429, 5826811614, 3930713426, - 3930713430, 6982605752, 6982605754, 3930713438, 6982605769, 6982605766, - 6982605767, 6982605762, 6982605764, 3930713446, 3930713454, - ], - tags: { access: "customers", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 401995684, - timestamp: "2020-10-07T10:52:00Z", - version: 3, - changeset: 92105972, - user: "effem", - uid: 16437, - nodes: [4044171963, 4044171954, 4044171924, 4044171936, 4044171941, 4044171963], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 500067820, - timestamp: "2017-06-13T07:49:12Z", - version: 1, - changeset: 49491219, - user: "Jakka", - uid: 2403313, - nodes: [ - 4912203166, 4912203165, 4912203164, 4912203163, 4912203162, 4912203161, - 4912203160, 4912203166, - ], - tags: { access: "yes", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 500067821, - timestamp: "2017-06-13T07:49:12Z", - version: 1, - changeset: 49491219, - user: "Jakka", - uid: 2403313, - nodes: [4912203170, 4912203169, 4912203168, 4912203167, 4912203170], - tags: { access: "yes", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 500067822, - timestamp: "2017-06-13T07:49:12Z", - version: 1, - changeset: 49491219, - user: "Jakka", - uid: 2403313, - nodes: [4912203174, 4912203173, 4912203172, 4912203171, 4912203174], - tags: { access: "yes", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 500067823, - timestamp: "2017-06-13T07:49:12Z", - version: 1, - changeset: 49491219, - user: "Jakka", - uid: 2403313, - nodes: [4912203179, 4912203178, 4912203177, 4912203176, 4912203179], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 500069613, - timestamp: "2018-10-11T09:30:48Z", - version: 2, - changeset: 63409550, - user: "Jakka", - uid: 2403313, - nodes: [ - 4912214695, 4912214685, 4912214694, 4912214693, 4912214692, 4912214680, - 4912214695, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 500071452, - timestamp: "2018-10-11T09:30:48Z", - version: 2, - changeset: 63409550, - user: "Jakka", - uid: 2403313, - nodes: [ - 4912225068, 4912225062, 4912225063, 4912225053, 4912225064, 4912214694, - 4912214685, 4912225068, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 500071455, - timestamp: "2018-10-11T09:30:48Z", - version: 2, - changeset: 63409550, - user: "Jakka", - uid: 2403313, - nodes: [ - 4912225070, 4912214681, 4912214686, 4912225052, 4912225051, 4912225067, - 4912225062, 4912225068, 4912225070, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 500071458, - timestamp: "2018-10-11T09:30:48Z", - version: 2, - changeset: 63409550, - user: "Jakka", - uid: 2403313, - nodes: [ - 4912214695, 4912214680, 4912225069, 4912225050, 1525460846, 4912214681, - 4912225070, 4912214695, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 533276307, - timestamp: "2019-12-13T10:02:12Z", - version: 2, - changeset: 78364882, - user: "skyman81", - uid: 955688, - nodes: [5173881316, 5173881317, 5173881318, 7054196467, 5173881316], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 533276308, - timestamp: "2017-10-17T23:36:18Z", - version: 1, - changeset: 53027174, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [5173881320, 5173881621, 5173881622, 5173881623, 5173881320], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 533276309, - timestamp: "2017-10-17T23:36:18Z", - version: 1, - changeset: 53027174, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [5173881624, 5173881625, 5173881626, 5173881627, 5173881624], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 579848581, - timestamp: "2020-04-05T07:08:57Z", - version: 6, - changeset: 83089516, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 4043782112, 5825400688, 5552466020, 6997096756, 6997096759, 6997096758, - 5552466018, 5552466013, 5552466014, 6997076567, 4043782112, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 584455569, - timestamp: "2019-10-31T19:49:18Z", - version: 2, - changeset: 76465627, - user: "Hopperpop", - uid: 3664604, - nodes: [5586765933, 5586765934, 5586765935, 5586765936, 5586765933], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 585977870, - timestamp: "2019-10-11T13:28:22Z", - version: 2, - changeset: 75566739, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 5587844460, 5599314384, 5599314385, 1659850846, 6870850178, 5587844462, - 5587844461, 6870863414, 5587844460, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 587014342, - timestamp: "2018-05-09T19:13:29Z", - version: 1, - changeset: 58829130, - user: "Sille Van Landschoot", - uid: 4852501, - nodes: [ - 5607796820, 5607798725, 5607798721, 5607798722, 5607796819, 5607798723, - 5607796820, - ], - tags: { - access: "permissive", - amenity: "parking", - park_ride: "no", - parking: "surface", - surface: "paved", - }, - }, - { - type: "way", - id: 590167103, - timestamp: "2018-05-22T12:37:36Z", - version: 1, - changeset: 59179114, - user: "ForstEK", - uid: 1737608, - nodes: [5635001277, 5635001274, 5635001275, 5635001276, 5635001277], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167113, - timestamp: "2020-07-29T17:45:20Z", - version: 2, - changeset: 88691835, - user: "JanFi", - uid: 672253, - nodes: [ - 5635001312, 5635001306, 7767137237, 7767137235, 7767137236, 7767137234, - 5635001312, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167134, - timestamp: "2018-05-22T12:37:37Z", - version: 1, - changeset: 59179114, - user: "ForstEK", - uid: 1737608, - nodes: [5635001374, 5635001373, 5635001372, 5635001371, 5635001374], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167135, - timestamp: "2018-05-22T12:37:37Z", - version: 1, - changeset: 59179114, - user: "ForstEK", - uid: 1737608, - nodes: [5635001378, 5635001377, 5635001376, 5635001375, 5635001378], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167136, - timestamp: "2018-05-22T12:37:37Z", - version: 1, - changeset: 59179114, - user: "ForstEK", - uid: 1737608, - nodes: [5635001382, 5635001381, 5635001380, 5635001379, 5635001382], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167137, - timestamp: "2019-07-06T15:58:19Z", - version: 3, - changeset: 71962591, - user: "gjosch", - uid: 1776978, - nodes: [ - 5635001386, 5882873336, 5882873337, 5882873338, 5882873335, 6593340582, - 6593340583, 5882873334, 6593340584, 5635001385, 5635001384, 5635001383, - 5635001386, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 590167147, - timestamp: "2018-05-22T12:37:38Z", - version: 1, - changeset: 59179114, - user: "ForstEK", - uid: 1737608, - nodes: [5635001417, 5635001414, 5635001415, 5635001416, 5635001417], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 601406079, - timestamp: "2018-06-24T22:15:06Z", - version: 1, - changeset: 60131072, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [5716136617, 5716136618, 5716136619, 5716136620, 5716136617], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 632813009, - timestamp: "2018-10-11T09:24:42Z", - version: 1, - changeset: 63409297, - user: "Jakka", - uid: 2403313, - nodes: [ - 5974489618, 1810326044, 1810326087, 5974489617, 5972179348, 5972179347, - 5972179346, 5972179345, 5972179344, 5972179343, 5972179331, 5974489616, - 5974489615, 5974489614, 5974489618, - ], - tags: { amenity: "parking", name: "Gemeenteplein", parking: "surface" }, - }, - { - type: "way", - id: 668043297, - timestamp: "2019-04-10T18:34:27Z", - version: 2, - changeset: 69093378, - user: "RudolpheDeer", - uid: 9408828, - nodes: [6255587424, 6255587425, 6255587426, 6255587427, 6255587424], - tags: { access: "private", amenity: "parking" }, - }, - { - type: "way", - id: 670104236, - timestamp: "2019-02-13T00:05:02Z", - version: 1, - changeset: 67147239, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [6275462768, 6275462769, 6275462770, 6275462771, 6275462768], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 670104238, - timestamp: "2019-02-13T00:05:02Z", - version: 1, - changeset: 67147239, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [6275462772, 6275462989, 6275462773, 6275462774, 6275462775, 6275462772], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 670104239, - timestamp: "2019-02-13T00:05:02Z", - version: 1, - changeset: 67147239, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [6275462776, 6275462777, 6275462778, 6275462779, 6275462776], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 670104241, - timestamp: "2019-02-13T00:05:02Z", - version: 1, - changeset: 67147239, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [6275462780, 6275462781, 6275462782, 6275462783, 6275462780], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 670104242, - timestamp: "2019-02-13T00:05:02Z", - version: 1, - changeset: 67147239, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [6275462784, 6275462985, 6275462988, 6275462986, 6275462987, 6275462784], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 671840055, - timestamp: "2019-02-20T15:15:45Z", - version: 1, - changeset: 67395313, - user: "Tim Couwelier", - uid: 7246683, - nodes: [ - 6291339827, 6291339828, 6291339816, 6291339815, 6291339822, 6291339821, - 6291339829, 6291339830, 6291339827, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 695825223, - timestamp: "2019-06-08T15:19:24Z", - version: 1, - changeset: 71053301, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 1519476746, 6533893620, 6533893621, 6533893622, 1519476797, 1519476620, - 1519476746, - ], - tags: { access: "yes", amenity: "parking" }, - }, - { - type: "way", - id: 695825224, - timestamp: "2019-06-08T15:19:24Z", - version: 1, - changeset: 71053301, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 1519476744, 6533893624, 6533893625, 6533893626, 1519476698, 1519476635, - 1519476744, - ], - tags: { access: "yes", amenity: "parking" }, - }, - { - type: "way", - id: 696040917, - timestamp: "2019-06-09T23:24:59Z", - version: 2, - changeset: 71082992, - user: "Hopperpop", - uid: 3664604, - nodes: [6536026850, 6536026851, 6536026852, 6536026853, 6536026850], - tags: { amenity: "parking", name: "Kasteel Tudor" }, - }, - { - type: "way", - id: 696043218, - timestamp: "2019-06-09T23:24:59Z", - version: 1, - changeset: 71082992, - user: "Hopperpop", - uid: 3664604, - nodes: [6536038234, 6536038235, 6536038236, 6536038237, 6536038234], - tags: { access: "customers", amenity: "parking" }, - }, - { - type: "way", - id: 700675991, - timestamp: "2020-12-18T10:48:20Z", - version: 2, - changeset: 96062619, - user: "Hopperpop", - uid: 3664604, - nodes: [6579962064, 6579962065, 6579962066, 6579962068, 6579962067, 6579962064], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 705278707, - timestamp: "2020-09-30T20:36:55Z", - version: 2, - changeset: 91787895, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 6625037999, 6625038000, 6625038001, 6625038002, 6625038003, 6004375826, - 6625038005, 6625038006, 6625037999, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 719482833, - timestamp: "2019-08-28T21:18:49Z", - version: 1, - changeset: 73857967, - user: "Hopperpop", - uid: 3664604, - nodes: [6754312544, 6754312543, 6754312542, 6754312541, 6754312544], - tags: { access: "yes", amenity: "parking", capacity: "5" }, - }, - { - type: "way", - id: 719482834, - timestamp: "2019-08-28T21:18:49Z", - version: 1, - changeset: 73857967, - user: "Hopperpop", - uid: 3664604, - nodes: [6754312565, 6754312564, 6754312563, 6754312562, 6754312565], - tags: { access: "yes", amenity: "parking", capacity: "12" }, - }, - { - type: "way", - id: 737054013, - timestamp: "2019-10-20T15:39:32Z", - version: 1, - changeset: 75957554, - user: "Hopperpop", - uid: 3664604, - nodes: [5826811496, 5826811497, 5826811494, 5826811495, 5826811496], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 737054014, - timestamp: "2019-10-20T15:39:32Z", - version: 1, - changeset: 75957554, - user: "Hopperpop", - uid: 3664604, - nodes: [5826810676, 5826810673, 5826810674, 5826810675, 5826810676], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 737054015, - timestamp: "2019-10-20T15:39:32Z", - version: 1, - changeset: 75957554, - user: "Hopperpop", - uid: 3664604, - nodes: [5826810681, 5826810678, 5826810679, 5826810680, 5826810681], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 737093410, - timestamp: "2021-08-14T21:52:07Z", - version: 2, - changeset: 109683899, - user: "effem", - uid: 16437, - nodes: [5826811559, 5826811536, 5826811535, 5826811561, 5826811560, 5826811559], - tags: { - access: "yes", - amenity: "parking", - capacity: "4", - fee: "no", - parking: "surface", - }, - }, - { - type: "way", - id: 737093411, - timestamp: "2021-08-14T21:52:03Z", - version: 2, - changeset: 109683899, - user: "effem", - uid: 16437, - nodes: [5826811551, 5826811547, 5826811548, 5826811549, 5826811550, 5826811551], - tags: { - access: "yes", - amenity: "parking", - capacity: "4", - fee: "no", - parking: "surface", - }, - }, - { - type: "way", - id: 739652949, - timestamp: "2019-10-28T20:18:16Z", - version: 1, - changeset: 76314556, - user: "Hopperpop", - uid: 3664604, - nodes: [6925536542, 6925536541, 6925536540, 6925536539, 6925536542], - tags: { amenity: "parking", capacity: "6", parking: "surface" }, - }, - { - type: "way", - id: 741675236, - timestamp: "2020-12-17T22:07:55Z", - version: 4, - changeset: 96029554, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 6943148207, 6943148206, 6943148205, 6943148204, 1637742821, 6943148203, - 6943148202, 6943148201, 6943148200, 6943148199, 6943148198, 6943148197, - 6943148207, - ], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 742295526, - timestamp: "2019-11-05T19:27:15Z", - version: 1, - changeset: 76664875, - user: "Hopperpop", - uid: 3664604, - nodes: [6949357909, 6949357908, 6949357907, 6949357906, 6949357909], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 742295527, - timestamp: "2019-11-05T19:27:15Z", - version: 1, - changeset: 76664875, - user: "Hopperpop", - uid: 3664604, - nodes: [6949357913, 6949357912, 6949357911, 6949357910, 6949357913], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 742295528, - timestamp: "2019-11-05T19:27:15Z", - version: 1, - changeset: 76664875, - user: "Hopperpop", - uid: 3664604, - nodes: [6949357917, 6949357916, 6949357915, 6949357914, 6949357917], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 742295529, - timestamp: "2019-11-05T19:27:15Z", - version: 1, - changeset: 76664875, - user: "Hopperpop", - uid: 3664604, - nodes: [6949357921, 6949357920, 6949357919, 6949357918, 6949357921], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 746170866, - timestamp: "2019-11-16T15:27:02Z", - version: 1, - changeset: 77167609, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 6982906547, 6982906546, 6982906545, 6982906544, 6982906543, 6982906542, - 6982906547, - ], - tags: { access: "customers", amenity: "parking" }, - }, - { - type: "way", - id: 747880657, - timestamp: "2021-09-19T12:40:45Z", - version: 2, - changeset: 111407274, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 3325315397, 6997076566, 6997076565, 6997076563, 6997076562, 6997076564, - 3325315395, 3325315397, - ], - tags: { access: "customers", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 763977465, - timestamp: "2020-12-17T21:59:46Z", - version: 2, - changeset: 96029554, - user: "Hopperpop", - uid: 3664604, - nodes: [7137343680, 7137343681, 7137343682, 7137343683, 7137343680], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 763977466, - timestamp: "2020-12-17T21:59:40Z", - version: 2, - changeset: 96029554, - user: "Hopperpop", - uid: 3664604, - nodes: [7137343684, 7137383185, 7137383186, 7137383187, 7137343684], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 803821090, - timestamp: "2020-05-14T17:37:10Z", - version: 1, - changeset: 85215781, - user: "Hopperpop", - uid: 3664604, - nodes: [7519058290, 7519058289, 7519058288, 7519058287, 7519058290], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 803821091, - timestamp: "2020-05-14T17:37:10Z", - version: 1, - changeset: 85215781, - user: "Hopperpop", - uid: 3664604, - nodes: [7519058294, 7519058293, 7519058292, 7519058291, 7519058294], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 803821092, - timestamp: "2020-05-14T17:37:10Z", - version: 1, - changeset: 85215781, - user: "Hopperpop", - uid: 3664604, - nodes: [7519058298, 7519058297, 7519058296, 7519058295, 7519058298], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 803821093, - timestamp: "2020-05-14T17:37:10Z", - version: 1, - changeset: 85215781, - user: "Hopperpop", - uid: 3664604, - nodes: [7519058302, 7519058301, 7519058300, 7519058299, 7519058302], - tags: { access: "private", amenity: "parking", capacity: "6", parking: "surface" }, - }, - { - type: "way", - id: 804963962, - timestamp: "2020-05-17T17:09:31Z", - version: 1, - changeset: 85342199, - user: "Hopperpop", - uid: 3664604, - nodes: [ - 7529417225, 7529417226, 7529417227, 7529417228, 7529417229, 7529417230, - 7529417232, 7529417225, - ], - tags: { - access: "customers", - amenity: "parking", - fee: "no", - operator: "’t Kiekekot", - parking: "surface", - surface: "unpaved", - }, - }, - { - type: "way", - id: 806875503, - timestamp: "2020-05-21T15:48:37Z", - version: 1, - changeset: 85563652, - user: "Hopperpop", - uid: 3664604, - nodes: [4042671969, 7545532512, 7545532514, 7545532513, 3359977305, 4042671969], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 806963547, - timestamp: "2020-05-21T21:16:34Z", - version: 1, - changeset: 85574048, - user: "Hopperpop", - uid: 3664604, - nodes: [7546193222, 7546193221, 7546193220, 7546193219, 7546193222], - tags: { access: "private", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 865357121, - timestamp: "2020-10-30T14:45:23Z", - version: 1, - changeset: 93296961, - user: "L'imaginaire", - uid: 654234, - nodes: [8065883228, 8065883227, 8065883226, 8065883225, 8065883228], - tags: { amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 865357122, - timestamp: "2020-10-30T14:45:23Z", - version: 1, - changeset: 93296961, - user: "L'imaginaire", - uid: 654234, - nodes: [8065883233, 8065883230, 8065883229, 8065883232, 8065883233], - tags: { amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 879281221, - timestamp: "2020-11-30T14:18:18Z", - version: 1, - changeset: 95050429, - user: "M!dgard", - uid: 763799, - nodes: [ - 8179735269, 8179735268, 8179735267, 8179735266, 8179735265, 8179735264, - 8179735224, 8179735269, - ], - tags: { access: "customers", amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 881770201, - timestamp: "2020-12-07T00:46:56Z", - version: 1, - changeset: 95385582, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [8200275847, 8200275848, 8200275844, 8200275853, 8200275849, 8200275847], - tags: { amenity: "parking", parking: "surface", surface: "grass" }, - }, - { - type: "way", - id: 978360549, - timestamp: "2021-09-01T08:53:08Z", - version: 1, - changeset: 110553113, - user: "JanFi", - uid: 672253, - nodes: [5761770202, 5761770204, 9052878229, 9052878228, 5761770202], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 1009692722, - timestamp: "2021-12-06T18:34:20Z", - version: 1, - changeset: 114629990, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 9316118540, 9316118536, 9316118531, 9316118535, 9316118534, 9316118533, - 9316118530, 9316118532, 3311835478, 9316118540, - ], - tags: { amenity: "parking", parking: "street_side" }, - }, - { - type: "relation", - id: 8188853, - timestamp: "2020-07-05T12:38:48Z", - version: 13, - changeset: 87554177, - user: "Pieter Vander Vennet", - uid: 3818858, - members: [ - { type: "way", ref: 577572397, role: "outer" }, - { type: "way", ref: 577572399, role: "outer" }, - ], - tags: { - access: "guided", - curator: "Luc Maene;Geert De Clercq", - email: "lucmaene@hotmail.com;geert.de.clercq1@pandora.be", - landuse: "meadow", - leisure: "nature_reserve", - name: "De Wulgenbroeken", - natural: "wetland", - operator: "Natuurpunt Brugge", - type: "multipolygon", - website: "https://natuurpuntbrugge.be/wulgenbroeken/", - wetland: "wet_meadow", - wikidata: "Q60061498", - wikipedia: "nl:Wulgenbroeken", - }, - }, - { - type: "relation", - id: 11163488, - timestamp: "2021-10-04T14:09:47Z", - version: 15, - changeset: 112079863, - user: "DieterWesttoer", - uid: 13062237, - members: [ - { type: "way", ref: 810604915, role: "outer" }, - { type: "way", ref: 989393316, role: "outer" }, - { type: "way", ref: 389026405, role: "inner" }, - { type: "way", ref: 810607458, role: "outer" }, - ], - tags: { - access: "yes", - curator: "Kris Lesage", - description: - "Wat Doeveren zo uniek maakt, zijn zijn kleine heidegebiedjes met soorten die erg verschillen van de Kempense heide. Doeveren en omstreken was vroeger één groot heidegebied, maar bestaat nu grotendeels uit bossen.", - dog: "leashed", - email: "doeveren@natuurpuntzedelgem.be", - image: "https://i.imgur.com/NEAsQZG.jpg", - "image:0": "https://i.imgur.com/Dq71hyQ.jpg", - "image:1": "https://i.imgur.com/mAIiT4f.jpg", - "image:2": "https://i.imgur.com/dELZU97.jpg", - "image:3": "https://i.imgur.com/Bso57JC.jpg", - "image:4": "https://i.imgur.com/9DtcfXo.jpg", - "image:5": "https://i.imgur.com/0R6eBfk.jpg", - "image:6": "https://i.imgur.com/b0JpvbR.jpg", - leisure: "nature_reserve", - name: "Doeveren", - operator: "Natuurpunt Zedelgem", - phone: "+32 486 25 25 30", - type: "multipolygon", - website: "https://www.natuurpuntzedelgem.be/gebieden/doeveren/", - wikidata: "Q56395754", - wikipedia: "nl:Doeveren (natuurgebied)", - }, - }, - { - type: "relation", - id: 11790117, - timestamp: "2020-10-24T19:11:01Z", - version: 1, - changeset: 92997462, - user: "Pieter Vander Vennet", - uid: 3818858, - members: [ - { type: "way", ref: 863373849, role: "outer" }, - { type: "way", ref: 777280458, role: "outer" }, - ], - tags: { - access: "no", - "description:0": "In gebruik als waterbuffering", - leisure: "nature_reserve", - name: "Kerkebeek", - operator: "Natuurpunt Brugge", - type: "multipolygon", - }, - }, - { type: "node", id: 1038638696, lat: 51.1197075, lon: 3.1827007 }, - { type: "node", id: 7578975029, lat: 51.1199041, lon: 3.1828945 }, - { type: "node", id: 7578975030, lat: 51.1201514, lon: 3.1831942 }, - { type: "node", id: 1038638743, lat: 51.1202445, lon: 3.1894878 }, - { type: "node", id: 7578975002, lat: 51.1202598, lon: 3.1897155 }, - { type: "node", id: 7578975007, lat: 51.1199771, lon: 3.1863015 }, - { type: "node", id: 7578975008, lat: 51.1199523, lon: 3.1863031 }, - { type: "node", id: 7578975009, lat: 51.1199279, lon: 3.1859524 }, - { type: "node", id: 1168728109, lat: 51.1275839, lon: 3.1765505 }, - { type: "node", id: 1168728158, lat: 51.1278835, lon: 3.1763986 }, - { type: "node", id: 1168728159, lat: 51.1276298, lon: 3.1767808 }, - { type: "node", id: 5637669355, lat: 51.1276039, lon: 3.1766509 }, - { type: "node", id: 1038638723, lat: 51.1272818, lon: 3.184601 }, - { type: "node", id: 7554434438, lat: 51.1225298, lon: 3.1847624 }, - { type: "node", id: 7578865273, lat: 51.122084, lon: 3.1846859 }, - { type: "node", id: 7578975032, lat: 51.1215762, lon: 3.1842866 }, - { type: "node", id: 1168727876, lat: 51.1290569, lon: 3.1766033 }, - { type: "node", id: 1168728208, lat: 51.1289763, lon: 3.1767696 }, - { type: "node", id: 1168728288, lat: 51.1291195, lon: 3.1766799 }, - { type: "node", id: 1168728325, lat: 51.1279295, lon: 3.1766288 }, - { type: "node", id: 1168728412, lat: 51.1290389, lon: 3.1768463 }, - { type: "node", id: 1038638712, lat: 51.1282367, lon: 3.1840296 }, - { type: "node", id: 1168727824, lat: 51.1312478, lon: 3.182233 }, - { type: "node", id: 3922375256, lat: 51.1301155, lon: 3.1848042 }, - { type: "node", id: 3922380071, lat: 51.1304048, lon: 3.1838954 }, - { type: "node", id: 3922380081, lat: 51.1305694, lon: 3.1845093 }, - { type: "node", id: 3922380086, lat: 51.1306445, lon: 3.1848152 }, - { type: "node", id: 3922380092, lat: 51.1307378, lon: 3.1849591 }, - { type: "node", id: 7577430793, lat: 51.1289492, lon: 3.1836032 }, - { type: "node", id: 7578975024, lat: 51.1299598, lon: 3.1841704 }, - { type: "node", id: 7578975028, lat: 51.1322547, lon: 3.1833542 }, - { type: "node", id: 7578975049, lat: 51.1313772, lon: 3.1838431 }, - { type: "node", id: 9167054153, lat: 51.1310258, lon: 3.1823668 }, - { type: "node", id: 9167054154, lat: 51.13145, lon: 3.1841492 }, - { type: "node", id: 9167054156, lat: 51.1316731, lon: 3.1850331 }, - { type: "node", id: 9274761589, lat: 51.1297088, lon: 3.1831312 }, - { type: "node", id: 9274761596, lat: 51.1296735, lon: 3.1831518 }, - { type: "node", id: 929120698, lat: 51.1276376, lon: 3.1903134 }, - { type: "node", id: 1038638592, lat: 51.1264889, lon: 3.19027 }, - { type: "node", id: 1038638661, lat: 51.1258582, lon: 3.1854626 }, - { type: "node", id: 1038638721, lat: 51.125636, lon: 3.1855855 }, - { type: "node", id: 1038638753, lat: 51.123845, lon: 3.1880289 }, - { type: "node", id: 3921878998, lat: 51.1255719, lon: 3.1902463 }, - { type: "node", id: 3921879004, lat: 51.1275463, lon: 3.188843 }, - { type: "node", id: 3921879011, lat: 51.1271626, lon: 3.1872368 }, - { type: "node", id: 3921879019, lat: 51.1277666, lon: 3.1868505 }, - { type: "node", id: 7554434436, lat: 51.1252645, lon: 3.1852941 }, - { type: "node", id: 7578865274, lat: 51.1230564, lon: 3.187978 }, - { type: "node", id: 7578865275, lat: 51.1226417, lon: 3.188075 }, - { type: "node", id: 7578904489, lat: 51.1247504, lon: 3.1900249 }, - { type: "node", id: 7578974988, lat: 51.1223221, lon: 3.1906513 }, - { type: "node", id: 7578974989, lat: 51.1224255, lon: 3.1905646 }, - { type: "node", id: 7578974990, lat: 51.1224672, lon: 3.1905195 }, - { type: "node", id: 7578974991, lat: 51.1228709, lon: 3.1901867 }, - { type: "node", id: 7578974992, lat: 51.1229568, lon: 3.1901459 }, - { type: "node", id: 7578974995, lat: 51.123814, lon: 3.1899138 }, - { type: "node", id: 7578974996, lat: 51.1239199, lon: 3.189925 }, - { type: "node", id: 7578974997, lat: 51.1244111, lon: 3.1899686 }, - { type: "node", id: 7578974998, lat: 51.1248503, lon: 3.190215 }, - { type: "node", id: 7578974999, lat: 51.1247917, lon: 3.1900516 }, - { type: "node", id: 7578975000, lat: 51.1248293, lon: 3.1900978 }, - { type: "node", id: 7578975001, lat: 51.1248444, lon: 3.1901483 }, - { type: "node", id: 7578975035, lat: 51.1250798, lon: 3.1851611 }, - { type: "node", id: 7578975045, lat: 51.1278881, lon: 3.1882941 }, - { type: "node", id: 9199177059, lat: 51.1256647, lon: 3.1855696 }, - { type: "node", id: 7578987409, lat: 51.1232707, lon: 3.1920382 }, - { type: "node", id: 7578987413, lat: 51.1260416, lon: 3.1973652 }, - { type: "node", id: 7578987414, lat: 51.1263443, lon: 3.1973775 }, - { type: "node", id: 7578987416, lat: 51.1278708, lon: 3.1974134 }, - { type: "node", id: 7578987417, lat: 51.126749, lon: 3.197258 }, - { type: "node", id: 1675648152, lat: 51.1281877, lon: 3.1903323 }, - { type: "node", id: 2732486274, lat: 51.1302553, lon: 3.1886305 }, - { type: "node", id: 3921879018, lat: 51.1280809, lon: 3.188102 }, - { type: "node", id: 3922380061, lat: 51.1301929, lon: 3.1895402 }, - { type: "node", id: 3922380083, lat: 51.1305788, lon: 3.1884337 }, - { type: "node", id: 3922380095, lat: 51.130784, lon: 3.1852632 }, - { type: "node", id: 7578865281, lat: 51.1283938, lon: 3.1903716 }, - { type: "node", id: 7578865283, lat: 51.1288414, lon: 3.1904911 }, - { type: "node", id: 7578960079, lat: 51.1303025, lon: 3.1855669 }, - { type: "node", id: 7578975012, lat: 51.1294792, lon: 3.1906231 }, - { type: "node", id: 7578975015, lat: 51.1297374, lon: 3.1907018 }, - { type: "node", id: 7578975016, lat: 51.1300451, lon: 3.1907679 }, - { type: "node", id: 7578975018, lat: 51.1305785, lon: 3.186668 }, - { type: "node", id: 7578975019, lat: 51.130956, lon: 3.1881852 }, - { type: "node", id: 7578975021, lat: 51.1303082, lon: 3.1855908 }, - { type: "node", id: 7578975026, lat: 51.1310167, lon: 3.1861981 }, - { type: "node", id: 7578975027, lat: 51.1318642, lon: 3.1857905 }, - { type: "node", id: 7578975040, lat: 51.1280348, lon: 3.1889503 }, - { type: "node", id: 7578975044, lat: 51.1279308, lon: 3.1875031 }, - { type: "node", id: 7578975046, lat: 51.1279329, lon: 3.1881992 }, - { type: "node", id: 9167054157, lat: 51.1308023, lon: 3.1852517 }, - { type: "node", id: 9274761591, lat: 51.1310994, lon: 3.1862668 }, - { type: "node", id: 9274761592, lat: 51.1310643, lon: 3.1862655 }, - { type: "node", id: 9274761593, lat: 51.1310386, lon: 3.1862393 }, - { type: "node", id: 7578987418, lat: 51.128003, lon: 3.1959031 }, - { type: "node", id: 7578987419, lat: 51.1282167, lon: 3.193723 }, - { type: "node", id: 5745833239, lat: 51.1258572, lon: 3.1996713 }, - { type: "node", id: 5745833240, lat: 51.1257519, lon: 3.1995939 }, - { type: "node", id: 5745833241, lat: 51.1253365, lon: 3.1991234 }, - { type: "node", id: 7578987410, lat: 51.1243814, lon: 3.1988516 }, - { type: "node", id: 7578987411, lat: 51.1243992, lon: 3.1989686 }, - { type: "node", id: 7578987412, lat: 51.1259883, lon: 3.1997074 }, - { type: "node", id: 7578987415, lat: 51.1260745, lon: 3.1997142 }, - { type: "node", id: 1590642829, lat: 51.1333867, lon: 3.2308055 }, - { type: "node", id: 1590642832, lat: 51.1334371, lon: 3.2308262 }, - { type: "node", id: 1590642849, lat: 51.1336392, lon: 3.2305316 }, - { type: "node", id: 1590642858, lat: 51.133659, lon: 3.2303991 }, - { type: "node", id: 1590642859, lat: 51.1336899, lon: 3.2305508 }, - { type: "node", id: 1590642860, lat: 51.1337096, lon: 3.2304183 }, - { type: "node", id: 1590642828, lat: 51.1333653, lon: 3.2309374 }, - { type: "node", id: 1590642830, lat: 51.1334157, lon: 3.2309581 }, - { type: "node", id: 3311835478, lat: 51.133195, lon: 3.2334351 }, - { type: "node", id: 9316118530, lat: 51.1331607, lon: 3.2333604 }, - { type: "node", id: 9316118531, lat: 51.1330927, lon: 3.2334241 }, - { type: "node", id: 9316118532, lat: 51.1331767, lon: 3.233347 }, - { type: "node", id: 9316118533, lat: 51.133147, lon: 3.2333808 }, - { type: "node", id: 9316118534, lat: 51.1331302, lon: 3.2334006 }, - { type: "node", id: 9316118535, lat: 51.1331127, lon: 3.2334144 }, - { type: "node", id: 9316118536, lat: 51.1330784, lon: 3.2334281 }, - { type: "node", id: 9316118540, lat: 51.1330946, lon: 3.2335103 }, - { type: "node", id: 6579962064, lat: 51.1367061, lon: 3.1640546 }, - { type: "node", id: 6579962065, lat: 51.1361156, lon: 3.1646435 }, - { type: "node", id: 6579962066, lat: 51.1357413, lon: 3.1637334 }, - { type: "node", id: 6579962067, lat: 51.1359511, lon: 3.1637408 }, - { type: "node", id: 6579962068, lat: 51.1359389, lon: 3.1638093 }, - { type: "node", id: 7546193219, lat: 51.1456739, lon: 3.1637073 }, - { type: "node", id: 7546193220, lat: 51.1455706, lon: 3.1643444 }, - { type: "node", id: 7546193221, lat: 51.1456623, lon: 3.1643821 }, - { type: "node", id: 7546193222, lat: 51.1457656, lon: 3.1637451 }, - { type: "node", id: 3359977305, lat: 51.1464982, lon: 3.1689911 }, - { type: "node", id: 4042671969, lat: 51.1461398, lon: 3.169233 }, - { type: "node", id: 7545532512, lat: 51.1463103, lon: 3.169498 }, - { type: "node", id: 7545532513, lat: 51.1466008, lon: 3.1695349 }, - { type: "node", id: 7545532514, lat: 51.1464331, lon: 3.16962 }, - { type: "node", id: 8200275848, lat: 51.1409105, lon: 3.1800288 }, - { type: "node", id: 8200275844, lat: 51.1416967, lon: 3.1797728 }, - { type: "node", id: 8200275847, lat: 51.1411211, lon: 3.1805153 }, - { type: "node", id: 8200275849, lat: 51.1417397, lon: 3.1802115 }, - { type: "node", id: 8200275853, lat: 51.1417351, lon: 3.1801651 }, - { type: "node", id: 111759500, lat: 51.1483444, lon: 3.1886487 }, - { type: "node", id: 1169056712, lat: 51.1482974, lon: 3.1884805 }, - { type: "node", id: 3325315226, lat: 51.147926, lon: 3.1887015 }, - { type: "node", id: 3325315230, lat: 51.1479856, lon: 3.1889124 }, - { type: "node", id: 3325315232, lat: 51.1480454, lon: 3.1891027 }, - { type: "node", id: 3325315243, lat: 51.1483285, lon: 3.1885919 }, - { type: "node", id: 3325315247, lat: 51.1484007, lon: 3.1888131 }, - { type: "node", id: 5826810673, lat: 51.1512507, lon: 3.1914885 }, - { type: "node", id: 5826810674, lat: 51.15126, lon: 3.1914533 }, - { type: "node", id: 5826810675, lat: 51.1510883, lon: 3.1912776 }, - { type: "node", id: 5826810676, lat: 51.151057, lon: 3.1912906 }, - { type: "node", id: 5826810678, lat: 51.1509897, lon: 3.1911759 }, - { type: "node", id: 5826810679, lat: 51.1510025, lon: 3.1911334 }, - { type: "node", id: 5826810680, lat: 51.1509352, lon: 3.1908874 }, - { type: "node", id: 5826810681, lat: 51.1509074, lon: 3.1908689 }, - { type: "node", id: 5826811494, lat: 51.1512125, lon: 3.1911315 }, - { type: "node", id: 5826811495, lat: 51.1512055, lon: 3.191187 }, - { type: "node", id: 5826811496, lat: 51.151296, lon: 3.1914182 }, - { type: "node", id: 5826811497, lat: 51.1513261, lon: 3.1914182 }, - { type: "node", id: 5826811535, lat: 51.1517839, lon: 3.1959375 }, - { type: "node", id: 5826811536, lat: 51.1518581, lon: 3.1959708 }, - { type: "node", id: 5826811547, lat: 51.1515913, lon: 3.196115 }, - { type: "node", id: 5826811548, lat: 51.1516307, lon: 3.1961465 }, - { type: "node", id: 5826811549, lat: 51.1516435, lon: 3.1961076 }, - { type: "node", id: 5826811550, lat: 51.1516087, lon: 3.1959541 }, - { type: "node", id: 5826811551, lat: 51.1515437, lon: 3.1959024 }, - { type: "node", id: 5826811559, lat: 51.1517896, lon: 3.1957717 }, - { type: "node", id: 5826811560, lat: 51.1517417, lon: 3.1957528 }, - { type: "node", id: 5826811561, lat: 51.1517365, lon: 3.1957872 }, - { type: "node", id: 3325315395, lat: 51.1544187, lon: 3.1856685 }, - { type: "node", id: 3325315397, lat: 51.1545358, lon: 3.1860117 }, - { type: "node", id: 3930713426, lat: 51.1571474, lon: 3.1889324 }, - { type: "node", id: 3930713429, lat: 51.1573669, lon: 3.1883265 }, - { type: "node", id: 3930713430, lat: 51.1573202, lon: 3.1890776 }, - { type: "node", id: 3930713435, lat: 51.1574561, lon: 3.1883916 }, - { type: "node", id: 3930713437, lat: 51.1574971, lon: 3.1893349 }, - { type: "node", id: 3930713438, lat: 51.1574907, lon: 3.1884223 }, - { type: "node", id: 3930713440, lat: 51.1575693, lon: 3.1890951 }, - { type: "node", id: 3930713442, lat: 51.1575932, lon: 3.1879961 }, - { type: "node", id: 3930713446, lat: 51.1577118, lon: 3.1885611 }, - { type: "node", id: 3930713447, lat: 51.157698, lon: 3.1894886 }, - { type: "node", id: 3930713451, lat: 51.1577702, lon: 3.1892488 }, - { type: "node", id: 3930713454, lat: 51.1577969, lon: 3.1882567 }, - { type: "node", id: 4043782112, lat: 51.1549447, lon: 3.1864571 }, - { type: "node", id: 5552466013, lat: 51.1545783, lon: 3.1874672 }, - { type: "node", id: 5552466014, lat: 51.1543143, lon: 3.1873045 }, - { type: "node", id: 5552466018, lat: 51.1545999, lon: 3.1873846 }, - { type: "node", id: 5552466020, lat: 51.1548079, lon: 3.1868846 }, - { type: "node", id: 5825400688, lat: 51.1549303, lon: 3.1867969 }, - { type: "node", id: 5826811614, lat: 51.1572659, lon: 3.1885288 }, - { type: "node", id: 6949357906, lat: 51.1572911, lon: 3.1915323 }, - { type: "node", id: 6949357909, lat: 51.1573109, lon: 3.1914572 }, - { type: "node", id: 6949357910, lat: 51.1574373, lon: 3.1913744 }, - { type: "node", id: 6949357911, lat: 51.1574179, lon: 3.1914502 }, - { type: "node", id: 6949357912, lat: 51.1575109, lon: 3.1915112 }, - { type: "node", id: 6949357913, lat: 51.1575304, lon: 3.1914354 }, - { type: "node", id: 6949357914, lat: 51.1574638, lon: 3.1912712 }, - { type: "node", id: 6949357915, lat: 51.1574444, lon: 3.1913473 }, - { type: "node", id: 6949357916, lat: 51.1575599, lon: 3.191423 }, - { type: "node", id: 6949357917, lat: 51.1575803, lon: 3.1913473 }, - { type: "node", id: 6949357918, lat: 51.157501, lon: 3.1911303 }, - { type: "node", id: 6949357919, lat: 51.1574808, lon: 3.1912058 }, - { type: "node", id: 6949357920, lat: 51.1577374, lon: 3.1913724 }, - { type: "node", id: 6949357921, lat: 51.1577563, lon: 3.1912987 }, - { type: "node", id: 6982605752, lat: 51.1574664, lon: 3.1885975 }, - { type: "node", id: 6982605754, lat: 51.1574428, lon: 3.1885793 }, - { type: "node", id: 6982605762, lat: 51.1576066, lon: 3.1884793 }, - { type: "node", id: 6982605764, lat: 51.1576853, lon: 3.18854 }, - { type: "node", id: 6982605766, lat: 51.1575125, lon: 3.1884502 }, - { type: "node", id: 6982605767, lat: 51.1575959, lon: 3.1885145 }, - { type: "node", id: 6982605769, lat: 51.1575152, lon: 3.1884412 }, - { type: "node", id: 6982906542, lat: 51.1591649, lon: 3.1904238 }, - { type: "node", id: 6982906543, lat: 51.1592937, lon: 3.1905221 }, - { type: "node", id: 6982906544, lat: 51.1593438, lon: 3.1903659 }, - { type: "node", id: 6982906545, lat: 51.1593158, lon: 3.1903453 }, - { type: "node", id: 6982906546, lat: 51.1593351, lon: 3.1902852 }, - { type: "node", id: 6982906547, lat: 51.1592385, lon: 3.1902052 }, - { type: "node", id: 6997076562, lat: 51.1546633, lon: 3.1858333 }, - { type: "node", id: 6997076563, lat: 51.1546293, lon: 3.1858642 }, - { type: "node", id: 6997076564, lat: 51.154594, lon: 3.1856715 }, - { type: "node", id: 6997076565, lat: 51.1546727, lon: 3.1859889 }, - { type: "node", id: 6997076566, lat: 51.1545545, lon: 3.1860687 }, - { type: "node", id: 6997076567, lat: 51.1543063, lon: 3.1869337 }, - { type: "node", id: 6997096756, lat: 51.1547387, lon: 3.1868376 }, - { type: "node", id: 6997096758, lat: 51.1546899, lon: 3.1870707 }, - { type: "node", id: 6997096759, lat: 51.1546751, lon: 3.1870601 }, - { type: "node", id: 7137343680, lat: 51.1558646, lon: 3.1876808 }, - { type: "node", id: 7137343681, lat: 51.1558466, lon: 3.1877456 }, - { type: "node", id: 7137343682, lat: 51.1555824, lon: 3.187559 }, - { type: "node", id: 7137343683, lat: 51.1556004, lon: 3.1874941 }, - { type: "node", id: 7137343684, lat: 51.1555549, lon: 3.1874612 }, - { type: "node", id: 7137383185, lat: 51.1555369, lon: 3.1875268 }, - { type: "node", id: 7137383186, lat: 51.1553925, lon: 3.1874261 }, - { type: "node", id: 7137383187, lat: 51.1554105, lon: 3.1873604 }, - { type: "node", id: 7519058287, lat: 51.1555296, lon: 3.1858152 }, - { type: "node", id: 7519058288, lat: 51.1555027, lon: 3.1858752 }, - { type: "node", id: 7519058289, lat: 51.1556329, lon: 3.1860234 }, - { type: "node", id: 7519058290, lat: 51.1556597, lon: 3.1859634 }, - { type: "node", id: 7519058291, lat: 51.1557039, lon: 3.1860155 }, - { type: "node", id: 7519058292, lat: 51.1556789, lon: 3.1860736 }, - { type: "node", id: 7519058293, lat: 51.1558105, lon: 3.1862177 }, - { type: "node", id: 7519058294, lat: 51.1558355, lon: 3.1861597 }, - { type: "node", id: 7519058295, lat: 51.1557209, lon: 3.185828 }, - { type: "node", id: 7519058296, lat: 51.1556932, lon: 3.1858902 }, - { type: "node", id: 7519058297, lat: 51.1558686, lon: 3.1860888 }, - { type: "node", id: 7519058298, lat: 51.1558963, lon: 3.1860265 }, - { type: "node", id: 7519058299, lat: 51.1555421, lon: 3.1856365 }, - { type: "node", id: 7519058300, lat: 51.1555168, lon: 3.1856923 }, - { type: "node", id: 7519058301, lat: 51.1556479, lon: 3.1858437 }, - { type: "node", id: 7519058302, lat: 51.1556732, lon: 3.1857879 }, - { type: "node", id: 150996092, lat: 51.1554945, lon: 3.1954639 }, - { type: "node", id: 150996093, lat: 51.155531, lon: 3.1953168 }, - { type: "node", id: 150996094, lat: 51.1554912, lon: 3.1943936 }, - { type: "node", id: 150996095, lat: 51.155456, lon: 3.1942488 }, - { type: "node", id: 150996097, lat: 51.155432, lon: 3.1942095 }, - { type: "node", id: 150996098, lat: 51.155397, lon: 3.1941906 }, - { type: "node", id: 150996099, lat: 51.1552938, lon: 3.1945134 }, - { type: "node", id: 150996100, lat: 51.1552701, lon: 3.1949002 }, - { type: "node", id: 150996101, lat: 51.1553901, lon: 3.1953415 }, - { type: "node", id: 1015567837, lat: 51.1603457, lon: 3.1926746 }, - { type: "node", id: 1015583939, lat: 51.1598982, lon: 3.1934595 }, - { type: "node", id: 1659850846, lat: 51.1562462, lon: 3.1925019 }, - { type: "node", id: 1811699776, lat: 51.1604859, lon: 3.1920734 }, - { type: "node", id: 1811699777, lat: 51.1605587, lon: 3.1917984 }, - { type: "node", id: 1817319289, lat: 51.1599853, lon: 3.1935159 }, - { type: "node", id: 1817319290, lat: 51.1600476, lon: 3.1933385 }, - { type: "node", id: 1817319291, lat: 51.1600745, lon: 3.1934309 }, - { type: "node", id: 1817319292, lat: 51.1602073, lon: 3.1941751 }, - { type: "node", id: 1817319293, lat: 51.160208, lon: 3.1941098 }, - { type: "node", id: 1817319294, lat: 51.1602178, lon: 3.1935425 }, - { type: "node", id: 1817319295, lat: 51.1602385, lon: 3.19297 }, - { type: "node", id: 1817319296, lat: 51.1602972, lon: 3.1940248 }, - { type: "node", id: 1817319297, lat: 51.1603313, lon: 3.193809 }, - { type: "node", id: 1817319298, lat: 51.1603427, lon: 3.1930326 }, - { type: "node", id: 1817319299, lat: 51.1603716, lon: 3.1940192 }, - { type: "node", id: 1817319300, lat: 51.1603777, lon: 3.1946319 }, - { type: "node", id: 1817319301, lat: 51.1604665, lon: 3.193304 }, - { type: "node", id: 1817319302, lat: 51.1604758, lon: 3.1939077 }, - { type: "node", id: 1817319303, lat: 51.1605421, lon: 3.194476 }, - { type: "node", id: 1817319304, lat: 51.1606037, lon: 3.1933895 }, - { type: "node", id: 1817320493, lat: 51.1604248, lon: 3.1927398 }, - { type: "node", id: 1817320494, lat: 51.1604851, lon: 3.192495 }, - { type: "node", id: 2451569392, lat: 51.1598651, lon: 3.1919974 }, - { type: "node", id: 2451574741, lat: 51.1594995, lon: 3.1924908 }, - { type: "node", id: 2451574742, lat: 51.1596217, lon: 3.1923259 }, - { type: "node", id: 2451574743, lat: 51.1597161, lon: 3.1922023 }, - { type: "node", id: 2451574744, lat: 51.1600391, lon: 3.1932123 }, - { type: "node", id: 2451574745, lat: 51.1601904, lon: 3.192947 }, - { type: "node", id: 2451574746, lat: 51.1603862, lon: 3.1925461 }, - { type: "node", id: 2451574747, lat: 51.1604272, lon: 3.19257 }, - { type: "node", id: 2451574748, lat: 51.1604837, lon: 3.1921535 }, - { type: "node", id: 2451574749, lat: 51.1605266, lon: 3.1921769 }, - { type: "node", id: 2451578121, lat: 51.159758, lon: 3.1921448 }, - { type: "node", id: 5587844460, lat: 51.1562664, lon: 3.1919544 }, - { type: "node", id: 5587844461, lat: 51.15612, lon: 3.1920681 }, - { type: "node", id: 5587844462, lat: 51.1561808, lon: 3.1922614 }, - { type: "node", id: 5599314384, lat: 51.1563882, lon: 3.1923707 }, - { type: "node", id: 5599314385, lat: 51.1563587, lon: 3.1924465 }, - { type: "node", id: 6754312541, lat: 51.154716, lon: 3.1952084 }, - { type: "node", id: 6754312542, lat: 51.1547723, lon: 3.1953372 }, - { type: "node", id: 6754312543, lat: 51.1548078, lon: 3.1952983 }, - { type: "node", id: 6754312544, lat: 51.1547519, lon: 3.1951702 }, - { type: "node", id: 6754312550, lat: 51.1555879, lon: 3.1950341 }, - { type: "node", id: 6754312551, lat: 51.1556025, lon: 3.194956 }, - { type: "node", id: 6754312552, lat: 51.1555664, lon: 3.1952408 }, - { type: "node", id: 6754312553, lat: 51.1556188, lon: 3.1951751 }, - { type: "node", id: 6754312554, lat: 51.1554565, lon: 3.195427 }, - { type: "node", id: 6754312555, lat: 51.1552894, lon: 3.1950649 }, - { type: "node", id: 6754312556, lat: 51.1553277, lon: 3.1951991 }, - { type: "node", id: 6754312557, lat: 51.1552774, lon: 3.1947027 }, - { type: "node", id: 6754312558, lat: 51.1553131, lon: 3.1943816 }, - { type: "node", id: 6754312559, lat: 51.1553397, lon: 3.1942577 }, - { type: "node", id: 6754312560, lat: 51.1553697, lon: 3.1942084 }, - { type: "node", id: 6754312562, lat: 51.1548064, lon: 3.1954209 }, - { type: "node", id: 6754312563, lat: 51.1548266, lon: 3.1954893 }, - { type: "node", id: 6754312564, lat: 51.1550417, lon: 3.1953175 }, - { type: "node", id: 6754312565, lat: 51.1550193, lon: 3.1952538 }, - { type: "node", id: 6870850178, lat: 51.1561935, lon: 3.1923019 }, - { type: "node", id: 6870863414, lat: 51.1562489, lon: 3.1919675 }, - { type: "node", id: 6949357907, lat: 51.1575239, lon: 3.1916859 }, - { type: "node", id: 6949357908, lat: 51.1575439, lon: 3.1916101 }, - { type: "node", id: 1448421081, lat: 51.167491, lon: 3.1713256 }, - { type: "node", id: 1448421091, lat: 51.1677042, lon: 3.1714548 }, - { type: "node", id: 1448421093, lat: 51.1677455, lon: 3.1702529 }, - { type: "node", id: 1448421099, lat: 51.1679647, lon: 3.1703977 }, - { type: "node", id: 6536026850, lat: 51.1711159, lon: 3.1669401 }, - { type: "node", id: 6536026851, lat: 51.1712692, lon: 3.167296 }, - { type: "node", id: 6536026852, lat: 51.1711296, lon: 3.1674712 }, - { type: "node", id: 6536026853, lat: 51.1709602, lon: 3.1671189 }, - { type: "node", id: 6536038234, lat: 51.1711913, lon: 3.165543 }, - { type: "node", id: 6536038235, lat: 51.1711301, lon: 3.1656263 }, - { type: "node", id: 6536038236, lat: 51.1712318, lon: 3.1658161 }, - { type: "node", id: 6536038237, lat: 51.171293, lon: 3.1657327 }, - { type: "node", id: 1038583451, lat: 51.1625071, lon: 3.191602 }, - { type: "node", id: 4044171936, lat: 51.1609177, lon: 3.1911926 }, - { type: "node", id: 4044171941, lat: 51.1609801, lon: 3.191229 }, - { type: "node", id: 4044171963, lat: 51.1611962, lon: 3.1913534 }, - { type: "node", id: 903903386, lat: 51.1618084, lon: 3.1932127 }, - { type: "node", id: 903903387, lat: 51.1623536, lon: 3.1936011 }, - { type: "node", id: 903903388, lat: 51.1624429, lon: 3.1931156 }, - { type: "node", id: 903903390, lat: 51.1618641, lon: 3.1930197 }, - { type: "node", id: 903904576, lat: 51.1618211, lon: 3.1931711 }, - { type: "node", id: 1038557012, lat: 51.16155, lon: 3.1931121 }, - { type: "node", id: 1038557014, lat: 51.1613774, lon: 3.1930711 }, - { type: "node", id: 1038557035, lat: 51.1615349, lon: 3.1931712 }, - { type: "node", id: 1038557072, lat: 51.1616138, lon: 3.1927483 }, - { type: "node", id: 1038557075, lat: 51.162286, lon: 3.1935989 }, - { type: "node", id: 1038557078, lat: 51.1616517, lon: 3.1928439 }, - { type: "node", id: 1038557083, lat: 51.1616055, lon: 3.1930249 }, - { type: "node", id: 1038557094, lat: 51.1618993, lon: 3.193299 }, - { type: "node", id: 1038557102, lat: 51.1613235, lon: 3.1927138 }, - { type: "node", id: 1038557104, lat: 51.1615987, lon: 3.1928077 }, - { type: "node", id: 1038557108, lat: 51.1615113, lon: 3.1926816 }, - { type: "node", id: 1038557137, lat: 51.1608873, lon: 3.1924416 }, - { type: "node", id: 1038557143, lat: 51.1622248, lon: 3.193662 }, - { type: "node", id: 1038557191, lat: 51.1608171, lon: 3.1926951 }, - { type: "node", id: 1038557195, lat: 51.162409, lon: 3.1933428 }, - { type: "node", id: 1038557227, lat: 51.1613767, lon: 3.1925113 }, - { type: "node", id: 1038557230, lat: 51.1615281, lon: 3.1926132 }, - { type: "node", id: 1038557233, lat: 51.1619765, lon: 3.1933723 }, - { type: "node", id: 1038575040, lat: 51.1608523, lon: 3.1925679 }, - { type: "node", id: 1038583375, lat: 51.1625113, lon: 3.1926776 }, - { type: "node", id: 1038583398, lat: 51.1624305, lon: 3.1925743 }, - { type: "node", id: 1038583404, lat: 51.1627055, lon: 3.191826 }, - { type: "node", id: 1038583425, lat: 51.1624272, lon: 3.1916637 }, - { type: "node", id: 1038583441, lat: 51.1621479, lon: 3.1921546 }, - { type: "node", id: 1038583446, lat: 51.1622018, lon: 3.1921814 }, - { type: "node", id: 1038583456, lat: 51.162174, lon: 3.1922806 }, - { type: "node", id: 1038583459, lat: 51.162259, lon: 3.1924885 }, - { type: "node", id: 1038583463, lat: 51.162661, lon: 3.1917442 }, - { type: "node", id: 1038583476, lat: 51.1626425, lon: 3.1918448 }, - { type: "node", id: 1038583479, lat: 51.1624196, lon: 3.1926561 }, - { type: "node", id: 1038583483, lat: 51.1625037, lon: 3.1927232 }, - { type: "node", id: 1038583491, lat: 51.1625701, lon: 3.1926387 }, - { type: "node", id: 1038583501, lat: 51.1624928, lon: 3.1917013 }, - { type: "node", id: 1811673418, lat: 51.1618484, lon: 3.1950253 }, - { type: "node", id: 1811673421, lat: 51.1619875, lon: 3.1951427 }, - { type: "node", id: 1811673423, lat: 51.162144, lon: 3.193835 }, - { type: "node", id: 1811673425, lat: 51.1622452, lon: 3.1939149 }, - { type: "node", id: 1811699778, lat: 51.1608187, lon: 3.1922973 }, - { type: "node", id: 1811699779, lat: 51.1608915, lon: 3.1920223 }, - { type: "node", id: 1817320495, lat: 51.1607269, lon: 3.192929 }, - { type: "node", id: 1817320496, lat: 51.1607872, lon: 3.1926841 }, - { type: "node", id: 1817324488, lat: 51.1615575, lon: 3.1921423 }, - { type: "node", id: 1817324489, lat: 51.1612682, lon: 3.1920196 }, - { type: "node", id: 1817324491, lat: 51.1617501, lon: 3.1918468 }, - { type: "node", id: 1817324505, lat: 51.1618272, lon: 3.1921231 }, - { type: "node", id: 1817324509, lat: 51.1618658, lon: 3.191793 }, - { type: "node", id: 1817324513, lat: 51.1619814, lon: 3.1920002 }, - { type: "node", id: 1817324515, lat: 51.161985, lon: 3.191793 }, - { type: "node", id: 3550860268, lat: 51.1617349, lon: 3.1921922 }, - { type: "node", id: 3550860269, lat: 51.1619403, lon: 3.1920686 }, - { type: "node", id: 4044171924, lat: 51.1608199, lon: 3.1916126 }, - { type: "node", id: 4044171954, lat: 51.1610853, lon: 3.191781 }, - { type: "node", id: 4044171957, lat: 51.1611433, lon: 3.1918701 }, - { type: "node", id: 4044171962, lat: 51.1611926, lon: 3.1916807 }, - { type: "node", id: 4044171976, lat: 51.1612826, lon: 3.1919582 }, - { type: "node", id: 4044171997, lat: 51.1613568, lon: 3.1917787 }, - { type: "node", id: 4044172003, lat: 51.1613248, lon: 3.1919027 }, - { type: "node", id: 4044172008, lat: 51.1614345, lon: 3.1919767 }, - { type: "node", id: 6397031888, lat: 51.1615029, lon: 3.1919851 }, - { type: "node", id: 6960473080, lat: 51.1614227, lon: 3.1931004 }, - { type: "node", id: 3335137692, lat: 51.1698302, lon: 3.1965654 }, - { type: "node", id: 3335137795, lat: 51.1698788, lon: 3.1970728 }, - { type: "node", id: 3335137802, lat: 51.1700082, lon: 3.1971734 }, - { type: "node", id: 3335137807, lat: 51.1701139, lon: 3.1965001 }, - { type: "node", id: 3335137812, lat: 51.1703247, lon: 3.197352 }, - { type: "node", id: 3335137823, lat: 51.1703133, lon: 3.1966232 }, - { type: "node", id: 6255587427, lat: 51.1694775, lon: 3.1980047 }, - { type: "node", id: 9163486855, lat: 51.1703854, lon: 3.1970901 }, - { type: "node", id: 9163486856, lat: 51.1702114, lon: 3.1969688 }, - { type: "node", id: 9163493632, lat: 51.1699473, lon: 3.197063 }, - { type: "node", id: 414025563, lat: 51.1768198, lon: 3.1839265 }, - { type: "node", id: 2325437840, lat: 51.1765747, lon: 3.1839745 }, - { type: "node", id: 2325437844, lat: 51.1768286, lon: 3.1825946 }, - { type: "node", id: 8191691970, lat: 51.1767611, lon: 3.1839766 }, - { type: "node", id: 8191691971, lat: 51.1767812, lon: 3.1826167 }, - { type: "node", id: 8191691972, lat: 51.1765804, lon: 3.1826583 }, - { type: "node", id: 8191691973, lat: 51.1766324, lon: 3.182616 }, - { type: "node", id: 5716136617, lat: 51.1773127, lon: 3.1969033 }, - { type: "node", id: 5716136618, lat: 51.1771859, lon: 3.1969078 }, - { type: "node", id: 5716136619, lat: 51.177203, lon: 3.1981369 }, - { type: "node", id: 5716136620, lat: 51.1773298, lon: 3.1981324 }, - { type: "node", id: 6004375826, lat: 51.1786839, lon: 3.1952389 }, - { type: "node", id: 6625037999, lat: 51.1788016, lon: 3.1950645 }, - { type: "node", id: 6625038000, lat: 51.1789916, lon: 3.1966631 }, - { type: "node", id: 6625038001, lat: 51.1789003, lon: 3.1966838 }, - { type: "node", id: 6625038002, lat: 51.1788554, lon: 3.1963547 }, - { type: "node", id: 6625038003, lat: 51.1788165, lon: 3.1963622 }, - { type: "node", id: 6625038005, lat: 51.1785124, lon: 3.1952362 }, - { type: "node", id: 6625038006, lat: 51.1784779, lon: 3.1950618 }, - { type: "node", id: 6925536539, lat: 51.1522475, lon: 3.1985416 }, - { type: "node", id: 6925536540, lat: 51.1522635, lon: 3.1986187 }, - { type: "node", id: 6925536541, lat: 51.1523922, lon: 3.1985517 }, - { type: "node", id: 6925536542, lat: 51.1523766, lon: 3.1984746 }, - { type: "node", id: 1637742821, lat: 51.158524, lon: 3.199021 }, - { type: "node", id: 5586765933, lat: 51.1566667, lon: 3.2008881 }, - { type: "node", id: 5586765934, lat: 51.1564286, lon: 3.2012575 }, - { type: "node", id: 5586765935, lat: 51.1562496, lon: 3.2008579 }, - { type: "node", id: 5586765936, lat: 51.1564982, lon: 3.200522 }, - { type: "node", id: 6943148197, lat: 51.1581916, lon: 3.1989071 }, - { type: "node", id: 6943148198, lat: 51.1582377, lon: 3.1989899 }, - { type: "node", id: 6943148199, lat: 51.1581859, lon: 3.1990728 }, - { type: "node", id: 6943148200, lat: 51.1583257, lon: 3.199295 }, - { type: "node", id: 6943148201, lat: 51.1583563, lon: 3.199246 }, - { type: "node", id: 6943148202, lat: 51.1583988, lon: 3.1993135 }, - { type: "node", id: 6943148203, lat: 51.1585529, lon: 3.1990669 }, - { type: "node", id: 6943148204, lat: 51.1586554, lon: 3.1988109 }, - { type: "node", id: 6943148205, lat: 51.1585617, lon: 3.1986619 }, - { type: "node", id: 6943148206, lat: 51.1586782, lon: 3.1984755 }, - { type: "node", id: 6943148207, lat: 51.1585692, lon: 3.1982996 }, - { type: "node", id: 8065883225, lat: 51.1366029, lon: 3.233506 }, - { type: "node", id: 8065883226, lat: 51.1364627, lon: 3.2335338 }, - { type: "node", id: 8065883227, lat: 51.1363922, lon: 3.2326314 }, - { type: "node", id: 8065883228, lat: 51.1365324, lon: 3.2326036 }, - { type: "node", id: 8065883229, lat: 51.1374344, lon: 3.2333338 }, - { type: "node", id: 8065883230, lat: 51.1373632, lon: 3.2324534 }, - { type: "node", id: 8065883232, lat: 51.1375819, lon: 3.2333035 }, - { type: "node", id: 8065883233, lat: 51.1375107, lon: 3.232423 }, - { type: "node", id: 1795793393, lat: 51.1475704, lon: 3.233832 }, - { type: "node", id: 3346575840, lat: 51.146485, lon: 3.2349284 }, - { type: "node", id: 3346575846, lat: 51.1465127, lon: 3.2355341 }, - { type: "node", id: 3346575853, lat: 51.1466617, lon: 3.2349708 }, - { type: "node", id: 3346575855, lat: 51.1466787, lon: 3.2355171 }, - { type: "node", id: 3346575858, lat: 51.1466968, lon: 3.2342532 }, - { type: "node", id: 3346575865, lat: 51.1468756, lon: 3.2344143 }, - { type: "node", id: 3346575873, lat: 51.1469706, lon: 3.2366779 }, - { type: "node", id: 3346575929, lat: 51.1474307, lon: 3.2366434 }, - { type: "node", id: 1523513488, lat: 51.1398248, lon: 3.2392608 }, - { type: "node", id: 1744641292, lat: 51.1395814, lon: 3.2390365 }, - { type: "node", id: 1744641293, lat: 51.1397822, lon: 3.2393867 }, - { type: "node", id: 1920143232, lat: 51.1394724, lon: 3.2390121 }, - { type: "node", id: 7393009684, lat: 51.1394307, lon: 3.2390001 }, - { type: "node", id: 7393048385, lat: 51.1394135, lon: 3.2390524 }, - { type: "node", id: 3346575843, lat: 51.1465041, lon: 3.2376453 }, - { type: "node", id: 3346575845, lat: 51.146508, lon: 3.2378517 }, - { type: "node", id: 3346575876, lat: 51.1470052, lon: 3.237604 }, - { type: "node", id: 3346575886, lat: 51.147098, lon: 3.2412975 }, - { type: "node", id: 3346575889, lat: 51.1471585, lon: 3.2428761 }, - { type: "node", id: 3346575891, lat: 51.1471715, lon: 3.2377865 }, - { type: "node", id: 3346575901, lat: 51.1472492, lon: 3.2398591 }, - { type: "node", id: 3346575903, lat: 51.1472654, lon: 3.242623 }, - { type: "node", id: 3346575908, lat: 51.1472751, lon: 3.2428571 }, - { type: "node", id: 3346575917, lat: 51.1473518, lon: 3.2426093 }, - { type: "node", id: 3346575923, lat: 51.1473906, lon: 3.2398205 }, - { type: "node", id: 3346575925, lat: 51.147408, lon: 3.2424474 }, - { type: "node", id: 3346575928, lat: 51.1474263, lon: 3.24062 }, - { type: "node", id: 3346575933, lat: 51.1474728, lon: 3.2421565 }, - { type: "node", id: 3346575943, lat: 51.1475354, lon: 3.2418174 }, - { type: "node", id: 3346575945, lat: 51.1475494, lon: 3.2412786 }, - { type: "node", id: 3346575946, lat: 51.1475667, lon: 3.2415454 }, - { type: "node", id: 6291339815, lat: 51.1434669, lon: 3.2496811 }, - { type: "node", id: 6291339816, lat: 51.1434103, lon: 3.2497287 }, - { type: "node", id: 6291339821, lat: 51.1435685, lon: 3.2496576 }, - { type: "node", id: 6291339822, lat: 51.1434825, lon: 3.2497283 }, - { type: "node", id: 6291339827, lat: 51.1437401, lon: 3.2490327 }, - { type: "node", id: 6291339828, lat: 51.1433037, lon: 3.2494123 }, - { type: "node", id: 6291339829, lat: 51.1436099, lon: 3.2497855 }, - { type: "node", id: 6291339830, lat: 51.1439041, lon: 3.249535 }, - { type: "node", id: 170464837, lat: 51.1533286, lon: 3.236239 }, - { type: "node", id: 170464839, lat: 51.1532379, lon: 3.2364578 }, - { type: "node", id: 1710262731, lat: 51.1513274, lon: 3.2373199 }, - { type: "node", id: 1710262732, lat: 51.1508759, lon: 3.2370371 }, - { type: "node", id: 1710262733, lat: 51.1509316, lon: 3.2370599 }, - { type: "node", id: 1710262735, lat: 51.1510166, lon: 3.2370123 }, - { type: "node", id: 1710262738, lat: 51.1512751, lon: 3.2372984 }, - { type: "node", id: 1710262742, lat: 51.1513127, lon: 3.237065 }, - { type: "node", id: 1710262743, lat: 51.1508201, lon: 3.2373832 }, - { type: "node", id: 1710262745, lat: 51.151027, lon: 3.2369479 }, - { type: "node", id: 1795793395, lat: 51.1476158, lon: 3.2338163 }, - { type: "node", id: 1795793397, lat: 51.1475842, lon: 3.2344427 }, - { type: "node", id: 1795793399, lat: 51.1477641, lon: 3.233033 }, - { type: "node", id: 1795793405, lat: 51.1477717, lon: 3.2338665 }, - { type: "node", id: 1795793406, lat: 51.1480775, lon: 3.2344787 }, - { type: "node", id: 1795793407, lat: 51.1478893, lon: 3.2344203 }, - { type: "node", id: 1795793408, lat: 51.1481719, lon: 3.2342485 }, - { type: "node", id: 1795793409, lat: 51.147607, lon: 3.2330256 }, - { type: "node", id: 4979389763, lat: 51.1476223, lon: 3.2330288 }, - { type: "node", id: 8179735224, lat: 51.156047, lon: 3.2252534 }, - { type: "node", id: 8179735264, lat: 51.1560464, lon: 3.2252785 }, - { type: "node", id: 8179735265, lat: 51.1558544, lon: 3.2252597 }, - { type: "node", id: 8179735266, lat: 51.1556789, lon: 3.2252522 }, - { type: "node", id: 8179735267, lat: 51.1555253, lon: 3.2252547 }, - { type: "node", id: 8179735268, lat: 51.1555747, lon: 3.2250024 }, - { type: "node", id: 8179735269, lat: 51.1560527, lon: 3.2250198 }, - { type: "node", id: 1525460846, lat: 51.1550489, lon: 3.2347709 }, - { type: "node", id: 1810326044, lat: 51.1547625, lon: 3.2355098 }, - { type: "node", id: 1810326087, lat: 51.1547809, lon: 3.2353708 }, - { type: "node", id: 4912203160, lat: 51.1554941, lon: 3.2364781 }, - { type: "node", id: 4912203161, lat: 51.1554192, lon: 3.2369649 }, - { type: "node", id: 4912203162, lat: 51.1554175, lon: 3.2371071 }, - { type: "node", id: 4912203163, lat: 51.1554301, lon: 3.2372063 }, - { type: "node", id: 4912203164, lat: 51.1553847, lon: 3.2372197 }, - { type: "node", id: 4912203165, lat: 51.1553763, lon: 3.2369502 }, - { type: "node", id: 4912203166, lat: 51.1554512, lon: 3.236462 }, - { type: "node", id: 4912203167, lat: 51.1555218, lon: 3.2366455 }, - { type: "node", id: 4912203168, lat: 51.1554742, lon: 3.2369592 }, - { type: "node", id: 4912203169, lat: 51.155516, lon: 3.2369752 }, - { type: "node", id: 4912203170, lat: 51.1555635, lon: 3.2366616 }, - { type: "node", id: 4912203171, lat: 51.1556125, lon: 3.2360775 }, - { type: "node", id: 4912203172, lat: 51.155547, lon: 3.2364923 }, - { type: "node", id: 4912203173, lat: 51.1555893, lon: 3.2365092 }, - { type: "node", id: 4912203174, lat: 51.1556547, lon: 3.2360945 }, - { type: "node", id: 4912203176, lat: 51.1554841, lon: 3.2362949 }, - { type: "node", id: 4912203177, lat: 51.1553752, lon: 3.2362591 }, - { type: "node", id: 4912203178, lat: 51.1553968, lon: 3.2360924 }, - { type: "node", id: 4912203179, lat: 51.1555056, lon: 3.2361281 }, - { type: "node", id: 4912214680, lat: 51.1553544, lon: 3.2348128 }, - { type: "node", id: 4912214681, lat: 51.1550186, lon: 3.2346814 }, - { type: "node", id: 4912214685, lat: 51.1554486, lon: 3.2338965 }, - { type: "node", id: 4912214686, lat: 51.1550098, lon: 3.2346405 }, - { type: "node", id: 4912214692, lat: 51.155386, lon: 3.2347722 }, - { type: "node", id: 4912214693, lat: 51.1555155, lon: 3.2338415 }, - { type: "node", id: 4912214694, lat: 51.1554587, lon: 3.2338214 }, - { type: "node", id: 4912214695, lat: 51.1553292, lon: 3.2347521 }, - { type: "node", id: 4912225050, lat: 51.1553136, lon: 3.234866 }, - { type: "node", id: 4912225051, lat: 51.1551036, lon: 3.2337751 }, - { type: "node", id: 4912225052, lat: 51.1549825, lon: 3.2346307 }, - { type: "node", id: 4912225053, lat: 51.1551894, lon: 3.2336789 }, - { type: "node", id: 4912225062, lat: 51.1551528, lon: 3.233747 }, - { type: "node", id: 4912225063, lat: 51.1551831, lon: 3.2337249 }, - { type: "node", id: 4912225064, lat: 51.1554653, lon: 3.2337755 }, - { type: "node", id: 4912225067, lat: 51.1551309, lon: 3.2337849 }, - { type: "node", id: 4912225068, lat: 51.1551727, lon: 3.2337999 }, - { type: "node", id: 4912225069, lat: 51.1553182, lon: 3.2348322 }, - { type: "node", id: 4912225070, lat: 51.1550516, lon: 3.2346556 }, - { type: "node", id: 5972179331, lat: 51.154488, lon: 3.2350802 }, - { type: "node", id: 5972179343, lat: 51.1545781, lon: 3.2351138 }, - { type: "node", id: 5972179344, lat: 51.1546444, lon: 3.2351409 }, - { type: "node", id: 5972179345, lat: 51.1546431, lon: 3.2351485 }, - { type: "node", id: 5972179346, lat: 51.1547126, lon: 3.2351739 }, - { type: "node", id: 5972179347, lat: 51.154714, lon: 3.2351659 }, - { type: "node", id: 5972179348, lat: 51.1547795, lon: 3.235188 }, - { type: "node", id: 5974489614, lat: 51.1546386, lon: 3.2355125 }, - { type: "node", id: 5974489615, lat: 51.1544914, lon: 3.2353516 }, - { type: "node", id: 5974489616, lat: 51.1544813, lon: 3.2351384 }, - { type: "node", id: 5974489617, lat: 51.1548024, lon: 3.2352003 }, - { type: "node", id: 5974489618, lat: 51.154732, lon: 3.2356091 }, - { type: "node", id: 7529417225, lat: 51.159095, lon: 3.2366844 }, - { type: "node", id: 7529417226, lat: 51.1589926, lon: 3.2372825 }, - { type: "node", id: 7529417227, lat: 51.1588687, lon: 3.2372286 }, - { type: "node", id: 7529417228, lat: 51.1589264, lon: 3.2368918 }, - { type: "node", id: 7529417229, lat: 51.1588879, lon: 3.236875 }, - { type: "node", id: 7529417230, lat: 51.1589326, lon: 3.2366138 }, - { type: "node", id: 7529417232, lat: 51.159019, lon: 3.2366513 }, - { type: "node", id: 170464840, lat: 51.1531619, lon: 3.2376814 }, - { type: "node", id: 170464841, lat: 51.1532306, lon: 3.2376888 }, - { type: "node", id: 1710245701, lat: 51.1528676, lon: 3.2390068 }, - { type: "node", id: 1710245703, lat: 51.1527703, lon: 3.239403 }, - { type: "node", id: 1710245705, lat: 51.1527888, lon: 3.2390966 }, - { type: "node", id: 1710245707, lat: 51.1526357, lon: 3.2390543 }, - { type: "node", id: 1710245709, lat: 51.1528763, lon: 3.2390382 }, - { type: "node", id: 1710245711, lat: 51.1528928, lon: 3.2390631 }, - { type: "node", id: 1710245713, lat: 51.1528729, lon: 3.2389738 }, - { type: "node", id: 1710245715, lat: 51.1528645, lon: 3.2394083 }, - { type: "node", id: 1710245718, lat: 51.1526407, lon: 3.2389524 }, - { type: "node", id: 1710262736, lat: 51.1512857, lon: 3.2375783 }, - { type: "node", id: 1710262737, lat: 51.151234, lon: 3.2375571 }, - { type: "node", id: 1710262739, lat: 51.151183, lon: 3.2375939 }, - { type: "node", id: 1710262741, lat: 51.1511924, lon: 3.2375358 }, - { type: "node", id: 1710262744, lat: 51.1512252, lon: 3.2376112 }, - { type: "node", id: 3346575950, lat: 51.1475926, lon: 3.2406028 }, - { type: "node", id: 1728421374, lat: 51.1554436, lon: 3.2438233 }, - { type: "node", id: 1728421375, lat: 51.15594, lon: 3.2438649 }, - { type: "node", id: 1728421377, lat: 51.15559, lon: 3.2439695 }, - { type: "node", id: 1728421379, lat: 51.1554335, lon: 3.243944 }, - { type: "node", id: 1770289505, lat: 51.1565437, lon: 3.2437924 }, - { type: "node", id: 4912197362, lat: 51.1565535, lon: 3.2438327 }, - { type: "node", id: 4912197363, lat: 51.1562818, lon: 3.2438374 }, - { type: "node", id: 4912197364, lat: 51.1565321, lon: 3.2435757 }, - { type: "node", id: 4912197365, lat: 51.1565279, lon: 3.2433181 }, - { type: "node", id: 4912197366, lat: 51.1562804, lon: 3.2435833 }, - { type: "node", id: 4912197367, lat: 51.1562798, lon: 3.2431702 }, - { type: "node", id: 4912197368, lat: 51.1562813, lon: 3.2437497 }, - { type: "node", id: 4912197369, lat: 51.1562802, lon: 3.243488 }, - { type: "node", id: 4912197370, lat: 51.1565257, lon: 3.2431702 }, - { type: "node", id: 4912197371, lat: 51.1565542, lon: 3.2439044 }, - { type: "node", id: 4912197372, lat: 51.1565308, lon: 3.2437482 }, - { type: "node", id: 4912197373, lat: 51.1565294, lon: 3.2434893 }, - { type: "node", id: 4912197374, lat: 51.1562835, lon: 3.2439005 }, - { type: "node", id: 1710276232, lat: 51.1572435, lon: 3.2451269 }, - { type: "node", id: 1710276240, lat: 51.156984, lon: 3.2453481 }, - { type: "node", id: 1710276242, lat: 51.1567167, lon: 3.2452913 }, - { type: "node", id: 1710276243, lat: 51.1570484, lon: 3.2451 }, - { type: "node", id: 1710276251, lat: 51.1562241, lon: 3.2457572 }, - { type: "node", id: 1710276253, lat: 51.156868, lon: 3.2451689 }, - { type: "node", id: 1710276255, lat: 51.1563873, lon: 3.2456553 }, - { type: "node", id: 1710276257, lat: 51.1572402, lon: 3.2450303 }, - { type: "node", id: 1710276259, lat: 51.1561703, lon: 3.2454299 }, - { type: "node", id: 1710276261, lat: 51.1564411, lon: 3.2457304 }, - { type: "node", id: 1728421376, lat: 51.1555858, lon: 3.2441572 }, - { type: "node", id: 1728421378, lat: 51.1559348, lon: 3.2441264 }, - { type: "node", id: 1810330766, lat: 51.1562599, lon: 3.2457349 }, - { type: "node", id: 1810345944, lat: 51.1572859, lon: 3.2458614 }, - { type: "node", id: 1810345947, lat: 51.1568366, lon: 3.2461909 }, - { type: "node", id: 1810345951, lat: 51.1572074, lon: 3.2455895 }, - { type: "node", id: 1810345955, lat: 51.1567582, lon: 3.245919 }, - { type: "node", id: 1810347217, lat: 51.1568783, lon: 3.2452387 }, - { type: "node", id: 6255587424, lat: 51.1699788, lon: 3.1988617 }, - { type: "node", id: 6255587425, lat: 51.1695322, lon: 3.1995447 }, - { type: "node", id: 6255587426, lat: 51.1690026, lon: 3.1986489 }, - { type: "node", id: 5173881316, lat: 51.1728811, lon: 3.210692 }, - { type: "node", id: 5173881317, lat: 51.1728829, lon: 3.21077 }, - { type: "node", id: 5173881318, lat: 51.1726197, lon: 3.2107914 }, - { type: "node", id: 5173881320, lat: 51.1728794, lon: 3.2108575 }, - { type: "node", id: 5173881621, lat: 51.1728791, lon: 3.2109364 }, - { type: "node", id: 5173881622, lat: 51.1727133, lon: 3.2109488 }, - { type: "node", id: 5173881623, lat: 51.1727117, lon: 3.2108703 }, - { type: "node", id: 5173881624, lat: 51.1728613, lon: 3.211069 }, - { type: "node", id: 5173881625, lat: 51.1728601, lon: 3.2111406 }, - { type: "node", id: 5173881626, lat: 51.1727019, lon: 3.2111316 }, - { type: "node", id: 5173881627, lat: 51.1727041, lon: 3.2110597 }, - { type: "node", id: 6275462772, lat: 51.1733576, lon: 3.2110928 }, - { type: "node", id: 6275462773, lat: 51.1733528, lon: 3.2112295 }, - { type: "node", id: 6275462774, lat: 51.1735278, lon: 3.2112449 }, - { type: "node", id: 6275462775, lat: 51.1735325, lon: 3.2111082 }, - { type: "node", id: 6275462776, lat: 51.1736659, lon: 3.2112568 }, - { type: "node", id: 6275462777, lat: 51.1736113, lon: 3.2112529 }, - { type: "node", id: 6275462784, lat: 51.1735598, lon: 3.2109277 }, - { type: "node", id: 6275462985, lat: 51.1734626, lon: 3.2109229 }, - { type: "node", id: 6275462986, lat: 51.1734599, lon: 3.2110592 }, - { type: "node", id: 6275462987, lat: 51.1735572, lon: 3.211064 }, - { type: "node", id: 6275462988, lat: 51.1734613, lon: 3.2109904 }, - { type: "node", id: 6275462989, lat: 51.173357, lon: 3.2111476 }, - { type: "node", id: 7054196467, lat: 51.1726209, lon: 3.2107131 }, - { type: "node", id: 8042845810, lat: 51.1737696, lon: 3.206708 }, - { type: "node", id: 8042845811, lat: 51.1734466, lon: 3.2056148 }, - { type: "node", id: 5952389321, lat: 51.1668901, lon: 3.2166736 }, - { type: "node", id: 5952389322, lat: 51.1664592, lon: 3.2166337 }, - { type: "node", id: 5952389323, lat: 51.1659941, lon: 3.2166018 }, - { type: "node", id: 5172938444, lat: 51.1667283, lon: 3.2192609 }, - { type: "node", id: 5536609426, lat: 51.1664884, lon: 3.2184803 }, - { type: "node", id: 5536620510, lat: 51.1668363, lon: 3.2197448 }, - { type: "node", id: 5536620511, lat: 51.1671911, lon: 3.2210959 }, - { type: "node", id: 5952389320, lat: 51.1665059, lon: 3.2183884 }, - { type: "node", id: 5536620506, lat: 51.1675299, lon: 3.2170283 }, - { type: "node", id: 5536620507, lat: 51.1672585, lon: 3.2168071 }, - { type: "node", id: 6275462778, lat: 51.1736042, lon: 3.2115044 }, - { type: "node", id: 6275462779, lat: 51.1736588, lon: 3.2115083 }, - { type: "node", id: 6275462780, lat: 51.1735687, lon: 3.2112964 }, - { type: "node", id: 6275462781, lat: 51.1735211, lon: 3.2112937 }, - { type: "node", id: 6275462782, lat: 51.1735156, lon: 3.2115423 }, - { type: "node", id: 6275462783, lat: 51.1735632, lon: 3.211545 }, - { type: "node", id: 5536620505, lat: 51.1683944, lon: 3.2180288 }, - { type: "node", id: 5536620512, lat: 51.1673641, lon: 3.2217199 }, - { type: "node", id: 5536620513, lat: 51.1675007, lon: 3.2221772 }, - { type: "node", id: 5536620514, lat: 51.1679104, lon: 3.2236675 }, - { type: "node", id: 5536620516, lat: 51.1679955, lon: 3.224005 }, - { type: "node", id: 5536620824, lat: 51.1700823, lon: 3.2236258 }, - { type: "node", id: 5536620826, lat: 51.171324, lon: 3.2230929 }, - { type: "node", id: 5536620827, lat: 51.1717731, lon: 3.2213846 }, - { type: "node", id: 5536620828, lat: 51.170726, lon: 3.2202369 }, - { type: "node", id: 5536620829, lat: 51.1706206, lon: 3.2201178 }, - { type: "node", id: 5536620830, lat: 51.170049, lon: 3.2215441 }, - { type: "node", id: 5536620831, lat: 51.1699442, lon: 3.2215294 }, - { type: "node", id: 5536620832, lat: 51.1683757, lon: 3.2194636 }, - { type: "node", id: 6067483781, lat: 51.1675243, lon: 3.222207 }, - { type: "node", id: 6067483782, lat: 51.1713888, lon: 3.2231705 }, - { type: "node", id: 7794736251, lat: 51.1688401, lon: 3.2184325 }, - { type: "node", id: 1069177852, lat: 51.1789546, lon: 3.2021791 }, - { type: "node", id: 1069177853, lat: 51.1801636, lon: 3.2031369 }, - { type: "node", id: 1069177873, lat: 51.1791663, lon: 3.2034541 }, - { type: "node", id: 1069177915, lat: 51.1799187, lon: 3.2029579 }, - { type: "node", id: 1069177919, lat: 51.1786053, lon: 3.2030903 }, - { type: "node", id: 1069177920, lat: 51.1790417, lon: 3.2033998 }, - { type: "node", id: 1069177925, lat: 51.1788415, lon: 3.2032442 }, - { type: "node", id: 1069177933, lat: 51.1798479, lon: 3.2029364 }, - { type: "node", id: 1069177967, lat: 51.178467, lon: 3.2025563 }, - { type: "node", id: 1069177976, lat: 51.1791427, lon: 3.2026954 }, - { type: "node", id: 1069177984, lat: 51.1783718, lon: 3.2028231 }, - { type: "node", id: 1069178021, lat: 51.1793534, lon: 3.2033094 }, - { type: "node", id: 1069178133, lat: 51.1784113, lon: 3.2028156 }, - { type: "node", id: 6853179202, lat: 51.1792037, lon: 3.2026994 }, - { type: "node", id: 6853179203, lat: 51.1790601, lon: 3.2026755 }, - { type: "node", id: 6853179204, lat: 51.1791861, lon: 3.2027108 }, - { type: "node", id: 6853179205, lat: 51.1791741, lon: 3.2027122 }, - { type: "node", id: 6853179206, lat: 51.1791634, lon: 3.2027102 }, - { type: "node", id: 6853179207, lat: 51.1791526, lon: 3.2027048 }, - { type: "node", id: 6853179208, lat: 51.1790128, lon: 3.202488 }, - { type: "node", id: 6853179209, lat: 51.1790434, lon: 3.2026225 }, - { type: "node", id: 6853179210, lat: 51.1789811, lon: 3.2023837 }, - { type: "node", id: 6853179211, lat: 51.1789744, lon: 3.2022415 }, - { type: "node", id: 6853179212, lat: 51.1789438, lon: 3.2022643 }, - { type: "node", id: 6853179213, lat: 51.1784216, lon: 3.2028567 }, - { type: "node", id: 6853179214, lat: 51.1784157, lon: 3.2028375 }, - { type: "node", id: 6853179215, lat: 51.1784506, lon: 3.2029294 }, - { type: "node", id: 6853179216, lat: 51.1783884, lon: 3.2026704 }, - { type: "node", id: 6853179217, lat: 51.178422, lon: 3.2026034 }, - { type: "node", id: 6853179218, lat: 51.1784117, lon: 3.2026185 }, - { type: "node", id: 6853179219, lat: 51.1784021, lon: 3.2026354 }, - { type: "node", id: 6853179220, lat: 51.1783957, lon: 3.2026505 }, - { type: "node", id: 6853179221, lat: 51.178443, lon: 3.2025785 }, - { type: "node", id: 6853179222, lat: 51.1784546, lon: 3.202567 }, - { type: "node", id: 6853179223, lat: 51.1784321, lon: 3.2025906 }, - { type: "node", id: 6853179224, lat: 51.1783719, lon: 3.2027464 }, - { type: "node", id: 6853179225, lat: 51.1783749, lon: 3.2027238 }, - { type: "node", id: 6853179226, lat: 51.1783827, lon: 3.2026894 }, - { type: "node", id: 6853179227, lat: 51.1783784, lon: 3.2027066 }, - { type: "node", id: 6853179228, lat: 51.1783697, lon: 3.2027864 }, - { type: "node", id: 6853179229, lat: 51.1783704, lon: 3.2027651 }, - { type: "node", id: 6853179230, lat: 51.1783703, lon: 3.2028048 }, - { type: "node", id: 6853179234, lat: 51.1798837, lon: 3.2029379 }, - { type: "node", id: 6853179235, lat: 51.1798988, lon: 3.2029445 }, - { type: "node", id: 6853179236, lat: 51.1798661, lon: 3.2029354 }, - { type: "node", id: 6853179237, lat: 51.1798345, lon: 3.2029407 }, - { type: "node", id: 6853179238, lat: 51.1798207, lon: 3.2029479 }, - { type: "node", id: 6853179239, lat: 51.1792634, lon: 3.2033377 }, - { type: "node", id: 6853179240, lat: 51.1793019, lon: 3.2033359 }, - { type: "node", id: 6853179241, lat: 51.1792828, lon: 3.20334 }, - { type: "node", id: 6853179242, lat: 51.1792732, lon: 3.2033393 }, - { type: "node", id: 6853179243, lat: 51.1792921, lon: 3.2033388 }, - { type: "node", id: 6853179244, lat: 51.1793279, lon: 3.2033257 }, - { type: "node", id: 6853179245, lat: 51.1793153, lon: 3.2033313 }, - { type: "node", id: 6853179246, lat: 51.1793413, lon: 3.2033182 }, - { type: "node", id: 6853179247, lat: 51.1792384, lon: 3.2033981 }, - { type: "node", id: 6853179248, lat: 51.1792572, lon: 3.2033605 }, - { type: "node", id: 6853179249, lat: 51.1792512, lon: 3.2033774 }, - { type: "node", id: 6853179250, lat: 51.1792456, lon: 3.2033888 }, - { type: "node", id: 6853179256, lat: 51.1790996, lon: 3.2034514 }, - { type: "node", id: 6853179257, lat: 51.1790731, lon: 3.2034317 }, - { type: "node", id: 6853179258, lat: 51.1790581, lon: 3.2034168 }, - { type: "node", id: 6853179259, lat: 51.1790862, lon: 3.2034422 }, - { type: "node", id: 6853179260, lat: 51.179133, lon: 3.2034648 }, - { type: "node", id: 6853179261, lat: 51.1791191, lon: 3.203461 }, - { type: "node", id: 6853179262, lat: 51.1791546, lon: 3.2034608 }, - { type: "node", id: 6853179263, lat: 51.1791443, lon: 3.2034639 }, - { type: "node", id: 6853179264, lat: 51.1789437, lon: 3.2033089 }, - { type: "node", id: 6853179267, lat: 51.1785146, lon: 3.2030128 }, - { type: "node", id: 6853179268, lat: 51.1785517, lon: 3.2030489 }, - { type: "node", id: 6853179269, lat: 51.1785863, lon: 3.2030773 }, - { type: "node", id: 6853179327, lat: 51.1789936, lon: 3.2024248 }, - { type: "node", id: 7252820961, lat: 51.175521, lon: 3.2045972 }, - { type: "node", id: 7252863798, lat: 51.1754304, lon: 3.2044959 }, - { type: "node", id: 8042845806, lat: 51.1753353, lon: 3.2041851 }, - { type: "node", id: 8042845807, lat: 51.175363, lon: 3.2043314 }, - { type: "node", id: 8042845812, lat: 51.1752711, lon: 3.2040127 }, - { type: "node", id: 4036885076, lat: 51.1740632, lon: 3.2050437 }, - { type: "node", id: 4036899624, lat: 51.1767493, lon: 3.2082945 }, - { type: "node", id: 5607796819, lat: 51.1782483, lon: 3.2091883 }, - { type: "node", id: 5607796820, lat: 51.1785128, lon: 3.2086016 }, - { type: "node", id: 5607798721, lat: 51.1786474, lon: 3.2090453 }, - { type: "node", id: 5607798722, lat: 51.1782874, lon: 3.2093115 }, - { type: "node", id: 5607798723, lat: 51.178141, lon: 3.2088491 }, - { type: "node", id: 5607798725, lat: 51.1785713, lon: 3.2087945 }, - { type: "node", id: 5728443539, lat: 51.1753294, lon: 3.2097039 }, - { type: "node", id: 5728443540, lat: 51.1752216, lon: 3.2089278 }, - { type: "node", id: 6275462768, lat: 51.174424, lon: 3.2105467 }, - { type: "node", id: 6275462769, lat: 51.1743524, lon: 3.2105548 }, - { type: "node", id: 6275462770, lat: 51.1743644, lon: 3.2108257 }, - { type: "node", id: 6275462771, lat: 51.1744361, lon: 3.2108176 }, - { type: "node", id: 7252820962, lat: 51.1756015, lon: 3.204854 }, - { type: "node", id: 7252820963, lat: 51.1755802, lon: 3.204928 }, - { type: "node", id: 7252820964, lat: 51.1755132, lon: 3.2049422 }, - { type: "node", id: 7252820965, lat: 51.1754719, lon: 3.2050156 }, - { type: "node", id: 7252820966, lat: 51.1754575, lon: 3.2051212 }, - { type: "node", id: 7252820967, lat: 51.1755143, lon: 3.2052892 }, - { type: "node", id: 7252820968, lat: 51.1755533, lon: 3.2055086 }, - { type: "node", id: 7252820969, lat: 51.1755563, lon: 3.2060065 }, - { type: "node", id: 7252820970, lat: 51.175491, lon: 3.2064409 }, - { type: "node", id: 7252820971, lat: 51.1753674, lon: 3.2068348 }, - { type: "node", id: 7252820972, lat: 51.1751944, lon: 3.2070531 }, - { type: "node", id: 7252820973, lat: 51.1751195, lon: 3.2071478 }, - { type: "node", id: 7252820974, lat: 51.1750834, lon: 3.2072467 }, - { type: "node", id: 7252820975, lat: 51.1750963, lon: 3.2073579 }, - { type: "node", id: 7252820976, lat: 51.1751376, lon: 3.2074032 }, - { type: "node", id: 7252820977, lat: 51.175215, lon: 3.2073826 }, - { type: "node", id: 7252820978, lat: 51.1752848, lon: 3.2073785 }, - { type: "node", id: 7252820979, lat: 51.1754252, lon: 3.2073858 }, - { type: "node", id: 7252820980, lat: 51.1754615, lon: 3.2074926 }, - { type: "node", id: 7252820981, lat: 51.1754259, lon: 3.20756 }, - { type: "node", id: 7252820982, lat: 51.17537, lon: 3.2076668 }, - { type: "node", id: 7252820983, lat: 51.1753304, lon: 3.2078901 }, - { type: "node", id: 7252820984, lat: 51.1753152, lon: 3.2079319 }, - { type: "node", id: 7252874885, lat: 51.1754423, lon: 3.2080951 }, - { type: "node", id: 7252874886, lat: 51.1754991, lon: 3.2083134 }, - { type: "node", id: 7252874887, lat: 51.1755307, lon: 3.2084864 }, - { type: "node", id: 7252874888, lat: 51.1755729, lon: 3.2087064 }, - { type: "node", id: 7252874889, lat: 51.1753248, lon: 3.2088635 }, - { type: "node", id: 7252874890, lat: 51.1752645, lon: 3.2092365 }, - { type: "node", id: 7252874891, lat: 51.1747746, lon: 3.2093558 }, - { type: "node", id: 8042845789, lat: 51.1748587, lon: 3.209526 }, - { type: "node", id: 8042845790, lat: 51.1749489, lon: 3.2096774 }, - { type: "node", id: 8042845791, lat: 51.1750595, lon: 3.2097458 }, - { type: "node", id: 8042845792, lat: 51.1753557, lon: 3.2077924 }, - { type: "node", id: 8042845793, lat: 51.1754621, lon: 3.2074425 }, - { type: "node", id: 8042845794, lat: 51.1754531, lon: 3.2074092 }, - { type: "node", id: 8042845795, lat: 51.1754729, lon: 3.2051839 }, - { type: "node", id: 8042845796, lat: 51.1754907, lon: 3.2052089 }, - { type: "node", id: 8042845797, lat: 51.1755084, lon: 3.2052355 }, - { type: "node", id: 8042845798, lat: 51.1755235, lon: 3.2053482 }, - { type: "node", id: 8042845799, lat: 51.1755387, lon: 3.2053805 }, - { type: "node", id: 8042845800, lat: 51.1755584, lon: 3.2057251 }, - { type: "node", id: 8042845801, lat: 51.1755536, lon: 3.205762 }, - { type: "node", id: 8042845802, lat: 51.1755492, lon: 3.2061312 }, - { type: "node", id: 8042845803, lat: 51.1755305, lon: 3.2062755 }, - { type: "node", id: 8042845804, lat: 51.1754335, lon: 3.2066603 }, - { type: "node", id: 8042845805, lat: 51.1755929, lon: 3.2047843 }, - { type: "node", id: 8042845808, lat: 51.1746278, lon: 3.2090183 }, - { type: "node", id: 8042845809, lat: 51.1740796, lon: 3.2076268 }, - { type: "node", id: 8042845844, lat: 51.1768218, lon: 3.20861 }, - { type: "node", id: 8042845845, lat: 51.1767935, lon: 3.2085031 }, - { type: "node", id: 8042845846, lat: 51.1769413, lon: 3.2089936 }, - { type: "node", id: 8042845847, lat: 51.1757541, lon: 3.2096988 }, - { type: "node", id: 8042845848, lat: 51.1757421, lon: 3.2096812 }, - { type: "node", id: 8042845849, lat: 51.1757312, lon: 3.2096924 }, - { type: "node", id: 8042845850, lat: 51.1757202, lon: 3.2096478 }, - { type: "node", id: 8042845851, lat: 51.1756902, lon: 3.2096207 }, - { type: "node", id: 8042845852, lat: 51.1756712, lon: 3.2096143 }, - { type: "node", id: 8042845853, lat: 51.1756602, lon: 3.2095745 }, - { type: "node", id: 8042845854, lat: 51.1756552, lon: 3.2095537 }, - { type: "node", id: 8042845855, lat: 51.1756657, lon: 3.2095174 }, - { type: "node", id: 8042845856, lat: 51.175658, lon: 3.20908 }, - { type: "node", id: 8042845857, lat: 51.1756525, lon: 3.2093366 }, - { type: "node", id: 8042845858, lat: 51.1756466, lon: 3.2088282 }, - { type: "node", id: 8042845859, lat: 51.1756582, lon: 3.2089151 }, - { type: "node", id: 8042845860, lat: 51.1765521, lon: 3.20839 }, - { type: "node", id: 1069177845, lat: 51.1809357, lon: 3.2035366 }, - { type: "node", id: 1069177849, lat: 51.1803975, lon: 3.2017749 }, - { type: "node", id: 1069178166, lat: 51.1804195, lon: 3.2033098 }, - { type: "node", id: 1519342742, lat: 51.1805239, lon: 3.2032684 }, - { type: "node", id: 1519342743, lat: 51.18064, lon: 3.2036951 }, - { type: "node", id: 1759437085, lat: 51.1806986, lon: 3.2036647 }, - { type: "node", id: 6852012577, lat: 51.1804541, lon: 3.2017867 }, - { type: "node", id: 6852012578, lat: 51.1804124, lon: 3.2018177 }, - { type: "node", id: 6852012579, lat: 51.1804106, lon: 3.2018165 }, - { type: "node", id: 6852012580, lat: 51.1804143, lon: 3.2018177 }, - { type: "node", id: 6852012581, lat: 51.1808363, lon: 3.2030295 }, - { type: "node", id: 6852012582, lat: 51.1807955, lon: 3.2030595 }, - { type: "node", id: 6852012583, lat: 51.180798, lon: 3.2030712 }, - { type: "node", id: 1519476620, lat: 51.1786696, lon: 3.2199463 }, - { type: "node", id: 1519476635, lat: 51.179306, lon: 3.2193119 }, - { type: "node", id: 1519476698, lat: 51.1795485, lon: 3.2192221 }, - { type: "node", id: 1519476744, lat: 51.1791125, lon: 3.2194529 }, - { type: "node", id: 1519476746, lat: 51.178483, lon: 3.2203218 }, - { type: "node", id: 1519476797, lat: 51.1788731, lon: 3.2196593 }, - { type: "node", id: 3780611492, lat: 51.1761568, lon: 3.2238485 }, - { type: "node", id: 3780611493, lat: 51.1762213, lon: 3.223901 }, - { type: "node", id: 3780611494, lat: 51.1762626, lon: 3.2237172 }, - { type: "node", id: 3780611495, lat: 51.1763208, lon: 3.2237628 }, - { type: "node", id: 3780611496, lat: 51.1763248, lon: 3.2236414 }, - { type: "node", id: 3780611497, lat: 51.1763881, lon: 3.2236926 }, - { type: "node", id: 3780611498, lat: 51.1764876, lon: 3.2235544 }, - { type: "node", id: 3780611499, lat: 51.1766551, lon: 3.2232337 }, - { type: "node", id: 3780611500, lat: 51.176687, lon: 3.2231945 }, - { type: "node", id: 3780611501, lat: 51.1767105, lon: 3.2232776 }, - { type: "node", id: 3780611502, lat: 51.176751, lon: 3.2232465 }, - { type: "node", id: 3780611503, lat: 51.1767812, lon: 3.2230729 }, - { type: "node", id: 3780611504, lat: 51.1768505, lon: 3.2231083 }, - { type: "node", id: 6533893620, lat: 51.178521, lon: 3.2203687 }, - { type: "node", id: 6533893621, lat: 51.1786845, lon: 3.220025 }, - { type: "node", id: 6533893622, lat: 51.1789011, lon: 3.2197183 }, - { type: "node", id: 6533893624, lat: 51.1791343, lon: 3.2195235 }, - { type: "node", id: 6533893625, lat: 51.1793269, lon: 3.2193854 }, - { type: "node", id: 6533893626, lat: 51.1795596, lon: 3.219299 }, - { type: "node", id: 5536620518, lat: 51.1683264, lon: 3.224863 }, - { type: "node", id: 5536620519, lat: 51.1684352, lon: 3.2251117 }, - { type: "node", id: 5536620520, lat: 51.1685675, lon: 3.2254022 }, - { type: "node", id: 5536620821, lat: 51.1687379, lon: 3.2258223 }, - { type: "node", id: 5536620822, lat: 51.1693682, lon: 3.2250177 }, - { type: "node", id: 5536620823, lat: 51.1693734, lon: 3.225049 }, - { type: "node", id: 5536620825, lat: 51.1707605, lon: 3.2244639 }, - { type: "node", id: 5536620837, lat: 51.1697793, lon: 3.2260181 }, - { type: "node", id: 5536620838, lat: 51.1699712, lon: 3.2262338 }, - { type: "node", id: 5536620839, lat: 51.1701247, lon: 3.2263242 }, - { type: "node", id: 5536620840, lat: 51.1704719, lon: 3.2266478 }, - { type: "node", id: 5536620841, lat: 51.1701028, lon: 3.2281081 }, - { type: "node", id: 5536620842, lat: 51.1698158, lon: 3.2276446 }, - { type: "node", id: 5536620843, lat: 51.1696441, lon: 3.2273837 }, - { type: "node", id: 5536620844, lat: 51.1695154, lon: 3.2272009 }, - { type: "node", id: 5536620845, lat: 51.169536, lon: 3.2271664 }, - { type: "node", id: 5536620846, lat: 51.1694515, lon: 3.2270181 }, - { type: "node", id: 5635001306, lat: 51.1737078, lon: 3.2354437 }, - { type: "node", id: 5635001371, lat: 51.1722128, lon: 3.2340273 }, - { type: "node", id: 5635001372, lat: 51.1723921, lon: 3.2343394 }, - { type: "node", id: 5635001373, lat: 51.1724213, lon: 3.2342967 }, - { type: "node", id: 5635001374, lat: 51.1722421, lon: 3.2339846 }, - { type: "node", id: 5635001375, lat: 51.1728995, lon: 3.2339319 }, - { type: "node", id: 5635001376, lat: 51.1729253, lon: 3.2339922 }, - { type: "node", id: 5635001377, lat: 51.1723583, lon: 3.2340816 }, - { type: "node", id: 5635001378, lat: 51.1723268, lon: 3.2340173 }, - { type: "node", id: 5635001379, lat: 51.172885, lon: 3.2337993 }, - { type: "node", id: 5635001380, lat: 51.1728611, lon: 3.2338706 }, - { type: "node", id: 5635001381, lat: 51.1723325, lon: 3.2339419 }, - { type: "node", id: 5635001382, lat: 51.1723464, lon: 3.2338696 }, - { type: "node", id: 5882873334, lat: 51.1736186, lon: 3.2330966 }, - { type: "node", id: 5882873335, lat: 51.1735451, lon: 3.2327633 }, - { type: "node", id: 5882873336, lat: 51.1737001, lon: 3.2327438 }, - { type: "node", id: 5882873337, lat: 51.1736796, lon: 3.2318764 }, - { type: "node", id: 5882873338, lat: 51.1735265, lon: 3.2318782 }, - { type: "node", id: 6593340582, lat: 51.1727872, lon: 3.2328745 }, - { type: "node", id: 6593340583, lat: 51.1728013, lon: 3.2332051 }, - { type: "node", id: 6593340584, lat: 51.1736743, lon: 3.2331435 }, - { type: "node", id: 7767137235, lat: 51.1735198, lon: 3.2355568 }, - { type: "node", id: 7767137236, lat: 51.1735366, lon: 3.2355246 }, - { type: "node", id: 7767137237, lat: 51.1735198, lon: 3.2356399 }, - { type: "node", id: 5635001274, lat: 51.1751425, lon: 3.2346144 }, - { type: "node", id: 5635001275, lat: 51.1751696, lon: 3.2347601 }, - { type: "node", id: 5635001276, lat: 51.1750553, lon: 3.2348141 }, - { type: "node", id: 5635001277, lat: 51.1750282, lon: 3.2346684 }, - { type: "node", id: 5635001312, lat: 51.174002, lon: 3.2349367 }, - { type: "node", id: 5635001383, lat: 51.1740709, lon: 3.233056 }, - { type: "node", id: 5635001384, lat: 51.1740249, lon: 3.2330598 }, - { type: "node", id: 5635001385, lat: 51.1740265, lon: 3.2331313 }, - { type: "node", id: 5635001386, lat: 51.1740597, lon: 3.2327202 }, - { type: "node", id: 5635001414, lat: 51.174281, lon: 3.2336147 }, - { type: "node", id: 5635001415, lat: 51.174081, lon: 3.2338914 }, - { type: "node", id: 5635001416, lat: 51.1740489, lon: 3.2338323 }, - { type: "node", id: 5635001417, lat: 51.1742489, lon: 3.2335556 }, - { type: "node", id: 5761770202, lat: 51.1783111, lon: 3.2342484 }, - { type: "node", id: 5761770204, lat: 51.1782819, lon: 3.2339616 }, - { type: "node", id: 7767137234, lat: 51.1739713, lon: 3.2348766 }, - { type: "node", id: 9052878228, lat: 51.1781206, lon: 3.234323 }, - { type: "node", id: 9052878229, lat: 51.1781054, lon: 3.2339448 }, - { - type: "way", - id: 810604915, - nodes: [ - 1168727824, 9167054153, 9274761589, 9274761596, 7577430793, 1038638712, - 1038638723, 1038638661, 9199177059, 1038638721, 7554434436, 7578975035, - 7554434438, 7578865273, 7578975032, 7578975030, 7578975029, 1038638696, - 7578975009, 7578975008, 7578975007, 1038638743, 7578975002, 7578974988, - 7578974989, 7578974990, 7578974991, 7578974992, 7578865275, 7578865274, - 1038638753, 7578974995, 7578974996, 7578974997, 7578904489, 7578974999, - 7578975000, 7578975001, 7578974998, 3921878998, 1038638592, 929120698, - 1675648152, 7578865281, 7578865283, 7578975012, 7578975015, 7578975016, - 3922380061, 2732486274, 3922380083, 7578975019, 7578975018, 7578975021, - 7578960079, 3922375256, 7578975024, 3922380071, - ], - }, - { - type: "way", - id: 989393316, - nodes: [ - 3922380071, 3922380081, 3922380086, 3922380092, 3922380095, 9167054157, - 7578975026, 9274761593, 9274761592, 9274761591, 7578975027, 9167054156, - 9167054154, 7578975049, 7578975028, 1168727824, - ], - }, - { - type: "way", - id: 389026405, - nodes: [ - 3921879019, 7578975044, 3921879018, 7578975046, 7578975045, 7578975040, - 3921879004, 3921879011, 3921879019, - ], - }, - { - type: "way", - id: 810607458, - nodes: [ - 7578987409, 7578987410, 7578987411, 5745833241, 5745833240, 5745833239, - 7578987412, 7578987415, 7578987413, 7578987414, 7578987417, 7578987416, - 7578987418, 7578987419, 7578987409, - ], - }, - { - type: "way", - id: 777280458, - nodes: [ - 8042845812, 8042845806, 8042845807, 7252863798, 7252820961, 8042845805, - 7252820962, 7252820963, 7252820964, 7252820965, 7252820966, 8042845795, - 8042845796, 8042845797, 7252820967, 8042845798, 8042845799, 7252820968, - 8042845800, 8042845801, 7252820969, 8042845802, 8042845803, 7252820970, - 8042845804, 7252820971, 7252820972, 7252820973, 7252820974, 7252820975, - 7252820976, 7252820977, 7252820978, 7252820979, 8042845794, 8042845793, - 7252820980, 7252820981, 7252820982, 8042845792, 7252820983, 7252820984, - 7252874885, 7252874886, 7252874887, 7252874888, 7252874889, 5728443540, - 7252874890, 5728443539, 8042845791, 8042845790, 8042845789, 7252874891, - 8042845808, 8042845809, 8042845810, 8042845811, 4036885076, 8042845812, - ], - }, - { - type: "way", - id: 577572397, - nodes: [ - 5536620518, 5536620519, 5536620520, 5536620821, 5536620822, 5536620823, - 5536620824, 5536620825, 5536620826, 6067483782, 5536620827, 5536620828, - 5536620829, 5536620830, 5536620831, 5536620832, 7794736251, 5536620505, - 5536620506, 5536620507, 5952389321, 5952389322, 5952389323, 5536609426, - 5952389320, 5172938444, 5536620510, 5536620511, 5536620512, 5536620513, - 6067483781, 5536620514, 5536620516, 5536620518, - ], - }, - { - type: "way", - id: 863373849, - nodes: [ - 4036899624, 8042845845, 8042845844, 8042845846, 8042845847, 8042845848, - 8042845849, 8042845850, 8042845851, 8042845852, 8042845853, 8042845854, - 8042845855, 8042845857, 8042845856, 8042845859, 8042845858, 8042845860, - 4036899624, - ], - }, - { - type: "way", - id: 577572399, - nodes: [ - 5536620837, 5536620838, 5536620839, 5536620840, 5536620841, 5536620842, - 5536620843, 5536620844, 5536620845, 5536620846, 5536620837, - ], - }, - ], - } - - Utils.injectJsonDownloadForTests( - "https://overpass-api.de/api/interpreter?data=%5Bout%3Ajson%5D%5Btimeout%3A60%5D%5Bbbox%3A51.124212757826875%2C3.1640625%2C51.17934297928927%2C3.251953125%5D%3B" + - query, - d - ) - - Utils.injectJsonDownloadForTests( - "https://overpass-api.de/api/interpreter?data=%5Bout%3Ajson%5D%5Btimeout%3A60%5D%5Bbbox%3A51.124212757826875%2C3.251953125%2C51.17934297928927%2C3.33984375%5D%3B" + - query, - { - version: 0.6, - generator: "Overpass API 0.7.57 93a4d346", - osm3s: { - timestamp_osm_base: "2022-02-14T00:02:14Z", - copyright: - "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL.", - }, - elements: [ - { - type: "node", - id: 668981602, - lat: 51.1588243, - lon: 3.2558654, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 668981622, - lat: 51.1565636, - lon: 3.2549888, - tags: { - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "no", - }, - }, - { - type: "node", - id: 1580339675, - lat: 51.1395949, - lon: 3.3332507, - tags: { amenity: "parking" }, - }, - { - type: "node", - id: 1764571836, - lat: 51.1701118, - lon: 3.3363371, - tags: { amenity: "parking" }, - }, - { - type: "node", - id: 2121652779, - lat: 51.1268536, - lon: 3.3239607, - tags: { amenity: "parking" }, - }, - { - type: "node", - id: 2386053906, - lat: 51.162161, - lon: 3.263065, - tags: { amenity: "toilets" }, - }, - { - type: "node", - id: 2978180520, - lat: 51.1329149, - lon: 3.3362322, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 2978183271, - lat: 51.1324243, - lon: 3.3373735, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 2978184471, - lat: 51.1436385, - lon: 3.2916539, - tags: { amenity: "bench", backrest: "yes", check_date: "2021-02-26" }, - }, - { - type: "node", - id: 3925976407, - lat: 51.1787486, - lon: 3.2831866, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 5158056232, - lat: 51.1592067, - lon: 3.2567111, - tags: { - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "no", - }, - }, - { - type: "node", - id: 5718776382, - lat: 51.1609023, - lon: 3.2582509, - tags: { check_date: "2021-02-26", covered: "no", leisure: "picnic_table" }, - }, - { - type: "node", - id: 5718776383, - lat: 51.1609488, - lon: 3.2581877, - tags: { check_date: "2021-02-26", covered: "no", leisure: "picnic_table" }, - }, - { - type: "node", - id: 5745727100, - lat: 51.1594639, - lon: 3.2604304, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 5745739587, - lat: 51.1580397, - lon: 3.263101, - tags: { check_date: "2021-02-26", leisure: "picnic_table" }, - }, - { - type: "node", - id: 5745739588, - lat: 51.1580631, - lon: 3.2630345, - tags: { check_date: "2021-02-26", leisure: "picnic_table" }, - }, - { - type: "node", - id: 5961596093, - lat: 51.1588103, - lon: 3.2633933, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 5964032193, - lat: 51.1514821, - lon: 3.2723766, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6034563379, - lat: 51.1421689, - lon: 3.3022271, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6034564191, - lat: 51.1722186, - lon: 3.2823584, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6034565298, - lat: 51.1722796, - lon: 3.282329, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6145151111, - lat: 51.1690435, - lon: 3.3388676, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6145151112, - lat: 51.1690023, - lon: 3.3388636, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6216549651, - lat: 51.1292813, - lon: 3.332369, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6216549652, - lat: 51.1292768, - lon: 3.3324259, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7204447030, - lat: 51.1791769, - lon: 3.283116, - tags: { - board_type: "nature", - information: "board", - mapillary: "0BHVgU1XCyTMM9cjvidUqk", - name: "De Assebroekse Meersen", - tourism: "information", - }, - }, - { - type: "node", - id: 7468175778, - lat: 51.1344104, - lon: 3.3348246, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7602473480, - lat: 51.1503874, - lon: 3.2836867, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7602473482, - lat: 51.150244, - lon: 3.2842925, - tags: { - board_type: "wildlife", - information: "board", - name: "Waterbeestjes", - operator: "Natuurpunt Vallei van de Zuidleie", - tourism: "information", - }, - }, - { - type: "node", - id: 7602699080, - lat: 51.1367031, - lon: 3.3320712, - tags: { amenity: "bench", backrest: "yes", material: "metal" }, - }, - { - type: "node", - id: 7680940369, - lat: 51.1380074, - lon: 3.3369928, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7726850522, - lat: 51.1418585, - lon: 3.3064234, - tags: { - "image:0": "https://i.imgur.com/Bh6UjYy.jpg", - information: "board", - tourism: "information", - }, - }, - { - type: "node", - id: 7727071212, - lat: 51.1501173, - lon: 3.2845352, - tags: { - board_type: "wildlife", - "image:0": "https://i.imgur.com/mFEQJWd.jpg", - information: "board", - name: "Vleermuizen", - operator: "Natuurpunt Vallei van de Zuidleie", - tourism: "information", - }, - }, - { - type: "node", - id: 9122376662, - lat: 51.1720505, - lon: 3.3308524, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 9425818876, - lat: 51.1325315, - lon: 3.3371616, - tags: { leisure: "picnic_table", mapillary: "101961548889238" }, - }, - { - type: "way", - id: 149408639, - nodes: [1623924235, 1623924236, 1623924238, 1864750831, 1623924241, 1623924235], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 149553206, - nodes: [ - 1625199938, 1625199951, 8377691836, 8378081366, 8378081429, 8378081386, - 1625199950, 6414383775, 1625199827, 1625199938, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 184402308, - nodes: [1948836195, 1948836194, 1948836193, 1948836189, 1948836192, 1948836195], - tags: { - building: "yes", - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "yes", - wheelchair: "yes", - }, - }, - { - type: "way", - id: 184402309, - nodes: [1948836029, 1948836038, 1948836032, 1948836025, 1948836023, 1948836029], - tags: { - building: "yes", - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "yes", - "source:geometry:date": "2011-11-07", - "source:geometry:ref": "Gbg/3489204", - }, - }, - { - type: "way", - id: 184402331, - nodes: [1948836104, 1948836072, 1948836068, 1948836093, 1948836104], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 236979353, - nodes: [ - 2449323191, 7962681624, 7962681623, 7962681621, 7962681622, 2449323193, - 7962681620, 7962681619, 8360787098, 4350143592, 6794421028, 6794421027, - 6794421041, 7962681614, 2123461969, 2449323198, 7962681615, 7962681616, - 6794421042, 2449323191, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 251590503, - nodes: [ - 2577910543, 2577910530, 2577910542, 2577910520, 6418533335, 2577910526, - 2577910545, 2577910543, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 313090489, - nodes: [ - 3190107423, 3190107435, 3190107442, 3190107439, 3190107432, 3190107428, - 3190107424, 3190107400, 3190107393, 3190107377, 3190107371, 3190107374, - 3190107408, 3190107407, 3190107415, 3190107416, 3190107420, 3190107423, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 314531418, - nodes: [ - 7468171455, 7468171451, 7727393212, 7727393211, 7727393210, 7727393208, - 7727393209, 8781449489, 7727393207, 7727393206, 7727393205, 7727393204, - 7727393203, 7727393202, 7727393201, 7727393200, 7390697550, 7390697534, - 7727393199, 7727393198, 7727393197, 7390697549, 7727393196, 7727393195, - 7727393194, 7727393193, 7727393192, 7727393191, 7727393190, 7727393189, - 7727393188, 7727393187, 7727393186, 7727393185, 7727339384, 7727339383, - 7727339382, 1553169911, 1553169836, 1493821433, 1493821422, 3185248088, - 7727339364, 7727339365, 7727339366, 7727339367, 7727339368, 7727339369, - 7727339370, 7727339371, 7727339372, 7727339373, 7727339374, 7727339375, - 7727339376, 7727339377, 3185248049, 3185248048, 3185248042, 3185248040, - 7727339378, 7727339379, 7727339380, 7727339381, 7468171438, 7468171442, - 7468171430, 7468171432, 7468171446, 7468171404, 7468171405, 7468171422, - 7468171426, 7468171433, 7468171423, 7468171428, 7468171431, 7468171429, - 7468171406, 7468171407, 7468171444, 7468171408, 7468171409, 7468171410, - 7468171411, 7468171412, 7468171413, 7190927792, 7190927791, 7190927793, - 7190927787, 7190927788, 7190927789, 7190927790, 7190927786, 7602692242, - 7190927785, 7190873584, 7468171450, 7190873582, 7190873576, 7190873578, - 7190873577, 7468171455, - ], - tags: { - access: "yes", - dog: "leashed", - dogs: "leashed", - image: "https://i.imgur.com/cOfwWTj.jpg", - "image:0": "https://i.imgur.com/RliQdyi.jpg", - "image:1": "https://i.imgur.com/IeKHahz.jpg", - "image:2": "https://i.imgur.com/1K0IORH.jpg", - "image:3": "https://i.imgur.com/jojP09s.jpg", - "image:4": "https://i.imgur.com/DK6kT51.jpg", - "image:5": "https://i.imgur.com/RizbGM1.jpg", - "image:6": "https://i.imgur.com/hyoY6Cl.jpg", - "image:7": "https://i.imgur.com/xDd7Wrq.jpg", - leisure: "nature_reserve", - name: "Miseriebocht", - operator: "Natuurpunt Beernem", - website: "https://www.natuurpunt.be/natuurgebied/miseriebocht", - wikidata: "Q97060915", - }, - }, - { - type: "way", - id: 366318480, - nodes: [3702926557, 3702926558, 3702926559, 3702926560, 3702926557], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 366318481, - nodes: [ - 3702878648, 3702926561, 3702926562, 3702926563, 3702926564, 3702926565, - 3702926566, 3702926567, 3702878648, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 366318482, - nodes: [3702926568, 8292789053, 8292789054, 3702878654, 3702926568], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 366320440, - nodes: [3702956173, 3702956174, 3702956175, 3702956176, 3702956173], - tags: { amenity: "parking", name: "Kleine Beer" }, - }, - { - type: "way", - id: 366321706, - nodes: [3702969714, 3702969715, 3702969716, 3702969717, 3702969714], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 480267681, - nodes: [ - 4732689641, 4732689640, 4732689639, 4732689638, 4732689637, 4732689636, - 4732689635, 4732689634, 4732689633, 4732689632, 4732689631, 4732689630, - 4732689629, 4732689628, 4732689627, 4732689626, 8294875888, 4732689641, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 554341620, - nodes: [5349884603, 5349884602, 5349884601, 5349884600, 5349884603], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 554341621, - nodes: [5349884607, 5349884606, 5349884605, 5349884604, 5349884607], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 561902092, - nodes: [5417516023, 5417515520, 5417516021, 5417516022, 5417516023], - tags: { - building: "yes", - image: "https://i.imgur.com/WmViSbL.jpg", - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "yes", - wheelchair: "no", - }, - }, - { - type: "way", - id: 605915064, - nodes: [5745739924, 5745739925, 5745739926, 5745739927, 5745739924], - tags: { amenity: "parking", surface: "fine2" }, - }, - { - type: "way", - id: 650285088, - nodes: [ - 3645188881, 6100803131, 6100803130, 6100803129, 6100803124, 6100803125, - 6100803128, 6100803127, 6100803126, 3645188881, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 655944574, - nodes: [ - 6145151107, 6145151108, 6145151109, 6145151115, 6145151114, 6145151110, - 6145151107, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 664171069, - nodes: [ - 6216549610, 6216549611, 6216549612, 6216549613, 1413470849, 1413470848, - 6216549605, 6216549604, 6216549610, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 664171076, - nodes: [ - 6216549656, 6216549655, 8307316294, 6216549661, 6216549657, 6216549658, - 6216549659, 6216549660, 6216549656, - ], - tags: { amenity: "parking", capacity: "50" }, - }, - { - type: "way", - id: 665330334, - nodes: [6227395993, 6227395991, 6227395992, 6227395997, 6227395993], - tags: { - access: "yes", - amenity: "parking", - fee: "no", - parking: "surface", - surface: "asphalt", - }, - }, - { - type: "way", - id: 684598363, - nodes: [ - 3227068565, 6416001161, 6414352054, 6414352055, 7274233390, 7274233391, - 7274233397, 7274233395, 7274233396, 6414352053, 3227068568, 3227068565, - ], - tags: { amenity: "parking", fee: "no" }, - }, - { - type: "way", - id: 684599810, - nodes: [ - 1317838331, 8279842668, 1384096112, 1317838328, 6414374315, 3227068446, - 6414374316, 6414374317, 6414374318, 3227068456, 6414374319, 6414374320, - 6414374321, 1317838317, 1317838331, - ], - tags: { access: "no", amenity: "parking", operator: "Politie" }, - }, - { - type: "way", - id: 761474468, - nodes: [ - 7114502201, 7114502203, 7114502200, 7114502202, 3170562439, 3170562437, - 3170562431, 7114502240, 7114502211, 7114502212, 7114502214, 7114502215, - 7114502228, 7114502234, 7114502235, 7114502236, 7114502237, 7114502238, - 7114502239, 7114502233, 7114502232, 7114502231, 7114502229, 7114502230, - 7114502227, 7114502226, 7114502225, 7114502216, 7114502217, 7114502224, - 3170562392, 7114502218, 3170562394, 7114502219, 7114502220, 7114502221, - 7114502222, 7114502223, 3170562395, 3170562396, 3170562397, 3170562402, - 3170562410, 7114502209, 7114502208, 7114502207, 7114502205, 7114502206, - 3170562436, 1475188519, 1475188516, 6627605025, 8294886142, 7114502201, - ], - tags: { - image: "http://valleivandezuidleie.be/wp-content/uploads/2011/12/2011-03-24_G12_088_1_1.JPG", - leisure: "nature_reserve", - name: "Merlebeek-Meerberg", - natural: "wetland", - operator: "Natuurpunt Vallei van de Zuidleie", - start_date: "2011", - website: - "http://valleivandezuidleie.be/info-over-de-vallei-van-de-zuidleie/merlebeek/", - wetland: "wet_meadow", - }, - }, - { - type: "way", - id: 813859435, - nodes: [ - 7602479690, 7459257985, 7602479691, 7459154784, 7459154782, 7602479692, - 5482441357, 7602479693, 7602479694, 7602479695, 7602479696, 7602479690, - ], - tags: { - access: "yes", - "image:0": "https://i.imgur.com/nb9nawa.jpg", - landuse: "grass", - leisure: "nature_reserve", - "name:signed": "no", - natural: "grass", - operator: "Natuurpunt Vallei van de Zuidleie", - }, - }, - { - type: "way", - id: 826103452, - nodes: [7713176912, 7713176911, 7713176910, 7713176909, 7713176912], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 893176022, - nodes: [1927235214, 8301349336, 8301349335, 8301349337, 1927235214], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943476, - nodes: [8328251887, 8328251886, 8328251885, 8328251884, 8328251887], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943477, - nodes: [8328251891, 8328251890, 8328251889, 8328251888, 8328251891], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943478, - nodes: [8328251895, 8328251894, 8328251893, 8328251892, 8328251895], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943479, - nodes: [8328251897, 8328251896, 8328251901, 8328251900, 8328251897], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943480, - nodes: [8328251898, 8328251899, 8328251903, 8328251902, 8328251898], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943481, - nodes: [8328251907, 8328251906, 8328251905, 8328251904, 8328251907], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943482, - nodes: [8328251911, 8328251910, 8328251909, 8328251908, 8328251911], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 901952767, - nodes: [8378097127, 8378097126, 8378097125, 8378097124, 8378097127], - tags: { amenity: "parking", parking: "street_side" }, - }, - { - type: "way", - id: 901952768, - nodes: [ - 8378097134, 8378097133, 8378097132, 8378097131, 8378097130, 8378097129, - 8378097128, 8378097134, - ], - tags: { amenity: "parking", parking: "street_side" }, - }, - { - type: "way", - id: 947325182, - nodes: [ - 8497007549, 8768981525, 8768981522, 8768981524, 8768981521, 8768981523, - 8768981520, 8768981519, 6206789709, 8768981533, 8497007549, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 966827815, - nodes: [8945026757, 8945026756, 8945026755, 8945026754, 8945026757], - tags: { access: "customers", amenity: "parking", parking: "surface" }, - }, - { - type: "relation", - id: 2589413, - members: [ - { type: "way", ref: 663050030, role: "outer" }, - { type: "way", ref: 184402334, role: "outer" }, - { type: "way", ref: 184402332, role: "outer" }, - { type: "way", ref: 184402325, role: "outer" }, - { type: "way", ref: 184402326, role: "outer" }, - { type: "way", ref: 314899865, role: "outer" }, - { type: "way", ref: 314956402, role: "outer" }, - ], - tags: { - access: "yes", - image: "https://i.imgur.com/Yu4qHh5.jpg", - leisure: "nature_reserve", - name: "Warandeputten", - operator: "Natuurpunt Vallei van de Zuidleie", - type: "multipolygon", - wikipedia: "nl:Warandeputten", - }, - }, - { - type: "relation", - id: 8782624, - members: [ - { type: "way", ref: 315041273, role: "outer" }, - { type: "way", ref: 631377343, role: "outer" }, - { type: "way", ref: 631371237, role: "outer" }, - { type: "way", ref: 631371236, role: "outer" }, - { type: "way", ref: 631371234, role: "outer" }, - { type: "way", ref: 631377344, role: "outer" }, - { type: "way", ref: 631371232, role: "outer" }, - { type: "way", ref: 631371231, role: "outer" }, - { type: "way", ref: 315041263, role: "outer" }, - { type: "way", ref: 631371228, role: "outer" }, - { type: "way", ref: 631377341, role: "outer" }, - { type: "way", ref: 315041261, role: "outer" }, - { type: "way", ref: 631371223, role: "outer" }, - ], - tags: { - access: "yes", - "image:0": "https://i.imgur.com/VuzX5jW.jpg", - "image:1": "https://i.imgur.com/tPppmJG.jpg", - "image:2": "https://i.imgur.com/ecY3RER.jpg", - "image:3": "https://i.imgur.com/lr4FK6j.jpg", - "image:5": "https://i.imgur.com/uufEeE6.jpg", - leisure: "nature_reserve", - name: "Leiemeersen Noord", - operator: "Natuurpunt Vallei van de Zuidleie", - type: "multipolygon", - website: - "http://valleivandezuidleie.be/info-over-de-vallei-van-de-zuidleie/leiemeersen-noord/", - }, - }, - { - type: "relation", - id: 8890076, - members: [ - { type: "way", ref: 640979982, role: "outer" }, - { type: "way", ref: 640979978, role: "outer" }, - ], - tags: { - access: "yes", - "image:0": "https://i.imgur.com/SAAaKBH.jpg", - "image:1": "https://i.imgur.com/DGK9iBN.jpg", - "image:2": "https://i.imgur.com/bte1KJx.jpg", - "image:3": "https://i.imgur.com/f75Gxnx.jpg", - leisure: "nature_reserve", - name: "Gevaerts Noord", - operator: "Natuurpunt Vallei van de Zuidleie", - type: "multipolygon", - }, - }, - { - type: "relation", - id: 9118029, - members: [ - { type: "way", ref: 606165130, role: "outer" }, - { type: "way", ref: 655652319, role: "outer" }, - { type: "way", ref: 655652321, role: "outer" }, - ], - tags: { - access: "no", - "image:0": "https://i.imgur.com/eBufo0v.jpg", - "image:1": "https://i.imgur.com/kBej2Nk.jpg", - "image:2": "https://i.imgur.com/QKoyIRl.jpg", - leisure: "nature_reserve", - name: "De Leiemeersen", - note: "Door de hoge kwetsbaarheid van het gebied zijn De Leiemeersen enkel te bezoeken onder begeleiding van een gids", - "note:mapping": - "NIET VOOR BEGINNENDE MAPPERS! Dit gebied is met relaties als multipolygonen gemapt (zo'n 50 stuks). Als je niet weet hoe dit werkt, vraag hulp.", - note_1: "wetland=marsh is het veenmoerasgebied", - operator: "Natuurpunt Vallei van de Zuidleie", - type: "multipolygon", - website: - "http://valleivandezuidleie.be/info-over-de-vallei-van-de-zuidleie/leiemeersen/", - }, - }, - { - type: "node", - id: 668981602, - lat: 51.1588243, - lon: 3.2558654, - timestamp: "2012-07-06T17:58:39Z", - version: 2, - changeset: 12133044, - user: "martino260", - uid: 655442, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 668981622, - lat: 51.1565636, - lon: 3.2549888, - timestamp: "2020-03-23T23:54:26Z", - version: 4, - changeset: 82544029, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "no", - }, - }, - { - type: "node", - id: 1580339675, - lat: 51.1395949, - lon: 3.3332507, - timestamp: "2012-01-07T00:44:42Z", - version: 1, - changeset: 10317754, - user: "popaultje", - uid: 519184, - tags: { amenity: "parking" }, - }, - { - type: "node", - id: 1764571836, - lat: 51.1701118, - lon: 3.3363371, - timestamp: "2012-05-24T21:06:50Z", - version: 1, - changeset: 11693640, - user: "popaultje", - uid: 519184, - tags: { amenity: "parking" }, - }, - { - type: "node", - id: 2121652779, - lat: 51.1268536, - lon: 3.3239607, - timestamp: "2013-01-20T20:12:50Z", - version: 1, - changeset: 14725799, - user: "tomvdb", - uid: 437764, - tags: { amenity: "parking" }, - }, - { - type: "node", - id: 2386053906, - lat: 51.162161, - lon: 3.263065, - timestamp: "2018-01-19T21:31:30Z", - version: 2, - changeset: 55589374, - user: "L'imaginaire", - uid: 654234, - tags: { amenity: "toilets" }, - }, - { - type: "node", - id: 2978180520, - lat: 51.1329149, - lon: 3.3362322, - timestamp: "2014-07-24T21:29:38Z", - version: 1, - changeset: 24338416, - user: "pieterjanheyse", - uid: 254767, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 2978183271, - lat: 51.1324243, - lon: 3.3373735, - timestamp: "2019-09-14T05:02:25Z", - version: 2, - changeset: 74462201, - user: "JanFi", - uid: 672253, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 2978184471, - lat: 51.1436385, - lon: 3.2916539, - timestamp: "2021-02-26T10:22:54Z", - version: 3, - changeset: 100041319, - user: "s8evq", - uid: 3710738, - tags: { amenity: "bench", backrest: "yes", check_date: "2021-02-26" }, - }, - { - type: "node", - id: 3925976407, - lat: 51.1787486, - lon: 3.2831866, - timestamp: "2019-08-12T19:48:31Z", - version: 2, - changeset: 73281516, - user: "s8evq", - uid: 3710738, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 5158056232, - lat: 51.1592067, - lon: 3.2567111, - timestamp: "2021-04-17T16:21:52Z", - version: 5, - changeset: 103110072, - user: "L'imaginaire", - uid: 654234, - tags: { - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "no", - }, - }, - { - type: "node", - id: 5718776382, - lat: 51.1609023, - lon: 3.2582509, - timestamp: "2021-10-18T10:27:50Z", - version: 3, - changeset: 112646117, - user: "s8evq", - uid: 3710738, - tags: { check_date: "2021-02-26", covered: "no", leisure: "picnic_table" }, - }, - { - type: "node", - id: 5718776383, - lat: 51.1609488, - lon: 3.2581877, - timestamp: "2021-10-18T10:27:50Z", - version: 3, - changeset: 112646117, - user: "s8evq", - uid: 3710738, - tags: { check_date: "2021-02-26", covered: "no", leisure: "picnic_table" }, - }, - { - type: "node", - id: 5745727100, - lat: 51.1594639, - lon: 3.2604304, - timestamp: "2018-07-07T18:00:29Z", - version: 1, - changeset: 60494261, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 5745739587, - lat: 51.1580397, - lon: 3.263101, - timestamp: "2021-02-26T10:07:56Z", - version: 2, - changeset: 100039706, - user: "s8evq", - uid: 3710738, - tags: { check_date: "2021-02-26", leisure: "picnic_table" }, - }, - { - type: "node", - id: 5745739588, - lat: 51.1580631, - lon: 3.2630345, - timestamp: "2021-02-26T10:07:41Z", - version: 2, - changeset: 100039706, - user: "s8evq", - uid: 3710738, - tags: { check_date: "2021-02-26", leisure: "picnic_table" }, - }, - { - type: "node", - id: 5961596093, - lat: 51.1588103, - lon: 3.2633933, - timestamp: "2018-10-06T14:41:12Z", - version: 1, - changeset: 63258667, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 5964032193, - lat: 51.1514821, - lon: 3.2723766, - timestamp: "2018-10-07T12:15:53Z", - version: 1, - changeset: 63277533, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6034563379, - lat: 51.1421689, - lon: 3.3022271, - timestamp: "2020-02-11T20:10:26Z", - version: 2, - changeset: 80868434, - user: "s8evq", - uid: 3710738, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6034564191, - lat: 51.1722186, - lon: 3.2823584, - timestamp: "2018-11-04T20:27:50Z", - version: 2, - changeset: 64177431, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6034565298, - lat: 51.1722796, - lon: 3.282329, - timestamp: "2018-11-04T20:27:50Z", - version: 2, - changeset: 64177431, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 6145151111, - lat: 51.1690435, - lon: 3.3388676, - timestamp: "2018-12-18T13:13:36Z", - version: 1, - changeset: 65580728, - user: "Siel Nollet", - uid: 3292414, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6145151112, - lat: 51.1690023, - lon: 3.3388636, - timestamp: "2018-12-18T13:13:36Z", - version: 1, - changeset: 65580728, - user: "Siel Nollet", - uid: 3292414, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6216549651, - lat: 51.1292813, - lon: 3.332369, - timestamp: "2019-01-17T16:29:02Z", - version: 1, - changeset: 66400781, - user: "Nilsnn", - uid: 4652000, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 6216549652, - lat: 51.1292768, - lon: 3.3324259, - timestamp: "2019-01-17T16:29:03Z", - version: 1, - changeset: 66400781, - user: "Nilsnn", - uid: 4652000, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7204447030, - lat: 51.1791769, - lon: 3.283116, - timestamp: "2021-01-01T12:18:32Z", - version: 3, - changeset: 96768203, - user: "L'imaginaire", - uid: 654234, - tags: { - board_type: "nature", - information: "board", - mapillary: "0BHVgU1XCyTMM9cjvidUqk", - name: "De Assebroekse Meersen", - tourism: "information", - }, - }, - { - type: "node", - id: 7468175778, - lat: 51.1344104, - lon: 3.3348246, - timestamp: "2020-04-30T19:07:49Z", - version: 1, - changeset: 84433940, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7602473480, - lat: 51.1503874, - lon: 3.2836867, - timestamp: "2020-06-08T10:39:07Z", - version: 1, - changeset: 86349440, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { leisure: "picnic_table" }, - }, - { - type: "node", - id: 7602473482, - lat: 51.150244, - lon: 3.2842925, - timestamp: "2021-02-26T10:18:19Z", - version: 4, - changeset: 100040859, - user: "s8evq", - uid: 3710738, - tags: { - board_type: "wildlife", - information: "board", - name: "Waterbeestjes", - operator: "Natuurpunt Vallei van de Zuidleie", - tourism: "information", - }, - }, - { - type: "node", - id: 7602699080, - lat: 51.1367031, - lon: 3.3320712, - timestamp: "2020-06-08T12:08:11Z", - version: 1, - changeset: 86353903, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { amenity: "bench", backrest: "yes", material: "metal" }, - }, - { - type: "node", - id: 7680940369, - lat: 51.1380074, - lon: 3.3369928, - timestamp: "2020-07-03T17:54:31Z", - version: 1, - changeset: 87513827, - user: "L'imaginaire", - uid: 654234, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 7726850522, - lat: 51.1418585, - lon: 3.3064234, - timestamp: "2020-07-18T15:57:43Z", - version: 1, - changeset: 88179934, - user: "Pieter Vander Vennet", - uid: 3818858, - tags: { - "image:0": "https://i.imgur.com/Bh6UjYy.jpg", - information: "board", - tourism: "information", - }, - }, - { - type: "node", - id: 7727071212, - lat: 51.1501173, - lon: 3.2845352, - timestamp: "2021-02-26T10:17:39Z", - version: 2, - changeset: 100040859, - user: "s8evq", - uid: 3710738, - tags: { - board_type: "wildlife", - "image:0": "https://i.imgur.com/mFEQJWd.jpg", - information: "board", - name: "Vleermuizen", - operator: "Natuurpunt Vallei van de Zuidleie", - tourism: "information", - }, - }, - { - type: "node", - id: 9122376662, - lat: 51.1720505, - lon: 3.3308524, - timestamp: "2021-09-25T13:32:42Z", - version: 1, - changeset: 111688164, - user: "TeamP8", - uid: 718373, - tags: { amenity: "bench" }, - }, - { - type: "node", - id: 9425818876, - lat: 51.1325315, - lon: 3.3371616, - timestamp: "2022-01-28T20:13:29Z", - version: 3, - changeset: 116721474, - user: "L'imaginaire", - uid: 654234, - tags: { leisure: "picnic_table", mapillary: "101961548889238" }, - }, - { - type: "way", - id: 149408639, - timestamp: "2012-08-15T19:09:59Z", - version: 4, - changeset: 12742790, - user: "tomvdb", - uid: 437764, - nodes: [1623924235, 1623924236, 1623924238, 1864750831, 1623924241, 1623924235], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 149553206, - timestamp: "2021-01-30T12:11:53Z", - version: 4, - changeset: 98411765, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 1625199938, 1625199951, 8377691836, 8378081366, 8378081429, 8378081386, - 1625199950, 6414383775, 1625199827, 1625199938, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 184402308, - timestamp: "2020-03-23T23:54:26Z", - version: 6, - changeset: 82544029, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [1948836195, 1948836194, 1948836193, 1948836189, 1948836192, 1948836195], - tags: { - building: "yes", - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "yes", - wheelchair: "yes", - }, - }, - { - type: "way", - id: 184402309, - timestamp: "2021-12-20T11:33:54Z", - version: 8, - changeset: 115161990, - user: "s8evq", - uid: 3710738, - nodes: [1948836029, 1948836038, 1948836032, 1948836025, 1948836023, 1948836029], - tags: { - building: "yes", - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "yes", - "source:geometry:date": "2011-11-07", - "source:geometry:ref": "Gbg/3489204", - }, - }, - { - type: "way", - id: 184402331, - timestamp: "2021-02-26T10:03:17Z", - version: 4, - changeset: 100039746, - user: "s8evq", - uid: 3710738, - nodes: [1948836104, 1948836072, 1948836068, 1948836093, 1948836104], - tags: { access: "yes", amenity: "parking", fee: "no", parking: "surface" }, - }, - { - type: "way", - id: 236979353, - timestamp: "2021-01-25T12:38:22Z", - version: 4, - changeset: 98122705, - user: "JosV", - uid: 170722, - nodes: [ - 2449323191, 7962681624, 7962681623, 7962681621, 7962681622, 2449323193, - 7962681620, 7962681619, 8360787098, 4350143592, 6794421028, 6794421027, - 6794421041, 7962681614, 2123461969, 2449323198, 7962681615, 7962681616, - 6794421042, 2449323191, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 251590503, - timestamp: "2019-04-20T22:04:54Z", - version: 2, - changeset: 69412561, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 2577910543, 2577910530, 2577910542, 2577910520, 6418533335, 2577910526, - 2577910545, 2577910543, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 313090489, - timestamp: "2014-11-16T14:59:12Z", - version: 1, - changeset: 26823403, - user: "JanFi", - uid: 672253, - nodes: [ - 3190107423, 3190107435, 3190107442, 3190107439, 3190107432, 3190107428, - 3190107424, 3190107400, 3190107393, 3190107377, 3190107371, 3190107374, - 3190107408, 3190107407, 3190107415, 3190107416, 3190107420, 3190107423, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 314531418, - timestamp: "2021-05-30T17:02:49Z", - version: 21, - changeset: 105576255, - user: "s8evq", - uid: 3710738, - nodes: [ - 7468171455, 7468171451, 7727393212, 7727393211, 7727393210, 7727393208, - 7727393209, 8781449489, 7727393207, 7727393206, 7727393205, 7727393204, - 7727393203, 7727393202, 7727393201, 7727393200, 7390697550, 7390697534, - 7727393199, 7727393198, 7727393197, 7390697549, 7727393196, 7727393195, - 7727393194, 7727393193, 7727393192, 7727393191, 7727393190, 7727393189, - 7727393188, 7727393187, 7727393186, 7727393185, 7727339384, 7727339383, - 7727339382, 1553169911, 1553169836, 1493821433, 1493821422, 3185248088, - 7727339364, 7727339365, 7727339366, 7727339367, 7727339368, 7727339369, - 7727339370, 7727339371, 7727339372, 7727339373, 7727339374, 7727339375, - 7727339376, 7727339377, 3185248049, 3185248048, 3185248042, 3185248040, - 7727339378, 7727339379, 7727339380, 7727339381, 7468171438, 7468171442, - 7468171430, 7468171432, 7468171446, 7468171404, 7468171405, 7468171422, - 7468171426, 7468171433, 7468171423, 7468171428, 7468171431, 7468171429, - 7468171406, 7468171407, 7468171444, 7468171408, 7468171409, 7468171410, - 7468171411, 7468171412, 7468171413, 7190927792, 7190927791, 7190927793, - 7190927787, 7190927788, 7190927789, 7190927790, 7190927786, 7602692242, - 7190927785, 7190873584, 7468171450, 7190873582, 7190873576, 7190873578, - 7190873577, 7468171455, - ], - tags: { - access: "yes", - dog: "leashed", - dogs: "leashed", - image: "https://i.imgur.com/cOfwWTj.jpg", - "image:0": "https://i.imgur.com/RliQdyi.jpg", - "image:1": "https://i.imgur.com/IeKHahz.jpg", - "image:2": "https://i.imgur.com/1K0IORH.jpg", - "image:3": "https://i.imgur.com/jojP09s.jpg", - "image:4": "https://i.imgur.com/DK6kT51.jpg", - "image:5": "https://i.imgur.com/RizbGM1.jpg", - "image:6": "https://i.imgur.com/hyoY6Cl.jpg", - "image:7": "https://i.imgur.com/xDd7Wrq.jpg", - leisure: "nature_reserve", - name: "Miseriebocht", - operator: "Natuurpunt Beernem", - website: "https://www.natuurpunt.be/natuurgebied/miseriebocht", - wikidata: "Q97060915", - }, - }, - { - type: "way", - id: 366318480, - timestamp: "2015-08-18T13:50:59Z", - version: 1, - changeset: 33415758, - user: "xras3r", - uid: 323672, - nodes: [3702926557, 3702926558, 3702926559, 3702926560, 3702926557], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 366318481, - timestamp: "2015-08-18T13:50:59Z", - version: 1, - changeset: 33415758, - user: "xras3r", - uid: 323672, - nodes: [ - 3702878648, 3702926561, 3702926562, 3702926563, 3702926564, 3702926565, - 3702926566, 3702926567, 3702878648, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 366318482, - timestamp: "2021-01-05T09:04:32Z", - version: 2, - changeset: 96964072, - user: "JosV", - uid: 170722, - nodes: [3702926568, 8292789053, 8292789054, 3702878654, 3702926568], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 366320440, - timestamp: "2015-08-18T14:02:00Z", - version: 1, - changeset: 33415981, - user: "xras3r", - uid: 323672, - nodes: [3702956173, 3702956174, 3702956175, 3702956176, 3702956173], - tags: { amenity: "parking", name: "Kleine Beer" }, - }, - { - type: "way", - id: 366321706, - timestamp: "2015-08-18T14:15:30Z", - version: 1, - changeset: 33416303, - user: "xras3r", - uid: 323672, - nodes: [3702969714, 3702969715, 3702969716, 3702969717, 3702969714], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 480267681, - timestamp: "2021-01-05T21:32:23Z", - version: 2, - changeset: 97006454, - user: "JosV", - uid: 170722, - nodes: [ - 4732689641, 4732689640, 4732689639, 4732689638, 4732689637, 4732689636, - 4732689635, 4732689634, 4732689633, 4732689632, 4732689631, 4732689630, - 4732689629, 4732689628, 4732689627, 4732689626, 8294875888, 4732689641, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 554341620, - timestamp: "2018-01-19T21:31:30Z", - version: 1, - changeset: 55589374, - user: "L'imaginaire", - uid: 654234, - nodes: [5349884603, 5349884602, 5349884601, 5349884600, 5349884603], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 554341621, - timestamp: "2018-01-19T21:31:30Z", - version: 1, - changeset: 55589374, - user: "L'imaginaire", - uid: 654234, - nodes: [5349884607, 5349884606, 5349884605, 5349884604, 5349884607], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 561902092, - timestamp: "2021-01-17T14:52:46Z", - version: 6, - changeset: 97640804, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [5417516023, 5417515520, 5417516021, 5417516022, 5417516023], - tags: { - building: "yes", - image: "https://i.imgur.com/WmViSbL.jpg", - leisure: "bird_hide", - operator: "Natuurpunt Vallei van de Zuidleie", - shelter: "yes", - wheelchair: "no", - }, - }, - { - type: "way", - id: 605915064, - timestamp: "2018-07-07T18:07:31Z", - version: 1, - changeset: 60494380, - user: "Pieter Vander Vennet", - uid: 3818858, - nodes: [5745739924, 5745739925, 5745739926, 5745739927, 5745739924], - tags: { amenity: "parking", surface: "fine2" }, - }, - { - type: "way", - id: 650285088, - timestamp: "2021-01-29T09:37:56Z", - version: 2, - changeset: 98352073, - user: "JosV", - uid: 170722, - nodes: [ - 3645188881, 6100803131, 6100803130, 6100803129, 6100803124, 6100803125, - 6100803128, 6100803127, 6100803126, 3645188881, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 655944574, - timestamp: "2020-02-23T22:05:54Z", - version: 2, - changeset: 81377451, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 6145151107, 6145151108, 6145151109, 6145151115, 6145151114, 6145151110, - 6145151107, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 664171069, - timestamp: "2019-01-17T16:29:08Z", - version: 1, - changeset: 66400781, - user: "Nilsnn", - uid: 4652000, - nodes: [ - 6216549610, 6216549611, 6216549612, 6216549613, 1413470849, 1413470848, - 6216549605, 6216549604, 6216549610, - ], - tags: { amenity: "parking" }, - }, - { - type: "way", - id: 664171076, - timestamp: "2021-01-09T21:56:37Z", - version: 3, - changeset: 97230316, - user: "JosV", - uid: 170722, - nodes: [ - 6216549656, 6216549655, 8307316294, 6216549661, 6216549657, 6216549658, - 6216549659, 6216549660, 6216549656, - ], - tags: { amenity: "parking", capacity: "50" }, - }, - { - type: "way", - id: 665330334, - timestamp: "2019-01-22T14:20:51Z", - version: 1, - changeset: 66539354, - user: "Nilsnn", - uid: 4652000, - nodes: [6227395993, 6227395991, 6227395992, 6227395997, 6227395993], - tags: { - access: "yes", - amenity: "parking", - fee: "no", - parking: "surface", - surface: "asphalt", - }, - }, - { - type: "way", - id: 684598363, - timestamp: "2020-03-07T14:21:22Z", - version: 3, - changeset: 81900556, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 3227068565, 6416001161, 6414352054, 6414352055, 7274233390, 7274233391, - 7274233397, 7274233395, 7274233396, 6414352053, 3227068568, 3227068565, - ], - tags: { amenity: "parking", fee: "no" }, - }, - { - type: "way", - id: 684599810, - timestamp: "2020-12-31T20:58:28Z", - version: 3, - changeset: 96751036, - user: "JosV", - uid: 170722, - nodes: [ - 1317838331, 8279842668, 1384096112, 1317838328, 6414374315, 3227068446, - 6414374316, 6414374317, 6414374318, 3227068456, 6414374319, 6414374320, - 6414374321, 1317838317, 1317838331, - ], - tags: { access: "no", amenity: "parking", operator: "Politie" }, - }, - { - type: "way", - id: 761474468, - timestamp: "2021-01-05T21:32:23Z", - version: 3, - changeset: 97006454, - user: "JosV", - uid: 170722, - nodes: [ - 7114502201, 7114502203, 7114502200, 7114502202, 3170562439, 3170562437, - 3170562431, 7114502240, 7114502211, 7114502212, 7114502214, 7114502215, - 7114502228, 7114502234, 7114502235, 7114502236, 7114502237, 7114502238, - 7114502239, 7114502233, 7114502232, 7114502231, 7114502229, 7114502230, - 7114502227, 7114502226, 7114502225, 7114502216, 7114502217, 7114502224, - 3170562392, 7114502218, 3170562394, 7114502219, 7114502220, 7114502221, - 7114502222, 7114502223, 3170562395, 3170562396, 3170562397, 3170562402, - 3170562410, 7114502209, 7114502208, 7114502207, 7114502205, 7114502206, - 3170562436, 1475188519, 1475188516, 6627605025, 8294886142, 7114502201, - ], - tags: { - image: "http://valleivandezuidleie.be/wp-content/uploads/2011/12/2011-03-24_G12_088_1_1.JPG", - leisure: "nature_reserve", - name: "Merlebeek-Meerberg", - natural: "wetland", - operator: "Natuurpunt Vallei van de Zuidleie", - start_date: "2011", - website: - "http://valleivandezuidleie.be/info-over-de-vallei-van-de-zuidleie/merlebeek/", - wetland: "wet_meadow", - }, - }, - { - type: "way", - id: 813859435, - timestamp: "2021-02-26T10:18:26Z", - version: 3, - changeset: 100040879, - user: "s8evq", - uid: 3710738, - nodes: [ - 7602479690, 7459257985, 7602479691, 7459154784, 7459154782, 7602479692, - 5482441357, 7602479693, 7602479694, 7602479695, 7602479696, 7602479690, - ], - tags: { - access: "yes", - "image:0": "https://i.imgur.com/nb9nawa.jpg", - landuse: "grass", - leisure: "nature_reserve", - "name:signed": "no", - natural: "grass", - operator: "Natuurpunt Vallei van de Zuidleie", - }, - }, - { - type: "way", - id: 826103452, - timestamp: "2020-07-14T09:05:14Z", - version: 1, - changeset: 87965884, - user: "L'imaginaire", - uid: 654234, - nodes: [7713176912, 7713176911, 7713176910, 7713176909, 7713176912], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 893176022, - timestamp: "2021-01-07T23:02:07Z", - version: 1, - changeset: 97133621, - user: "JosV", - uid: 170722, - nodes: [1927235214, 8301349336, 8301349335, 8301349337, 1927235214], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943476, - timestamp: "2021-01-16T17:56:50Z", - version: 1, - changeset: 97614669, - user: "JosV", - uid: 170722, - nodes: [8328251887, 8328251886, 8328251885, 8328251884, 8328251887], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943477, - timestamp: "2021-01-16T17:56:50Z", - version: 1, - changeset: 97614669, - user: "JosV", - uid: 170722, - nodes: [8328251891, 8328251890, 8328251889, 8328251888, 8328251891], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943478, - timestamp: "2021-01-16T17:56:50Z", - version: 1, - changeset: 97614669, - user: "JosV", - uid: 170722, - nodes: [8328251895, 8328251894, 8328251893, 8328251892, 8328251895], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943479, - timestamp: "2021-01-16T17:56:50Z", - version: 1, - changeset: 97614669, - user: "JosV", - uid: 170722, - nodes: [8328251897, 8328251896, 8328251901, 8328251900, 8328251897], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943480, - timestamp: "2021-01-16T17:56:50Z", - version: 1, - changeset: 97614669, - user: "JosV", - uid: 170722, - nodes: [8328251898, 8328251899, 8328251903, 8328251902, 8328251898], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943481, - timestamp: "2021-01-16T17:56:50Z", - version: 1, - changeset: 97614669, - user: "JosV", - uid: 170722, - nodes: [8328251907, 8328251906, 8328251905, 8328251904, 8328251907], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 895943482, - timestamp: "2021-01-16T17:56:50Z", - version: 1, - changeset: 97614669, - user: "JosV", - uid: 170722, - nodes: [8328251911, 8328251910, 8328251909, 8328251908, 8328251911], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 901952767, - timestamp: "2021-01-30T12:19:15Z", - version: 1, - changeset: 98412028, - user: "L'imaginaire", - uid: 654234, - nodes: [8378097127, 8378097126, 8378097125, 8378097124, 8378097127], - tags: { amenity: "parking", parking: "street_side" }, - }, - { - type: "way", - id: 901952768, - timestamp: "2021-01-30T12:19:15Z", - version: 1, - changeset: 98412028, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 8378097134, 8378097133, 8378097132, 8378097131, 8378097130, 8378097129, - 8378097128, 8378097134, - ], - tags: { amenity: "parking", parking: "street_side" }, - }, - { - type: "way", - id: 947325182, - timestamp: "2021-05-26T15:16:06Z", - version: 1, - changeset: 105366812, - user: "L'imaginaire", - uid: 654234, - nodes: [ - 8497007549, 8768981525, 8768981522, 8768981524, 8768981521, 8768981523, - 8768981520, 8768981519, 6206789709, 8768981533, 8497007549, - ], - tags: { amenity: "parking", parking: "surface" }, - }, - { - type: "way", - id: 966827815, - timestamp: "2021-07-23T20:01:09Z", - version: 1, - changeset: 108511361, - user: "L'imaginaire", - uid: 654234, - nodes: [8945026757, 8945026756, 8945026755, 8945026754, 8945026757], - tags: { access: "customers", amenity: "parking", parking: "surface" }, - }, - { - type: "relation", - id: 2589413, - timestamp: "2021-03-06T15:13:01Z", - version: 7, - changeset: 100540347, - user: "Pieter Vander Vennet", - uid: 3818858, - members: [ - { type: "way", ref: 663050030, role: "outer" }, - { type: "way", ref: 184402334, role: "outer" }, - { type: "way", ref: 184402332, role: "outer" }, - { type: "way", ref: 184402325, role: "outer" }, - { type: "way", ref: 184402326, role: "outer" }, - { type: "way", ref: 314899865, role: "outer" }, - { type: "way", ref: 314956402, role: "outer" }, - ], - tags: { - access: "yes", - image: "https://i.imgur.com/Yu4qHh5.jpg", - leisure: "nature_reserve", - name: "Warandeputten", - operator: "Natuurpunt Vallei van de Zuidleie", - type: "multipolygon", - wikipedia: "nl:Warandeputten", - }, - }, - { - type: "relation", - id: 8782624, - timestamp: "2021-03-08T13:15:15Z", - version: 14, - changeset: 100640534, - user: "M!dgard", - uid: 763799, - members: [ - { type: "way", ref: 315041273, role: "outer" }, - { type: "way", ref: 631377343, role: "outer" }, - { type: "way", ref: 631371237, role: "outer" }, - { type: "way", ref: 631371236, role: "outer" }, - { type: "way", ref: 631371234, role: "outer" }, - { type: "way", ref: 631377344, role: "outer" }, - { type: "way", ref: 631371232, role: "outer" }, - { type: "way", ref: 631371231, role: "outer" }, - { type: "way", ref: 315041263, role: "outer" }, - { type: "way", ref: 631371228, role: "outer" }, - { type: "way", ref: 631377341, role: "outer" }, - { type: "way", ref: 315041261, role: "outer" }, - { type: "way", ref: 631371223, role: "outer" }, - ], - tags: { - access: "yes", - "image:0": "https://i.imgur.com/VuzX5jW.jpg", - "image:1": "https://i.imgur.com/tPppmJG.jpg", - "image:2": "https://i.imgur.com/ecY3RER.jpg", - "image:3": "https://i.imgur.com/lr4FK6j.jpg", - "image:5": "https://i.imgur.com/uufEeE6.jpg", - leisure: "nature_reserve", - name: "Leiemeersen Noord", - operator: "Natuurpunt Vallei van de Zuidleie", - type: "multipolygon", - website: - "http://valleivandezuidleie.be/info-over-de-vallei-van-de-zuidleie/leiemeersen-noord/", - }, - }, - { - type: "relation", - id: 8890076, - timestamp: "2020-07-18T15:56:50Z", - version: 6, - changeset: 88179905, - user: "Pieter Vander Vennet", - uid: 3818858, - members: [ - { type: "way", ref: 640979982, role: "outer" }, - { type: "way", ref: 640979978, role: "outer" }, - ], - tags: { - access: "yes", - "image:0": "https://i.imgur.com/SAAaKBH.jpg", - "image:1": "https://i.imgur.com/DGK9iBN.jpg", - "image:2": "https://i.imgur.com/bte1KJx.jpg", - "image:3": "https://i.imgur.com/f75Gxnx.jpg", - leisure: "nature_reserve", - name: "Gevaerts Noord", - operator: "Natuurpunt Vallei van de Zuidleie", - type: "multipolygon", - }, - }, - { - type: "relation", - id: 9118029, - timestamp: "2020-07-11T12:04:16Z", - version: 4, - changeset: 87852045, - user: "Pieter Vander Vennet", - uid: 3818858, - members: [ - { type: "way", ref: 606165130, role: "outer" }, - { type: "way", ref: 655652319, role: "outer" }, - { type: "way", ref: 655652321, role: "outer" }, - ], - tags: { - access: "no", - "image:0": "https://i.imgur.com/eBufo0v.jpg", - "image:1": "https://i.imgur.com/kBej2Nk.jpg", - "image:2": "https://i.imgur.com/QKoyIRl.jpg", - leisure: "nature_reserve", - name: "De Leiemeersen", - note: "Door de hoge kwetsbaarheid van het gebied zijn De Leiemeersen enkel te bezoeken onder begeleiding van een gids", - "note:mapping": - "NIET VOOR BEGINNENDE MAPPERS! Dit gebied is met relaties als multipolygonen gemapt (zo'n 50 stuks). Als je niet weet hoe dit werkt, vraag hulp.", - note_1: "wetland=marsh is het veenmoerasgebied", - operator: "Natuurpunt Vallei van de Zuidleie", - type: "multipolygon", - website: - "http://valleivandezuidleie.be/info-over-de-vallei-van-de-zuidleie/leiemeersen/", - }, - }, - { type: "node", id: 7114502229, lat: 51.1340508, lon: 3.2949303 }, - { type: "node", id: 7114502231, lat: 51.1340428, lon: 3.2959613 }, - { type: "node", id: 7114502232, lat: 51.134096, lon: 3.2971973 }, - { type: "node", id: 1927235214, lat: 51.1267391, lon: 3.3222921 }, - { type: "node", id: 8301349336, lat: 51.1266966, lon: 3.3223356 }, - { type: "node", id: 1413470848, lat: 51.1276558, lon: 3.3278194 }, - { type: "node", id: 1413470849, lat: 51.1276852, lon: 3.3274627 }, - { type: "node", id: 6216549605, lat: 51.1276578, lon: 3.3279376 }, - { type: "node", id: 6216549611, lat: 51.1277354, lon: 3.3273608 }, - { type: "node", id: 6216549612, lat: 51.1277281, lon: 3.3274215 }, - { type: "node", id: 6216549613, lat: 51.1277062, lon: 3.3274249 }, - { type: "node", id: 8301349335, lat: 51.1269096, lon: 3.3228882 }, - { type: "node", id: 8301349337, lat: 51.126955, lon: 3.3228466 }, - { type: "node", id: 8328251884, lat: 51.1278701, lon: 3.3253392 }, - { type: "node", id: 8328251885, lat: 51.127857, lon: 3.3254078 }, - { type: "node", id: 8328251888, lat: 51.1278797, lon: 3.325168 }, - { type: "node", id: 8328251889, lat: 51.1278665, lon: 3.3252369 }, - { type: "node", id: 8328251892, lat: 51.1271569, lon: 3.3248958 }, - { type: "node", id: 8328251893, lat: 51.1272208, lon: 3.3252329 }, - { type: "node", id: 8328251894, lat: 51.1272677, lon: 3.3252111 }, - { type: "node", id: 8328251895, lat: 51.1272033, lon: 3.3248728 }, - { type: "node", id: 8328251896, lat: 51.1271015, lon: 3.3257575 }, - { type: "node", id: 8328251897, lat: 51.1270872, lon: 3.3256877 }, - { type: "node", id: 8328251898, lat: 51.1271271, lon: 3.325744 }, - { type: "node", id: 8328251899, lat: 51.1271128, lon: 3.3256743 }, - { type: "node", id: 8328251900, lat: 51.1270462, lon: 3.3257067 }, - { type: "node", id: 8328251901, lat: 51.1270609, lon: 3.3257756 }, - { type: "node", id: 8328251902, lat: 51.1272095, lon: 3.3257043 }, - { type: "node", id: 8328251903, lat: 51.1271957, lon: 3.3256351 }, - { type: "node", id: 8328251904, lat: 51.1269631, lon: 3.3253915 }, - { type: "node", id: 8328251905, lat: 51.1269756, lon: 3.3254617 }, - { type: "node", id: 8328251906, lat: 51.1271678, lon: 3.3253747 }, - { type: "node", id: 8328251907, lat: 51.1271557, lon: 3.3253045 }, - { type: "node", id: 8328251908, lat: 51.1270115, lon: 3.3255467 }, - { type: "node", id: 8328251909, lat: 51.1270235, lon: 3.3256138 }, - { type: "node", id: 8328251910, lat: 51.1271099, lon: 3.3255749 }, - { type: "node", id: 8328251911, lat: 51.1270975, lon: 3.3255072 }, - { type: "node", id: 2449323191, lat: 51.1340437, lon: 3.3216558 }, - { type: "node", id: 2449323193, lat: 51.134285, lon: 3.3221086 }, - { type: "node", id: 2449323198, lat: 51.1334724, lon: 3.3223472 }, - { type: "node", id: 4350143592, lat: 51.1344661, lon: 3.3226292 }, - { type: "node", id: 4732689626, lat: 51.1301038, lon: 3.3223672 }, - { type: "node", id: 4732689627, lat: 51.1301351, lon: 3.3223533 }, - { type: "node", id: 4732689628, lat: 51.130161, lon: 3.3224805 }, - { type: "node", id: 4732689629, lat: 51.1301379, lon: 3.3224524 }, - { type: "node", id: 4732689630, lat: 51.130151, lon: 3.3225204 }, - { type: "node", id: 4732689631, lat: 51.1301375, lon: 3.3225274 }, - { type: "node", id: 4732689632, lat: 51.1301505, lon: 3.322593 }, - { type: "node", id: 4732689634, lat: 51.1297694, lon: 3.3223954 }, - { type: "node", id: 4732689635, lat: 51.1298034, lon: 3.3223641 }, - { type: "node", id: 4732689636, lat: 51.1297546, lon: 3.3221883 }, - { type: "node", id: 4732689637, lat: 51.1297088, lon: 3.3219708 }, - { type: "node", id: 4732689638, lat: 51.1297683, lon: 3.3219402 }, - { type: "node", id: 4732689639, lat: 51.1297781, lon: 3.3219034 }, - { type: "node", id: 4732689640, lat: 51.1297694, lon: 3.3218478 }, - { type: "node", id: 4732689641, lat: 51.1300088, lon: 3.32173 }, - { type: "node", id: 6794421027, lat: 51.1341852, lon: 3.3223337 }, - { type: "node", id: 6794421042, lat: 51.1336271, lon: 3.3220973 }, - { type: "node", id: 7962681614, lat: 51.1336656, lon: 3.322624 }, - { type: "node", id: 7962681615, lat: 51.1335301, lon: 3.322287 }, - { type: "node", id: 7962681616, lat: 51.1335065, lon: 3.3222317 }, - { type: "node", id: 7962681619, lat: 51.134306, lon: 3.3222136 }, - { type: "node", id: 7962681620, lat: 51.1343186, lon: 3.3221995 }, - { type: "node", id: 7962681621, lat: 51.1341453, lon: 3.321775 }, - { type: "node", id: 7962681622, lat: 51.1341537, lon: 3.321769 }, - { type: "node", id: 7962681623, lat: 51.1341067, lon: 3.3216811 }, - { type: "node", id: 7962681624, lat: 51.1340675, lon: 3.3217193 }, - { type: "node", id: 8294875888, lat: 51.1300197, lon: 3.3219403 }, - { type: "node", id: 8360787098, lat: 51.1344583, lon: 3.3225918 }, - { type: "node", id: 2123461969, lat: 51.1336123, lon: 3.3226786 }, - { type: "node", id: 4732689633, lat: 51.1298776, lon: 3.3227393 }, - { type: "node", id: 6216549604, lat: 51.1280095, lon: 3.328024 }, - { type: "node", id: 6216549610, lat: 51.128134, lon: 3.3274542 }, - { type: "node", id: 6794421028, lat: 51.1343476, lon: 3.3227429 }, - { type: "node", id: 6794421041, lat: 51.1337382, lon: 3.3227794 }, - { type: "node", id: 8328251886, lat: 51.128048, lon: 3.3255015 }, - { type: "node", id: 8328251887, lat: 51.1280616, lon: 3.3254329 }, - { type: "node", id: 8328251890, lat: 51.1280791, lon: 3.3253407 }, - { type: "node", id: 8328251891, lat: 51.1280922, lon: 3.3252721 }, - { type: "node", id: 1623924235, lat: 51.131361, lon: 3.333018 }, - { type: "node", id: 1623924236, lat: 51.1311039, lon: 3.3325764 }, - { type: "node", id: 1623924238, lat: 51.1310489, lon: 3.3326527 }, - { type: "node", id: 1623924241, lat: 51.1314273, lon: 3.3331479 }, - { type: "node", id: 1864750831, lat: 51.1313615, lon: 3.3332222 }, - { type: "node", id: 6216549655, lat: 51.134416, lon: 3.3347284 }, - { type: "node", id: 6216549656, lat: 51.1343187, lon: 3.3349542 }, - { type: "node", id: 6216549657, lat: 51.1334196, lon: 3.335723 }, - { type: "node", id: 6216549658, lat: 51.1334902, lon: 3.3357377 }, - { type: "node", id: 6216549659, lat: 51.133716, lon: 3.335546 }, - { type: "node", id: 6216549660, lat: 51.133883, lon: 3.3353885 }, - { type: "node", id: 6216549661, lat: 51.133662, lon: 3.3355049 }, - { type: "node", id: 8307316294, lat: 51.1338503, lon: 3.3353095 }, - { type: "node", id: 1493821422, lat: 51.1320567, lon: 3.3398517 }, - { type: "node", id: 1493821433, lat: 51.1316132, lon: 3.3408892 }, - { type: "node", id: 1553169836, lat: 51.1311998, lon: 3.3415993 }, - { type: "node", id: 3185248088, lat: 51.1323359, lon: 3.3389757 }, - { type: "node", id: 7727339364, lat: 51.1321819, lon: 3.3388758 }, - { type: "node", id: 7727339365, lat: 51.1319823, lon: 3.339559 }, - { type: "node", id: 7727339366, lat: 51.1316011, lon: 3.3405485 }, - { type: "node", id: 7727339367, lat: 51.1312764, lon: 3.3411848 }, - { type: "node", id: 7727339368, lat: 51.1310889, lon: 3.3414676 }, - { type: "node", id: 7727339369, lat: 51.1304838, lon: 3.3422395 }, - { type: "node", id: 3185248048, lat: 51.1268693, lon: 3.3480331 }, - { type: "node", id: 3185248049, lat: 51.1269831, lon: 3.3475504 }, - { type: "node", id: 7727339376, lat: 51.1274147, lon: 3.3464515 }, - { type: "node", id: 7727339377, lat: 51.1271283, lon: 3.3469817 }, - { type: "node", id: 3185248040, lat: 51.1266778, lon: 3.3491476 }, - { type: "node", id: 3185248042, lat: 51.126712, lon: 3.3489485 }, - { type: "node", id: 7468171404, lat: 51.1277325, lon: 3.3553578 }, - { type: "node", id: 7468171430, lat: 51.1270326, lon: 3.353594 }, - { type: "node", id: 7468171432, lat: 51.1271802, lon: 3.3540454 }, - { type: "node", id: 7468171438, lat: 51.1268064, lon: 3.3526001 }, - { type: "node", id: 7468171442, lat: 51.1268303, lon: 3.3527746 }, - { type: "node", id: 7468171446, lat: 51.1273659, lon: 3.3546333 }, - { type: "node", id: 7727339378, lat: 51.1265656, lon: 3.3505324 }, - { type: "node", id: 7727339379, lat: 51.1266463, lon: 3.350873 }, - { type: "node", id: 7727339380, lat: 51.1266302, lon: 3.3514032 }, - { type: "node", id: 7727339381, lat: 51.1267774, lon: 3.3523929 }, - { type: "node", id: 7727393190, lat: 51.1276264, lon: 3.3489611 }, - { type: "node", id: 7727393191, lat: 51.1274853, lon: 3.3497484 }, - { type: "node", id: 7727393192, lat: 51.127332, lon: 3.350571 }, - { type: "node", id: 7727393193, lat: 51.1273663, lon: 3.3514386 }, - { type: "node", id: 7727393194, lat: 51.1274853, lon: 3.3519206 }, - { type: "node", id: 7727393195, lat: 51.1276889, lon: 3.3531674 }, - { type: "node", id: 1553169911, lat: 51.1313314, lon: 3.3425771 }, - { type: "node", id: 7727339370, lat: 51.1300705, lon: 3.3427304 }, - { type: "node", id: 7727339371, lat: 51.1293083, lon: 3.343643 }, - { type: "node", id: 7727339372, lat: 51.1285642, lon: 3.3445074 }, - { type: "node", id: 7727339373, lat: 51.1285702, lon: 3.3445781 }, - { type: "node", id: 7727339374, lat: 51.1283181, lon: 3.3448801 }, - { type: "node", id: 7727339375, lat: 51.1281023, lon: 3.3451404 }, - { type: "node", id: 7727339382, lat: 51.1311196, lon: 3.3430952 }, - { type: "node", id: 7727339383, lat: 51.1309343, lon: 3.3434996 }, - { type: "node", id: 7727339384, lat: 51.1306234, lon: 3.343745 }, - { type: "node", id: 7727393185, lat: 51.1300873, lon: 3.3443449 }, - { type: "node", id: 7727393186, lat: 51.129401, lon: 3.3453846 }, - { type: "node", id: 7727393187, lat: 51.1290865, lon: 3.345963 }, - { type: "node", id: 7727393188, lat: 51.1285581, lon: 3.3468788 }, - { type: "node", id: 7727393189, lat: 51.1280217, lon: 3.3479457 }, - { type: "node", id: 7390697549, lat: 51.128429, lon: 3.354427 }, - { type: "node", id: 7727393196, lat: 51.1279813, lon: 3.353749 }, - { type: "node", id: 3209560114, lat: 51.1538874, lon: 3.2531858 }, - { type: "node", id: 3209560115, lat: 51.1539649, lon: 3.2531836 }, - { type: "node", id: 3209560116, lat: 51.1541197, lon: 3.2537986 }, - { type: "node", id: 3209560117, lat: 51.1541021, lon: 3.253149 }, - { type: "node", id: 3210389695, lat: 51.1539646, lon: 3.2534427 }, - { type: "node", id: 416917618, lat: 51.1565737, lon: 3.2549365 }, - { type: "node", id: 668981667, lat: 51.1576026, lon: 3.2555383 }, - { type: "node", id: 668981668, lat: 51.1564046, lon: 3.2547045 }, - { type: "node", id: 1815081998, lat: 51.1575578, lon: 3.2555439 }, - { type: "node", id: 1948835662, lat: 51.155957, lon: 3.2562889 }, - { type: "node", id: 1948835742, lat: 51.156182, lon: 3.2540291 }, - { type: "node", id: 1948835950, lat: 51.1591027, lon: 3.2559292 }, - { type: "node", id: 1948836096, lat: 51.1600831, lon: 3.2562989 }, - { type: "node", id: 1948836149, lat: 51.1605378, lon: 3.2568906 }, - { type: "node", id: 2026541837, lat: 51.1543652, lon: 3.2542901 }, - { type: "node", id: 2026541841, lat: 51.1545903, lon: 3.2546809 }, - { type: "node", id: 2026541843, lat: 51.1548384, lon: 3.2550342 }, - { type: "node", id: 2026541846, lat: 51.155086, lon: 3.2553429 }, - { type: "node", id: 2026541851, lat: 51.15565, lon: 3.2542965 }, - { type: "node", id: 3209560118, lat: 51.1542244, lon: 3.2530815 }, - { type: "node", id: 3209560119, lat: 51.1544741, lon: 3.252803 }, - { type: "node", id: 3209560121, lat: 51.1546028, lon: 3.2526667 }, - { type: "node", id: 3209560124, lat: 51.1547447, lon: 3.2525904 }, - { type: "node", id: 3209560126, lat: 51.1550296, lon: 3.2525253 }, - { type: "node", id: 3209560127, lat: 51.1551915, lon: 3.2525297 }, - { type: "node", id: 3209560131, lat: 51.1553436, lon: 3.2525627 }, - { type: "node", id: 3209560132, lat: 51.1554055, lon: 3.2526006 }, - { type: "node", id: 3209560134, lat: 51.155479, lon: 3.2526651 }, - { type: "node", id: 3209560135, lat: 51.155558, lon: 3.2527647 }, - { type: "node", id: 3209560136, lat: 51.1556482, lon: 3.2528805 }, - { type: "node", id: 3209560138, lat: 51.1559652, lon: 3.2534517 }, - { type: "node", id: 3209560139, lat: 51.1560327, lon: 3.2535705 }, - { type: "node", id: 5417515520, lat: 51.1545998, lon: 3.2545767 }, - { type: "node", id: 5417516021, lat: 51.1545476, lon: 3.2544989 }, - { type: "node", id: 5417516022, lat: 51.1545856, lon: 3.2544342 }, - { type: "node", id: 5417516023, lat: 51.1546378, lon: 3.2545119 }, - { type: "node", id: 6206789688, lat: 51.1553578, lon: 3.255668 }, - { type: "node", id: 416917603, lat: 51.1591104, lon: 3.2595563 }, - { type: "node", id: 416917605, lat: 51.1592829, lon: 3.2591123 }, - { type: "node", id: 416917609, lat: 51.1597764, lon: 3.2597849 }, - { type: "node", id: 1554514806, lat: 51.1586314, lon: 3.2636074 }, - { type: "node", id: 1948835842, lat: 51.1570345, lon: 3.2574053 }, - { type: "node", id: 1948835900, lat: 51.1583428, lon: 3.258761 }, - { type: "node", id: 1948835986, lat: 51.1595419, lon: 3.2593891 }, - { type: "node", id: 1948836023, lat: 51.1597284, lon: 3.2585312 }, - { type: "node", id: 1948836025, lat: 51.1597477, lon: 3.2585581 }, - { type: "node", id: 1948836029, lat: 51.1597506, lon: 3.2584887 }, - { type: "node", id: 1948836032, lat: 51.159778, lon: 3.2585905 }, - { type: "node", id: 1948836038, lat: 51.1597992, lon: 3.2585462 }, - { type: "node", id: 1948836068, lat: 51.1598265, lon: 3.2595499 }, - { type: "node", id: 1948836072, lat: 51.1598725, lon: 3.2596329 }, - { type: "node", id: 1948836093, lat: 51.1600505, lon: 3.2592835 }, - { type: "node", id: 1948836104, lat: 51.1601026, lon: 3.2593298 }, - { type: "node", id: 1948836189, lat: 51.1606847, lon: 3.2577063 }, - { type: "node", id: 5745739926, lat: 51.160615, lon: 3.2602336 }, - { type: "node", id: 5747937642, lat: 51.1580603, lon: 3.2636348 }, - { type: "node", id: 5962340003, lat: 51.1587851, lon: 3.2632739 }, - { type: "node", id: 5962414276, lat: 51.158949, lon: 3.2635109 }, - { type: "node", id: 5962415498, lat: 51.1589617, lon: 3.2635523 }, - { type: "node", id: 5962415499, lat: 51.1589623, lon: 3.2635339 }, - { type: "node", id: 5962415500, lat: 51.1589568, lon: 3.2635203 }, - { type: "node", id: 5962415538, lat: 51.1589195, lon: 3.2636407 }, - { type: "node", id: 6206789709, lat: 51.1545829, lon: 3.2585784 }, - { type: "node", id: 8497007481, lat: 51.1588349, lon: 3.2633671 }, - { type: "node", id: 8497007549, lat: 51.1548977, lon: 3.257909 }, - { type: "node", id: 8768981519, lat: 51.1545715, lon: 3.2585859 }, - { type: "node", id: 8768981520, lat: 51.1544691, lon: 3.2584642 }, - { type: "node", id: 8768981521, lat: 51.1543652, lon: 3.2584253 }, - { type: "node", id: 8768981522, lat: 51.1543648, lon: 3.2583401 }, - { type: "node", id: 8768981523, lat: 51.1544439, lon: 3.2585178 }, - { type: "node", id: 8768981524, lat: 51.1543892, lon: 3.258369 }, - { type: "node", id: 8768981525, lat: 51.1547185, lon: 3.2575482 }, - { type: "node", id: 8768981533, lat: 51.1545902, lon: 3.2585725 }, - { type: "node", id: 3162627482, lat: 51.1513402, lon: 3.2701364 }, - { type: "node", id: 3162627509, lat: 51.1517878, lon: 3.2696503 }, - { type: "node", id: 5745963944, lat: 51.1524768, lon: 3.2700312 }, - { type: "node", id: 5745963946, lat: 51.1519527, lon: 3.2698278 }, - { type: "node", id: 5745963947, lat: 51.1513495, lon: 3.2697903 }, - { type: "node", id: 5747937657, lat: 51.1538472, lon: 3.269262 }, - { type: "node", id: 5747937658, lat: 51.1536592, lon: 3.2698471 }, - { type: "node", id: 5747937668, lat: 51.1514068, lon: 3.2699149 }, - { type: "node", id: 5747937669, lat: 51.1514278, lon: 3.26999 }, - { type: "node", id: 5747937670, lat: 51.1514329, lon: 3.2701134 }, - { type: "node", id: 5747937671, lat: 51.1514387, lon: 3.2702207 }, - { type: "node", id: 5747937672, lat: 51.1515532, lon: 3.2702006 }, - { type: "node", id: 5747937673, lat: 51.1515986, lon: 3.2701456 }, - { type: "node", id: 5747937674, lat: 51.151872, lon: 3.2701201 }, - { type: "node", id: 6142725039, lat: 51.1513841, lon: 3.2701799 }, - { type: "node", id: 6142725060, lat: 51.1499168, lon: 3.2700268 }, - { type: "node", id: 6142725061, lat: 51.1497438, lon: 3.2697269 }, - { type: "node", id: 6142725064, lat: 51.1499256, lon: 3.2692437 }, - { type: "node", id: 6142727594, lat: 51.1500369, lon: 3.2694178 }, - { type: "node", id: 6142727597, lat: 51.1513713, lon: 3.2701053 }, - { type: "node", id: 6143075014, lat: 51.1512852, lon: 3.2702041 }, - { type: "node", id: 1554514655, lat: 51.1533288, lon: 3.2766885 }, - { type: "node", id: 1554514811, lat: 51.1537898, lon: 3.2754789 }, - { type: "node", id: 3211247019, lat: 51.1538788, lon: 3.2763248 }, - { type: "node", id: 3211247021, lat: 51.1538471, lon: 3.2764673 }, - { type: "node", id: 5745963922, lat: 51.1499683, lon: 3.2708447 }, - { type: "node", id: 5745963942, lat: 51.1530892, lon: 3.2704581 }, - { type: "node", id: 5745963943, lat: 51.1529125, lon: 3.2703349 }, - { type: "node", id: 5747535101, lat: 51.1533021, lon: 3.2705247 }, - { type: "node", id: 5747937659, lat: 51.1534063, lon: 3.2705828 }, - { type: "node", id: 5747937675, lat: 51.1521092, lon: 3.2702998 }, - { type: "node", id: 5747937676, lat: 51.1524591, lon: 3.2704822 }, - { type: "node", id: 5747937677, lat: 51.1527839, lon: 3.2706727 }, - { type: "node", id: 5747937678, lat: 51.1529841, lon: 3.2707276 }, - { type: "node", id: 5747937679, lat: 51.15326, lon: 3.2708913 }, - { type: "node", id: 5747937680, lat: 51.1535477, lon: 3.2712989 }, - { type: "node", id: 5747937681, lat: 51.1536831, lon: 3.2717402 }, - { type: "node", id: 5747937682, lat: 51.1536839, lon: 3.2721747 }, - { type: "node", id: 5747937683, lat: 51.1536469, lon: 3.2724161 }, - { type: "node", id: 5747937684, lat: 51.1534635, lon: 3.2730652 }, - { type: "node", id: 5747937685, lat: 51.1540625, lon: 3.2735735 }, - { type: "node", id: 5962339921, lat: 51.1535454, lon: 3.2761201 }, - { type: "node", id: 5962496725, lat: 51.1538672, lon: 3.276374 }, - { type: "node", id: 6142725040, lat: 51.1514014, lon: 3.2704388 }, - { type: "node", id: 6142725041, lat: 51.1514255, lon: 3.2722713 }, - { type: "node", id: 6142725042, lat: 51.1514044, lon: 3.2722821 }, - { type: "node", id: 6142725043, lat: 51.1513617, lon: 3.2722639 }, - { type: "node", id: 6142725044, lat: 51.1513141, lon: 3.2722165 }, - { type: "node", id: 6142725045, lat: 51.1512346, lon: 3.2721372 }, - { type: "node", id: 6142725046, lat: 51.1511314, lon: 3.2720304 }, - { type: "node", id: 6142725047, lat: 51.1509926, lon: 3.2718816 }, - { type: "node", id: 6142725048, lat: 51.1508222, lon: 3.2716946 }, - { type: "node", id: 6142725049, lat: 51.1507329, lon: 3.2715909 }, - { type: "node", id: 6142725050, lat: 51.1506561, lon: 3.2714915 }, - { type: "node", id: 6142725051, lat: 51.1506027, lon: 3.2714102 }, - { type: "node", id: 6142725052, lat: 51.1505293, lon: 3.2712712 }, - { type: "node", id: 6142725053, lat: 51.1504912, lon: 3.2711826 }, - { type: "node", id: 6142725054, lat: 51.1504464, lon: 3.2710615 }, - { type: "node", id: 6142725055, lat: 51.1503588, lon: 3.2707712 }, - { type: "node", id: 6142725056, lat: 51.1503432, lon: 3.2707336 }, - { type: "node", id: 6142725057, lat: 51.1503035, lon: 3.2706647 }, - { type: "node", id: 6142725058, lat: 51.1501763, lon: 3.2704663 }, - { type: "node", id: 6142725059, lat: 51.1500465, lon: 3.270256 }, - { type: "node", id: 6142725065, lat: 51.1511856, lon: 3.2703268 }, - { type: "node", id: 6142725066, lat: 51.1510244, lon: 3.2705705 }, - { type: "node", id: 6142725067, lat: 51.1509355, lon: 3.270823 }, - { type: "node", id: 6142725074, lat: 51.149723, lon: 3.271666 }, - { type: "node", id: 6142725076, lat: 51.1498322, lon: 3.2716555 }, - { type: "node", id: 6142725077, lat: 51.1499403, lon: 3.2715209 }, - { type: "node", id: 6142725078, lat: 51.1500262, lon: 3.2714548 }, - { type: "node", id: 6142725079, lat: 51.1501462, lon: 3.2714359 }, - { type: "node", id: 6142725080, lat: 51.1502306, lon: 3.2714572 }, - { type: "node", id: 6142725081, lat: 51.1502676, lon: 3.2714288 }, - { type: "node", id: 6142725084, lat: 51.1496118, lon: 3.2714056 }, - { type: "node", id: 6142727585, lat: 51.1497709, lon: 3.271258 }, - { type: "node", id: 6142727586, lat: 51.1496767, lon: 3.2713056 }, - { type: "node", id: 6142727587, lat: 51.1498468, lon: 3.2711798 }, - { type: "node", id: 6142727588, lat: 51.1500514, lon: 3.2703954 }, - { type: "node", id: 6142727589, lat: 51.1503033, lon: 3.270777 }, - { type: "node", id: 6142727590, lat: 51.1503141, lon: 3.2713705 }, - { type: "node", id: 6142727591, lat: 51.1498992, lon: 3.2710354 }, - { type: "node", id: 6142727592, lat: 51.1499461, lon: 3.270906 }, - { type: "node", id: 6142727595, lat: 51.1505973, lon: 3.2702942 }, - { type: "node", id: 6142727596, lat: 51.1508515, lon: 3.2706917 }, - { type: "node", id: 6142727598, lat: 51.1504337, lon: 3.2712326 }, - { type: "node", id: 6143074993, lat: 51.1501845, lon: 3.2706959 }, - { type: "node", id: 6143075018, lat: 51.1508744, lon: 3.2707275 }, - { type: "node", id: 8638721230, lat: 51.151458, lon: 3.2721756 }, - { type: "node", id: 8638721239, lat: 51.1514715, lon: 3.2719839 }, - { type: "node", id: 1554514618, lat: 51.1581789, lon: 3.2646401 }, - { type: "node", id: 1554514658, lat: 51.1578742, lon: 3.2653982 }, - { type: "node", id: 1554514750, lat: 51.1568056, lon: 3.2677508 }, - { type: "node", id: 1554514755, lat: 51.1573435, lon: 3.2665694 }, - { type: "node", id: 1554514831, lat: 51.1561729, lon: 3.2691965 }, - { type: "node", id: 3211247042, lat: 51.1557516, lon: 3.2701591 }, - { type: "node", id: 3211247054, lat: 51.1570321, lon: 3.2690614 }, - { type: "node", id: 3211247058, lat: 51.1571866, lon: 3.2685962 }, - { type: "node", id: 3211247059, lat: 51.1572111, lon: 3.2673633 }, - { type: "node", id: 3211247060, lat: 51.1572915, lon: 3.2701354 }, - { type: "node", id: 3211247261, lat: 51.157332, lon: 3.2685775 }, - { type: "node", id: 3211247265, lat: 51.1575267, lon: 3.2694937 }, - { type: "node", id: 3211247266, lat: 51.1576636, lon: 3.2677588 }, - { type: "node", id: 3211247291, lat: 51.15873, lon: 3.2640973 }, - { type: "node", id: 3211247328, lat: 51.1594831, lon: 3.2648765 }, - { type: "node", id: 3211247331, lat: 51.1596428, lon: 3.2643669 }, - { type: "node", id: 5747937641, lat: 51.1581621, lon: 3.2637783 }, - { type: "node", id: 5747937643, lat: 51.1576288, lon: 3.2640519 }, - { type: "node", id: 5747937644, lat: 51.1572672, lon: 3.2645347 }, - { type: "node", id: 5747937645, lat: 51.1568533, lon: 3.2650349 }, - { type: "node", id: 5747937646, lat: 51.1564933, lon: 3.2652213 }, - { type: "node", id: 5747937647, lat: 51.1562275, lon: 3.2654708 }, - { type: "node", id: 5747937648, lat: 51.1560046, lon: 3.2656894 }, - { type: "node", id: 5747937649, lat: 51.1556867, lon: 3.2659965 }, - { type: "node", id: 5747937650, lat: 51.1551778, lon: 3.2664082 }, - { type: "node", id: 5747937651, lat: 51.1550608, lon: 3.2664739 }, - { type: "node", id: 5747937652, lat: 51.1547446, lon: 3.2666805 }, - { type: "node", id: 5747937653, lat: 51.1546747, lon: 3.2667583 }, - { type: "node", id: 5747937654, lat: 51.1546066, lon: 3.266895 }, - { type: "node", id: 5747937655, lat: 51.1544363, lon: 3.2672456 }, - { type: "node", id: 5747937656, lat: 51.1543612, lon: 3.2677126 }, - { type: "node", id: 5747937688, lat: 51.1553145, lon: 3.2701637 }, - { type: "node", id: 5747937689, lat: 51.1564724, lon: 3.2675566 }, - { type: "node", id: 5747937690, lat: 51.1580352, lon: 3.2640939 }, - { type: "node", id: 5747937691, lat: 51.1581075, lon: 3.2639222 }, - { type: "node", id: 5962338038, lat: 51.1580663, lon: 3.2649607 }, - { type: "node", id: 5962340009, lat: 51.1585904, lon: 3.2644244 }, - { type: "node", id: 5962340010, lat: 51.1579212, lon: 3.2658517 }, - { type: "node", id: 5962340011, lat: 51.1575197, lon: 3.2676156 }, - { type: "node", id: 5962340012, lat: 51.1573374, lon: 3.2674543 }, - { type: "node", id: 5962340013, lat: 51.1574265, lon: 3.2698457 }, - { type: "node", id: 5962415543, lat: 51.1591427, lon: 3.2638615 }, - { type: "node", id: 5962415564, lat: 51.1563757, lon: 3.269274 }, - { type: "node", id: 5962444841, lat: 51.1584746, lon: 3.2639653 }, - { type: "node", id: 5962444846, lat: 51.1585428, lon: 3.2645259 }, - { type: "node", id: 5962444849, lat: 51.1586783, lon: 3.2642185 }, - { type: "node", id: 5962444850, lat: 51.1586559, lon: 3.2642708 }, - { type: "node", id: 5962496715, lat: 51.1577157, lon: 3.2657479 }, - { type: "node", id: 5962496718, lat: 51.1572172, lon: 3.2685923 }, - { type: "node", id: 5962496719, lat: 51.1571618, lon: 3.2686709 }, - { type: "node", id: 1554514640, lat: 51.1556541, lon: 3.2703864 }, - { type: "node", id: 1554514735, lat: 51.1546949, lon: 3.273022 }, - { type: "node", id: 1554514739, lat: 51.1552715, lon: 3.271391 }, - { type: "node", id: 1554514744, lat: 51.154165, lon: 3.2744194 }, - { type: "node", id: 3211247024, lat: 51.1541505, lon: 3.2756042 }, - { type: "node", id: 3211247029, lat: 51.154764, lon: 3.2739293 }, - { type: "node", id: 3211247032, lat: 51.1550284, lon: 3.2731777 }, - { type: "node", id: 3211247034, lat: 51.1553184, lon: 3.2723493 }, - { type: "node", id: 3211247036, lat: 51.1554324, lon: 3.2715569 }, - { type: "node", id: 3211247037, lat: 51.1555577, lon: 3.2717139 }, - { type: "node", id: 3211247039, lat: 51.1556614, lon: 3.2709968 }, - { type: "node", id: 5747937686, lat: 51.1543123, lon: 3.272868 }, - { type: "node", id: 5747937687, lat: 51.1551484, lon: 3.2706069 }, - { type: "node", id: 5962415565, lat: 51.1558985, lon: 3.2703959 }, - { type: "node", id: 3170562436, lat: 51.138924, lon: 3.2898032 }, - { type: "node", id: 7114502205, lat: 51.1385818, lon: 3.2899019 }, - { type: "node", id: 7114502206, lat: 51.1387244, lon: 3.289738 }, - { type: "node", id: 1475188516, lat: 51.1392568, lon: 3.2899836 }, - { type: "node", id: 1475188519, lat: 51.1391868, lon: 3.2900136 }, - { type: "node", id: 3170562392, lat: 51.1364433, lon: 3.2910119 }, - { type: "node", id: 3170562394, lat: 51.1368907, lon: 3.291708 }, - { type: "node", id: 3170562395, lat: 51.137106, lon: 3.2924864 }, - { type: "node", id: 3170562396, lat: 51.137265, lon: 3.2919875 }, - { type: "node", id: 3170562397, lat: 51.1374825, lon: 3.2913695 }, - { type: "node", id: 3170562402, lat: 51.1378658, lon: 3.2906394 }, - { type: "node", id: 3170562410, lat: 51.1378512, lon: 3.2905949 }, - { type: "node", id: 3170562431, lat: 51.138431, lon: 3.2910415 }, - { type: "node", id: 3170562437, lat: 51.1389596, lon: 3.2905649 }, - { type: "node", id: 3170562439, lat: 51.1391839, lon: 3.2903042 }, - { type: "node", id: 6627605025, lat: 51.1393014, lon: 3.2899729 }, - { type: "node", id: 7114502200, lat: 51.139377, lon: 3.2901873 }, - { type: "node", id: 7114502201, lat: 51.1395801, lon: 3.2901445 }, - { type: "node", id: 7114502202, lat: 51.1393183, lon: 3.290151 }, - { type: "node", id: 7114502203, lat: 51.1395636, lon: 3.2902055 }, - { type: "node", id: 7114502207, lat: 51.13835, lon: 3.2901225 }, - { type: "node", id: 7114502208, lat: 51.138259, lon: 3.2902362 }, - { type: "node", id: 7114502209, lat: 51.1381351, lon: 3.2903372 }, - { type: "node", id: 7114502211, lat: 51.1376987, lon: 3.2921954 }, - { type: "node", id: 7114502212, lat: 51.1373598, lon: 3.2927952 }, - { type: "node", id: 7114502214, lat: 51.1364782, lon: 3.2933157 }, - { type: "node", id: 7114502215, lat: 51.1356558, lon: 3.2938122 }, - { type: "node", id: 7114502216, lat: 51.1364577, lon: 3.2922382 }, - { type: "node", id: 7114502217, lat: 51.1365055, lon: 3.2917901 }, - { type: "node", id: 7114502218, lat: 51.1369977, lon: 3.2911659 }, - { type: "node", id: 7114502219, lat: 51.1367691, lon: 3.2924051 }, - { type: "node", id: 7114502220, lat: 51.1367022, lon: 3.2927526 }, - { type: "node", id: 7114502221, lat: 51.1367354, lon: 3.2929091 }, - { type: "node", id: 7114502222, lat: 51.1367962, lon: 3.2930156 }, - { type: "node", id: 7114502223, lat: 51.1369436, lon: 3.2928838 }, - { type: "node", id: 7114502224, lat: 51.1365124, lon: 3.2914123 }, - { type: "node", id: 7114502225, lat: 51.135804, lon: 3.2926995 }, - { type: "node", id: 7114502226, lat: 51.1354385, lon: 3.2929897 }, - { type: "node", id: 7114502227, lat: 51.135128, lon: 3.2935318 }, - { type: "node", id: 7114502228, lat: 51.1352851, lon: 3.2947114 }, - { type: "node", id: 7114502230, lat: 51.135254, lon: 3.2946241 }, - { type: "node", id: 7114502234, lat: 51.1354035, lon: 3.2959064 }, - { type: "node", id: 7114502235, lat: 51.1362234, lon: 3.2955042 }, - { type: "node", id: 7114502236, lat: 51.1362859, lon: 3.2964578 }, - { type: "node", id: 7114502240, lat: 51.1379417, lon: 3.2917937 }, - { type: "node", id: 8294886142, lat: 51.1394294, lon: 3.290127 }, - { type: "node", id: 7114502233, lat: 51.1354361, lon: 3.2966386 }, - { type: "node", id: 7114502237, lat: 51.1362025, lon: 3.2964851 }, - { type: "node", id: 7114502238, lat: 51.1362303, lon: 3.2969415 }, - { type: "node", id: 7114502239, lat: 51.1355032, lon: 3.297292 }, - { type: "node", id: 1494027348, lat: 51.1419125, lon: 3.3028989 }, - { type: "node", id: 1494027349, lat: 51.1419929, lon: 3.3024731 }, - { type: "node", id: 1951759298, lat: 51.1421456, lon: 3.3024503 }, - { type: "node", id: 6037556099, lat: 51.1420961, lon: 3.3023333 }, - { type: "node", id: 6037556100, lat: 51.142124, lon: 3.302398 }, - { type: "node", id: 6037556101, lat: 51.1421476, lon: 3.3023664 }, - { type: "node", id: 6037556102, lat: 51.1421686, lon: 3.3023966 }, - { type: "node", id: 6037556103, lat: 51.1421392, lon: 3.3024395 }, - { type: "node", id: 6037556104, lat: 51.1419363, lon: 3.3027154 }, - { type: "node", id: 6037556128, lat: 51.1421612, lon: 3.3024776 }, - { type: "node", id: 6037556129, lat: 51.1421598, lon: 3.3025191 }, - { type: "node", id: 1554514647, lat: 51.1510862, lon: 3.2830051 }, - { type: "node", id: 1554514679, lat: 51.1528746, lon: 3.2778124 }, - { type: "node", id: 1554514683, lat: 51.152459, lon: 3.2789175 }, - { type: "node", id: 1554514730, lat: 51.1514294, lon: 3.2817821 }, - { type: "node", id: 1554514747, lat: 51.1515758, lon: 3.2812966 }, - { type: "node", id: 1554514765, lat: 51.1518248, lon: 3.2805268 }, - { type: "node", id: 1554514773, lat: 51.15167, lon: 3.280964 }, - { type: "node", id: 1554514775, lat: 51.1517205, lon: 3.2808218 }, - { type: "node", id: 3211246555, lat: 51.1516922, lon: 3.2825734 }, - { type: "node", id: 3211246995, lat: 51.1531241, lon: 3.2787307 }, - { type: "node", id: 3211246997, lat: 51.1532256, lon: 3.2781298 }, - { type: "node", id: 3211247004, lat: 51.1532551, lon: 3.2780324 }, - { type: "node", id: 3211247008, lat: 51.1532769, lon: 3.2780262 }, - { type: "node", id: 3211247010, lat: 51.1533547, lon: 3.2780182 }, - { type: "node", id: 3211247013, lat: 51.1534072, lon: 3.2778679 }, - { type: "node", id: 5962338069, lat: 51.1531224, lon: 3.2785269 }, - { type: "node", id: 5962338070, lat: 51.1532454, lon: 3.2781435 }, - { type: "node", id: 5962338071, lat: 51.1531839, lon: 3.2785716 }, - { type: "node", id: 9284562682, lat: 51.153154, lon: 3.2784189 }, - { type: "node", id: 1554514649, lat: 51.1502146, lon: 3.285505 }, - { type: "node", id: 1554514661, lat: 51.1499067, lon: 3.2861781 }, - { type: "node", id: 1554514682, lat: 51.150038, lon: 3.2859179 }, - { type: "node", id: 1554514693, lat: 51.1498077, lon: 3.2862946 }, - { type: "node", id: 1554514703, lat: 51.1497368, lon: 3.2863685 }, - { type: "node", id: 1554514726, lat: 51.1498748, lon: 3.2862129 }, - { type: "node", id: 1554514783, lat: 51.1497118, lon: 3.2864368 }, - { type: "node", id: 1554514787, lat: 51.1499524, lon: 3.2860908 }, - { type: "node", id: 1554514789, lat: 51.1506254, lon: 3.2845029 }, - { type: "node", id: 3203029856, lat: 51.1498994, lon: 3.2867119 }, - { type: "node", id: 3211246542, lat: 51.1505988, lon: 3.2851597 }, - { type: "node", id: 5482441357, lat: 51.1504615, lon: 3.2836968 }, - { type: "node", id: 7459154782, lat: 51.1501541, lon: 3.2834044 }, - { type: "node", id: 7459154784, lat: 51.14999, lon: 3.2838049 }, - { type: "node", id: 7459257985, lat: 51.1496077, lon: 3.2846412 }, - { type: "node", id: 7602479690, lat: 51.150018, lon: 3.2848987 }, - { type: "node", id: 7602479691, lat: 51.149806, lon: 3.2842251 }, - { type: "node", id: 7602479692, lat: 51.1504686, lon: 3.2836785 }, - { type: "node", id: 7602479693, lat: 51.1504309, lon: 3.2838118 }, - { type: "node", id: 7602479694, lat: 51.1504255, lon: 3.2838072 }, - { type: "node", id: 7602479695, lat: 51.1503423, lon: 3.2840392 }, - { type: "node", id: 7602479696, lat: 51.1501698, lon: 3.2845196 }, - { type: "node", id: 7727406921, lat: 51.1497539, lon: 3.2864986 }, - { type: "node", id: 8945026754, lat: 51.1518736, lon: 3.2973911 }, - { type: "node", id: 8945026755, lat: 51.1521646, lon: 3.297599 }, - { type: "node", id: 8945026756, lat: 51.1522042, lon: 3.2974333 }, - { type: "node", id: 8945026757, lat: 51.1519287, lon: 3.2972449 }, - { type: "node", id: 416917612, lat: 51.1610842, lon: 3.2579973 }, - { type: "node", id: 1948836192, lat: 51.1607104, lon: 3.2576119 }, - { type: "node", id: 1948836193, lat: 51.1607138, lon: 3.2577265 }, - { type: "node", id: 1948836194, lat: 51.1607261, lon: 3.2576817 }, - { type: "node", id: 1948836195, lat: 51.1607396, lon: 3.2576322 }, - { type: "node", id: 1948836202, lat: 51.1608376, lon: 3.2582648 }, - { type: "node", id: 1948836209, lat: 51.1608764, lon: 3.2583297 }, - { type: "node", id: 1948836218, lat: 51.1609914, lon: 3.2580679 }, - { type: "node", id: 1948836237, lat: 51.1610295, lon: 3.2581432 }, - { type: "node", id: 1948836277, lat: 51.1610699, lon: 3.2580833 }, - { type: "node", id: 5349884600, lat: 51.1623355, lon: 3.2627383 }, - { type: "node", id: 5349884601, lat: 51.1621681, lon: 3.2630327 }, - { type: "node", id: 5349884602, lat: 51.1622043, lon: 3.2630816 }, - { type: "node", id: 5349884603, lat: 51.162375, lon: 3.2627913 }, - { type: "node", id: 5349884604, lat: 51.1622594, lon: 3.2633478 }, - { type: "node", id: 5349884605, lat: 51.1621955, lon: 3.2632528 }, - { type: "node", id: 5349884606, lat: 51.1623824, lon: 3.2629335 }, - { type: "node", id: 5349884607, lat: 51.1624462, lon: 3.2630285 }, - { type: "node", id: 5745739924, lat: 51.1608677, lon: 3.2604028 }, - { type: "node", id: 5745739925, lat: 51.160749, lon: 3.2605301 }, - { type: "node", id: 5745739927, lat: 51.160719, lon: 3.2600865 }, - { type: "node", id: 1494027341, lat: 51.1418334, lon: 3.3035292 }, - { type: "node", id: 1494027359, lat: 51.1413757, lon: 3.3074828 }, - { type: "node", id: 1494027374, lat: 51.14132, lon: 3.3086261 }, - { type: "node", id: 1494027376, lat: 51.1415625, lon: 3.3055864 }, - { type: "node", id: 1494027385, lat: 51.1414514, lon: 3.3065628 }, - { type: "node", id: 1494027386, lat: 51.1416927, lon: 3.3044972 }, - { type: "node", id: 1494027389, lat: 51.1412677, lon: 3.3093509 }, - { type: "node", id: 6037556130, lat: 51.1420599, lon: 3.303249 }, - { type: "node", id: 6037556131, lat: 51.1419859, lon: 3.3038109 }, - { type: "node", id: 6037556132, lat: 51.1418857, lon: 3.3046747 }, - { type: "node", id: 6037556133, lat: 51.1418916, lon: 3.3050562 }, - { type: "node", id: 6037556134, lat: 51.141927, lon: 3.3054665 }, - { type: "node", id: 6037556135, lat: 51.1419362, lon: 3.3056543 }, - { type: "node", id: 6037556136, lat: 51.1419369, lon: 3.3060001 }, - { type: "node", id: 6037556137, lat: 51.1419328, lon: 3.3061768 }, - { type: "node", id: 6037556138, lat: 51.1419139, lon: 3.3062337 }, - { type: "node", id: 6037556139, lat: 51.1419032, lon: 3.3062932 }, - { type: "node", id: 6037556140, lat: 51.1419001, lon: 3.3065944 }, - { type: "node", id: 6037556141, lat: 51.1419262, lon: 3.3066872 }, - { type: "node", id: 6037556142, lat: 51.1419119, lon: 3.3074563 }, - { type: "node", id: 6037556143, lat: 51.1419027, lon: 3.3079359 }, - { type: "node", id: 6037556144, lat: 51.1418924, lon: 3.3090764 }, - { type: "node", id: 6037556145, lat: 51.1418843, lon: 3.3094015 }, - { type: "node", id: 1494027342, lat: 51.1411958, lon: 3.3123073 }, - { type: "node", id: 1494027346, lat: 51.1411509, lon: 3.3144776 }, - { type: "node", id: 1494027353, lat: 51.141193, lon: 3.3127209 }, - { type: "node", id: 1494027361, lat: 51.1411944, lon: 3.3118627 }, - { type: "node", id: 1494027366, lat: 51.1411788, lon: 3.3132011 }, - { type: "node", id: 1494027368, lat: 51.1411457, lon: 3.3148322 }, - { type: "node", id: 1494027372, lat: 51.1412147, lon: 3.3112199 }, - { type: "node", id: 1494027373, lat: 51.1411641, lon: 3.3142758 }, - { type: "node", id: 1494027378, lat: 51.1412256, lon: 3.3114828 }, - { type: "node", id: 1494027382, lat: 51.1412174, lon: 3.3105548 }, - { type: "node", id: 6037556098, lat: 51.1411339, lon: 3.315984 }, - { type: "node", id: 6037556146, lat: 51.1418276, lon: 3.309789 }, - { type: "node", id: 6037556148, lat: 51.1418583, lon: 3.3096011 }, - { type: "node", id: 6037556149, lat: 51.1418039, lon: 3.3098357 }, - { type: "node", id: 6037556150, lat: 51.1417743, lon: 3.3098805 }, - { type: "node", id: 6037556151, lat: 51.1416977, lon: 3.3101207 }, - { type: "node", id: 6037556152, lat: 51.1416834, lon: 3.3102232 }, - { type: "node", id: 6037556153, lat: 51.1416823, lon: 3.3102705 }, - { type: "node", id: 6037556154, lat: 51.1416302, lon: 3.3105557 }, - { type: "node", id: 6037556155, lat: 51.1416042, lon: 3.3107844 }, - { type: "node", id: 6037556156, lat: 51.1415929, lon: 3.3110547 }, - { type: "node", id: 6037556157, lat: 51.1415904, lon: 3.3118741 }, - { type: "node", id: 6037556158, lat: 51.1415817, lon: 3.3145778 }, - { type: "node", id: 6037556159, lat: 51.14157, lon: 3.3146421 }, - { type: "node", id: 6037556160, lat: 51.1415557, lon: 3.3146975 }, - { type: "node", id: 6037556161, lat: 51.1415541, lon: 3.3148416 }, - { type: "node", id: 6037556162, lat: 51.1415551, lon: 3.3149739 }, - { type: "node", id: 6037556163, lat: 51.1415802, lon: 3.3150635 }, - { type: "node", id: 1494027343, lat: 51.1410607, lon: 3.3204128 }, - { type: "node", id: 1494027365, lat: 51.141078, lon: 3.3193848 }, - { type: "node", id: 1494027369, lat: 51.1410588, lon: 3.3212214 }, - { type: "node", id: 1494027380, lat: 51.1410813, lon: 3.317924 }, - { type: "node", id: 6037556105, lat: 51.141131, lon: 3.31628 }, - { type: "node", id: 6037556106, lat: 51.1411581, lon: 3.3165005 }, - { type: "node", id: 6037556107, lat: 51.141174, lon: 3.3167222 }, - { type: "node", id: 6037556108, lat: 51.1411155, lon: 3.3169939 }, - { type: "node", id: 6037556109, lat: 51.1410508, lon: 3.3221866 }, - { type: "node", id: 6037556110, lat: 51.1410676, lon: 3.3223383 }, - { type: "node", id: 6037556164, lat: 51.1415792, lon: 3.3164565 }, - { type: "node", id: 6037556165, lat: 51.141573, lon: 3.3172357 }, - { type: "node", id: 6037556166, lat: 51.1415771, lon: 3.3182023 }, - { type: "node", id: 6037556167, lat: 51.1415495, lon: 3.3182625 }, - { type: "node", id: 6037556168, lat: 51.1415495, lon: 3.3185605 }, - { type: "node", id: 6037556169, lat: 51.1415684, lon: 3.3186069 }, - { type: "node", id: 6037556170, lat: 51.1415633, lon: 3.3207654 }, - { type: "node", id: 6037556171, lat: 51.1415444, lon: 3.3214131 }, - { type: "node", id: 6037556172, lat: 51.1415097, lon: 3.3218544 }, - { type: "node", id: 6037556173, lat: 51.1414642, lon: 3.3225703 }, - { type: "node", id: 1493821404, lat: 51.1410663, lon: 3.3233361 }, - { type: "node", id: 1494027355, lat: 51.1410465, lon: 3.3227899 }, - { type: "node", id: 1801373604, lat: 51.1410713, lon: 3.3237592 }, - { type: "node", id: 1951759314, lat: 51.1411466, lon: 3.3245554 }, - { type: "node", id: 2474247506, lat: 51.1411643, lon: 3.3245589 }, - { type: "node", id: 6037556111, lat: 51.1410551, lon: 3.3241916 }, - { type: "node", id: 6037556112, lat: 51.1410504, lon: 3.3243751 }, - { type: "node", id: 6037556118, lat: 51.1410331, lon: 3.3245183 }, - { type: "node", id: 6037556120, lat: 51.1411823, lon: 3.3245635 }, - { type: "node", id: 6037556174, lat: 51.1414121, lon: 3.3230419 }, - { type: "node", id: 6037556175, lat: 51.1413953, lon: 3.3230745 }, - { type: "node", id: 6037556176, lat: 51.1413682, lon: 3.323164 }, - { type: "node", id: 6037556177, lat: 51.1413426, lon: 3.3233187 }, - { type: "node", id: 6037556178, lat: 51.1413353, lon: 3.3234317 }, - { type: "node", id: 6037556179, lat: 51.1413527, lon: 3.3235128 }, - { type: "node", id: 6037556180, lat: 51.141294, lon: 3.3239374 }, - { type: "node", id: 3702878648, lat: 51.1401344, lon: 3.3337214 }, - { type: "node", id: 3702878654, lat: 51.1395918, lon: 3.3338166 }, - { type: "node", id: 3702926557, lat: 51.1404241, lon: 3.333592 }, - { type: "node", id: 3702926558, lat: 51.1406229, lon: 3.3335608 }, - { type: "node", id: 3702926559, lat: 51.1406259, lon: 3.3336496 }, - { type: "node", id: 3702926560, lat: 51.1404301, lon: 3.333676 }, - { type: "node", id: 3702926561, lat: 51.1403789, lon: 3.3336784 }, - { type: "node", id: 3702926562, lat: 51.1403729, lon: 3.3335752 }, - { type: "node", id: 3702926563, lat: 51.1403021, lon: 3.333592 }, - { type: "node", id: 3702926564, lat: 51.1402991, lon: 3.3335295 }, - { type: "node", id: 3702926565, lat: 51.1402253, lon: 3.3335392 }, - { type: "node", id: 3702926566, lat: 51.1402283, lon: 3.3336112 }, - { type: "node", id: 3702926567, lat: 51.1401304, lon: 3.3336285 }, - { type: "node", id: 3702926568, lat: 51.1400111, lon: 3.3337421 }, - { type: "node", id: 3702956173, lat: 51.1406298, lon: 3.3321023 }, - { type: "node", id: 3702956174, lat: 51.140115, lon: 3.3325162 }, - { type: "node", id: 3702956175, lat: 51.1400702, lon: 3.3323734 }, - { type: "node", id: 3702956176, lat: 51.1405896, lon: 3.3319784 }, - { type: "node", id: 3702969714, lat: 51.1404452, lon: 3.3324432 }, - { type: "node", id: 3702969715, lat: 51.1402175, lon: 3.3326312 }, - { type: "node", id: 3702969716, lat: 51.1401965, lon: 3.3325686 }, - { type: "node", id: 3702969717, lat: 51.1404269, lon: 3.3323835 }, - { type: "node", id: 7713176909, lat: 51.1387744, lon: 3.3335489 }, - { type: "node", id: 7713176910, lat: 51.1389364, lon: 3.3334306 }, - { type: "node", id: 7713176911, lat: 51.1387319, lon: 3.3327211 }, - { type: "node", id: 7713176912, lat: 51.1385501, lon: 3.332864 }, - { type: "node", id: 8292789053, lat: 51.1400165, lon: 3.3338082 }, - { type: "node", id: 8292789054, lat: 51.1396286, lon: 3.333874 }, - { type: "node", id: 8378097124, lat: 51.140324, lon: 3.3355884 }, - { type: "node", id: 8378097125, lat: 51.1403179, lon: 3.3355055 }, - { type: "node", id: 8378097126, lat: 51.1406041, lon: 3.3354515 }, - { type: "node", id: 8378097127, lat: 51.1406102, lon: 3.3355344 }, - { type: "node", id: 8378097131, lat: 51.14074, lon: 3.3354785 }, - { type: "node", id: 8378097132, lat: 51.1407879, lon: 3.3354717 }, - { type: "node", id: 8378097133, lat: 51.1407993, lon: 3.3356206 }, - { type: "node", id: 1317838317, lat: 51.1372361, lon: 3.338639 }, - { type: "node", id: 1317838328, lat: 51.1369619, lon: 3.3386232 }, - { type: "node", id: 1317838331, lat: 51.1371806, lon: 3.3384037 }, - { type: "node", id: 1384096112, lat: 51.1371794, lon: 3.3384971 }, - { type: "node", id: 1625199827, lat: 51.1408446, lon: 3.3375722 }, - { type: "node", id: 1625199950, lat: 51.1407563, lon: 3.3373232 }, - { type: "node", id: 3227068446, lat: 51.1369433, lon: 3.3387663 }, - { type: "node", id: 3227068456, lat: 51.1371252, lon: 3.3388226 }, - { type: "node", id: 3227068565, lat: 51.1371943, lon: 3.3389942 }, - { type: "node", id: 3227068568, lat: 51.1372698, lon: 3.3389467 }, - { type: "node", id: 6227395991, lat: 51.1392741, lon: 3.3367141 }, - { type: "node", id: 6227395992, lat: 51.1392236, lon: 3.3367356 }, - { type: "node", id: 6227395993, lat: 51.139313, lon: 3.3369193 }, - { type: "node", id: 6227395997, lat: 51.1392584, lon: 3.3369405 }, - { type: "node", id: 6414352053, lat: 51.1374056, lon: 3.3395184 }, - { type: "node", id: 6414352054, lat: 51.1371278, lon: 3.3390236 }, - { type: "node", id: 6414352055, lat: 51.1372589, lon: 3.3395769 }, - { type: "node", id: 6414374315, lat: 51.1369166, lon: 3.338648 }, - { type: "node", id: 6414374316, lat: 51.1369683, lon: 3.3388767 }, - { type: "node", id: 6414374317, lat: 51.1370053, lon: 3.3388543 }, - { type: "node", id: 6414374318, lat: 51.1371169, lon: 3.3387872 }, - { type: "node", id: 6414374319, lat: 51.1371271, lon: 3.3388309 }, - { type: "node", id: 6414374320, lat: 51.137238, lon: 3.3387628 }, - { type: "node", id: 6414374321, lat: 51.1372074, lon: 3.338651 }, - { type: "node", id: 6414383775, lat: 51.1407766, lon: 3.3373855 }, - { type: "node", id: 6416001161, lat: 51.1371631, lon: 3.3390024 }, - { type: "node", id: 7274233390, lat: 51.1372936, lon: 3.3395562 }, - { type: "node", id: 7274233391, lat: 51.137309, lon: 3.3396231 }, - { type: "node", id: 7274233395, lat: 51.1374325, lon: 3.3396124 }, - { type: "node", id: 7274233396, lat: 51.137422, lon: 3.3395803 }, - { type: "node", id: 7274233397, lat: 51.1373776, lon: 3.3396587 }, - { type: "node", id: 8279842668, lat: 51.1371603, lon: 3.3384139 }, - { type: "node", id: 8377691836, lat: 51.1409346, lon: 3.337164 }, - { type: "node", id: 8378081366, lat: 51.140887, lon: 3.3372065 }, - { type: "node", id: 8378081386, lat: 51.1407701, lon: 3.3373106 }, - { type: "node", id: 8378081429, lat: 51.1408204, lon: 3.3372657 }, - { type: "node", id: 8378097128, lat: 51.1407837, lon: 3.3359331 }, - { type: "node", id: 8378097129, lat: 51.1407736, lon: 3.3358962 }, - { type: "node", id: 8378097130, lat: 51.1407627, lon: 3.3358224 }, - { type: "node", id: 8378097134, lat: 51.1408355, lon: 3.3359157 }, - { type: "node", id: 1625199938, lat: 51.1411535, lon: 3.3372917 }, - { type: "node", id: 1625199951, lat: 51.1410675, lon: 3.3370397 }, - { type: "node", id: 6100803125, lat: 51.1672329, lon: 3.3331872 }, - { type: "node", id: 6100803128, lat: 51.1672258, lon: 3.3333568 }, - { type: "node", id: 2577910530, lat: 51.1685713, lon: 3.3356917 }, - { type: "node", id: 2577910542, lat: 51.1681925, lon: 3.3356582 }, - { type: "node", id: 2577910543, lat: 51.1685972, lon: 3.3356499 }, - { type: "node", id: 3645188881, lat: 51.1679055, lon: 3.3337545 }, - { type: "node", id: 6100803124, lat: 51.1673065, lon: 3.3331116 }, - { type: "node", id: 6100803126, lat: 51.1672788, lon: 3.3336938 }, - { type: "node", id: 6100803127, lat: 51.1672758, lon: 3.3333629 }, - { type: "node", id: 6100803129, lat: 51.1675567, lon: 3.3331312 }, - { type: "node", id: 6100803130, lat: 51.1675525, lon: 3.3333096 }, - { type: "node", id: 6100803131, lat: 51.1679171, lon: 3.3333454 }, - { type: "node", id: 2577910520, lat: 51.1681983, lon: 3.3360484 }, - { type: "node", id: 2577910526, lat: 51.1687824, lon: 3.3362878 }, - { type: "node", id: 2577910545, lat: 51.16885, lon: 3.3360431 }, - { type: "node", id: 6145151107, lat: 51.1694008, lon: 3.3386946 }, - { type: "node", id: 6145151108, lat: 51.1688319, lon: 3.3385737 }, - { type: "node", id: 6145151109, lat: 51.1688258, lon: 3.3386467 }, - { type: "node", id: 6145151110, lat: 51.1693942, lon: 3.3387681 }, - { type: "node", id: 6145151114, lat: 51.1691188, lon: 3.3387104 }, - { type: "node", id: 6145151115, lat: 51.1688569, lon: 3.3386537 }, - { type: "node", id: 6418533335, lat: 51.1685948, lon: 3.3361624 }, - { type: "node", id: 3190107371, lat: 51.1741471, lon: 3.3384365 }, - { type: "node", id: 3190107374, lat: 51.1741746, lon: 3.3388275 }, - { type: "node", id: 3190107377, lat: 51.1741968, lon: 3.3384276 }, - { type: "node", id: 3190107393, lat: 51.1743618, lon: 3.3383982 }, - { type: "node", id: 3190107400, lat: 51.1744043, lon: 3.3384036 }, - { type: "node", id: 3190107407, lat: 51.1744809, lon: 3.3386827 }, - { type: "node", id: 3190107408, lat: 51.1744875, lon: 3.3387716 }, - { type: "node", id: 3190107415, lat: 51.1746495, lon: 3.3386505 }, - { type: "node", id: 3190107416, lat: 51.1746548, lon: 3.3387211 }, - { type: "node", id: 3190107420, lat: 51.1747424, lon: 3.3387044 }, - { type: "node", id: 3190107423, lat: 51.1747681, lon: 3.3390477 }, - { type: "node", id: 3190107424, lat: 51.1747706, lon: 3.3384763 }, - { type: "node", id: 3190107428, lat: 51.1747993, lon: 3.3384858 }, - { type: "node", id: 3190107432, lat: 51.1748198, lon: 3.3387594 }, - { type: "node", id: 3190107435, lat: 51.1750772, lon: 3.3389888 }, - { type: "node", id: 3190107439, lat: 51.1752356, lon: 3.3387751 }, - { type: "node", id: 3190107442, lat: 51.1752492, lon: 3.338956 }, - { type: "node", id: 7390697534, lat: 51.1297479, lon: 3.3578326 }, - { type: "node", id: 7390697550, lat: 51.1297706, lon: 3.3588263 }, - { type: "node", id: 7468171405, lat: 51.1279967, lon: 3.3559306 }, - { type: "node", id: 7468171406, lat: 51.1288847, lon: 3.3598888 }, - { type: "node", id: 7468171407, lat: 51.1288383, lon: 3.3606949 }, - { type: "node", id: 7468171408, lat: 51.128796, lon: 3.3619912 }, - { type: "node", id: 7468171422, lat: 51.1285744, lon: 3.3574498 }, - { type: "node", id: 7468171423, lat: 51.1288319, lon: 3.3586353 }, - { type: "node", id: 7468171426, lat: 51.1287158, lon: 3.3579031 }, - { type: "node", id: 7468171428, lat: 51.1288787, lon: 3.3588438 }, - { type: "node", id: 7468171429, lat: 51.1288942, lon: 3.3595607 }, - { type: "node", id: 7468171431, lat: 51.1288954, lon: 3.3592459 }, - { type: "node", id: 7468171433, lat: 51.1287831, lon: 3.3582062 }, - { type: "node", id: 7468171444, lat: 51.1287924, lon: 3.361646 }, - { type: "node", id: 7727393197, lat: 51.1288566, lon: 3.3555067 }, - { type: "node", id: 7727393198, lat: 51.1292196, lon: 3.356551 }, - { type: "node", id: 7727393199, lat: 51.129399, lon: 3.3566378 }, - { type: "node", id: 7190873584, lat: 51.1287267, lon: 3.3682604 }, - { type: "node", id: 7190927785, lat: 51.1287057, lon: 3.3681343 }, - { type: "node", id: 7190927786, lat: 51.1287121, lon: 3.3673045 }, - { type: "node", id: 7190927787, lat: 51.1286419, lon: 3.3641562 }, - { type: "node", id: 7190927788, lat: 51.1286407, lon: 3.3644331 }, - { type: "node", id: 7190927789, lat: 51.128655, lon: 3.3650286 }, - { type: "node", id: 7190927790, lat: 51.128688, lon: 3.3661227 }, - { type: "node", id: 7190927791, lat: 51.1286681, lon: 3.3635132 }, - { type: "node", id: 7190927792, lat: 51.1286863, lon: 3.3631709 }, - { type: "node", id: 7190927793, lat: 51.1286419, lon: 3.3639077 }, - { type: "node", id: 7468171409, lat: 51.1288502, lon: 3.3621534 }, - { type: "node", id: 7468171410, lat: 51.1288454, lon: 3.3623212 }, - { type: "node", id: 7468171411, lat: 51.1287924, lon: 3.36282 }, - { type: "node", id: 7468171412, lat: 51.1287335, lon: 3.363023 }, - { type: "node", id: 7468171413, lat: 51.1286942, lon: 3.363003 }, - { type: "node", id: 7602692242, lat: 51.1287128, lon: 3.3675239 }, - { type: "node", id: 7727393200, lat: 51.129492, lon: 3.3624712 }, - { type: "node", id: 7727393201, lat: 51.1294385, lon: 3.3629631 }, - { type: "node", id: 7727393202, lat: 51.1293629, lon: 3.3638402 }, - { type: "node", id: 7727393203, lat: 51.1293586, lon: 3.3662748 }, - { type: "node", id: 7727393204, lat: 51.1292824, lon: 3.3662748 }, - { type: "node", id: 7727393205, lat: 51.1292881, lon: 3.3667997 }, - { type: "node", id: 7727393206, lat: 51.1293315, lon: 3.3682391 }, - { type: "node", id: 7190873576, lat: 51.1287907, lon: 3.36923 }, - { type: "node", id: 7190873577, lat: 51.1289144, lon: 3.369348 }, - { type: "node", id: 7190873578, lat: 51.1288472, lon: 3.3692931 }, - { type: "node", id: 7190873582, lat: 51.1287752, lon: 3.3691898 }, - { type: "node", id: 7468171450, lat: 51.1287442, lon: 3.3685677 }, - { type: "node", id: 7468171451, lat: 51.1290764, lon: 3.3693851 }, - { type: "node", id: 7468171455, lat: 51.1289835, lon: 3.3693823 }, - { type: "node", id: 7727393207, lat: 51.1293768, lon: 3.368773 }, - { type: "node", id: 7727393208, lat: 51.1293116, lon: 3.3692219 }, - { type: "node", id: 7727393209, lat: 51.1293718, lon: 3.368916 }, - { type: "node", id: 7727393210, lat: 51.1292845, lon: 3.3692843 }, - { type: "node", id: 7727393211, lat: 51.1292517, lon: 3.3693264 }, - { type: "node", id: 7727393212, lat: 51.1291932, lon: 3.3693491 }, - { type: "node", id: 8781449489, lat: 51.1293725, lon: 3.3688971 }, - { - type: "way", - id: 640979978, - nodes: [ - 6037556099, 6037556100, 6037556101, 6037556102, 6037556103, 1951759298, - 6037556128, 6037556129, 6037556130, 6037556131, 6037556132, 6037556133, - 6037556134, 6037556135, 6037556136, 6037556137, 6037556138, 6037556139, - 6037556140, 6037556141, 6037556142, 6037556143, 6037556144, 6037556145, - 6037556148, 6037556146, 6037556149, 6037556150, 6037556151, 6037556152, - 6037556153, 6037556154, 6037556155, 6037556156, 6037556157, 6037556158, - 6037556159, 6037556160, 6037556161, 6037556162, 6037556163, 6037556164, - 6037556165, 6037556166, 6037556167, 6037556168, 6037556169, 6037556170, - 6037556171, 6037556172, 6037556173, 6037556174, 6037556175, 6037556176, - 6037556177, 6037556178, 6037556179, 6037556180, 6037556120, 2474247506, - 1951759314, 6037556118, - ], - }, - { - type: "way", - id: 640979982, - nodes: [ - 6037556099, 1494027349, 6037556104, 1494027348, 1494027341, 1494027386, - 1494027376, 1494027385, 1494027359, 1494027374, 1494027389, 1494027382, - 1494027372, 1494027378, 1494027361, 1494027342, 1494027353, 1494027366, - 1494027373, 1494027346, 1494027368, 6037556098, 6037556105, 6037556106, - 6037556107, 6037556108, 1494027380, 1494027365, 1494027343, 1494027369, - 6037556109, 6037556110, 1494027355, 1493821404, 1801373604, 6037556111, - 6037556112, 6037556118, - ], - }, - { - type: "way", - id: 184402325, - nodes: [ - 1948835900, 416917603, 416917605, 1948835986, 416917609, 1948836072, - 1948836068, 1948836093, 1948836104, 1948836209, 1948836202, 1948836218, - 1948836237, 1948836277, 416917612, 1948836149, 1948836096, 1948835950, - ], - }, - { type: "way", id: 184402326, nodes: [1948835900, 1948835842, 1948835662] }, - { - type: "way", - id: 314899865, - nodes: [ - 2026541851, 2026541846, 2026541843, 2026541841, 2026541837, 3209560116, - 3210389695, 3209560114, 3209560115, 3209560117, 3209560118, 3209560119, - 3209560121, 3209560124, 3209560126, 3209560127, 3209560131, 3209560132, - 3209560134, 3209560135, 3209560136, 3209560138, - ], - }, - { - type: "way", - id: 315041273, - nodes: [ - 1554514739, 1554514640, 3211247042, 1554514831, 1554514750, 1554514755, - 5962496715, 1554514658, 5962338038, 1554514618, 5962444841, 1554514806, - 5962340003, 8497007481, 5962414276, 5962415500, 5962415499, 5962415498, - 5962415538, 5962415543, 3211247331, 3211247328, 3211247291, 5962444849, - 5962444850, 5962340009, 5962444846, 5962340010, 3211247059, 5962340012, - 5962340011, 3211247266, 3211247261, 5962496718, 3211247058, 5962496719, - 3211247054, 3211247265, 5962340013, 3211247060, 5962415564, 5962415565, - ], - }, - { - type: "way", - id: 606165130, - nodes: [ - 5747937641, 5747937642, 5747937643, 5747937644, 5747937645, 5747937646, - 5747937647, 5747937648, 5747937649, 5747937650, 5747937651, 5747937652, - 5747937653, 5747937654, 5747937655, 5747937656, 5747937657, 5747937658, - 5747937659, 5747535101, 5745963942, 5745963943, 5745963944, 5745963946, - 3162627509, 5745963947, 5747937668, 5747937669, 5747937670, 5747937671, - 5747937672, 5747937673, 5747937674, 5747937675, 5747937676, 5747937677, - 5747937678, 5747937679, 5747937680, 5747937681, 5747937682, 5747937683, - 5747937684, 5747937685, 5747937686, 5747937687, 5747937688, 5747937689, - 5747937690, 5747937691, 5747937641, - ], - }, - { - type: "way", - id: 184402332, - nodes: [668981668, 416917618, 1815081998, 668981667, 1948835950], - }, - { type: "way", id: 184402334, nodes: [6206789688, 1948835742, 668981668] }, - { type: "way", id: 314956402, nodes: [2026541851, 3209560139, 3209560138] }, - { type: "way", id: 663050030, nodes: [1948835662, 6206789688] }, - { type: "way", id: 631371232, nodes: [3211247019, 5962496725] }, - { type: "way", id: 631377344, nodes: [5962496725, 3211247021] }, - { - type: "way", - id: 655652321, - nodes: [ - 6142727588, 6143074993, 6142727589, 6142727598, 6142727590, 6142725081, - 6142725080, 6142725079, 6142725078, 6142725077, 6142725076, 6142725074, - 6142725084, 6142727586, 6142727585, 6142727587, 6142727591, 6142727592, - 5745963922, 6142727588, - ], - }, - { type: "way", id: 631371231, nodes: [3211247019, 3211247024] }, - { type: "way", id: 631371234, nodes: [3211247013, 3211247021] }, - { - type: "way", - id: 631377343, - nodes: [ - 1554514739, 1554514735, 1554514744, 1554514811, 5962339921, 1554514655, - 1554514679, 1554514683, 1554514765, 1554514775, 1554514773, 1554514747, - 1554514730, 1554514647, 1554514789, 1554514649, 1554514682, 1554514787, - 1554514661, 1554514726, 1554514693, 1554514703, 1554514783, - ], - }, - { - type: "way", - id: 655652319, - nodes: [ - 6142725039, 6142725040, 8638721239, 8638721230, 6142725041, 6142725042, - 6142725043, 6142725044, 6142725045, 6142725046, 6142725047, 6142725048, - 6142725049, 6142725050, 6142725051, 6142725052, 6142725053, 6142725054, - 6142725055, 6142725056, 6142725057, 6142725058, 6142725059, 6142725060, - 6142725061, 6142725064, 6142727594, 6142727595, 6142727596, 6143075018, - 6142725067, 6142725066, 6142725065, 6143075014, 3162627482, 6142727597, - 6142725039, - ], - }, - { type: "way", id: 315041261, nodes: [3211247039, 3211247036] }, - { type: "way", id: 315041263, nodes: [3211247032, 3211247029, 3211247024] }, - { type: "way", id: 631371223, nodes: [5962415565, 3211247039] }, - { type: "way", id: 631371228, nodes: [3211247034, 3211247032] }, - { type: "way", id: 631377341, nodes: [3211247036, 3211247037, 3211247034] }, - { - type: "way", - id: 631371236, - nodes: [ - 3211247013, 3211247010, 3211247008, 3211247004, 3211246997, 5962338070, - 9284562682, 5962338069, 5962338071, 3211246995, - ], - }, - { - type: "way", - id: 631371237, - nodes: [3211246995, 3211246555, 3211246542, 3203029856, 7727406921, 1554514783], - }, - ], - } - ) - - Utils.injectJsonDownloadForTests( - "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country/0.0.0.json", - ["BE"] // The entire world is belgium! - ) - Utils.injectJsonDownloadForTests( - "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country/3.4.2.json", - [5, 5, 6, 6] - ) - Utils.injectJsonDownloadForTests( - "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country/6.32.21.json", - [8, 8, 8, 8] - ) - Utils.injectJsonDownloadForTests( - "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country/8.130.85.json", - [10, 10, 10, "BE"] - ) - - Utils.injectJsonDownloadForTests( - "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country/10.521.342.json", - ["BE"] - ) -} - -describe("GenerateCache", () => { - it("should generate a cached file for the Natuurpunt-theme", async () => { - /* TODO ENABLE - // We use /var/tmp instead of /tmp, as more OS's (such as MAC) have this - const dir = "/var/tmp/" - const cachename = "nature_cache" - if (!existsSync(dir)) { - console.log("Not executing caching test: no temp directory found") - } - if (!existsSync(dir + cachename)) { - mkdirSync(dir + cachename) - } else { - ScriptUtils.readDirRecSync(dir + cachename).forEach((p) => unlinkSync(p)) - } - initDownloads( - "(nwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%3Bnwr%5B%22information%22%3D%22map%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22information%22%3D%22guidepost%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22tourism%22%3D%22map%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3Bnwr%5B%22drinking_water%22%3D%22yes%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B" - ) - await main([ - "nature", - "12", - dir + cachename, - "51.15423567022531", - "3.250579833984375", - "51.162821593316934", - "3.262810707092285", - "--generate-point-overview", - "nature_reserve", - ]) - await ScriptUtils.sleep(250) - const birdhides = JSON.parse( - readFileSync(dir + cachename + "/nature_birdhide_12_2085_1368.geojson", { - encoding: "utf8", - }) - ) - expect(birdhides.features.length).toEqual(5) - // "Didn't find birdhide node/5158056232 " - expect(birdhides.features.some((f) => f.properties.id === "node/5158056232")).toBe(true) - //*/ - }, 10000) -}) From 760276922dba78004ab0c928fd301925c76f275f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 24 Feb 2024 21:05:46 +0100 Subject: [PATCH 52/57] Re-enable osm source in all cases, fix #1798 --- .../FeatureSource/Sources/LayoutSource.ts | 45 +++++++++---------- .../NewPointLocationInput.svelte | 1 + .../InputElement/Helpers/LocationInput.svelte | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Logic/FeatureSource/Sources/LayoutSource.ts b/src/Logic/FeatureSource/Sources/LayoutSource.ts index 60f24ab64..dcb19e089 100644 --- a/src/Logic/FeatureSource/Sources/LayoutSource.ts +++ b/src/Logic/FeatureSource/Sources/LayoutSource.ts @@ -57,38 +57,37 @@ export default class LayoutSource extends FeatureSourceMerger { const nonMvtLayers = osmLayers.filter((l) => !mvtAvailableLayers.has(l.id)) const isLoading = new UIEventSource(false) + + const osmApiSource = LayoutSource.setupOsmApiSource( + osmLayers, + bounds, + zoom, + backend, + featureSwitches, + fullNodeDatabaseSource + ) + nonMvtSources.push(osmApiSource) + + let overpassSource: OverpassFeatureSource = undefined if (nonMvtLayers.length > 0) { console.log( "Layers ", nonMvtLayers.map((l) => l.id), " cannot be fetched from the cache server, defaulting to overpass/OSM-api" ) - const overpassSource = LayoutSource.setupOverpass( - osmLayers, - bounds, - zoom, - featureSwitches - ) - const osmApiSource = LayoutSource.setupOsmApiSource( - osmLayers, - bounds, - zoom, - backend, - featureSwitches, - fullNodeDatabaseSource - ) - nonMvtSources.push(overpassSource, osmApiSource) - - function setIsLoading() { - const loading = overpassSource?.runningQuery?.data || osmApiSource?.isRunning?.data - isLoading.setData(loading) - } - - overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading()) - osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading()) + overpassSource = LayoutSource.setupOverpass(osmLayers, bounds, zoom, featureSwitches) + nonMvtSources.push(overpassSource) supportsForceDownload.push(overpassSource) } + function setIsLoading() { + const loading = overpassSource?.runningQuery?.data || osmApiSource?.isRunning?.data + isLoading.setData(loading) + } + + overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading()) + osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading()) + const geojsonSources: UpdatableFeatureSource[] = geojsonlayers.map((l) => LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id)) ) diff --git a/src/UI/BigComponents/NewPointLocationInput.svelte b/src/UI/BigComponents/NewPointLocationInput.svelte index 6f0a69f9a..95d1b72cb 100644 --- a/src/UI/BigComponents/NewPointLocationInput.svelte +++ b/src/UI/BigComponents/NewPointLocationInput.svelte @@ -87,6 +87,7 @@ if (snapToLayers?.length > 0) { const snapSources: FeatureSource[] = [] for (const layerId of snapToLayers ?? []) { + // We assume that the layer contains the data, as the OSM-API-Feature-source should have loaded them, even though the layer might not be displayed const layer: FeatureSourceForLayer = state.perLayer.get(layerId) snapSources.push(layer) if (layer.features === undefined) { diff --git a/src/UI/InputElement/Helpers/LocationInput.svelte b/src/UI/InputElement/Helpers/LocationInput.svelte index a48492136..3aad6bf8f 100644 --- a/src/UI/InputElement/Helpers/LocationInput.svelte +++ b/src/UI/InputElement/Helpers/LocationInput.svelte @@ -66,7 +66,7 @@ if (!rangeIsShown) { new ShowDataLayer(map, { - layer: new LayerConfig(boundsdisplay), + layer: new LayerConfig( boundsdisplay), features: new StaticFeatureSource([ turf.circle(c, maxDistanceInMeters, { units: "meters", From b37ea885a56d1de518f0ed7fedf3761c94eeb1e9 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 25 Feb 2024 02:49:34 +0100 Subject: [PATCH 53/57] Correctly handle non-working optional match groups in generate build_db script --- scripts/osm2pgsql/generateBuildDbScript.ts | 96 ++++++++++++++-------- 1 file changed, 61 insertions(+), 35 deletions(-) diff --git a/scripts/osm2pgsql/generateBuildDbScript.ts b/scripts/osm2pgsql/generateBuildDbScript.ts index 5f6eb28b7..7ddd217f0 100644 --- a/scripts/osm2pgsql/generateBuildDbScript.ts +++ b/scripts/osm2pgsql/generateBuildDbScript.ts @@ -10,28 +10,27 @@ import { AllKnownLayouts } from "../../src/Customizations/AllKnownLayouts" import { OsmObject } from "../../src/Logic/Osm/OsmObject" class LuaSnippets { - public static helpers = [ "function countTbl(tbl)\n" + - " local c = 0\n" + - " for n in pairs(tbl) do \n" + - " c = c + 1 \n" + - " end\n" + - " return c\n" + - "end", + " local c = 0\n" + + " for n in pairs(tbl) do \n" + + " c = c + 1 \n" + + " end\n" + + " return c\n" + + "end", ].join("\n") - public static isPolygonFeature(): { blacklist: TagsFilter, whitelisted: TagsFilter } { + public static isPolygonFeature(): { blacklist: TagsFilter; whitelisted: TagsFilter } { const dict = OsmObject.polygonFeatures const or: TagsFilter[] = [] - const blacklisted : TagsFilter[] = [] + const blacklisted: TagsFilter[] = [] dict.forEach(({ values, blacklist }, k) => { - if(blacklist){ - if(values === undefined){ + if (blacklist) { + if (values === undefined) { blacklisted.push(new RegexTag(k, /.+/is)) return } - values.forEach(v => { + values.forEach((v) => { blacklisted.push(new RegexTag(k, v)) }) return @@ -40,11 +39,14 @@ class LuaSnippets { or.push(new RegexTag(k, /.+/is)) return } - values.forEach(v => { + values.forEach((v) => { or.push(new RegexTag(k, v)) }) }) - console.log("Polygon features are:", or.map(t => t.asHumanString(false, false, {}))) + console.log( + "Polygon features are:", + or.map((t) => t.asHumanString(false, false, {})) + ) return { blacklist: new Or(blacklisted), whitelisted: new Or(or) } } @@ -53,14 +55,14 @@ class LuaSnippets { return `object.tags["${tag.key}"] == "${tag.value}"` } if (tag instanceof And) { - const expr = tag.and.map(t => this.toLuaFilter(t, true)).join(" and ") + const expr = tag.and.map((t) => this.toLuaFilter(t, true)).join(" and ") if (useParens) { return "(" + expr + ")" } return expr } if (tag instanceof Or) { - const expr = tag.or.map(t => this.toLuaFilter(t, true)).join(" or ") + const expr = tag.or.map((t) => this.toLuaFilter(t, true)).join(" or ") if (useParens) { return "(" + expr + ")" } @@ -87,7 +89,7 @@ class LuaSnippets { return `object.tags["${tag.key}"] == "${tag.value}"` } - const v = (tag.value).source.replace(/\\\//g, "/") + let v: string = (tag.value).source.replace(/\\\//g, "/") if ("" + tag.value === "/.+/is" && !tag.invert) { return `object.tags["${tag.key}"] ~= nil` @@ -101,18 +103,35 @@ class LuaSnippets { return `object.tags["${tag.key}"] == nil or object.tags["${tag.key}"] == ""` } - if (tag.matchesEmpty && tag.invert) { return `object.tags["${tag.key}"] ~= nil or object.tags["${tag.key}"] ~= ""` } + let head = "^((.*;)?" + let tail = "(;.*)?)$" + if (v.startsWith(head)) { + v = "(" + v.substring(head.length) + } + if (v.endsWith(tail)) { + v = v.substring(0, v.length - tail.length) + ")" + // We basically remove the optional parts at the start and the end, as object.find has this freedom anyway. + // This might result in _some_ incorrect values that end up in the database (e.g. when matching 'friture', it might als match "abc;foo_friture_bar;xyz", but the frontend will filter this out + } + + if (v.indexOf(")?") > 0) { + throw ( + "LUA regexes have a bad support for (optional) capture groups, as such, " + + v + + " is not supported" + ) + } + if (tag.invert) { return `object.tags["${tag.key}"] == nil or not string.find(object.tags["${tag.key}"], "${v}")` } return `(object.tags["${tag.key}"] ~= nil and string.find(object.tags["${tag.key}"], "${v}"))` } - } class GenerateLayerLua { @@ -163,8 +182,6 @@ class GenerateLayerLua { "", ].join("\n") } - - } class GenerateBuildDbScript extends Script { @@ -174,7 +191,7 @@ class GenerateBuildDbScript extends Script { async main(args: string[]) { const allNeededLayers = new ValidateThemeEnsemble().convertStrict( - AllKnownLayouts.allKnownLayouts.values(), + AllKnownLayouts.allKnownLayouts.values() ) const generators: GenerateLayerLua[] = [] @@ -186,29 +203,30 @@ class GenerateBuildDbScript extends Script { const script = [ "local db_tables = {}", LuaSnippets.helpers, - ...generators.map(g => g.generateTables()), + ...generators.map((g) => g.generateTables()), this.generateProcessPoi(allNeededLayers), this.generateProcessWay(allNeededLayers), ].join("\n\n\n") const path = "build_db.lua" fs.writeFileSync(path, script, "utf-8") console.log("Written", path) - console.log(allNeededLayers.size + " layers will be created with 3 tables each. Make sure to set 'max_connections' to at least " + (10 + 3 * allNeededLayers.size)) + console.log( + allNeededLayers.size + + " layers will be created with 3 tables each. Make sure to set 'max_connections' to at least " + + (10 + 3 * allNeededLayers.size) + ) } private earlyAbort() { - return [" if countTbl(object.tags) == 0 then", - " return", - " end", - ""].join("\n") + return [" if countTbl(object.tags) == 0 then", " return", " end", ""].join("\n") } - private generateProcessPoi(allNeededLayers: Map) { + private generateProcessPoi( + allNeededLayers: Map + ) { const body: string[] = [] allNeededLayers.forEach(({ tags }, layerId) => { - body.push( - this.insertInto(tags, layerId, "pois_").join("\n"), - ) + body.push(this.insertInto(tags, layerId, "pois_").join("\n")) }) return [ @@ -228,7 +246,11 @@ class GenerateBuildDbScript extends Script { * @param tableprefix * @private */ - private insertInto(tags: TagsFilter, layerId: string, tableprefix: "pois_" | "lines_" | "polygons_") { + private insertInto( + tags: TagsFilter, + layerId: string, + tableprefix: "pois_" | "lines_" | "polygons_" + ) { const filter = LuaSnippets.toLuaFilter(tags) return [ " matches_filter = " + filter, @@ -265,8 +287,12 @@ class GenerateBuildDbScript extends Script { "", "function osm2pgsql.process_way(object)", this.earlyAbort(), - " local object_is_line = not object.is_closed or "+LuaSnippets.toLuaFilter(isPolygon.blacklist), - ` local object_is_area = object.is_closed and (object.tags["area"] == "yes" or (not object_is_line and ${LuaSnippets.toLuaFilter(isPolygon.whitelisted, true)}))`, + " local object_is_line = not object.is_closed or " + + LuaSnippets.toLuaFilter(isPolygon.blacklist), + ` local object_is_area = object.is_closed and (object.tags["area"] == "yes" or (not object_is_line and ${LuaSnippets.toLuaFilter( + isPolygon.whitelisted, + true + )}))`, " if object_is_area then", " process_polygon(object, object:as_polygon())", " else", From 5999c01c6ab242cba7af5df1c60191ca09110088 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 25 Feb 2024 12:35:27 +0100 Subject: [PATCH 54/57] Refactoring: fix small typing error --- src/UI/ThemeViewGUI.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index a42acc3c8..2dac52e78 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -281,7 +281,7 @@ > {#if $currentZoom < Constants.minZoomLevelToAddNewPoint} - {:else if state.lastClickObject.hasPresets} + {:else if state.layout.hasPresets()} {:else} From 4e1f4391279681caf15ce08254afb06a46e59a18 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 25 Feb 2024 12:40:07 +0100 Subject: [PATCH 55/57] Tone down etymology range, fix #1562 --- assets/layers/etymology/etymology.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/layers/etymology/etymology.json b/assets/layers/etymology/etymology.json index 45d9bdf68..443c1f705 100644 --- a/assets/layers/etymology/etymology.json +++ b/assets/layers/etymology/etymology.json @@ -30,7 +30,7 @@ } }, "calculatedTags": [ - "_same_name_ids=closestn(feat)('*', 250, undefined, 5000)?.filter(f => f.feat.properties.name === feat.properties.name)?.map(f => f.feat.properties.id)??[]" + "_same_name_ids=closestn(feat)('*', 250, undefined, 3000)?.filter(f => f.feat.properties.name === feat.properties.name)?.map(f => f.feat.properties.id)??[]" ], "minzoom": 12, "title": { From db92a600d569adc02a86c70125feed64e624bdc5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 25 Feb 2024 18:17:52 +0100 Subject: [PATCH 56/57] Add feedback to wikidata-input, fix #1650 --- langs/en.json | 4 +++- .../InputElement/Validators/WikidataValidator.ts | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/langs/en.json b/langs/en.json index 7f192dfac..6979f1fe8 100644 --- a/langs/en.json +++ b/langs/en.json @@ -785,7 +785,9 @@ "feedback": "This is not a valid web address" }, "wikidata": { - "description": "A Wikidata identifier" + "description": "A Wikidata identifier", + "empty": "Please, enter some wikidata-entries", + "startsWithQ": "A wikidata identifier starts with Q and is followed by a number" } } } diff --git a/src/UI/InputElement/Validators/WikidataValidator.ts b/src/UI/InputElement/Validators/WikidataValidator.ts index 48910a5ab..b2fc48c48 100644 --- a/src/UI/InputElement/Validators/WikidataValidator.ts +++ b/src/UI/InputElement/Validators/WikidataValidator.ts @@ -2,6 +2,8 @@ import Combine from "../../Base/Combine" import Wikidata from "../../../Logic/Web/Wikidata" import WikidataSearchBox from "../../Wikipedia/WikidataSearchBox" import { Validator } from "../Validator" +import { Translation } from "../../i18n/Translation" +import Translations from "../../i18n/Translations" export default class WikidataValidator extends Validator { constructor() { @@ -12,12 +14,23 @@ export default class WikidataValidator extends Validator { if (str === undefined) { return false } - if (str.length <= 2) { + if (str.length == 1) { return false } return !str.split(";").some((str) => Wikidata.ExtractKey(str) === undefined) } + getFeedback(s: string, _?: () => string): Translation | undefined { + const t = Translations.t.validation.wikidata + if (s === "") { + return t.empty + } + if (!s.match(/(Q[0-9]+)(;Q[0-9]+)*/)) { + return t.startsWithQ + } + return undefined + } + public reformat(str) { if (str === undefined) { return undefined From c53fc3333921cbd9f58993016e218b99ddff0ad9 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 26 Feb 2024 13:33:21 +0100 Subject: [PATCH 57/57] Increase cache time --- scripts/osm2pgsql/tilecountServer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/osm2pgsql/tilecountServer.ts b/scripts/osm2pgsql/tilecountServer.ts index 6f92f6e8e..86aa8c4d7 100644 --- a/scripts/osm2pgsql/tilecountServer.ts +++ b/scripts/osm2pgsql/tilecountServer.ts @@ -162,7 +162,7 @@ class CachedSqlCount { const connectionString = "postgresql://user:password@localhost:5444/osm-poi" const tcs = new OsmPoiDatabase(connectionString) -const withCache = new CachedSqlCount(tcs, 60 * 60 * 24) +const withCache = new CachedSqlCount(tcs, 14 * 60 * 60 * 24) new Server(2345, { ignorePathPrefix: ["summary"] }, [ { mustMatch: "status.json",