mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-09 23:26:31 +00:00
import and export of routes, ref #192
This commit is contained in:
1 parent
da9abbae5e
commit
dc36de2a48
10 files changed
+620
-62
No files matched your search
@@ -21,6 +21,7 @@
|
||||
Orbit,
|
||||
Thermometer,
|
||||
SquareActivity,
|
||||
Route,
|
||||
} from '@lucide/svelte';
|
||||
import { i18n } from '$lib/i18n.svelte';
|
||||
import { engine, type ExportOptions } from '$lib/engine';
|
||||
@@ -37,6 +38,7 @@
|
||||
atemp: true,
|
||||
power: true,
|
||||
osm: false,
|
||||
asRoute: false,
|
||||
});
|
||||
// the files that are exported
|
||||
let fileIds: string[] = $derived.by(() => {
|
||||
@@ -61,6 +63,8 @@
|
||||
atemp: !available.atemp,
|
||||
power: !available.power,
|
||||
osm: !available.osm,
|
||||
// always possible
|
||||
asRoute: false,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -113,6 +117,7 @@
|
||||
atemp: exportOptions.atemp && !hide.atemp,
|
||||
power: exportOptions.power && !hide.power,
|
||||
osm: exportOptions.osm && !hide.osm,
|
||||
asRoute: exportOptions.asRoute,
|
||||
};
|
||||
if (exportState.current === ExportState.SELECTION) {
|
||||
exportSelectedFiles(options);
|
||||
@@ -192,6 +197,13 @@
|
||||
{i18n._('quantities.osm_extensions')}
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-1.5">
|
||||
<Checkbox id="export-as-route" bind:checked={exportOptions.asRoute} />
|
||||
<Label for="export-as-route" class="flex flex-row items-center gap-1">
|
||||
<Route size="16" />
|
||||
{i18n._('menu.export_as_route', 'Export as route')}
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -5,7 +5,14 @@ import { readFileSync } from 'node:fs';
|
||||
import { beforeEach, describe, it } from 'node:test';
|
||||
import { get } from 'svelte/store';
|
||||
import { isHidden } from '$lib/file-visibility';
|
||||
import { engine, FileStateCollectionObserver, NO_TIME, type FileState } from '$lib/engine';
|
||||
import {
|
||||
ALL_EXPORT_OPTIONS,
|
||||
engine,
|
||||
FileStateCollectionObserver,
|
||||
NO_TIME,
|
||||
type ExportOptions,
|
||||
type FileState,
|
||||
} from '$lib/engine';
|
||||
|
||||
function fixture(name: string): Uint8Array {
|
||||
return new Uint8Array(
|
||||
@@ -387,6 +394,49 @@ describe('observer of the files', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('export', () => {
|
||||
const text = (id: string, options: Partial<ExportOptions> = {}) =>
|
||||
new TextDecoder().decode(engine.exportFile(id, { ...ALL_EXPORT_OPTIONS, ...options }));
|
||||
|
||||
it('writes tracks, or routes when asked to', async () => {
|
||||
await load('with_tracks_and_segments');
|
||||
const file = fileStates()[0];
|
||||
const segments = file.structure.tracks.reduce(
|
||||
(sum, track) => sum + track.segments.length,
|
||||
0
|
||||
);
|
||||
const id = file.structure.id;
|
||||
|
||||
const tracks = text(id);
|
||||
assert.ok(tracks.includes('<trk>') && !tracks.includes('<rte>'));
|
||||
assert.equal(tracks.match(/<trkseg>/g)?.length, segments);
|
||||
|
||||
const routes = text(id, { asRoute: true });
|
||||
assert.ok(routes.includes('<rte>') && !routes.includes('<trk>'));
|
||||
assert.equal(routes.match(/<rte>/g)?.length, segments);
|
||||
assert.ok(routes.includes('<rtept'));
|
||||
});
|
||||
|
||||
it('does not count the way a file is written among its data', async () => {
|
||||
await load('simple');
|
||||
const data = engine.exportableData(get(engine.order));
|
||||
assert.equal(data.asRoute, false);
|
||||
});
|
||||
|
||||
it('can read back the routes it writes', async () => {
|
||||
await load('with_tracks_and_segments');
|
||||
const id = get(engine.order)[0];
|
||||
const before = fileStates()[0].segments.features.length;
|
||||
const routes = engine.exportFile(id, { ...ALL_EXPORT_OPTIONS, asRoute: true })!;
|
||||
await engine.loadFiles([{ data: routes, name: 'routes' }]);
|
||||
const [, loaded] = get(engine.order);
|
||||
const state = get(get(engine.files).get(loaded)!);
|
||||
// one route per segment, each read as a track of one segment
|
||||
assert.equal(state.structure.tracks.length, before);
|
||||
assert.equal(state.segments.features.length, before);
|
||||
});
|
||||
});
|
||||
|
||||
describe('visibility', () => {
|
||||
it('hides the selection and shows it again', async () => {
|
||||
await load('simple');
|
||||
|
||||
@@ -509,7 +509,8 @@ class Engine {
|
||||
options.cad,
|
||||
options.atemp,
|
||||
options.power,
|
||||
options.osm
|
||||
options.osm,
|
||||
options.asRoute
|
||||
) as Uint8Array<ArrayBuffer> | undefined;
|
||||
}
|
||||
|
||||
@@ -523,6 +524,8 @@ class Engine {
|
||||
atemp: (bits & 8) !== 0,
|
||||
power: (bits & 16) !== 0,
|
||||
osm: (bits & 32) !== 0,
|
||||
// not a data
|
||||
asRoute: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,11 @@ export type ExportOptions = {
|
||||
power: boolean;
|
||||
/** The OpenStreetMap data: surface, highway, SAC scale and MTB scale. */
|
||||
osm: boolean;
|
||||
/**
|
||||
* Writes the segments as routes instead of tracks: the anchors are the route points, and the
|
||||
* trackpoints between them the points of their path (which only keep their position).
|
||||
*/
|
||||
asRoute: boolean;
|
||||
};
|
||||
|
||||
export const ALL_EXPORT_OPTIONS: ExportOptions = {
|
||||
@@ -126,6 +131,7 @@ export const ALL_EXPORT_OPTIONS: ExportOptions = {
|
||||
atemp: true,
|
||||
power: true,
|
||||
osm: true,
|
||||
asRoute: false,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"export": "Export...",
|
||||
"export_all": "Export all...",
|
||||
"export_options": "Export options",
|
||||
"export_as_route": "Export as route",
|
||||
"support_message": "The tool is free to use, but not free to run. Please consider supporting the website if you use it frequently. Thank you!",
|
||||
"support_button": "Help keep the website free",
|
||||
"download_file": "Download file",
|
||||
|
||||
Reference in new issue
Block a user