This repository has been archived on 2025-11-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
trafficcue-client/src/lib/components/lnv/sidebar/NearbyPOISidebar.svelte
Jannik b8ddada94e
Some checks failed
TrafficCue CI / check (push) Failing after 1m34s
TrafficCue CI / build-android (push) Has been cancelled
TrafficCue CI / build (push) Has been cancelled
fix: add text wrap in POI list, increase search radius and exclude street_side parking
2025-09-30 10:28:25 +02:00

112 lines
2.8 KiB
Svelte

<script lang="ts">
import { m } from "$lang/messages";
import Button from "$lib/components/ui/button/button.svelte";
import { fetchNearbyPOI, type OverpassElement } from "$lib/services/Overpass";
import { location } from "../location.svelte";
import { map, pin } from "../map.svelte";
import { view } from "../view.svelte";
import SidebarHeader from "./SidebarHeader.svelte";
let { tags }: { tags?: string } = $props();
let pois: OverpassElement[] = $state([]);
$effect(() => {
if (!tags) {
view.loading = false;
pois = [];
return;
}
view.loading = true;
fetchNearbyPOI(location.lat, location.lng, tags.split(","), 5000).then(
(results) => {
pois = results.elements.sort((a, b) => {
const distA = distanceTo(
a.lat ?? a.center?.lat ?? 0.0,
a.lon ?? a.center?.lon ?? 0.0,
);
const distB = distanceTo(
b.lat ?? b.center?.lat ?? 0.0,
b.lon ?? b.center?.lon ?? 0.0,
);
return distA - distB;
});
view.loading = false;
},
);
});
// returns meters
function distanceTo(lat: number, lon: number) {
const R = 6371e3; // metres
const φ1 = (location.lat * Math.PI) / 180; // φ, λ in radians
const φ2 = (lat * Math.PI) / 180;
const Δφ = ((lat - location.lat) * Math.PI) / 180;
const Δλ = ((lon - location.lng) * Math.PI) / 180;
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // in metres
}
</script>
<SidebarHeader>
{#if tags}
{tags.split(",")[0].split("=")[1]?.replace(/_/g, " ") ?? "POIs"}
{:else}
{m["sidebar.nearby-poi.header"]()}
{/if}
</SidebarHeader>
{#if view.loading}
<div class="text-sm text-muted-foreground">{m.loading()}</div>
{:else}
<div class="flex flex-col gap-2 p-2">
{#if pois.length === 0}
<div class="text-sm text-muted-foreground">
{m["sidebar.nearby-poi.no-poi"]()}
</div>
{/if}
{#each pois as poi (poi.id)}
<Button
variant="secondary"
class="w-full flex flex-col items-start h-auto"
onclick={() => {
pin.dropPin(
poi.lat ?? poi.center?.lat ?? 0.0,
poi.lon ?? poi.center?.lon ?? 0.0,
);
pin.showInfo();
map.value?.flyTo({
center: [
poi.lon ?? poi.center?.lon ?? 0.0,
poi.lat ?? poi.center?.lat ?? 0.0,
],
zoom: 19,
});
}}
>
<div class="font-bold text-wrap">
{poi.tags.name ?? poi.tags.brand ?? m.unnamed()}
</div>
<div class="text-sm">
{#if poi.tags.amenity}
<span class="capitalize">{poi.tags.amenity}</span>
{/if}
<span>
{Math.round(
distanceTo(
poi.lat ?? poi.center?.lat ?? 0.0,
poi.lon ?? poi.center?.lon ?? 0.0,
),
)}m
</span>
</div>
</Button>
{/each}
</div>
{/if}