improves gpx elevation calculation

This commit is contained in:
Christian Beutel
2025-04-25 13:18:29 +02:00
parent c6b17ad32e
commit 29ea15b36f
4 changed files with 42 additions and 19 deletions

View File

@@ -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",

View File

@@ -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<Response> = 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<GPX | Error> {
@@ -182,7 +206,7 @@ export default class GPX {
return str;
}
]
}, (err, xml) => {
}, async (err, xml) => {
if (err) {
reject(err);
}

View File

@@ -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<Response> = fetch) {
const gpx = await GPX.parse(gpxString);
if (gpx instanceof Error) {
throw gpx;
}
await gpx.correctElevation(f)
const trail = new Trail("");

View File

@@ -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" } })