more route drawing
This commit is contained in:
@@ -21,9 +21,14 @@
|
|||||||
let L: any;
|
let L: any;
|
||||||
let controlElevation: any;
|
let controlElevation: any;
|
||||||
|
|
||||||
$: if (trail?.expand.gpx_data !== undefined && controlElevation) {
|
$: gpxData = trail?.expand.gpx_data;
|
||||||
|
$: if (gpxData && controlElevation) {
|
||||||
controlElevation.clear();
|
controlElevation.clear();
|
||||||
controlElevation.load(trail.expand.gpx_data);
|
controlElevation.load(gpxData);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if(options) {
|
||||||
|
controlElevation?.updateOptions(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
@@ -38,7 +43,7 @@
|
|||||||
|
|
||||||
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],
|
||||||
16,
|
3,
|
||||||
);
|
);
|
||||||
map!.attributionControl.setPrefix(false);
|
map!.attributionControl.setPrefix(false);
|
||||||
|
|
||||||
@@ -77,9 +82,9 @@
|
|||||||
ruler: false,
|
ruler: false,
|
||||||
legend: true,
|
legend: true,
|
||||||
// Toggle "leaflet-almostover" integration
|
// Toggle "leaflet-almostover" integration
|
||||||
almostOver: true,
|
almostOver: false,
|
||||||
// Toggle "leaflet-distance-markers" integration
|
// Toggle "leaflet-distance-markers" integration
|
||||||
distanceMarkers: false,
|
distanceMarkers: { lazy: false, distance: false, direction: true, offset: 500 },
|
||||||
// Toggle "leaflet-edgescale" integration
|
// Toggle "leaflet-edgescale" integration
|
||||||
edgeScale: false,
|
edgeScale: false,
|
||||||
// Toggle "leaflet-hotline" integration
|
// Toggle "leaflet-hotline" integration
|
||||||
@@ -91,11 +96,14 @@
|
|||||||
wptLabels: false,
|
wptLabels: false,
|
||||||
preferCanvas: true,
|
preferCanvas: true,
|
||||||
trkStart: {
|
trkStart: {
|
||||||
|
interactive: false,
|
||||||
|
className: "hihi",
|
||||||
icon: L.AwesomeMarkers.icon({
|
icon: L.AwesomeMarkers.icon({
|
||||||
icon: "circle-half-stroke",
|
icon: "circle-half-stroke",
|
||||||
prefix: "fa",
|
prefix: "fa",
|
||||||
markerColor: "cadetblue",
|
markerColor: "cadetblue",
|
||||||
iconColor: "white",
|
iconColor: "white",
|
||||||
|
className: "awesome-marker pointer-events-none"
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
trkEnd: {
|
trkEnd: {
|
||||||
@@ -104,9 +112,11 @@
|
|||||||
prefix: "fa",
|
prefix: "fa",
|
||||||
markerColor: "cadetblue",
|
markerColor: "cadetblue",
|
||||||
iconColor: "white",
|
iconColor: "white",
|
||||||
|
className: "awesome-marker pointer-events-none"
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
graticule: false,
|
graticule: false,
|
||||||
|
drawing: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const elevation_options = Object.assign(
|
const elevation_options = Object.assign(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { LatLng, Marker } from "leaflet";
|
||||||
|
|
||||||
interface ValhallaRouteResponse {
|
interface ValhallaRouteResponse {
|
||||||
trip: {
|
trip: {
|
||||||
legs: {
|
legs: {
|
||||||
@@ -10,4 +12,11 @@ interface ValhallaHeightResponse {
|
|||||||
height: number[];
|
height: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export { type ValhallaRouteResponse, type ValhallaHeightResponse }
|
interface ValhallaAnchor {
|
||||||
|
id: string,
|
||||||
|
lat: number,
|
||||||
|
lon: number,
|
||||||
|
marker?: Marker
|
||||||
|
}
|
||||||
|
|
||||||
|
export { type ValhallaRouteResponse, type ValhallaHeightResponse, type ValhallaAnchor }
|
||||||
@@ -2,16 +2,21 @@ import GPX from "$lib/models/gpx/gpx";
|
|||||||
import type Track from "$lib/models/gpx/track";
|
import type Track from "$lib/models/gpx/track";
|
||||||
import TrackSegment from "$lib/models/gpx/track-segment";
|
import TrackSegment from "$lib/models/gpx/track-segment";
|
||||||
import Waypoint from "$lib/models/gpx/waypoint";
|
import Waypoint from "$lib/models/gpx/waypoint";
|
||||||
import { type ValhallaHeightResponse, type ValhallaRouteResponse } from "$lib/models/valhalla";
|
import { type ValhallaAnchor, type ValhallaHeightResponse, type ValhallaRouteResponse } from "$lib/models/valhalla";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
|
|
||||||
const emtpyTrack: Track = { trkseg: [] }
|
const emtpyTrack: Track = { trkseg: [] }
|
||||||
export let route: GPX = new GPX({ trk: [emtpyTrack] });
|
export let route: GPX = new GPX({ trk: [emtpyTrack] });
|
||||||
|
export let anchors: ValhallaAnchor[] = [];
|
||||||
|
|
||||||
export function clearRoute() {
|
export function clearRoute() {
|
||||||
route = new GPX({ trk: [emtpyTrack] });
|
route = new GPX({ trk: [emtpyTrack] });
|
||||||
|
anchors = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setRoute(newRoute: GPX) {
|
||||||
|
route = newRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function calculateRouteBetween(startLat: number, startLon: number, endLat: number, endLon: number) {
|
export async function calculateRouteBetween(startLat: number, startLon: number, endLat: number, endLon: number) {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export async function gpx2trail(gpxString: string) {
|
|||||||
trail.elevation_gain = totals.elevationGain;
|
trail.elevation_gain = totals.elevationGain;
|
||||||
trail.distance = totals.distance
|
trail.distance = totals.distance
|
||||||
|
|
||||||
return trail
|
return {gpx: gpx, trail: trail}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fromKML(kmlData: string) {
|
export function fromKML(kmlData: string) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Waypoint } from "$lib/models/waypoint";
|
import type { Waypoint } from "$lib/models/waypoint";
|
||||||
import type { Icon, LeafletEvent, Map, Marker } from "leaflet";
|
import type { Icon, LatLng, LeafletEvent, Map, Marker } from "leaflet";
|
||||||
|
|
||||||
export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?: (event: LeafletEvent) => void): Marker {
|
export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?: (event: LeafletEvent) => void): Marker {
|
||||||
const fontAwesomeIcon = L.AwesomeMarkers.icon({
|
const fontAwesomeIcon = L.AwesomeMarkers.icon({
|
||||||
@@ -28,10 +28,35 @@ export function createMarkerFromWaypoint(L: any, waypoint: Waypoint, onDragEnd?:
|
|||||||
if (onDragEnd) {
|
if (onDragEnd) {
|
||||||
marker.on("dragend", onDragEnd);
|
marker.on("dragend", onDragEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
return marker;
|
return marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createAnchorMarker(L: any, lat: number, lon: number, index: number, onDeleteClick: () => void, onDragEnd: (event: LeafletEvent) => void): Marker {
|
||||||
|
|
||||||
|
const anchorIconElement = document.createElement("span")
|
||||||
|
anchorIconElement.textContent = "" + index
|
||||||
|
const anchorIcon = L.divIcon({
|
||||||
|
html: anchorIconElement,
|
||||||
|
iconSize: [24, 24],
|
||||||
|
className: "leaflet-anchor"
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteButton = document.createElement("button");
|
||||||
|
deleteButton.className = "btn-icon fa fa-trash text-red-500";
|
||||||
|
deleteButton.addEventListener("click", onDeleteClick)
|
||||||
|
const marker = L.marker([lat, lon], {
|
||||||
|
icon: anchorIcon,
|
||||||
|
draggable: true,
|
||||||
|
})
|
||||||
|
.bindPopup(
|
||||||
|
deleteButton
|
||||||
|
);
|
||||||
|
marker.on("dragend", onDragEnd);
|
||||||
|
|
||||||
|
return marker
|
||||||
|
}
|
||||||
|
|
||||||
export function calculatePixelPerMeter(map: Map, meters: number) {
|
export function calculatePixelPerMeter(map: Map, meters: number) {
|
||||||
const y = map.getSize().y;
|
const y = map.getSize().y;
|
||||||
const x = map.getSize().x;
|
const x = map.getSize().x;
|
||||||
@@ -65,7 +90,7 @@ export function calculateScaleFactor(map: Map) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const screenMetersPer100Pixels = _pxTOmm()(100) / 1000;
|
const screenMetersPer100Pixels = _pxTOmm()(100) / 1000;
|
||||||
|
|
||||||
const scaleFactor = realWorlMetersPer100Pixels / screenMetersPer100Pixels
|
const scaleFactor = realWorlMetersPer100Pixels / screenMetersPer100Pixels
|
||||||
|
|
||||||
return scaleFactor
|
return scaleFactor
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as _ from './utils';
|
|
||||||
import { Options } from './options';
|
import { Options } from './options';
|
||||||
|
import * as _ from './utils';
|
||||||
|
|
||||||
// "leaflet-i18n" fallback
|
// "leaflet-i18n" fallback
|
||||||
if (!L._ || !L.i18n) {
|
if (!L._ || !L.i18n) {
|
||||||
@@ -26,6 +26,15 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|||||||
__modulesFolder: '/vendor/leaflet-elevation/src/handlers/',
|
__modulesFolder: '/vendor/leaflet-elevation/src/handlers/',
|
||||||
__btnIcon: '/vendor/leaflet-elevation/images/elevation.svg',
|
__btnIcon: '/vendor/leaflet-elevation/images/elevation.svg',
|
||||||
|
|
||||||
|
updateOptions(options) {
|
||||||
|
Object.assign(this.options, options)
|
||||||
|
if (!this.options.showStartEnd) {
|
||||||
|
this._circleMarkers.remove();
|
||||||
|
} else if (this._data.length) {
|
||||||
|
this._circleMarkers.addTo(this._map);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add data to the diagram either from GPX or GeoJSON and update the axis domain and data
|
* Add data to the diagram either from GPX or GeoJSON and update the axis domain and data
|
||||||
*/
|
*/
|
||||||
@@ -171,7 +180,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|||||||
* Initialize chart control "options" and "container".
|
* Initialize chart control "options" and "container".
|
||||||
*/
|
*/
|
||||||
initialize(opts) {
|
initialize(opts) {
|
||||||
|
|
||||||
// opts = L.setOptions(this, opts);
|
// opts = L.setOptions(this, opts);
|
||||||
|
|
||||||
// Fixes: https://github.com/Raruto/leaflet-elevation/pull/240
|
// Fixes: https://github.com/Raruto/leaflet-elevation/pull/240
|
||||||
@@ -198,7 +206,6 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|||||||
if (opts.trkStart) this._start.addTo(this._circleMarkers);
|
if (opts.trkStart) this._start.addTo(this._circleMarkers);
|
||||||
if (opts.trkEnd) this._end.addTo(this._circleMarkers);
|
if (opts.trkEnd) this._end.addTo(this._circleMarkers);
|
||||||
|
|
||||||
|
|
||||||
this._markedSegments.setStyle(opts.polylineSegments);
|
this._markedSegments.setStyle(opts.polylineSegments);
|
||||||
|
|
||||||
// Leaflet canvas renderer colors
|
// Leaflet canvas renderer colors
|
||||||
@@ -488,7 +495,9 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|||||||
]).then(() => {
|
]).then(() => {
|
||||||
if (this.options.polyline) {
|
if (this.options.polyline) {
|
||||||
this._layers.addLayer(layer.addTo(map)); // hotfix for: https://github.com/Raruto/leaflet-elevation/issues/233
|
this._layers.addLayer(layer.addTo(map)); // hotfix for: https://github.com/Raruto/leaflet-elevation/issues/233
|
||||||
this._circleMarkers.addTo(map);
|
if (this.options.showStartEnd) {
|
||||||
|
this._circleMarkers.addTo(map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (this.options.autofitBounds) {
|
if (this.options.autofitBounds) {
|
||||||
this.fitBounds(layer.getBounds());
|
this.fitBounds(layer.getBounds());
|
||||||
@@ -1254,7 +1263,7 @@ export const Elevation = L.Control.Elevation = L.Control.extend({
|
|||||||
* Update the position/height indicator marker drawn onto the map
|
* Update the position/height indicator marker drawn onto the map
|
||||||
*/
|
*/
|
||||||
_updateMarker(item) {
|
_updateMarker(item) {
|
||||||
if (this._marker) {
|
if (this._marker && this.options.mapTooltip) {
|
||||||
this._marker.update({
|
this._marker.update({
|
||||||
map: this._map,
|
map: this._map,
|
||||||
item: item,
|
item: item,
|
||||||
|
|||||||
@@ -347,4 +347,15 @@
|
|||||||
.leaflet-popup-content {
|
.leaflet-popup-content {
|
||||||
width: max-content !important;
|
width: max-content !important;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-anchor {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 2px solid black;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
-moz-border-radius: 50%;
|
||||||
|
-webkit-border-radius: 50%;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -50,6 +50,8 @@ export var Options = {
|
|||||||
time: true,
|
time: true,
|
||||||
timeFactor: 3600,
|
timeFactor: 3600,
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
|
showStartEnd: true,
|
||||||
|
mapTooltip: true,
|
||||||
trkStart: { className: 'start-marker', radius: 6, weight: 2, color: '#fff', fillColor: '#00d800', fillOpacity: 1, interactive: false },
|
trkStart: { className: 'start-marker', radius: 6, weight: 2, color: '#fff', fillColor: '#00d800', fillOpacity: 1, interactive: false },
|
||||||
trkEnd: { className: 'end-marker', radius: 6, weight: 2, color: '#fff', fillColor: '#ff0606', fillOpacity: 1, interactive: false },
|
trkEnd: { className: 'end-marker', radius: 6, weight: 2, color: '#fff', fillColor: '#ff0606', fillOpacity: 1, interactive: false },
|
||||||
waypoints: true,
|
waypoints: true,
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import { ClientResponseError } from "pocketbase";
|
|||||||
export async function PUT(event: RequestEvent) {
|
export async function PUT(event: RequestEvent) {
|
||||||
try {
|
try {
|
||||||
const data = await event.request.blob();
|
const data = await event.request.blob();
|
||||||
const gpx = await data.text();
|
const gpxString = await data.text();
|
||||||
if (!gpx.length) {
|
if (!gpxString.length) {
|
||||||
throw new ClientResponseError({ status: 400, response: { message: "Empty file" } })
|
throw new ClientResponseError({ status: 400, response: { message: "Empty file" } })
|
||||||
}
|
}
|
||||||
let trail: Trail;
|
let trail: Trail;
|
||||||
try {
|
try {
|
||||||
trail = await gpx2trail(gpx);
|
trail = (await gpx2trail(gpxString)).trail;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new ClientResponseError({ status: 400, response: { message: "Invalid GPX file" } })
|
throw new ClientResponseError({ status: 400, response: { message: "Invalid GPX file" } })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,11 @@
|
|||||||
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";
|
||||||
|
import GPX from "$lib/models/gpx/gpx";
|
||||||
import type { List } from "$lib/models/list";
|
import type { List } from "$lib/models/list";
|
||||||
import { SummitLog } from "$lib/models/summit_log";
|
import { SummitLog } from "$lib/models/summit_log";
|
||||||
import { Trail } from "$lib/models/trail";
|
import { Trail } from "$lib/models/trail";
|
||||||
|
import type { ValhallaAnchor } from "$lib/models/valhalla";
|
||||||
import { Waypoint } from "$lib/models/waypoint";
|
import { Waypoint } from "$lib/models/waypoint";
|
||||||
import { categories } from "$lib/stores/category_store";
|
import { categories } from "$lib/stores/category_store";
|
||||||
import {
|
import {
|
||||||
@@ -32,12 +34,14 @@
|
|||||||
trails_update,
|
trails_update,
|
||||||
} from "$lib/stores/trail_store";
|
} from "$lib/stores/trail_store";
|
||||||
import {
|
import {
|
||||||
|
anchors,
|
||||||
appendToRoute,
|
appendToRoute,
|
||||||
calculateRouteBetween,
|
calculateRouteBetween,
|
||||||
clearRoute,
|
clearRoute,
|
||||||
deleteFromRoute,
|
deleteFromRoute,
|
||||||
editRoute,
|
editRoute,
|
||||||
route,
|
route,
|
||||||
|
setRoute,
|
||||||
} from "$lib/stores/valhalla_store";
|
} from "$lib/stores/valhalla_store";
|
||||||
import { waypoint } from "$lib/stores/waypoint_store";
|
import { waypoint } from "$lib/stores/waypoint_store";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
@@ -47,10 +51,13 @@
|
|||||||
formatTimeHHMM,
|
formatTimeHHMM,
|
||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import { fromKML, fromTCX, gpx2trail } from "$lib/util/gpx_util";
|
import { fromKML, fromTCX, gpx2trail } from "$lib/util/gpx_util";
|
||||||
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
import {
|
||||||
|
createAnchorMarker,
|
||||||
|
createMarkerFromWaypoint,
|
||||||
|
} from "$lib/util/leaflet_util";
|
||||||
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 { LeafletMouseEvent, Map } from "leaflet";
|
import type { DivIcon, LatLng, LeafletMouseEvent, Map } from "leaflet";
|
||||||
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";
|
||||||
@@ -73,6 +80,7 @@
|
|||||||
let gpxFile: File | Blob | null = null;
|
let gpxFile: File | Blob | null = null;
|
||||||
|
|
||||||
let drawingActive = false;
|
let drawingActive = false;
|
||||||
|
let overwriteGPX = false;
|
||||||
|
|
||||||
const trailSchema = object<Trail>({
|
const trailSchema = object<Trail>({
|
||||||
id: string().optional(),
|
id: string().optional(),
|
||||||
@@ -126,6 +134,13 @@
|
|||||||
submittedTrail.photos = submittedTrail.photos.filter(
|
submittedTrail.photos = submittedTrail.photos.filter(
|
||||||
(p) => !p.startsWith("data:image/svg+xml;base64"),
|
(p) => !p.startsWith("data:image/svg+xml;base64"),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($form.expand.gpx_data && overwriteGPX) {
|
||||||
|
gpxFile = new Blob([$form.expand.gpx_data], {
|
||||||
|
type: "text/xml",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!submittedTrail.id) {
|
if (!submittedTrail.id) {
|
||||||
const createdTrail = await trails_create(
|
const createdTrail = await trails_create(
|
||||||
submittedTrail,
|
submittedTrail,
|
||||||
@@ -165,8 +180,18 @@
|
|||||||
L = (await import("leaflet")).default;
|
L = (await import("leaflet")).default;
|
||||||
await import("leaflet.awesome-markers");
|
await import("leaflet.awesome-markers");
|
||||||
|
|
||||||
$form.lat = 47.74604748696788;
|
// $form.lat = 47.74604748696788;
|
||||||
$form.lon = 11.588988304138185;
|
// $form.lon = 11.588988304138185;
|
||||||
|
clearAnchorMarker();
|
||||||
|
clearRoute();
|
||||||
|
|
||||||
|
if ($form.expand.gpx_data) {
|
||||||
|
const gpx = await GPX.parse($form.expand.gpx_data);
|
||||||
|
if (!(gpx instanceof Error)) {
|
||||||
|
setRoute(gpx);
|
||||||
|
initRouteAnchors(gpx);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function openFileBrowser() {
|
function openFileBrowser() {
|
||||||
@@ -183,7 +208,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearWaypoints();
|
clearWaypoints();
|
||||||
|
clearAnchorMarker();
|
||||||
clearRoute();
|
clearRoute();
|
||||||
|
drawingActive = false;
|
||||||
|
overwriteGPX = false;
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
|
|
||||||
reader.readAsText(selectedFile);
|
reader.readAsText(selectedFile);
|
||||||
@@ -202,9 +230,13 @@
|
|||||||
gpxFile = selectedFile;
|
gpxFile = selectedFile;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$form = await gpx2trail(gpxData);
|
const parseResult = await gpx2trail(gpxData);
|
||||||
|
$form = parseResult.trail;
|
||||||
$form.expand.gpx_data = gpxData;
|
$form.expand.gpx_data = gpxData;
|
||||||
|
|
||||||
|
setRoute(parseResult.gpx);
|
||||||
|
initRouteAnchors(parseResult.gpx);
|
||||||
|
|
||||||
for (const waypoint of $form.expand.waypoints) {
|
for (const waypoint of $form.expand.waypoints) {
|
||||||
saveWaypoint(waypoint);
|
saveWaypoint(waypoint);
|
||||||
}
|
}
|
||||||
@@ -245,6 +277,32 @@
|
|||||||
$form.waypoints = [];
|
$form.waypoints = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearAnchorMarker() {
|
||||||
|
for (const anchor of anchors) {
|
||||||
|
anchor.marker?.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initRouteAnchors(gpx: GPX) {
|
||||||
|
const segments = gpx.trk?.at(0)?.trkseg ?? [];
|
||||||
|
|
||||||
|
for (let i = 0; i < segments.length; i++) {
|
||||||
|
const segment = segments[i];
|
||||||
|
const points = segment.trkpt ?? [];
|
||||||
|
|
||||||
|
if (points.length > 0) {
|
||||||
|
addAnchor(points[0].$.lat!, points[0].$.lon!, false);
|
||||||
|
}
|
||||||
|
if (i == segments.length - 1) {
|
||||||
|
addAnchor(
|
||||||
|
points[points.length - 1].$.lat!,
|
||||||
|
points[points.length - 1].$.lon!,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function openMarkerPopup(waypoint: Waypoint) {
|
function openMarkerPopup(waypoint: Waypoint) {
|
||||||
waypoint.marker?.openPopup();
|
waypoint.marker?.openPopup();
|
||||||
}
|
}
|
||||||
@@ -273,28 +331,6 @@
|
|||||||
$form.expand.waypoints.splice(index, 1);
|
$form.expand.waypoints.splice(index, 1);
|
||||||
$form.waypoints.splice(index, 1);
|
$form.waypoints.splice(index, 1);
|
||||||
$form.expand.waypoints = $form.expand.waypoints;
|
$form.expand.waypoints = $form.expand.waypoints;
|
||||||
|
|
||||||
if (drawingActive) {
|
|
||||||
if (index == 0) {
|
|
||||||
deleteFromRoute(index);
|
|
||||||
updateTrailWithRouteData();
|
|
||||||
const nextWaypoint = $form.expand.waypoints[index];
|
|
||||||
nextWaypoint.icon = "circle-half-stroke";
|
|
||||||
saveWaypoint(nextWaypoint);
|
|
||||||
} else if (index == $form.expand.waypoints.length) {
|
|
||||||
deleteFromRoute(index - 1);
|
|
||||||
updateTrailWithRouteData();
|
|
||||||
|
|
||||||
if ($form.expand.waypoints.length > 1) {
|
|
||||||
const previousWaypoint = $form.expand.waypoints[index - 1];
|
|
||||||
previousWaypoint.icon = "flag-checkered";
|
|
||||||
saveWaypoint(previousWaypoint);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
deleteFromRoute(index - 1);
|
|
||||||
recalculateRoute(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveWaypoint(savedWaypoint: Waypoint) {
|
function saveWaypoint(savedWaypoint: Waypoint) {
|
||||||
@@ -320,10 +356,6 @@
|
|||||||
editableWaypoint!.lat = position.lat;
|
editableWaypoint!.lat = position.lat;
|
||||||
editableWaypoint!.lon = position.lng;
|
editableWaypoint!.lon = position.lng;
|
||||||
$form.expand.waypoints = [...$form.expand.waypoints];
|
$form.expand.waypoints = [...$form.expand.waypoints];
|
||||||
|
|
||||||
if (drawingActive) {
|
|
||||||
recalculateRoute(editableWaypointIndex);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
marker.addTo(map);
|
marker.addTo(map);
|
||||||
@@ -386,42 +418,37 @@
|
|||||||
function startDrawing() {
|
function startDrawing() {
|
||||||
drawingActive = true;
|
drawingActive = true;
|
||||||
if (!route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.length) {
|
if (!route.trk?.at(0)?.trkseg?.at(0)?.trkpt?.length) {
|
||||||
$form.expand.gpx_data = "";
|
}
|
||||||
clearWaypoints();
|
for (const anchor of anchors) {
|
||||||
|
anchor.marker?.addTo(map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopDrawing() {
|
function stopDrawing() {
|
||||||
drawingActive = false;
|
drawingActive = false;
|
||||||
|
for (const anchor of anchors) {
|
||||||
|
anchor.marker?.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMapClick(e: LeafletMouseEvent) {
|
async function handleMapClick(e: LeafletMouseEvent) {
|
||||||
if (!drawingActive) {
|
if (!drawingActive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const waypointCount = $form.expand.waypoints.length;
|
const anchorCount = anchors.length;
|
||||||
const wp = new Waypoint(e.latlng.lat, e.latlng.lng, {
|
if (anchorCount == 0) {
|
||||||
name: `${$_("waypoints", { values: { n: 1 } })} ${waypointCount + 1}`,
|
addAnchor(e.latlng.lat, e.latlng.lng);
|
||||||
icon: waypointCount ? "flag-checkered" : "circle-half-stroke",
|
|
||||||
});
|
|
||||||
if (waypointCount == 0) {
|
|
||||||
saveWaypoint(wp);
|
|
||||||
} else {
|
} else {
|
||||||
const previousWaypoint = $form.expand.waypoints[waypointCount - 1];
|
const previousAnchor = anchors[anchorCount - 1];
|
||||||
try {
|
try {
|
||||||
const routeWaypoints = await calculateRouteBetween(
|
const routeWaypoints = await calculateRouteBetween(
|
||||||
previousWaypoint.lat,
|
previousAnchor.lat,
|
||||||
previousWaypoint.lon,
|
previousAnchor.lon,
|
||||||
e.latlng.lat,
|
e.latlng.lat,
|
||||||
e.latlng.lng,
|
e.latlng.lng,
|
||||||
);
|
);
|
||||||
appendToRoute(routeWaypoints);
|
appendToRoute(routeWaypoints);
|
||||||
saveWaypoint(wp);
|
addAnchor(e.latlng.lat, e.latlng.lng);
|
||||||
|
|
||||||
if (waypointCount > 1) {
|
|
||||||
previousWaypoint.icon = "circle";
|
|
||||||
saveWaypoint(previousWaypoint);
|
|
||||||
}
|
|
||||||
updateTrailWithRouteData();
|
updateTrailWithRouteData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@@ -434,39 +461,104 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function recalculateRoute(waypointIndex: number) {
|
function addAnchor(lat: number, lon: number, addtoMap: boolean = true) {
|
||||||
const waypoint = $form.expand.waypoints[waypointIndex];
|
const anchor: ValhallaAnchor = {
|
||||||
let nextWaypoints;
|
id: cryptoRandomString({ length: 15 }),
|
||||||
let previousWaypoints;
|
lat: lat,
|
||||||
if (waypointIndex < $form.expand.waypoints.length - 1) {
|
lon: lon,
|
||||||
const nextWaypoint = $form.expand.waypoints[waypointIndex + 1];
|
};
|
||||||
|
const marker = createAnchorMarker(
|
||||||
|
L,
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
|
anchors.length + 1,
|
||||||
|
() => {
|
||||||
|
removeAnchor(anchors.findIndex((a) => a.id == anchor.id));
|
||||||
|
},
|
||||||
|
(_) => {
|
||||||
|
if (!drawingActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const position = marker.getLatLng();
|
||||||
|
anchor.lat = position.lat;
|
||||||
|
anchor.lon = position.lng;
|
||||||
|
recalculateRoute(anchors.findIndex((a) => a.id == anchor.id));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (addtoMap) {
|
||||||
|
marker.addTo(map);
|
||||||
|
}
|
||||||
|
anchor.marker = marker;
|
||||||
|
anchors.push(anchor);
|
||||||
|
}
|
||||||
|
|
||||||
nextWaypoints = await calculateRouteBetween(
|
function removeAnchor(anchorIndex: number) {
|
||||||
waypoint.lat,
|
if (!drawingActive) {
|
||||||
waypoint.lon,
|
return;
|
||||||
nextWaypoint.lat,
|
}
|
||||||
nextWaypoint.lon,
|
anchors[anchorIndex]?.marker?.remove();
|
||||||
|
anchors.splice(anchorIndex, 1);
|
||||||
|
for (let i = anchorIndex; i < anchors.length; i++) {
|
||||||
|
const anchor = anchors[i];
|
||||||
|
const markerIcon = anchor.marker?.getIcon() as DivIcon | undefined;
|
||||||
|
if (markerIcon) {
|
||||||
|
const markerText =
|
||||||
|
(markerIcon.options.html as HTMLSpanElement).textContent ??
|
||||||
|
"0";
|
||||||
|
const markerIndex = parseInt(markerText);
|
||||||
|
(markerIcon.options.html as HTMLSpanElement).textContent =
|
||||||
|
markerIndex - 1 + "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (anchorIndex == 0) {
|
||||||
|
deleteFromRoute(anchorIndex);
|
||||||
|
updateTrailWithRouteData();
|
||||||
|
} else if (anchorIndex == anchors.length) {
|
||||||
|
deleteFromRoute(anchorIndex - 1);
|
||||||
|
updateTrailWithRouteData();
|
||||||
|
} else {
|
||||||
|
deleteFromRoute(anchorIndex - 1);
|
||||||
|
recalculateRoute(anchorIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function recalculateRoute(anchorIndex: number) {
|
||||||
|
const anchor = anchors[anchorIndex];
|
||||||
|
if (!anchor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let nextRouteSegment;
|
||||||
|
let previousRouteSegment;
|
||||||
|
if (anchorIndex < anchors.length - 1) {
|
||||||
|
const nextAnchor = anchors[anchorIndex + 1];
|
||||||
|
|
||||||
|
nextRouteSegment = await calculateRouteBetween(
|
||||||
|
anchor.lat,
|
||||||
|
anchor.lon,
|
||||||
|
nextAnchor.lat,
|
||||||
|
nextAnchor.lon,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (waypointIndex > 0) {
|
if (anchorIndex > 0) {
|
||||||
const previousWaypoint = $form.expand.waypoints[waypointIndex - 1];
|
const previousAnchor = anchors[anchorIndex - 1];
|
||||||
previousWaypoints = await calculateRouteBetween(
|
previousRouteSegment = await calculateRouteBetween(
|
||||||
previousWaypoint.lat,
|
previousAnchor.lat,
|
||||||
previousWaypoint.lon,
|
previousAnchor.lon,
|
||||||
waypoint.lat,
|
anchor.lat,
|
||||||
waypoint.lon,
|
anchor.lon,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (nextWaypoints) {
|
if (nextRouteSegment) {
|
||||||
editRoute(waypointIndex, nextWaypoints);
|
editRoute(anchorIndex, nextRouteSegment);
|
||||||
}
|
}
|
||||||
if (previousWaypoints) {
|
if (previousRouteSegment) {
|
||||||
editRoute(waypointIndex - 1, previousWaypoints);
|
editRoute(anchorIndex - 1, previousRouteSegment);
|
||||||
}
|
}
|
||||||
updateTrailWithRouteData();
|
updateTrailWithRouteData();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTrailWithRouteData() {
|
function updateTrailWithRouteData() {
|
||||||
|
overwriteGPX = true;
|
||||||
const totals = route.getTotals();
|
const totals = route.getTotals();
|
||||||
$form.distance = totals.distance;
|
$form.distance = totals.distance;
|
||||||
$form.duration = totals.duration;
|
$form.duration = totals.duration;
|
||||||
@@ -720,11 +812,11 @@
|
|||||||
trail={$form}
|
trail={$form}
|
||||||
crosshair={drawingActive}
|
crosshair={drawingActive}
|
||||||
options={{
|
options={{
|
||||||
hotline: true,
|
autofitBounds: !drawingActive,
|
||||||
autofitBounds: false,
|
mapTooltip: !drawingActive,
|
||||||
trkStart: false,
|
speed: !drawingActive,
|
||||||
trkEnd: false,
|
speedFactor: drawingActive ? 0 : 1,
|
||||||
speed: false,
|
showStartEnd: !drawingActive,
|
||||||
}}
|
}}
|
||||||
bind:map
|
bind:map
|
||||||
on:click={(e) => handleMapClick(e.detail)}
|
on:click={(e) => handleMapClick(e.detail)}
|
||||||
@@ -742,11 +834,11 @@
|
|||||||
></ListSelectModal>
|
></ListSelectModal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:global(#map) {
|
#map {
|
||||||
height: calc(400px);
|
height: calc(400px);
|
||||||
}
|
}
|
||||||
@media only screen and (min-width: 768px) {
|
@media only screen and (min-width: 768px) {
|
||||||
:global(#map),
|
#map,
|
||||||
form {
|
form {
|
||||||
height: calc(100vh - 124px);
|
height: calc(100vh - 124px);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user