mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-11-01 20:19:39 +00:00
Migration to new Rust-based engine (#401)
* wasm first steps * parsing start * parsing continued * progress * progress * progress * progress * progress * progress * progress * progress * fixes * reorganize * change type * fix * progress * refactoring * progress * progress * progress * keep style info * progress * progress * remove (copy) in name * progress * merge methods into 1 * splice * progress * progress * progress * cleaning * progress * progress * progress * visibility * progress * progress * progress * progress * progress * fixes * progress * fix markers * fix * progress * progress * progress * progress * progress * progress * small fix * use engine info * fix popups * small fix * progress * progress * progress * clean tool * handle self closing elements * reduce data exchanges across boundary * copy on wasm side to avoid any data corruption * speed fix * routing * refactoring * reverse * reverse * crop * split * merge * progress * time tool * fix bounds for waypoints * reduce * fix reactivity * handle corrupt time data * export * progress * remove gpx js package * remove todo * update readme * cleaning * progress * fix link fields * remove unused fields * refactoring and tests * import and export of routes, ref #192 * per-chunk categories storage * cross-tab sync * outdated state * refactoring * reorder methods * refactoring * fix * improve flow * renaming * progress * progress * fix * progress * per file history * keep original flag * use history variant * fix * progress * file explorer * progress * fix * sticky folders * improved sticky folders * fix sortable * fix sortable * fix * fix tab size * no file delete on desktop * fix routing on other file * fix sorting * remember language * open links in browser * commit missing changes * avoid selecting on resize * clarify filename * explorer actions * fix folder styling * drop anywhere in folder * cleaning * fixes * remove focus-ring * full screen * small fix * consistent highlight color * improved preview * performance improvements * sparser data transfers * sparser data transfers * show popup once data is loaded * fix routing control size * fix * sparse desktop build * remove custom ign styles * use new endpoint for openhikingmap * strip native menu * reorder context menu items * route full support * route - track conversion * create route file action * route export, filter out anchors at high zoom level * clarify name handling * move hide & center to view * routes continued * update docs * fix power format, closes #378 * lint fixes * fix display of additional data with gaps, closes #361 * fix linting issues * fix import modules * fix multidrag * change base to resolve * fix type errors * fix layer typings * fix last type error * each keys * improve typings * deploy to cloudflare --------- Co-authored-by: vcoppe <vcoppe@hexaly.com>
This commit is contained in:
1 parent
6a0d434371
commit
8fe7d8d64d
484 files changed
+53949
-41152
No files matched your search
+39
-115
@@ -1,12 +1,10 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { base } from '$app/paths';
|
||||
import { languages } from '$lib/languages';
|
||||
import { TrackPoint, Waypoint, type Coordinates, crossarcDistance, distance, GPXFile } from 'gpx';
|
||||
import { Map as MapLibreMap, LngLatBounds } from 'maplibre-gl';
|
||||
import { resolve } from '$app/paths';
|
||||
import { languages } from '$lib/i18n/languages';
|
||||
import type { Coordinates } from '$lib/geo/coordinates';
|
||||
import { LngLatBounds, type Map as MapLibreMap } from 'maplibre-gl';
|
||||
import { pointToTile, pointToTileFraction } from '@mapbox/tilebelt';
|
||||
import type { GPXStatisticsTree } from '$lib/logic/statistics-tree';
|
||||
import { ListTrackSegmentItem } from '$lib/components/file-list/file-list';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
@@ -19,108 +17,27 @@ export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, 'childre
|
||||
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
||||
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
|
||||
|
||||
export function getClosestLinePoint(
|
||||
points: TrackPoint[],
|
||||
point: TrackPoint | Coordinates,
|
||||
details: any = {}
|
||||
): TrackPoint {
|
||||
let closest = points[0];
|
||||
let closestDist = Number.MAX_VALUE;
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
let dist = crossarcDistance(points[i], points[i + 1], point);
|
||||
if (dist < closestDist) {
|
||||
closestDist = dist;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
details['distance'] = closestDist;
|
||||
return closest;
|
||||
}
|
||||
|
||||
export function getClosestTrackSegments(
|
||||
file: GPXFile,
|
||||
statistics: GPXStatisticsTree,
|
||||
point: Coordinates
|
||||
): [number, number][] {
|
||||
let segmentBoundsDistances: [number, number, number][] = [];
|
||||
file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
||||
let segmentStatistics = statistics.getStatisticsFor(
|
||||
new ListTrackSegmentItem(file._data.id, trackIndex, segmentIndex)
|
||||
);
|
||||
let segmentBounds = segmentStatistics.global.bounds;
|
||||
let northEast = segmentBounds.northEast;
|
||||
let southWest = segmentBounds.southWest;
|
||||
let bounds = new LngLatBounds(southWest, northEast);
|
||||
if (bounds.contains(point)) {
|
||||
segmentBoundsDistances.push([0, trackIndex, segmentIndex]);
|
||||
} else {
|
||||
let northWest: Coordinates = { lat: northEast.lat, lon: southWest.lon };
|
||||
let southEast: Coordinates = { lat: southWest.lat, lon: northEast.lon };
|
||||
let distanceToBounds = Math.min(
|
||||
crossarcDistance(northWest, northEast, point),
|
||||
crossarcDistance(northEast, southEast, point),
|
||||
crossarcDistance(southEast, southWest, point),
|
||||
crossarcDistance(southWest, northWest, point)
|
||||
);
|
||||
segmentBoundsDistances.push([distanceToBounds, trackIndex, segmentIndex]);
|
||||
}
|
||||
});
|
||||
segmentBoundsDistances.sort((a, b) => a[0] - b[0]);
|
||||
|
||||
let closest: { distance: number; indices: [number, number][] } = {
|
||||
distance: Number.MAX_VALUE,
|
||||
indices: [],
|
||||
};
|
||||
for (let s = 0; s < segmentBoundsDistances.length; s++) {
|
||||
if (segmentBoundsDistances[s][0] > closest.distance) {
|
||||
break;
|
||||
}
|
||||
const segment = file.getSegment(segmentBoundsDistances[s][1], segmentBoundsDistances[s][2]);
|
||||
segment.trkpt.forEach((pt) => {
|
||||
let dist = distance(pt.getCoordinates(), point);
|
||||
if (dist < closest.distance) {
|
||||
closest.distance = dist;
|
||||
closest.indices = [[segmentBoundsDistances[s][1], segmentBoundsDistances[s][2]]];
|
||||
} else if (dist === closest.distance) {
|
||||
closest.indices.push([segmentBoundsDistances[s][1], segmentBoundsDistances[s][2]]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return closest.indices;
|
||||
}
|
||||
|
||||
export function getElevation(
|
||||
points: (TrackPoint | Waypoint | Coordinates)[],
|
||||
points: Coordinates[],
|
||||
ELEVATION_ZOOM: number = 12,
|
||||
tileSize = 512
|
||||
): Promise<number[]> {
|
||||
let coordinates = points.map((point) =>
|
||||
point instanceof TrackPoint || point instanceof Waypoint ? point.getCoordinates() : point
|
||||
);
|
||||
let bbox = new LngLatBounds();
|
||||
const coordinates = points;
|
||||
const bbox = new LngLatBounds();
|
||||
coordinates.forEach((coord) => bbox.extend(coord));
|
||||
|
||||
let tiles = coordinates.map((coord) => pointToTile(coord.lon, coord.lat, ELEVATION_ZOOM));
|
||||
let uniqueTiles = Array.from(new Set(tiles.map((tile) => tile.join(',')))).map((tile) =>
|
||||
const tiles = coordinates.map((coord) => pointToTile(coord.lng, coord.lat, ELEVATION_ZOOM));
|
||||
const uniqueTiles = Array.from(new Set(tiles.map((tile) => tile.join(',')))).map((tile) =>
|
||||
tile.split(',').map((x) => parseInt(x))
|
||||
);
|
||||
let images = new Map<string, ImageData>();
|
||||
const images = new Map<string, ImageData>();
|
||||
|
||||
const getPixelFromImageData = (imageData: ImageData, x: number, y: number): number[] => {
|
||||
const index = (y * imageData.width + x) * 4;
|
||||
return [imageData.data[index], imageData.data[index + 1], imageData.data[index + 2]];
|
||||
};
|
||||
|
||||
let promises = uniqueTiles.map((tile) =>
|
||||
const promises = uniqueTiles.map((tile) =>
|
||||
fetch(`https://tiles.gpx.studio/mapterhorn/${ELEVATION_ZOOM}/${tile[0]}/${tile[1]}.webp`, {
|
||||
cache: 'force-cache',
|
||||
})
|
||||
@@ -154,20 +71,20 @@ export function getElevation(
|
||||
|
||||
return Promise.all(promises).then(() =>
|
||||
coordinates.map((coord, index) => {
|
||||
let tile = tiles[index];
|
||||
let imageData = images.get(tile.join(','));
|
||||
const tile = tiles[index];
|
||||
const imageData = images.get(tile.join(','));
|
||||
|
||||
if (!imageData) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let tf = pointToTileFraction(coord.lon, coord.lat, ELEVATION_ZOOM);
|
||||
let x = tileSize * (tf[0] - tile[0]);
|
||||
let y = tileSize * (tf[1] - tile[1]);
|
||||
let _x = Math.floor(x);
|
||||
let _y = Math.floor(y);
|
||||
let dx = x - _x;
|
||||
let dy = y - _y;
|
||||
const tf = pointToTileFraction(coord.lng, coord.lat, ELEVATION_ZOOM);
|
||||
const x = tileSize * (tf[0] - tile[0]);
|
||||
const y = tileSize * (tf[1] - tile[1]);
|
||||
const _x = Math.floor(x);
|
||||
const _y = Math.floor(y);
|
||||
const dx = x - _x;
|
||||
const dy = y - _y;
|
||||
|
||||
const p00 = getPixelFromImageData(imageData, _x, _y);
|
||||
const p01 = getPixelFromImageData(imageData, _x, _y + (_y + 1 == tileSize ? 0 : 1));
|
||||
@@ -178,10 +95,10 @@ export function getElevation(
|
||||
_y + (_y + 1 == tileSize ? 0 : 1)
|
||||
);
|
||||
|
||||
let ele00 = -32768 + p00[0] * 256 + p00[1] + p00[2] / 256;
|
||||
let ele01 = -32768 + p01[0] * 256 + p01[1] + p01[2] / 256;
|
||||
let ele10 = -32768 + p10[0] * 256 + p10[1] + p10[2] / 256;
|
||||
let ele11 = -32768 + p11[0] * 256 + p11[1] + p11[2] / 256;
|
||||
const ele00 = -32768 + p00[0] * 256 + p00[1] + p00[2] / 256;
|
||||
const ele01 = -32768 + p01[0] * 256 + p01[1] + p01[2] / 256;
|
||||
const ele10 = -32768 + p10[0] * 256 + p10[1] + p10[2] / 256;
|
||||
const ele11 = -32768 + p11[0] * 256 + p11[1] + p11[2] / 256;
|
||||
|
||||
return (
|
||||
ele00 * (1 - dx) * (1 - dy) +
|
||||
@@ -195,7 +112,7 @@ export function getElevation(
|
||||
|
||||
export function loadSVGIcon(map: MapLibreMap, id: string, svg: string, size: number = 100) {
|
||||
if (!map.hasImage(id)) {
|
||||
let icon = new Image(size, size);
|
||||
const icon = new Image(size, size);
|
||||
icon.onload = () => {
|
||||
if (!map.hasImage(id)) {
|
||||
map.addImage(id, icon);
|
||||
@@ -213,11 +130,19 @@ export function isSafari() {
|
||||
return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* The path of a page in another language, without the base path (pass it to `resolve`). `path` is
|
||||
* the path of the page, with or without the base path.
|
||||
*/
|
||||
export function getURLForLanguage(lang: string, path: string): string {
|
||||
let newPath = path.replace(base, '');
|
||||
const base = resolve('/').slice(0, -1);
|
||||
let newPath =
|
||||
base && (path === base || path.startsWith(`${base}/`))
|
||||
? path.slice(base.length) || '/'
|
||||
: path;
|
||||
|
||||
let languageInPath = newPath.split('/')[1];
|
||||
if (!languages.hasOwnProperty(languageInPath)) {
|
||||
if (!Object.hasOwn(languages, languageInPath)) {
|
||||
languageInPath = 'en';
|
||||
}
|
||||
|
||||
@@ -227,17 +152,16 @@ export function getURLForLanguage(lang: string, path: string): string {
|
||||
|
||||
if (languageInPath === 'en') {
|
||||
if (lang === 'en') {
|
||||
return `${base}${newPath}`;
|
||||
return newPath;
|
||||
} else {
|
||||
return `${base}/${lang}${newPath}`;
|
||||
return `/${lang}${newPath}`;
|
||||
}
|
||||
} else {
|
||||
if (lang === 'en') {
|
||||
newPath = newPath.replace(`/${languageInPath}`, '');
|
||||
return newPath === '' ? `${base}/` : `${base}${newPath}`;
|
||||
return newPath === '' ? '/' : newPath;
|
||||
} else {
|
||||
newPath = newPath.replace(`/${languageInPath}`, `/${lang}`);
|
||||
return `${base}${newPath}`;
|
||||
return newPath.replace(`/${languageInPath}`, `/${lang}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user