feat: offline tiles
Some checks failed
TrafficCue CI / build (push) Has been cancelled
TrafficCue CI / check (push) Has been cancelled

This commit is contained in:
Cfp
2025-08-09 19:02:21 +02:00
parent bece1a299c
commit 7b2df7d304
19 changed files with 3858 additions and 4177 deletions

View File

@ -13,6 +13,8 @@
"@capacitor/core": "^7.3.0", "@capacitor/core": "^7.3.0",
"@diffusionstudio/vits-web": "^1.0.3", "@diffusionstudio/vits-web": "^1.0.3",
"@eslint/js": "^9.29.0", "@eslint/js": "^9.29.0",
"@tauri-apps/plugin-fs": "~2",
"@tauri-apps/plugin-upload": "~2",
"@types/sql.js": "^1.4.9", "@types/sql.js": "^1.4.9",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"eslint": "^9.29.0", "eslint": "^9.29.0",
@ -21,6 +23,7 @@
"libsodium-wrappers": "^0.7.15", "libsodium-wrappers": "^0.7.15",
"opening_hours": "^3.8.0", "opening_hours": "^3.8.0",
"pako": "^2.1.0", "pako": "^2.1.0",
"pmtiles": "^4.3.0",
"sql.js": "^1.13.0", "sql.js": "^1.13.0",
"svelte-maplibre-gl": "^0.1.8", "svelte-maplibre-gl": "^0.1.8",
"tauri-plugin-keep-screen-on-api": "^0.1.4", "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/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=="], "@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=="], "@types/emscripten": ["@types/emscripten@1.40.1", "", {}, "sha512-sr53lnYkQNhjHNN0oJDdUm5564biioI5DuOpycufDVK7D3y+GR3oUswe2rlwY1nPNyusHbrJ9WoTyIHl4/Bpwg=="],

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <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" /> <link rel="manifest" href="manifest.json" />
<title>TrafficCue</title> <title>TrafficCue</title>
</head> </head>

View File

@ -46,6 +46,8 @@
"@capacitor/core": "^7.3.0", "@capacitor/core": "^7.3.0",
"@diffusionstudio/vits-web": "^1.0.3", "@diffusionstudio/vits-web": "^1.0.3",
"@eslint/js": "^9.29.0", "@eslint/js": "^9.29.0",
"@tauri-apps/plugin-fs": "~2",
"@tauri-apps/plugin-upload": "~2",
"@types/sql.js": "^1.4.9", "@types/sql.js": "^1.4.9",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"eslint": "^9.29.0", "eslint": "^9.29.0",
@ -54,6 +56,7 @@
"libsodium-wrappers": "^0.7.15", "libsodium-wrappers": "^0.7.15",
"opening_hours": "^3.8.0", "opening_hours": "^3.8.0",
"pako": "^2.1.0", "pako": "^2.1.0",
"pmtiles": "^4.3.0",
"sql.js": "^1.13.0", "sql.js": "^1.13.0",
"svelte-maplibre-gl": "^0.1.8", "svelte-maplibre-gl": "^0.1.8",
"tauri-plugin-keep-screen-on-api": "^0.1.4", "tauri-plugin-keep-screen-on-api": "^0.1.4",

File diff suppressed because it is too large Load Diff

370
src-tauri/Cargo.lock generated
View File

