feat: add speed limit display
Some checks failed
TrafficCue CI / check (push) Failing after 1m53s
TrafficCue CI / build (push) Successful in 10m10s
TrafficCue CI / build-android (push) Successful in 30m40s

This commit is contained in:
2025-10-20 17:51:06 +02:00
parent b46646a462
commit 4ef2667c4e
5 changed files with 366 additions and 1 deletions

View File

@@ -14,6 +14,7 @@
import RequiresCapability from "./RequiresCapability.svelte";
import {
advertiseRemoteLocation,
getRoadMetadata,
location,
remoteLocation,
} from "./location.svelte";
@@ -27,6 +28,7 @@
import Progressbar from "../Progressbar.svelte";
import { postHazard } from "$lib/services/lnv";
import { fetchHazards } from "./hazards.svelte";
import { getSpeed } from "$lib/services/TileMeta";
const views: Record<string, string> = {
main: "MainSidebar",
@@ -180,6 +182,17 @@
{(location.speed * 3.6 | 0).toFixed(0)}
</div>
{/if}
{#if getRoadMetadata()}
{@const meta = getRoadMetadata()!}
{#if meta.maxspeed}
{@const maxspeed = getSpeed(meta.maxspeed)}
{#if maxspeed && maxspeed < 100}
<div id="max-speed" style="position: fixed; {mobileView ? `bottom: calc(50px + ${sidebarHeight.current}px + 10px + 2ch + 20px + 10px); right: 10px;` : "bottom: calc(10px + 2ch + 20px + 10px); right: 10px;"}">
{meta.maxspeed}
</div>
{/if}
{/if}
{/if}
<div
id="sidebar"
@@ -406,7 +419,8 @@
padding-bottom: calc(40px + env(safe-area-inset-bottom));
}
#speedometer {
#speedometer,
#max-speed {
z-index: 30;
background-color: hsla(0, 0%, 5%, 0.6);
backdrop-filter: blur(5px);
@@ -417,5 +431,12 @@
border-radius: 10px;
text-align: center;
width: calc(2ch + 20px);
height: calc(2ch + 20px);
}
#max-speed {
border-radius: 50%;
border: 5px solid red;
padding: 5px;
}
</style>

View File

@@ -1,5 +1,6 @@
import { LNV_SERVER } from "$lib/services/hosts";
import { routing } from "$lib/services/navigation/routing.svelte";
import { getTransportationMeta } from "$lib/services/TileMeta";
import { map } from "./map.svelte";
export const location = $state({
@@ -39,6 +40,12 @@ export function isDriving() {
return _isDriving;
}
const roadMetadata = $derived(getTransportationMeta({ lat: location.lat, lon: location.lng }));
export function getRoadMetadata() {
return roadMetadata;
}
export function watchLocation() {
if (navigator.geolocation) {
navigator.geolocation.watchPosition(

View File

@@ -0,0 +1,49 @@
import { map } from "$lib/components/lnv/map.svelte";
import { lineString, pointToLineDistance } from "@turf/turf";
function getFeatureDistance(f: GeoJSON.Feature, point: [number, number]) {
if (f.geometry.type === "LineString") {
return pointToLineDistance(point, lineString(f.geometry.coordinates));
} 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))
)
);
} else {
return Infinity;
}
}
export function getTransportationMeta(coord: WorldLocation) {
if(!map.value) return null;
const features = map.value.querySourceFeatures("openmaptiles", {
sourceLayer: "transportation"
});
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) => {
const distA = getFeatureDistance(a, [coord.lon, coord.lat]);
const distB = getFeatureDistance(b, [coord.lon, coord.lat]);
return distA < distB ? a : b;
});
return nearest ? nearest.properties : null;
}
const IMPLICIT_SPEEDS: Record<string, number> = {
"DE:urban": 50,
"DE:rural": 100, // TODO: 80 (hgv weight > 3.5t, or trailer), 60 (weight > 7.5t)
"DE:living_street": 7,
"DE:bicycle_road": 30
}
export function getSpeed(maxspeed: string): number | null {
if(!isNaN(parseInt(maxspeed))) return parseInt(maxspeed);
if(maxspeed.endsWith(" mph")) {
const val = parseInt(maxspeed.replace(" mph", ""));
if(!isNaN(val)) return Math.round(val * 1.60934); // Convert to km/h
}
if(maxspeed === "walk") return 7; // https://wiki.openstreetmap.org/wiki/Proposed_features/maxspeed_walk
return IMPLICIT_SPEEDS[maxspeed as keyof typeof IMPLICIT_SPEEDS] || null;
}