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,
exportState,
ExportState,
flyToBounds,
gpxStatistics
centerMapOnSelection
} from '$lib/stores';
import {
copied,
@ -225,13 +224,6 @@
<PaintBucket size="16" class="mr-1" />
{$_('menu.style.button')}
</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
on:click={() => {
if ($allHidden) {
@ -257,6 +249,17 @@
{$_('menu.select_all')}
<Shortcut key="A" ctrl={true} />
</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}
<Menubar.Separator />
<Menubar.Item on:click={copySelection} disabled={$selection.size === 0}>
@ -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();

View file

@ -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<AnyGPXTreeElement> | Waypoint[] | Waypoint;
export let item: ListItem;
@ -227,14 +225,6 @@
{$_('menu.style.button')}
</ContextMenu.Item>
{/if}
<ContextMenu.Item
on:click={() => {
flyToBounds($gpxStatistics.global.bounds, $map);
}}
>
<Maximize size="16" class="mr-1" />
{$_('menu.fly_to')}
</ContextMenu.Item>
<ContextMenu.Item
on:click={() => {
if ($allHidden) {
@ -294,8 +284,13 @@
{$_('menu.select_all')}
<Shortcut key="A" ctrl={true} />
</ContextMenu.Item>
<ContextMenu.Separator />
{/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}>
<Copy size="16" class="mr-1" />
{$_('menu.duplicate')}

View file

@ -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<GPXStatistics> = 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<string, () => 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<string, GPXLayer> = new Map();
export const routingControls: Map<string, RoutingControls> = 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<Tool | null>(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<GPXFile | null> {
let result = await new Promise<GPXFile | null>((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<GPXFile | null>((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>(ExportState.NONE);

View file

@ -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"
}