@ -99,8 +99,10 @@ dependencies = [
"serde_json", "serde_json",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-fs",
"tauri-plugin-keep-screen-on", "tauri-plugin-keep-screen-on",
"tauri-plugin-log", "tauri-plugin-log",
"tauri-plugin-upload",
] ]
[[package]] [[package]]
@ -484,6 +486,16 @@ dependencies = [
"version_check", "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]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.10.1" version = "0.10.1"
@ -507,7 +519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"core-foundation", "core-foundation 0.10.1",
"core-graphics-types", "core-graphics-types",
"foreign-types", "foreign-types",
"libc", "libc",
@ -520,7 +532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"core-foundation", "core-foundation 0.10.1",
"libc", "libc",
] ]
@ -921,6 +933,21 @@ dependencies = [
"new_debug_unreachable", "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]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.31" version = "0.3.31"
@ -928,6 +955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@ -982,6 +1010,7 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-macro", "futures-macro",
@ -1129,8 +1158,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi 0.11.1+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -1140,9 +1171,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"r-efi", "r-efi",
"wasi 0.14.2+wasi-0.2.4", "wasi 0.14.2+wasi-0.2.4",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -1378,6 +1411,12 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.10.1" version = "1.10.1"
@ -1403,6 +1442,23 @@ dependencies = [
"want", "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]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.16" version = "0.1.16"
@ -1421,10 +1477,12 @@ dependencies = [
"libc", "libc",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2 0.6.0",
"system-configuration",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
"windows-registry",
] ]
[[package]] [[package]]
@ -1819,6 +1877,12 @@ dependencies = [
"value-bag", "value-bag",
] ]
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]] [[package]]
name = "mac" name = "mac"
version = "0.1.1" version = "0.1.1"
@ -2592,6 +2656,61 @@ dependencies = [
"memchr", "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]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.40"
@ -2638,6 +2757,16 @@ dependencies = [
"rand_core 0.6.4", "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]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.2.2" version = "0.2.2"
@ -2658,6 +2787,16 @@ dependencies = [
"rand_core 0.6.4", "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]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.5.1" version = "0.5.1"
@ -2676,6 +2815,15 @@ dependencies = [
"getrandom 0.2.16", "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]] [[package]]
name = "rand_hc" name = "rand_hc"
version = "0.2.0" version = "0.2.0"
@ -2700,6 +2848,17 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" 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]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.17" version = "0.5.17"
@ -2792,16 +2951,21 @@ dependencies = [
"http-body", "http-body",
"http-body-util", "http-body-util",
"hyper", "hyper",
"hyper-rustls",
"hyper-util", "hyper-util",
"js-sys", "js-sys",
"log", "log",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-rustls",
"tokio-util", "tokio-util",
"tower", "tower",
"tower-http", "tower-http",
@ -2811,6 +2975,21 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams", "wasm-streams",
"web-sys", "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]] [[package]]
@ -2864,6 +3043,12 @@ version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.1" version = "0.4.1"
@ -2873,6 +3058,41 @@ dependencies = [
"semver", "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]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.21" version = "1.0.21"
@ -3196,6 +3416,16 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 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]] [[package]]
name = "socket2" name = "socket2"
version = "0.6.0" version = "0.6.0"
@ -3291,6 +3521,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "swift-rs" name = "swift-rs"
version = "1.0.7" version = "1.0.7"
@ -3356,6 +3592,27 @@ dependencies = [
"syn 2.0.104", "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]] [[package]]
name = "system-deps" name = "system-deps"
version = "6.2.2" version = "6.2.2"
@ -3376,7 +3633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"core-foundation", "core-foundation 0.10.1",
"core-graphics", "core-graphics",
"crossbeam-channel", "crossbeam-channel",
"dispatch", "dispatch",
@ -3447,6 +3704,7 @@ dependencies = [
"gtk", "gtk",
"heck 0.5.0", "heck 0.5.0",
"http", "http",
"http-range",
"jni", "jni",
"libc", "libc",
"log", "log",
@ -3561,6 +3819,28 @@ dependencies = [
"walkdir", "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]] [[package]]
name = "tauri-plugin-keep-screen-on" name = "tauri-plugin-keep-screen-on"
version = "0.1.4" version = "0.1.4"
@ -3595,6 +3875,25 @@ dependencies = [
"time", "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]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.7.1" version = "2.7.1"
@ -3815,10 +4114,20 @@ dependencies = [
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"slab", "slab",
"socket2", "socket2 0.6.0",
"windows-sys 0.59.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]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.16" version = "0.7.16"
@ -4073,6 +4382,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.4" version = "2.5.4"
@ -4299,6 +4614,16 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "webkit2gtk" name = "webkit2gtk"
version = "2.0.1" version = "2.0.1"
@ -4343,6 +4668,15 @@ dependencies = [
"system-deps", "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]] [[package]]
name = "webview2-com" name = "webview2-com"
version = "0.38.0" version = "0.38.0"
@ -4509,6 +4843,17 @@ dependencies = [
"windows-link", "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]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.3.4" version = "0.3.4"
@ -4536,6 +4881,15 @@ dependencies = [
"windows-targets 0.42.2", "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]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.59.0"
@ -4937,6 +5291,12 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]] [[package]]
name = "zerotrie" name = "zerotrie"
version = "0.2.2" version = "0.2.2"

View File

@ -21,6 +21,8 @@ tauri-build = { version = "2.3.1", features = [] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
log = "0.4" log = "0.4"
tauri = { version = "2.7.0", features = [] } tauri = { version = "2.7.0", features = ["protocol-asset"] }
tauri-plugin-log = "2" tauri-plugin-log = "2"
tauri-plugin-keep-screen-on = "0.1.2" tauri-plugin-keep-screen-on = "0.1.2"
tauri-plugin-upload = "2"
tauri-plugin-fs = "2"

View File

@ -6,6 +6,34 @@
"main" "main"
], ],
"permissions": [ "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"
] ]
} }

View File

@ -17,3 +17,5 @@ key.properties
/.tauri /.tauri
/tauri.settings.gradle /tauri.settings.gradle
keystore.properties

View File

@ -1,4 +1,5 @@
import java.util.Properties import java.util.Properties
import java.io.FileInputStream
plugins { plugins {
id("com.android.application") id("com.android.application")
@ -24,6 +25,20 @@ android {
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") 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 { buildTypes {
getByName("debug") { getByName("debug") {
manifestPlaceholders["usesCleartextTraffic"] = "true" manifestPlaceholders["usesCleartextTraffic"] = "true"
@ -43,6 +58,7 @@ android {
.plus(getDefaultProguardFile("proguard-android-optimize.txt")) .plus(getDefaultProguardFile("proguard-android-optimize.txt"))
.toList().toTypedArray() .toList().toTypedArray()
) )
signingConfig = signingConfigs.getByName("release")
} }
} }
kotlinOptions { kotlinOptions {

View File

@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" /> <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 --> <!-- AndroidTV support -->
<uses-feature android:name="android.software.leanback" android:required="false" /> <uses-feature android:name="android.software.leanback" android:required="false" />

View File

@ -1,6 +1,8 @@
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_upload::init())
.plugin(tauri_plugin_keep_screen_on::init()) .plugin(tauri_plugin_keep_screen_on::init())
.setup(|app| { .setup(|app| {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {

View File

@ -20,9 +20,16 @@
} }
], ],
"security": { "security": {
"csp": null "assetProtocol": {
"enable": true,
"scope": {
"allow": ["$APPDATA/**"]
} }
}, },
"csp": null
},
"withGlobalTauri": true
},
"bundle": { "bundle": {
"active": true, "active": true,
"targets": "all", "targets": "all",

View File

@ -8,9 +8,10 @@
routing, routing,
} from "$lib/services/navigation/routing.svelte"; } from "$lib/services/navigation/routing.svelte";
import { location } from "./location.svelte"; import { location } from "./location.svelte";
import { protocol } from "$lib/services/OfflineTiles";
import { saved } from "$lib/saved.svelte"; import { saved } from "$lib/saved.svelte";
import RoutingLayers from "$lib/services/navigation/RoutingLayers.svelte"; import RoutingLayers from "$lib/services/navigation/RoutingLayers.svelte";
import { protocol } from "$lib/services/OfflineTiles";
import { layers, worldLayers } from "$lib/mapLayers";
onMount(() => { onMount(() => {
window.addEventListener("resize", map.updateMapPadding); window.addEventListener("resize", map.updateMapPadding);
@ -26,7 +27,9 @@
const DEBUG_POINTS = false; // Set to true to show debug points on the map const DEBUG_POINTS = false; // Set to true to show debug points on the map
</script> </script>
<Protocol scheme="tiles" loadFn={protocol} /> <!-- <Protocol scheme="tiles" loadFn={protocol} /> -->
<!-- <PMTilesProtocol /> -->
<Protocol scheme="pmtiles" loadFn={protocol} />
<MapLibre <MapLibre
class="w-full h-full" class="w-full h-full"
@ -39,6 +42,32 @@
location.locked = true; location.locked = true;
// @ts-expect-error - not typed // @ts-expect-error - not typed
window.map = map.value; 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) => { onclick={(e) => {
if (view.current.type == "main" || view.current.type == "info") { if (view.current.type == "main" || view.current.type == "info") {

View File

@ -28,7 +28,7 @@
import { routing } from "$lib/services/navigation/routing.svelte"; import { routing } from "$lib/services/navigation/routing.svelte";
import InRouteSidebar from "./sidebar/InRouteSidebar.svelte"; import InRouteSidebar from "./sidebar/InRouteSidebar.svelte";
import say from "$lib/services/navigation/TTS"; 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 // eslint-disable-next-line @typescript-eslint/no-explicit-any
const views: Record<string, Component<any>> = { const views: Record<string, Component<any>> = {
@ -211,9 +211,11 @@
<Button <Button
variant="outline" variant="outline"
onclick={async () => { onclick={async () => {
const name = prompt("Name?");
if(!name) return;
const url = prompt("URL?"); const url = prompt("URL?");
if (!url) return; if (!url) return;
await test(url); await downloadPMTiles(url, name);
}} }}
> >
Test Offline tiles Test Offline tiles
@ -249,6 +251,7 @@
#floating-search { #floating-search {
position: fixed; position: fixed;
margin: 10px; margin: 10px;
margin-top: calc(10px + env(safe-area-inset-top));
top: 0; top: 0;
left: 0; left: 0;
z-index: 20; z-index: 20;
@ -267,6 +270,7 @@
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
margin-bottom: 0; margin-bottom: 0;
padding: 10px; padding: 10px;
padding-bottom: calc(10px + env(safe-area-inset-bottom));
margin: 10px; margin: 10px;
width: calc(25% - 20px); width: calc(25% - 20px);
/* border-top-left-radius: 15px; /* border-top-left-radius: 15px;
@ -283,7 +287,7 @@
#sidebar.mobileView { #sidebar.mobileView {
position: fixed; position: fixed;
top: unset; top: unset;
bottom: 50px; bottom: calc(50px + env(safe-area-inset-bottom));
left: 0; left: 0;
/* min-width: calc(100% - 20px); /* min-width: calc(100% - 20px);
max-width: calc(100% - 20px); */ max-width: calc(100% - 20px); */

2996
src/lib/mapLayers.ts Normal file

File diff suppressed because it is too large Load Diff

View 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 };
}

View File

@ -1,173 +1,210 @@
import { import { appDataDir, join } from "@tauri-apps/api/path";
CapacitorSQLite, import { BaseDirectory, exists, mkdir, open, remove, SeekMode } from "@tauri-apps/plugin-fs";
SQLiteConnection, import { download } from "@tauri-apps/plugin-upload";
SQLiteDBConnection, import { PMTiles, TileType, type Source } from "pmtiles";
} 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; export async function downloadPMTiles(url: string, name: string): Promise<void> {
let db: SQLiteDBConnection; // if(!window.__TAURI__) {
// throw new Error("Tauri environment is not available.");
// }
export async function downloadMBTiles(url: string): Promise<Uint8Array> { const filename = name + ".pmtiles";
return fetch(url) const baseDir = BaseDirectory.AppData;
.then((res) => res.arrayBuffer()) const appDataDirPath = await appDataDir();
.then((ab) => new Uint8Array(ab));
}
export async function copyMBTiles(data: Uint8Array) { if(!await exists(appDataDirPath)) {
if (!db) { await mkdir(appDataDirPath, { recursive: true });
await initDB();
} }
const SQL = await initSqlJs();
const mdb = new SQL.Database(data); if(await exists(filename, { baseDir })) {
const res = mdb.exec("SELECT * FROM tiles"); console.log(`File ${filename} already exists, deleting it.`);
//// const chunkSize = 10; // Adjust chunk size as needed await remove(filename, { baseDir });
//// 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) { console.log(`Downloading PMTiles from ${url} to ${filename}`);
const res = await downloadMBTiles(url); const res = await fetch(url);
console.log("Downloaded MBTiles data");
await copyMBTiles(res);
}
export async function initDB() { if (!res.ok) {
if (!Capacitor.isNativePlatform()) { throw new Error(`Failed to download PMTiles: ${res.statusText}`);
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() { const path = await join(appDataDirPath, filename);
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 await download(url, path, ({ progress, total }) => {
window.deleteDB = deleteDB; console.log(`Download progress: ${Math.round((progress / total) * 100)}% (${progress}\tof ${total} bytes)`);
// @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 = ?`, console.log(`Download completed: ${path}`);
[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 export async function getPMTiles(name: string) {
window.getTile = getTile; const filename = name + ".pmtiles";
const baseDir = BaseDirectory.AppData;
const appDataDirPath = await appDataDir();
async function decompressGzip(blob: Uint8Array): Promise<Uint8Array> { if(!await exists(appDataDirPath)) {
// const ds = new DecompressionStream("gzip"); throw new Error("App data directory does not exist.");
// const decompressedStream = new Blob([blob]).stream().pipeThrough(ds); }
// return new Uint8Array(await new Response(decompressedStream).arrayBuffer());
return ungzip(blob); 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);
} }
export async function protocol(params: { async function readBytes(name: string, offset: number, length: number): Promise<Uint8Array> {
url: string; const file = await open(name + ".pmtiles", { read: true, baseDir: BaseDirectory.AppData });
}, { signal }: AbortController): Promise<{ data: Uint8Array }> { const buffer = new Uint8Array(length);
console.log("Protocol called with params:", params); await file.seek(offset, SeekMode.Start);
const url = new URL(params.url); await file.read(buffer);
const pathname = url.pathname.replace(/^\//, ""); // Remove leading slash await file.close();
const z = parseInt(pathname.split("/")[0]); return buffer;
const x = parseInt(pathname.split("/")[1]); }
const y = parseInt(pathname.split("/")[2]);
if (!Capacitor.isNativePlatform()) { export class FSSource implements Source {
const t = await fetch( name: string;
`https://tiles.openfreemap.org/planet/20250528_001001_pt/${z}/${x}/${y}.pbf`,
{ signal } constructor(name: string) {
); this.name = name;
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) { async getBytes(offset: number, length: number, _signal?: AbortSignal, _etag?: string) { // TODO: abort signal
await initDB(); const data = await readBytes(this.name, offset, length);
}
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 { return {
data: new Uint8Array(), // Return empty array if tile not found data: data.buffer as ArrayBuffer,
etag: undefined,
cacheControl: undefined,
expires: undefined
}
}
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),
}; };
} }
// return { data: await fetch("/0.pbf").then(res => res.arrayBuffer()).then(ab => new Uint8Array(ab)) };
return { data }; 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;