This commit is contained in:
vcoppe committed 2026-10-08 18:34:19 +02:00
1 parent 606a0e974a
commit 67a4c090bd
107 files changed
+3474 -3948

No files matched your search

@@ -17,7 +17,7 @@
async function addElevation() {
const stats = get(statistics);
const points = Array.from(stats.lng, (lon, i) => ({ lon, lat: stats.lat[i] }));
const points = Array.from(stats.lng, (lng, i) => ({ lng, lat: stats.lat[i] }));
const ele = await getElevation(points);
// the selection or the files may have changed while the elevation was loading
@@ -1,4 +1,4 @@
import { distance, type Coordinates } from 'gpx';
import { distance, type Coordinates } from '$lib/geo';
import { get, writable } from 'svelte/store';
import maplibregl, {
type MapMouseEvent,
@@ -50,17 +50,11 @@ type Anchor = {
type AnchorProperties = { anchorIndex: number; minZoom: number };
type AnchorFeature = GeoJSON.Feature<GeoJSON.Point, AnchorProperties>;
type Position = { lng: number; lat: number };
/** Whether the tool has to show controls for a selection: it covers segments, or can get some. */
function isRoutable(selection: Selection) {
return selection.type === 'file' || selection.type === 'track' || selection.type === 'segment';
}
function toCoordinates({ lng, lat }: Position): Coordinates {
return { lat, lon: lng };
}
/**
* The anchors of the selection of the engine, shown on the map where the user can drag them to
* reroute the segments, or click on the segments to add some. Every interaction is turned into a
@@ -103,7 +97,7 @@ export class RoutingControls {
* Where the pointer hovers a segment of the selection: an anchor that can be dragged or
* clicked to be added.
*/
temporaryAnchor: (Position & { segment: number }) | null = null;
temporaryAnchor: (Coordinates & { segment: number }) | null = null;
showTemporaryAnchorBinded: (e: MapLayerMouseEvent) => void =
this.showTemporaryAnchor.bind(this);
updateTemporaryAnchorBinded: (e: MapMouseEvent) => void = this.updateTemporaryAnchor.bind(this);
@@ -328,11 +322,11 @@ export class RoutingControls {
this.popup.remove();
}
position(index: number): Position {
position(index: number): Coordinates {
return { lng: this.statistics!.lng[index], lat: this.statistics!.lat[index] };
}
async moveAnchor(anchor: Anchor, coordinates: Position) {
async moveAnchor(anchor: Anchor, coordinates: Coordinates) {
// Move the anchor and update the route from and to the neighbouring anchors
const initialAnchor = anchor;
const initialCoordinates = { lng: anchor.lng, lat: anchor.lat };
@@ -349,7 +343,7 @@ export class RoutingControls {
const [previousAnchor, nextAnchor] = this.getNeighbouringAnchors(anchor);
const chain: Anchor[] = [];
const targets: Position[] = [];
const targets: Coordinates[] = [];
if (previousAnchor) {
chain.push(previousAnchor);
targets.push(this.position(previousAnchor.index));
@@ -457,7 +451,7 @@ export class RoutingControls {
const first = this.position(start);
const last = this.position(end - 1);
// the end of the segment has to be close to its start, up to a kilometer
return distance(toCoordinates(first), toCoordinates(last)) <= 1000;
return distance(first, last) <= 1000;
}
async appendAnchor(e: maplibregl.MapMouseEvent) {
@@ -480,7 +474,7 @@ export class RoutingControls {
this.appendAnchorWithCoordinates({ lng: e.lngLat.lng, lat: e.lngLat.lat });
}
async appendAnchorWithCoordinates(coordinates: Position) {
async appendAnchorWithCoordinates(coordinates: Coordinates) {
// Add a new anchor to the end of the last segment
const statistics = this.statistics;
const revision = this.anchorData?.revision;
@@ -546,7 +540,7 @@ export class RoutingControls {
* single segment) or where they were moved to, and replaces what is between the first and
* the last anchor by the route.
*/
async routeBetweenAnchors(anchors: Anchor[], targets: Position[]): Promise<boolean> {
async routeBetweenAnchors(anchors: Anchor[], targets: Coordinates[]): Promise<boolean> {
const revision = this.anchorData?.revision;
if (revision === undefined) {
return false;
@@ -565,7 +559,7 @@ export class RoutingControls {
let response;
try {
response = await route(targets.map(toCoordinates));
response = await route(targets);
} catch (e: any) {
toast.error(i18n._(e.message, e.message));
return false;
@@ -581,9 +575,9 @@ export class RoutingControls {
const keepFirst =
first.index !== segmentStart &&
(first.index !== lastIndex ||
distance(toCoordinates(targets[0]), {
distance(targets[0], {
lat: response.lat[0],
lon: response.lng[0],
lng: response.lng[0],
}) > 1);
const keepLast = last.index !== lastIndex;
@@ -843,7 +837,7 @@ export class RoutingControls {
return false;
}
moveAnchorFeature(anchor: Anchor, coordinates: Position) {
moveAnchorFeature(anchor: Anchor, coordinates: Coordinates) {
const source = get(map)?.getSource(
this.layers.get(anchor.id === this.anchors.length ? 0 : anchor.zoom)?.id ?? ''
) as GeoJSONSource | undefined;
@@ -1,4 +1,4 @@
import { distance, type Coordinates } from 'gpx';
import { distance, type Coordinates } from '$lib/geo';
import { settings } from '$lib/logic/settings';
import { getElevation } from '$lib/utils';
import { get } from 'svelte/store';
@@ -143,7 +143,7 @@ async function getGraphHopperRoute(
'Content-Type': 'application/json',
},
body: JSON.stringify({
points: points.map((point) => [point.lon, point.lat]),
points: points.map((point) => [point.lng, point.lat]),
profile: graphHopperProfile,
elevation: true,
points_encoded: false,
@@ -209,7 +209,7 @@ async function getBRouterRoute(
points: Coordinates[],
brouterProfile: string
): Promise<RoutedPoints> {
let url = `https://brouter.de/brouter?lonlats=${points.map((point) => `${point.lon.toFixed(8)},${point.lat.toFixed(8)}`).join('|')}&profile=${brouterProfile}&format=geojson&alternativeidx=0`;
let url = `https://brouter.de/brouter?lonlats=${points.map((point) => `${point.lng.toFixed(8)},${point.lat.toFixed(8)}`).join('|')}&profile=${brouterProfile}&format=geojson&alternativeidx=0`;
let response = await fetch(url);
@@ -283,15 +283,15 @@ function getIntermediatePoints(points: Coordinates[]): Promise<RoutedPoints> {
const dist = distance(points[i], points[i + 1]) / 1000;
for (let d = 0; d < dist; d += step) {
const lat = points[i].lat + (d / dist) * (points[i + 1].lat - points[i].lat);
const lon = points[i].lon + (d / dist) * (points[i + 1].lon - points[i].lon);
addPoint(route, lon, lat, 0);
const lng = points[i].lng + (d / dist) * (points[i + 1].lng - points[i].lng);
addPoint(route, lng, lat, 0);
}
}
const last = points[points.length - 1];
addPoint(route, last.lon, last.lat, 0);
addPoint(route, last.lng, last.lat, 0);
return getElevation(route.lng.map((lng, i) => ({ lon: lng, lat: route.lat[i] }))).then(
return getElevation(route.lng.map((lng, i) => ({ lng, lat: route.lat[i] }))).then(
(elevations) => {
route.ele = elevations;
return route;
@@ -1,51 +0,0 @@
import { ramerDouglasPeucker, type GPXFile, type TrackSegment } from 'gpx';
const earthRadius = 6371008.8;
export const MIN_ANCHOR_ZOOM = 0;
export const MAX_ANCHOR_ZOOM = 22;
export function getZoomLevelForDistance(latitude: number, distance?: number): number {
if (distance === undefined) {
return MIN_ANCHOR_ZOOM;
}
const rad = Math.PI / 180;
const lat = latitude * rad;
return Math.min(
MAX_ANCHOR_ZOOM,
Math.max(MIN_ANCHOR_ZOOM, Math.round(Math.log2((earthRadius * Math.cos(lat)) / distance)))
);
}
export function updateAnchorPoints(file: GPXFile) {
let segments = file.getSegments();
for (let segment of segments) {
if (!segment._data.anchors) {
// New segment, compute anchor points for it
computeAnchorPoints(segment);
continue;
}
if (segment.trkpt.length > 0) {
// Ensure first and last points are anchors and always visible
segment.trkpt[0]._data.anchor = true;
segment.trkpt[0]._data.zoom = 0;
segment.trkpt[segment.trkpt.length - 1]._data.anchor = true;
segment.trkpt[segment.trkpt.length - 1]._data.zoom = 0;
}
}
}
function computeAnchorPoints(segment: TrackSegment) {
let points = segment.trkpt;
let anchors = ramerDouglasPeucker(points, 1);
anchors.forEach((anchor) => {
let point = anchor.point;
point._data.anchor = true;
point._data.zoom = getZoomLevelForDistance(point.getLatitude(), anchor.distance);
});
segment._data.anchors = true;
}
@@ -88,7 +88,7 @@
longitude = parseFloat(longitude.toFixed(6));
const target = $selectedWaypoint;
const [ele] = await getElevation([{ lat: latitude, lon: longitude }]);
const [ele] = await getElevation([{ lat: latitude, lng: longitude }]);
const waypoint = {
lng: longitude,
lat: latitude,