improve zoom on selected POIs, rework zoom buttons and add shortcut

This commit is contained in:
vcoppe 2024-08-26 13:59:01 +02:00
parent 5bd7cf6938
commit df2985c999
4 changed files with 849 additions and 829 deletions

View file

@ -56,8 +56,7 @@
editStyle, editStyle,
exportState, exportState,
ExportState, ExportState,
flyToBounds, centerMapOnSelection
gpxStatistics
} from '$lib/stores'; } from '$lib/stores';
import { import {
copied, copied,
@ -225,13 +224,6 @@
<PaintBucket size="16" class="mr-1" /> <PaintBucket size="16" class="mr-1" />
{$_('menu.style.button')} {$_('menu.style.button')}
</Menubar.Item> </Menubar.Item>
<Menubar.Item
disabled={$selection.size === 0}
on:click={() => flyToBounds($gpxStatistics.global.bounds, $map)}
>
<Maximize size="16" class="mr-1" />
{$_('menu.fly_to_selection')}
</Menubar.Item>
<Menubar.Item <Menubar.Item
on:click={() => { on:click={() => {
if ($allHidden) { if ($allHidden) {
@ -257,6 +249,17 @@
{$_('menu.select_all')} {$_('menu.select_all')}
<Shortcut key="A" ctrl={true} /> <Shortcut key="A" ctrl={true} />
</Menubar.Item> </Menubar.Item>
<Menubar.Item
on:click={() => {
if ($selection.size > 0) {
centerMapOnSelection();
}
}}
>
<Maximize size="16" class="mr-1" />
{$_('menu.center')}
<Shortcut key="⏎" ctrl={true} />
</Menubar.Item>
{#if $verticalFileView} {#if $verticalFileView}
<Menubar.Separator /> <Menubar.Separator />
<Menubar.Item on:click={copySelection} disabled={$selection.size === 0}> <Menubar.Item on:click={copySelection} disabled={$selection.size === 0}>
@ -545,6 +548,10 @@
dbUtils.setHiddenToSelection(true); dbUtils.setHiddenToSelection(true);
} }
e.preventDefault(); e.preventDefault();
} else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
if ($selection.size > 0) {
centerMapOnSelection();
}
} else if (e.key === 'F1') { } else if (e.key === 'F1') {
switchBasemaps(); switchBasemaps();
e.preventDefault(); e.preventDefault();

View file

@ -45,9 +45,8 @@
editMetadata, editMetadata,
editStyle, editStyle,
embedding, embedding,
flyToBounds, centerMapOnSelection,
gpxLayers, gpxLayers,
gpxStatistics,
map map
} from '$lib/stores'; } from '$lib/stores';
import { import {
@ -61,7 +60,6 @@
import { _ } from 'svelte-i18n'; import { _ } from 'svelte-i18n';
import MetadataDialog from './MetadataDialog.svelte'; import MetadataDialog from './MetadataDialog.svelte';
import StyleDialog from './StyleDialog.svelte'; import StyleDialog from './StyleDialog.svelte';
import mapboxgl from 'mapbox-gl';
export let node: GPXTreeElement<AnyGPXTreeElement> | Waypoint[] | Waypoint; export let node: GPXTreeElement<AnyGPXTreeElement> | Waypoint[] | Waypoint;
export let item: ListItem; export let item: ListItem;
@ -227,14 +225,6 @@
{$_('menu.style.button')} {$_('menu.style.button')}
</ContextMenu.Item> </ContextMenu.Item>
{/if} {/if}
<ContextMenu.Item
on:click={() => {
flyToBounds($gpxStatistics.global.bounds, $map);
}}
>
<Maximize size="16" class="mr-1" />
{$_('menu.fly_to')}
</ContextMenu.Item>
<ContextMenu.Item <ContextMenu.Item
on:click={() => { on:click={() => {
if ($allHidden) { if ($allHidden) {
@ -294,8 +284,13 @@
{$_('menu.select_all')} {$_('menu.select_all')}
<Shortcut key="A" ctrl={true} /> <Shortcut key="A" ctrl={true} />
</ContextMenu.Item> </ContextMenu.Item>
<ContextMenu.Separator />
{/if} {/if}
<ContextMenu.Item on:click={centerMapOnSelection}>
<Maximize size="16" class="mr-1" />
{$_('menu.center')}
<Shortcut key="⏎" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Separator />
<ContextMenu.Item on:click={dbUtils.duplicateSelection}> <ContextMenu.Item on:click={dbUtils.duplicateSelection}>
<Copy size="16" class="mr-1" /> <Copy size="16" class="mr-1" />
{$_('menu.duplicate')} {$_('menu.duplicate')}

View file

@ -7,19 +7,19 @@ import { _ } from 'svelte-i18n';
import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer'; import type { GPXLayer } from '$lib/components/gpx-layer/GPXLayer';
import { dbUtils, fileObservers, getFile, getStatistics, settings } from './db'; import { dbUtils, fileObservers, getFile, getStatistics, settings } from './db';
import { import {
addSelectItem, addSelectItem,
applyToOrderedSelectedItemsFromFile, applyToOrderedSelectedItemsFromFile,
selectFile, selectFile,
selectItem, selectItem,
selection selection
} from '$lib/components/file-list/Selection'; } from '$lib/components/file-list/Selection';
import { import {
ListFileItem, ListFileItem,
ListItem, ListItem,
ListTrackItem, ListTrackItem,
ListTrackSegmentItem, ListTrackSegmentItem,
ListWaypointItem, ListWaypointItem,
ListWaypointsItem ListWaypointsItem
} from '$lib/components/file-list/FileList'; } from '$lib/components/file-list/FileList';
import type { RoutingControls } from '$lib/components/toolbar/tools/routing/RoutingControls'; import type { RoutingControls } from '$lib/components/toolbar/tools/routing/RoutingControls';
import { SplitType } from '$lib/components/toolbar/tools/scissors/Scissors.svelte'; 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<GPXStatistics> = writable(new GPXStatistics()); export const gpxStatistics: Writable<GPXStatistics> = writable(new GPXStatistics());
export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> = export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> =
writable(undefined); writable(undefined);
export function updateGPXData() { export function updateGPXData() {
let statistics = new GPXStatistics(); let statistics = new GPXStatistics();
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let stats = getStatistics(fileId); let stats = getStatistics(fileId);
if (stats) { if (stats) {
let first = true; let first = true;
items.forEach((item) => { items.forEach((item) => {
if (!(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) || first) { if (!(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) || first) {
statistics.mergeWith(stats.getStatisticsFor(item)); statistics.mergeWith(stats.getStatisticsFor(item));
first = false; first = false;
} }
}); });
} }
}, false); }, false);
gpxStatistics.set(statistics); gpxStatistics.set(statistics);
} }
let unsubscribes: Map<string, () => void> = new Map(); let unsubscribes: Map<string, () => void> = new Map();
selection.subscribe(($selection) => { selection.subscribe(($selection) => {
// Maintain up-to-date statistics for the current selection // Maintain up-to-date statistics for the current selection
updateGPXData(); updateGPXData();
while (unsubscribes.size > 0) { while (unsubscribes.size > 0) {
let [fileId, unsubscribe] = unsubscribes.entries().next().value; let [fileId, unsubscribe] = unsubscribes.entries().next().value;
unsubscribe(); unsubscribe();
unsubscribes.delete(fileId); unsubscribes.delete(fileId);
} }
$selection.forEach((item) => { $selection.forEach((item) => {
let fileId = item.getFileId(); let fileId = item.getFileId();
if (!unsubscribes.has(fileId)) { if (!unsubscribes.has(fileId)) {
let fileObserver = get(fileObservers).get(fileId); let fileObserver = get(fileObservers).get(fileId);
if (fileObserver) { if (fileObserver) {
let first = true; let first = true;
unsubscribes.set( unsubscribes.set(
fileId, fileId,
fileObserver.subscribe(() => { fileObserver.subscribe(() => {
if (first) first = false; if (first) first = false;
else updateGPXData(); else updateGPXData();
}) })
); );
} }
} }
}); });
}); });
gpxStatistics.subscribe(() => { gpxStatistics.subscribe(() => {
slicedGPXStatistics.set(undefined); slicedGPXStatistics.set(undefined);
}); });
const targetMapBounds = writable({ const targetMapBounds = writable({
bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]), bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
count: 0, count: 0,
total: -1 total: -1
}); });
derived([targetMapBounds, map], (x) => x).subscribe(([bounds, $map]) => { derived([targetMapBounds, map], (x) => x).subscribe(([bounds, $map]) => {
if ( if (
$map === null || $map === null ||
bounds.count !== bounds.total || bounds.count !== bounds.total ||
(bounds.bounds.getSouth() === 90 && (bounds.bounds.getSouth() === 90 &&
bounds.bounds.getWest() === 180 && bounds.bounds.getWest() === 180 &&
bounds.bounds.getNorth() === -90 && bounds.bounds.getNorth() === -90 &&
bounds.bounds.getEast() === -180) bounds.bounds.getEast() === -180)
) { ) {
return; return;
} }
let currentBounds = $map.getBounds(); let currentBounds = $map.getBounds();
if (bounds.count !== get(fileObservers).size && currentBounds) { if (bounds.count !== get(fileObservers).size && currentBounds) {
// There are other files on the map // There are other files on the map
if ( if (
currentBounds.contains(bounds.bounds.getSouthEast()) && currentBounds.contains(bounds.bounds.getSouthEast()) &&
currentBounds.contains(bounds.bounds.getNorthWest()) currentBounds.contains(bounds.bounds.getNorthWest())
) { ) {
return; return;
} }
bounds.bounds.extend(currentBounds.getSouthWest()); bounds.bounds.extend(currentBounds.getSouthWest());
bounds.bounds.extend(currentBounds.getNorthEast()); 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) { export function initTargetMapBounds(total: number) {
targetMapBounds.set({ targetMapBounds.set({
bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]), bounds: new mapboxgl.LngLatBounds([180, 90, -180, -90]),
count: 0, count: 0,
total: total total: total
}); });
} }
export function updateTargetMapBounds(bounds: { southWest: Coordinates; northEast: Coordinates }) { export function updateTargetMapBounds(bounds: { southWest: Coordinates; northEast: Coordinates }) {
if ( if (
bounds.southWest.lat == 90 && bounds.southWest.lat == 90 &&
bounds.southWest.lon == 180 && bounds.southWest.lon == 180 &&
bounds.northEast.lat == -90 && bounds.northEast.lat == -90 &&
bounds.northEast.lon == -180 bounds.northEast.lon == -180
) { ) {
// Avoid update for empty (new) files // Avoid update for empty (new) files
targetMapBounds.update((target) => { targetMapBounds.update((target) => {
target.count += 1; target.count += 1;
return target; return target;
}); });
return; return;
} }
targetMapBounds.update((target) => { targetMapBounds.update((target) => {
target.bounds.extend(bounds.southWest); target.bounds.extend(bounds.southWest);
target.bounds.extend(bounds.northEast); target.bounds.extend(bounds.northEast);
target.count += 1; target.count += 1;
return target; return target;
}); });
} }
export function flyToBounds( export function centerMapOnSelection(
bounds: { southWest: Coordinates; northEast: Coordinates },
map: mapboxgl.Map | null
) { ) {
const mapBoxBounds = new mapboxgl.LngLatBounds([ let selected = get(selection).getSelected();
[bounds.northEast.lon, bounds.northEast.lat], let bounds = new mapboxgl.LngLatBounds();
[bounds.southWest.lon, bounds.southWest.lat]
]); if (selected.find((item) => item instanceof ListWaypointItem)) {
map?.fitBounds(mapBoxBounds, { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
padding: 80 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<string, GPXLayer> = new Map(); export const gpxLayers: Map<string, GPXLayer> = new Map();
export const routingControls: Map<string, RoutingControls> = new Map(); export const routingControls: Map<string, RoutingControls> = new Map();
export enum Tool { export enum Tool {
ROUTING, ROUTING,
WAYPOINT, WAYPOINT,
SCISSORS, SCISSORS,
TIME, TIME,
MERGE, MERGE,
EXTRACT, EXTRACT,
REDUCE, REDUCE,
CLEAN CLEAN
} }
export const currentTool = writable<Tool | null>(null); export const currentTool = writable<Tool | null>(null);
export const splitAs = writable(SplitType.FILES); export const splitAs = writable(SplitType.FILES);
export const streetViewEnabled = writable(false); export const streetViewEnabled = writable(false);
export function newGPXFile() { 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; let maxNewFileNumber = 0;
get(fileObservers).forEach((f) => { get(fileObservers).forEach((f) => {
let file = get(f)?.file; let file = get(f)?.file;
if (file && file.metadata.name && file.metadata.name.startsWith(newFileName)) { if (file && file.metadata.name && file.metadata.name.startsWith(newFileName)) {
let number = parseInt(file.metadata.name.split(' ').pop() ?? '0'); let number = parseInt(file.metadata.name.split(' ').pop() ?? '0');
if (!isNaN(number) && number > maxNewFileNumber) { if (!isNaN(number) && number > maxNewFileNumber) {
maxNewFileNumber = number; maxNewFileNumber = number;
} }
} }
}); });
file.metadata.name = `${newFileName} ${maxNewFileNumber + 1}`; file.metadata.name = `${newFileName} ${maxNewFileNumber + 1}`;
return file; return file;
} }
export function createFile() { export function createFile() {
let file = newGPXFile(); let file = newGPXFile();
dbUtils.add(file); dbUtils.add(file);
selectFileWhenLoaded(file._data.id); selectFileWhenLoaded(file._data.id);
currentTool.set(Tool.ROUTING); currentTool.set(Tool.ROUTING);
} }
export function triggerFileInput() { export function triggerFileInput() {
const input = document.createElement('input'); const input = document.createElement('input');
input.type = 'file'; input.type = 'file';
input.accept = '.gpx'; input.accept = '.gpx';
input.multiple = true; input.multiple = true;
input.className = 'hidden'; input.className = 'hidden';
input.onchange = () => { input.onchange = () => {
if (input.files) { if (input.files) {
loadFiles(input.files); loadFiles(input.files);
} }
}; };
input.click(); input.click();
} }
export async function loadFiles(list: FileList | File[]) { export async function loadFiles(list: FileList | File[]) {
let files = []; let files = [];
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
let file = await loadFile(list[i]); let file = await loadFile(list[i]);
if (file) { if (file) {
files.push(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<GPXFile | null> { export async function loadFile(file: File): Promise<GPXFile | null> {
let result = await new Promise<GPXFile | null>((resolve) => { let result = await new Promise<GPXFile | null>((resolve) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => { reader.onload = () => {
let data = reader.result?.toString() ?? null; let data = reader.result?.toString() ?? null;
if (data) { if (data) {
let gpx = parseGPX(data); let gpx = parseGPX(data);
if (gpx.metadata === undefined) { if (gpx.metadata === undefined) {
gpx.metadata = { name: file.name.split('.').slice(0, -1).join('.') }; gpx.metadata = { name: file.name.split('.').slice(0, -1).join('.') };
} else if (gpx.metadata.name === undefined) { } else if (gpx.metadata.name === undefined) {
gpx.metadata['name'] = file.name.split('.').slice(0, -1).join('.'); gpx.metadata['name'] = file.name.split('.').slice(0, -1).join('.');
} }
resolve(gpx); resolve(gpx);
} else { } else {
resolve(null); resolve(null);
} }
}; };
reader.readAsText(file); reader.readAsText(file);
}); });
return result; return result;
} }
export function selectFileWhenLoaded(fileId: string) { export function selectFileWhenLoaded(fileId: string) {
const unsubscribe = fileObservers.subscribe((files) => { const unsubscribe = fileObservers.subscribe((files) => {
if (files.has(fileId)) { if (files.has(fileId)) {
tick().then(() => { tick().then(() => {
selectFile(fileId); selectFile(fileId);
}); });
unsubscribe(); unsubscribe();
} }
}); });
} }
export function updateSelectionFromKey(down: boolean, shift: boolean) { export function updateSelectionFromKey(down: boolean, shift: boolean) {
let selected = get(selection).getSelected(); let selected = get(selection).getSelected();
if (selected.length === 0) { if (selected.length === 0) {
return; return;
} }
let next: ListItem | undefined = undefined; let next: ListItem | undefined = undefined;
if (selected[0] instanceof ListFileItem) { if (selected[0] instanceof ListFileItem) {
let order = get(fileOrder); let order = get(fileOrder);
let limitIndex: number | undefined = undefined; let limitIndex: number | undefined = undefined;
selected.forEach((item) => { selected.forEach((item) => {
let index = order.indexOf(item.getFileId()); let index = order.indexOf(item.getFileId());
if ( if (
limitIndex === undefined || limitIndex === undefined ||
(down && index > limitIndex) || (down && index > limitIndex) ||
(!down && index < limitIndex) (!down && index < limitIndex)
) { ) {
limitIndex = index; limitIndex = index;
} }
}); });
if (limitIndex !== undefined) { if (limitIndex !== undefined) {
let nextIndex = down ? limitIndex + 1 : limitIndex - 1; let nextIndex = down ? limitIndex + 1 : limitIndex - 1;
while (true) { while (true) {
if (nextIndex < 0) { if (nextIndex < 0) {
nextIndex = order.length - 1; nextIndex = order.length - 1;
} else if (nextIndex >= order.length) { } else if (nextIndex >= order.length) {
nextIndex = 0; nextIndex = 0;
} }
if (nextIndex === limitIndex) { if (nextIndex === limitIndex) {
break; break;
} }
next = new ListFileItem(order[nextIndex]); next = new ListFileItem(order[nextIndex]);
if (!get(selection).has(next)) { if (!get(selection).has(next)) {
break; break;
} }
nextIndex += down ? 1 : -1; nextIndex += down ? 1 : -1;
} }
} }
} else if ( } else if (
selected[0] instanceof ListTrackItem && selected[0] instanceof ListTrackItem &&
selected[selected.length - 1] instanceof ListTrackItem selected[selected.length - 1] instanceof ListTrackItem
) { ) {
let fileId = selected[0].getFileId(); let fileId = selected[0].getFileId();
let file = getFile(fileId); let file = getFile(fileId);
if (file) { if (file) {
let numberOfTracks = file.trk.length; let numberOfTracks = file.trk.length;
let trackIndex = down let trackIndex = down
? selected[selected.length - 1].getTrackIndex() ? selected[selected.length - 1].getTrackIndex()
: selected[0].getTrackIndex(); : selected[0].getTrackIndex();
if (down && trackIndex < numberOfTracks - 1) { if (down && trackIndex < numberOfTracks - 1) {
next = new ListTrackItem(fileId, trackIndex + 1); next = new ListTrackItem(fileId, trackIndex + 1);
} else if (!down && trackIndex > 0) { } else if (!down && trackIndex > 0) {
next = new ListTrackItem(fileId, trackIndex - 1); next = new ListTrackItem(fileId, trackIndex - 1);
} }
} }
} else if ( } else if (
selected[0] instanceof ListTrackSegmentItem && selected[0] instanceof ListTrackSegmentItem &&
selected[selected.length - 1] instanceof ListTrackSegmentItem selected[selected.length - 1] instanceof ListTrackSegmentItem
) { ) {
let fileId = selected[0].getFileId(); let fileId = selected[0].getFileId();
let file = getFile(fileId); let file = getFile(fileId);
if (file) { if (file) {
let trackIndex = selected[0].getTrackIndex(); let trackIndex = selected[0].getTrackIndex();
let numberOfSegments = file.trk[trackIndex].trkseg.length; let numberOfSegments = file.trk[trackIndex].trkseg.length;
let segmentIndex = down let segmentIndex = down
? selected[selected.length - 1].getSegmentIndex() ? selected[selected.length - 1].getSegmentIndex()
: selected[0].getSegmentIndex(); : selected[0].getSegmentIndex();
if (down && segmentIndex < numberOfSegments - 1) { if (down && segmentIndex < numberOfSegments - 1) {
next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1); next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex + 1);
} else if (!down && segmentIndex > 0) { } else if (!down && segmentIndex > 0) {
next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex - 1); next = new ListTrackSegmentItem(fileId, trackIndex, segmentIndex - 1);
} }
} }
} else if ( } else if (
selected[0] instanceof ListWaypointItem && selected[0] instanceof ListWaypointItem &&
selected[selected.length - 1] instanceof ListWaypointItem selected[selected.length - 1] instanceof ListWaypointItem
) { ) {
let fileId = selected[0].getFileId(); let fileId = selected[0].getFileId();
let file = getFile(fileId); let file = getFile(fileId);
if (file) { if (file) {
let numberOfWaypoints = file.wpt.length; let numberOfWaypoints = file.wpt.length;
let waypointIndex = down let waypointIndex = down
? selected[selected.length - 1].getWaypointIndex() ? selected[selected.length - 1].getWaypointIndex()
: selected[0].getWaypointIndex(); : selected[0].getWaypointIndex();
if (down && waypointIndex < numberOfWaypoints - 1) { if (down && waypointIndex < numberOfWaypoints - 1) {
next = new ListWaypointItem(fileId, waypointIndex + 1); next = new ListWaypointItem(fileId, waypointIndex + 1);
} else if (!down && waypointIndex > 0) { } else if (!down && waypointIndex > 0) {
next = new ListWaypointItem(fileId, waypointIndex - 1); next = new ListWaypointItem(fileId, waypointIndex - 1);
} }
} }
} }
if (next && (!get(selection).has(next) || !shift)) { if (next && (!get(selection).has(next) || !shift)) {
if (shift) { if (shift) {
addSelectItem(next); addSelectItem(next);
} else { } else {
selectItem(next); selectItem(next);
} }
} }
} }
async function exportFiles(fileIds: string[], exclude: string[]) { async function exportFiles(fileIds: string[], exclude: string[]) {
for (let fileId of fileIds) { for (let fileId of fileIds) {
let file = getFile(fileId); let file = getFile(fileId);
if (file) { if (file) {
exportFile(file, exclude); exportFile(file, exclude);
await new Promise((resolve) => setTimeout(resolve, 200)); await new Promise((resolve) => setTimeout(resolve, 200));
} }
} }
} }
export function exportSelectedFiles(exclude: string[]) { export function exportSelectedFiles(exclude: string[]) {
let fileIds: string[] = []; let fileIds: string[] = [];
applyToOrderedSelectedItemsFromFile(async (fileId, level, items) => { applyToOrderedSelectedItemsFromFile(async (fileId, level, items) => {
fileIds.push(fileId); fileIds.push(fileId);
}); });
exportFiles(fileIds, exclude); exportFiles(fileIds, exclude);
} }
export function exportAllFiles(exclude: string[]) { export function exportAllFiles(exclude: string[]) {
exportFiles(get(fileOrder), exclude); exportFiles(get(fileOrder), exclude);
} }
export function exportFile(file: GPXFile, exclude: string[]) { export function exportFile(file: GPXFile, exclude: string[]) {
let blob = new Blob([buildGPX(file, exclude)], { type: 'application/gpx+xml' }); let blob = new Blob([buildGPX(file, exclude)], { type: 'application/gpx+xml' });
let url = URL.createObjectURL(blob); let url = URL.createObjectURL(blob);
let a = document.createElement('a'); let a = document.createElement('a');
a.href = url; a.href = url;
a.download = file.metadata.name + '.gpx'; a.download = file.metadata.name + '.gpx';
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
export const allHidden = writable(false); export const allHidden = writable(false);
export function updateAllHidden() { export function updateAllHidden() {
let hidden = true; let hidden = true;
applyToOrderedSelectedItemsFromFile((fileId, level, items) => { applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
let file = getFile(fileId); let file = getFile(fileId);
if (file) { if (file) {
for (let item of items) { for (let item of items) {
if (!hidden) { if (!hidden) {
return; return;
} }
if (item instanceof ListFileItem) { if (item instanceof ListFileItem) {
hidden = hidden && file._data.hidden === true; hidden = hidden && file._data.hidden === true;
} else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) { } else if (item instanceof ListTrackItem && item.getTrackIndex() < file.trk.length) {
hidden = hidden && file.trk[item.getTrackIndex()]._data.hidden === true; hidden = hidden && file.trk[item.getTrackIndex()]._data.hidden === true;
} else if ( } else if (
item instanceof ListTrackSegmentItem && item instanceof ListTrackSegmentItem &&
item.getTrackIndex() < file.trk.length && item.getTrackIndex() < file.trk.length &&
item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length item.getSegmentIndex() < file.trk[item.getTrackIndex()].trkseg.length
) { ) {
hidden = hidden =
hidden && hidden &&
file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true; file.trk[item.getTrackIndex()].trkseg[item.getSegmentIndex()]._data.hidden === true;
} else if (item instanceof ListWaypointsItem) { } else if (item instanceof ListWaypointsItem) {
hidden = hidden && file._data.hiddenWpt === true; hidden = hidden && file._data.hiddenWpt === true;
} else if (item instanceof ListWaypointItem && item.getWaypointIndex() < file.wpt.length) { } else if (item instanceof ListWaypointItem && item.getWaypointIndex() < file.wpt.length) {
hidden = hidden && file.wpt[item.getWaypointIndex()]._data.hidden === true; hidden = hidden && file.wpt[item.getWaypointIndex()]._data.hidden === true;
} }
} }
} }
}); });
allHidden.set(hidden); allHidden.set(hidden);
} }
selection.subscribe(updateAllHidden); selection.subscribe(updateAllHidden);
@ -452,8 +471,8 @@ export const editMetadata = writable(false);
export const editStyle = writable(false); export const editStyle = writable(false);
export enum ExportState { export enum ExportState {
NONE, NONE,
SELECTION, SELECTION,
ALL ALL
} }
export const exportState = writable<ExportState>(ExportState.NONE); export const exportState = writable<ExportState>(ExportState.NONE);

View file

@ -1,477 +1,476 @@
{ {
"metadata": { "metadata": {
"home_title": "home", "home_title": "home",
"app_title": "the online GPX file editor", "app_title": "the online GPX file editor",
"embed_title": "the online GPX file editor", "embed_title": "the online GPX file editor",
"help_title": "help", "help_title": "help",
"404_title": "page not found", "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." "description": "View, edit, and create GPX files online with advanced route planning capabilities and file processing tools, beautiful maps and detailed data visualizations."
}, },
"menu": { "menu": {
"new": "New", "new": "New",
"new_file": "New file", "new_file": "New file",
"new_track": "New track", "new_track": "New track",
"new_segment": "New segment", "new_segment": "New segment",
"open": "Open...", "open": "Open...",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"close": "Close", "close": "Close",
"close_all": "Close all", "close_all": "Close all",
"copy": "Copy", "copy": "Copy",
"paste": "Paste", "paste": "Paste",
"cut": "Cut", "cut": "Cut",
"export": "Export...", "export": "Export...",
"export_all": "Export all...", "export_all": "Export all...",
"export_options": "Export options", "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_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", "support_button": "Help keep the website free",
"download_file": "Download file", "download_file": "Download file",
"download_files": "Download files", "download_files": "Download files",
"edit": "Edit", "edit": "Edit",
"undo": "Undo", "undo": "Undo",
"redo": "Redo", "redo": "Redo",
"delete": "Delete", "delete": "Delete",
"select_all": "Select all", "select_all": "Select all",
"view": "View", "view": "View",
"elevation_profile": "Elevation profile", "elevation_profile": "Elevation profile",
"vertical_file_view": "Vertical file list", "vertical_file_view": "Vertical file list",
"switch_basemap": "Switch to previous basemap", "switch_basemap": "Switch to previous basemap",
"toggle_overlays": "Toggle overlays", "toggle_overlays": "Toggle overlays",
"toggle_3d": "Toggle 3D", "toggle_3d": "Toggle 3D",
"settings": "Settings", "settings": "Settings",
"distance_units": "Distance units", "distance_units": "Distance units",
"metric": "Metric", "metric": "Metric",
"imperial": "Imperial", "imperial": "Imperial",
"velocity_units": "Velocity units", "velocity_units": "Velocity units",
"temperature_units": "Temperature units", "temperature_units": "Temperature units",
"celsius": "Celsius", "celsius": "Celsius",
"fahrenheit": "Fahrenheit", "fahrenheit": "Fahrenheit",
"language": "Language", "language": "Language",
"mode": "Theme", "mode": "Theme",
"system": "System", "system": "System",
"light": "Light", "light": "Light",
"dark": "Dark", "dark": "Dark",
"street_view_source": "Street view source", "street_view_source": "Street view source",
"mapillary": "Mapillary", "mapillary": "Mapillary",
"google": "Google", "google": "Google",
"layers": "Map layers...", "layers": "Map layers...",
"distance_markers": "Distance markers", "distance_markers": "Distance markers",
"direction_markers": "Direction arrows", "direction_markers": "Direction arrows",
"help": "Help", "help": "Help",
"more": "More...", "more": "More...",
"donate": "Donate", "donate": "Donate",
"ctrl": "Ctrl", "ctrl": "Ctrl",
"click": "Click", "click": "Click",
"drag": "Drag", "drag": "Drag",
"metadata": { "metadata": {
"button": "Info...", "button": "Info...",
"name": "Name", "name": "Name",
"description": "Description", "description": "Description",
"save": "Save" "save": "Save"
}, },
"style": { "style": {
"button": "Appearance...", "button": "Appearance...",
"color": "Color", "color": "Color",
"opacity": "Opacity", "opacity": "Opacity",
"width": "Width" "width": "Width"
}, },
"hide": "Hide", "hide": "Hide",
"unhide": "Unhide", "unhide": "Unhide",
"open_in": "Open in", "center": "Center",
"fly_to": "Fly to", "open_in": "Open in"
"fly_to_selection": "Fly to selection" },
}, "toolbar": {
"toolbar": { "routing": {
"routing": { "tooltip": "Plan or edit a route",
"tooltip": "Plan or edit a route", "activity": "Activity",
"activity": "Activity", "use_routing": "Routing",
"use_routing": "Routing", "use_routing_tooltip": "Connect anchor points via road network, or in a straight line if disabled",
"use_routing_tooltip": "Connect anchor points via road network, or in a straight line if disabled", "allow_private": "Allow private roads",
"allow_private": "Allow private roads", "reverse": {
"reverse": { "button": "Reverse",
"button": "Reverse", "tooltip": "Reverse the direction of the route"
"tooltip": "Reverse the direction of the route" },
}, "route_back_to_start": {
"route_back_to_start": { "button": "Back to start",
"button": "Back to start", "tooltip": "Connect the last point of the route with the starting point"
"tooltip": "Connect the last point of the route with the starting point" },
}, "round_trip": {
"round_trip": { "button": "Round trip",
"button": "Round trip", "tooltip": "Return to the starting point by the same route"
"tooltip": "Return to the starting point by the same route" },
}, "start_loop_here": "Start loop here",
"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_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.",
"help": "Click on the map to add a new anchor point, or drag existing ones to change the route.", "activities": {
"activities": { "bike": "Bike",
"bike": "Bike", "racing_bike": "Road bike",
"racing_bike": "Road bike", "gravel_bike": "Gravel bike",
"gravel_bike": "Gravel bike", "mountain_bike": "Mountain bike",
"mountain_bike": "Mountain bike", "foot": "Run/hike",
"foot": "Run/hike", "motorcycle": "Motorcycle",
"motorcycle": "Motorcycle", "water": "Water",
"water": "Water", "railway": "Railway"
"railway": "Railway" },
}, "surface": {
"surface": { "unknown": "Unknown",
"unknown": "Unknown", "paved": "Paved",
"paved": "Paved", "unpaved": "Unpaved",
"unpaved": "Unpaved", "asphalt": "Asphalt",
"asphalt": "Asphalt", "concrete": "Concrete",
"concrete": "Concrete", "chipseal": "Chipseal",
"chipseal": "Chipseal", "cobblestone": "Cobblestone",
"cobblestone": "Cobblestone", "unhewn_cobblestone": "Unhewn cobblestone",
"unhewn_cobblestone": "Unhewn cobblestone", "paving_stones": "Paving stones",
"paving_stones": "Paving stones", "stepping_stones": "Stepping stones",
"stepping_stones": "Stepping stones", "sett": "Sett",
"sett": "Sett", "metal": "Metal",
"metal": "Metal", "wood": "Wood",
"wood": "Wood", "compacted": "Compacted gravel",
"compacted": "Compacted gravel", "fine_gravel": "Fine gravel",
"fine_gravel": "Fine gravel", "gravel": "Gravel",
"gravel": "Gravel", "pebblestone": "Pebblestone",
"pebblestone": "Pebblestone", "rock": "Rock",
"rock": "Rock", "dirt": "Dirt",
"dirt": "Dirt", "ground": "Ground",
"ground": "Ground", "earth": "Earth",
"earth": "Earth", "snow": "Snow",
"snow": "Snow", "ice": "Ice",
"ice": "Ice", "salt": "Salt",
"salt": "Salt", "mud": "Mud",
"mud": "Mud", "sand": "Sand",
"sand": "Sand", "woodchips": "Woodchips",
"woodchips": "Woodchips", "grass": "Grass",
"grass": "Grass", "grass_paver": "Grass paver"
"grass_paver": "Grass paver" },
}, "error": {
"error": { "from": "The start point is too far from the nearest road",
"from": "The start point is too far from the nearest road", "via": "The via 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",
"to": "The end point is too far from the nearest road", "timeout": "Route calculation took too long, try adding points closer together"
"timeout": "Route calculation took too long, try adding points closer together" }
} },
}, "scissors": {
"scissors": { "tooltip": "Crop or split",
"tooltip": "Crop or split", "crop": "Crop",
"crop": "Crop", "split_as": "Split the trace into",
"split_as": "Split the trace into", "help_invalid_selection": "Select a trace to crop or split.",
"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."
"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": {
"time": { "tooltip": "Manage time data",
"tooltip": "Manage time data", "start": "Start",
"start": "Start", "end": "End",
"end": "End", "total_time": "Moving time",
"total_time": "Moving time", "pick_date": "Pick a date",
"pick_date": "Pick a date", "artificial": "Create realistic time data",
"artificial": "Create realistic time data", "update": "Update time data",
"update": "Update time data", "help": "Use the form to set new time data.",
"help": "Use the form to set new time data.", "help_invalid_selection": "Select a single trace to manage its time data."
"help_invalid_selection": "Select a single trace to manage its time data." },
}, "merge": {
"merge": { "merge_traces": "Connect the traces",
"merge_traces": "Connect the traces", "merge_contents": "Merge the contents and keep the traces disconnected",
"merge_contents": "Merge the contents and keep the traces disconnected", "merge_selection": "Merge selection",
"merge_selection": "Merge selection", "tooltip": "Merge items together",
"tooltip": "Merge items together", "help_merge_traces": "Connecting the selected traces will create a single continuous trace.",
"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_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_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."
"help_cannot_merge_contents": "Your selection must contain several items to merge their contents." },
}, "extract": {
"extract": { "tooltip": "Extract contents to separate items",
"tooltip": "Extract contents to separate items", "button": "Extract",
"button": "Extract", "help": "Extracting the contents of the selected items will create a separate item for each of their contents.",
"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."
"help_invalid_selection": "Your selection must contain items with multiple traces to extract them." },
}, "waypoint": {
"waypoint": { "tooltip": "Create and edit points of interest",
"tooltip": "Create and edit points of interest", "icon": "Icon",
"icon": "Icon", "link": "Link",
"link": "Link", "longitude": "Longitude",
"longitude": "Longitude", "latitude": "Latitude",
"latitude": "Latitude", "create": "Create point of interest",
"create": "Create point of interest", "add": "Add point of interest to file",
"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": "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."
"help_no_selection": "Select a file to create or edit points of interest." },
}, "reduce": {
"reduce": { "tooltip": "Reduce the number of GPS points",
"tooltip": "Reduce the number of GPS points", "tolerance": "Tolerance",
"tolerance": "Tolerance", "number_of_points": "Number of GPS points",
"number_of_points": "Number of GPS points", "button": "Minify",
"button": "Minify", "help": "Use the slider to choose the number of GPS points to keep.",
"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."
"help_no_selection": "Select a trace to reduce the number of its GPS points." },
}, "clean": {
"clean": { "tooltip": "Clean GPS points and points of interest with a rectangle selection",
"tooltip": "Clean GPS points and points of interest with a rectangle selection", "delete_trackpoints": "Delete GPS points",
"delete_trackpoints": "Delete GPS points", "delete_waypoints": "Delete points of interest",
"delete_waypoints": "Delete points of interest", "delete_inside": "Delete inside selection",
"delete_inside": "Delete inside selection", "delete_outside": "Delete outside selection",
"delete_outside": "Delete outside selection", "button": "Delete",
"button": "Delete", "help": "Select a rectangle area on the map to remove GPS points and points of interest.",
"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."
"help_no_selection": "Select a trace to clean GPS points and points of interest." }
} },
}, "layers": {
"layers": { "settings": "Layer settings",
"settings": "Layer settings", "settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.",
"settings_help": "Select the map layers you want to show in the interface, add custom ones, and adjust their settings.", "selection": "Layer selection",
"selection": "Layer selection", "custom_layers": {
"custom_layers": { "title": "Custom layers",
"title": "Custom layers", "new": "New custom layer",
"new": "New custom layer", "edit": "Edit custom layer",
"edit": "Edit custom layer", "urls": "URL(s)",
"urls": "URL(s)", "url_placeholder": "WMTS, WMS or Mapbox style JSON",
"url_placeholder": "WMTS, WMS or Mapbox style JSON", "max_zoom": "Max zoom",
"max_zoom": "Max zoom", "layer_type": "Layer type",
"layer_type": "Layer type", "basemap": "Basemap",
"basemap": "Basemap", "overlay": "Overlay",
"overlay": "Overlay", "create": "Create layer",
"create": "Create layer", "update": "Update layer"
"update": "Update layer" },
}, "opacity": "Overlay opacity",
"opacity": "Overlay opacity", "label": {
"label": { "basemaps": "Basemaps",
"basemaps": "Basemaps", "overlays": "Overlays",
"overlays": "Overlays", "custom": "Custom",
"custom": "Custom", "world": "World",
"world": "World", "countries": "Countries",
"countries": "Countries", "belgium": "Belgium",
"belgium": "Belgium", "bulgaria": "Bulgaria",
"bulgaria": "Bulgaria", "finland": "Finland",
"finland": "Finland", "france": "France",
"france": "France", "new_zealand": "New Zealand",
"new_zealand": "New Zealand", "norway": "Norway",
"norway": "Norway", "spain": "Spain",
"spain": "Spain", "sweden": "Sweden",
"sweden": "Sweden", "switzerland": "Switzerland",
"switzerland": "Switzerland", "united_kingdom": "United Kingdom",
"united_kingdom": "United Kingdom", "united_states": "United States",
"united_states": "United States", "mapboxOutdoors": "Mapbox Outdoors",
"mapboxOutdoors": "Mapbox Outdoors", "mapboxSatellite": "Mapbox Satellite",
"mapboxSatellite": "Mapbox Satellite", "openStreetMap": "OpenStreetMap",
"openStreetMap": "OpenStreetMap", "openTopoMap": "OpenTopoMap",
"openTopoMap": "OpenTopoMap", "openHikingMap": "OpenHikingMap",
"openHikingMap": "OpenHikingMap", "cyclOSM": "CyclOSM",
"cyclOSM": "CyclOSM", "linz": "LINZ Topo",
"linz": "LINZ Topo", "linzTopo": "LINZ Topo50",
"linzTopo": "LINZ Topo50", "swisstopoRaster": "swisstopo Raster",
"swisstopoRaster": "swisstopo Raster", "swisstopoVector": "swisstopo Vector",
"swisstopoVector": "swisstopo Vector", "swisstopoSatellite": "swisstopo Satellite",
"swisstopoSatellite": "swisstopo Satellite", "ignBe": "IGN Topo",
"ignBe": "IGN Topo", "ignFrPlan": "IGN Plan",
"ignFrPlan": "IGN Plan", "ignFrTopo": "IGN Topo",
"ignFrTopo": "IGN Topo", "ignFrScan25": "IGN SCAN25",
"ignFrScan25": "IGN SCAN25", "ignFrSatellite": "IGN Satellite",
"ignFrSatellite": "IGN Satellite", "ignEs": "IGN",
"ignEs": "IGN", "ordnanceSurvey": "Ordnance Survey",
"ordnanceSurvey": "Ordnance Survey", "norwayTopo": "Topografisk Norgeskart 4",
"norwayTopo": "Topografisk Norgeskart 4", "swedenTopo": "Lantmäteriet Topo",
"swedenTopo": "Lantmäteriet Topo", "finlandTopo": "Lantmäteriverket Terrängkarta",
"finlandTopo": "Lantmäteriverket Terrängkarta", "bgMountains": "BGMountains",
"bgMountains": "BGMountains", "usgs": "USGS",
"usgs": "USGS", "cyclOSMlite": "CyclOSM Lite",
"cyclOSMlite": "CyclOSM Lite", "swisstopoSlope": "swisstopo Slope",
"swisstopoSlope": "swisstopo Slope", "swisstopoHiking": "swisstopo Hiking",
"swisstopoHiking": "swisstopo Hiking", "swisstopoHikingClosures": "swisstopo Hiking Closures",
"swisstopoHikingClosures": "swisstopo Hiking Closures", "swisstopoCycling": "swisstopo Cycling",
"swisstopoCycling": "swisstopo Cycling", "swisstopoCyclingClosures": "swisstopo Cycling Closures",
"swisstopoCyclingClosures": "swisstopo Cycling Closures", "swisstopoMountainBike": "swisstopo MTB",
"swisstopoMountainBike": "swisstopo MTB", "swisstopoMountainBikeClosures": "swisstopo MTB Closures",
"swisstopoMountainBikeClosures": "swisstopo MTB Closures", "swisstopoSkiTouring": "swisstopo Ski Touring",
"swisstopoSkiTouring": "swisstopo Ski Touring", "ignFrCadastre": "IGN Cadastre",
"ignFrCadastre": "IGN Cadastre", "ignSlope": "IGN Slope",
"ignSlope": "IGN Slope", "ignSkiTouring": "IGN Ski Touring",
"ignSkiTouring": "IGN Ski Touring", "waymarked_trails": "Waymarked Trails",
"waymarked_trails": "Waymarked Trails", "waymarkedTrailsHiking": "Hiking",
"waymarkedTrailsHiking": "Hiking", "waymarkedTrailsCycling": "Cycling",
"waymarkedTrailsCycling": "Cycling", "waymarkedTrailsMTB": "MTB",
"waymarkedTrailsMTB": "MTB", "waymarkedTrailsSkating": "Skating",
"waymarkedTrailsSkating": "Skating", "waymarkedTrailsHorseRiding": "Horse Riding",
"waymarkedTrailsHorseRiding": "Horse Riding", "waymarkedTrailsWinter": "Winter",
"waymarkedTrailsWinter": "Winter", "points_of_interest": "Points of interest",
"points_of_interest": "Points of interest", "food": "Food",
"food": "Food", "bakery": "Bakery",
"bakery": "Bakery", "food-store": "Food Store",
"food-store": "Food Store", "eat-and-drink": "Eat and Drink",
"eat-and-drink": "Eat and Drink", "amenities": "Amenities",
"amenities": "Amenities", "toilets": "Toilets",
"toilets": "Toilets", "water": "Water",
"water": "Water", "shower": "Shower",
"shower": "Shower", "shelter": "Shelter",
"shelter": "Shelter", "motorized": "Cars and Motorcycles",
"motorized": "Cars and Motorcycles", "fuel-station": "Fuel Station",
"fuel-station": "Fuel Station", "parking": "Parking",
"parking": "Parking", "garage": "Garage",
"garage": "Garage", "barrier": "Barrier",
"barrier": "Barrier", "tourism": "Tourism",
"tourism": "Tourism", "attraction": "Attraction",
"attraction": "Attraction", "viewpoint": "Viewpoint",
"viewpoint": "Viewpoint", "hotel": "Hotel",
"hotel": "Hotel", "campsite": "Campsite",
"campsite": "Campsite", "hut": "Hut",
"hut": "Hut", "picnic": "Picnic Area",
"picnic": "Picnic Area", "summit": "Summit",
"summit": "Summit", "pass": "Pass",
"pass": "Pass", "climbing": "Climbing",
"climbing": "Climbing", "bicycle": "Bicycle",
"bicycle": "Bicycle", "bicycle-parking": "Bicycle Parking",
"bicycle-parking": "Bicycle Parking", "bicycle-rental": "Bicycle Rental",
"bicycle-rental": "Bicycle Rental", "bicycle-shop": "Bicycle Shop",
"bicycle-shop": "Bicycle Shop", "public-transport": "Public Transport",
"public-transport": "Public Transport", "railway-station": "Railway Station",
"railway-station": "Railway Station", "tram-stop": "Tram Stop",
"tram-stop": "Tram Stop", "bus-stop": "Bus Stop",
"bus-stop": "Bus Stop", "ferry": "Ferry"
"ferry": "Ferry" },
}, "color": {
"color": { "blue": "Blue",
"blue": "Blue", "bluered": "Blue Red",
"bluered": "Blue Red", "gray": "Gray",
"gray": "Gray", "hot": "Hot",
"hot": "Hot", "purple": "Purple",
"purple": "Purple", "orange": "Orange"
"orange": "Orange" }
} },
}, "chart": {
"chart": { "show_slope": "Show slope data",
"show_slope": "Show slope data", "show_surface": "Show surface data",
"show_surface": "Show surface data", "show_speed": "Show speed data",
"show_speed": "Show speed data", "show_pace": "Show pace data",
"show_pace": "Show pace data", "show_heartrate": "Show heart rate data",
"show_heartrate": "Show heart rate data", "show_cadence": "Show cadence data",
"show_cadence": "Show cadence data", "show_temperature": "Show temperature data",
"show_temperature": "Show temperature data", "show_power": "Show power data"
"show_power": "Show power data" },
}, "quantities": {
"quantities": { "distance": "Distance",
"distance": "Distance", "elevation": "Elevation",
"elevation": "Elevation", "temperature": "Temperature",
"temperature": "Temperature", "speed": "Speed",
"speed": "Speed", "pace": "Pace",
"pace": "Pace", "heartrate": "Heart rate",
"heartrate": "Heart rate", "cadence": "Cadence",
"cadence": "Cadence", "power": "Power",
"power": "Power", "slope": "Slope",
"slope": "Slope", "surface": "Surface",
"surface": "Surface", "time": "Time",
"time": "Time", "moving": "Moving",
"moving": "Moving", "total": "Total"
"total": "Total" },
}, "units": {
"units": { "meters": "m",
"meters": "m", "feet": "ft",
"feet": "ft", "kilometers": "km",
"kilometers": "km", "miles": "mi",
"miles": "mi", "celsius": "°C",
"celsius": "°C", "fahrenheit": "°F",
"fahrenheit": "°F", "kilometers_per_hour": "km/h",
"kilometers_per_hour": "km/h", "miles_per_hour": "mph",
"miles_per_hour": "mph", "minutes_per_kilometer": "min/km",
"minutes_per_kilometer": "min/km", "minutes_per_mile": "min/mi",
"minutes_per_mile": "min/mi", "heartrate": "bpm",
"heartrate": "bpm", "cadence": "rpm",
"cadence": "rpm", "power": "W"
"power": "W" },
}, "gpx": {
"gpx": { "file": "File",
"file": "File", "files": "Files",
"files": "Files", "track": "Track",
"track": "Track", "tracks": "Tracks",
"tracks": "Tracks", "segment": "Segment",
"segment": "Segment", "segments": "Segments",
"segments": "Segments", "waypoint": "Point of interest",
"waypoint": "Point of interest", "waypoints": "Points of interest",
"waypoints": "Points of interest", "symbol": {
"symbol": { "alert": "Alert",
"alert": "Alert", "anchor": "Anchor",
"anchor": "Anchor", "bank": "Bank",
"bank": "Bank", "beach": "Beach",
"beach": "Beach", "bike_trail": "Bike Trail",
"bike_trail": "Bike Trail", "binoculars": "Binoculars",
"binoculars": "Binoculars", "bridge": "Bridge",
"bridge": "Bridge", "building": "Building",
"building": "Building", "campground": "Campsite",
"campground": "Campsite", "car": "Car",
"car": "Car", "car_repair": "Garage",
"car_repair": "Garage", "convenience_store": "Convenience Store",
"convenience_store": "Convenience Store", "crossing": "Crossing",
"crossing": "Crossing", "department_store": "Department Store",
"department_store": "Department Store", "drinking_water": "Water",
"drinking_water": "Water", "exit": "Exit",
"exit": "Exit", "lodge": "Hut",
"lodge": "Hut", "lodging": "Accommodation",
"lodging": "Accommodation", "forest": "Forest",
"forest": "Forest", "gas_station": "Fuel Station",
"gas_station": "Fuel Station", "ground_transportation": "Ground Transportation",
"ground_transportation": "Ground Transportation", "hotel": "Hotel",
"hotel": "Hotel", "house": "House",
"house": "House", "information": "Information",
"information": "Information", "park": "Park",
"park": "Park", "parking_area": "Parking",
"parking_area": "Parking", "pharmacy": "Pharmacy",
"pharmacy": "Pharmacy", "picnic_area": "Picnic Area",
"picnic_area": "Picnic Area", "restaurant": "Restaurant",
"restaurant": "Restaurant", "restricted_area": "Restricted Area",
"restricted_area": "Restricted Area", "restroom": "Toilets",
"restroom": "Toilets", "road": "Road",
"road": "Road", "scenic_area": "Scenic Area",
"scenic_area": "Scenic Area", "shelter": "Shelter",
"shelter": "Shelter", "shopping_center": "Shopping Center",
"shopping_center": "Shopping Center", "shower": "Shower",
"shower": "Shower", "summit": "Summit",
"summit": "Summit", "telephone": "Telephone",
"telephone": "Telephone", "tunnel": "Tunnel",
"tunnel": "Tunnel", "water_source": "Water Source"
"water_source": "Water Source" }
} },
}, "homepage": {
"homepage": { "website": "Website",
"website": "Website", "home": "Home",
"home": "Home", "app": "App",
"app": "App", "contact": "Contact",
"contact": "Contact", "x": "X",
"x": "X", "facebook": "Facebook",
"facebook": "Facebook", "github": "GitHub",
"github": "GitHub", "crowdin": "Crowdin",
"crowdin": "Crowdin", "email": "Email",
"email": "Email", "contribute": "Contribute",
"contribute": "Contribute", "supported_by": "supported by",
"supported_by": "supported by", "support_button": "Support gpx.studio on Ko-fi",
"support_button": "Support gpx.studio on Ko-fi", "route_planning": "Route planning",
"route_planning": "Route planning", "route_planning_description": "An intuitive interface to create itineraries tailored to each sport, based on OpenStreetMap data.",
"route_planning_description": "An intuitive interface to create itineraries tailored to each sport, based on OpenStreetMap data.", "file_processing": "Advanced file processing",
"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.",
"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": "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.",
"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": "Data visualization", "data_visualization_description": "An interactive elevation profile with detailed statistics to analyze recorded activities and future objectives.",
"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": "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."
"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": {
"embedding": { "title": "Create your own map",
"title": "Create your own map", "mapbox_token": "Mapbox access token",
"mapbox_token": "Mapbox access token", "file_urls": "File URLs (separated by commas)",
"file_urls": "File URLs (separated by commas)", "drive_ids": "Google Drive file IDs (separated by commas)",
"drive_ids": "Google Drive file IDs (separated by commas)", "basemap": "Basemap",
"basemap": "Basemap", "height": "Height",
"height": "Height", "fill_by": "Fill by",
"fill_by": "Fill by", "none": "None",
"none": "None", "show_controls": "Show controls",
"show_controls": "Show controls", "manual_camera": "Manual camera",
"manual_camera": "Manual camera", "manual_camera_description": "You can move the map below to adjust the camera position.",
"manual_camera_description": "You can move the map below to adjust the camera position.", "latitude": "Latitude",
"latitude": "Latitude", "longitude": "Longitude",
"longitude": "Longitude", "zoom": "Zoom",
"zoom": "Zoom", "pitch": "Pitch",
"pitch": "Pitch", "bearing": "Bearing",
"bearing": "Bearing", "preview": "Preview",
"preview": "Preview", "code": "Integration code"
"code": "Integration code" },
}, "webgl2_required": "WebGL 2 is required to display the map.",
"webgl2_required": "WebGL 2 is required to display the map.", "enable_webgl2": "Learn how to enable WebGL 2 in your browser",
"enable_webgl2": "Learn how to enable WebGL 2 in your browser", "page_not_found": "page not found"
"page_not_found": "page not found" }
}