feat: initial map matching
Some checks failed
TrafficCue CI / check (push) Failing after 1m38s

This commit is contained in:
2025-10-21 20:08:08 +02:00
parent 5ec74129e2
commit aa0bcdd091
3 changed files with 87 additions and 4 deletions

View File

@ -7,7 +7,7 @@
decodePolyline, decodePolyline,
routing, routing,
} from "$lib/services/navigation/routing.svelte"; } from "$lib/services/navigation/routing.svelte";
import { location } from "./location.svelte"; import { getSnappedLocation, location } from "./location.svelte";
import RoutingLayers from "$lib/services/navigation/RoutingLayers.svelte"; import RoutingLayers from "$lib/services/navigation/RoutingLayers.svelte";
import { import {
getPMTilesURL, getPMTilesURL,
@ -137,6 +137,15 @@
pitchAlignment="map" pitchAlignment="map"
/> />
{/if} {/if}
{#if getSnappedLocation().current}
<Marker
lnglat={{
lat: getSnappedLocation().current!.lat,
lng: getSnappedLocation().current!.lon,
}}
color="blue"
/>
{/if}
<MapLocationMarkers /> <MapLocationMarkers />

View File

@ -1,7 +1,8 @@
import { LNV_SERVER } from "$lib/services/hosts"; import { LNV_SERVER } from "$lib/services/hosts";
import { routing } from "$lib/services/navigation/routing.svelte"; import { routing } from "$lib/services/navigation/routing.svelte";
import type { WrappedValue } from "$lib/services/stores.svelte"; import type { WrappedValue } from "$lib/services/stores.svelte";
import { getMeta } from "$lib/services/TileMeta"; import { getFeature, getMeta } from "$lib/services/TileMeta";
import { lineString, nearestPointOnLine, point } from "@turf/turf";
import { map } from "./map.svelte"; import { map } from "./map.svelte";
export const location = $state({ export const location = $state({
@ -44,11 +45,59 @@ export function isDriving() {
const roadMetadata: WrappedValue<GeoJSON.GeoJsonProperties> = $state({ const roadMetadata: WrappedValue<GeoJSON.GeoJsonProperties> = $state({
current: null, current: null,
}); });
const roadFeature: WrappedValue<GeoJSON.Feature | null> = $state({
current: null,
});
const snappedLocation: WrappedValue<WorldLocation | null> = $state({
current: null,
});
export function getRoadMetadata() { export function getRoadMetadata() {
return roadMetadata; return roadMetadata;
} }
export function getRoadFeature() {
return roadFeature;
}
export function getSnappedLocation() {
return snappedLocation;
}
function snapLocation() {
const feature = roadFeature.current;
console.log("Snapping location to road feature:", feature);
if (!feature) return;
if (feature.geometry.type === "LineString") {
const loc = nearestPointOnLine(lineString(feature.geometry.coordinates), point([location.lng, location.lat]));
snappedLocation.current = {
lat: loc.geometry.coordinates[1],
lon: loc.geometry.coordinates[0],
};
} else if (feature.geometry.type === "MultiLineString") {
// Find nearest point across all parts
let nearestLoc: GeoJSON.Feature<GeoJSON.Point> | null = null;
let minDist = Infinity;
for (const coords of feature.geometry.coordinates) {
const loc = nearestPointOnLine(lineString(coords), point([location.lng, location.lat]));
const dist = Math.hypot(
loc.geometry.coordinates[0] - location.lng,
loc.geometry.coordinates[1] - location.lat,
);
if (dist < minDist) {
minDist = dist;
nearestLoc = loc;
}
}
if (nearestLoc) {
snappedLocation.current = {
lat: nearestLoc.geometry.coordinates[1],
lon: nearestLoc.geometry.coordinates[0],
};
}
}
}
export function watchLocation() { export function watchLocation() {
if (navigator.geolocation) { if (navigator.geolocation) {
navigator.geolocation.watchPosition( navigator.geolocation.watchPosition(
@ -63,12 +112,32 @@ export function watchLocation() {
location.heading = pos.coords.heading; location.heading = pos.coords.heading;
location.lastUpdate = new Date(); location.lastUpdate = new Date();
const blacklist = ["footway", "platform"];
getMeta( getMeta(
{ lat: location.lat, lon: location.lng }, { lat: location.lat, lon: location.lng },
"transportation", "transportation",
(f) => {
if(f.properties) {
return !blacklist.includes(f.properties["subclass"]);
}
return true;
}
).then((meta) => { ).then((meta) => {
roadMetadata.current = meta; roadMetadata.current = meta;
}); });
getFeature(
{ lat: location.lat, lon: location.lng },
"transportation",
(f) => {
if(f.properties) {
return !blacklist.includes(f.properties["subclass"]);
}
return true;
}
).then((feature) => {
roadFeature.current = feature;
snapLocation();
});
if (location.locked) { if (location.locked) {
map.value?.flyTo( map.value?.flyTo(

View File

@ -19,7 +19,7 @@ function getFeatureDistance(f: GeoJSON.Feature, point: [number, number]) {
} }
} }
export async function getMeta(coord: WorldLocation, layer: string) { export async function getFeature(coord: WorldLocation, layer: string, filter?: (feature: GeoJSON.Feature) => boolean) {
const zxy = coordToTile(coord, 14); const zxy = coordToTile(coord, 14);
const tile = await fetchTile(zxy.z, zxy.x, zxy.y); const tile = await fetchTile(zxy.z, zxy.x, zxy.y);
const layerData = tile.layers[layer]; const layerData = tile.layers[layer];
@ -33,13 +33,18 @@ export async function getMeta(coord: WorldLocation, layer: string) {
const filtered = features.filter( const filtered = features.filter(
(f) => (f) =>
f.geometry.type === "LineString" || f.geometry.type == "MultiLineString", f.geometry.type === "LineString" || f.geometry.type == "MultiLineString",
); ).filter((f) => (filter ? filter(f) : true));
if (filtered.length === 0) return null; if (filtered.length === 0) return null;
const nearest = filtered.reduce((a, b) => { const nearest = filtered.reduce((a, b) => {
const distA = getFeatureDistance(a, [coord.lon, coord.lat]); const distA = getFeatureDistance(a, [coord.lon, coord.lat]);
const distB = getFeatureDistance(b, [coord.lon, coord.lat]); const distB = getFeatureDistance(b, [coord.lon, coord.lat]);
return distA < distB ? a : b; return distA < distB ? a : b;
}); });
return nearest;
}
export async function getMeta(coord: WorldLocation, layer: string, filter?: (feature: GeoJSON.Feature) => boolean) {
const nearest = await getFeature(coord, layer, filter);
return nearest ? nearest.properties : null; return nearest ? nearest.properties : null;
} }