mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-02 16:52:31 +00:00
progress
This commit is contained in:
@@ -34,12 +34,10 @@
|
|||||||
<ElevationProfile />
|
<ElevationProfile />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="shrink-0">
|
|
||||||
{#if $verticalFileView}
|
{#if $verticalFileView}
|
||||||
<FileList orientation="vertical" recursive={true} class="w-60" />
|
<FileList orientation="vertical" recursive={true} class="w-60" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
div :global(.toaster.group) {
|
div :global(.toaster.group) {
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ScrollArea
|
<ScrollArea
|
||||||
class={orientation === 'vertical' ? 'p-1 pr-3' : 'h-10 px-1'}
|
class="shrink-0 {orientation === 'vertical' ? 'p-1 pr-3' : 'h-10 px-1'}"
|
||||||
{orientation}
|
{orientation}
|
||||||
scrollbarXClasses={orientation === 'vertical' ? '' : 'mt-1 h-2'}
|
scrollbarXClasses={orientation === 'vertical' ? '' : 'mt-1 h-2'}
|
||||||
scrollbarYClasses={orientation === 'vertical' ? '' : ''}
|
scrollbarYClasses={orientation === 'vertical' ? '' : ''}
|
||||||
|
@@ -58,7 +58,21 @@
|
|||||||
|
|
||||||
if ($fileOrder.length !== $fileObservers.size) {
|
if ($fileOrder.length !== $fileObservers.size) {
|
||||||
// Files were added or removed
|
// Files were added or removed
|
||||||
fileOrder.set(sortable.toArray());
|
fileOrder.update((order) => {
|
||||||
|
for (let i = 0; i < order.length; ) {
|
||||||
|
if (!$fileObservers.has(order[i])) {
|
||||||
|
order.splice(i, 1);
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let id of $fileObservers.keys()) {
|
||||||
|
if (!order.includes(id)) {
|
||||||
|
order.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return order;
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { distance, type Coordinates, TrackPoint, TrackSegment } from "gpx";
|
import { distance, type Coordinates, TrackPoint, TrackSegment } from "gpx";
|
||||||
import { original } from "immer";
|
import { original } from "immer";
|
||||||
import { get, type Readable } from "svelte/store";
|
import { get, type Readable } from "svelte/store";
|
||||||
import { computeAnchorPoints } from "./Simplify";
|
|
||||||
import mapboxgl from "mapbox-gl";
|
import mapboxgl from "mapbox-gl";
|
||||||
import { route } from "./Routing";
|
import { route } from "./Routing";
|
||||||
|
|
||||||
@@ -66,22 +65,6 @@ export class RoutingControls {
|
|||||||
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
|
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
|
||||||
let segment = segments[segmentIndex];
|
let segment = segments[segmentIndex];
|
||||||
|
|
||||||
if (!segment._data.anchors) { // New segment, compute anchor points for it
|
|
||||||
computeAnchorPoints(segment);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (segment.trkpt.length > 0) {
|
|
||||||
if (!segment.trkpt[0]._data.anchor) { // First point is not an anchor, make it one
|
|
||||||
segment.trkpt[0]._data.anchor = true;
|
|
||||||
segment.trkpt[0]._data.zoom = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!segment.trkpt[segment.trkpt.length - 1]._data.anchor) { // Last point is not an anchor, make it one
|
|
||||||
segment.trkpt[segment.trkpt.length - 1]._data.anchor = true;
|
|
||||||
segment.trkpt[segment.trkpt.length - 1]._data.zoom = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let point of segment.trkpt) { // Update the existing anchors (could be improved by matching the existing anchors with the new ones?)
|
for (let point of segment.trkpt) { // Update the existing anchors (could be improved by matching the existing anchors with the new ones?)
|
||||||
if (point._data.anchor) {
|
if (point._data.anchor) {
|
||||||
if (anchorIndex < this.anchors.length) {
|
if (anchorIndex < this.anchors.length) {
|
||||||
@@ -433,17 +416,19 @@ export class RoutingControls {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (anchors[0].point._data.index === 0) { // First anchor is the first point of the segment
|
if (anchors[0].point._data.index === 0) { // First anchor is the first point of the segment
|
||||||
anchors[0].point = response[0]; // Update the first anchor
|
anchors[0].point = response[0]; // Replace the first anchor
|
||||||
anchors[0].point._data.index = 0;
|
anchors[0].point._data.index = 0;
|
||||||
} else {
|
} else {
|
||||||
response.splice(0, 0, anchors[0].point); // Keep the original start point
|
anchors[0].point = anchors[0].point.clone(); // Clone the anchor to assign new properties
|
||||||
|
response.splice(0, 0, anchors[0].point); // Insert it in the response to keep it
|
||||||
}
|
}
|
||||||
|
|
||||||
if (anchors[anchors.length - 1].point._data.index === segment.trkpt.length - 1) { // Last anchor is the last point of the segment
|
if (anchors[anchors.length - 1].point._data.index === segment.trkpt.length - 1) { // Last anchor is the last point of the segment
|
||||||
anchors[anchors.length - 1].point = response[response.length - 1]; // Update the last anchor
|
anchors[anchors.length - 1].point = response[response.length - 1]; // Replace the last anchor
|
||||||
anchors[anchors.length - 1].point._data.index = segment.trkpt.length - 1;
|
anchors[anchors.length - 1].point._data.index = segment.trkpt.length - 1;
|
||||||
} else {
|
} else {
|
||||||
response.push(anchors[anchors.length - 1].point); // Keep the original end point
|
anchors[anchors.length - 1].point = anchors[anchors.length - 1].point.clone(); // Clone the anchor to assign new properties
|
||||||
|
response.push(anchors[anchors.length - 1].point); // Insert it in the response to keep it
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 1; i < anchors.length - 1; i++) {
|
for (let i = 1; i < anchors.length - 1; i++) {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import type { Coordinates, TrackPoint, TrackSegment } from "gpx";
|
import type { Coordinates, GPXFile, TrackPoint, TrackSegment } from "gpx";
|
||||||
|
|
||||||
type SimplifiedTrackPoint = { point: TrackPoint, distance?: number };
|
type SimplifiedTrackPoint = { point: TrackPoint, distance?: number };
|
||||||
|
|
||||||
@@ -15,7 +15,32 @@ export function getZoomLevelForDistance(latitude: number, distance?: number): nu
|
|||||||
return Math.min(20, Math.max(0, Math.floor(Math.log2((earthRadius * Math.cos(lat)) / distance))));
|
return Math.min(20, Math.max(0, Math.floor(Math.log2((earthRadius * Math.cos(lat)) / distance))));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computeAnchorPoints(segment: TrackSegment) {
|
export function updateAnchorPoints(file: GPXFile) {
|
||||||
|
let segments = file.getSegments();
|
||||||
|
|
||||||
|
for (let segmentIndex = 0; segmentIndex < segments.length; segmentIndex++) {
|
||||||
|
let segment = segments[segmentIndex];
|
||||||
|
|
||||||
|
if (!segment._data.anchors) { // New segment, compute anchor points for it
|
||||||
|
computeAnchorPoints(segment);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment.trkpt.length > 0) {
|
||||||
|
if (!segment.trkpt[0]._data.anchor) { // First point is not an anchor, make it one
|
||||||
|
segment.trkpt[0]._data.anchor = true;
|
||||||
|
segment.trkpt[0]._data.zoom = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!segment.trkpt[segment.trkpt.length - 1]._data.anchor) { // Last point is not an anchor, make it one
|
||||||
|
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 points = segment.trkpt;
|
||||||
let anchors = ramerDouglasPeucker(points);
|
let anchors = ramerDouglasPeucker(points);
|
||||||
anchors.forEach((anchor) => {
|
anchors.forEach((anchor) => {
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
import Dexie, { liveQuery } from 'dexie';
|
import Dexie, { liveQuery } from 'dexie';
|
||||||
import { GPXFile, GPXStatistics, Track } from 'gpx';
|
import { GPXFile, GPXStatistics, Track } from 'gpx';
|
||||||
import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDraft, castDraft, Immer } from 'immer';
|
import { enableMapSet, enablePatches, applyPatches, type Patch, type WritableDraft, castDraft, freeze, produceWithPatches, original } from 'immer';
|
||||||
import { writable, get, derived, type Readable, type Writable } from 'svelte/store';
|
import { writable, get, derived, type Readable, type Writable } from 'svelte/store';
|
||||||
import { initTargetMapBounds, updateTargetMapBounds } from './stores';
|
import { initTargetMapBounds, updateTargetMapBounds } from './stores';
|
||||||
import { mode } from 'mode-watcher';
|
import { mode } from 'mode-watcher';
|
||||||
import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays } from './assets/layers';
|
import { defaultBasemap, defaultBasemapTree, defaultOverlayTree, defaultOverlays } from './assets/layers';
|
||||||
import { selection } from '$lib/components/file-list/Selection';
|
import { selection } from '$lib/components/file-list/Selection';
|
||||||
import { ListFileItem, ListItem, type ListLevel } from '$lib/components/file-list/FileList';
|
import { ListFileItem, ListItem, type ListLevel } from '$lib/components/file-list/FileList';
|
||||||
|
import { updateAnchorPoints } from '$lib/components/toolbar/tools/routing/Simplify';
|
||||||
|
|
||||||
enableMapSet();
|
enableMapSet();
|
||||||
enablePatches();
|
enablePatches();
|
||||||
|
|
||||||
const noFreezeImmer = new Immer({ autoFreeze: false }); // Do not freeze files that are not concerned by an update, otherwise cannot assign anchors for them
|
|
||||||
|
|
||||||
class Database extends Dexie {
|
class Database extends Dexie {
|
||||||
|
|
||||||
@@ -163,6 +163,7 @@ function dexieGPXFileStore(id: string): Readable<GPXFileWithStatistics> & { dest
|
|||||||
let query = liveQuery(() => db.files.get(id)).subscribe(value => {
|
let query = liveQuery(() => db.files.get(id)).subscribe(value => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
let gpx = new GPXFile(value);
|
let gpx = new GPXFile(value);
|
||||||
|
updateAnchorPoints(gpx);
|
||||||
|
|
||||||
let statistics = new GPXStatisticsTree(gpx);
|
let statistics = new GPXStatisticsTree(gpx);
|
||||||
if (!fileState.has(id)) { // Update the map bounds for new files
|
if (!fileState.has(id)) { // Update the map bounds for new files
|
||||||
@@ -260,7 +261,7 @@ export const canRedo: Readable<boolean> = derived([patchIndex, patchMinMaxIndex]
|
|||||||
|
|
||||||
// Helper function to apply a callback to the global file state
|
// Helper function to apply a callback to the global file state
|
||||||
function applyGlobal(callback: (files: Map<string, GPXFile>) => void) {
|
function applyGlobal(callback: (files: Map<string, GPXFile>) => void) {
|
||||||
const [newFileState, patch, inversePatch] = noFreezeImmer.produceWithPatches(fileState, callback);
|
const [newFileState, patch, inversePatch] = produceWithPatches(fileState, callback);
|
||||||
|
|
||||||
storePatches(patch, inversePatch);
|
storePatches(patch, inversePatch);
|
||||||
|
|
||||||
@@ -269,7 +270,7 @@ function applyGlobal(callback: (files: Map<string, GPXFile>) => void) {
|
|||||||
|
|
||||||
// Helper function to apply a callback to multiple files
|
// Helper function to apply a callback to multiple files
|
||||||
function applyToFiles(fileIds: string[], callback: (file: WritableDraft<GPXFile>) => GPXFile) {
|
function applyToFiles(fileIds: string[], callback: (file: WritableDraft<GPXFile>) => GPXFile) {
|
||||||
const [newFileState, patch, inversePatch] = noFreezeImmer.produceWithPatches(fileState, (draft) => {
|
const [newFileState, patch, inversePatch] = produceWithPatches(fileState, (draft) => {
|
||||||
fileIds.forEach((fileId) => {
|
fileIds.forEach((fileId) => {
|
||||||
let file = draft.get(fileId);
|
let file = draft.get(fileId);
|
||||||
if (file) {
|
if (file) {
|
||||||
@@ -336,7 +337,7 @@ export const dbUtils = {
|
|||||||
add: (file: GPXFile) => {
|
add: (file: GPXFile) => {
|
||||||
file._data.id = getFileIds(1)[0];
|
file._data.id = getFileIds(1)[0];
|
||||||
return applyGlobal((draft) => {
|
return applyGlobal((draft) => {
|
||||||
draft.set(file._data.id, file);
|
draft.set(file._data.id, freeze(file));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addMultiple: (files: GPXFile[]) => {
|
addMultiple: (files: GPXFile[]) => {
|
||||||
@@ -344,7 +345,7 @@ export const dbUtils = {
|
|||||||
let ids = getFileIds(files.length);
|
let ids = getFileIds(files.length);
|
||||||
files.forEach((file, index) => {
|
files.forEach((file, index) => {
|
||||||
file._data.id = ids[index];
|
file._data.id = ids[index];
|
||||||
draft.set(file._data.id, file);
|
draft.set(file._data.id, freeze(file));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -360,12 +361,12 @@ export const dbUtils = {
|
|||||||
// TODO
|
// TODO
|
||||||
let ids = getFileIds(get(settings.fileOrder).length);
|
let ids = getFileIds(get(settings.fileOrder).length);
|
||||||
get(settings.fileOrder).forEach((fileId, index) => {
|
get(settings.fileOrder).forEach((fileId, index) => {
|
||||||
if (get(selection).has(fileId)) {
|
if (get(selection).has(new ListFileItem(fileId))) {
|
||||||
let file = draft.get(fileId);
|
let file = original(draft)?.get(fileId);
|
||||||
if (file) {
|
if (file) {
|
||||||
let clone = file.clone();
|
let clone = file.clone();
|
||||||
clone._data.id = ids[index];
|
clone._data.id = ids[index];
|
||||||
draft.set(clone._data.id, clone);
|
draft.set(clone._data.id, freeze(clone));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -71,6 +71,11 @@ targetMapBounds.subscribe((bounds) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentBounds = get(map)?.getBounds();
|
||||||
|
if (currentBounds && currentBounds.contains(bounds.bounds.getSouthEast()) && currentBounds.contains(bounds.bounds.getNorthWest())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
get(map)?.fitBounds(bounds.bounds, {
|
get(map)?.fitBounds(bounds.bounds, {
|
||||||
padding: 80,
|
padding: 80,
|
||||||
linear: true,
|
linear: true,
|
||||||
|
Reference in New Issue
Block a user