improve zoom on selected POIs, rework zoom buttons and add shortcut
This commit is contained in:
parent
5bd7cf6938
commit
df2985c999
4 changed files with 849 additions and 829 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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')}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
Loading…
Reference in a new issue