feat: fetch metadata from pmtiles
This commit is contained in:
20
bun.lock
20
bun.lock
@ -6,6 +6,7 @@
|
||||
"dependencies": {
|
||||
"@diffusionstudio/vits-web": "^1.0.3",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@mapbox/vector-tile": "^2.0.4",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-upload": "~2",
|
||||
"@turf/turf": "^7.2.0",
|
||||
@ -19,6 +20,7 @@
|
||||
"libsodium-wrappers": "^0.7.15",
|
||||
"opening_hours": "^3.8.0",
|
||||
"pako": "^2.1.0",
|
||||
"pbf": "^4.0.1",
|
||||
"pmtiles": "^4.3.0",
|
||||
"sql.js": "^1.13.0",
|
||||
"svelte-maplibre-gl": "^0.1.8",
|
||||
@ -206,13 +208,13 @@
|
||||
|
||||
"@mapbox/jsonlint-lines-primitives": ["@mapbox/jsonlint-lines-primitives@2.0.2", "", {}, "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ=="],
|
||||
|
||||
"@mapbox/point-geometry": ["@mapbox/point-geometry@0.1.0", "", {}, "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="],
|
||||
"@mapbox/point-geometry": ["@mapbox/point-geometry@1.1.0", "", {}, "sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ=="],
|
||||
|
||||
"@mapbox/tiny-sdf": ["@mapbox/tiny-sdf@2.0.6", "", {}, "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA=="],
|
||||
|
||||
"@mapbox/unitbezier": ["@mapbox/unitbezier@0.0.1", "", {}, "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw=="],
|
||||
|
||||
"@mapbox/vector-tile": ["@mapbox/vector-tile@1.3.1", "", { "dependencies": { "@mapbox/point-geometry": "~0.1.0" } }, "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw=="],
|
||||
"@mapbox/vector-tile": ["@mapbox/vector-tile@2.0.4", "", { "dependencies": { "@mapbox/point-geometry": "~1.1.0", "@types/geojson": "^7946.0.16", "pbf": "^4.0.1" } }, "sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg=="],
|
||||
|
||||
"@mapbox/whoots-js": ["@mapbox/whoots-js@3.1.0", "", {}, "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q=="],
|
||||
|
||||
@ -1000,7 +1002,7 @@
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"pbf": ["pbf@3.3.0", "", { "dependencies": { "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" }, "bin": { "pbf": "bin/pbf" } }, "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q=="],
|
||||
"pbf": ["pbf@4.0.1", "", { "dependencies": { "resolve-protobuf-schema": "^2.1.0" }, "bin": { "pbf": "bin/pbf" } }, "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
@ -1222,8 +1224,14 @@
|
||||
|
||||
"global-prefix/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
|
||||
|
||||
"maplibre-gl/@mapbox/point-geometry": ["@mapbox/point-geometry@0.1.0", "", {}, "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="],
|
||||
|
||||
"maplibre-gl/@mapbox/vector-tile": ["@mapbox/vector-tile@1.3.1", "", { "dependencies": { "@mapbox/point-geometry": "~0.1.0" } }, "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw=="],
|
||||
|
||||
"maplibre-gl/earcut": ["earcut@3.0.1", "", {}, "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw=="],
|
||||
|
||||
"maplibre-gl/pbf": ["pbf@3.3.0", "", { "dependencies": { "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" }, "bin": { "pbf": "bin/pbf" } }, "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"rbush/quickselect": ["quickselect@2.0.0", "", {}, "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="],
|
||||
@ -1242,6 +1250,12 @@
|
||||
|
||||
"vaul-svelte/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
|
||||
|
||||
"vt-pbf/@mapbox/point-geometry": ["@mapbox/point-geometry@0.1.0", "", {}, "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ=="],
|
||||
|
||||
"vt-pbf/@mapbox/vector-tile": ["@mapbox/vector-tile@1.3.1", "", { "dependencies": { "@mapbox/point-geometry": "~0.1.0" } }, "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw=="],
|
||||
|
||||
"vt-pbf/pbf": ["pbf@3.3.0", "", { "dependencies": { "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" }, "bin": { "pbf": "bin/pbf" } }, "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"geojson-polygon-self-intersections/rbush/quickselect": ["quickselect@1.1.1", "", {}, "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ=="],
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
"dependencies": {
|
||||
"@diffusionstudio/vits-web": "^1.0.3",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@mapbox/vector-tile": "^2.0.4",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-upload": "~2",
|
||||
"@turf/turf": "^7.2.0",
|
||||
@ -55,6 +56,7 @@
|
||||
"libsodium-wrappers": "^0.7.15",
|
||||
"opening_hours": "^3.8.0",
|
||||
"pako": "^2.1.0",
|
||||
"pbf": "^4.0.1",
|
||||
"pmtiles": "^4.3.0",
|
||||
"sql.js": "^1.13.0",
|
||||
"svelte-maplibre-gl": "^0.1.8",
|
||||
|
||||
@ -182,8 +182,8 @@
|
||||
{(location.speed * 3.6 | 0).toFixed(0)}
|
||||
</div>
|
||||
{/if}
|
||||
{#if getRoadMetadata()}
|
||||
{@const meta = getRoadMetadata()!}
|
||||
{#if getRoadMetadata().current}
|
||||
{@const meta = getRoadMetadata().current!}
|
||||
{#if meta.maxspeed}
|
||||
{@const maxspeed = getSpeed(meta.maxspeed)}
|
||||
{#if maxspeed && maxspeed < 100}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { LNV_SERVER } from "$lib/services/hosts";
|
||||
import { routing } from "$lib/services/navigation/routing.svelte";
|
||||
import { getTransportationMeta } from "$lib/services/TileMeta";
|
||||
import type { WrappedValue } from "$lib/services/stores.svelte";
|
||||
import { getMeta } from "$lib/services/TileMeta";
|
||||
import { map } from "./map.svelte";
|
||||
|
||||
export const location = $state({
|
||||
@ -40,7 +41,7 @@ export function isDriving() {
|
||||
return _isDriving;
|
||||
}
|
||||
|
||||
const roadMetadata = $derived(getTransportationMeta({ lat: location.lat, lon: location.lng }));
|
||||
const roadMetadata: WrappedValue<GeoJSON.GeoJsonProperties> = $state({ current: null });
|
||||
|
||||
export function getRoadMetadata() {
|
||||
return roadMetadata;
|
||||
@ -60,6 +61,10 @@ export function watchLocation() {
|
||||
location.heading = pos.coords.heading;
|
||||
location.lastUpdate = new Date();
|
||||
|
||||
getMeta({ lat: location.lat, lon: location.lng }, "transportation").then((meta) => {
|
||||
roadMetadata.current = meta;
|
||||
});
|
||||
|
||||
if (location.locked) {
|
||||
map.value?.flyTo(
|
||||
{
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { map } from "$lib/components/lnv/map.svelte";
|
||||
import { lineString, pointToLineDistance } from "@turf/turf";
|
||||
import { FSSource, hasPMTiles } from "./OfflineTiles";
|
||||
import { PMTiles } from "pmtiles";
|
||||
import { VectorTile } from "@mapbox/vector-tile";
|
||||
import Protobuf from "pbf";
|
||||
|
||||
function getFeatureDistance(f: GeoJSON.Feature, point: [number, number]) {
|
||||
if (f.geometry.type === "LineString") {
|
||||
@ -16,11 +19,17 @@ function getFeatureDistance(f: GeoJSON.Feature, point: [number, number]) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getTransportationMeta(coord: WorldLocation) {
|
||||
if(!map.value) return null;
|
||||
const features = map.value.querySourceFeatures("openmaptiles", {
|
||||
sourceLayer: "transportation"
|
||||
});
|
||||
export async function getMeta(coord: WorldLocation, layer: string) {
|
||||
const zxy = coordToTile(coord, 14);
|
||||
const tile = await fetchTile(zxy.z, zxy.x, zxy.y);
|
||||
const layerData = tile.layers[layer];
|
||||
if (!layerData) return null;
|
||||
|
||||
const features: GeoJSON.Feature[] = [];
|
||||
for (let i = 0; i < layerData.length; i++) {
|
||||
const feature = layerData.feature(i);
|
||||
features.push(feature.toGeoJSON(zxy.x, zxy.y, zxy.z));
|
||||
}
|
||||
const filtered = features.filter((f) => f.geometry.type === "LineString" || f.geometry.type == "MultiLineString");
|
||||
if(filtered.length === 0) return null;
|
||||
const nearest = filtered.reduce((a, b) => {
|
||||
@ -47,3 +56,74 @@ export function getSpeed(maxspeed: string): number | null {
|
||||
if(maxspeed === "walk") return 7; // https://wiki.openstreetmap.org/wiki/Proposed_features/maxspeed_walk
|
||||
return IMPLICIT_SPEEDS[maxspeed as keyof typeof IMPLICIT_SPEEDS] || null;
|
||||
}
|
||||
|
||||
function coordToTile(coord: WorldLocation, zoom: number) {
|
||||
const z = zoom;
|
||||
const x = Math.floor(((coord.lon + 180) / 360) * Math.pow(2, z));
|
||||
const y = Math.floor(
|
||||
((1 -
|
||||
Math.log(
|
||||
Math.tan((coord.lat * Math.PI) / 180) +
|
||||
1 / Math.cos((coord.lat * Math.PI) / 180)
|
||||
) /
|
||||
Math.PI) /
|
||||
2) *
|
||||
Math.pow(2, z)
|
||||
);
|
||||
return { z, x, y };
|
||||
}
|
||||
|
||||
class Cache<T> {
|
||||
data: Map<string, T>;
|
||||
size: number;
|
||||
|
||||
constructor(size: number) {
|
||||
this.data = new Map<string, T>();
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
get(key: string): T | undefined {
|
||||
if (this.data.has(key)) {
|
||||
const value = this.data.get(key)!;
|
||||
this.data.delete(key);
|
||||
this.data.set(key, value);
|
||||
return value;
|
||||
}
|
||||
return this.data.get(key);
|
||||
}
|
||||
|
||||
has(key: string): boolean {
|
||||
return this.data.has(key);
|
||||
}
|
||||
|
||||
set(key: string, value: T): void {
|
||||
this.data.set(key, value);
|
||||
if (this.data.size > this.size) {
|
||||
const firstKey = this.data.keys().next().value;
|
||||
if (firstKey) {
|
||||
this.data.delete(firstKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tileCache = new Cache<VectorTile>(5);
|
||||
|
||||
export async function fetchTile(z: number, x: number, y: number) {
|
||||
const cacheKey = `${z}/${x}/${y}`;
|
||||
if (tileCache.has(cacheKey)) {
|
||||
return tileCache.get(cacheKey)!;
|
||||
}
|
||||
const pmtiles = (await hasPMTiles("tiles"))
|
||||
? new PMTiles(new FSSource("tiles"))
|
||||
: new PMTiles("https://trafficcue-tiles.picoscratch.de/germany.pmtiles");
|
||||
const tile = (await pmtiles.getZxy(z, x, y));
|
||||
if (!tile) {
|
||||
console.log(tile);
|
||||
throw new Error(`Tile not found: z${z} x${x} y${y}`);
|
||||
}
|
||||
const data = tile.data;
|
||||
const vectorTile = new VectorTile(new Protobuf(data));
|
||||
tileCache.set(cacheKey, vectorTile);
|
||||
return vectorTile;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user