diff --git a/docker-compose.yml b/docker-compose.yml index 0dfd8ccb..f3814097 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,7 +71,7 @@ services: volumes: - ./data/valhalla:/custom_files environment: - - tile_urls=https://download.geofabrik.de/europe/germany-latest.osm.pbf + - tile_urls=https://download.geofabrik.de/europe/germany-latest.osm.pbf https://download.geofabrik.de/europe/germany/bayern/oberbayern-latest.osm.pbf - use_tiles_ignore_pbf=True - force_rebuild=False - force_rebuild_elevation=False diff --git a/web/src/lib/components/trail/map_with_elevation.svelte b/web/src/lib/components/trail/map_with_elevation.svelte index fe233fd5..4db80dca 100644 --- a/web/src/lib/components/trail/map_with_elevation.svelte +++ b/web/src/lib/components/trail/map_with_elevation.svelte @@ -14,14 +14,15 @@ export let map: Map | null = null; export let options = {}; export let graticule: AutoGraticule | null = null; + export let crosshair: boolean = false; const dispatch = createEventDispatcher(); let L: any; let controlElevation: any; - $: if (trail?.expand.gpx_data && controlElevation) { - controlElevation.clear(); + $: if (trail?.expand.gpx_data !== undefined && controlElevation) { + controlElevation.clear(); controlElevation.load(trail.expand.gpx_data); } @@ -31,7 +32,9 @@ await import("leaflet.awesome-markers"); //@ts-ignore await import("$lib/vendor/leaflet-elevation/src/index.js"); - const AutoGraticule = (await import("$lib/vendor/leaflet-graticule/leaflet-auto-graticule")).default; + const AutoGraticule = ( + await import("$lib/vendor/leaflet-graticule/leaflet-auto-graticule") + ).default; map = L.map("map", { preferCanvas: true }).setView( [trail?.lat ?? 0, trail?.lon ?? 0], @@ -43,6 +46,10 @@ dispatch("zoomend", map); }); + map!.on("click", function (e) { + dispatch("click", e); + }); + L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: "© OpenStreetMap contributors", }).addTo(map); @@ -99,7 +106,7 @@ iconColor: "white", }), }, - graticule: false + graticule: false, }; const elevation_options = Object.assign( @@ -115,16 +122,21 @@ markers.push(marker); } - if(elevation_options.graticule) { + if (elevation_options.graticule) { graticule = new AutoGraticule(); graticule.addTo(map!); } - });
-
+
diff --git a/web/src/lib/components/trail/trail_info_panel.svelte b/web/src/lib/components/trail/trail_info_panel.svelte index 711f95cb..8c295c07 100644 --- a/web/src/lib/components/trail/trail_info_panel.svelte +++ b/web/src/lib/components/trail/trail_info_panel.svelte @@ -38,7 +38,7 @@ const tabs = [ $_("description"), - $_("waypoints"), + $_("waypoints", { values: { n: 2 } }), $_("photos"), $_("summit-book"), ...($currentUser ? [$_("comment", { values: { n: 2 } })] : []), diff --git a/web/src/lib/i18n/locales/de.json b/web/src/lib/i18n/locales/de.json index 5083e610..a7dd9b5f 100644 --- a/web/src/lib/i18n/locales/de.json +++ b/web/src/lib/i18n/locales/de.json @@ -40,6 +40,7 @@ "distance": "Distanz", "documentation": "Dokumentation", "download-gpx": "GPX herunterladen", + "draw-on-map": "", "dutch": "Niederländisch", "easy": "Einfach", "edit": "Bearbeiten", @@ -128,6 +129,7 @@ "show-on-map": "Auf der Karte anzeigen", "slogan": "Speichere deine Abenteuer!", "sort": "Sortieren", + "stop-drawing": "", "summit-book": "Gipfelbuch", "text": "Text", "trail": "{n, plural, =1 {Route} other {Routen}}", @@ -136,7 +138,7 @@ "upload-file": "Datei hochladen", "upload-gpx": "GPX hochladen", "username": "Nutzername", - "waypoints": "Wegpunkte", + "waypoints": "{n, plural, =1 {Wegpunkt} other {Wegpunkte}}", "welcome_to": "Willkommen bei", "wrong-username-or-password": "Falscher Nutzername oder falsches Passwort" } diff --git a/web/src/lib/i18n/locales/en.json b/web/src/lib/i18n/locales/en.json index 131cf09a..1a365926 100644 --- a/web/src/lib/i18n/locales/en.json +++ b/web/src/lib/i18n/locales/en.json @@ -40,6 +40,7 @@ "distance": "Distance", "documentation": "Documentation", "download-gpx": "Download GPX", + "draw-on-map": "Draw on map", "dutch": "Dutch", "easy": "Easy", "edit": "Edit", @@ -128,6 +129,7 @@ "show-on-map": "Show on map", "slogan": "Save your adventures!", "sort": "Sort", + "stop-drawing": "Stop drawing", "summit-book": "Summit Book", "text": "Text", "trail": "{n, plural, =1 {Trail} other {Trails}}", @@ -136,7 +138,7 @@ "upload-file": "Upload file", "upload-gpx": "Upload GPX", "username": "Username", - "waypoints": "Waypoints", + "waypoints": "{n, plural, =1 {Waypoint} other {Waypoints}}", "welcome_to": "Welcome to", "wrong-username-or-password": "Wrong username or password" } diff --git a/web/src/lib/i18n/locales/fr.json b/web/src/lib/i18n/locales/fr.json index ffba2cd3..4c3af7ca 100644 --- a/web/src/lib/i18n/locales/fr.json +++ b/web/src/lib/i18n/locales/fr.json @@ -40,6 +40,7 @@ "distance": "Distance", "documentation": "Documentation", "download-gpx": "Télécharger le GPX", + "draw-on-map": "", "dutch": "Néerlandais", "easy": "Facile", "edit": "Editer", @@ -128,6 +129,7 @@ "show-on-map": "Voir sur la carte", "slogan": "Sauvegarder vos aventures !", "sort": "Trier", + "stop-drawing": "", "summit-book": "Livre du sommet", "text": "Texte", "trail": "{n, plural, =1 {Itinéraire} other {Itinéraires}}", diff --git a/web/src/lib/i18n/locales/hu.json b/web/src/lib/i18n/locales/hu.json index b84c910a..acbb5aa3 100644 --- a/web/src/lib/i18n/locales/hu.json +++ b/web/src/lib/i18n/locales/hu.json @@ -40,6 +40,7 @@ "distance": "Távolság", "documentation": "Dokumentáció", "download-gpx": "GPX letöltése", + "draw-on-map": "", "dutch": "Holland", "easy": "Könnyű", "edit": "Szerkesztés", @@ -128,6 +129,7 @@ "show-on-map": "Mutatás térképen", "slogan": "Mentse el a kalandjait!", "sort": "Rendezés", + "stop-drawing": "", "summit-book": "Csúcspont könyv", "text": "Szöveg", "trail": "{n, plural, =1 {Útvonal} other {Útvonalak}}", diff --git a/web/src/lib/i18n/locales/nl.json b/web/src/lib/i18n/locales/nl.json index 9d7a11b9..764a177c 100644 --- a/web/src/lib/i18n/locales/nl.json +++ b/web/src/lib/i18n/locales/nl.json @@ -40,6 +40,7 @@ "distance": "Afstand", "documentation": "Documentatie", "download-gpx": "GPX-bestand downloaden", + "draw-on-map": "", "dutch": "Nederlands", "easy": "Makkelijk", "edit": "Bewerken", @@ -128,6 +129,7 @@ "show-on-map": "Tonen op kaart", "slogan": "Bewaar je avonturen!", "sort": "Sorteren", + "stop-drawing": "", "summit-book": "Bergtopboek", "text": "Tekst", "trail": "{n, plural, =1 {wandelroute} other {wandelroutes}}", diff --git a/web/src/lib/i18n/locales/pl.json b/web/src/lib/i18n/locales/pl.json index 9ce4314a..0b22aca3 100644 --- a/web/src/lib/i18n/locales/pl.json +++ b/web/src/lib/i18n/locales/pl.json @@ -40,6 +40,7 @@ "distance": "Dystans", "documentation": "Dokumentacja", "download-gpx": "Popierz GPX", + "draw-on-map": "", "dutch": "Niderlandzki", "easy": "Łatwy", "edit": "Edytuj", @@ -128,6 +129,7 @@ "show-on-map": "Pokaż na mapie", "slogan": "Zapisz swoją wyprawę!", "sort": "Sortowanie", + "stop-drawing": "", "summit-book": "Logbook", "text": "Tekst", "trail": "{n, plural, =1 {Szlak} other {Szlaki}}", diff --git a/web/src/lib/i18n/locales/pt.json b/web/src/lib/i18n/locales/pt.json index 3928d8f1..4e9c8028 100644 --- a/web/src/lib/i18n/locales/pt.json +++ b/web/src/lib/i18n/locales/pt.json @@ -40,6 +40,7 @@ "distance": "Distância", "documentation": "Documentação", "download-gpx": "Baixar GPX", + "draw-on-map": "", "dutch": "Holandês", "easy": "Fácil", "edit": "Editar", @@ -128,6 +129,7 @@ "show-on-map": "Mostrar no mapa", "slogan": "Guarde as suas aventuras!", "sort": "Ordenar", + "stop-drawing": "", "summit-book": "Livro da cimeira", "text": "Texto", "trail": "{n, plural, =1 {Percurso} other {Percursos}}", diff --git a/web/src/lib/i18n/locales/zh.json b/web/src/lib/i18n/locales/zh.json index c5585021..b9a23232 100644 --- a/web/src/lib/i18n/locales/zh.json +++ b/web/src/lib/i18n/locales/zh.json @@ -40,6 +40,7 @@ "distance": "距离", "documentation": "文档", "download-gpx": "下载 GPX", + "draw-on-map": "", "dutch": "荷兰语", "easy": "简单", "edit": "编辑", @@ -128,6 +129,7 @@ "show-on-map": "地图中展示", "slogan": "保存你的冒险!", "sort": "排序", + "stop-drawing": "", "summit-book": "详细日程", "text": "文本", "trail": "{n, plural, =1 {路线} other {路线}}", diff --git a/web/src/lib/models/valhalla.ts b/web/src/lib/models/valhalla.ts new file mode 100644 index 00000000..9350d6b7 --- /dev/null +++ b/web/src/lib/models/valhalla.ts @@ -0,0 +1,31 @@ +interface ValhallaResponse { + trip: { + legs: { + shape: string[]; + }[]; + }; +} + +class ValhallaRoute { + type: "Feature"; + properties: {}; + geometry: { + type: "LineString"; + coordinates: number[][]; + }; + + constructor() { + this.type = "Feature" + this.properties = {} + this.geometry = { + type: "LineString", + coordinates: [] + } + } + + toString() { + return JSON.stringify(this); + } +} + +export {type ValhallaResponse, ValhallaRoute} \ No newline at end of file diff --git a/web/src/lib/stores/valhalla_store.ts b/web/src/lib/stores/valhalla_store.ts new file mode 100644 index 00000000..8ed1001b --- /dev/null +++ b/web/src/lib/stores/valhalla_store.ts @@ -0,0 +1,79 @@ +import { ValhallaRoute, type ValhallaResponse } from "$lib/models/valhalla"; +import { ClientResponseError } from "pocketbase"; + + + +export const route: ValhallaRoute = new ValhallaRoute(); + + +export function clearRoute() { + route.geometry.coordinates = []; +} + +export async function calculateRouteBetween(startLat: number, startLon: number, endLat: number, endLon: number) { + const requestBody = { + "directions_type": "none", + "locations": [{ "lat": startLat, "lon": startLon }, { "lat": endLat, "lon": endLon }], + "costing": "pedestrian", "costing_options": { "pedestrian": { "max_hiking_difficulty": 6, "use_ferry": 0 } } + } + const r = await fetch("/api/v1/valhalla", { method: "POST", body: JSON.stringify(requestBody) }) + if (!r.ok) { + throw new ClientResponseError(await r.json()) + } + const response: ValhallaResponse = await r.json(); + + appendToRoute(response); +} + +function appendToRoute(valhallaResponse: any) { + const routeGeometry = decodeShape(valhallaResponse.trip.legs[0].shape); + + for (const point of routeGeometry) { + route.geometry.coordinates.push([point[1], point[0]]) + } +} + + +function decodeShape(shape: string, precision: number = 6) { + var index = 0, + lat = 0, + lng = 0, + coordinates = [], + shift = 0, + result = 0, + byte = null, + latitude_change, + longitude_change, + factor = Math.pow(10, precision); + + while (index < shape.length) { + byte = null; + shift = 0; + result = 0; + + do { + byte = shape.charCodeAt(index++) - 63; + result |= (byte & 0x1f) << shift; + shift += 5; + } while (byte >= 0x20); + + latitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1)); + + shift = result = 0; + + do { + byte = shape.charCodeAt(index++) - 63; + result |= (byte & 0x1f) << shift; + shift += 5; + } while (byte >= 0x20); + + longitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1)); + + lat += latitude_change; + lng += longitude_change; + + coordinates.push([lat / factor, lng / factor]); + } + + return coordinates; +}; \ No newline at end of file diff --git a/web/src/routes/api/v1/valhalla/+server.ts b/web/src/routes/api/v1/valhalla/+server.ts index 275b5228..60d0f70c 100644 --- a/web/src/routes/api/v1/valhalla/+server.ts +++ b/web/src/routes/api/v1/valhalla/+server.ts @@ -1,16 +1,20 @@ -import { error, json, type RequestEvent } from "@sveltejs/kit"; -import { env } from '$env/dynamic/public' +import { env } from '$env/dynamic/public'; +import { error, json, type NumericRange, type RequestEvent } from "@sveltejs/kit"; export async function POST(event: RequestEvent) { const data = await event.request.json() - if(!env.PUBLIC_VALHALLA_URL) { + if (!env.PUBLIC_VALHALLA_URL) { return error(400, "PUBLIC_VALHALLA_URL not set") } try { - const r = await event.fetch(env.PUBLIC_VALHALLA_URL + '/route', data); - return json(r); + const r = await event.fetch(env.PUBLIC_VALHALLA_URL + '/route', { method: "POST", body: JSON.stringify(data) }); + const response = await r.json(); + if (!r.ok) { + throw error(r.status as NumericRange<400,500>, response); + } + return json(response); } catch (e: any) { - throw error(e.status, e) + throw error(e.status || 500, e) } } \ No newline at end of file diff --git a/web/src/routes/trail/edit/[id]/+page.svelte b/web/src/routes/trail/edit/[id]/+page.svelte index dabf307c..744e04cf 100644 --- a/web/src/routes/trail/edit/[id]/+page.svelte +++ b/web/src/routes/trail/edit/[id]/+page.svelte @@ -1,4 +1,5 @@ @@ -342,9 +392,21 @@ on:submit={handleSubmit} >

{$_("pick-a-trail")}

- {$_("upload-file")} + {#if PUBLIC_VALHALLA_URL} + + {/if}
-

{$_("waypoints")}

+

+ {$_("waypoints", { values: { n: 2 } })} +

    {#each $form.expand.waypoints ?? [] as waypoint, i}
  • openMarkerPopup(waypoint)}> @@ -549,7 +613,13 @@ {loading}>{$_("save-trail")} - + handleMapClick(e.detail)} + >