adds route editing
This commit is contained in:
@@ -38,7 +38,7 @@
|
||||
|
||||
map = L.map("map", { preferCanvas: true }).setView(
|
||||
[trail?.lat ?? 0, trail?.lon ?? 0],
|
||||
3,
|
||||
16,
|
||||
);
|
||||
map!.attributionControl.setPrefix(false);
|
||||
|
||||
|
||||
12
web/src/lib/models/gpx/bounds.ts
Normal file
12
web/src/lib/models/gpx/bounds.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export default class Bounds {
|
||||
minlat: number;
|
||||
minlon: number;
|
||||
maxlat: number;
|
||||
maxlon: number;
|
||||
constructor(object: {minlat: number, minlon: number, maxlat: number, maxlon: number}) {
|
||||
this.minlat = object.minlat;
|
||||
this.minlon = object.minlon;
|
||||
this.maxlat = object.maxlat;
|
||||
this.maxlon = object.maxlon;
|
||||
}
|
||||
}
|
||||
10
web/src/lib/models/gpx/copyright.ts
Normal file
10
web/src/lib/models/gpx/copyright.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export default class Copyright {
|
||||
author: string;
|
||||
year: number;
|
||||
license: string;
|
||||
constructor(object: { author: string, year: number, license: string }) {
|
||||
this.author = object.author;
|
||||
this.year = object.year;
|
||||
this.license = object.license;
|
||||
}
|
||||
}
|
||||
101
web/src/lib/models/gpx/gpx.ts
Normal file
101
web/src/lib/models/gpx/gpx.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import * as xml2js from 'isomorphic-xml2js';
|
||||
import Metadata from './metadata';
|
||||
import Waypoint from './waypoint';
|
||||
import Route from './route';
|
||||
import Track from './track';
|
||||
import { removeEmpty, allDatesToISOString } from './utils';
|
||||
|
||||
const defaultAttributes = {
|
||||
version: '1.1',
|
||||
creator: 'wanderer',
|
||||
xmlns: 'http://www.topografix.com/GPX/1/1',
|
||||
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||
'xsi:schemaLocation':
|
||||
'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd'
|
||||
}
|
||||
|
||||
export default class GPX {
|
||||
$: {
|
||||
version: string;
|
||||
creator: string;
|
||||
xmlns: string;
|
||||
'xmlns:xsi': string;
|
||||
'xsi:schemaLocation': string;
|
||||
}
|
||||
extensions?: string;
|
||||
metadata?: Metadata;
|
||||
wpt?: Waypoint[];
|
||||
rte?: Route[];
|
||||
trk?: Track[];
|
||||
|
||||
constructor(object: {
|
||||
$?: {
|
||||
version: string,
|
||||
creator: string,
|
||||
xmlns: string,
|
||||
'xmlns:xsi': string,
|
||||
'xsi:schemaLocation': string,
|
||||
}
|
||||
extensions?: string,
|
||||
metadata?: Metadata,
|
||||
wpt?: Waypoint[] | Waypoint,
|
||||
rte?: Route[] | Route,
|
||||
trk?: Track[],
|
||||
}) {
|
||||
this.$ = Object.assign({}, defaultAttributes, object.$ || {});
|
||||
if (object.extensions) {
|
||||
this.extensions = object.extensions;
|
||||
}
|
||||
|
||||
if (object.metadata) {
|
||||
this.metadata = object.metadata;
|
||||
}
|
||||
if (object.wpt) {
|
||||
if (!Array.isArray(object.wpt)) {
|
||||
object.wpt = [object.wpt];
|
||||
}
|
||||
this.wpt = object.wpt.map(wpt => new Waypoint(wpt))
|
||||
}
|
||||
if (object.rte) {
|
||||
if (!Array.isArray(object.rte)) {
|
||||
object.rte = [object.rte];
|
||||
}
|
||||
this.rte = object.rte.map(rte => new Route(rte))
|
||||
}
|
||||
if (object.trk) {
|
||||
if (!Array.isArray(object.trk)) {
|
||||
object.trk = [object.trk];
|
||||
}
|
||||
this.trk = object.trk.map(trk => new Track(trk))
|
||||
}
|
||||
|
||||
removeEmpty(this);
|
||||
}
|
||||
|
||||
static parse(gpxString: string): Promise<GPX | Error> {
|
||||
return new Promise<GPX | Error>((resolve, reject) => xml2js.parseString(gpxString, {
|
||||
explicitArray: false
|
||||
}, (err, xml) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
const gpx = new GPX({
|
||||
$: xml.gpx.$,
|
||||
metadata: xml.gpx.metadata,
|
||||
wpt: xml.gpx.wpt,
|
||||
rte: xml.gpx.rte,
|
||||
trk: xml.gpx.trk
|
||||
});
|
||||
resolve(gpx)
|
||||
}));
|
||||
}
|
||||
|
||||
toString(options?: xml2js.BuilderOptions) {
|
||||
options = options || {};
|
||||
options.rootName = 'gpx';
|
||||
|
||||
const builder = new xml2js.Builder(options), gpx = new GPX(this);
|
||||
allDatesToISOString(gpx);
|
||||
return builder.buildObject(gpx);
|
||||
}
|
||||
}
|
||||
13
web/src/lib/models/gpx/link.ts
Normal file
13
web/src/lib/models/gpx/link.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default class Link {
|
||||
$: {
|
||||
href?: string;
|
||||
}
|
||||
text: string;
|
||||
type: string;
|
||||
constructor(object: any) {
|
||||
this.$ = {};
|
||||
this.$.href = object.$.href || object.href;
|
||||
this.text = object.text;
|
||||
this.type = object.type;
|
||||
}
|
||||
}
|
||||
48
web/src/lib/models/gpx/metadata.ts
Normal file
48
web/src/lib/models/gpx/metadata.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import Copyright from './copyright';
|
||||
import Link from './link';
|
||||
import Person from './person';
|
||||
import Bounds from './bounds';
|
||||
|
||||
export default class Metadata {
|
||||
name: string;
|
||||
desc: string;
|
||||
time: Date;
|
||||
keywords: string;
|
||||
extensions: string;
|
||||
author?: Person;
|
||||
link?: Link[];
|
||||
bounds?: Bounds;
|
||||
copyright?: Copyright;
|
||||
constructor(object: {
|
||||
name: string,
|
||||
desc: string,
|
||||
time: string,
|
||||
keywords: string,
|
||||
extensions: string,
|
||||
author?: Person,
|
||||
link?: Link | Link[],
|
||||
bounds?: Bounds,
|
||||
copyright?: Copyright
|
||||
}) {
|
||||
this.name = object.name;
|
||||
this.desc = object.desc;
|
||||
this.time = object.time ? new Date(object.time) : new Date();
|
||||
this.keywords = object.keywords;
|
||||
this.extensions = object.extensions;
|
||||
if (object.author) {
|
||||
this.author = new Person(object.author);
|
||||
}
|
||||
if (object.link) {
|
||||
if (!Array.isArray(object.link)) {
|
||||
object.link = [object.link];
|
||||
}
|
||||
this.link = object.link.map(l => new Link(l));
|
||||
}
|
||||
if (object.bounds) {
|
||||
this.bounds = new Bounds(object.bounds);
|
||||
}
|
||||
if (object.copyright) {
|
||||
this.copyright = new Copyright(object.copyright);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
web/src/lib/models/gpx/person.ts
Normal file
14
web/src/lib/models/gpx/person.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import Link from './link';
|
||||
|
||||
export default class Person {
|
||||
name: string;
|
||||
email: string;
|
||||
link?: Link;
|
||||
constructor(object: { name: string, email: string, link?: Link }) {
|
||||
this.name = object.name;
|
||||
this.email = object.email;
|
||||
if (object.link) {
|
||||
this.link = new Link(object.link);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
web/src/lib/models/gpx/route.ts
Normal file
45
web/src/lib/models/gpx/route.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import Waypoint from './waypoint';
|
||||
import Link from './link';
|
||||
|
||||
export default class Route {
|
||||
name: string;
|
||||
cmt: string;
|
||||
desc: string;
|
||||
src: string;
|
||||
number: number;
|
||||
type: string;
|
||||
extensions: string;
|
||||
link?: Link[];
|
||||
rtept?: Waypoint[];
|
||||
constructor(object: {
|
||||
name: string,
|
||||
cmt: string, desc: string,
|
||||
src: string,
|
||||
number: number,
|
||||
type: string,
|
||||
extensions: string,
|
||||
link?: Link | Link[],
|
||||
rtept?: Waypoint | Waypoint[]
|
||||
}) {
|
||||
this.name = object.name;
|
||||
this.cmt = object.cmt;
|
||||
this.desc = object.desc;
|
||||
this.src = object.src;
|
||||
this.number = object.number;
|
||||
this.type = object.type;
|
||||
this.extensions = object.extensions;
|
||||
if (object.link) {
|
||||
if (!Array.isArray(object.link)) {
|
||||
object.link = [object.link];
|
||||
}
|
||||
this.link = object.link.map(l => new Link(l));
|
||||
}
|
||||
|
||||
if (object.rtept) {
|
||||
if (!Array.isArray(object.rtept)) {
|
||||
this.rtept = [object.rtept];
|
||||
}
|
||||
this.rtept = (object.rtept as Waypoint[]).map(rtept => new Waypoint(rtept))
|
||||
}
|
||||
}
|
||||
}
|
||||
15
web/src/lib/models/gpx/track-segment.ts
Normal file
15
web/src/lib/models/gpx/track-segment.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import Waypoint from './waypoint';
|
||||
|
||||
export default class TrackSegment {
|
||||
trkpt?: Waypoint[];
|
||||
extensions?: string;
|
||||
constructor(object: { trkpt?: Waypoint[], extensions?: string }) {
|
||||
if (object.trkpt) {
|
||||
if (!Array.isArray(object.trkpt)) {
|
||||
object.trkpt = [object.trkpt];
|
||||
}
|
||||
this.trkpt = object.trkpt.map(trkpt => new Waypoint(trkpt))
|
||||
}
|
||||
this.extensions = object.extensions;
|
||||
}
|
||||
}
|
||||
45
web/src/lib/models/gpx/track.ts
Normal file
45
web/src/lib/models/gpx/track.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import TrackSegment from './track-segment';
|
||||
import Link from './link';
|
||||
|
||||
export default class Track {
|
||||
name?: string;
|
||||
cmt?: string;
|
||||
desc?: string;
|
||||
src?: string;
|
||||
number?: number;
|
||||
type?: string;
|
||||
extensions?: string;
|
||||
link?: Link[];
|
||||
trkseg?: TrackSegment[];
|
||||
constructor(object: {
|
||||
name?: string,
|
||||
cmt?: string,
|
||||
desc?: string,
|
||||
src?: string,
|
||||
number?: number,
|
||||
type?: string,
|
||||
extensions?: string,
|
||||
link?: Link | Link[],
|
||||
trkseg?: TrackSegment | TrackSegment[]
|
||||
}) {
|
||||
this.name = object.name;
|
||||
this.cmt = object.cmt;
|
||||
this.desc = object.desc;
|
||||
this.src = object.src;
|
||||
this.number = object.number;
|
||||
this.type = object.type;
|
||||
this.extensions = object.extensions;
|
||||
if (object.link) {
|
||||
if (!Array.isArray(object.link)) {
|
||||
object.link = [object.link];
|
||||
}
|
||||
this.link = object.link.map(l => new Link(l));
|
||||
}
|
||||
if (object.trkseg) {
|
||||
if (!Array.isArray(object.trkseg)) {
|
||||
object.trkseg = [object.trkseg];
|
||||
}
|
||||
this.trkseg = object.trkseg.map(trkseg => new TrackSegment(trkseg));;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
web/src/lib/models/gpx/utils.ts
Normal file
23
web/src/lib/models/gpx/utils.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
function removeEmpty(obj: Record<string, any>) {
|
||||
Object.entries(obj).forEach(([key, val]) => {
|
||||
if (val && val instanceof Object) {
|
||||
removeEmpty(val);
|
||||
} else if (val == null) {
|
||||
delete obj[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function allDatesToISOString(obj: Record<string, any>) {
|
||||
Object.entries(obj).forEach(([key, val]) => {
|
||||
if (val) {
|
||||
if (val instanceof Date) {
|
||||
obj[key] = val.toISOString().split('.')[0] + 'Z';
|
||||
} else if (val instanceof Object) {
|
||||
allDatesToISOString(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { removeEmpty, allDatesToISOString };
|
||||
81
web/src/lib/models/gpx/waypoint.ts
Normal file
81
web/src/lib/models/gpx/waypoint.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import Link from './link';
|
||||
|
||||
export default class Waypoint {
|
||||
$: {
|
||||
lat?: number;
|
||||
lon?: number;
|
||||
}
|
||||
ele?: number;
|
||||
time?: Date;
|
||||
magvar?: string;
|
||||
geoidheight?: string;
|
||||
name?: string;
|
||||
cmt?: string
|
||||
desc?: string;
|
||||
src?: string;
|
||||
sym?: string;
|
||||
type?: string;
|
||||
sat?: string;
|
||||
hdop?: string;
|
||||
vdop?: string;
|
||||
pdop?: string;
|
||||
ageofdgpsdata?: string;
|
||||
dgpsid?: string;
|
||||
extensions?: string;
|
||||
link?: Link[];
|
||||
constructor(object: {
|
||||
lat?: number,
|
||||
lon?: number,
|
||||
$: {
|
||||
lat?: number,
|
||||
lon?: number
|
||||
},
|
||||
ele?: number,
|
||||
time?: Date,
|
||||
magvar?: string,
|
||||
geoidheight?: string,
|
||||
name?: string,
|
||||
cmt?: string,
|
||||
desc?: string,
|
||||
src?: string,
|
||||
sym?: string,
|
||||
type?: string,
|
||||
sat?: string,
|
||||
hdop?: string,
|
||||
vdop?: string,
|
||||
pdop?: string,
|
||||
ageofdgpsdata?: string,
|
||||
dgpsid?: string,
|
||||
extensions?: string,
|
||||
link?: Link[]
|
||||
}) {
|
||||
this.$ = {};
|
||||
this.$.lat = object.$.lat === 0 || object.lat === 0 ? 0 : object.$.lat || object.lat || -1;
|
||||
this.$.lon = object.$.lon === 0 || object.lon === 0 ? 0 : object.$.lon || object.lon || -1;
|
||||
this.ele = object.ele;
|
||||
if (object.time) {
|
||||
this.time = new Date(object.time);
|
||||
}
|
||||
this.magvar = object.magvar;
|
||||
this.geoidheight = object.geoidheight;
|
||||
this.name = object.name;
|
||||
this.cmt = object.cmt;
|
||||
this.desc = object.desc;
|
||||
this.src = object.src;
|
||||
this.sym = object.sym;
|
||||
this.type = object.type;
|
||||
this.sat = object.sat;
|
||||
this.hdop = object.hdop;
|
||||
this.vdop = object.vdop;
|
||||
this.pdop = object.pdop;
|
||||
this.ageofdgpsdata = object.ageofdgpsdata;
|
||||
this.dgpsid = object.dgpsid;
|
||||
this.extensions = object.extensions;
|
||||
if (object.link) {
|
||||
if (!Array.isArray(object.link)) {
|
||||
object.link = [object.link];
|
||||
}
|
||||
this.link = object.link.map(l => new Link(l));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,9 @@
|
||||
interface ValhallaResponse {
|
||||
trip: {
|
||||
legs: {
|
||||
shape: string[];
|
||||
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}
|
||||
export { type ValhallaResponse }
|
||||
@@ -1,13 +1,17 @@
|
||||
import { ValhallaRoute, type ValhallaResponse } from "$lib/models/valhalla";
|
||||
import GPX from "$lib/models/gpx/gpx";
|
||||
import type Track from "$lib/models/gpx/track";
|
||||
import TrackSegment from "$lib/models/gpx/track-segment";
|
||||
import Waypoint from "$lib/models/gpx/waypoint";
|
||||
import { type ValhallaResponse } from "$lib/models/valhalla";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
|
||||
|
||||
export const route: ValhallaRoute = new ValhallaRoute();
|
||||
const emtpyTrack: Track = { trkseg: [] }
|
||||
export let route: GPX = new GPX({ trk: [emtpyTrack] });
|
||||
|
||||
|
||||
export function clearRoute() {
|
||||
route.geometry.coordinates = [];
|
||||
route = new GPX({ trk: [emtpyTrack] });
|
||||
}
|
||||
|
||||
export async function calculateRouteBetween(startLat: number, startLon: number, endLat: number, endLon: number) {
|
||||
@@ -21,16 +25,30 @@ export async function calculateRouteBetween(startLat: number, startLon: number,
|
||||
throw new ClientResponseError(await r.json())
|
||||
}
|
||||
const response: ValhallaResponse = await r.json();
|
||||
const points = decodeShape(response.trip.legs[0].shape);
|
||||
const waypoints = points.map((p) => new Waypoint({ $: { lat: p[0], lon: p[1] } }))
|
||||
|
||||
appendToRoute(response);
|
||||
return waypoints
|
||||
}
|
||||
|
||||
function appendToRoute(valhallaResponse: any) {
|
||||
const routeGeometry = decodeShape(valhallaResponse.trip.legs[0].shape);
|
||||
export async function appendToRoute(waypoints: Waypoint[]) {
|
||||
const segment = new TrackSegment({ trkpt: [] })
|
||||
|
||||
for (const point of routeGeometry) {
|
||||
route.geometry.coordinates.push([point[1], point[0]])
|
||||
for (const wpt of waypoints) {
|
||||
segment.trkpt!.push(wpt)
|
||||
}
|
||||
route.trk?.at(0)?.trkseg?.push(segment);
|
||||
}
|
||||
|
||||
export async function editRoute(index: number, waypoints: Waypoint[]) {
|
||||
const segment = route.trk?.at(0)?.trkseg?.at(index)
|
||||
if (segment) {
|
||||
segment.trkpt = waypoints
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteFromRoute(index: number) {
|
||||
route.trk?.at(0)?.trkseg?.splice(index, 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import GPX from "$lib/models/gpx/gpx";
|
||||
import { Trail } from "$lib/models/trail";
|
||||
import { Waypoint } from "$lib/models/waypoint";
|
||||
import { kml, tcx } from "$lib/vendor/toGeoJSON/toGeoJSON"
|
||||
import GeoJsonToGpx from "$lib/vendor/geoJSONToGPX"
|
||||
import { browser } from "$app/environment";
|
||||
import GeoJsonToGpx from "$lib/vendor/geoJSONToGPX";
|
||||
import { kml, tcx } from "$lib/vendor/toGeoJSON/toGeoJSON";
|
||||
|
||||
function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
||||
const R = 6371; // Radius of the Earth in km
|
||||
@@ -17,38 +17,23 @@ function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: numbe
|
||||
return distance;
|
||||
}
|
||||
|
||||
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
|
||||
xml = new JSDOM(gpx).window.document
|
||||
export async function gpx2trail(gpxString: string) {
|
||||
const gpx = await GPX.parse(gpxString);
|
||||
|
||||
if (gpx instanceof Error) {
|
||||
throw gpx;
|
||||
}
|
||||
|
||||
const trail = new Trail("");
|
||||
|
||||
let name = xml.getElementsByTagName('name');
|
||||
if (name.length > 0) {
|
||||
trail.name = name[0].textContent ?? "";
|
||||
}
|
||||
let desc = xml.getElementsByTagName('desc');
|
||||
if (desc.length > 0) {
|
||||
trail.description = desc[0].textContent ?? "";
|
||||
}
|
||||
trail.name = gpx.metadata?.name ?? "";
|
||||
|
||||
const el = xml.getElementsByTagName('wpt');
|
||||
const elLength = el.length
|
||||
for (let i = 0; i < elLength; i++) {
|
||||
const wp = new Waypoint(parseFloat(el[i].getAttribute('lat')!), parseFloat(el[i].getAttribute('lon')!));
|
||||
|
||||
let nameEl = el[i].getElementsByTagName('name');
|
||||
wp.name = nameEl.length > 0 ? nameEl[0].textContent ?? "" : '';
|
||||
|
||||
let descEl = el[i].getElementsByTagName('desc');
|
||||
wp.description = descEl.length > 0 ? descEl[0].textContent ?? "" : '';
|
||||
trail.description = gpx.metadata?.desc;
|
||||
|
||||
for (const wpt of gpx.wpt ?? []) {
|
||||
const wp = new Waypoint(wpt.$.lat ?? 0, wpt.$.lon ?? 0);
|
||||
wp.name = wp.name
|
||||
wp.description = wp.description;
|
||||
trail.expand.waypoints.push(wp);
|
||||
}
|
||||
|
||||
@@ -56,17 +41,13 @@ export async function gpx2trail(gpx: string) {
|
||||
let totalDuration = 0;
|
||||
let totalDistance = 0;
|
||||
|
||||
const tracks = xml.getElementsByTagName("trk")
|
||||
for (const track of tracks) {
|
||||
const segments = track.getElementsByTagName("trkseg")
|
||||
for (const segment of segments) {
|
||||
const points = segment.getElementsByTagName("trkpt")
|
||||
for (const track of gpx.trk ?? []) {
|
||||
for (const segment of track.trkseg ?? []) {
|
||||
const points = segment.trkpt ?? [];
|
||||
|
||||
if (points.length >= 2) {
|
||||
const startTimeString = points[0].querySelector('time')?.textContent;
|
||||
const startTime = startTimeString ? new Date(startTimeString) : undefined;
|
||||
const endTimeString = points[points.length - 1].querySelector('time')?.textContent;
|
||||
const endTime = endTimeString ? new Date(endTimeString) : undefined;
|
||||
const startTime = points[0].time;
|
||||
const endTime = points[points.length - 1].time
|
||||
|
||||
if (startTime && endTime) {
|
||||
totalDuration += endTime.getTime() - startTime.getTime();
|
||||
@@ -79,30 +60,30 @@ export async function gpx2trail(gpx: string) {
|
||||
|
||||
const pointLength = points.length
|
||||
for (let i = 1; i < pointLength; i++) {
|
||||
const elevation = parseFloat(points[i].querySelector('ele')?.textContent || '0')
|
||||
const previousElevation = parseFloat(points[i - 1].querySelector('ele')?.textContent || '0')
|
||||
const prevPoint = points[i - 1];
|
||||
const point = points[i];
|
||||
const elevation = point.ele ?? 0
|
||||
const previousElevation = prevPoint.ele ?? 0
|
||||
const elevationDiff = elevation - previousElevation;
|
||||
if (elevationDiff > 0) {
|
||||
totalElevationGain += elevationDiff;
|
||||
}
|
||||
|
||||
const prevPoint = points[i - 1];
|
||||
const point = points[i];
|
||||
const distance = calculateDistance(
|
||||
parseFloat(prevPoint.getAttribute("lat") || '0'),
|
||||
parseFloat(prevPoint.getAttribute("lon") || '0'),
|
||||
parseFloat(point.getAttribute("lat") || '0'),
|
||||
parseFloat(point.getAttribute("lon") || '0')
|
||||
prevPoint.$.lat ?? 0,
|
||||
prevPoint.$.lon ?? 0,
|
||||
point.$.lat ?? 0,
|
||||
point.$.lon ?? 0,
|
||||
);
|
||||
totalDistance += distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const startPoint = xml.getElementsByTagName("trk")[0].getElementsByTagName("trkseg")[0].getElementsByTagName("trkpt")[0];
|
||||
const startPoint = gpx.trk?.at(0)?.trkseg?.at(0)?.trkpt?.at(0);
|
||||
if (startPoint) {
|
||||
trail.lat = parseFloat(startPoint.getAttribute('lat')!)
|
||||
trail.lon = parseFloat(startPoint.getAttribute('lon')!)
|
||||
trail.lat = startPoint.$.lat
|
||||
trail.lon = startPoint.$.lon
|
||||
}
|
||||
|
||||
trail.duration = totalDuration / 1000 / 60
|
||||
|
||||
@@ -32,8 +32,11 @@
|
||||
trails_update,
|
||||
} from "$lib/stores/trail_store";
|
||||
import {
|
||||
appendToRoute,
|
||||
calculateRouteBetween,
|
||||
clearRoute,
|
||||
deleteFromRoute,
|
||||
editRoute,
|
||||
route,
|
||||
} from "$lib/stores/valhalla_store";
|
||||
import { waypoint } from "$lib/stores/waypoint_store";
|
||||
@@ -47,7 +50,7 @@
|
||||
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
|
||||
import { createForm } from "$lib/vendor/svelte-form-lib";
|
||||
import cryptoRandomString from "crypto-random-string";
|
||||
import type { LatLng, LeafletMouseEvent, Map } from "leaflet";
|
||||
import type { LeafletMouseEvent, Map } from "leaflet";
|
||||
import { onMount } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { array, number, object, string } from "yup";
|
||||
@@ -161,6 +164,9 @@
|
||||
onMount(async () => {
|
||||
L = (await import("leaflet")).default;
|
||||
await import("leaflet.awesome-markers");
|
||||
|
||||
$form.lat = 47.74604748696788;
|
||||
$form.lon = 11.588988304138185;
|
||||
});
|
||||
|
||||
function openFileBrowser() {
|
||||
@@ -242,9 +248,7 @@
|
||||
openWaypointModal();
|
||||
} else if (e.detail.value === "delete") {
|
||||
currentWaypoint.marker?.remove();
|
||||
$form.expand.waypoints.splice(index, 1);
|
||||
$form.waypoints.splice(index, 1);
|
||||
$form.expand.waypoints = $form.expand.waypoints;
|
||||
deleteWaypoint(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +258,34 @@
|
||||
openWaypointModal();
|
||||
}
|
||||
|
||||
function deleteWaypoint(index: number) {
|
||||
$form.expand.waypoints.splice(index, 1);
|
||||
$form.waypoints.splice(index, 1);
|
||||
$form.expand.waypoints = $form.expand.waypoints;
|
||||
|
||||
if (drawingActive) {
|
||||
if (index == 0) {
|
||||
deleteFromRoute(index);
|
||||
$form.expand.gpx_data = route.toString();
|
||||
const nextWaypoint = $form.expand.waypoints[index];
|
||||
nextWaypoint.icon = "circle-half-stroke";
|
||||
saveWaypoint(nextWaypoint);
|
||||
} else if (index == $form.expand.waypoints.length) {
|
||||
deleteFromRoute(index - 1);
|
||||
$form.expand.gpx_data = route.toString();
|
||||
|
||||
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) {
|
||||
let editedWaypointIndex = $form.expand.waypoints.findIndex(
|
||||
(s) => s.id == savedWaypoint.id,
|
||||
@@ -270,12 +302,18 @@
|
||||
const marker = createMarkerFromWaypoint(L, savedWaypoint, (event) => {
|
||||
var marker = event.target;
|
||||
var position = marker.getLatLng();
|
||||
const editableWaypoint = $form.expand.waypoints.find(
|
||||
const editableWaypointIndex = $form.expand.waypoints.findIndex(
|
||||
(w) => w.id == savedWaypoint.id,
|
||||
);
|
||||
const editableWaypoint =
|
||||
$form.expand.waypoints[editableWaypointIndex];
|
||||
editableWaypoint!.lat = position.lat;
|
||||
editableWaypoint!.lon = position.lng;
|
||||
$form.expand.waypoints = [...$form.expand.waypoints];
|
||||
|
||||
if (drawingActive) {
|
||||
recalculateRoute(editableWaypointIndex);
|
||||
}
|
||||
});
|
||||
|
||||
marker.addTo(map);
|
||||
@@ -327,7 +365,6 @@
|
||||
await lists_index();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
show_toast({
|
||||
type: "error",
|
||||
icon: "close",
|
||||
@@ -353,22 +390,29 @@
|
||||
const waypointCount = $form.expand.waypoints.length;
|
||||
const wp = new Waypoint(e.latlng.lat, e.latlng.lng, {
|
||||
name: `${$_("waypoints", { values: { n: 1 } })} ${waypointCount + 1}`,
|
||||
icon: waypointCount ? undefined : "circle-half-stroke",
|
||||
icon: waypointCount ? "flag-checkered" : "circle-half-stroke",
|
||||
});
|
||||
saveWaypoint(wp);
|
||||
|
||||
if (waypointCount >= 1) {
|
||||
if (waypointCount == 0) {
|
||||
saveWaypoint(wp);
|
||||
} else {
|
||||
const previousWaypoint = $form.expand.waypoints[waypointCount - 1];
|
||||
try {
|
||||
await calculateRouteBetween(
|
||||
const routeWaypoints = await calculateRouteBetween(
|
||||
previousWaypoint.lat,
|
||||
previousWaypoint.lon,
|
||||
e.latlng.lat,
|
||||
e.latlng.lng,
|
||||
);
|
||||
appendToRoute(routeWaypoints);
|
||||
saveWaypoint(wp);
|
||||
|
||||
if (waypointCount > 1) {
|
||||
previousWaypoint.icon = "circle";
|
||||
saveWaypoint(previousWaypoint);
|
||||
}
|
||||
$form.expand.gpx_data = route.toString();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
show_toast({
|
||||
text: "Error calculating route",
|
||||
icon: "close",
|
||||
@@ -377,6 +421,38 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function recalculateRoute(waypointIndex: number) {
|
||||
const waypoint = $form.expand.waypoints[waypointIndex];
|
||||
let nextWaypoints;
|
||||
let previousWaypoints;
|
||||
if (waypointIndex < $form.expand.waypoints.length - 1) {
|
||||
const nextWaypoint = $form.expand.waypoints[waypointIndex + 1];
|
||||
|
||||
nextWaypoints = await calculateRouteBetween(
|
||||
waypoint.lat,
|
||||
waypoint.lon,
|
||||
nextWaypoint.lat,
|
||||
nextWaypoint.lon,
|
||||
);
|
||||
}
|
||||
if (waypointIndex > 0) {
|
||||
const previousWaypoint = $form.expand.waypoints[waypointIndex - 1];
|
||||
previousWaypoints = await calculateRouteBetween(
|
||||
previousWaypoint.lat,
|
||||
previousWaypoint.lon,
|
||||
waypoint.lat,
|
||||
waypoint.lon,
|
||||
);
|
||||
}
|
||||
if (nextWaypoints) {
|
||||
editRoute(waypointIndex, nextWaypoints);
|
||||
}
|
||||
if (previousWaypoints) {
|
||||
editRoute(waypointIndex - 1, previousWaypoints);
|
||||
}
|
||||
$form.expand.gpx_data = route.toString();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -616,7 +692,12 @@
|
||||
<MapWithElevation
|
||||
trail={$form}
|
||||
crosshair={drawingActive}
|
||||
options={{ hotline: false }}
|
||||
options={{
|
||||
hotline: false,
|
||||
autofitBounds: false,
|
||||
trkStart: false,
|
||||
trkEnd: false,
|
||||
}}
|
||||
bind:map
|
||||
on:click={(e) => handleMapClick(e.detail)}
|
||||
></MapWithElevation>
|
||||
|
||||
Reference in New Issue
Block a user