mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-17 16:42:28 +00:00
progress
This commit is contained in:
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,
|
||||
|
||||
Reference in new issue
Block a user