forked from MapComplete/MapComplete
Feature: offline basemaps: first working version (without ability to install basemaps from the UI)
This commit is contained in:
parent
4c858fbe7e
commit
2cd0b11448
13 changed files with 494 additions and 178 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -21,6 +21,7 @@ missing_translations.txt
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Svg.ts
|
Svg.ts
|
||||||
data/
|
data/
|
||||||
|
src/service-worker/.rollup.cache
|
||||||
|
|
||||||
Folder.DotSettings.user
|
Folder.DotSettings.user
|
||||||
index_*.ts
|
index_*.ts
|
||||||
|
|
106
package-lock.json
generated
106
package-lock.json
generated
|
@ -19,6 +19,7 @@
|
||||||
"@rapideditor/location-conflation": "^1.3.0",
|
"@rapideditor/location-conflation": "^1.3.0",
|
||||||
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
||||||
"@rgossiaux/svelte-heroicons": "^0.1.2",
|
"@rgossiaux/svelte-heroicons": "^0.1.2",
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.6",
|
||||||
"@rollup/plugin-typescript": "^11.0.0",
|
"@rollup/plugin-typescript": "^11.0.0",
|
||||||
"@turf/boolean-intersects": "^7.2.0",
|
"@turf/boolean-intersects": "^7.2.0",
|
||||||
"@turf/buffer": "^7.2.0",
|
"@turf/buffer": "^7.2.0",
|
||||||
|
@ -27,13 +28,6 @@
|
||||||
"@turf/distance": "^7.2.0",
|
"@turf/distance": "^7.2.0",
|
||||||
"@turf/length": "^7.2.0",
|
"@turf/length": "^7.2.0",
|
||||||
"@turf/turf": "^7.2.0",
|
"@turf/turf": "^7.2.0",
|
||||||
"@types/dompurify": "^3.0.2",
|
|
||||||
"@types/follow-redirects": "^1.14.4",
|
|
||||||
"@types/node": "^22.13.5",
|
|
||||||
"@types/pannellum": "^2.5.0",
|
|
||||||
"@types/pg": "^8.11.11",
|
|
||||||
"@types/qrcode-generator": "^1.0.6",
|
|
||||||
"@types/showdown": "^2.0.0",
|
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"chart.js": "^3.8.0",
|
"chart.js": "^3.8.0",
|
||||||
"comunica": "^2.0.0",
|
"comunica": "^2.0.0",
|
||||||
|
@ -103,12 +97,19 @@
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.0.2",
|
"@sveltejs/vite-plugin-svelte": "^2.0.2",
|
||||||
"@tsconfig/svelte": "^3.0.0",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
"@types/chai": "^5.0.1",
|
"@types/chai": "^5.0.1",
|
||||||
|
"@types/dompurify": "^3.0.2",
|
||||||
|
"@types/follow-redirects": "^1.14.4",
|
||||||
"@types/geojson": "^7946.0.10",
|
"@types/geojson": "^7946.0.10",
|
||||||
"@types/jsonld": "^1.5.13",
|
"@types/jsonld": "^1.5.13",
|
||||||
"@types/lz-string": "^1.3.34",
|
"@types/lz-string": "^1.3.34",
|
||||||
"@types/mocha": "^10.0.1",
|
"@types/mocha": "^10.0.1",
|
||||||
|
"@types/node": "^22.13.5",
|
||||||
|
"@types/pannellum": "^2.5.0",
|
||||||
"@types/papaparse": "^5.3.15",
|
"@types/papaparse": "^5.3.15",
|
||||||
|
"@types/pg": "^8.11.11",
|
||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
|
"@types/qrcode-generator": "^1.0.6",
|
||||||
|
"@types/showdown": "^2.0.0",
|
||||||
"@types/xml2js": "^0.4.9",
|
"@types/xml2js": "^0.4.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||||
"@typescript-eslint/parser": "^6.1.0",
|
"@typescript-eslint/parser": "^6.1.0",
|
||||||
|
@ -6508,6 +6509,67 @@
|
||||||
"svelte": "^3.44.0"
|
"svelte": "^3.44.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs": {
|
||||||
|
"version": "28.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz",
|
||||||
|
"integrity": "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"commondir": "^1.0.1",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"fdir": "^6.2.0",
|
||||||
|
"is-reference": "1.2.1",
|
||||||
|
"magic-string": "^0.30.3",
|
||||||
|
"picomatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0 || 14 >= 14.17"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.68.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs/node_modules/fdir": {
|
||||||
|
"version": "6.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
|
||||||
|
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"picomatch": "^3 || ^4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"picomatch": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs/node_modules/magic-string": {
|
||||||
|
"version": "0.30.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||||
|
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs/node_modules/picomatch": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-json": {
|
"node_modules/@rollup/plugin-json": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
@ -11502,6 +11564,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@types/dompurify": {
|
"node_modules/@types/dompurify": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/trusted-types": "*"
|
"@types/trusted-types": "*"
|
||||||
|
@ -11516,6 +11579,7 @@
|
||||||
"version": "1.14.4",
|
"version": "1.14.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/follow-redirects/-/follow-redirects-1.14.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/follow-redirects/-/follow-redirects-1.14.4.tgz",
|
||||||
"integrity": "sha512-GWXfsD0Jc1RWiFmMuMFCpXMzi9L7oPDVwxUnZdg89kDNnqsRfUKXEtUYtA98A6lig1WXH/CYY/fvPW9HuN5fTA==",
|
"integrity": "sha512-GWXfsD0Jc1RWiFmMuMFCpXMzi9L7oPDVwxUnZdg89kDNnqsRfUKXEtUYtA98A6lig1WXH/CYY/fvPW9HuN5fTA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
|
@ -11630,6 +11694,7 @@
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/pannellum/-/pannellum-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/pannellum/-/pannellum-2.5.0.tgz",
|
||||||
"integrity": "sha512-iFVwMHmsTx91t74gU12bDmB1ty5lRgmfK6X+FxymQe8n0nuw3Pp/vk0nw73YdL9WqZgthrpf1KLPzQjZDUsj0g==",
|
"integrity": "sha512-iFVwMHmsTx91t74gU12bDmB1ty5lRgmfK6X+FxymQe8n0nuw3Pp/vk0nw73YdL9WqZgthrpf1KLPzQjZDUsj0g==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/papaparse": {
|
"node_modules/@types/papaparse": {
|
||||||
|
@ -11650,6 +11715,7 @@
|
||||||
"version": "8.11.11",
|
"version": "8.11.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz",
|
||||||
"integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==",
|
"integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
|
@ -11659,6 +11725,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@types/pg/node_modules/pg-types": {
|
"node_modules/@types/pg/node_modules/pg-types": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-int8": "1.0.1",
|
"pg-int8": "1.0.1",
|
||||||
|
@ -11675,6 +11742,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@types/pg/node_modules/postgres-array": {
|
"node_modules/@types/pg/node_modules/postgres-array": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
@ -11682,6 +11750,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@types/pg/node_modules/postgres-bytea": {
|
"node_modules/@types/pg/node_modules/postgres-bytea": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"obuf": "~1.1.2"
|
"obuf": "~1.1.2"
|
||||||
|
@ -11692,6 +11761,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@types/pg/node_modules/postgres-date": {
|
"node_modules/@types/pg/node_modules/postgres-date": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
@ -11699,6 +11769,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@types/pg/node_modules/postgres-interval": {
|
"node_modules/@types/pg/node_modules/postgres-interval": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
@ -11719,6 +11790,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@types/qrcode-generator": {
|
"node_modules/@types/qrcode-generator": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"qrcode-generator": "*"
|
"qrcode-generator": "*"
|
||||||
|
@ -11752,6 +11824,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@types/showdown": {
|
"node_modules/@types/showdown": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/slice-ansi": {
|
"node_modules/@types/slice-ansi": {
|
||||||
|
@ -11785,6 +11858,7 @@
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/uritemplate": {
|
"node_modules/@types/uritemplate": {
|
||||||
|
@ -13160,6 +13234,12 @@
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/commondir": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/compare-func": {
|
"node_modules/compare-func": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
|
||||||
|
@ -17120,6 +17200,15 @@
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/is-reference": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-regex": {
|
"node_modules/is-regex": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||||
|
@ -23030,6 +23119,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/obuf": {
|
"node_modules/obuf": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
|
@ -23401,6 +23491,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/pg-numeric": {
|
"node_modules/pg-numeric": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
|
@ -23783,6 +23874,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/postgres-range": {
|
"node_modules/postgres-range": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/potpack": {
|
"node_modules/potpack": {
|
||||||
|
|
19
package.json
19
package.json
|
@ -108,7 +108,7 @@
|
||||||
"query:licenses": "vite-node scripts/generateLicenseInfo.ts -- --query && npm run generate:licenses",
|
"query:licenses": "vite-node scripts/generateLicenseInfo.ts -- --query && npm run generate:licenses",
|
||||||
"clean:licenses": "find . -type f -name \"*.license\" -exec rm -f {} +",
|
"clean:licenses": "find . -type f -name \"*.license\" -exec rm -f {} +",
|
||||||
"generate:contributor-list": "vite-node scripts/generateContributors.ts",
|
"generate:contributor-list": "vite-node scripts/generateContributors.ts",
|
||||||
"generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i.bak \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js && rm public/service-worker.js.bak",
|
"generate:service-worker": "cd ./src/service-worker/ && rollup -c ",
|
||||||
"generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run reset:layeroverview && npm run generate:service-worker",
|
"generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run reset:layeroverview && npm run generate:service-worker",
|
||||||
"generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -",
|
"generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -",
|
||||||
"clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm",
|
"clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm",
|
||||||
|
@ -184,6 +184,7 @@
|
||||||
"@rapideditor/location-conflation": "^1.3.0",
|
"@rapideditor/location-conflation": "^1.3.0",
|
||||||
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
||||||
"@rgossiaux/svelte-heroicons": "^0.1.2",
|
"@rgossiaux/svelte-heroicons": "^0.1.2",
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.6",
|
||||||
"@rollup/plugin-typescript": "^11.0.0",
|
"@rollup/plugin-typescript": "^11.0.0",
|
||||||
"@turf/boolean-intersects": "^7.2.0",
|
"@turf/boolean-intersects": "^7.2.0",
|
||||||
"@turf/buffer": "^7.2.0",
|
"@turf/buffer": "^7.2.0",
|
||||||
|
@ -192,13 +193,6 @@
|
||||||
"@turf/distance": "^7.2.0",
|
"@turf/distance": "^7.2.0",
|
||||||
"@turf/length": "^7.2.0",
|
"@turf/length": "^7.2.0",
|
||||||
"@turf/turf": "^7.2.0",
|
"@turf/turf": "^7.2.0",
|
||||||
"@types/dompurify": "^3.0.2",
|
|
||||||
"@types/follow-redirects": "^1.14.4",
|
|
||||||
"@types/node": "^22.13.5",
|
|
||||||
"@types/pannellum": "^2.5.0",
|
|
||||||
"@types/pg": "^8.11.11",
|
|
||||||
"@types/qrcode-generator": "^1.0.6",
|
|
||||||
"@types/showdown": "^2.0.0",
|
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"chart.js": "^3.8.0",
|
"chart.js": "^3.8.0",
|
||||||
"comunica": "^2.0.0",
|
"comunica": "^2.0.0",
|
||||||
|
@ -268,12 +262,19 @@
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.0.2",
|
"@sveltejs/vite-plugin-svelte": "^2.0.2",
|
||||||
"@tsconfig/svelte": "^3.0.0",
|
"@tsconfig/svelte": "^3.0.0",
|
||||||
"@types/chai": "^5.0.1",
|
"@types/chai": "^5.0.1",
|
||||||
|
"@types/dompurify": "^3.0.2",
|
||||||
|
"@types/follow-redirects": "^1.14.4",
|
||||||
"@types/geojson": "^7946.0.10",
|
"@types/geojson": "^7946.0.10",
|
||||||
"@types/jsonld": "^1.5.13",
|
"@types/jsonld": "^1.5.13",
|
||||||
"@types/lz-string": "^1.3.34",
|
"@types/lz-string": "^1.3.34",
|
||||||
"@types/mocha": "^10.0.1",
|
"@types/mocha": "^10.0.1",
|
||||||
|
"@types/node": "^22.13.5",
|
||||||
|
"@types/pannellum": "^2.5.0",
|
||||||
"@types/papaparse": "^5.3.15",
|
"@types/papaparse": "^5.3.15",
|
||||||
|
"@types/pg": "^8.11.11",
|
||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
|
"@types/qrcode-generator": "^1.0.6",
|
||||||
|
"@types/showdown": "^2.0.0",
|
||||||
"@types/xml2js": "^0.4.9",
|
"@types/xml2js": "^0.4.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||||
"@typescript-eslint/parser": "^6.1.0",
|
"@typescript-eslint/parser": "^6.1.0",
|
||||||
|
@ -300,4 +301,4 @@
|
||||||
"vite-node": "^3.0.5",
|
"vite-node": "^3.0.5",
|
||||||
"vitest": "^3.2.1"
|
"vitest": "^3.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
"protomaps": {
|
"protomaps": {
|
||||||
"attribution": "<a href=\"https://github.com/protomaps/basemaps\">Protomaps</a> © <a href=\"https://openstreetmap.org\">OpenStreetMap</a>",
|
"attribution": "<a href=\"https://github.com/protomaps/basemaps\">Protomaps</a> © <a href=\"https://openstreetmap.org\">OpenStreetMap</a>",
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"url": "pmtiles://https://cache.mapcomplete.org/planet-latest.pmtiles",
|
"tiles": [
|
||||||
"maxzoom": 15
|
"https://127.0.0.1/service-worker/offline-basemap/{z}-{x}-{y}.mvt"
|
||||||
|
],
|
||||||
|
"maxzoom": 15,
|
||||||
|
"minzoom": 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layers": [
|
"layers": [
|
||||||
|
|
39
scripts/generatePmTilesExtractionScript.ts
Normal file
39
scripts/generatePmTilesExtractionScript.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import Script from "./Script"
|
||||||
|
import { Tiles } from "../src/Models/TileRange"
|
||||||
|
|
||||||
|
class GeneratePmTilesExtractionScript extends Script {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super("Generates a bash script to extract all subpyramids of maxzoom=8 from planet-latest.pmtiles")
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitRange(z: number, x: number, y: number, maxzoom?: number): string {
|
||||||
|
const [[max_lat, min_lon], [min_lat, max_lon]] = Tiles.tile_bounds(z, x, y)
|
||||||
|
let maxzoomflag = ""
|
||||||
|
if(maxzoom !== undefined){
|
||||||
|
maxzoomflag = " --maxzoom="+maxzoom
|
||||||
|
}
|
||||||
|
return (`./pmtiles extract planet-latest.pmtiles --minzoom=${z}${maxzoomflag} --bbox=${[min_lon, min_lat + 0.0001, max_lon, max_lat].join(",")} ${z}-${x}-${y}.pmtiles`)
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateField(z: number, maxzoom?:number){
|
||||||
|
const boundary = 2 << z
|
||||||
|
for (let x = 0; x < boundary; x++) {
|
||||||
|
const xCommands = []
|
||||||
|
for (let y = 0; y < boundary; y++) {
|
||||||
|
xCommands.push(this.emitRange(z, x, y,maxzoom))
|
||||||
|
}
|
||||||
|
console.log(xCommands.join(" && ") + " && echo 'All pyramids for x = " + x + " are generated' & ")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async main(): Promise<void> {
|
||||||
|
this.generateField(0, 4)
|
||||||
|
this.generateField(5, 8)
|
||||||
|
this.generateField(9)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
new GeneratePmTilesExtractionScript().run()
|
|
@ -5,7 +5,7 @@ window.addEventListener("load", async () => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await navigator.serviceWorker.register("/service-worker.js")
|
await navigator.serviceWorker.register("/service-worker.js", { type: "module" })
|
||||||
console.log("Service worker registration successful")
|
console.log("Service worker registration successful")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Service worker registration failed", err)
|
console.error("Service worker registration failed", err)
|
||||||
|
|
|
@ -1,63 +1,4 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PMTiles } from "pmtiles"
|
|
||||||
|
|
||||||
// What piece of the map to download and cache locally for the current map view?
|
|
||||||
const gentBBox = {
|
|
||||||
"type": "Feature",
|
|
||||||
"properties": {},
|
|
||||||
"geometry": {
|
|
||||||
"coordinates": [
|
|
||||||
[
|
|
||||||
[
|
|
||||||
3.6911333519349228,
|
|
||||||
51.069673467969096
|
|
||||||
],
|
|
||||||
[
|
|
||||||
3.6911333519349228,
|
|
||||||
51.02886180987505
|
|
||||||
],
|
|
||||||
[
|
|
||||||
3.773873676856965,
|
|
||||||
51.02886180987505
|
|
||||||
],
|
|
||||||
[
|
|
||||||
3.773873676856965,
|
|
||||||
51.069673467969096
|
|
||||||
],
|
|
||||||
[
|
|
||||||
3.6911333519349228,
|
|
||||||
51.069673467969096
|
|
||||||
]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
"type": "Polygon"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL or fetch source of the PMTiles archive
|
|
||||||
let source = new PMTiles("https://cache.mapcomplete.org/planet-latest.pmtiles")
|
|
||||||
|
|
||||||
async function test(z: number, x: number, y: number) {
|
|
||||||
|
|
||||||
|
|
||||||
const meta = await source.getMetadata()
|
|
||||||
console.log(meta)
|
|
||||||
const minzoom = meta["minzoom"]
|
|
||||||
const maxzoom = meta["maxzoom"]
|
|
||||||
console.log("Range is", minzoom, maxzoom)
|
|
||||||
for (const l of meta["vector_layers"]) {
|
|
||||||
// console.log(l)
|
|
||||||
}
|
|
||||||
// const header = await source.getHeader()
|
|
||||||
// console.log(header)
|
|
||||||
let resp = await source.getZxy(z, x, y)
|
|
||||||
console.log(resp)
|
|
||||||
// let tileData = resp?.data
|
|
||||||
// do something with tileData
|
|
||||||
}
|
|
||||||
|
|
||||||
test(14, 8338, 5469) // Elf-julistraat Brugge
|
|
||||||
test(15, 12233, 10534)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
Nothing to test
|
Nothing to test
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
const version = "0.0.0"
|
|
||||||
|
|
||||||
interface ServiceWorkerFetchEvent extends Event {
|
|
||||||
request: RequestInfo & { url: string }
|
|
||||||
respondWith: (response: any | PromiseLike<Response>) => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
async function install() {
|
|
||||||
console.log("Installing service worker!")
|
|
||||||
}
|
|
||||||
|
|
||||||
addEventListener("install", (e) => (<any>e).waitUntil(install()))
|
|
||||||
addEventListener("activate", (e) => (<any>e).waitUntil(activate()))
|
|
||||||
|
|
||||||
async function clearCaches(exceptVersion = undefined) {
|
|
||||||
const keys = await caches.keys()
|
|
||||||
await Promise.all(keys.map((k) => k !== version && caches.delete(k)))
|
|
||||||
console.log("Cleared caches")
|
|
||||||
}
|
|
||||||
|
|
||||||
async function activate() {
|
|
||||||
console.log("Activating service worker")
|
|
||||||
await clearCaches(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchAndCache(event: ServiceWorkerFetchEvent): Promise<Response> {
|
|
||||||
const networkResponse = await fetch(event.request)
|
|
||||||
const cache = await caches.open(version)
|
|
||||||
await cache.put(event.request, networkResponse.clone())
|
|
||||||
console.log("Cached", event.request)
|
|
||||||
return networkResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
async function cacheFirst(event: ServiceWorkerFetchEvent, attemptUpdate: boolean = false) {
|
|
||||||
const cacheResponse = await caches.match(event.request, { ignoreSearch: true })
|
|
||||||
if (cacheResponse === undefined) {
|
|
||||||
return fetchAndCache(event)
|
|
||||||
}
|
|
||||||
console.debug("Loaded from cache: ", event.request)
|
|
||||||
if (attemptUpdate) {
|
|
||||||
fetchAndCache(event)
|
|
||||||
}
|
|
||||||
return cacheResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
const neverCache: RegExp[] = [/\.html$/, /service-worker/]
|
|
||||||
const neverCacheHost: RegExp[] = [/127\.0\.0\.[0-9]+/, /\.local/, /\.gitpod\.io/, /localhost/]
|
|
||||||
|
|
||||||
async function handleRequest(event: ServiceWorkerFetchEvent) {
|
|
||||||
const origin = new URL(self.origin)
|
|
||||||
const requestUrl = new URL(event.request.url)
|
|
||||||
if (requestUrl.pathname.endsWith("service-worker-version")) {
|
|
||||||
console.log("Sending version number...")
|
|
||||||
await event.respondWith(new Response(JSON.stringify({ "service-worker-version": version })))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (requestUrl.pathname.endsWith("/service-worker-clear")) {
|
|
||||||
await clearCaches()
|
|
||||||
await event.respondWith(new Response(JSON.stringify({ "cache-cleared": true })))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldBeCached =
|
|
||||||
origin.host === requestUrl.host &&
|
|
||||||
!neverCacheHost.some((blacklisted) => origin.host.match(blacklisted)) &&
|
|
||||||
!neverCache.some((blacklisted) => event.request.url.match(blacklisted))
|
|
||||||
if (!shouldBeCached) {
|
|
||||||
console.debug("Not intercepting ", requestUrl.toString(), origin.host, requestUrl.host)
|
|
||||||
// We return _without_ calling event.respondWith, which signals the browser that it'll have to handle it himself
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await event.respondWith(await cacheFirst(event))
|
|
||||||
}
|
|
||||||
|
|
||||||
self.addEventListener("fetch", async (e) => {
|
|
||||||
// Important: this lambda must run synchronously, as the browser will otherwise handle the request
|
|
||||||
const event: ServiceWorkerFetchEvent = <ServiceWorkerFetchEvent>e
|
|
||||||
try {
|
|
||||||
await handleRequest(event)
|
|
||||||
} catch (e) {
|
|
||||||
console.error("CRASH IN SW:", e)
|
|
||||||
await event.respondWith(fetch(event.request.url))
|
|
||||||
}
|
|
||||||
})
|
|
257
src/service-worker/OfflineBasemapManager.ts
Normal file
257
src/service-worker/OfflineBasemapManager.ts
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
import { PMTiles, RangeResponse, Source } from "pmtiles"
|
||||||
|
|
||||||
|
|
||||||
|
interface AreaDescription {
|
||||||
|
/**
|
||||||
|
* Thie filename at the host and in the indexedDb
|
||||||
|
* Host name is not included
|
||||||
|
*/
|
||||||
|
name: string
|
||||||
|
/**
|
||||||
|
* Minzoom that is covered, inclusive
|
||||||
|
*/
|
||||||
|
minzoom: number,
|
||||||
|
/**
|
||||||
|
* Maxzoom that is covered, inclusive
|
||||||
|
*/
|
||||||
|
maxzoom: number,
|
||||||
|
/**
|
||||||
|
* The x, y of the tile that is covered (at minzoom)
|
||||||
|
*/
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ISO-datestring of when the data was processed
|
||||||
|
*/
|
||||||
|
dataVersion?: string
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypedIdb<T> {
|
||||||
|
private readonly _db: Promise<IDBDatabase>
|
||||||
|
private readonly _name: string
|
||||||
|
|
||||||
|
|
||||||
|
constructor(db: string) {
|
||||||
|
this._name = db
|
||||||
|
this._db = TypedIdb.openDb(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static openDb(name: string): Promise<IDBDatabase> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
const request: IDBOpenDBRequest = indexedDB.open(name)
|
||||||
|
request.onerror = (event) => {
|
||||||
|
console.error("Could not open the Database: ", event)
|
||||||
|
reject(event)
|
||||||
|
}
|
||||||
|
request.onupgradeneeded = () => {
|
||||||
|
const db = request.result
|
||||||
|
db.createObjectStore(name)
|
||||||
|
}
|
||||||
|
request.onsuccess = () => {
|
||||||
|
resolve(request.result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async set(key: string, value: T): Promise<void> {
|
||||||
|
const db = await this._db
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction([this._name], "readwrite")
|
||||||
|
const store = tx.objectStore(this._name)
|
||||||
|
const request = store.put(value, key)
|
||||||
|
|
||||||
|
request.onsuccess = () => resolve()
|
||||||
|
request.onerror = () => reject(request.error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key: string): Promise<T> {
|
||||||
|
const db = await this._db
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction([this._name], "readonly")
|
||||||
|
const store = tx.objectStore(this._name)
|
||||||
|
const request: IDBRequest<T> = store.get(key)
|
||||||
|
|
||||||
|
request.onsuccess = () => resolve(request.result)
|
||||||
|
request.onerror = () => reject(request.error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllValues(): Promise<T[]> {
|
||||||
|
const db = await this._db
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction([this._name], "readonly")
|
||||||
|
const store = tx.objectStore(this._name)
|
||||||
|
const values: T[] = []
|
||||||
|
const request = store.openCursor()
|
||||||
|
|
||||||
|
request.onsuccess = () => {
|
||||||
|
const cursor = request.result
|
||||||
|
if (cursor) {
|
||||||
|
values.push(cursor.value)
|
||||||
|
cursor.continue() // Triggers a new 'onsuccess' event
|
||||||
|
} else {
|
||||||
|
resolve(values)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request.onerror = () => reject(request.error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BlobSource implements Source {
|
||||||
|
private readonly _blob: Blob
|
||||||
|
private readonly _name: string
|
||||||
|
public readonly pmtiles: PMTiles
|
||||||
|
|
||||||
|
constructor(name: string, blob: Blob) {
|
||||||
|
this._name = name
|
||||||
|
this._blob = blob
|
||||||
|
this.pmtiles = new PMTiles(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBytes(offset: number, length: number): Promise<RangeResponse> {
|
||||||
|
const sliced = this._blob.slice(offset, offset + length)
|
||||||
|
return {
|
||||||
|
data: await sliced.arrayBuffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getKey() {
|
||||||
|
return this._name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlying data was created with an extract from OpenStreetMap,
|
||||||
|
* extracted at a certain date. This date can be used as version indicator.
|
||||||
|
*/
|
||||||
|
async getDataVersion(): Promise<string> {
|
||||||
|
const meta: Record<string, string> = <any>await this.pmtiles.getMetadata()
|
||||||
|
return meta["planetiler:osm:osmosisreplicationtime"]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OfflineBasemapManager {
|
||||||
|
/**
|
||||||
|
* Where to get the initial map tiles
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _host: string
|
||||||
|
|
||||||
|
private readonly blobs: TypedIdb<any>
|
||||||
|
private readonly meta: TypedIdb<AreaDescription>
|
||||||
|
private metaCached: AreaDescription[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The 'offline base map manager' is responsible for keeping track of the locally installed 'protomaps' subpyramids.
|
||||||
|
*
|
||||||
|
* Roughly taken, a 'protomaps pyramid' is a collection of MVT-tiles in a single file, all belonging to a certain bbox.
|
||||||
|
* The 'cache.mapcomplete.org' server has a collection of sliced pyramids:
|
||||||
|
*
|
||||||
|
* global-basemap.pmtiles: all tiles from z=0 to z=4, ~15MB
|
||||||
|
* 5-{x}-{y}.pmtiles: zoom levels 5 'till 8 (up to 9MB)
|
||||||
|
* 9-{x}-{y}.pmtimes: zoom levels 8 till 15 for a region, up to 90MB
|
||||||
|
*
|
||||||
|
* When a user downloads an offline map, they download a 9-* subpyramid, the corresponding 5-8 pyramid and the 'global-basemap'
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public constructor(host: string) {
|
||||||
|
if (!host.endsWith("/")) {
|
||||||
|
host += "/"
|
||||||
|
}
|
||||||
|
this._host = host
|
||||||
|
this.blobs = new TypedIdb("OfflineBasemap")
|
||||||
|
this.meta = new TypedIdb<AreaDescription>("OfflineBasemapMeta")
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateCachedMeta() {
|
||||||
|
this.metaCached = await this.meta.getAllValues()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ! Must be called from a fetch event in the service worker !
|
||||||
|
* @param areaDescription
|
||||||
|
*/
|
||||||
|
public async installArea(areaDescription: AreaDescription) {
|
||||||
|
const target = this._host + areaDescription.name
|
||||||
|
console.log(">>><<< installing area from "+target)
|
||||||
|
const response = await fetch(target)
|
||||||
|
|
||||||
|
const blob = await response.blob()
|
||||||
|
await this.blobs.set(areaDescription.name, blob)
|
||||||
|
areaDescription.dataVersion = await new BlobSource(areaDescription.name, blob).getDataVersion()
|
||||||
|
await this.meta.set(areaDescription.name, areaDescription)
|
||||||
|
await this.updateCachedMeta()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Knows about the cache structure of cache.mapcomplete.org
|
||||||
|
* @see GeneratePmTilesExtractionScript
|
||||||
|
*/
|
||||||
|
public static getAreaDescriptionForMapcomplete(name: string): AreaDescription {
|
||||||
|
|
||||||
|
if (!name.endsWith(".pmtiles")) {
|
||||||
|
throw "Invalid filename, should end with .pmtiles"
|
||||||
|
}
|
||||||
|
const [z, x, y] = name.substring(0, name.length - ".pmtiles".length).split("-").map(Number)
|
||||||
|
const maxzooms: Record<number, number> = { 0: 4, 5: 8, 9: 15 }
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
minzoom: z,
|
||||||
|
maxzoom: maxzooms[z],
|
||||||
|
x, y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMeta(): AreaDescription[] {
|
||||||
|
return this.metaCached
|
||||||
|
}
|
||||||
|
|
||||||
|
private determineArea(z: number, x: number, y: number): AreaDescription | undefined {
|
||||||
|
for (const areaDescription of this.metaCached) {
|
||||||
|
if (areaDescription.minzoom > z) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (areaDescription.maxzoom < z) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const zdiff = z - areaDescription.minzoom
|
||||||
|
const xAtMinz = x >> zdiff
|
||||||
|
const yAtMinZ = y >> zdiff
|
||||||
|
if (xAtMinz === areaDescription.x && yAtMinZ === areaDescription.y) {
|
||||||
|
return areaDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTileResponse(z: number, x: number, y: number): Promise<Response> {
|
||||||
|
if (this.metaCached.length === 0) {
|
||||||
|
await this.updateCachedMeta()
|
||||||
|
}
|
||||||
|
const area = this.determineArea(z, x, y)
|
||||||
|
if (!area) {
|
||||||
|
return new Response("Not found", { status: 404 })
|
||||||
|
}
|
||||||
|
const blob = await this.blobs.get(area.name)
|
||||||
|
const pmtiles = new BlobSource(area.name, blob)
|
||||||
|
const tileData = await pmtiles.pmtiles.getZxy(z, x, y)
|
||||||
|
if (!tileData) {
|
||||||
|
return new Response("Not found (not in tile archive, should not happen)", { status: 404 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
tileData.data,
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': 'application/x.protobuf' }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
47
src/service-worker/index.ts
Normal file
47
src/service-worker/index.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// This file must have worker types, but not DOM types.
|
||||||
|
// The global should be that of a service worker.
|
||||||
|
|
||||||
|
// This fixes `self`'s type.
|
||||||
|
declare var self: ServiceWorkerGlobalScope
|
||||||
|
export {}
|
||||||
|
import { OfflineBasemapManager } from "./OfflineBasemapManager"
|
||||||
|
|
||||||
|
const offlinemaps = new OfflineBasemapManager("https://cache.mapcomplete.org/")
|
||||||
|
|
||||||
|
|
||||||
|
function routeOffline(event: FetchEvent) {
|
||||||
|
const url = event.request.url
|
||||||
|
const rest = url.split("/service-worker/offline-basemap/")[1]
|
||||||
|
if (rest.indexOf("install") >= 0) {
|
||||||
|
const filename = url.split("/").pop()
|
||||||
|
if (!filename) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const description = OfflineBasemapManager.getAreaDescriptionForMapcomplete(filename)
|
||||||
|
event.respondWith(
|
||||||
|
offlinemaps.installArea(description).then(
|
||||||
|
() => {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
"status": "installed",
|
||||||
|
installed_previously: offlinemaps.getMeta()
|
||||||
|
}), { headers: { "Content-Type": "application/json" } })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const tileRegex =/(\d+-\d+-\d+).mvt$/
|
||||||
|
const tileMatch = rest.match(tileRegex)
|
||||||
|
if (tileMatch) {
|
||||||
|
const [z, x, y] = tileMatch[1].split("-").map(Number)
|
||||||
|
event.respondWith(offlinemaps.getTileResponse(z, x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener("fetch", (event) => {
|
||||||
|
const url = event.request.url
|
||||||
|
if (url.indexOf("/service-worker/offline-basemap/") >= 0) {
|
||||||
|
routeOffline(event)
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
12
src/service-worker/rollup.config.js
Normal file
12
src/service-worker/rollup.config.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import typescript from "@rollup/plugin-typescript"
|
||||||
|
import resolve from "@rollup/plugin-node-resolve"
|
||||||
|
import commonjs from "@rollup/plugin-commonjs"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: "index.ts",
|
||||||
|
output: {
|
||||||
|
file: "../../public/service-worker.js",
|
||||||
|
format: "es"
|
||||||
|
},
|
||||||
|
plugins: [resolve(), commonjs(), typescript()]
|
||||||
|
}
|
20
src/service-worker/tsconfig.json
Normal file
20
src/service-worker/tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"rootDir": ".",
|
||||||
|
"outDir": "../../public/service-worker/",
|
||||||
|
"composite": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"webworker"
|
||||||
|
],
|
||||||
|
"types": ["pmtiles"]
|
||||||
|
},
|
||||||
|
"include": ["index.ts", "OfflineBasemapManager.ts"]
|
||||||
|
}
|
19
test.html
19
test.html
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue