diff --git a/web/src/lib/i18n/locales/en.json b/web/src/lib/i18n/locales/en.json index e3610173..b3c5daf6 100644 --- a/web/src/lib/i18n/locales/en.json +++ b/web/src/lib/i18n/locales/en.json @@ -101,7 +101,7 @@ "empty-activities": "{username} has no activity yet", "empty-bio": "{username} has not added a bio yet", "empty-lists": "{username} has no public lists", - "enable-auto-routing": "", + "enable-auto-routing": "Enable auto-routing", "english": "English", "entry": "Entry", "error-creating-user": "Error creating user", diff --git a/web/src/lib/models/gpx/gpx.ts b/web/src/lib/models/gpx/gpx.ts index deced4df..a62fabb0 100644 --- a/web/src/lib/models/gpx/gpx.ts +++ b/web/src/lib/models/gpx/gpx.ts @@ -7,6 +7,9 @@ import Waypoint from './waypoint'; import GpxMetricsComputation from './gpx-metrics-computation'; //@ts-ignore import geohash from "ngeohash" +import { encodePolyline } from '$lib/util/polyline_util'; +import { APIError } from '$lib/util/api_util'; +import type { ValhallaHeightResponse } from '../valhalla'; const defaultAttributes = { version: '1.1', @@ -97,7 +100,7 @@ export default class GPX { let totalLat = 0 let totalLon = 0 - const metrics = new GpxMetricsComputation(5, 10); + const metrics = new GpxMetricsComputation(5, 5); let minLat = Infinity, maxLat = -Infinity, minLon = Infinity, maxLon = -Infinity; @@ -155,19 +158,40 @@ export default class GPX { return hashes.sort().join('').slice(0, 10); } - isSimilar(other: GPX, distanceThreshold = 50): boolean { - const f1 = this.features; - const f2 = other.features; - const centroidDistance = haversineDistance(f1.centroid.lat, f1.centroid.lon, f2.centroid.lat, f2.centroid.lon); - const boundingBoxOverlap = - f1.boundingBox.minLat <= f2.boundingBox.maxLat && f1.boundingBox.maxLat >= f2.boundingBox.minLat && - f1.boundingBox.minLon <= f2.boundingBox.maxLon && f1.boundingBox.maxLon >= f2.boundingBox.minLon; + async correctElevation(f: (url: RequestInfo | URL, config?: RequestInit) => Promise = fetch) { + let coordinates: number[][] = [] + for (const track of this.trk ?? []) { + for (const segment of track.trkseg ?? []) { + if (!segment.trkpt) { + continue + } + coordinates = coordinates.concat(segment.trkpt.map(pt => [pt.$.lat ?? 0, pt.$.lon ?? 0])) + } + } - const lengthDifference = Math.abs(f1.distance - f2.distance); - const hashSimilarity = f1.hash === f2.hash; + const shape = encodePolyline(coordinates); + const r2 = await f("/api/v1/valhalla/height", { method: "POST", body: JSON.stringify({ encoded_polyline: shape }) }) - return centroidDistance < distanceThreshold || boundingBoxOverlap || (lengthDifference < 100 && hashSimilarity); + if (!r2.ok) { + const response = await r2.json(); + throw new APIError(r2.status, response.message, response.detail) + } + const heightResponse: ValhallaHeightResponse = await r2.json() + + let heightIndex = 0; + for (const track of this.trk ?? []) { + for (const segment of track.trkseg ?? []) { + if (!segment.trkpt) { + continue + } + segment.trkpt.forEach((pt) => { + pt.ele = heightResponse.height[heightIndex] + heightIndex++; + }) + } + } + this.features = this.getTotals() } static parse(gpxString: string): Promise { @@ -182,7 +206,7 @@ export default class GPX { return str; } ] - }, (err, xml) => { + }, async (err, xml) => { if (err) { reject(err); } diff --git a/web/src/lib/util/gpx_util.ts b/web/src/lib/util/gpx_util.ts index 473756fb..c2dc870e 100644 --- a/web/src/lib/util/gpx_util.ts +++ b/web/src/lib/util/gpx_util.ts @@ -1,10 +1,8 @@ import GPX from "$lib/models/gpx/gpx"; import { Trail } from "$lib/models/trail"; import { Waypoint } from "$lib/models/waypoint"; -import { currentUser } from "$lib/stores/user_store"; import { gpx, kml, tcx } from "$lib/vendor/toGeoJSON/toGeoJSON"; import cryptoRandomString from "crypto-random-string"; -import { get } from "svelte/store"; //@ts-ignore import { browser } from "$app/environment"; import Track from "$lib/models/gpx/track"; @@ -12,19 +10,20 @@ import TrackSegment from "$lib/models/gpx/track-segment"; import GPXWaypoint from "$lib/models/gpx/waypoint"; import EasyFit from "$lib/vendor/easy-fit/easy-fit"; import type { Feature, FeatureCollection, GeoJSON, GeoJsonProperties, Position } from 'geojson'; -import * as xmldom from 'xmldom'; -import { bbox, splitMultiLineStringToLineStrings } from "./geojson_util"; import JSZip from "jszip"; import type { AuthRecord } from "pocketbase"; +import * as xmldom from 'xmldom'; +import { bbox, splitMultiLineStringToLineStrings } from "./geojson_util"; -export async function gpx2trail(gpxString: string, fallbackName?: string) { +export async function gpx2trail(gpxString: string, fallbackName?: string, f: (url: RequestInfo | URL, config?: RequestInit) => Promise = fetch) { const gpx = await GPX.parse(gpxString); if (gpx instanceof Error) { throw gpx; } + await gpx.correctElevation(f) const trail = new Trail(""); diff --git a/web/src/routes/api/v1/trail/upload/+server.ts b/web/src/routes/api/v1/trail/upload/+server.ts index b44ecf4c..714317e2 100644 --- a/web/src/routes/api/v1/trail/upload/+server.ts +++ b/web/src/routes/api/v1/trail/upload/+server.ts @@ -19,7 +19,7 @@ export async function PUT(event: RequestEvent) { } let parseResult: { trail: Trail, gpx: GPX }; try { - parseResult = (await gpx2trail(gpxData, data.get("name") as string | undefined)); + parseResult = (await gpx2trail(gpxData, data.get("name") as string | undefined, event.fetch)); } catch (e: any) { console.error(e) throw new ClientResponseError({ status: 400, response: { message: "Invalid file" } })