style: run eslint and prettier
This commit is contained in:
@ -1,2 +1,3 @@
|
||||
android/
|
||||
dist/
|
||||
dist/
|
||||
src-tauri/
|
@ -26,7 +26,7 @@ export default tseslint.config(
|
||||
],
|
||||
},
|
||||
},
|
||||
[globalIgnores(["./android", "./dist"])],
|
||||
[globalIgnores(["./android", "./dist", "./src-tauri"])],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
|
@ -3,7 +3,10 @@
|
||||
<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, viewport-fit=cover" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
|
||||
/>
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<title>TrafficCue</title>
|
||||
</head>
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"version": 8,
|
||||
"sources": {
|
||||
},
|
||||
"sources": {},
|
||||
"sprite": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm",
|
||||
"glyphs": "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
|
||||
"layers": []
|
||||
|
@ -10,7 +10,11 @@
|
||||
import { location } from "./location.svelte";
|
||||
import { saved } from "$lib/saved.svelte";
|
||||
import RoutingLayers from "$lib/services/navigation/RoutingLayers.svelte";
|
||||
import { getPMTilesURL, hasPMTiles, protocol } from "$lib/services/OfflineTiles";
|
||||
import {
|
||||
getPMTilesURL,
|
||||
hasPMTiles,
|
||||
protocol,
|
||||
} from "$lib/services/OfflineTiles";
|
||||
import { layers, worldLayers } from "$lib/mapLayers";
|
||||
import { PMTilesProtocol } from "svelte-maplibre-gl/pmtiles";
|
||||
|
||||
@ -43,32 +47,26 @@
|
||||
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: await getPMTilesURL("world"),
|
||||
attribution: "Natural Earth",
|
||||
// maxzoom: 6
|
||||
})
|
||||
|
||||
// @ts-expect-error - not typed correctly
|
||||
worldLayers.forEach(l => map.value!.addLayer(l));
|
||||
// }
|
||||
map.value!.addSource("ne2_shaded", {
|
||||
// TODO: rename to world
|
||||
type: "vector",
|
||||
url: await getPMTilesURL("world"),
|
||||
attribution: "Natural Earth",
|
||||
});
|
||||
|
||||
// const url = await getPMTiles("tiles");
|
||||
// console.log(url)
|
||||
// if(url) {
|
||||
map.value!.addSource("openmaptiles", {
|
||||
type: "vector",
|
||||
url: await hasPMTiles("tiles") ? await getPMTilesURL("tiles") : "pmtiles://https://trafficcue-tiles.picoscratch.de/germany.pmtiles"
|
||||
})
|
||||
// @ts-expect-error - not typed correctly
|
||||
worldLayers.forEach((l) => map.value!.addLayer(l));
|
||||
|
||||
map.value!.addSource("openmaptiles", {
|
||||
type: "vector",
|
||||
url: (await hasPMTiles("tiles"))
|
||||
? await getPMTilesURL("tiles")
|
||||
: "pmtiles://https://trafficcue-tiles.picoscratch.de/germany.pmtiles",
|
||||
});
|
||||
|
||||
// @ts-expect-error - not typed correctly
|
||||
layers.forEach(l => map.value!.addLayer(l));
|
||||
// }
|
||||
// @ts-expect-error - not typed correctly
|
||||
layers.forEach((l) => map.value!.addLayer(l));
|
||||
}}
|
||||
onclick={(e) => {
|
||||
if (view.current.type == "main" || view.current.type == "info") {
|
||||
|
@ -27,8 +27,6 @@
|
||||
import * as Popover from "../ui/popover";
|
||||
import { routing } from "$lib/services/navigation/routing.svelte";
|
||||
import InRouteSidebar from "./sidebar/InRouteSidebar.svelte";
|
||||
import say from "$lib/services/navigation/TTS";
|
||||
import { downloadPMTiles } from "$lib/services/OfflineTiles";
|
||||
import SettingsSidebar from "./sidebar/settings/SettingsSidebar.svelte";
|
||||
import AboutSidebar from "./sidebar/settings/AboutSidebar.svelte";
|
||||
import OfflineMapsSidebar from "./sidebar/settings/OfflineMapsSidebar.svelte";
|
||||
@ -45,7 +43,7 @@
|
||||
settings: SettingsSidebar,
|
||||
about: AboutSidebar,
|
||||
"offline-maps": OfflineMapsSidebar,
|
||||
"dev-options": DeveloperSidebar
|
||||
"dev-options": DeveloperSidebar,
|
||||
};
|
||||
|
||||
let isDragging = false;
|
||||
@ -164,9 +162,11 @@
|
||||
<UserIcon />
|
||||
</button>
|
||||
</RequiresCapability>
|
||||
<button onclick={() => {
|
||||
view.switch("settings");
|
||||
}}>
|
||||
<button
|
||||
onclick={() => {
|
||||
view.switch("settings");
|
||||
}}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</button>
|
||||
<!-- <button onclick={() => {
|
||||
|
@ -6,18 +6,21 @@
|
||||
const dev = getDeveloperToggle();
|
||||
</script>
|
||||
|
||||
<SidebarHeader>
|
||||
About
|
||||
</SidebarHeader>
|
||||
<SidebarHeader>About</SidebarHeader>
|
||||
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<h1 style="font-size: 2em; font-weight: bold;" onclick={() => {
|
||||
count--;
|
||||
if(count == 0) {
|
||||
dev.current = "true";
|
||||
}
|
||||
}}>TrafficCue</h1>
|
||||
<h1
|
||||
style="font-size: 2em; font-weight: bold;"
|
||||
onclick={() => {
|
||||
count--;
|
||||
if (count == 0) {
|
||||
dev.current = "true";
|
||||
}
|
||||
}}
|
||||
>
|
||||
TrafficCue
|
||||
</h1>
|
||||
<span>Powered by:</span>
|
||||
<ul>
|
||||
<li>© OpenStreetMap contributors</li>
|
||||
|
@ -10,31 +10,42 @@
|
||||
const dev = getDeveloperToggle();
|
||||
</script>
|
||||
|
||||
<SidebarHeader>
|
||||
Developer Settings
|
||||
</SidebarHeader>
|
||||
<SidebarHeader>Developer Settings</SidebarHeader>
|
||||
|
||||
<div id="sections">
|
||||
<section>
|
||||
<h2>Test</h2>
|
||||
<SettingsButton icon={SpeechIcon} text="Test TTS" onclick={async () => {
|
||||
await say("Test")
|
||||
}} />
|
||||
<SettingsButton icon={MapIcon} disabled={!window.__TAURI__} text="Download tiles from URL{window.__TAURI__ ? "" : " (Unavailable)"}" onclick={async () => {
|
||||
const name = prompt("Name?");
|
||||
if(!name) return;
|
||||
const url = prompt("URL?");
|
||||
if(!url) return;
|
||||
await downloadPMTiles(url, name);
|
||||
}} />
|
||||
<SettingsButton
|
||||
icon={SpeechIcon}
|
||||
text="Test TTS"
|
||||
onclick={async () => {
|
||||
await say("Test");
|
||||
}}
|
||||
/>
|
||||
<SettingsButton
|
||||
icon={MapIcon}
|
||||
disabled={!window.__TAURI__}
|
||||
text="Download tiles from URL{window.__TAURI__ ? '' : ' (Unavailable)'}"
|
||||
onclick={async () => {
|
||||
const name = prompt("Name?");
|
||||
if (!name) return;
|
||||
const url = prompt("URL?");
|
||||
if (!url) return;
|
||||
await downloadPMTiles(url, name);
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Other</h2>
|
||||
<SettingsButton icon={ToggleLeftIcon} text="Disable Developer Options" onclick={async () => {
|
||||
dev.current = "false";
|
||||
view.back();
|
||||
}} />
|
||||
<SettingsButton
|
||||
icon={ToggleLeftIcon}
|
||||
text="Disable Developer Options"
|
||||
onclick={async () => {
|
||||
dev.current = "false";
|
||||
view.back();
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@ -50,4 +61,4 @@
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -4,12 +4,10 @@
|
||||
import SettingsButton from "./SettingsButton.svelte";
|
||||
import SidebarHeader from "../SidebarHeader.svelte";
|
||||
|
||||
let progresses: {[key: string]: number} = $state({});
|
||||
let progresses: Record<string, number> = $state({});
|
||||
</script>
|
||||
|
||||
<SidebarHeader>
|
||||
Offline Maps
|
||||
</SidebarHeader>
|
||||
<SidebarHeader>Offline Maps</SidebarHeader>
|
||||
|
||||
{#await getRemoteList()}
|
||||
<p>Loading...</p>
|
||||
@ -18,19 +16,29 @@
|
||||
{#if list.length === 0}
|
||||
<p>No offline maps available.</p>
|
||||
{/if}
|
||||
|
||||
|
||||
{#if !window.__TAURI__}
|
||||
<p>Offline maps are only available on mobile.</p>
|
||||
{/if}
|
||||
|
||||
{#each list as item, _index (item.file)}
|
||||
<SettingsButton disabled={!window.__TAURI__} icon={DownloadCloudIcon} text={item.name} progress={progresses[item.file] ?? -1} onclick={async () => {
|
||||
await downloadPMTiles("https://trafficcue-tiles.picoscratch.de/" + item.file, item.name.includes("World") ? "world": "tiles", (progress, total) => {
|
||||
progresses[item.file] = (progress / total) * 100;
|
||||
});
|
||||
alert(`Downloaded ${item.name}`);
|
||||
location.reload();
|
||||
}} />
|
||||
<SettingsButton
|
||||
disabled={!window.__TAURI__}
|
||||
icon={DownloadCloudIcon}
|
||||
text={item.name}
|
||||
progress={progresses[item.file] ?? -1}
|
||||
onclick={async () => {
|
||||
await downloadPMTiles(
|
||||
"https://trafficcue-tiles.picoscratch.de/" + item.file,
|
||||
item.name.includes("World") ? "world" : "tiles",
|
||||
(progress, total) => {
|
||||
progresses[item.file] = (progress / total) * 100;
|
||||
},
|
||||
);
|
||||
alert(`Downloaded ${item.name}`);
|
||||
location.reload();
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/await}
|
||||
{/await}
|
||||
|
@ -6,16 +6,31 @@
|
||||
import Progress from "$lib/components/ui/progress/progress.svelte";
|
||||
|
||||
const {
|
||||
icon: Icon, text, view: viewName, onclick, disabled, progress
|
||||
icon: Icon,
|
||||
text,
|
||||
view: viewName,
|
||||
onclick,
|
||||
disabled,
|
||||
progress,
|
||||
}: {
|
||||
icon: Component<IconProps>, text: string, view?: string, onclick?: () => void, disabled?: boolean, progress?: number
|
||||
icon: Component<IconProps>;
|
||||
text: string;
|
||||
view?: string;
|
||||
onclick?: () => void;
|
||||
disabled?: boolean;
|
||||
progress?: number;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<Button variant="secondary" style="width: 100%; height: 40px;" {disabled} onclick={() => {
|
||||
if(viewName) view.switch(viewName)
|
||||
if(onclick) onclick();
|
||||
}}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
style="width: 100%; height: 40px;"
|
||||
{disabled}
|
||||
onclick={() => {
|
||||
if (viewName) view.switch(viewName);
|
||||
if (onclick) onclick();
|
||||
}}
|
||||
>
|
||||
<Icon />
|
||||
{text}
|
||||
{#if progress == -1}
|
||||
@ -23,4 +38,4 @@
|
||||
{:else if progress}
|
||||
<Progress value={progress} />
|
||||
{/if}
|
||||
</Button>
|
||||
</Button>
|
||||
|
@ -1,5 +1,11 @@
|
||||
<script>
|
||||
import { CodeIcon, InfoIcon, LanguagesIcon, MapIcon, PaintbrushIcon } from "@lucide/svelte";
|
||||
import {
|
||||
CodeIcon,
|
||||
InfoIcon,
|
||||
LanguagesIcon,
|
||||
MapIcon,
|
||||
PaintbrushIcon,
|
||||
} from "@lucide/svelte";
|
||||
import SidebarHeader from "../SidebarHeader.svelte";
|
||||
import SettingsButton from "./SettingsButton.svelte";
|
||||
import { getDeveloperToggle } from "./developer.svelte";
|
||||
@ -7,26 +13,28 @@
|
||||
const dev = getDeveloperToggle();
|
||||
</script>
|
||||
|
||||
<SidebarHeader>
|
||||
Settings
|
||||
</SidebarHeader>
|
||||
<SidebarHeader>Settings</SidebarHeader>
|
||||
|
||||
<div id="sections">
|
||||
<section>
|
||||
<h2>General</h2>
|
||||
<SettingsButton icon={LanguagesIcon} text="Language" disabled />
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<h2>Map</h2>
|
||||
<SettingsButton icon={MapIcon} text="Offline Maps" view="offline-maps" />
|
||||
<SettingsButton icon={PaintbrushIcon} text="Map Style" disabled />
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<h2>About</h2>
|
||||
{#if dev.current == "true"}
|
||||
<SettingsButton icon={CodeIcon} text="Developer Settings" view="dev-options" />
|
||||
<SettingsButton
|
||||
icon={CodeIcon}
|
||||
text="Developer Settings"
|
||||
view="dev-options"
|
||||
/>
|
||||
{/if}
|
||||
<SettingsButton icon={InfoIcon} text="About" view="about" />
|
||||
</section>
|
||||
@ -44,4 +52,4 @@
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -1,7 +1,7 @@
|
||||
export function getDeveloperToggle() {
|
||||
const value = localStorage.getItem("developer")
|
||||
const value = localStorage.getItem("developer");
|
||||
const state = $state({
|
||||
current: value == null ? "false" : value
|
||||
current: value == null ? "false" : value,
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
@ -9,4 +9,4 @@ export function getDeveloperToggle() {
|
||||
});
|
||||
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,10 @@
|
||||
<ProgressPrimitive.Root
|
||||
bind:ref
|
||||
data-slot="progress"
|
||||
class={cn("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", className)}
|
||||
class={cn(
|
||||
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",
|
||||
className,
|
||||
)}
|
||||
{value}
|
||||
{max}
|
||||
{...restProps}
|
||||
|
1386
src/lib/mapLayers.ts
1386
src/lib/mapLayers.ts
File diff suppressed because it is too large
Load Diff
@ -1,22 +1,33 @@
|
||||
import { appDataDir, join } from "@tauri-apps/api/path";
|
||||
import { BaseDirectory, exists, mkdir, open, remove, SeekMode } from "@tauri-apps/plugin-fs";
|
||||
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";
|
||||
|
||||
export async function downloadPMTiles(url: string, name: string, onprogress?: (progress: number, total: number) => any): Promise<void> {
|
||||
// if(!window.__TAURI__) {
|
||||
// throw new Error("Tauri environment is not available.");
|
||||
// }
|
||||
export async function downloadPMTiles(
|
||||
url: string,
|
||||
name: string,
|
||||
onprogress?: (progress: number, total: number) => void,
|
||||
): Promise<void> {
|
||||
// if(!window.__TAURI__) {
|
||||
// throw new Error("Tauri environment is not available.");
|
||||
// }
|
||||
|
||||
const filename = name + ".pmtiles";
|
||||
const baseDir = BaseDirectory.AppData;
|
||||
const appDataDirPath = await appDataDir();
|
||||
|
||||
if(!await exists(appDataDirPath)) {
|
||||
if (!(await exists(appDataDirPath))) {
|
||||
await mkdir(appDataDirPath, { recursive: true });
|
||||
}
|
||||
|
||||
if(await exists(filename, { baseDir })) {
|
||||
if (await exists(filename, { baseDir })) {
|
||||
console.log(`File ${filename} already exists, deleting it.`);
|
||||
await remove(filename, { baseDir });
|
||||
}
|
||||
@ -30,30 +41,36 @@ export async function downloadPMTiles(url: string, name: string, onprogress?: (p
|
||||
|
||||
const path = await join(appDataDirPath, filename);
|
||||
|
||||
let totalProgress = 0;
|
||||
let totalProgress = 0;
|
||||
await download(url, path, ({ progress, total }) => {
|
||||
totalProgress += progress;
|
||||
console.log(`Download progress: ${Math.round((totalProgress / total) * 100)}% (${totalProgress}\tof ${total} bytes)`);
|
||||
if(onprogress) onprogress(totalProgress, total);
|
||||
totalProgress += progress;
|
||||
console.log(
|
||||
`Download progress: ${Math.round((totalProgress / total) * 100)}% (${totalProgress}\tof ${total} bytes)`,
|
||||
);
|
||||
if (onprogress) onprogress(totalProgress, total);
|
||||
});
|
||||
|
||||
console.log(`Download completed: ${path}`);
|
||||
}
|
||||
|
||||
export async function getRemoteList(): Promise<{ name: string, file: string }[]> {
|
||||
return await fetch("https://trafficcue-tiles.picoscratch.de/index.json").then(res => res.json());
|
||||
export async function getRemoteList(): Promise<
|
||||
{ name: string; file: string }[]
|
||||
> {
|
||||
return await fetch("https://trafficcue-tiles.picoscratch.de/index.json").then(
|
||||
(res) => res.json(),
|
||||
);
|
||||
}
|
||||
|
||||
export async function hasPMTiles(name: string): Promise<boolean> {
|
||||
if(!window.__TAURI__) {
|
||||
if (!window.__TAURI__) {
|
||||
return false; // Tauri environment is not available
|
||||
}
|
||||
|
||||
|
||||
const filename = name + ".pmtiles";
|
||||
const baseDir = BaseDirectory.AppData;
|
||||
const appDataDirPath = await appDataDir();
|
||||
|
||||
if(!await exists(appDataDirPath)) {
|
||||
if (!(await exists(appDataDirPath))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -62,21 +79,21 @@ export async function hasPMTiles(name: string): Promise<boolean> {
|
||||
}
|
||||
|
||||
export async function getPMTilesURL(name: string) {
|
||||
if(!window.__TAURI__) {
|
||||
if (!window.__TAURI__) {
|
||||
return `pmtiles://https://trafficcue-tiles.picoscratch.de/${name}.pmtiles`;
|
||||
}
|
||||
const filename = name + ".pmtiles";
|
||||
const baseDir = BaseDirectory.AppData;
|
||||
const appDataDirPath = await appDataDir();
|
||||
|
||||
if(!await exists(appDataDirPath)) {
|
||||
if (!(await exists(appDataDirPath))) {
|
||||
return `pmtiles://https://trafficcue-tiles.picoscratch.de/${name}.pmtiles`;
|
||||
// throw new Error("App data directory does not exist.");
|
||||
}
|
||||
|
||||
const filePath = await join(appDataDirPath, filename);
|
||||
|
||||
if(!await exists(filePath, { baseDir })) {
|
||||
if (!(await exists(filePath, { baseDir }))) {
|
||||
return `pmtiles://https://trafficcue-tiles.picoscratch.de/${name}.pmtiles`;
|
||||
// throw new Error(`PMTiles file not found: ${filePath}`);
|
||||
}
|
||||
@ -84,8 +101,15 @@ export async function getPMTilesURL(name: string) {
|
||||
return `tiles://${name}`;
|
||||
}
|
||||
|
||||
async function readBytes(name: string, offset: number, length: number): Promise<Uint8Array> {
|
||||
const file = await open(name + ".pmtiles", { read: true, baseDir: BaseDirectory.AppData });
|
||||
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);
|
||||
@ -100,135 +124,142 @@ export class FSSource implements Source {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
async getBytes(offset: number, length: number, _signal?: AbortSignal, _etag?: string) { // TODO: abort signal
|
||||
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
|
||||
}
|
||||
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;
|
||||
};
|
||||
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;
|
||||
/** @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;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
/**
|
||||
* 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(8);
|
||||
let instance = this.tiles.get(pmtilesUrl);
|
||||
if (!instance) {
|
||||
instance = new PMTiles(new FSSource(pmtilesUrl));
|
||||
this.tiles.set(pmtilesUrl, instance);
|
||||
}
|
||||
/** @hidden */
|
||||
tilev4 = async (
|
||||
params: RequestParameters,
|
||||
abortController: AbortController,
|
||||
) => {
|
||||
if (params.type === "json") {
|
||||
const pmtilesUrl = params.url.substr(8);
|
||||
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),
|
||||
};
|
||||
}
|
||||
if (this.metadata) {
|
||||
return {
|
||||
data: await instance.getTileJson(params.url),
|
||||
};
|
||||
}
|
||||
|
||||
const h = await instance.getHeader();
|
||||
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.`
|
||||
);
|
||||
}
|
||||
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(/tiles:\/\/(.+)\/(\d+)\/(\d+)\/(\d+)/);
|
||||
const result = params.url.match(re);
|
||||
if (!result) {
|
||||
throw new Error("Invalid Tiles protocol URL");
|
||||
}
|
||||
const pmtilesUrl = result[1];
|
||||
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(/tiles:\/\/(.+)\/(\d+)\/(\d+)\/(\d+)/);
|
||||
const result = params.url.match(re);
|
||||
if (!result) {
|
||||
throw new Error("Invalid Tiles protocol URL");
|
||||
}
|
||||
const pmtilesUrl = result[1];
|
||||
|
||||
let instance = this.tiles.get(pmtilesUrl);
|
||||
if (!instance) {
|
||||
instance = new PMTiles(new FSSource(pmtilesUrl));
|
||||
this.tiles.set(pmtilesUrl, instance);
|
||||
}
|
||||
const z = result[2];
|
||||
const x = result[3];
|
||||
const y = result[4];
|
||||
let instance = this.tiles.get(pmtilesUrl);
|
||||
if (!instance) {
|
||||
instance = new PMTiles(new FSSource(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 };
|
||||
};
|
||||
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({
|
||||
|
@ -60,7 +60,12 @@ out center tags;`,
|
||||
}).then((res) => res.json() as Promise<OverpassResult>);
|
||||
}
|
||||
|
||||
export async function fetchNearbyPOI(lat: number, lon: number, tags: string[], radius: number) {
|
||||
export async function fetchNearbyPOI(
|
||||
lat: number,
|
||||
lon: number,
|
||||
tags: string[],
|
||||
radius: number,
|
||||
) {
|
||||
return await fetch(OVERPASS_SERVER, {
|
||||
method: "POST",
|
||||
body: `[out:json];
|
||||
|
@ -23,7 +23,7 @@ export default async function say(text: string) {
|
||||
duck();
|
||||
if (tts !== "web") {
|
||||
try {
|
||||
await invoke("plugin:tts|speak", { text })
|
||||
await invoke("plugin:tts|speak", { text });
|
||||
} catch (e) {
|
||||
console.error("Error speaking text", e);
|
||||
alert(e);
|
||||
|
@ -129,7 +129,7 @@ function drawCurrentTrip() {
|
||||
}
|
||||
|
||||
export async function startRoute(trip: Trip) {
|
||||
if(window.__TAURI__) {
|
||||
if (window.__TAURI__) {
|
||||
await keepScreenOn(true);
|
||||
}
|
||||
routing.currentTrip = trip;
|
||||
@ -270,7 +270,7 @@ export function stopNavigation() {
|
||||
routing.currentTrip = null;
|
||||
map.updateMapPadding(); // TODO: REMOVE
|
||||
removeAllRoutes();
|
||||
if(window.__TAURI__) {
|
||||
if (window.__TAURI__) {
|
||||
keepScreenOn(false);
|
||||
}
|
||||
}
|
||||
|
@ -12,22 +12,23 @@ export default defineConfig({
|
||||
port: 5173,
|
||||
strictPort: true,
|
||||
host: host || false,
|
||||
hmr: host ? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421
|
||||
} : undefined,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: "ws",
|
||||
host,
|
||||
port: 1421,
|
||||
}
|
||||
: undefined,
|
||||
watch: {
|
||||
ignored: ["**/src-tauri/**"]
|
||||
}
|
||||
ignored: ["**/src-tauri/**"],
|
||||
},
|
||||
},
|
||||
envPrefix: ["VITE_", "TAURI_ENV_*"],
|
||||
build: {
|
||||
target: process.env.TAURI_ENV_PLATFORM == "windows"
|
||||
? "chrome105"
|
||||
: "safari13",
|
||||
target:
|
||||
process.env.TAURI_ENV_PLATFORM == "windows" ? "chrome105" : "safari13",
|
||||
minify: !process.env.TAURI_ENV_DEBUG ? "esbuild" : false,
|
||||
sourcemap: !!process.env.TAURI_ENV_DEBUG
|
||||
sourcemap: !!process.env.TAURI_ENV_DEBUG,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
Reference in New Issue
Block a user