diff --git a/src/lib/components/lnv/location.svelte.ts b/src/lib/components/lnv/location.svelte.ts index e572104..fd88368 100644 --- a/src/lib/components/lnv/location.svelte.ts +++ b/src/lib/components/lnv/location.svelte.ts @@ -1,7 +1,7 @@ import { LNV_SERVER } from "$lib/services/hosts"; import { routing } from "$lib/services/navigation/routing.svelte"; import type { WrappedValue } from "$lib/services/stores.svelte"; -import { getFeature, getMeta } from "$lib/services/TileMeta"; +import { getFeature } from "$lib/services/TileMeta"; import { lineString, nearestPointOnLine, point } from "@turf/turf"; import { map } from "./map.svelte"; @@ -51,6 +51,7 @@ const roadFeature: WrappedValue = $state({ const snappedLocation: WrappedValue = $state({ current: null, }); +let lastFeatureId: string | null = null; export function getRoadMetadata() { return roadMetadata; @@ -116,15 +117,19 @@ export function watchLocation() { getFeature( { lat: location.lat, lon: location.lng }, "transportation", - (f) => { - if(f.properties) { - return !blacklist.includes(f.properties["class"]); - } - return true; + { + filter: (f) => { + if(f.properties) { + return !blacklist.includes(f.properties["class"]); + } + return true; + }, + lastId: lastFeatureId || undefined, } ).then((feature) => { roadFeature.current = feature; roadMetadata.current = feature ? feature.properties : null; + lastFeatureId = feature ? String(feature.id) : null; snapLocation(); }); diff --git a/src/lib/services/TileMeta.ts b/src/lib/services/TileMeta.ts index 4de3692..3e169a3 100644 --- a/src/lib/services/TileMeta.ts +++ b/src/lib/services/TileMeta.ts @@ -3,15 +3,16 @@ import { FSSource, hasPMTiles } from "./OfflineTiles"; import { PMTiles } from "pmtiles"; import { VectorTile } from "@mapbox/vector-tile"; import Protobuf from "pbf"; +import { location } from "$lib/components/lnv/location.svelte"; function getFeatureDistance(f: GeoJSON.Feature, point: [number, number]) { if (f.geometry.type === "LineString") { - return pointToLineDistance(point, lineString(f.geometry.coordinates)); + return pointToLineDistance(point, lineString(f.geometry.coordinates), { units: "meters" }); } else if (f.geometry.type === "MultiLineString") { // Compute the min distance across all parts return Math.min( ...f.geometry.coordinates.map((coords) => - pointToLineDistance(point, lineString(coords)), + pointToLineDistance(point, lineString(coords), { units: "meters" }), ), ); } else { @@ -19,7 +20,17 @@ function getFeatureDistance(f: GeoJSON.Feature, point: [number, number]) { } } -export async function getFeature(coord: WorldLocation, layer: string, filter?: (feature: GeoJSON.Feature) => boolean) { +interface GetFeatureOptions { + lastId?: string; + filter?: (feature: GeoJSON.Feature) => boolean; +} + +function getBias() { + if(!location.speed) return 5; + return Math.max(5, Math.min(15, location.speed * 0.5)); // Bias increases with speed, min 5, max 15, 0.5 per km/h +} + +export async function getFeature(coord: WorldLocation, layer: string, { lastId, filter }: GetFeatureOptions = {}) { const zxy = coordToTile(coord, 14); const tile = await fetchTile(zxy.z, zxy.x, zxy.y); const layerData = tile.layers[layer]; @@ -36,15 +47,31 @@ export async function getFeature(coord: WorldLocation, layer: string, filter?: ( ).filter((f) => (filter ? filter(f) : true)); if (filtered.length === 0) return null; const nearest = filtered.reduce((a, b) => { - const distA = getFeatureDistance(a, [coord.lon, coord.lat]); - const distB = getFeatureDistance(b, [coord.lon, coord.lat]); + let distA = getFeatureDistance(a, [coord.lon, coord.lat]); + let distB = getFeatureDistance(b, [coord.lon, coord.lat]); + + console.log("lastId:", lastId, "a.id:", a.id, "b.id:", b.id); + const STAY_BIAS = getBias(); + + if (lastId && String(a.id) == lastId) { + console.log("Applying stay bias to B"); + distB += STAY_BIAS; + } + if (lastId && String(b.id) == lastId) { + console.log("Applying stay bias to A"); + distA += STAY_BIAS; + } + + console.log("Distances:", distA, distB); + return distA < distB ? a : b; }); + console.log("ID: ", nearest.id); return nearest; } -export async function getMeta(coord: WorldLocation, layer: string, filter?: (feature: GeoJSON.Feature) => boolean) { - const nearest = await getFeature(coord, layer, filter); +export async function getMeta(coord: WorldLocation, layer: string, options?: GetFeatureOptions) { + const nearest = await getFeature(coord, layer, options); return nearest ? nearest.properties : null; } diff --git a/src/lib/services/hosts.ts b/src/lib/services/hosts.ts index ab1f60c..595b3c3 100644 --- a/src/lib/services/hosts.ts +++ b/src/lib/services/hosts.ts @@ -9,4 +9,4 @@ export const LNV_SERVER = ? "http://localhost:3000/api" : location.hostname.includes("staging") ? "https://staging-trafficcue-api.picoscratch.de/api" - : "https://trafficcue-api.picoscratch.de/api"; + : "https://staging-trafficcue-api.picoscratch.de/api";