342 lines
7.9 KiB
Svelte
342 lines
7.9 KiB
Svelte
<script lang="ts">
|
|
import { Button } from "$lib/components/ui/button";
|
|
import { POIIcons } from "$lib/POIIcons";
|
|
import {
|
|
BriefcaseIcon,
|
|
EllipsisIcon,
|
|
GlobeIcon,
|
|
HomeIcon,
|
|
MailIcon,
|
|
PhoneIcon,
|
|
RouteIcon,
|
|
SchoolIcon,
|
|
} from "@lucide/svelte";
|
|
import { pin } from "../map.svelte";
|
|
import SidebarHeader from "./SidebarHeader.svelte";
|
|
import { fetchPOI, type OverpassElement } from "$lib/services/Overpass";
|
|
import OpeningHours from "../info/OpeningHours.svelte";
|
|
import Badge from "$lib/components/ui/badge/badge.svelte";
|
|
import FuelStation from "../info/FuelStation.svelte";
|
|
import { view } from "../view.svelte";
|
|
import * as Popover from "$lib/components/ui/popover";
|
|
import Reviews from "../info/Reviews.svelte";
|
|
import MapAi from "../info/MapAI.svelte";
|
|
import RequiresCapability from "../RequiresCapability.svelte";
|
|
import { getDeveloperToggle } from "./settings/developer.svelte";
|
|
import InternetAccess from "../info/InternetAccess.svelte";
|
|
import RestaurantInfo from "../info/RestaurantInfo.svelte";
|
|
import { m } from "$lang/messages";
|
|
import { onMount } from "svelte";
|
|
import { updateStore } from "$lib/services/stores.svelte";
|
|
|
|
// let { feature }: { feature: Feature } = $props();
|
|
|
|
// let Icon = $derived(POIIcons[feature.properties.osm_key + "=" + feature.properties.osm_value]);
|
|
|
|
let { lat, lng }: { lat: number; lng: number } = $props();
|
|
const dev = getDeveloperToggle();
|
|
|
|
function getIcon(
|
|
tags: Record<string, string>,
|
|
): (typeof POIIcons)[keyof typeof POIIcons] | null {
|
|
const key = Object.keys(tags).find(
|
|
(k) => k.startsWith("amenity") || k.startsWith("shop"),
|
|
);
|
|
if (key && POIIcons[key + "=" + tags[key]]) {
|
|
return POIIcons[key + "=" + tags[key]];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getDistance(
|
|
aLat: number,
|
|
aLon: number,
|
|
lat: number,
|
|
lon: number,
|
|
): number {
|
|
const R = 6371e3; // Earth radius in meters
|
|
const φ1 = (lat * Math.PI) / 180;
|
|
const φ2 = (aLat * Math.PI) / 180;
|
|
const Δφ = ((aLat - lat) * Math.PI) / 180;
|
|
const Δλ = ((aLon - lon) * Math.PI) / 180;
|
|
|
|
const a =
|
|
Math.sin(Δφ / 2) ** 2 +
|
|
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) ** 2;
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
|
|
return R * c;
|
|
}
|
|
|
|
function sortByDistance(
|
|
elements: OverpassElement[],
|
|
lat: number,
|
|
lng: number,
|
|
): OverpassElement[] {
|
|
return elements.sort((a: OverpassElement, b: OverpassElement) => {
|
|
const aLoc = a.center || a;
|
|
const bLoc = b.center || b;
|
|
return (
|
|
getDistance(aLoc.lat!, aLoc.lon!, lat, lng) -
|
|
getDistance(bLoc.lat!, bLoc.lon!, lat, lng)
|
|
);
|
|
});
|
|
}
|
|
|
|
onMount(() => {
|
|
view.loading = true;
|
|
});
|
|
</script>
|
|
|
|
{#await fetchPOI(lat, lng, 20).then((r) => {
|
|
view.loading = false;
|
|
return r;
|
|
})}
|
|
<SidebarHeader
|
|
onback={() => {
|
|
pin.liftPin();
|
|
}}
|
|
>
|
|
{m["sidebar.info.dropped"]()}
|
|
</SidebarHeader>
|
|
<div id="actions">
|
|
<Button
|
|
onclick={() => {
|
|
view.switch("route", {
|
|
to: lat + "," + lng,
|
|
});
|
|
}}
|
|
>
|
|
<RouteIcon />
|
|
{m["sidebar.info.route"]()}
|
|
</Button>
|
|
</div>
|
|
<p>{m.loading()}</p>
|
|
{:then res}
|
|
{#if res.elements.length === 0}
|
|
<SidebarHeader
|
|
onback={() => {
|
|
pin.liftPin();
|
|
}}
|
|
>
|
|
{m["sidebar.info.dropped"]()}
|
|
</SidebarHeader>
|
|
<span style="color: #acacac;">© OpenStreetMap</span>
|
|
<pre>{JSON.stringify(res, null, 2)}</pre>
|
|
{:else}
|
|
{@const elements = sortByDistance(res.elements, lat, lng)}
|
|
{@const tags = elements[0].tags}
|
|
{@const firstElement = elements[0]}
|
|
{@const ellat = firstElement.center?.lat || firstElement.lat!}
|
|
{@const ellng = firstElement.center?.lon || firstElement.lon!}
|
|
|
|
<SidebarHeader
|
|
onback={() => {
|
|
pin.liftPin();
|
|
}}
|
|
>
|
|
{#if getIcon(tags)}
|
|
{@const Icon = getIcon(tags)}
|
|
<Icon />
|
|
{/if}
|
|
{tags.name ||
|
|
(tags["addr:street"]
|
|
? tags["addr:street"] + " " + tags["addr:housenumber"]
|
|
: "")}
|
|
</SidebarHeader>
|
|
<div id="actions">
|
|
<Button
|
|
onclick={() => {
|
|
view.switch("route", {
|
|
to: lat + "," + lng,
|
|
});
|
|
}}
|
|
>
|
|
<RouteIcon />
|
|
{m["sidebar.info.route"]()}
|
|
</Button>
|
|
{#if tags.email || tags["contact:email"]}
|
|
<Button
|
|
variant="secondary"
|
|
href={`mailto:${tags.email || tags["contact:email"]}`}
|
|
target="_blank"
|
|
>
|
|
<MailIcon />
|
|
{m["sidebar.info.email"]()}
|
|
</Button>
|
|
{/if}
|
|
{#if tags.website || tags["contact:website"]}
|
|
<Button
|
|
variant="secondary"
|
|
href={tags.website || tags["contact:website"]}
|
|
target="_blank"
|
|
>
|
|
<GlobeIcon />
|
|
{m["sidebar.info.website"]()}
|
|
</Button>
|
|
{/if}
|
|
{#if tags.phone || tags["contact:phone"]}
|
|
<Button
|
|
variant="secondary"
|
|
href={`tel:${tags.phone || tags["contact:phone"]}`}
|
|
target="_blank"
|
|
>
|
|
<PhoneIcon />
|
|
{m["sidebar.info.call"]()}
|
|
</Button>
|
|
{/if}
|
|
<Popover.Root>
|
|
<Popover.Trigger>
|
|
<Button variant="secondary">
|
|
<EllipsisIcon />
|
|
{m.more()}
|
|
</Button>
|
|
</Popover.Trigger>
|
|
<Popover.Content>
|
|
<div class="flex flex-col gap-2">
|
|
<Button
|
|
variant="outline"
|
|
onclick={() => {
|
|
updateStore(
|
|
{ name: "home", type: "location" },
|
|
{
|
|
lat,
|
|
lng,
|
|
name: "home",
|
|
icon: "home",
|
|
},
|
|
);
|
|
}}
|
|
>
|
|
<HomeIcon />
|
|
{m["sidebar.info.set-as"]({ name: m["saved.home"]() })}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onclick={() => {
|
|
updateStore(
|
|
{ name: "school", type: "location" },
|
|
{
|
|
lat,
|
|
lng,
|
|
name: "school",
|
|
icon: "school",
|
|
},
|
|
);
|
|
}}
|
|
>
|
|
<SchoolIcon />
|
|
{m["sidebar.info.set-as"]({ name: m["saved.school"]() })}
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onclick={() => {
|
|
updateStore(
|
|
{ name: "work", type: "location" },
|
|
{
|
|
lat,
|
|
lng,
|
|
name: "work",
|
|
icon: "work",
|
|
},
|
|
);
|
|
}}
|
|
>
|
|
<BriefcaseIcon />
|
|
{m["sidebar.info.set-as"]({ name: m["saved.work"]() })}
|
|
</Button>
|
|
{#if dev.current == "true"}
|
|
<Button
|
|
variant="outline"
|
|
onclick={() => {
|
|
alert(JSON.stringify(elements, null, 3));
|
|
console.log(elements);
|
|
}}
|
|
>
|
|
Show raw data
|
|
</Button>
|
|
{/if}
|
|
</div>
|
|
</Popover.Content>
|
|
</Popover.Root>
|
|
</div>
|
|
|
|
<RequiresCapability capability="ai">
|
|
<MapAi lat={ellat} lon={ellng} />
|
|
</RequiresCapability>
|
|
|
|
<!--
|
|
"addr:city": "Hagen",
|
|
"addr:housenumber": "12",
|
|
"addr:postcode": "58135",
|
|
"addr:street": -->
|
|
<p class="mt-2">{tags["addr:street"]} {tags["addr:housenumber"]}</p>
|
|
<p>{tags["addr:postcode"]} {tags["addr:city"]}</p>
|
|
|
|
{#if tags.opening_hours}
|
|
<OpeningHours hours={tags.opening_hours} {lat} lon={lng} />
|
|
{/if}
|
|
|
|
{#if tags.internet_access}
|
|
<InternetAccess {tags} />
|
|
{/if}
|
|
|
|
<RestaurantInfo {tags} />
|
|
|
|
{#if tags.amenity == "fuel"}
|
|
<FuelStation {tags} {lat} {lng} />
|
|
{/if}
|
|
|
|
<!-- any payment:* tag -->
|
|
{#if Object.keys(tags).some((key) => key.startsWith("payment:"))}
|
|
<h3 class="text-lg font-bold mt-2">
|
|
{m["sidebar.info.payment-methods"]()}
|
|
</h3>
|
|
<ul style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
|
{#each Object.entries(tags).filter( ([key]) => key.startsWith("payment:"), ) as [key, value] (key)}
|
|
<Badge
|
|
>{key.replace("payment:", "")}{value == "yes"
|
|
? ""
|
|
: ": " + value}</Badge
|
|
>
|
|
{/each}
|
|
</ul>
|
|
{/if}
|
|
|
|
<RequiresCapability capability="reviews">
|
|
<Reviews lat={ellat} lng={ellng} />
|
|
</RequiresCapability>
|
|
|
|
<span style="color: #acacac;">© OpenStreetMap</span>
|
|
{/if}
|
|
{:catch err}
|
|
<SidebarHeader
|
|
onback={() => {
|
|
pin.liftPin();
|
|
}}
|
|
>
|
|
Dropped Pin
|
|
</SidebarHeader>
|
|
<div id="actions">
|
|
<Button
|
|
onclick={() => {
|
|
view.switch("route", {
|
|
to: lat + "," + lng,
|
|
});
|
|
}}
|
|
>
|
|
<RouteIcon />
|
|
{m["sidebar.info.route"]()}
|
|
</Button>
|
|
</div>
|
|
<p>Error: {err.message}</p>
|
|
{/await}
|
|
|
|
<style>
|
|
#actions {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
overflow-y: scroll;
|
|
}
|
|
</style>
|