From ef2f1487c6dee52f52e81319b7f9f3bf9094bcce Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 22 Jan 2024 01:42:05 +0100 Subject: [PATCH] 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 @@
- +