uses map with elevation for edit mode
This commit is contained in:
@@ -56,6 +56,7 @@ services:
|
|||||||
UPLOAD_FOLDER: /app/uploads
|
UPLOAD_FOLDER: /app/uploads
|
||||||
UPLOAD_USER:
|
UPLOAD_USER:
|
||||||
UPLOAD_PASSWORD:
|
UPLOAD_PASSWORD:
|
||||||
|
PUBLIC_VALHALLA_URL: http://localhost:8002
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/uploads:/app/uploads
|
- ./data/uploads:/app/uploads
|
||||||
ports:
|
ports:
|
||||||
@@ -63,6 +64,20 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- wanderer
|
- wanderer
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
valhalla:
|
||||||
|
image: ghcr.io/gis-ops/docker-valhalla/valhalla:latest
|
||||||
|
ports:
|
||||||
|
- "8002:8002"
|
||||||
|
volumes:
|
||||||
|
- ./data/valhalla:/custom_files
|
||||||
|
environment:
|
||||||
|
- tile_urls=https://download.geofabrik.de/europe/germany-latest.osm.pbf
|
||||||
|
- use_tiles_ignore_pbf=True
|
||||||
|
- force_rebuild=False
|
||||||
|
- force_rebuild_elevation=False
|
||||||
|
- build_elevation=True
|
||||||
|
- build_admins=True
|
||||||
|
- build_time_zones=True
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
wanderer:
|
wanderer:
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { isRouteProtected } from '$lib/util/authorization_util'
|
|||||||
import { redirect, type Handle } from '@sveltejs/kit'
|
import { redirect, type Handle } from '@sveltejs/kit'
|
||||||
import { MeiliSearch } from 'meilisearch'
|
import { MeiliSearch } from 'meilisearch'
|
||||||
import { locale } from 'svelte-i18n'
|
import { locale } from 'svelte-i18n'
|
||||||
import { get } from 'svelte/store'
|
|
||||||
|
|
||||||
export const handle: Handle = async ({ event, resolve }) => {
|
export const handle: Handle = async ({ event, resolve }) => {
|
||||||
// load the store data from the request cookie string
|
// load the store data from the request cookie string
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
||||||
|
import "$lib/vendor/leaflet-elevation/src/index.css";
|
||||||
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
|
import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule";
|
||||||
import type { Map, Marker } from "leaflet";
|
import type { Map, Marker } from "leaflet";
|
||||||
|
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
||||||
|
import "leaflet/dist/leaflet.css";
|
||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
|
|
||||||
export let trail: Trail;
|
export let trail: Trail | null;
|
||||||
export let markers: Marker[] = [];
|
export let markers: Marker[] = [];
|
||||||
export let map: Map | null = null;
|
export let map: Map | null = null;
|
||||||
export let options = {};
|
export let options = {};
|
||||||
@@ -17,7 +20,8 @@
|
|||||||
let L: any;
|
let L: any;
|
||||||
let controlElevation: any;
|
let controlElevation: any;
|
||||||
|
|
||||||
$: if (trail.expand.gpx_data && controlElevation) {
|
$: if (trail?.expand.gpx_data && controlElevation) {
|
||||||
|
controlElevation.clear();
|
||||||
controlElevation.load(trail.expand.gpx_data);
|
controlElevation.load(trail.expand.gpx_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,8 +34,8 @@
|
|||||||
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(
|
map = L.map("map", { preferCanvas: true }).setView(
|
||||||
[trail.lat ?? 0, trail.lon ?? 0],
|
[trail?.lat ?? 0, trail?.lon ?? 0],
|
||||||
14,
|
3,
|
||||||
);
|
);
|
||||||
map!.attributionControl.setPrefix(false);
|
map!.attributionControl.setPrefix(false);
|
||||||
|
|
||||||
@@ -105,7 +109,7 @@
|
|||||||
|
|
||||||
controlElevation = L.control.elevation(elevation_options).addTo(map);
|
controlElevation = L.control.elevation(elevation_options).addTo(map);
|
||||||
|
|
||||||
for (const waypoint of trail.expand.waypoints) {
|
for (const waypoint of trail?.expand.waypoints ?? []) {
|
||||||
const marker = createMarkerFromWaypoint(L, waypoint);
|
const marker = createMarkerFromWaypoint(L, waypoint);
|
||||||
marker.addTo(map!);
|
marker.addTo(map!);
|
||||||
markers.push(marker);
|
markers.push(marker);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
validationSchema: waypointSchema,
|
validationSchema: waypointSchema,
|
||||||
onSubmit: async (submittedWaypoint) => {
|
onSubmit: async (submittedWaypoint) => {
|
||||||
dispatch("save", submittedWaypoint);
|
dispatch("save", submittedWaypoint);
|
||||||
|
|
||||||
closeModal!();
|
closeModal!();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Trail } from "$lib/models/trail";
|
|||||||
import { Waypoint } from "$lib/models/waypoint";
|
import { Waypoint } from "$lib/models/waypoint";
|
||||||
import { kml, tcx } from "$lib/vendor/toGeoJSON/toGeoJSON"
|
import { kml, tcx } from "$lib/vendor/toGeoJSON/toGeoJSON"
|
||||||
import GeoJsonToGpx from "$lib/vendor/geoJSONToGPX"
|
import GeoJsonToGpx from "$lib/vendor/geoJSONToGPX"
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
||||||
const R = 6371; // Radius of the Earth in km
|
const R = 6371; // Radius of the Earth in km
|
||||||
@@ -17,8 +18,15 @@ function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: numbe
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function gpx2trail(gpx: string) {
|
export async function gpx2trail(gpx: string) {
|
||||||
|
let xml;
|
||||||
|
if (browser) {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
xml = parser.parseFromString(gpx, "application/xml")
|
||||||
|
} else {
|
||||||
const JSDOM = (await import("jsdom")).JSDOM
|
const JSDOM = (await import("jsdom")).JSDOM
|
||||||
const xml = new JSDOM(gpx).window.document
|
xml = new JSDOM(gpx).window.document
|
||||||
|
}
|
||||||
|
|
||||||
const trail = new Trail("");
|
const trail = new Trail("");
|
||||||
|
|
||||||
let name = xml.getElementsByTagName('name');
|
let name = xml.getElementsByTagName('name');
|
||||||
|
|||||||
16
web/src/routes/api/v1/valhalla/+server.ts
Normal file
16
web/src/routes/api/v1/valhalla/+server.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { error, json, type RequestEvent } from "@sveltejs/kit";
|
||||||
|
import { env } from '$env/dynamic/public'
|
||||||
|
|
||||||
|
|
||||||
|
export async function POST(event: RequestEvent) {
|
||||||
|
const data = await event.request.json()
|
||||||
|
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);
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e.status, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from "$app/stores";
|
|
||||||
import Button from "$lib/components/base/button.svelte";
|
import Button from "$lib/components/base/button.svelte";
|
||||||
import Datepicker from "$lib/components/base/datepicker.svelte";
|
import Datepicker from "$lib/components/base/datepicker.svelte";
|
||||||
import Select from "$lib/components/base/select.svelte";
|
import Select from "$lib/components/base/select.svelte";
|
||||||
@@ -9,6 +8,7 @@
|
|||||||
import ListSelectModal from "$lib/components/list/list_select_modal.svelte";
|
import ListSelectModal from "$lib/components/list/list_select_modal.svelte";
|
||||||
import SummitLogCard from "$lib/components/summit_log/summit_log_card.svelte";
|
import SummitLogCard from "$lib/components/summit_log/summit_log_card.svelte";
|
||||||
import SummitLogModal from "$lib/components/summit_log/summit_log_modal.svelte";
|
import SummitLogModal from "$lib/components/summit_log/summit_log_modal.svelte";
|
||||||
|
import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte";
|
||||||
import PhotoPicker from "$lib/components/trail/photo_picker.svelte";
|
import PhotoPicker from "$lib/components/trail/photo_picker.svelte";
|
||||||
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
|
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
|
||||||
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
|
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
|
||||||
@@ -37,14 +37,11 @@
|
|||||||
formatElevation,
|
formatElevation,
|
||||||
formatTimeHHMM,
|
formatTimeHHMM,
|
||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import { fromKML, fromTCX } from "$lib/util/gpx_util";
|
import { fromKML, fromTCX, gpx2trail } from "$lib/util/gpx_util";
|
||||||
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
||||||
import "$lib/vendor/leaflet-elevation/src/index.css";
|
|
||||||
import { createForm } from "$lib/vendor/svelte-form-lib";
|
import { createForm } from "$lib/vendor/svelte-form-lib";
|
||||||
import cryptoRandomString from "crypto-random-string";
|
import cryptoRandomString from "crypto-random-string";
|
||||||
import type { GPX, Icon, LeafletEvent, Map } from "leaflet";
|
import type { Map } from "leaflet";
|
||||||
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
|
|
||||||
import "leaflet/dist/leaflet.css";
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import { array, number, object, string } from "yup";
|
import { array, number, object, string } from "yup";
|
||||||
@@ -54,8 +51,6 @@
|
|||||||
let L: any;
|
let L: any;
|
||||||
let map: Map;
|
let map: Map;
|
||||||
|
|
||||||
let gpxLayer: GPX;
|
|
||||||
|
|
||||||
let openWaypointModal: () => void;
|
let openWaypointModal: () => void;
|
||||||
let openSummitLogModal: () => void;
|
let openSummitLogModal: () => void;
|
||||||
let openListSelectModal: () => void;
|
let openListSelectModal: () => void;
|
||||||
@@ -157,129 +152,9 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
L = (await import("leaflet")).default;
|
L = (await import("leaflet")).default;
|
||||||
await import("leaflet-gpx");
|
|
||||||
await import("leaflet.awesome-markers");
|
await import("leaflet.awesome-markers");
|
||||||
|
|
||||||
map = L.map("map").setView([0, 0], 2);
|
|
||||||
map.attributionControl.setPrefix(false);
|
|
||||||
|
|
||||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
|
||||||
attribution: "© OpenStreetMap contributors",
|
|
||||||
}).addTo(map);
|
|
||||||
|
|
||||||
if (
|
|
||||||
data.trail.expand.gpx_data &&
|
|
||||||
data.trail.expand.gpx_data.length > 0
|
|
||||||
) {
|
|
||||||
addGPXLayer(data.trail.expand.gpx_data, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.trail.expand.waypoints?.length > 0) {
|
|
||||||
for (const waypoint of data.trail.expand.waypoints) {
|
|
||||||
const marker = createMarkerFromWaypoint(
|
|
||||||
L,
|
|
||||||
waypoint,
|
|
||||||
(event) => {
|
|
||||||
var marker = event.target;
|
|
||||||
var position = marker.getLatLng();
|
|
||||||
const editableWaypoint = $form.expand.waypoints.find(
|
|
||||||
(w) => w.id == waypoint.id,
|
|
||||||
);
|
|
||||||
editableWaypoint!.lat = position.lat;
|
|
||||||
editableWaypoint!.lon = position.lng;
|
|
||||||
$form.expand.waypoints = [...$form.expand.waypoints];
|
|
||||||
},
|
|
||||||
);
|
|
||||||
marker.addTo(map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function addGPXLayer(gpx: string, addWaypoints: boolean = true) {
|
|
||||||
return new Promise<void>(function (resolve, reject) {
|
|
||||||
gpxLayer?.remove();
|
|
||||||
gpxLayer = new L.GPX(gpx, {
|
|
||||||
async: true,
|
|
||||||
polyline_options: {
|
|
||||||
className: "lightblue-theme elevation-polyline",
|
|
||||||
opacity: 0.75,
|
|
||||||
weight: 5,
|
|
||||||
},
|
|
||||||
gpx_options: {
|
|
||||||
parseElements: [
|
|
||||||
"track",
|
|
||||||
"route",
|
|
||||||
...(addWaypoints ? ["waypoint"] : []),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
marker_options: {
|
|
||||||
wptIcons: {
|
|
||||||
"": L.AwesomeMarkers.icon({
|
|
||||||
icon: "circle",
|
|
||||||
prefix: "fa",
|
|
||||||
markerColor: "cadetblue",
|
|
||||||
iconColor: "white",
|
|
||||||
}) as Icon,
|
|
||||||
},
|
|
||||||
startIcon: L.AwesomeMarkers.icon({
|
|
||||||
icon: "circle-half-stroke",
|
|
||||||
prefix: "fa",
|
|
||||||
markerColor: "cadetblue",
|
|
||||||
iconColor: "white",
|
|
||||||
}) as Icon,
|
|
||||||
endIcon: L.AwesomeMarkers.icon({
|
|
||||||
icon: "flag-checkered",
|
|
||||||
prefix: "fa",
|
|
||||||
markerColor: "cadetblue",
|
|
||||||
iconColor: "white",
|
|
||||||
}) as Icon,
|
|
||||||
startIconUrl: "",
|
|
||||||
endIconUrl: "",
|
|
||||||
shadowUrl: "",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.on("addpoint", function (e: any) {
|
|
||||||
if (e.point_type === "start") {
|
|
||||||
e.point.setZIndexOffset(1000);
|
|
||||||
$form.lat = e.point._latlng.lat;
|
|
||||||
$form.lon = e.point._latlng.lng;
|
|
||||||
} else if (e.point_type === "waypoint") {
|
|
||||||
const waypoint = new Waypoint(
|
|
||||||
e.point._latlng.lat,
|
|
||||||
e.point._latlng.lng,
|
|
||||||
{ name: e.point.options.title, marker: e.point },
|
|
||||||
);
|
|
||||||
$form.expand.waypoints.push(waypoint);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on("loaded", function (e: LeafletEvent) {
|
|
||||||
map.fitBounds(e.target.getBounds());
|
|
||||||
if (!$form.id) {
|
|
||||||
$form.name = e.target._info.name;
|
|
||||||
}
|
|
||||||
$form.distance = Math.round(e.target.get_distance());
|
|
||||||
$form.elevation_gain = Math.round(
|
|
||||||
e.target.get_elevation_gain(),
|
|
||||||
);
|
|
||||||
$form.duration = Math.round(
|
|
||||||
e.target.get_total_time() / 1000 / 60,
|
|
||||||
);
|
|
||||||
if ($form.duration && !$form.date) {
|
|
||||||
$form.date = e.target
|
|
||||||
.get_start_time()
|
|
||||||
.toISOString()
|
|
||||||
.substring(0, 10);
|
|
||||||
} else if (!$form.date) {
|
|
||||||
$form.date = new Date().toISOString().substring(0, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.on("error", reject)
|
|
||||||
.addTo(map);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function openFileBrowser() {
|
function openFileBrowser() {
|
||||||
document.getElementById("fileInput")!.click();
|
document.getElementById("fileInput")!.click();
|
||||||
}
|
}
|
||||||
@@ -314,8 +189,11 @@
|
|||||||
gpxFile = selectedFile;
|
gpxFile = selectedFile;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await addGPXLayer(gpxData);
|
$form = await gpx2trail(gpxData);
|
||||||
|
$form.expand.gpx_data = gpxData;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
|
||||||
show_toast({
|
show_toast({
|
||||||
icon: "close",
|
icon: "close",
|
||||||
type: "error",
|
type: "error",
|
||||||
@@ -671,7 +549,7 @@
|
|||||||
{loading}>{$_("save-trail")}</Button
|
{loading}>{$_("save-trail")}</Button
|
||||||
>
|
>
|
||||||
</form>
|
</form>
|
||||||
<div class="rounded-xl" id="map"></div>
|
<MapWithElevation trail={$form} bind:map></MapWithElevation>
|
||||||
</main>
|
</main>
|
||||||
<WaypointModal
|
<WaypointModal
|
||||||
bind:openModal={openWaypointModal}
|
bind:openModal={openWaypointModal}
|
||||||
|
|||||||
Reference in New Issue
Block a user