feat: fetch metadata from pmtiles
This commit is contained in:
@@ -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;
|
||||
@@ -59,6 +60,10 @@ export function watchLocation() {
|
||||
location.available = true;
|
||||
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