From 5be02d1c36263aa79c0eca24ef29cde19098a529 Mon Sep 17 00:00:00 2001 From: vcoppe Date: Wed, 4 Sep 2024 14:42:26 +0200 Subject: [PATCH] add new anchors by clicking on the temporary one --- gpx/src/simplify.ts | 51 +++++++++++++++++ .../toolbar/tools/routing/RoutingControls.ts | 56 +++++++++++++++++-- website/src/lib/utils.ts | 2 + 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/gpx/src/simplify.ts b/gpx/src/simplify.ts index 69da3682..bffdba1e 100644 --- a/gpx/src/simplify.ts +++ b/gpx/src/simplify.ts @@ -101,4 +101,55 @@ function bearing(latA: number, lonA: number, latB: number, lonB: number): number // Finds the bearing from one lat / lon point to another. return Math.atan2(Math.sin(lonB - lonA) * Math.cos(latB), Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(lonB - lonA)); +} + +export function projectedPoint(point1: TrackPoint, point2: TrackPoint, point3: TrackPoint | Coordinates): Coordinates { + return projected(point1.getCoordinates(), point2.getCoordinates(), point3 instanceof TrackPoint ? point3.getCoordinates() : point3); +} + +function projected(coord1: Coordinates, coord2: Coordinates, coord3: Coordinates): Coordinates { + // Calculates the point on the line defined by p1 and p2 + // that is closest to the third point, p3. + // Input lat1,lon1,lat2,lon2,lat3,lon3 in degrees. + + const rad = Math.PI / 180; + const lat1 = coord1.lat * rad; + const lat2 = coord2.lat * rad; + const lat3 = coord3.lat * rad; + + const lon1 = coord1.lon * rad; + const lon2 = coord2.lon * rad; + const lon3 = coord3.lon * rad; + + // Prerequisites for the formulas + const bear12 = bearing(lat1, lon1, lat2, lon2); + const bear13 = bearing(lat1, lon1, lat3, lon3); + let dis13 = distance(lat1, lon1, lat3, lon3); + + let diff = Math.abs(bear13 - bear12); + if (diff > Math.PI) { + diff = 2 * Math.PI - diff; + } + + // Is relative bearing obtuse? + if (diff > (Math.PI / 2)) { + return coord1; + } + + // Find the cross-track distance. + let dxt = Math.asin(Math.sin(dis13 / earthRadius) * Math.sin(bear13 - bear12)) * earthRadius; + + // Is p4 beyond the arc? + let dis12 = distance(lat1, lon1, lat2, lon2); + let dis14 = Math.acos(Math.cos(dis13 / earthRadius) / Math.cos(dxt / earthRadius)) * earthRadius; + if (dis14 > dis12) { + return coord2; + } else { + // Determine the closest point (p4) on the great circle + const f = dis14 / earthRadius; + const lat4 = Math.asin(Math.sin(lat1) * Math.cos(f) + Math.cos(lat1) * Math.sin(f) * Math.cos(bear12)); + const lon4 = lon1 + Math.atan2(Math.sin(bear12) * Math.sin(f) * Math.cos(lat1), Math.cos(f) - Math.sin(lat1) * Math.sin(lat4)); + + return { lat: lat4 / rad, lon: lon4 / rad }; + } } \ No newline at end of file diff --git a/website/src/lib/components/toolbar/tools/routing/RoutingControls.ts b/website/src/lib/components/toolbar/tools/routing/RoutingControls.ts index 08b21755..3434dc70 100644 --- a/website/src/lib/components/toolbar/tools/routing/RoutingControls.ts +++ b/website/src/lib/components/toolbar/tools/routing/RoutingControls.ts @@ -1,4 +1,4 @@ -import { distance, type Coordinates, TrackPoint, TrackSegment, Track, crossarcDistance } from "gpx"; +import { distance, type Coordinates, TrackPoint, TrackSegment, Track, projectedPoint } from "gpx"; import { get, writable, type Readable } from "svelte/store"; import mapboxgl from "mapbox-gl"; import { route } from "./Routing"; @@ -10,7 +10,7 @@ import { dbUtils, type GPXFileWithStatistics } from "$lib/db"; import { getOrderedSelection, selection } from "$lib/components/file-list/Selection"; import { ListFileItem, ListTrackItem, ListTrackSegmentItem } from "$lib/components/file-list/FileList"; import { currentTool, streetViewEnabled, Tool } from "$lib/stores"; -import { getClosestLinePoint, resetCursor, setGrabbingCursor } from "$lib/utils"; +import { getClosestLinePoint, getElevation, resetCursor, setGrabbingCursor } from "$lib/utils"; export const canChangeStart = writable(false); @@ -185,11 +185,13 @@ export class RoutingControls { return (e: any) => { e.preventDefault(); e.stopPropagation(); - if (marker === this.temporaryAnchor.marker) { + + if (Date.now() - this.lastDragEvent < 100) { // Prevent click event during drag return; } - if (Date.now() - this.lastDragEvent < 100) { // Prevent click event during drag + if (marker === this.temporaryAnchor.marker) { + this.turnIntoPermanentAnchor(); return; } @@ -364,6 +366,52 @@ export class RoutingControls { return minAnchor; } + turnIntoPermanentAnchor() { + let file = get(this.file)?.file; + + // Find the point closest to the temporary anchor + let minDetails: any = { distance: Number.MAX_VALUE }; + let minInfo = { + point: this.temporaryAnchor.point, + trackIndex: -1, + segmentIndex: -1, + trkptIndex: -1 + }; + + file?.forEachSegment((segment, trackIndex, segmentIndex) => { + if (get(selection).hasAnyParent(new ListTrackSegmentItem(this.fileId, trackIndex, segmentIndex))) { + let details: any = {}; + getClosestLinePoint(segment.trkpt, this.temporaryAnchor.point, details); + if (details.distance < minDetails.distance) { + minDetails = details; + let before = details.before ? details.index : details.index - 1; + + let projectedPt = projectedPoint(segment.trkpt[before], segment.trkpt[before + 1], this.temporaryAnchor.point); + let ratio = distance(segment.trkpt[before], projectedPt) / distance(segment.trkpt[before], segment.trkpt[before + 1]); + + minInfo = { + point: new TrackPoint({ + attributes: projectedPt, + ele: (1 - ratio) * (segment.trkpt[before].ele ?? 0) + ratio * (segment.trkpt[before + 1].ele ?? 0), + time: (segment.trkpt[before].time && segment.trkpt[before + 1].time) ? new Date((1 - ratio) * segment.trkpt[before].time.getTime() + ratio * segment.trkpt[before + 1].time.getTime()) : undefined, + _data: { + anchor: true, + zoom: 0 + } + }), + trackIndex, + segmentIndex, + trkptIndex: before + 1 + }; + } + } + }); + + if (minInfo.trackIndex !== -1) { + dbUtils.applyToFile(this.fileId, (file) => file.replaceTrackPoints(minInfo.trackIndex, minInfo.segmentIndex, minInfo.trkptIndex, minInfo.trkptIndex - 1, [minInfo.point])); + } + } + getDeleteAnchor(anchor: Anchor) { return () => this.deleteAnchor(anchor); } diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts index 1e0d6e40..120cb3da 100644 --- a/website/src/lib/utils.ts +++ b/website/src/lib/utils.ts @@ -78,9 +78,11 @@ export function getClosestLinePoint(points: TrackPoint[], point: TrackPoint | Co if (distance(points[i], point) <= distance(points[i + 1], point)) { closest = points[i]; details['before'] = true; + details['index'] = i; } else { closest = points[i + 1]; details['before'] = false; + details['index'] = i + 1; } } }