This commit is contained in:
@ -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 />
|
||||||
|
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user