Compare commits

...

2 Commits

Author SHA1 Message Date
Cfp
cecba34979 style: run prettier
All checks were successful
TrafficCue CI / check (push) Successful in 58s
TrafficCue CI / build (push) Successful in 46s
2025-07-03 18:07:42 +02:00
Cfp
0b7ba06e9e feat: keep device awake during navigation 2025-07-03 18:07:05 +02:00
9 changed files with 1063 additions and 33 deletions

View File

@ -9,6 +9,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies { dependencies {
implementation project(':capacitor-community-keep-awake')
implementation project(':capacitor-community-native-audio') implementation project(':capacitor-community-native-audio')
implementation project(':capacitor-community-sqlite') implementation project(':capacitor-community-sqlite')
implementation project(':capacitor-community-text-to-speech') implementation project(':capacitor-community-text-to-speech')

View File

@ -2,6 +2,9 @@
include ':capacitor-android' include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-community-keep-awake'
project(':capacitor-community-keep-awake').projectDir = new File('../node_modules/@capacitor-community/keep-awake/android')
include ':capacitor-community-native-audio' include ':capacitor-community-native-audio'
project(':capacitor-community-native-audio').projectDir = new File('../node_modules/@capacitor-community/native-audio/android') project(':capacitor-community-native-audio').projectDir = new File('../node_modules/@capacitor-community/native-audio/android')

View File

@ -4,6 +4,7 @@
"": { "": {
"name": "librenav", "name": "librenav",
"dependencies": { "dependencies": {
"@capacitor-community/keep-awake": "^7.1.0",
"@capacitor-community/native-audio": "^7.0.0", "@capacitor-community/native-audio": "^7.0.0",
"@capacitor-community/sqlite": "^7.0.0", "@capacitor-community/sqlite": "^7.0.0",
"@capacitor-community/text-to-speech": "^6.0.0", "@capacitor-community/text-to-speech": "^6.0.0",
@ -54,6 +55,8 @@
"@babel/runtime": ["@babel/runtime@7.27.3", "", {}, "sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw=="], "@babel/runtime": ["@babel/runtime@7.27.3", "", {}, "sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw=="],
"@capacitor-community/keep-awake": ["@capacitor-community/keep-awake@7.1.0", "", { "peerDependencies": { "@capacitor/core": ">=7.0.0" } }, "sha512-4Hj6OKnBd/DAOeiMnqBy74tg1rwC+qLYWQbWJmysMZm2e2nptz43hxaKu22Xlyyw5O71CwwskLCM0skDFMPpQQ=="],
"@capacitor-community/native-audio": ["@capacitor-community/native-audio@7.0.0", "", { "peerDependencies": { "@capacitor/core": ">=7.0.0" } }, "sha512-wi2l68tU6KDLJWKL6I0lPUKOdB+hNxlvF7RMe/jfkKOMZnd1eUq7kszmbMqyGFZFIxdNG/GMiZrEztZHDypf8A=="], "@capacitor-community/native-audio": ["@capacitor-community/native-audio@7.0.0", "", { "peerDependencies": { "@capacitor/core": ">=7.0.0" } }, "sha512-wi2l68tU6KDLJWKL6I0lPUKOdB+hNxlvF7RMe/jfkKOMZnd1eUq7kszmbMqyGFZFIxdNG/GMiZrEztZHDypf8A=="],
"@capacitor-community/sqlite": ["@capacitor-community/sqlite@7.0.0", "", { "dependencies": { "jeep-sqlite": "^2.7.2" }, "peerDependencies": { "@capacitor/core": ">=7.0.0" } }, "sha512-GSHxLEiyuhPoWwpMLOwfaOhLpfZglhfI1sqEgA5bSbtIYt1I+iK02b/ymonaghB176NhA5wuGd+spOIaL0kgHw=="], "@capacitor-community/sqlite": ["@capacitor-community/sqlite@7.0.0", "", { "dependencies": { "jeep-sqlite": "^2.7.2" }, "peerDependencies": { "@capacitor/core": ">=7.0.0" } }, "sha512-GSHxLEiyuhPoWwpMLOwfaOhLpfZglhfI1sqEgA5bSbtIYt1I+iK02b/ymonaghB176NhA5wuGd+spOIaL0kgHw=="],

View File

@ -33,6 +33,7 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@capacitor-community/keep-awake": "^7.1.0",
"@capacitor-community/native-audio": "^7.0.0", "@capacitor-community/native-audio": "^7.0.0",
"@capacitor-community/sqlite": "^7.0.0", "@capacitor-community/sqlite": "^7.0.0",
"@capacitor-community/text-to-speech": "^6.0.0", "@capacitor-community/text-to-speech": "^6.0.0",

File diff suppressed because one or more lines are too long

View File

@ -27,9 +27,7 @@
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 <Protocol scheme="tiles" loadFn={protocol} />
scheme="tiles"
loadFn={protocol} />
<!-- <Protocol <!-- <Protocol
scheme="tiles" scheme="tiles"

View File

@ -212,7 +212,7 @@
variant="outline" variant="outline"
onclick={async () => { onclick={async () => {
const url = prompt("URL?"); const url = prompt("URL?");
if(!url) return; if (!url) return;
await test(url); await test(url);
}} }}
> >

View File

@ -1,4 +1,8 @@
import { CapacitorSQLite, SQLiteConnection, SQLiteDBConnection } from "@capacitor-community/sqlite"; import {
CapacitorSQLite,
SQLiteConnection,
SQLiteDBConnection,
} from "@capacitor-community/sqlite";
import initSqlJs from "sql.js"; import initSqlJs from "sql.js";
import { Buffer } from "buffer"; import { Buffer } from "buffer";
import { Capacitor } from "@capacitor/core"; import { Capacitor } from "@capacitor/core";
@ -9,8 +13,8 @@ let db: SQLiteDBConnection;
export async function downloadMBTiles(url: string): Promise<Uint8Array> { export async function downloadMBTiles(url: string): Promise<Uint8Array> {
return fetch(url) return fetch(url)
.then(res => res.arrayBuffer()) .then((res) => res.arrayBuffer())
.then(ab => new Uint8Array(ab)) .then((ab) => new Uint8Array(ab));
} }
export async function copyMBTiles(data: Uint8Array) { export async function copyMBTiles(data: Uint8Array) {
@ -37,13 +41,18 @@ export async function copyMBTiles(data: Uint8Array) {
const total = res[0].values.length; const total = res[0].values.length;
for (const [idx, row] of res[0].values.entries()) { for (const [idx, row] of res[0].values.entries()) {
const [z, x, y, data] = row; const [z, x, y, data] = row;
await db.run(`INSERT OR REPLACE INTO tiles (z, x, y, data) VALUES (?, ?, ?, ?)`, [ await db.run(
z, `INSERT OR REPLACE INTO tiles (z, x, y, data) VALUES (?, ?, ?, ?)`,
x, [
y, z,
Buffer.from(data as Uint8Array) // Convert Uint8Array to Buffer x,
]); y,
console.log(`Inserted tile z=${z}, x=${x}, y=${y}. Item ${idx + 1} of ${total}`); 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`); console.log(`Copied ${res[0].values.length} tiles from MBTiles data`);
} }
@ -55,7 +64,7 @@ export async function test(url: string) {
} }
export async function initDB() { export async function initDB() {
if(!Capacitor.isNativePlatform()) { if (!Capacitor.isNativePlatform()) {
throw new Error("initDB is only available on native platforms"); throw new Error("initDB is only available on native platforms");
} }
console.log("Initializing SQLite database for tiles"); console.log("Initializing SQLite database for tiles");
@ -69,11 +78,13 @@ export async function initDB() {
data BLOB NOT NULL, data BLOB NOT NULL,
PRIMARY KEY (z, x, y) PRIMARY KEY (z, x, y)
)`); )`);
await db.execute(`CREATE INDEX IF NOT EXISTS idx_tiles_zxy ON tiles (z, x, y)`); await db.execute(
`CREATE INDEX IF NOT EXISTS idx_tiles_zxy ON tiles (z, x, y)`,
);
} }
async function deleteDB() { async function deleteDB() {
if(!Capacitor.isNativePlatform()) { if (!Capacitor.isNativePlatform()) {
throw new Error("deleteDB is only available on native platforms"); throw new Error("deleteDB is only available on native platforms");
} }
await db.execute(`DROP TABLE IF EXISTS tiles`); await db.execute(`DROP TABLE IF EXISTS tiles`);
@ -86,8 +97,15 @@ window.deleteDB = deleteDB;
// @ts-expect-error aaaaa // @ts-expect-error aaaaa
window.initDB = initDB; window.initDB = initDB;
export async function getTile(z: number, x: number, y: number): Promise<Uint8Array | null> { export async function getTile(
const res = await db.query(`SELECT data FROM tiles WHERE z = ? AND x = ? AND y = ?`, [z, x, y]); z: number,
x: number,
y: number,
): Promise<Uint8Array | null> {
const res = await db.query(
`SELECT data FROM tiles WHERE z = ? AND x = ? AND y = ?`,
[z, x, y],
);
if (!res.values || res.values.length === 0) { if (!res.values || res.values.length === 0) {
return null; return null;
} }
@ -99,22 +117,25 @@ export async function getTile(z: number, x: number, y: number): Promise<Uint8Arr
window.getTile = getTile; window.getTile = getTile;
async function decompressGzip(blob: Uint8Array): Promise<Uint8Array> { async function decompressGzip(blob: Uint8Array): Promise<Uint8Array> {
// const ds = new DecompressionStream("gzip"); // const ds = new DecompressionStream("gzip");
// const decompressedStream = new Blob([blob]).stream().pipeThrough(ds); // const decompressedStream = new Blob([blob]).stream().pipeThrough(ds);
// return new Uint8Array(await new Response(decompressedStream).arrayBuffer()); // return new Uint8Array(await new Response(decompressedStream).arrayBuffer());
return ungzip(blob); return ungzip(blob);
} }
export async function protocol(params: {
export async function protocol(params: { url: string }): Promise<{ data: Uint8Array }> { url: string;
}): Promise<{ data: Uint8Array }> {
console.log("Protocol called with params:", params); console.log("Protocol called with params:", params);
const url = new URL(params.url); const url = new URL(params.url);
const pathname = url.pathname.replace(/^\//, ""); // Remove leading slash const pathname = url.pathname.replace(/^\//, ""); // Remove leading slash
const z = parseInt(pathname.split("/")[0]); const z = parseInt(pathname.split("/")[0]);
const x = parseInt(pathname.split("/")[1]); const x = parseInt(pathname.split("/")[1]);
const y = parseInt(pathname.split("/")[2]); const y = parseInt(pathname.split("/")[2]);
if(!Capacitor.isNativePlatform()) { if (!Capacitor.isNativePlatform()) {
const t = await fetch(`https://tiles.openfreemap.org/planet/20250528_001001_pt/${z}/${x}/${y}.pbf`); const t = await fetch(
`https://tiles.openfreemap.org/planet/20250528_001001_pt/${z}/${x}/${y}.pbf`,
);
if (t.status == 200) { if (t.status == 200) {
const buffer = await t.arrayBuffer(); const buffer = await t.arrayBuffer();
return { data: new Uint8Array(buffer) }; return { data: new Uint8Array(buffer) };
@ -122,7 +143,7 @@ export async function protocol(params: { url: string }): Promise<{ data: Uint8Ar
throw new Error(`Tile fetch error: ${t.statusText}`); throw new Error(`Tile fetch error: ${t.statusText}`);
} }
} }
if(!db) { if (!db) {
await initDB(); await initDB();
} }
const tmsY = (1 << z) - 1 - y; // Invert y for TMS const tmsY = (1 << z) - 1 - y; // Invert y for TMS
@ -131,8 +152,8 @@ export async function protocol(params: { url: string }): Promise<{ data: Uint8Ar
if (!data) { if (!data) {
console.warn(`Tile not found: z=${z}, x=${x}, y=${y}`); console.warn(`Tile not found: z=${z}, x=${x}, y=${y}`);
return { return {
data: new Uint8Array() // Return empty array if tile not found 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: await fetch("/0.pbf").then(res => res.arrayBuffer()).then(ab => new Uint8Array(ab)) };
return { data }; return { data };

View File

@ -4,6 +4,8 @@ import say from "./TTS";
import type { ValhallaRequest } from "./ValhallaRequest"; import type { ValhallaRequest } from "./ValhallaRequest";
import type { LngLatBoundsLike } from "maplibre-gl"; import type { LngLatBoundsLike } from "maplibre-gl";
import { generateVoiceGuidance } from "./VoiceGuidance"; import { generateVoiceGuidance } from "./VoiceGuidance";
import { Capacitor } from "@capacitor/core";
import { KeepAwake } from "@capacitor-community/keep-awake";
export const routing = $state({ export const routing = $state({
geojson: { geojson: {
@ -128,6 +130,9 @@ function drawCurrentTrip() {
} }
export async function startRoute(trip: Trip) { export async function startRoute(trip: Trip) {
if (Capacitor.isNativePlatform()) {
await KeepAwake.keepAwake();
}
routing.currentTrip = trip; routing.currentTrip = trip;
removeAllRoutes(); removeAllRoutes();
routing.geojson.route = tripToGeoJSON(trip); routing.geojson.route = tripToGeoJSON(trip);
@ -266,6 +271,9 @@ export function stopNavigation() {
routing.currentTrip = null; routing.currentTrip = null;
map.updateMapPadding(); // TODO: REMOVE map.updateMapPadding(); // TODO: REMOVE
removeAllRoutes(); removeAllRoutes();
if (Capacitor.isNativePlatform()) {
KeepAwake.allowSleep();
}
} }
// function getUserLocation(): WorldLocation { // function getUserLocation(): WorldLocation {