diff --git a/website/src/lib/components/Menu.svelte b/website/src/lib/components/Menu.svelte
index f2f31cdc..f42ce49f 100644
--- a/website/src/lib/components/Menu.svelte
+++ b/website/src/lib/components/Menu.svelte
@@ -56,8 +56,7 @@
editStyle,
exportState,
ExportState,
- flyToBounds,
- gpxStatistics
+ centerMapOnSelection
} from '$lib/stores';
import {
copied,
@@ -225,13 +224,6 @@
{$_('menu.style.button')}
- flyToBounds($gpxStatistics.global.bounds, $map)}
- >
-
- {$_('menu.fly_to_selection')}
-
{
if ($allHidden) {
@@ -257,6 +249,17 @@
{$_('menu.select_all')}
+ {
+ if ($selection.size > 0) {
+ centerMapOnSelection();
+ }
+ }}
+ >
+
+ {$_('menu.center')}
+
+
{#if $verticalFileView}
@@ -545,6 +548,10 @@
dbUtils.setHiddenToSelection(true);
}
e.preventDefault();
+ } else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
+ if ($selection.size > 0) {
+ centerMapOnSelection();
+ }
} else if (e.key === 'F1') {
switchBasemaps();
e.preventDefault();
diff --git a/website/src/lib/components/file-list/FileListNodeLabel.svelte b/website/src/lib/components/file-list/FileListNodeLabel.svelte
index 7f8137dd..f6880557 100644
--- a/website/src/lib/components/file-list/FileListNodeLabel.svelte
+++ b/website/src/lib/components/file-list/FileListNodeLabel.svelte
@@ -45,9 +45,8 @@
editMetadata,
editStyle,
embedding,
- flyToBounds,
+ centerMapOnSelection,
gpxLayers,
- gpxStatistics,
map
} from '$lib/stores';
import {
@@ -61,7 +60,6 @@
import { _ } from 'svelte-i18n';
import MetadataDialog from './MetadataDialog.svelte';
import StyleDialog from './StyleDialog.svelte';
- import mapboxgl from 'mapbox-gl';
export let node: GPXTreeElement | Waypoint[] | Waypoint;
export let item: ListItem;
@@ -227,14 +225,6 @@
{$_('menu.style.button')}
{/if}
- {
- flyToBounds($gpxStatistics.global.bounds, $map);
- }}
- >
-
- {$_('menu.fly_to')}
-
{
if ($allHidden) {
@@ -294,8 +284,13 @@
{$_('menu.select_all')}
-
{/if}
+
+
+ {$_('menu.center')}
+
+
+
{$_('menu.duplicate')}
diff --git a/website/src/lib/stores.ts b/website/src/lib/stores.ts
index 9490a893..65643db1 100644
--- a/website/src/lib/stores.ts
+++ b/website/src/lib/stores.ts
@@ -7,19 +7,19 @@ import { _ } from 'svelte-i18n';
import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer';
import { dbUtils, fileObservers, getFile, getStatistics, settings } from './db';
import {
- addSelectItem,
- applyToOrderedSelectedItemsFromFile,
- selectFile,
- selectItem,
- selection
+ addSelectItem,
+ applyToOrderedSelectedItemsFromFile,
+ selectFile,
+ selectItem,
+ selection
} from '$lib/components/file-list/Selection';
import {
- ListFileItem,
- ListItem,
- ListTrackItem,
- ListTrackSegmentItem,
- ListWaypointItem,
- ListWaypointsItem
+ ListFileItem,
+ ListItem,
+ ListTrackItem,
+ ListTrackSegmentItem,
+ ListWaypointItem,
+ ListWaypointsItem
} from '$lib/components/file-list/FileList';
import type { RoutingControls } from '$lib/components/toolbar/tools/routing/RoutingControls';
import { SplitType } from '$lib/components/toolbar/tools/scissors/Scissors.svelte';
@@ -32,419 +32,438 @@ export const selectFiles = writable<{ [key: string]: (fileId?: string) => void }
export const gpxStatistics: Writable = writable(new GPXStatistics());
export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> =
- writable(undefined);
+ writable(undefined);
export function updateGPXData() {
- let statistics = new GPXStatistics();
- applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
- let stats = getStatistics(fileId);
- if (stats) {
- let first = true;
- items.forEach((item) => {
- if (!(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) || first) {
- statistics.mergeWith(stats.getStatisticsFor(item));
- first = false;
- }
- });
- }
- }, false);
- gpxStatistics.set(statistics);
+ let statistics = new GPXStatistics();
+ applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
+ let stats = getStatistics(fileId);
+ if (stats) {
+ let first = true;
+ items.forEach((item) => {
+ if (!(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) || first) {
+ statistics.mergeWith(stats.getStatisticsFor(item));
+ first = false;
+ }
+ });
+ }
+ }, false);
+ gpxStatistics.set(statistics);
}
let unsubscribes: Map void> = new Map();
selection.subscribe(($selection) => {
- // Maintain up-to-date statistics for the current selection
- updateGPXData();
+ // Maintain up-to-date statistics for the current selection
+ updateGPXData();
- while (unsubscribes.size > 0) {
- let [fileId, unsubscribe] = unsubscribes.entries().next().value;
- unsubscribe();
- unsubscribes.delete(fileId);
- }
+ while (unsubscribes.size > 0) {
+ let [fileId, unsubscribe] = unsubscribes.entries().next().value;
+ unsubscribe();
+ unsubscribes.delete(fileId);
+ }
- $selection.forEach((item) => {
- let fileId = item.getFileId();
- if (!unsubscribes.has(fileId)) {
- let fileObserver = get(fileObservers).get(fileId);
- if (fileObserver) {
- let first = true;
- unsubscribes.set(
- fileId,
- fileObserver.subscribe(() => {
- if (first) first = false;
- else updateGPXData();
- })
- );
- }
- }
- });
+ $selection.forEach((item) => {
+ let fileId = item.getFileId();
+ if (!unsubscribes.has(fileId)) {
+ let fileObserver = get(fileObservers).get(fileId);
+ if (fileObserver) {
+ let first = true;
+ unsubscribes.set(
+ fileId,
+ fileObserver.subscribe(() => {
+ if (first) first = false;
+ else updateGPXData();
+ })
+ );
+ }
+ }
+ });
});
gpxStatistics.subscribe(() => {
- slicedGPXStatistics.set(undefined);
+ slicedGPXStatistics.set(undefined);
});
const targetMapBounds = writable({
- bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
- count: 0,
- total: -1
+ bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
+ count: 0,
+ total: -1
});
derived([targetMapBounds, map], (x) => x).subscribe(([bounds, $map]) => {
- if (
- $map === null ||
- bounds.count !== bounds.total ||
- (bounds.bounds.getSouth() === 90 &&
- bounds.bounds.getWest() === 180 &&
- bounds.bounds.getNorth() === -90 &&
- bounds.bounds.getEast() === -180)
- ) {
- return;
- }
+ if (
+ $map === null ||
+ bounds.count !== bounds.total ||
+ (bounds.bounds.getSouth() === 90 &&
+ bounds.bounds.getWest() === 180 &&
+ bounds.bounds.getNorth() === -90 &&
+ bounds.bounds.getEast() === -180)
+ ) {
+ return;
+ }
- let currentBounds = $map.getBounds();
- if (bounds.count !== get(fileObservers).size && currentBounds) {
- // There are other files on the map
+ let currentBounds = $map.getBounds();
+ if (bounds.count !== get(fileObservers).size && currentBounds) {
+ // There are other files on the map
- if (
- currentBounds.contains(bounds.bounds.getSouthEast()) &&
- currentBounds.contains(bounds.bounds.getNorthWest())
- ) {
- return;
- }
+ if (
+ currentBounds.contains(bounds.bounds.getSouthEast()) &&
+ currentBounds.contains(bounds.bounds.getNorthWest())
+ ) {
+ return;
+ }
- bounds.bounds.extend(currentBounds.getSouthWest());
- bounds.bounds.extend(currentBounds.getNorthEast());
- }
+ bounds.bounds.extend(currentBounds.getSouthWest());
+ bounds.bounds.extend(currentBounds.getNorthEast());
+ }
- $map.fitBounds(bounds.bounds, { padding: 80, linear: true, easing: () => 1 });
+ $map.fitBounds(bounds.bounds, { padding: 80, linear: true, easing: () => 1 });
});
export function initTargetMapBounds(total: number) {
- targetMapBounds.set({
- bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
- count: 0,
- total: total
- });
+ targetMapBounds.set({
+ bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
+ count: 0,
+ total: total
+ });
}
export function updateTargetMapBounds(bounds: { southWest: Coordinates; northEast: Coordinates }) {
- if (
- bounds.southWest.lat == 90 &&
- bounds.southWest.lon == 180 &&
- bounds.northEast.lat == -90 &&
- bounds.northEast.lon == -180
- ) {
- // Avoid update for empty (new) files
- targetMapBounds.update((target) => {
- target.count += 1;
- return target;
- });
- return;
- }
+ if (
+ bounds.southWest.lat == 90 &&
+ bounds.southWest.lon == 180 &&
+ bounds.northEast.lat == -90 &&
+ bounds.northEast.lon == -180
+ ) {
+ // Avoid update for empty (new) files
+ targetMapBounds.update((target) => {
+ target.count += 1;
+ return target;
+ });
+ return;
+ }
- targetMapBounds.update((target) => {
- target.bounds.extend(bounds.southWest);
- target.bounds.extend(bounds.northEast);
- target.count += 1;
- return target;
- });
+ targetMapBounds.update((target) => {
+ target.bounds.extend(bounds.southWest);
+ target.bounds.extend(bounds.northEast);
+ target.count += 1;
+ return target;
+ });
}
-export function flyToBounds(
- bounds: { southWest: Coordinates; northEast: Coordinates },
- map: mapboxgl.Map | null
+export function centerMapOnSelection(
) {
- const mapBoxBounds = new mapboxgl.LngLatBounds([
- [bounds.northEast.lon, bounds.northEast.lat],
- [bounds.southWest.lon, bounds.southWest.lat]
- ]);
- map?.fitBounds(mapBoxBounds, {
- padding: 80
- });
+ let selected = get(selection).getSelected();
+ let bounds = new mapboxgl.LngLatBounds();
+
+ if (selected.find((item) => item instanceof ListWaypointItem)) {
+ applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
+ let file = getFile(fileId);
+ if (file) {
+ items.forEach((item) => {
+ if (item instanceof ListWaypointItem) {
+ let waypoint = file.wpt[item.getWaypointIndex()];
+ if (waypoint) {
+ bounds.extend([waypoint.getLongitude(), waypoint.getLatitude()]);
+ }
+ }
+ });
+ }
+ });
+ } else {
+ let selectionBounds = get(gpxStatistics).global.bounds;
+ bounds.setNorthEast(selectionBounds.northEast);
+ bounds.setSouthWest(selectionBounds.southWest);
+ }
+
+ get(map)?.fitBounds(bounds, {
+ padding: 80,
+ easing: () => 1,
+ maxZoom: 15
+ });
}
export const gpxLayers: Map = new Map();
export const routingControls: Map = new Map();
export enum Tool {
- ROUTING,
- WAYPOINT,
- SCISSORS,
- TIME,
- MERGE,
- EXTRACT,
- REDUCE,
- CLEAN
+ ROUTING,
+ WAYPOINT,
+ SCISSORS,
+ TIME,
+ MERGE,
+ EXTRACT,
+ REDUCE,
+ CLEAN
}
export const currentTool = writable(null);
export const splitAs = writable(SplitType.FILES);
export const streetViewEnabled = writable(false);
export function newGPXFile() {
- const newFileName = get(_)('menu.new_file');
+ const newFileName = get(_)('menu.new_file');
- let file = new GPXFile();
+ let file = new GPXFile();
- let maxNewFileNumber = 0;
- get(fileObservers).forEach((f) => {
- let file = get(f)?.file;
- if (file && file.metadata.name && file.metadata.name.startsWith(newFileName)) {
- let number = parseInt(file.metadata.name.split(' ').pop() ?? '0');
- if (!isNaN(number) && number > maxNewFileNumber) {
- maxNewFileNumber = number;
- }
- }
- });
+ let maxNewFileNumber = 0;
+ get(fileObservers).forEach((f) => {
+ let file = get(f)?.file;
+ if (file && file.metadata.name && file.metadata.name.startsWith(newFileName)) {
+ let number = parseInt(file.metadata.name.split(' ').pop() ?? '0');
+ if (!isNaN(number) && number > maxNewFileNumber) {
+ maxNewFileNumber = number;
+ }
+ }
+ });
- file.metadata.name = `${newFileName} ${maxNewFileNumber + 1}`;
+ file.metadata.name = `${newFileName} ${maxNewFileNumber + 1}`;
- return file;
+ return file;
}
export function createFile() {
- let file = newGPXFile();
+ let file = newGPXFile();
- dbUtils.add(file);
+ dbUtils.add(file);
- selectFileWhenLoaded(file._data.id);
- currentTool.set(Tool.ROUTING);
+ selectFileWhenLoaded(file._data.id);
+ currentTool.set(Tool.ROUTING);
}
export function triggerFileInput() {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = '.gpx';
- input.multiple = true;
- input.className = 'hidden';
- input.onchange = () => {
- if (input.files) {
- loadFiles(input.files);
- }
- };
- input.click();
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.gpx';
+ input.multiple = true;
+ input.className = 'hidden';
+ input.onchange = () => {
+ if (input.files) {
+ loadFiles(input.files);
+ }
+ };
+ input.click();
}
export async function loadFiles(list: FileList | File[]) {
- let files = [];
- for (let i = 0; i < list.length; i++) {
- let file = await loadFile(list[i]);
- if (file) {
- files.push(file);
- }
- }
+ let files = [];
+ for (let i = 0; i < list.length; i++) {
+ let file = await loadFile(list[i]);
+ if (file) {
+ files.push(file);
+ }
+ }
- initTargetMapBounds(list.length);
+ initTargetMapBounds(list.length);
- dbUtils.addMultiple(files);
+ dbUtils.addMultiple(files);
- selectFileWhenLoaded(files[0]._data.id);
+ selectFileWhenLoaded(files[0]._data.id);
}
export async function loadFile(file: File): Promise {
- let result = await new Promise((resolve) => {
- const reader = new FileReader();
- reader.onload = () => {
- let data = reader.result?.toString() ?? null;
- if (data) {
- let gpx = parseGPX(data);
- if (gpx.metadata === undefined) {
- gpx.metadata = { name: file.name.split('.').slice(0, -1).join('.') };
- } else if (gpx.metadata.name === undefined) {
- gpx.metadata['name'] = file.name.split('.').slice(0, -1).join('.');
- }
- resolve(gpx);
- } else {
- resolve(null);
- }
- };
- reader.readAsText(file);
- });
- return result;
+ let result = await new Promise((resolve) => {
+ const reader = new FileReader();
+ reader.onload = () => {
+ let data = reader.result?.toString() ?? null;
+ if (data) {
+ let gpx = parseGPX(data);
+ if (gpx.metadata === undefined) {
+ gpx.metadata = { name: file.name.split('.').slice(0, -1).join('.') };
+ } else if (gpx.metadata.name === undefined) {
+ gpx.metadata['name'] = file.name.split('.').slice(0, -1).join('.');
+ }
+ resolve(gpx);
+ } else {
+ resolve(null);
+ }
+ };
+ reader.readAsText(file);
+ });
+ return result;
}
export function selectFileWhenLoaded(fileId: string) {
- const unsubscribe = fileObservers.subscribe((files) => {
- if (files.has(fileId)) {
- tick().then(() => {
- selectFile(fileId);
- });
- unsubscribe();
- }
- });
+ const unsubscribe = fileObservers.subscribe((files) => {
+ if (files.has(fileId)) {
+ tick().then(() => {
+ selectFile(fileId);
+ });
+ unsubscribe();
+ }
+ });
}
export function updateSelectionFromKey(down: boolean, shift: boolean) {
- let selected = get(selection).getSelected();
- if (selected.length === 0) {
- return;
- }
+ let selected = get(selection).getSelected();
+ if (selected.length === 0) {
+ return;
+ }
- let next: ListItem | undefined = undefined;
- if (selected[0] instanceof ListFileItem) {
- let order = get(fileOrder);
- let limitIndex: number | undefined = undefined;
- selected.forEach((item) => {
- let index = order.indexOf(item.getFileId());
- if (
- limitIndex === undefined ||
- (down && index > limitIndex) ||
- (!down && index < limitIndex)
- ) {
- limitIndex = index;
- }
- });
+ let next: ListItem | undefined = undefined;
+ if (selected[0] instanceof ListFileItem) {
+ let order = get(fileOrder);
+ let limitIndex: number | undefined = undefined;
+ selected.forEach((item) => {
+ let index = order.indexOf(item.getFileId());
+ if (
+ limitIndex === undefined ||
+ (down && index > limitIndex) ||
+ (!down && index < limitIndex)
+ ) {
+ limitIndex = index;
+ }
+ });
- if (limitIndex !== undefined) {
- let nextIndex = down ? limitIndex + 1 : limitIndex - 1;
+ if (limitIndex !== undefined) {
+ let nextIndex = down ? limitIndex + 1 : limitIndex - 1;
- while (true) {
- if (nextIndex < 0) {
- nextIndex = order.length - 1;
- } else if (nextIndex >= order.length) {
- nextIndex = 0;
- }
+ while (true) {
+ if (nextIndex < 0) {
+ nextIndex = order.length - 1;
+ } else if (nextIndex >= order.length) {
+ nextIndex = 0;
+ }
- if (nextIndex === limitIndex) {
- break;
- }
+ if (nextIndex === limitIndex) {
+ break;
+ }
- next = new ListFileItem(order[nextIndex]);
- if (!get(selection).has(next)) {
- break;
- }
+ next = new ListFileItem(order[nextIndex]);
+ if (!get(selection).has(next)) {
+ break;
+ }
- nextIndex += down ? 1 : -1;
- }
- }
- } else if (
- selected[0] instanceof ListTrackItem &&
- selected[selected.length - 1] instanceof ListTrackItem
- ) {
- let fileId = selected[0].getFileId();
- let file = getFile(fileId);
- if (file) {
- let numberOfTracks = file.trk.length;
- let trackIndex = down
- ? selected[selected.length - 1].getTrackIndex()
- : selected[0].getTrackIndex();
- if (down && trackIndex < numberOfTracks - 1) {
- next = new ListTrackItem(fileId, trackIndex + 1);
- } else if (!down && trackIndex > 0) {
- next = new ListTrackItem(fileId, trackIndex - 1);
- }
- }
- } else if (
- selected[0] instanceof ListTrackSegmentItem &&
- selected[selected.length - 1] instanceof ListTrackSegmentItem
- ) {
- let fileId = selected[0].getFileId();
- let file = getFile(fileId);
- if (file) {
- let trackIndex = selected[0].getTrackIndex();
- let numberOfSegments = file.trk[trackIndex].trkseg.length;
- let segmentIndex = down
- ? selected[selected.length - 1].getSegmentIndex()
- : selected[0].getSegmentIndex();
- if (down && segmentIndex < numberOfSegments - 1) {
- next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1);
- } else if (!down && segmentIndex > 0) {
- next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex - 1);
- }
- }
- } else if (
- selected[0] instanceof ListWaypointItem &&
- selected[selected.length - 1] instanceof ListWaypointItem
- ) {
- let fileId = selected[0].getFileId();
- let file = getFile(fileId);
- if (file) {
- let numberOfWaypoints = file.wpt.length;
- let waypointIndex = down
- ? selected[selected.length - 1].getWaypointIndex()
- : selected[0].getWaypointIndex();
- if (down && waypointIndex < numberOfWaypoints - 1) {
- next = new ListWaypointItem(fileId, waypointIndex + 1);
- } else if (!down && waypointIndex > 0) {
- next = new ListWaypointItem(fileId, waypointIndex - 1);
- }
- }
- }
+ nextIndex += down ? 1 : -1;
+ }
+ }
+ } else if (
+ selected[0] instanceof ListTrackItem &&
+ selected[selected.length - 1] instanceof ListTrackItem
+ ) {
+ let fileId = selected[0].getFileId();
+ let file = getFile(fileId);
+ if (file) {
+ let numberOfTracks = file.trk.length;
+ let trackIndex = down
+ ? selected[selected.length - 1].getTrackIndex()
+ : selected[0].getTrackIndex();
+ if (down && trackIndex < numberOfTracks - 1) {
+ next = new ListTrackItem(fileId, trackIndex + 1);
+ } else if (!down && trackIndex > 0) {
+ next = new ListTrackItem(fileId, trackIndex - 1);
+ }
+ }
+ } else if (
+ selected[0] instanceof ListTrackSegmentItem &&
+ selected[selected.length - 1] instanceof ListTrackSegmentItem
+ ) {
+ let fileId = selected[0].getFileId();
+ let file = getFile(fileId);
+ if (file) {
+ let trackIndex = selected[0].getTrackIndex();
+ let numberOfSegments = file.trk[trackIndex].trkseg.length;
+ let segmentIndex = down
+ ? selected[selected.length - 1].getSegmentIndex()
+ : selected[0].getSegmentIndex();
+ if (down && segmentIndex < numberOfSegments - 1) {
+ next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1);
+ } else if (!down && segmentIndex > 0) {
+ next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex - 1);
+ }
+ }
+ } else if (
+ selected[0] instanceof ListWaypointItem &&
+ selected[selected.length - 1] instanceof ListWaypointItem
+ ) {
+ let fileId = selected[0].getFileId();
+ let file = getFile(fileId);
+ if (file) {
+ let numberOfWaypoints = file.wpt.length;
+ let waypointIndex = down
+ ? selected[selected.length - 1].getWaypointIndex()
+ : selected[0].getWaypointIndex();
+ if (down && waypointIndex < numberOfWaypoints - 1) {
+ next = new ListWaypointItem(fileId, waypointIndex + 1);
+ } else if (!down && waypointIndex > 0) {
+ next = new ListWaypointItem(fileId, waypointIndex - 1);
+ }
+ }
+ }
- if (next && (!get(selection).has(next) || !shift)) {
- if (shift) {
- addSelectItem(next);
- } else {
- selectItem(next);
- }
- }
+ if (next && (!get(selection).has(next) || !shift)) {
+ if (shift) {
+ addSelectItem(next);
+ } else {
+ selectItem(next);
+ }
+ }
}
async function exportFiles(fileIds: string[], exclude: string[]) {
- for (let fileId of fileIds) {
- let file = getFile(fileId);
- if (file) {
- exportFile(file, exclude);
- await new Promise((resolve) => setTimeout(resolve, 200));
- }
- }
+ for (let fileId of fileIds) {
+ let file = getFile(fileId);
+ if (file) {
+ exportFile(file, exclude);
+ await new Promise((resolve) => setTimeout(resolve, 200));
+ }
+ }
}
export function exportSelectedFiles(exclude: string[]) {
- let fileIds: string[] = [];
- applyToOrderedSelectedItemsFromFile(async (fileId, level, items) => {
- fileIds.push(fileId);
- });
- exportFiles(fileIds, exclude);
+ let fileIds: string[] = [];
+ applyToOrderedSelectedItemsFromFile(async (fileId, level, items) => {
+ fileIds.push(fileId);
+ });
+ exportFiles(fileIds, exclude);
}
export function exportAllFiles(exclude: string[]) {
- exportFiles(get(fileOrder), exclude);
+ exportFiles(get(fileOrder), exclude);
}
export function exportFile(file: GPXFile, exclude: string[]) {
- let blob = new Blob([buildGPX(file, exclude)], { type: 'application/gpx+xml' });
- let url = URL.createObjectURL(blob);
- let a = document.createElement('a');
- a.href = url;
- a.download = file.metadata.name + '.gpx';
- a.click();
- URL.revokeObjectURL(url);
+ let blob = new Blob([buildGPX(file, exclude)], { type: 'application/gpx+xml' });
+ let url = URL.createObjectURL(blob);
+ let a = document.createElement('a');
+ a.href = url;
+ a.download = file.metadata.name + '.gpx';
+ a.click();
+ URL.revokeObjectURL(url);
}
export const allHidden = writable(false);
export function updateAllHidden() {
- let hidden = true;
- applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
- let file = getFile(fileId);
- if (file) {
- for (let item of items) {
- if (!hidden) {
- return;
- }
+ let hidden = true;
+ applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
+ let file = getFile(fileId);
+ if (file) {
+ for (let item of items) {
+ if (!hidden) {
+ return;
+ }
- if (item instanceof ListFileItem) {
- hidden = hidden && file._data.hidden === true;
- } else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) {
- hidden = hidden && file.trk[item.getTrackIndex()]._data.hidden === true;
- } else if (
- item instanceof ListTrackSegmentItem &&
- item.getTrackIndex() < file.trk.length &&
- item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length
- ) {
- hidden =
- hidden &&
- file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true;
- } else if (item instanceof ListWaypointsItem) {
- hidden = hidden && file._data.hiddenWpt === true;
- } else if (item instanceof ListWaypointItem && item.getWaypointIndex() < file.wpt.length) {
- hidden = hidden && file.wpt[item.getWaypointIndex()]._data.hidden === true;
- }
- }
- }
- });
- allHidden.set(hidden);
+ if (item instanceof ListFileItem) {
+ hidden = hidden && file._data.hidden === true;
+ } else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) {
+ hidden = hidden && file.trk[item.getTrackIndex()]._data.hidden === true;
+ } else if (
+ item instanceof ListTrackSegmentItem &&
+ item.getTrackIndex() < file.trk.length &&
+ item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length
+ ) {
+ hidden =
+ hidden &&
+ file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true;
+ } else if (item instanceof ListWaypointsItem) {
+ hidden = hidden && file._data.hiddenWpt === true;
+ } else if (item instanceof ListWaypointItem && item.getWaypointIndex() < file.wpt.length) {
+ hidden = hidden && file.wpt[item.getWaypointIndex()]._data.hidden === true;
+ }
+ }
+ }
+ });
+ allHidden.set(hidden);
}
selection.subscribe(updateAllHidden);
@@ -452,8 +471,8 @@ export const editMetadata = writable(false);
export const editStyle = writable(false);
export enum ExportState {
- NONE,
- SELECTION,
- ALL
+ NONE,
+ SELECTION,
+ ALL
}
export const exportState = writable(ExportState.NONE);
diff --git a/website/src/locales/en.json b/website/src/locales/en.json
index 258a26b3..f9268ea9 100644
--- a/website/src/locales/en.json
+++ b/website/src/locales/en.json
@@ -1,477 +1,476 @@
{
- "metadata": {
- "home_title": "home",
- "app_title": "the online GPX file editor",
- "embed_title": "the online GPX file editor",
- "help_title": "help",
- "404_title": "page not found",
- "description": "View, edit, and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations."
- },
- "menu": {
- "new": "New",
- "new_file": "New file",
- "new_track": "New track",
- "new_segment": "New segment",
- "open": "Open...",
- "duplicate": "Duplicate",
- "close": "Close",
- "close_all": "Close all",
- "copy": "Copy",
- "paste": "Paste",
- "cut": "Cut",
- "export": "Export...",
- "export_all": "Export all...",
- "export_options": "Export options",
- "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",
- "download_files": "Download files",
- "edit": "Edit",
- "undo": "Undo",
- "redo": "Redo",
- "delete": "Delete",
- "select_all": "Select all",
- "view": "View",
- "elevation_profile": "Elevation profile",
- "vertical_file_view": "Vertical file list",
- "switch_basemap": "Switch to previous basemap",
- "toggle_overlays": "Toggle overlays",
- "toggle_3d": "Toggle 3D",
- "settings": "Settings",
- "distance_units": "Distance units",
- "metric": "Metric",
- "imperial": "Imperial",
- "velocity_units": "Velocity units",
- "temperature_units": "Temperature units",
- "celsius": "Celsius",
- "fahrenheit": "Fahrenheit",
- "language": "Language",
- "mode": "Theme",
- "system": "System",
- "light": "Light",
- "dark": "Dark",
- "street_view_source": "Street view source",
- "mapillary": "Mapillary",
- "google": "Google",
- "layers": "Map layers...",
- "distance_markers": "Distance markers",
- "direction_markers": "Direction arrows",
- "help": "Help",
- "more": "More...",
- "donate": "Donate",
- "ctrl": "Ctrl",
- "click": "Click",
- "drag": "Drag",
- "metadata": {
- "button": "Info...",
- "name": "Name",
- "description": "Description",
- "save": "Save"
- },
- "style": {
- "button": "Appearance...",
- "color": "Color",
- "opacity": "Opacity",
- "width": "Width"
- },
- "hide": "Hide",
- "unhide": "Unhide",
- "open_in": "Open in",
- "fly_to": "Fly to",
- "fly_to_selection": "Fly to selection"
- },
- "toolbar": {
- "routing": {
- "tooltip": "Plan or edit a route",
- "activity": "Activity",
- "use_routing": "Routing",
- "use_routing_tooltip": "Connect anchor points via road network, or in a straight line if disabled",
- "allow_private": "Allow private roads",
- "reverse": {
- "button": "Reverse",
- "tooltip": "Reverse the direction of the route"
- },
- "route_back_to_start": {
- "button": "Back to start",
- "tooltip": "Connect the last point of the route with the starting point"
- },
- "round_trip": {
- "button": "Round trip",
- "tooltip": "Return to the starting point by the same route"
- },
- "start_loop_here": "Start loop here",
- "help_no_file": "Select a trace to use the routing tool, or click on the map to start creating a new route.",
- "help": "Click on the map to add a new anchor point, or drag existing ones to change the route.",
- "activities": {
- "bike": "Bike",
- "racing_bike": "Road bike",
- "gravel_bike": "Gravel bike",
- "mountain_bike": "Mountain bike",
- "foot": "Run/hike",
- "motorcycle": "Motorcycle",
- "water": "Water",
- "railway": "Railway"
- },
- "surface": {
- "unknown": "Unknown",
- "paved": "Paved",
- "unpaved": "Unpaved",
- "asphalt": "Asphalt",
- "concrete": "Concrete",
- "chipseal": "Chipseal",
- "cobblestone": "Cobblestone",
- "unhewn_cobblestone": "Unhewn cobblestone",
- "paving_stones": "Paving stones",
- "stepping_stones": "Stepping stones",
- "sett": "Sett",
- "metal": "Metal",
- "wood": "Wood",
- "compacted": "Compacted gravel",
- "fine_gravel": "Fine gravel",
- "gravel": "Gravel",
- "pebblestone": "Pebblestone",
- "rock": "Rock",
- "dirt": "Dirt",
- "ground": "Ground",
- "earth": "Earth",
- "snow": "Snow",
- "ice": "Ice",
- "salt": "Salt",
- "mud": "Mud",
- "sand": "Sand",
- "woodchips": "Woodchips",
- "grass": "Grass",
- "grass_paver": "Grass paver"
- },
- "error": {
- "from": "The start point is too far from the nearest road",
- "via": "The via point is too far from the nearest road",
- "to": "The end point is too far from the nearest road",
- "timeout": "Route calculation took too long, try adding points closer together"
- }
- },
- "scissors": {
- "tooltip": "Crop or split",
- "crop": "Crop",
- "split_as": "Split the trace into",
- "help_invalid_selection": "Select a trace to crop or split.",
- "help": "Use the slider to crop the trace, or split it by clicking on one of the split markers or on the trace itself."
- },
- "time": {
- "tooltip": "Manage time data",
- "start": "Start",
- "end": "End",
- "total_time": "Moving time",
- "pick_date": "Pick a date",
- "artificial": "Create realistic time data",
- "update": "Update time data",
- "help": "Use the form to set new time data.",
- "help_invalid_selection": "Select a single trace to manage its time data."
- },
- "merge": {
- "merge_traces": "Connect the traces",
- "merge_contents": "Merge the contents and keep the traces disconnected",
- "merge_selection": "Merge selection",
- "tooltip": "Merge items together",
- "help_merge_traces": "Connecting the selected traces will create a single continuous trace.",
- "help_cannot_merge_traces": "Your selection must contain several traces to connect them.",
- "help_merge_contents": "Merging the contents of the selected items will group all the contents inside the first item.",
- "help_cannot_merge_contents": "Your selection must contain several items to merge their contents."
- },
- "extract": {
- "tooltip": "Extract contents to separate items",
- "button": "Extract",
- "help": "Extracting the contents of the selected items will create a separate item for each of their contents.",
- "help_invalid_selection": "Your selection must contain items with multiple traces to extract them."
- },
- "waypoint": {
- "tooltip": "Create and edit points of interest",
- "icon": "Icon",
- "link": "Link",
- "longitude": "Longitude",
- "latitude": "Latitude",
- "create": "Create point of interest",
- "add": "Add point of interest to file",
- "help": "Fill in the form to create a new point of interest, or click on an existing one to edit it. Click on the map to fill the coordinates, or drag points of interest to move them.",
- "help_no_selection": "Select a file to create or edit points of interest."
- },
- "reduce": {
- "tooltip": "Reduce the number of GPS points",
- "tolerance": "Tolerance",
- "number_of_points": "Number of GPS points",
- "button": "Minify",
- "help": "Use the slider to choose the number of GPS points to keep.",
- "help_no_selection": "Select a trace to reduce the number of its GPS points."
- },
- "clean": {
- "tooltip": "Clean GPS points and points of interest with a rectangle selection",
- "delete_trackpoints": "Delete GPS points",
- "delete_waypoints": "Delete points of interest",
- "delete_inside": "Delete inside selection",
- "delete_outside": "Delete outside selection",
- "button": "Delete",
- "help": "Select a rectangle area on the map to remove GPS points and points of interest.",
- "help_no_selection": "Select a trace to clean GPS points and points of interest."
- }
- },
- "layers": {
- "settings": "Layer settings",
- "settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.",
- "selection": "Layer selection",
- "custom_layers": {
- "title": "Custom layers",
- "new": "New custom layer",
- "edit": "Edit custom layer",
- "urls": "URL(s)",
- "url_placeholder": "WMTS, WMS or Mapbox style JSON",
- "max_zoom": "Max zoom",
- "layer_type": "Layer type",
- "basemap": "Basemap",
- "overlay": "Overlay",
- "create": "Create layer",
- "update": "Update layer"
- },
- "opacity": "Overlay opacity",
- "label": {
- "basemaps": "Basemaps",
- "overlays": "Overlays",
- "custom": "Custom",
- "world": "World",
- "countries": "Countries",
- "belgium": "Belgium",
- "bulgaria": "Bulgaria",
- "finland": "Finland",
- "france": "France",
- "new_zealand": "New Zealand",
- "norway": "Norway",
- "spain": "Spain",
- "sweden": "Sweden",
- "switzerland": "Switzerland",
- "united_kingdom": "United Kingdom",
- "united_states": "United States",
- "mapboxOutdoors": "Mapbox Outdoors",
- "mapboxSatellite": "Mapbox Satellite",
- "openStreetMap": "OpenStreetMap",
- "openTopoMap": "OpenTopoMap",
- "openHikingMap": "OpenHikingMap",
- "cyclOSM": "CyclOSM",
- "linz": "LINZ Topo",
- "linzTopo": "LINZ Topo50",
- "swisstopoRaster": "swisstopo Raster",
- "swisstopoVector": "swisstopo Vector",
- "swisstopoSatellite": "swisstopo Satellite",
- "ignBe": "IGN Topo",
- "ignFrPlan": "IGN Plan",
- "ignFrTopo": "IGN Topo",
- "ignFrScan25": "IGN SCAN25",
- "ignFrSatellite": "IGN Satellite",
- "ignEs": "IGN",
- "ordnanceSurvey": "Ordnance Survey",
- "norwayTopo": "Topografisk Norgeskart 4",
- "swedenTopo": "Lantmäteriet Topo",
- "finlandTopo": "Lantmäteriverket Terrängkarta",
- "bgMountains": "BGMountains",
- "usgs": "USGS",
- "cyclOSMlite": "CyclOSM Lite",
- "swisstopoSlope": "swisstopo Slope",
- "swisstopoHiking": "swisstopo Hiking",
- "swisstopoHikingClosures": "swisstopo Hiking Closures",
- "swisstopoCycling": "swisstopo Cycling",
- "swisstopoCyclingClosures": "swisstopo Cycling Closures",
- "swisstopoMountainBike": "swisstopo MTB",
- "swisstopoMountainBikeClosures": "swisstopo MTB Closures",
- "swisstopoSkiTouring": "swisstopo Ski Touring",
- "ignFrCadastre": "IGN Cadastre",
- "ignSlope": "IGN Slope",
- "ignSkiTouring": "IGN Ski Touring",
- "waymarked_trails": "Waymarked Trails",
- "waymarkedTrailsHiking": "Hiking",
- "waymarkedTrailsCycling": "Cycling",
- "waymarkedTrailsMTB": "MTB",
- "waymarkedTrailsSkating": "Skating",
- "waymarkedTrailsHorseRiding": "Horse Riding",
- "waymarkedTrailsWinter": "Winter",
- "points_of_interest": "Points of interest",
- "food": "Food",
- "bakery": "Bakery",
- "food-store": "Food Store",
- "eat-and-drink": "Eat and Drink",
- "amenities": "Amenities",
- "toilets": "Toilets",
- "water": "Water",
- "shower": "Shower",
- "shelter": "Shelter",
- "motorized": "Cars and Motorcycles",
- "fuel-station": "Fuel Station",
- "parking": "Parking",
- "garage": "Garage",
- "barrier": "Barrier",
- "tourism": "Tourism",
- "attraction": "Attraction",
- "viewpoint": "Viewpoint",
- "hotel": "Hotel",
- "campsite": "Campsite",
- "hut": "Hut",
- "picnic": "Picnic Area",
- "summit": "Summit",
- "pass": "Pass",
- "climbing": "Climbing",
- "bicycle": "Bicycle",
- "bicycle-parking": "Bicycle Parking",
- "bicycle-rental": "Bicycle Rental",
- "bicycle-shop": "Bicycle Shop",
- "public-transport": "Public Transport",
- "railway-station": "Railway Station",
- "tram-stop": "Tram Stop",
- "bus-stop": "Bus Stop",
- "ferry": "Ferry"
- },
- "color": {
- "blue": "Blue",
- "bluered": "Blue Red",
- "gray": "Gray",
- "hot": "Hot",
- "purple": "Purple",
- "orange": "Orange"
- }
- },
- "chart": {
- "show_slope": "Show slope data",
- "show_surface": "Show surface data",
- "show_speed": "Show speed data",
- "show_pace": "Show pace data",
- "show_heartrate": "Show heart rate data",
- "show_cadence": "Show cadence data",
- "show_temperature": "Show temperature data",
- "show_power": "Show power data"
- },
- "quantities": {
- "distance": "Distance",
- "elevation": "Elevation",
- "temperature": "Temperature",
- "speed": "Speed",
- "pace": "Pace",
- "heartrate": "Heart rate",
- "cadence": "Cadence",
- "power": "Power",
- "slope": "Slope",
- "surface": "Surface",
- "time": "Time",
- "moving": "Moving",
- "total": "Total"
- },
- "units": {
- "meters": "m",
- "feet": "ft",
- "kilometers": "km",
- "miles": "mi",
- "celsius": "°C",
- "fahrenheit": "°F",
- "kilometers_per_hour": "km/h",
- "miles_per_hour": "mph",
- "minutes_per_kilometer": "min/km",
- "minutes_per_mile": "min/mi",
- "heartrate": "bpm",
- "cadence": "rpm",
- "power": "W"
- },
- "gpx": {
- "file": "File",
- "files": "Files",
- "track": "Track",
- "tracks": "Tracks",
- "segment": "Segment",
- "segments": "Segments",
- "waypoint": "Point of interest",
- "waypoints": "Points of interest",
- "symbol": {
- "alert": "Alert",
- "anchor": "Anchor",
- "bank": "Bank",
- "beach": "Beach",
- "bike_trail": "Bike Trail",
- "binoculars": "Binoculars",
- "bridge": "Bridge",
- "building": "Building",
- "campground": "Campsite",
- "car": "Car",
- "car_repair": "Garage",
- "convenience_store": "Convenience Store",
- "crossing": "Crossing",
- "department_store": "Department Store",
- "drinking_water": "Water",
- "exit": "Exit",
- "lodge": "Hut",
- "lodging": "Accommodation",
- "forest": "Forest",
- "gas_station": "Fuel Station",
- "ground_transportation": "Ground Transportation",
- "hotel": "Hotel",
- "house": "House",
- "information": "Information",
- "park": "Park",
- "parking_area": "Parking",
- "pharmacy": "Pharmacy",
- "picnic_area": "Picnic Area",
- "restaurant": "Restaurant",
- "restricted_area": "Restricted Area",
- "restroom": "Toilets",
- "road": "Road",
- "scenic_area": "Scenic Area",
- "shelter": "Shelter",
- "shopping_center": "Shopping Center",
- "shower": "Shower",
- "summit": "Summit",
- "telephone": "Telephone",
- "tunnel": "Tunnel",
- "water_source": "Water Source"
- }
- },
- "homepage": {
- "website": "Website",
- "home": "Home",
- "app": "App",
- "contact": "Contact",
- "x": "X",
- "facebook": "Facebook",
- "github": "GitHub",
- "crowdin": "Crowdin",
- "email": "Email",
- "contribute": "Contribute",
- "supported_by": "supported by",
- "support_button": "Support gpx.studio on Ko-fi",
- "route_planning": "Route planning",
- "route_planning_description": "An intuitive interface to create itineraries tailored to each sport, based on OpenStreetMap data.",
- "file_processing": "Advanced file processing",
- "file_processing_description": "A suite of tools for performing all common file processing tasks, and which can be applied to multiple files at once.",
- "maps": "Global and local maps",
- "maps_description": "A large collection of basemaps, overlays and points of interest to help you craft your next outdoor adventure, or visualize your latest achievement.",
- "data_visualization": "Data visualization",
- "data_visualization_description": "An interactive elevation profile with detailed statistics to analyze recorded activities and future objectives.",
- "identity": "Free, ad-free and open source",
- "identity_description": "The website is free to use, without ads, and the source code is publicly available on GitHub. This is only possible thanks to the incredible support of the community."
- },
- "embedding": {
- "title": "Create your own map",
- "mapbox_token": "Mapbox access token",
- "file_urls": "File URLs (separated by commas)",
- "drive_ids": "Google Drive file IDs (separated by commas)",
- "basemap": "Basemap",
- "height": "Height",
- "fill_by": "Fill by",
- "none": "None",
- "show_controls": "Show controls",
- "manual_camera": "Manual camera",
- "manual_camera_description": "You can move the map below to adjust the camera position.",
- "latitude": "Latitude",
- "longitude": "Longitude",
- "zoom": "Zoom",
- "pitch": "Pitch",
- "bearing": "Bearing",
- "preview": "Preview",
- "code": "Integration code"
- },
- "webgl2_required": "WebGL 2 is required to display the map.",
- "enable_webgl2": "Learn how to enable WebGL 2 in your browser",
- "page_not_found": "page not found"
-}
+ "metadata": {
+ "home_title": "home",
+ "app_title": "the online GPX file editor",
+ "embed_title": "the online GPX file editor",
+ "help_title": "help",
+ "404_title": "page not found",
+ "description": "View, edit, and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations."
+ },
+ "menu": {
+ "new": "New",
+ "new_file": "New file",
+ "new_track": "New track",
+ "new_segment": "New segment",
+ "open": "Open...",
+ "duplicate": "Duplicate",
+ "close": "Close",
+ "close_all": "Close all",
+ "copy": "Copy",
+ "paste": "Paste",
+ "cut": "Cut",
+ "export": "Export...",
+ "export_all": "Export all...",
+ "export_options": "Export options",
+ "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",
+ "download_files": "Download files",
+ "edit": "Edit",
+ "undo": "Undo",
+ "redo": "Redo",
+ "delete": "Delete",
+ "select_all": "Select all",
+ "view": "View",
+ "elevation_profile": "Elevation profile",
+ "vertical_file_view": "Vertical file list",
+ "switch_basemap": "Switch to previous basemap",
+ "toggle_overlays": "Toggle overlays",
+ "toggle_3d": "Toggle 3D",
+ "settings": "Settings",
+ "distance_units": "Distance units",
+ "metric": "Metric",
+ "imperial": "Imperial",
+ "velocity_units": "Velocity units",
+ "temperature_units": "Temperature units",
+ "celsius": "Celsius",
+ "fahrenheit": "Fahrenheit",
+ "language": "Language",
+ "mode": "Theme",
+ "system": "System",
+ "light": "Light",
+ "dark": "Dark",
+ "street_view_source": "Street view source",
+ "mapillary": "Mapillary",
+ "google": "Google",
+ "layers": "Map layers...",
+ "distance_markers": "Distance markers",
+ "direction_markers": "Direction arrows",
+ "help": "Help",
+ "more": "More...",
+ "donate": "Donate",
+ "ctrl": "Ctrl",
+ "click": "Click",
+ "drag": "Drag",
+ "metadata": {
+ "button": "Info...",
+ "name": "Name",
+ "description": "Description",
+ "save": "Save"
+ },
+ "style": {
+ "button": "Appearance...",
+ "color": "Color",
+ "opacity": "Opacity",
+ "width": "Width"
+ },
+ "hide": "Hide",
+ "unhide": "Unhide",
+ "center": "Center",
+ "open_in": "Open in"
+ },
+ "toolbar": {
+ "routing": {
+ "tooltip": "Plan or edit a route",
+ "activity": "Activity",
+ "use_routing": "Routing",
+ "use_routing_tooltip": "Connect anchor points via road network, or in a straight line if disabled",
+ "allow_private": "Allow private roads",
+ "reverse": {
+ "button": "Reverse",
+ "tooltip": "Reverse the direction of the route"
+ },
+ "route_back_to_start": {
+ "button": "Back to start",
+ "tooltip": "Connect the last point of the route with the starting point"
+ },
+ "round_trip": {
+ "button": "Round trip",
+ "tooltip": "Return to the starting point by the same route"
+ },
+ "start_loop_here": "Start loop here",
+ "help_no_file": "Select a trace to use the routing tool, or click on the map to start creating a new route.",
+ "help": "Click on the map to add a new anchor point, or drag existing ones to change the route.",
+ "activities": {
+ "bike": "Bike",
+ "racing_bike": "Road bike",
+ "gravel_bike": "Gravel bike",
+ "mountain_bike": "Mountain bike",
+ "foot": "Run/hike",
+ "motorcycle": "Motorcycle",
+ "water": "Water",
+ "railway": "Railway"
+ },
+ "surface": {
+ "unknown": "Unknown",
+ "paved": "Paved",
+ "unpaved": "Unpaved",
+ "asphalt": "Asphalt",
+ "concrete": "Concrete",
+ "chipseal": "Chipseal",
+ "cobblestone": "Cobblestone",
+ "unhewn_cobblestone": "Unhewn cobblestone",
+ "paving_stones": "Paving stones",
+ "stepping_stones": "Stepping stones",
+ "sett": "Sett",
+ "metal": "Metal",
+ "wood": "Wood",
+ "compacted": "Compacted gravel",
+ "fine_gravel": "Fine gravel",
+ "gravel": "Gravel",
+ "pebblestone": "Pebblestone",
+ "rock": "Rock",
+ "dirt": "Dirt",
+ "ground": "Ground",
+ "earth": "Earth",
+ "snow": "Snow",
+ "ice": "Ice",
+ "salt": "Salt",
+ "mud": "Mud",
+ "sand": "Sand",
+ "woodchips": "Woodchips",
+ "grass": "Grass",
+ "grass_paver": "Grass paver"
+ },
+ "error": {
+ "from": "The start point is too far from the nearest road",
+ "via": "The via point is too far from the nearest road",
+ "to": "The end point is too far from the nearest road",
+ "timeout": "Route calculation took too long, try adding points closer together"
+ }
+ },
+ "scissors": {
+ "tooltip": "Crop or split",
+ "crop": "Crop",
+ "split_as": "Split the trace into",
+ "help_invalid_selection": "Select a trace to crop or split.",
+ "help": "Use the slider to crop the trace, or split it by clicking on one of the split markers or on the trace itself."
+ },
+ "time": {
+ "tooltip": "Manage time data",
+ "start": "Start",
+ "end": "End",
+ "total_time": "Moving time",
+ "pick_date": "Pick a date",
+ "artificial": "Create realistic time data",
+ "update": "Update time data",
+ "help": "Use the form to set new time data.",
+ "help_invalid_selection": "Select a single trace to manage its time data."
+ },
+ "merge": {
+ "merge_traces": "Connect the traces",
+ "merge_contents": "Merge the contents and keep the traces disconnected",
+ "merge_selection": "Merge selection",
+ "tooltip": "Merge items together",
+ "help_merge_traces": "Connecting the selected traces will create a single continuous trace.",
+ "help_cannot_merge_traces": "Your selection must contain several traces to connect them.",
+ "help_merge_contents": "Merging the contents of the selected items will group all the contents inside the first item.",
+ "help_cannot_merge_contents": "Your selection must contain several items to merge their contents."
+ },
+ "extract": {
+ "tooltip": "Extract contents to separate items",
+ "button": "Extract",
+ "help": "Extracting the contents of the selected items will create a separate item for each of their contents.",
+ "help_invalid_selection": "Your selection must contain items with multiple traces to extract them."
+ },
+ "waypoint": {
+ "tooltip": "Create and edit points of interest",
+ "icon": "Icon",
+ "link": "Link",
+ "longitude": "Longitude",
+ "latitude": "Latitude",
+ "create": "Create point of interest",
+ "add": "Add point of interest to file",
+ "help": "Fill in the form to create a new point of interest, or click on an existing one to edit it. Click on the map to fill the coordinates, or drag points of interest to move them.",
+ "help_no_selection": "Select a file to create or edit points of interest."
+ },
+ "reduce": {
+ "tooltip": "Reduce the number of GPS points",
+ "tolerance": "Tolerance",
+ "number_of_points": "Number of GPS points",
+ "button": "Minify",
+ "help": "Use the slider to choose the number of GPS points to keep.",
+ "help_no_selection": "Select a trace to reduce the number of its GPS points."
+ },
+ "clean": {
+ "tooltip": "Clean GPS points and points of interest with a rectangle selection",
+ "delete_trackpoints": "Delete GPS points",
+ "delete_waypoints": "Delete points of interest",
+ "delete_inside": "Delete inside selection",
+ "delete_outside": "Delete outside selection",
+ "button": "Delete",
+ "help": "Select a rectangle area on the map to remove GPS points and points of interest.",
+ "help_no_selection": "Select a trace to clean GPS points and points of interest."
+ }
+ },
+ "layers": {
+ "settings": "Layer settings",
+ "settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.",
+ "selection": "Layer selection",
+ "custom_layers": {
+ "title": "Custom layers",
+ "new": "New custom layer",
+ "edit": "Edit custom layer",
+ "urls": "URL(s)",
+ "url_placeholder": "WMTS, WMS or Mapbox style JSON",
+ "max_zoom": "Max zoom",
+ "layer_type": "Layer type",
+ "basemap": "Basemap",
+ "overlay": "Overlay",
+ "create": "Create layer",
+ "update": "Update layer"
+ },
+ "opacity": "Overlay opacity",
+ "label": {
+ "basemaps": "Basemaps",
+ "overlays": "Overlays",
+ "custom": "Custom",
+ "world": "World",
+ "countries": "Countries",
+ "belgium": "Belgium",
+ "bulgaria": "Bulgaria",
+ "finland": "Finland",
+ "france": "France",
+ "new_zealand": "New Zealand",
+ "norway": "Norway",
+ "spain": "Spain",
+ "sweden": "Sweden",
+ "switzerland": "Switzerland",
+ "united_kingdom": "United Kingdom",
+ "united_states": "United States",
+ "mapboxOutdoors": "Mapbox Outdoors",
+ "mapboxSatellite": "Mapbox Satellite",
+ "openStreetMap": "OpenStreetMap",
+ "openTopoMap": "OpenTopoMap",
+ "openHikingMap": "OpenHikingMap",
+ "cyclOSM": "CyclOSM",
+ "linz": "LINZ Topo",
+ "linzTopo": "LINZ Topo50",
+ "swisstopoRaster": "swisstopo Raster",
+ "swisstopoVector": "swisstopo Vector",
+ "swisstopoSatellite": "swisstopo Satellite",
+ "ignBe": "IGN Topo",
+ "ignFrPlan": "IGN Plan",
+ "ignFrTopo": "IGN Topo",
+ "ignFrScan25": "IGN SCAN25",
+ "ignFrSatellite": "IGN Satellite",
+ "ignEs": "IGN",
+ "ordnanceSurvey": "Ordnance Survey",
+ "norwayTopo": "Topografisk Norgeskart 4",
+ "swedenTopo": "Lantmäteriet Topo",
+ "finlandTopo": "Lantmäteriverket Terrängkarta",
+ "bgMountains": "BGMountains",
+ "usgs": "USGS",
+ "cyclOSMlite": "CyclOSM Lite",
+ "swisstopoSlope": "swisstopo Slope",
+ "swisstopoHiking": "swisstopo Hiking",
+ "swisstopoHikingClosures": "swisstopo Hiking Closures",
+ "swisstopoCycling": "swisstopo Cycling",
+ "swisstopoCyclingClosures": "swisstopo Cycling Closures",
+ "swisstopoMountainBike": "swisstopo MTB",
+ "swisstopoMountainBikeClosures": "swisstopo MTB Closures",
+ "swisstopoSkiTouring": "swisstopo Ski Touring",
+ "ignFrCadastre": "IGN Cadastre",
+ "ignSlope": "IGN Slope",
+ "ignSkiTouring": "IGN Ski Touring",
+ "waymarked_trails": "Waymarked Trails",
+ "waymarkedTrailsHiking": "Hiking",
+ "waymarkedTrailsCycling": "Cycling",
+ "waymarkedTrailsMTB": "MTB",
+ "waymarkedTrailsSkating": "Skating",
+ "waymarkedTrailsHorseRiding": "Horse Riding",
+ "waymarkedTrailsWinter": "Winter",
+ "points_of_interest": "Points of interest",
+ "food": "Food",
+ "bakery": "Bakery",
+ "food-store": "Food Store",
+ "eat-and-drink": "Eat and Drink",
+ "amenities": "Amenities",
+ "toilets": "Toilets",
+ "water": "Water",
+ "shower": "Shower",
+ "shelter": "Shelter",
+ "motorized": "Cars and Motorcycles",
+ "fuel-station": "Fuel Station",
+ "parking": "Parking",
+ "garage": "Garage",
+ "barrier": "Barrier",
+ "tourism": "Tourism",
+ "attraction": "Attraction",
+ "viewpoint": "Viewpoint",
+ "hotel": "Hotel",
+ "campsite": "Campsite",
+ "hut": "Hut",
+ "picnic": "Picnic Area",
+ "summit": "Summit",
+ "pass": "Pass",
+ "climbing": "Climbing",
+ "bicycle": "Bicycle",
+ "bicycle-parking": "Bicycle Parking",
+ "bicycle-rental": "Bicycle Rental",
+ "bicycle-shop": "Bicycle Shop",
+ "public-transport": "Public Transport",
+ "railway-station": "Railway Station",
+ "tram-stop": "Tram Stop",
+ "bus-stop": "Bus Stop",
+ "ferry": "Ferry"
+ },
+ "color": {
+ "blue": "Blue",
+ "bluered": "Blue Red",
+ "gray": "Gray",
+ "hot": "Hot",
+ "purple": "Purple",
+ "orange": "Orange"
+ }
+ },
+ "chart": {
+ "show_slope": "Show slope data",
+ "show_surface": "Show surface data",
+ "show_speed": "Show speed data",
+ "show_pace": "Show pace data",
+ "show_heartrate": "Show heart rate data",
+ "show_cadence": "Show cadence data",
+ "show_temperature": "Show temperature data",
+ "show_power": "Show power data"
+ },
+ "quantities": {
+ "distance": "Distance",
+ "elevation": "Elevation",
+ "temperature": "Temperature",
+ "speed": "Speed",
+ "pace": "Pace",
+ "heartrate": "Heart rate",
+ "cadence": "Cadence",
+ "power": "Power",
+ "slope": "Slope",
+ "surface": "Surface",
+ "time": "Time",
+ "moving": "Moving",
+ "total": "Total"
+ },
+ "units": {
+ "meters": "m",
+ "feet": "ft",
+ "kilometers": "km",
+ "miles": "mi",
+ "celsius": "°C",
+ "fahrenheit": "°F",
+ "kilometers_per_hour": "km/h",
+ "miles_per_hour": "mph",
+ "minutes_per_kilometer": "min/km",
+ "minutes_per_mile": "min/mi",
+ "heartrate": "bpm",
+ "cadence": "rpm",
+ "power": "W"
+ },
+ "gpx": {
+ "file": "File",
+ "files": "Files",
+ "track": "Track",
+ "tracks": "Tracks",
+ "segment": "Segment",
+ "segments": "Segments",
+ "waypoint": "Point of interest",
+ "waypoints": "Points of interest",
+ "symbol": {
+ "alert": "Alert",
+ "anchor": "Anchor",
+ "bank": "Bank",
+ "beach": "Beach",
+ "bike_trail": "Bike Trail",
+ "binoculars": "Binoculars",
+ "bridge": "Bridge",
+ "building": "Building",
+ "campground": "Campsite",
+ "car": "Car",
+ "car_repair": "Garage",
+ "convenience_store": "Convenience Store",
+ "crossing": "Crossing",
+ "department_store": "Department Store",
+ "drinking_water": "Water",
+ "exit": "Exit",
+ "lodge": "Hut",
+ "lodging": "Accommodation",
+ "forest": "Forest",
+ "gas_station": "Fuel Station",
+ "ground_transportation": "Ground Transportation",
+ "hotel": "Hotel",
+ "house": "House",
+ "information": "Information",
+ "park": "Park",
+ "parking_area": "Parking",
+ "pharmacy": "Pharmacy",
+ "picnic_area": "Picnic Area",
+ "restaurant": "Restaurant",
+ "restricted_area": "Restricted Area",
+ "restroom": "Toilets",
+ "road": "Road",
+ "scenic_area": "Scenic Area",
+ "shelter": "Shelter",
+ "shopping_center": "Shopping Center",
+ "shower": "Shower",
+ "summit": "Summit",
+ "telephone": "Telephone",
+ "tunnel": "Tunnel",
+ "water_source": "Water Source"
+ }
+ },
+ "homepage": {
+ "website": "Website",
+ "home": "Home",
+ "app": "App",
+ "contact": "Contact",
+ "x": "X",
+ "facebook": "Facebook",
+ "github": "GitHub",
+ "crowdin": "Crowdin",
+ "email": "Email",
+ "contribute": "Contribute",
+ "supported_by": "supported by",
+ "support_button": "Support gpx.studio on Ko-fi",
+ "route_planning": "Route planning",
+ "route_planning_description": "An intuitive interface to create itineraries tailored to each sport, based on OpenStreetMap data.",
+ "file_processing": "Advanced file processing",
+ "file_processing_description": "A suite of tools for performing all common file processing tasks, and which can be applied to multiple files at once.",
+ "maps": "Global and local maps",
+ "maps_description": "A large collection of basemaps, overlays and points of interest to help you craft your next outdoor adventure, or visualize your latest achievement.",
+ "data_visualization": "Data visualization",
+ "data_visualization_description": "An interactive elevation profile with detailed statistics to analyze recorded activities and future objectives.",
+ "identity": "Free, ad-free and open source",
+ "identity_description": "The website is free to use, without ads, and the source code is publicly available on GitHub. This is only possible thanks to the incredible support of the community."
+ },
+ "embedding": {
+ "title": "Create your own map",
+ "mapbox_token": "Mapbox access token",
+ "file_urls": "File URLs (separated by commas)",
+ "drive_ids": "Google Drive file IDs (separated by commas)",
+ "basemap": "Basemap",
+ "height": "Height",
+ "fill_by": "Fill by",
+ "none": "None",
+ "show_controls": "Show controls",
+ "manual_camera": "Manual camera",
+ "manual_camera_description": "You can move the map below to adjust the camera position.",
+ "latitude": "Latitude",
+ "longitude": "Longitude",
+ "zoom": "Zoom",
+ "pitch": "Pitch",
+ "bearing": "Bearing",
+ "preview": "Preview",
+ "code": "Integration code"
+ },
+ "webgl2_required": "WebGL 2 is required to display the map.",
+ "enable_webgl2": "Learn how to enable WebGL 2 in your browser",
+ "page_not_found": "page not found"
+}
\ No newline at end of file