feat: offline tiles
This commit is contained in:
7
bun.lock
7
bun.lock
@ -13,6 +13,8 @@
|
||||
"@capacitor/core": "^7.3.0",
|
||||
"@diffusionstudio/vits-web": "^1.0.3",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-upload": "~2",
|
||||
"@types/sql.js": "^1.4.9",
|
||||
"buffer": "^6.0.3",
|
||||
"eslint": "^9.29.0",
|
||||
@ -21,6 +23,7 @@
|
||||
"libsodium-wrappers": "^0.7.15",
|
||||
"opening_hours": "^3.8.0",
|
||||
"pako": "^2.1.0",
|
||||
"pmtiles": "^4.3.0",
|
||||
"sql.js": "^1.13.0",
|
||||
"svelte-maplibre-gl": "^0.1.8",
|
||||
"tauri-plugin-keep-screen-on-api": "^0.1.4",
|
||||
@ -381,6 +384,10 @@
|
||||
|
||||
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.7.1", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Q9kDObutuirCNLxYQ7KAg2Xxg99AjcdYz/KuMw5HvyEPbkC9Q7JL0vOrQOrHEHxIQ2lYzFOZvKKoC2yyqXcg=="],
|
||||
|
||||
"@tauri-apps/plugin-fs": ["@tauri-apps/plugin-fs@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-vJlKZVGF3UAFGoIEVT6Oq5L4HGDCD78WmA4uhzitToqYiBKWAvZR61M6zAyQzHqLs0ADemkE4RSy/5sCmZm6ZQ=="],
|
||||
|
||||
"@tauri-apps/plugin-upload": ["@tauri-apps/plugin-upload@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-nqaMbmn78vdoFf/tiS8XtbEJ/S5MRUMzfFRZw+t49JKtXDPXUUsG3CYai9mGDpZQe5PW8zvfXHKto9eLj03/lg=="],
|
||||
|
||||
"@tsconfig/svelte": ["@tsconfig/svelte@5.0.4", "", {}, "sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q=="],
|
||||
|
||||
"@types/emscripten": ["@types/emscripten@1.40.1", "", {}, "sha512-sr53lnYkQNhjHNN0oJDdUm5564biioI5DuOpycufDVK7D3y+GR3oUswe2rlwY1nPNyusHbrJ9WoTyIHl4/Bpwg=="],
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<title>TrafficCue</title>
|
||||
</head>
|
||||
|
||||
@ -46,6 +46,8 @@
|
||||
"@capacitor/core": "^7.3.0",
|
||||
"@diffusionstudio/vits-web": "^1.0.3",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-upload": "~2",
|
||||
"@types/sql.js": "^1.4.9",
|
||||
"buffer": "^6.0.3",
|
||||
"eslint": "^9.29.0",
|
||||
@ -54,6 +56,7 @@
|
||||
"libsodium-wrappers": "^0.7.15",
|
||||
"opening_hours": "^3.8.0",
|
||||
"pako": "^2.1.0",
|
||||
"pmtiles": "^4.3.0",
|
||||
"sql.js": "^1.13.0",
|
||||
"svelte-maplibre-gl": "^0.1.8",
|
||||
"tauri-plugin-keep-screen-on-api": "^0.1.4",
|
||||
|
||||
3990
public/style.json
3990
public/style.json
File diff suppressed because it is too large
Load Diff
370
src-tauri/Cargo.lock
generated
370
src-tauri/Cargo.lock
generated
@ -99,8 +99,10 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-fs",
|
||||
"tauri-plugin-keep-screen-on",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-upload",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -484,6 +486,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
@ -507,7 +519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
@ -520,7 +532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@ -921,6 +933,21 @@ dependencies = [
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@ -928,6 +955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -982,6 +1010,7 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
@ -1129,8 +1158,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1140,9 +1171,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1378,6 +1411,12 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
@ -1403,6 +1442,23 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.16"
|
||||
@ -1421,10 +1477,12 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.6.0",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1819,6 +1877,12 @@ dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-slab"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
@ -2592,6 +2656,61 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases 0.2.1",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.3.3",
|
||||
"lru-slab",
|
||||
"rand 0.9.2",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.12",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-udp"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970"
|
||||
dependencies = [
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
@ -2638,6 +2757,16 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
@ -2658,6 +2787,16 @@ dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
@ -2676,6 +2815,15 @@ dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
@ -2700,6 +2848,17 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "read-progress-stream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6435842fc2fea44b528719eb8c32203bbc1bb2f5b619fbe0c0a3d8350fd8d2a8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.17"
|
||||
@ -2792,16 +2951,21 @@ dependencies = [
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
@ -2811,6 +2975,21 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"wasm-streams",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2864,6 +3043,12 @@ version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
@ -2873,6 +3058,41 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
|
||||
dependencies = [
|
||||
"web-time",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
@ -3196,6 +3416,16 @@ version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.0"
|
||||
@ -3291,6 +3521,12 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
@ -3356,6 +3592,27 @@ dependencies = [
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
@ -3376,7 +3633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation",
|
||||
"core-foundation 0.10.1",
|
||||
"core-graphics",
|
||||
"crossbeam-channel",
|
||||
"dispatch",
|
||||
@ -3447,6 +3704,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"http-range",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
@ -3561,6 +3819,28 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c6ef84ee2f2094ce093e55106d90d763ba343fad57566992962e8f76d113f99"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"percent-encoding",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"toml 0.8.2",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-keep-screen-on"
|
||||
version = "0.1.4"
|
||||
@ -3595,6 +3875,25 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-upload"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3a3a78df0924a08d166d2d8041c6b9e752e6d94913177b52f1bfa11b5f73d3"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"read-progress-stream",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.7.1"
|
||||
@ -3815,10 +4114,20 @@ dependencies = [
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"socket2",
|
||||
"socket2 0.6.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.16"
|
||||
@ -4073,6 +4382,12 @@ version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
@ -4299,6 +4614,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webkit2gtk"
|
||||
version = "2.0.1"
|
||||
@ -4343,6 +4668,15 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.38.0"
|
||||
@ -4509,6 +4843,17 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
@ -4536,6 +4881,15 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@ -4937,6 +5291,12 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.2"
|
||||
|
||||
@ -21,6 +21,8 @@ tauri-build = { version = "2.3.1", features = [] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.7.0", features = [] }
|
||||
tauri = { version = "2.7.0", features = ["protocol-asset"] }
|
||||
tauri-plugin-log = "2"
|
||||
tauri-plugin-keep-screen-on = "0.1.2"
|
||||
tauri-plugin-upload = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
@ -6,6 +6,34 @@
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
"core:default",
|
||||
"upload:default",
|
||||
"fs:allow-app-read",
|
||||
"fs:allow-app-write",
|
||||
{
|
||||
"identifier": "fs:allow-write-file",
|
||||
"allow": [
|
||||
{
|
||||
"path": "$APPDATA/**/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
"fs:read-files",
|
||||
"fs:read-dirs",
|
||||
{
|
||||
"identifier": "fs:scope",
|
||||
"allow": [
|
||||
{
|
||||
"path": "**"
|
||||
},
|
||||
{
|
||||
"path": "$APPDATA/**/*"
|
||||
},
|
||||
{
|
||||
"path": "$APPDATA/**/.*"
|
||||
}
|
||||
]
|
||||
},
|
||||
"upload:allow-download"
|
||||
]
|
||||
}
|
||||
}
|
||||
4
src-tauri/gen/android/.gitignore
vendored
4
src-tauri/gen/android/.gitignore
vendored
@ -16,4 +16,6 @@ local.properties
|
||||
key.properties
|
||||
|
||||
/.tauri
|
||||
/tauri.settings.gradle
|
||||
/tauri.settings.gradle
|
||||
|
||||
keystore.properties
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import java.util.Properties
|
||||
import java.io.FileInputStream
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
@ -24,6 +25,20 @@ android {
|
||||
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
|
||||
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
|
||||
}
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
val keystorePropertiesFile = rootProject.file("keystore.properties")
|
||||
val keystoreProperties = Properties()
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
}
|
||||
|
||||
keyAlias = keystoreProperties["keyAlias"] as String
|
||||
keyPassword = keystoreProperties["password"] as String
|
||||
storeFile = file(keystoreProperties["storeFile"] as String)
|
||||
storePassword = keystoreProperties["password"] as String
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
@ -43,6 +58,7 @@ android {
|
||||
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
|
||||
.toList().toTypedArray()
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
@ -67,4 +83,4 @@ dependencies {
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
||||
}
|
||||
|
||||
apply(from = "tauri.build.gradle.kts")
|
||||
apply(from = "tauri.build.gradle.kts")
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<!-- AndroidTV support -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_keep_screen_on::init())
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_upload::init())
|
||||
.plugin(tauri_plugin_keep_screen_on::init())
|
||||
.setup(|app| {
|
||||
if cfg!(debug_assertions) {
|
||||
app.handle().plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@ -2,5 +2,5 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
app_lib::run();
|
||||
}
|
||||
|
||||
@ -20,8 +20,15 @@
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": {
|
||||
"allow": ["$APPDATA/**"]
|
||||
}
|
||||
},
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"withGlobalTauri": true
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
|
||||
@ -8,9 +8,10 @@
|
||||
routing,
|
||||
} from "$lib/services/navigation/routing.svelte";
|
||||
import { location } from "./location.svelte";
|
||||
import { protocol } from "$lib/services/OfflineTiles";
|
||||
import { saved } from "$lib/saved.svelte";
|
||||
import RoutingLayers from "$lib/services/navigation/RoutingLayers.svelte";
|
||||
import { protocol } from "$lib/services/OfflineTiles";
|
||||
import { layers, worldLayers } from "$lib/mapLayers";
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener("resize", map.updateMapPadding);
|
||||
@ -26,7 +27,9 @@
|
||||
const DEBUG_POINTS = false; // Set to true to show debug points on the map
|
||||
</script>
|
||||
|
||||
<Protocol scheme="tiles" loadFn={protocol} />
|
||||
<!-- <Protocol scheme="tiles" loadFn={protocol} /> -->
|
||||
<!-- <PMTilesProtocol /> -->
|
||||
<Protocol scheme="pmtiles" loadFn={protocol} />
|
||||
|
||||
<MapLibre
|
||||
class="w-full h-full"
|
||||
@ -39,6 +42,32 @@
|
||||
location.locked = true;
|
||||
// @ts-expect-error - not typed
|
||||
window.map = map.value;
|
||||
|
||||
// const worldUrl = await getPMTiles("world");
|
||||
// if(worldUrl) {
|
||||
map.value!.addSource("ne2_shaded", { // TODO: rename to world
|
||||
type: "vector",
|
||||
url: "pmtiles://world",
|
||||
attribution: "Natural Earth",
|
||||
// maxzoom: 6
|
||||
})
|
||||
|
||||
// @ts-expect-error - not typed correctly
|
||||
worldLayers.forEach(l => map.value!.addLayer(l));
|
||||
// }
|
||||
|
||||
// const url = await getPMTiles("tiles");
|
||||
// console.log(url)
|
||||
// if(url) {
|
||||
map.value!.addSource("openmaptiles", {
|
||||
type: "vector",
|
||||
url: "pmtiles://tiles"
|
||||
})
|
||||
|
||||
|
||||
// @ts-expect-error - not typed correctly
|
||||
layers.forEach(l => map.value!.addLayer(l));
|
||||
// }
|
||||
}}
|
||||
onclick={(e) => {
|
||||
if (view.current.type == "main" || view.current.type == "info") {
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
import { routing } from "$lib/services/navigation/routing.svelte";
|
||||
import InRouteSidebar from "./sidebar/InRouteSidebar.svelte";
|
||||
import say from "$lib/services/navigation/TTS";
|
||||
import { test } from "$lib/services/OfflineTiles";
|
||||
import { downloadPMTiles } from "$lib/services/OfflineTiles";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const views: Record<string, Component<any>> = {
|
||||
@ -211,9 +211,11 @@
|
||||
<Button
|
||||
variant="outline"
|
||||
onclick={async () => {
|
||||
const name = prompt("Name?");
|
||||
if(!name) return;
|
||||
const url = prompt("URL?");
|
||||
if (!url) return;
|
||||
await test(url);
|
||||
await downloadPMTiles(url, name);
|
||||
}}
|
||||
>
|
||||
Test Offline tiles
|
||||
@ -249,6 +251,7 @@
|
||||
#floating-search {
|
||||
position: fixed;
|
||||
margin: 10px;
|
||||
margin-top: calc(10px + env(safe-area-inset-top));
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 20;
|
||||
@ -267,6 +270,7 @@
|
||||
backdrop-filter: blur(5px);
|
||||
margin-bottom: 0;
|
||||
padding: 10px;
|
||||
padding-bottom: calc(10px + env(safe-area-inset-bottom));
|
||||
margin: 10px;
|
||||
width: calc(25% - 20px);
|
||||
/* border-top-left-radius: 15px;
|
||||
@ -283,7 +287,7 @@
|
||||
#sidebar.mobileView {
|
||||
position: fixed;
|
||||
top: unset;
|
||||
bottom: 50px;
|
||||
bottom: calc(50px + env(safe-area-inset-bottom));
|
||||
left: 0;
|
||||
/* min-width: calc(100% - 20px);
|
||||
max-width: calc(100% - 20px); */
|
||||
|
||||
2996
src/lib/mapLayers.ts
Normal file
2996
src/lib/mapLayers.ts
Normal file
File diff suppressed because it is too large
Load Diff
173
src/lib/services/OfflineTiles-Cap.ts
Normal file
173
src/lib/services/OfflineTiles-Cap.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import {
|
||||
CapacitorSQLite,
|
||||
SQLiteConnection,
|
||||
SQLiteDBConnection,
|
||||
} from "@capacitor-community/sqlite";
|
||||
import initSqlJs from "sql.js";
|
||||
import { Buffer } from "buffer";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import { ungzip } from "pako";
|
||||
|
||||
let sqlite: SQLiteConnection;
|
||||
let db: SQLiteDBConnection;
|
||||
|
||||
export async function downloadMBTiles(url: string): Promise<Uint8Array> {
|
||||
return fetch(url)
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((ab) => new Uint8Array(ab));
|
||||
}
|
||||
|
||||
export async function copyMBTiles(data: Uint8Array) {
|
||||
if (!db) {
|
||||
await initDB();
|
||||
}
|
||||
const SQL = await initSqlJs();
|
||||
const mdb = new SQL.Database(data);
|
||||
const res = mdb.exec("SELECT * FROM tiles");
|
||||
//// const chunkSize = 10; // Adjust chunk size as needed
|
||||
//// const values = res[0].values;
|
||||
//// for (let i = 0; i < values.length; i += chunkSize) {
|
||||
//// const chunk = values.slice(i, i + chunkSize);
|
||||
//// const statements = chunk.map(row => {
|
||||
//// const [z, x, y, data] = row;
|
||||
//// return {
|
||||
//// statement: `INSERT OR REPLACE INTO tiles (z, x, y, data) VALUES (?, ?, ?, ?)`,
|
||||
//// values: [z, x, y, Buffer.from(data as Uint8Array)]
|
||||
//// };
|
||||
//// });
|
||||
//// await db.executeSet(statements);
|
||||
//// console.log(`Inserted chunk ${i / chunkSize + 1} of ${Math.ceil(values.length / chunkSize)}: z=${chunk[0][0]}, x=${chunk[0][1]}, y=${chunk[0][2]}`);
|
||||
//// }
|
||||
const total = res[0].values.length;
|
||||
for (const [idx, row] of res[0].values.entries()) {
|
||||
const [z, x, y, data] = row;
|
||||
await db.run(
|
||||
`INSERT OR REPLACE INTO tiles (z, x, y, data) VALUES (?, ?, ?, ?)`,
|
||||
[
|
||||
z,
|
||||
x,
|
||||
y,
|
||||
Buffer.from(data as Uint8Array), // Convert Uint8Array to Buffer
|
||||
],
|
||||
);
|
||||
console.log(
|
||||
`Inserted tile z=${z}, x=${x}, y=${y}. Item ${idx + 1} of ${total}`,
|
||||
);
|
||||
}
|
||||
console.log(`Copied ${res[0].values.length} tiles from MBTiles data`);
|
||||
}
|
||||
|
||||
export async function test(url: string) {
|
||||
const res = await downloadMBTiles(url);
|
||||
console.log("Downloaded MBTiles data");
|
||||
await copyMBTiles(res);
|
||||
}
|
||||
|
||||
export async function initDB() {
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
throw new Error("initDB is only available on native platforms");
|
||||
}
|
||||
console.log("Initializing SQLite database for tiles");
|
||||
sqlite = new SQLiteConnection(CapacitorSQLite);
|
||||
db = await sqlite.createConnection("tiles", false, "no-encryption", 1, false);
|
||||
await db.open();
|
||||
await db.execute(`CREATE TABLE IF NOT EXISTS tiles (
|
||||
z INTEGER NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
y INTEGER NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
PRIMARY KEY (z, x, y)
|
||||
)`);
|
||||
await db.execute(
|
||||
`CREATE INDEX IF NOT EXISTS idx_tiles_zxy ON tiles (z, x, y)`,
|
||||
);
|
||||
}
|
||||
|
||||
async function deleteDB() {
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
throw new Error("deleteDB is only available on native platforms");
|
||||
}
|
||||
await db.execute(`DROP TABLE IF EXISTS tiles`);
|
||||
await initDB();
|
||||
}
|
||||
|
||||
// @ts-expect-error aaaaa
|
||||
window.deleteDB = deleteDB;
|
||||
|
||||
// @ts-expect-error aaaaa
|
||||
window.initDB = initDB;
|
||||
|
||||
export async function getTile(
|
||||
z: number,
|
||||
x: number,
|
||||
y: number,
|
||||
signal?: AbortSignal
|
||||
): Promise<Uint8Array | null> {
|
||||
if (signal?.aborted) {
|
||||
throw new DOMException("Aborted", "AbortError");
|
||||
}
|
||||
const abortPromise = new Promise<never>((_, reject) => {
|
||||
if (signal) {
|
||||
signal.addEventListener("abort", () => {
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
}, { once: true });
|
||||
}
|
||||
});
|
||||
const queryPromise = db.query(
|
||||
`SELECT data FROM tiles WHERE z = ? AND x = ? AND y = ?`,
|
||||
[z, x, y],
|
||||
);
|
||||
const res = await Promise.race([queryPromise, abortPromise]);
|
||||
if (!res.values || res.values.length === 0) {
|
||||
return null;
|
||||
}
|
||||
console.log(res);
|
||||
return await decompressGzip(res.values[0].data as Uint8Array);
|
||||
}
|
||||
|
||||
// @ts-expect-error aaaaa
|
||||
window.getTile = getTile;
|
||||
|
||||
async function decompressGzip(blob: Uint8Array): Promise<Uint8Array> {
|
||||
// const ds = new DecompressionStream("gzip");
|
||||
// const decompressedStream = new Blob([blob]).stream().pipeThrough(ds);
|
||||
// return new Uint8Array(await new Response(decompressedStream).arrayBuffer());
|
||||
return ungzip(blob);
|
||||
}
|
||||
|
||||
export async function protocol(params: {
|
||||
url: string;
|
||||
}, { signal }: AbortController): Promise<{ data: Uint8Array }> {
|
||||
console.log("Protocol called with params:", params);
|
||||
const url = new URL(params.url);
|
||||
const pathname = url.pathname.replace(/^\//, ""); // Remove leading slash
|
||||
const z = parseInt(pathname.split("/")[0]);
|
||||
const x = parseInt(pathname.split("/")[1]);
|
||||
const y = parseInt(pathname.split("/")[2]);
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
const t = await fetch(
|
||||
`https://tiles.openfreemap.org/planet/20250528_001001_pt/${z}/${x}/${y}.pbf`,
|
||||
{ signal }
|
||||
);
|
||||
if (t.status == 200) {
|
||||
const buffer = await t.arrayBuffer();
|
||||
return { data: new Uint8Array(buffer) };
|
||||
} else {
|
||||
throw new Error(`Tile fetch error: ${t.statusText}`);
|
||||
}
|
||||
}
|
||||
if (!db) {
|
||||
await initDB();
|
||||
}
|
||||
const tmsY = (1 << z) - 1 - y; // Invert y for TMS
|
||||
console.log(`Fetching tile: z=${z}, x=${x}, y=${y}, tmsY=${tmsY}`);
|
||||
const data = await getTile(z, x, tmsY, signal);
|
||||
if (!data) {
|
||||
console.warn(`Tile not found: z=${z}, x=${x}, y=${y}`);
|
||||
return {
|
||||
data: new Uint8Array(), // Return empty array if tile not found
|
||||
};
|
||||
}
|
||||
// return { data: await fetch("/0.pbf").then(res => res.arrayBuffer()).then(ab => new Uint8Array(ab)) };
|
||||
return { data };
|
||||
}
|
||||
@ -1,173 +1,210 @@
|
||||
import {
|
||||
CapacitorSQLite,
|
||||
SQLiteConnection,
|
||||
SQLiteDBConnection,
|
||||
} from "@capacitor-community/sqlite";
|
||||
import initSqlJs from "sql.js";
|
||||
import { Buffer } from "buffer";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import { ungzip } from "pako";
|
||||
import { appDataDir, join } from "@tauri-apps/api/path";
|
||||
import { BaseDirectory, exists, mkdir, open, remove, SeekMode } from "@tauri-apps/plugin-fs";
|
||||
import { download } from "@tauri-apps/plugin-upload";
|
||||
import { PMTiles, TileType, type Source } from "pmtiles";
|
||||
|
||||
let sqlite: SQLiteConnection;
|
||||
let db: SQLiteDBConnection;
|
||||
export async function downloadPMTiles(url: string, name: string): Promise<void> {
|
||||
// if(!window.__TAURI__) {
|
||||
// throw new Error("Tauri environment is not available.");
|
||||
// }
|
||||
|
||||
export async function downloadMBTiles(url: string): Promise<Uint8Array> {
|
||||
return fetch(url)
|
||||
.then((res) => res.arrayBuffer())
|
||||
.then((ab) => new Uint8Array(ab));
|
||||
}
|
||||
const filename = name + ".pmtiles";
|
||||
const baseDir = BaseDirectory.AppData;
|
||||
const appDataDirPath = await appDataDir();
|
||||
|
||||
export async function copyMBTiles(data: Uint8Array) {
|
||||
if (!db) {
|
||||
await initDB();
|
||||
if(!await exists(appDataDirPath)) {
|
||||
await mkdir(appDataDirPath, { recursive: true });
|
||||
}
|
||||
const SQL = await initSqlJs();
|
||||
const mdb = new SQL.Database(data);
|
||||
const res = mdb.exec("SELECT * FROM tiles");
|
||||
//// const chunkSize = 10; // Adjust chunk size as needed
|
||||
//// const values = res[0].values;
|
||||
//// for (let i = 0; i < values.length; i += chunkSize) {
|
||||
//// const chunk = values.slice(i, i + chunkSize);
|
||||
//// const statements = chunk.map(row => {
|
||||
//// const [z, x, y, data] = row;
|
||||
//// return {
|
||||
//// statement: `INSERT OR REPLACE INTO tiles (z, x, y, data) VALUES (?, ?, ?, ?)`,
|
||||
//// values: [z, x, y, Buffer.from(data as Uint8Array)]
|
||||
//// };
|
||||
//// });
|
||||
//// await db.executeSet(statements);
|
||||
//// console.log(`Inserted chunk ${i / chunkSize + 1} of ${Math.ceil(values.length / chunkSize)}: z=${chunk[0][0]}, x=${chunk[0][1]}, y=${chunk[0][2]}`);
|
||||
//// }
|
||||
const total = res[0].values.length;
|
||||
for (const [idx, row] of res[0].values.entries()) {
|
||||
const [z, x, y, data] = row;
|
||||
await db.run(
|
||||
`INSERT OR REPLACE INTO tiles (z, x, y, data) VALUES (?, ?, ?, ?)`,
|
||||
[
|
||||
z,
|
||||
x,
|
||||
y,
|
||||
Buffer.from(data as Uint8Array), // Convert Uint8Array to Buffer
|
||||
],
|
||||
);
|
||||
console.log(
|
||||
`Inserted tile z=${z}, x=${x}, y=${y}. Item ${idx + 1} of ${total}`,
|
||||
);
|
||||
|
||||
if(await exists(filename, { baseDir })) {
|
||||
console.log(`File ${filename} already exists, deleting it.`);
|
||||
await remove(filename, { baseDir });
|
||||
}
|
||||
console.log(`Copied ${res[0].values.length} tiles from MBTiles data`);
|
||||
}
|
||||
|
||||
export async function test(url: string) {
|
||||
const res = await downloadMBTiles(url);
|
||||
console.log("Downloaded MBTiles data");
|
||||
await copyMBTiles(res);
|
||||
}
|
||||
console.log(`Downloading PMTiles from ${url} to ${filename}`);
|
||||
const res = await fetch(url);
|
||||
|
||||
export async function initDB() {
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
throw new Error("initDB is only available on native platforms");
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to download PMTiles: ${res.statusText}`);
|
||||
}
|
||||
console.log("Initializing SQLite database for tiles");
|
||||
sqlite = new SQLiteConnection(CapacitorSQLite);
|
||||
db = await sqlite.createConnection("tiles", false, "no-encryption", 1, false);
|
||||
await db.open();
|
||||
await db.execute(`CREATE TABLE IF NOT EXISTS tiles (
|
||||
z INTEGER NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
y INTEGER NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
PRIMARY KEY (z, x, y)
|
||||
)`);
|
||||
await db.execute(
|
||||
`CREATE INDEX IF NOT EXISTS idx_tiles_zxy ON tiles (z, x, y)`,
|
||||
);
|
||||
}
|
||||
|
||||
async function deleteDB() {
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
throw new Error("deleteDB is only available on native platforms");
|
||||
}
|
||||
await db.execute(`DROP TABLE IF EXISTS tiles`);
|
||||
await initDB();
|
||||
}
|
||||
const path = await join(appDataDirPath, filename);
|
||||
|
||||
// @ts-expect-error aaaaa
|
||||
window.deleteDB = deleteDB;
|
||||
|
||||
// @ts-expect-error aaaaa
|
||||
window.initDB = initDB;
|
||||
|
||||
export async function getTile(
|
||||
z: number,
|
||||
x: number,
|
||||
y: number,
|
||||
signal?: AbortSignal
|
||||
): Promise<Uint8Array | null> {
|
||||
if (signal?.aborted) {
|
||||
throw new DOMException("Aborted", "AbortError");
|
||||
}
|
||||
const abortPromise = new Promise<never>((_, reject) => {
|
||||
if (signal) {
|
||||
signal.addEventListener("abort", () => {
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
}, { once: true });
|
||||
}
|
||||
await download(url, path, ({ progress, total }) => {
|
||||
console.log(`Download progress: ${Math.round((progress / total) * 100)}% (${progress}\tof ${total} bytes)`);
|
||||
});
|
||||
const queryPromise = db.query(
|
||||
`SELECT data FROM tiles WHERE z = ? AND x = ? AND y = ?`,
|
||||
[z, x, y],
|
||||
);
|
||||
const res = await Promise.race([queryPromise, abortPromise]);
|
||||
if (!res.values || res.values.length === 0) {
|
||||
return null;
|
||||
|
||||
console.log(`Download completed: ${path}`);
|
||||
}
|
||||
|
||||
export async function getPMTiles(name: string) {
|
||||
const filename = name + ".pmtiles";
|
||||
const baseDir = BaseDirectory.AppData;
|
||||
const appDataDirPath = await appDataDir();
|
||||
|
||||
if(!await exists(appDataDirPath)) {
|
||||
throw new Error("App data directory does not exist.");
|
||||
}
|
||||
console.log(res);
|
||||
return await decompressGzip(res.values[0].data as Uint8Array);
|
||||
|
||||
const filePath = await join(appDataDirPath, filename);
|
||||
|
||||
if(!await exists(filePath, { baseDir })) {
|
||||
throw new Error(`PMTiles file not found: ${filePath}`);
|
||||
}
|
||||
|
||||
return `asset:/${filename}`;
|
||||
// return convertFileSrc(filePath);
|
||||
}
|
||||
|
||||
// @ts-expect-error aaaaa
|
||||
window.getTile = getTile;
|
||||
|
||||
async function decompressGzip(blob: Uint8Array): Promise<Uint8Array> {
|
||||
// const ds = new DecompressionStream("gzip");
|
||||
// const decompressedStream = new Blob([blob]).stream().pipeThrough(ds);
|
||||
// return new Uint8Array(await new Response(decompressedStream).arrayBuffer());
|
||||
return ungzip(blob);
|
||||
async function readBytes(name: string, offset: number, length: number): Promise<Uint8Array> {
|
||||
const file = await open(name + ".pmtiles", { read: true, baseDir: BaseDirectory.AppData });
|
||||
const buffer = new Uint8Array(length);
|
||||
await file.seek(offset, SeekMode.Start);
|
||||
await file.read(buffer);
|
||||
await file.close();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export async function protocol(params: {
|
||||
url: string;
|
||||
}, { signal }: AbortController): Promise<{ data: Uint8Array }> {
|
||||
console.log("Protocol called with params:", params);
|
||||
const url = new URL(params.url);
|
||||
const pathname = url.pathname.replace(/^\//, ""); // Remove leading slash
|
||||
const z = parseInt(pathname.split("/")[0]);
|
||||
const x = parseInt(pathname.split("/")[1]);
|
||||
const y = parseInt(pathname.split("/")[2]);
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
const t = await fetch(
|
||||
`https://tiles.openfreemap.org/planet/20250528_001001_pt/${z}/${x}/${y}.pbf`,
|
||||
{ signal }
|
||||
);
|
||||
if (t.status == 200) {
|
||||
const buffer = await t.arrayBuffer();
|
||||
return { data: new Uint8Array(buffer) };
|
||||
} else {
|
||||
throw new Error(`Tile fetch error: ${t.statusText}`);
|
||||
export class FSSource implements Source {
|
||||
name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
async getBytes(offset: number, length: number, _signal?: AbortSignal, _etag?: string) { // TODO: abort signal
|
||||
const data = await readBytes(this.name, offset, length);
|
||||
return {
|
||||
data: data.buffer as ArrayBuffer,
|
||||
etag: undefined,
|
||||
cacheControl: undefined,
|
||||
expires: undefined
|
||||
}
|
||||
}
|
||||
if (!db) {
|
||||
await initDB();
|
||||
}
|
||||
const tmsY = (1 << z) - 1 - y; // Invert y for TMS
|
||||
console.log(`Fetching tile: z=${z}, x=${x}, y=${y}, tmsY=${tmsY}`);
|
||||
const data = await getTile(z, x, tmsY, signal);
|
||||
if (!data) {
|
||||
console.warn(`Tile not found: z=${z}, x=${x}, y=${y}`);
|
||||
return {
|
||||
data: new Uint8Array(), // Return empty array if tile not found
|
||||
};
|
||||
}
|
||||
// return { data: await fetch("/0.pbf").then(res => res.arrayBuffer()).then(ab => new Uint8Array(ab)) };
|
||||
return { data };
|
||||
|
||||
getKey = () => this.name;
|
||||
}
|
||||
|
||||
interface RequestParameters {
|
||||
url: string;
|
||||
headers?: unknown;
|
||||
method?: "GET" | "POST" | "PUT";
|
||||
body?: string;
|
||||
type?: "string" | "json" | "arrayBuffer" | "image";
|
||||
credentials?: "same-origin" | "include";
|
||||
collectResourceTiming?: boolean;
|
||||
};
|
||||
|
||||
export class Protocol {
|
||||
/** @hidden */
|
||||
tiles: Map<string, PMTiles>;
|
||||
metadata: boolean;
|
||||
errorOnMissingTile: boolean;
|
||||
|
||||
/**
|
||||
* Initialize the MapLibre PMTiles protocol.
|
||||
*
|
||||
* * metadata: also load the metadata section of the PMTiles. required for some "inspect" functionality
|
||||
* and to automatically populate the map attribution. Requires an extra HTTP request.
|
||||
* * errorOnMissingTile: When a vector MVT tile is missing from the archive, raise an error instead of
|
||||
* returning the empty array. Not recommended. This is only to reproduce the behavior of ZXY tile APIs
|
||||
* which some applications depend on when overzooming.
|
||||
*/
|
||||
constructor(options?: { metadata?: boolean; errorOnMissingTile?: boolean }) {
|
||||
this.tiles = new Map<string, PMTiles>();
|
||||
this.metadata = options?.metadata || false;
|
||||
this.errorOnMissingTile = options?.errorOnMissingTile || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link PMTiles} instance to the global protocol instance.
|
||||
*
|
||||
* For remote fetch sources, references in MapLibre styles like pmtiles://http://...
|
||||
* will resolve to the same instance if the URLs match.
|
||||
*/
|
||||
add(p: PMTiles) {
|
||||
this.tiles.set(p.source.getKey(), p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a {@link PMTiles} instance by URL, for remote PMTiles instances.
|
||||
*/
|
||||
get(url: string) {
|
||||
return this.tiles.get(url);
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
tilev4 = async (
|
||||
params: RequestParameters,
|
||||
abortController: AbortController
|
||||
) => {
|
||||
if (params.type === "json") {
|
||||
const pmtilesUrl = params.url.substr(10);
|
||||
let instance = this.tiles.get(pmtilesUrl);
|
||||
if (!instance) {
|
||||
instance = new PMTiles(new FSSource(pmtilesUrl));
|
||||
this.tiles.set(pmtilesUrl, instance);
|
||||
}
|
||||
|
||||
if (this.metadata) {
|
||||
return {
|
||||
data: await instance.getTileJson(params.url),
|
||||
};
|
||||
}
|
||||
|
||||
const h = await instance.getHeader();
|
||||
|
||||
if (h.minLon >= h.maxLon || h.minLat >= h.maxLat) {
|
||||
console.error(
|
||||
`Bounds of PMTiles archive ${h.minLon},${h.minLat},${h.maxLon},${h.maxLat} are not valid.`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
data: {
|
||||
tiles: [`${params.url}/{z}/{x}/{y}`],
|
||||
minzoom: h.minZoom,
|
||||
maxzoom: h.maxZoom,
|
||||
bounds: [h.minLon, h.minLat, h.maxLon, h.maxLat],
|
||||
},
|
||||
};
|
||||
}
|
||||
const re = new RegExp(/pmtiles:\/\/(.+)\/(\d+)\/(\d+)\/(\d+)/);
|
||||
const result = params.url.match(re);
|
||||
if (!result) {
|
||||
throw new Error("Invalid PMTiles protocol URL");
|
||||
}
|
||||
const pmtilesUrl = result[1];
|
||||
|
||||
let instance = this.tiles.get(pmtilesUrl);
|
||||
if (!instance) {
|
||||
instance = new PMTiles(pmtilesUrl);
|
||||
this.tiles.set(pmtilesUrl, instance);
|
||||
}
|
||||
const z = result[2];
|
||||
const x = result[3];
|
||||
const y = result[4];
|
||||
|
||||
const header = await instance.getHeader();
|
||||
const resp = await instance?.getZxy(+z, +x, +y, abortController.signal);
|
||||
if (resp) {
|
||||
return {
|
||||
data: new Uint8Array(resp.data),
|
||||
cacheControl: resp.cacheControl,
|
||||
expires: resp.expires,
|
||||
};
|
||||
}
|
||||
if (header.tileType === TileType.Mvt) {
|
||||
if (this.errorOnMissingTile) {
|
||||
throw new Error("Tile not found.");
|
||||
}
|
||||
return { data: new Uint8Array() };
|
||||
}
|
||||
return { data: null };
|
||||
};
|
||||
}
|
||||
|
||||
export const protocol = new Protocol({
|
||||
metadata: true,
|
||||
errorOnMissingTile: false,
|
||||
}).tilev4;
|
||||
|
||||
Reference in New Issue
Block a user