90 Commits

Author SHA1 Message Date
vcoppe
a0bfc17723 New translations en.json (Dutch) 2026-03-18 23:41:40 +01:00
vcoppe
c9b26dbc1c New translations en.json (French) 2026-03-18 19:59:04 +01:00
vcoppe
92e658376d New translations en.json (Serbian (Latin)) 2026-03-18 18:42:14 +01:00
vcoppe
55b7f17cd4 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-18 18:42:12 +01:00
vcoppe
fc030ecd4d New translations en.json (Latvian) 2026-03-18 18:42:10 +01:00
vcoppe
a76f6f0e0a New translations en.json (Thai) 2026-03-18 18:42:08 +01:00
vcoppe
03b20ea067 New translations en.json (Indonesian) 2026-03-18 18:42:06 +01:00
vcoppe
259f0d66c7 New translations en.json (Portuguese, Brazilian) 2026-03-18 18:42:04 +01:00
vcoppe
30f272c404 New translations en.json (Vietnamese) 2026-03-18 18:42:03 +01:00
vcoppe
7a02f1d5b1 New translations en.json (Chinese Simplified) 2026-03-18 18:42:02 +01:00
vcoppe
c421c2a404 New translations en.json (Ukrainian) 2026-03-18 18:42:01 +01:00
vcoppe
2e58d270b9 New translations en.json (Turkish) 2026-03-18 18:41:59 +01:00
vcoppe
d087fed76b New translations en.json (Swedish) 2026-03-18 18:41:58 +01:00
vcoppe
ad0efd3372 New translations en.json (Russian) 2026-03-18 18:41:57 +01:00
vcoppe
1414f0a7f0 New translations en.json (Portuguese) 2026-03-18 18:41:56 +01:00
vcoppe
19b0b33944 New translations en.json (Polish) 2026-03-18 18:41:54 +01:00
vcoppe
d0c3bfb3d3 New translations en.json (Norwegian) 2026-03-18 18:41:53 +01:00
vcoppe
b8a17c8ffe New translations en.json (Lithuanian) 2026-03-18 18:41:52 +01:00
vcoppe
e69c03f6fb New translations en.json (Korean) 2026-03-18 18:41:51 +01:00
vcoppe
aafe7df561 New translations en.json (Italian) 2026-03-18 18:41:50 +01:00
vcoppe
818e07df93 New translations en.json (Hungarian) 2026-03-18 18:41:48 +01:00
vcoppe
23b19e0367 New translations en.json (Hebrew) 2026-03-18 18:41:47 +01:00
vcoppe
6e23b01434 New translations en.json (Finnish) 2026-03-18 18:41:46 +01:00
vcoppe
1c5180aca7 New translations en.json (Greek) 2026-03-18 18:41:44 +01:00
vcoppe
b968f8d28f New translations en.json (German) 2026-03-18 18:41:42 +01:00
vcoppe
b1f1adcc9d New translations en.json (Danish) 2026-03-18 18:41:40 +01:00
vcoppe
49277340ed New translations en.json (Czech) 2026-03-18 18:41:37 +01:00
vcoppe
6d489e279c New translations en.json (Belarusian) 2026-03-18 18:41:36 +01:00
vcoppe
a2eb7ae9c3 New translations en.json (Spanish) 2026-03-18 18:41:33 +01:00
vcoppe
892074fd1b New translations en.json (French) 2026-03-18 18:41:31 +01:00
vcoppe
11eec6cf15 New translations en.json (Romanian) 2026-03-18 18:41:30 +01:00
vcoppe
f17d412a22 New translations en.json (Catalan) 2026-03-18 18:41:27 +01:00
vcoppe
892db21e8f New translations en.json (Dutch) 2026-03-18 18:41:25 +01:00
vcoppe
e4ba56ff0f New translations en.json (Basque) 2026-03-18 18:41:21 +01:00
vcoppe
edfe28d61f New translations en.json (Serbian (Latin)) 2026-03-18 18:32:23 +01:00
vcoppe
188197ab15 New translations en.json (Chinese Traditional, Hong Kong) 2026-03-18 18:32:22 +01:00
vcoppe
3429af3f33 New translations en.json (Latvian) 2026-03-18 18:32:20 +01:00
vcoppe
232f13d2e0 New translations en.json (Thai) 2026-03-18 18:32:19 +01:00
vcoppe
0eb2d7543e New translations en.json (Indonesian) 2026-03-18 18:32:18 +01:00
vcoppe
c2caa68268 New translations en.json (Portuguese, Brazilian) 2026-03-18 18:32:16 +01:00
vcoppe
0033e16d4a New translations en.json (Vietnamese) 2026-03-18 18:32:15 +01:00
vcoppe
34ccb86f8a New translations en.json (Chinese Simplified) 2026-03-18 18:32:12 +01:00
vcoppe
3800fab9a8 New translations en.json (Ukrainian) 2026-03-18 18:32:05 +01:00
vcoppe
0216e27a22 New translations en.json (Turkish) 2026-03-18 18:32:03 +01:00
vcoppe
1325f58cab New translations en.json (Swedish) 2026-03-18 18:32:01 +01:00
vcoppe
7e50594ada New translations en.json (Russian) 2026-03-18 18:32:00 +01:00
vcoppe
3fabc250e9 New translations en.json (Portuguese) 2026-03-18 18:31:59 +01:00
vcoppe
ec4d4e0cb5 New translations en.json (Polish) 2026-03-18 18:31:58 +01:00
vcoppe
cc88ea5cdf New translations en.json (Norwegian) 2026-03-18 18:31:56 +01:00
vcoppe
4452f2fc75 New translations en.json (Lithuanian) 2026-03-18 18:31:55 +01:00
vcoppe
3cefe39cd7 New translations en.json (Korean) 2026-03-18 18:31:54 +01:00
vcoppe
a5fcc95299 New translations en.json (Italian) 2026-03-18 18:31:53 +01:00
vcoppe
a5d5c85fdd New translations en.json (Hungarian) 2026-03-18 18:31:51 +01:00
vcoppe
36c3d7dd9e New translations en.json (Hebrew) 2026-03-18 18:31:50 +01:00
vcoppe
9022974cf4 New translations en.json (Finnish) 2026-03-18 18:31:48 +01:00
vcoppe
3744fac4ad New translations en.json (Greek) 2026-03-18 18:31:47 +01:00
vcoppe
ee208c0191 New translations en.json (German) 2026-03-18 18:31:46 +01:00
vcoppe
6562ef643b New translations en.json (Danish) 2026-03-18 18:31:41 +01:00
vcoppe
3ca4845d34 New translations en.json (Czech) 2026-03-18 18:31:36 +01:00
vcoppe
dabb014689 New translations en.json (Belarusian) 2026-03-18 18:31:32 +01:00
vcoppe
6a233fd695 New translations en.json (Spanish) 2026-03-18 18:31:29 +01:00
vcoppe
d2ce6d0297 New translations en.json (French) 2026-03-18 18:31:27 +01:00
vcoppe
203d9de289 New translations en.json (Romanian) 2026-03-18 18:31:23 +01:00
vcoppe
a624144e66 New translations en.json (Catalan) 2026-03-18 18:31:16 +01:00
vcoppe
6a58e5044e New translations en.json (Dutch) 2026-03-18 18:31:13 +01:00
vcoppe
54779beeff New translations en.json (Basque) 2026-03-18 18:31:08 +01:00
vcoppe
9d5fc48286 New translations file.mdx (Ukrainian) 2026-03-11 19:04:38 +01:00
vcoppe
fa0339ed9f New translations translation.mdx (Ukrainian) 2026-03-11 17:56:19 +01:00
vcoppe
f6a89784b8 New translations funding.mdx (Ukrainian) 2026-03-11 17:56:18 +01:00
vcoppe
6301df55d5 New translations file.mdx (Catalan) 2026-03-10 09:10:17 +01:00
vcoppe
378f66de7a New translations en.json (Catalan) 2026-03-10 09:10:16 +01:00
vcoppe
0c48b52b5b New translations en.json (Dutch) 2026-03-05 14:53:55 +01:00
vcoppe
3a1e81467f New translations en.json (Basque) 2026-03-04 10:42:12 +01:00
vcoppe
40422b9059 New translations files-and-stats.mdx (Portuguese, Brazilian) 2026-03-01 17:36:41 +01:00
vcoppe
767fdbd773 New translations file.mdx (Portuguese, Brazilian) 2026-03-01 16:34:19 +01:00
vcoppe
1473886f54 New translations file.mdx (French) 2026-02-26 09:27:01 +01:00
vcoppe
daeb3d4f57 New translations en.json (Indonesian) 2026-02-19 06:29:33 +01:00
vcoppe
65bad83635 New translations en.json (Norwegian) 2026-02-12 21:05:16 +01:00
vcoppe
c2ac4fb7d9 New translations en.json (Hungarian) 2026-02-08 09:31:02 +01:00
vcoppe
c52fa0001a New translations mapbox.mdx (German) 2026-02-02 18:59:24 +01:00
vcoppe
dfad2ef3ef New translations en.json (German) 2026-02-02 18:59:22 +01:00
vcoppe
9c6e03f4a8 improve layer stacking 2026-01-30 21:30:37 +01:00
vcoppe
2a4dfe010e improve color management 2026-01-30 21:17:59 +01:00
vcoppe
f42a916c25 remove unused parameter 2026-01-30 21:17:11 +01:00
vcoppe
772b810fa8 simplify initialization 2026-01-30 21:16:56 +01:00
vcoppe
4d4d10d5c2 small UI tweaks 2026-01-30 21:16:32 +01:00
vcoppe
0e4c7dbe64 New translations en.json (Chinese Simplified) (#306) 2026-01-30 21:02:21 +01:00
vcoppe
375204c379 New Crowdin updates (#304)
* New translations en.json (Dutch)

* New translations en.json (Czech)

* New translations en.json (Spanish)

* New translations file.mdx (Vietnamese)

* New translations en.json (Polish)
2026-01-28 17:54:01 +01:00
vcoppe
d76c03af4f add try catch to setTerrain 2026-01-28 17:53:26 +01:00
vcoppe
200a6586ba remove unused glyphs url with empty key causing problems 2026-01-28 17:53:12 +01:00
64 changed files with 401 additions and 458 deletions

View File

@@ -27,8 +27,8 @@ Any help is greatly appreciated!
The code is split into two parts: The code is split into two parts:
- `gpx`: a Typescript library for parsing and manipulating GPX files, - `gpx`: a Typescript library for parsing and manipulating GPX files,
- `website`: the website itself, which is a [SvelteKit](https://kit.svelte.dev/) application. - `website`: the website itself, which is a [SvelteKit](https://kit.svelte.dev/) application.
You will need [Node.js](https://nodejs.org/) to build and run these two parts. You will need [Node.js](https://nodejs.org/) to build and run these two parts.
@@ -55,25 +55,25 @@ npm run dev
This project has been made possible thanks to the following open source projects: This project has been made possible thanks to the following open source projects:
- Development: - Development:
- [Svelte](https://github.com/sveltejs/svelte) and [SvelteKit](https://github.com/sveltejs/kit) — seamless development experience - [Svelte](https://github.com/sveltejs/svelte) and [SvelteKit](https://github.com/sveltejs/kit) — seamless development experience
- [MDsveX](https://github.com/pngwn/MDsveX) — allowing a Markdown-based documentation - [MDsveX](https://github.com/pngwn/MDsveX) — allowing a Markdown-based documentation
- Design: - Design:
- [shadcn-svelte](https://github.com/huntabyte/shadcn-svelte) — beautiful components - [shadcn-svelte](https://github.com/huntabyte/shadcn-svelte) — beautiful components
- [@lucide/svelte](https://github.com/lucide-icons/lucide/tree/main/packages/svelte) — beautiful icons - [@lucide/svelte](https://github.com/lucide-icons/lucide/tree/main/packages/svelte) — beautiful icons
- [tailwindcss](https://github.com/tailwindlabs/tailwindcss) — easy styling - [tailwindcss](https://github.com/tailwindlabs/tailwindcss) — easy styling
- [Chart.js](https://github.com/chartjs/Chart.js) — beautiful and fast charts - [Chart.js](https://github.com/chartjs/Chart.js) — beautiful and fast charts
- Logic: - Logic:
- [immer](https://github.com/immerjs/immer) — complex state management - [immer](https://github.com/immerjs/immer) — complex state management
- [Dexie.js](https://github.com/dexie/Dexie.js) — IndexedDB wrapper - [Dexie.js](https://github.com/dexie/Dexie.js) — IndexedDB wrapper
- [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) — fast GPX file parsing - [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) — fast GPX file parsing
- [SortableJS](https://github.com/SortableJS/Sortable) — creating a sortable file tree - [SortableJS](https://github.com/SortableJS/Sortable) — creating a sortable file tree
- Mapping: - Mapping:
- [Mapbox GL JS](https://github.com/mapbox/mapbox-gl-js) — beautiful and fast interactive maps - [Mapbox GL JS](https://github.com/mapbox/mapbox-gl-js) — beautiful and fast interactive maps
- [GraphHopper](https://github.com/graphhopper/graphhopper) — routing engine - [brouter](https://github.com/abrensch/brouter) — routing engine
- [OpenStreetMap](https://www.openstreetmap.org) — map data used by Mapbox and GraphHopper - [OpenStreetMap](https://www.openstreetmap.org) — map data used by Mapbox and brouter
- Search: - Search:
- [DocSearch](https://github.com/algolia/docsearch) — search engine for the documentation - [DocSearch](https://github.com/algolia/docsearch) — search engine for the documentation
## License ## License

View File

@@ -1398,7 +1398,10 @@ export class TrackPoint {
: undefined; : undefined;
} }
setExtension(key: string, value: string) { setExtensions(extensions: Record<string, string>) {
if (Object.keys(extensions).length === 0) {
return;
}
if (!this.extensions) { if (!this.extensions) {
this.extensions = {}; this.extensions = {};
} }
@@ -1408,12 +1411,8 @@ export class TrackPoint {
if (!this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']) { if (!this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']) {
this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'] = {}; this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'] = {};
} }
this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'][key] = value;
}
setExtensions(extensions: Record<string, string>) {
Object.entries(extensions).forEach(([key, value]) => { Object.entries(extensions).forEach(([key, value]) => {
this.setExtension(key, value); this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions'][key] = value;
}); });
} }

View File

@@ -17,7 +17,6 @@
} }
}, },
"sprite": "https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite", "sprite": "https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite",
"glyphs": "https://api.maptiler.com/fonts/{fontstack}/{range}.pbf?key={key}",
"layers": [ "layers": [
{ {
"id": "background", "id": "background",

View File

@@ -34,11 +34,10 @@
import { editStyle } from '$lib/components/file-list/style/utils.svelte'; import { editStyle } from '$lib/components/file-list/style/utils.svelte';
import { getSymbolKey, symbols } from '$lib/assets/symbols'; import { getSymbolKey, symbols } from '$lib/assets/symbols';
import { selection, copied, cut } from '$lib/logic/selection'; import { selection, copied, cut } from '$lib/logic/selection';
import { map } from '$lib/components/map/map';
import { fileActions, pasteSelection } from '$lib/logic/file-actions'; import { fileActions, pasteSelection } from '$lib/logic/file-actions';
import { allHidden } from '$lib/logic/hidden'; import { allHidden } from '$lib/logic/hidden';
import { boundsManager } from '$lib/logic/bounds'; import { boundsManager } from '$lib/logic/bounds';
import { gpxLayers } from '$lib/components/map/gpx-layer/gpx-layers'; import { gpxColors, gpxLayers } from '$lib/components/map/gpx-layer/gpx-layers';
import { fileStateCollection } from '$lib/logic/file-state'; import { fileStateCollection } from '$lib/logic/file-state';
import { waypointPopup } from '$lib/components/map/gpx-layer/gpx-layer-popup'; import { waypointPopup } from '$lib/components/map/gpx-layer/gpx-layer-popup';
import { allowedPastes } from './sortable-file-list'; import { allowedPastes } from './sortable-file-list';
@@ -58,19 +57,11 @@
let singleSelection = $derived($selection.size === 1); let singleSelection = $derived($selection.size === 1);
let nodeColors: string[] = $state([]); let nodeColors: string[] = $derived.by(() => {
$effect.pre(() => {
let colors: string[] = []; let colors: string[] = [];
if (node && $map) { if (node) {
if (node instanceof GPXFile) { if (node instanceof GPXFile) {
let defaultColor = undefined; let defaultColor = $gpxColors.get(item.getFileId());
let layer = gpxLayers.getLayer(item.getFileId());
if (layer) {
defaultColor = layer.layerColor;
}
let style = node.getStyle(defaultColor); let style = node.getStyle(defaultColor);
colors = style.color; colors = style.color;
} else if (node instanceof Track) { } else if (node instanceof Track) {
@@ -83,14 +74,14 @@
colors.push(style['gpx_style:color']); colors.push(style['gpx_style:color']);
} }
if (colors.length === 0) { if (colors.length === 0) {
let layer = gpxLayers.getLayer(item.getFileId()); let defaultColor = $gpxColors.get(item.getFileId());
if (layer) { if (defaultColor) {
colors.push(layer.layerColor); colors.push(defaultColor);
} }
} }
} }
} }
nodeColors = colors; return colors;
}); });
let symbolKey = $derived(node instanceof Waypoint ? getSymbolKey(node.sym) : undefined); let symbolKey = $derived(node instanceof Waypoint ? getSymbolKey(node.sym) : undefined);

View File

@@ -48,7 +48,7 @@
language = 'en'; language = 'en';
} }
map.init(PUBLIC_MAPBOX_TOKEN, language, hash, geocoder, geolocate); map.init(language, hash, geocoder, geolocate);
}); });
onDestroy(() => { onDestroy(() => {

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { onDestroy, onMount } from 'svelte'; import { onDestroy } from 'svelte';
import { gpxLayers } from '$lib/components/map/gpx-layer/gpx-layers'; import { gpxLayers } from '$lib/components/map/gpx-layer/gpx-layers';
import { DistanceMarkers } from '$lib/components/map/gpx-layer/distance-markers'; import { DistanceMarkers } from '$lib/components/map/gpx-layer/distance-markers';
import { StartEndMarkers } from '$lib/components/map/gpx-layer/start-end-markers'; import { StartEndMarkers } from '$lib/components/map/gpx-layer/start-end-markers';
@@ -9,13 +9,10 @@
let distanceMarkers: DistanceMarkers; let distanceMarkers: DistanceMarkers;
let startEndMarkers: StartEndMarkers; let startEndMarkers: StartEndMarkers;
onMount(() => { map.onLoad((map_) => {
gpxLayers.init(); gpxLayers.init();
startEndMarkers = new StartEndMarkers(); startEndMarkers = new StartEndMarkers();
distanceMarkers = new DistanceMarkers(); distanceMarkers = new DistanceMarkers();
});
map.onLoad((map_) => {
createPopups(map_); createPopups(map_);
}); });

View File

@@ -41,6 +41,7 @@
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
class="justify-start"
href={`https://www.openstreetmap.org/edit?#map=${(($map?.getZoom() ?? 17) + 1).toFixed(0)}/${trackpoint.item.getLatitude().toFixed(5)}/${trackpoint.item.getLongitude().toFixed(5)}`} href={`https://www.openstreetmap.org/edit?#map=${(($map?.getZoom() ?? 17) + 1).toFixed(0)}/${trackpoint.item.getLatitude().toFixed(5)}/${trackpoint.item.getLongitude().toFixed(5)}`}
target="_blank" target="_blank"
> >

View File

@@ -3,7 +3,7 @@ import { gpxStatistics } from '$lib/logic/statistics';
import { getConvertedDistanceToKilometers } from '$lib/units'; import { getConvertedDistanceToKilometers } from '$lib/units';
import type { GeoJSONSource } from 'mapbox-gl'; import type { GeoJSONSource } from 'mapbox-gl';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { map } from '$lib/components/map/map'; import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import { allHidden } from '$lib/logic/hidden'; import { allHidden } from '$lib/logic/hidden';
const { distanceMarkers, distanceUnits } = settings; const { distanceMarkers, distanceUnits } = settings;
@@ -44,44 +44,45 @@ export class DistanceMarkers {
}); });
} }
if (!map_.getLayer('distance-markers')) { if (!map_.getLayer('distance-markers')) {
map_.addLayer({ map_.addLayer(
id: 'distance-markers', {
type: 'symbol', id: 'distance-markers',
source: 'distance-markers', type: 'symbol',
filter: [ source: 'distance-markers',
'match', filter: [
['get', 'level'], 'match',
100, ['get', 'level'],
['>=', ['zoom'], 0], 100,
50, ['>=', ['zoom'], 0],
['>=', ['zoom'], 7], 50,
25, ['>=', ['zoom'], 7],
[ 25,
'any', [
['all', ['>=', ['zoom'], 8], ['<=', ['zoom'], 9]], 'any',
['all', ['>=', ['zoom'], 8], ['<=', ['zoom'], 9]],
['>=', ['zoom'], 11],
],
10,
['>=', ['zoom'], 10],
5,
['>=', ['zoom'], 11], ['>=', ['zoom'], 11],
1,
['>=', ['zoom'], 13],
false,
], ],
10, layout: {
['>=', ['zoom'], 10], 'text-field': ['get', 'distance'],
5, 'text-size': 14,
['>=', ['zoom'], 11], 'text-font': ['Open Sans Bold'],
1, },
['>=', ['zoom'], 13], paint: {
false, 'text-color': 'black',
], 'text-halo-width': 2,
layout: { 'text-halo-color': 'white',
'text-field': ['get', 'distance'], },
'text-size': 14,
'text-font': ['Open Sans Bold'],
}, },
paint: { ANCHOR_LAYER_KEY.distanceMarkers
'text-color': 'black', );
'text-halo-width': 2,
'text-halo-color': 'white',
},
});
} else {
map_.moveLayer('distance-markers');
} }
} else { } else {
if (map_.getLayer('distance-markers')) { if (map_.getLayer('distance-markers')) {

View File

@@ -1,6 +1,6 @@
import { get, type Readable } from 'svelte/store'; import { get, type Readable } from 'svelte/store';
import mapboxgl, { type FilterSpecification } from 'mapbox-gl'; import mapboxgl, { type FilterSpecification } from 'mapbox-gl';
import { map } from '$lib/components/map/map'; import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import { waypointPopup, trackpointPopup } from './gpx-layer-popup'; import { waypointPopup, trackpointPopup } from './gpx-layer-popup';
import { import {
ListTrackSegmentItem, ListTrackSegmentItem,
@@ -22,6 +22,7 @@ import { fileActionManager } from '$lib/logic/file-action-manager';
import { fileActions } from '$lib/logic/file-actions'; import { fileActions } from '$lib/logic/file-actions';
import { splitAs } from '$lib/components/toolbar/tools/scissors/scissors'; import { splitAs } from '$lib/components/toolbar/tools/scissors/scissors';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor'; import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { gpxColors } from '$lib/components/map/gpx-layer/gpx-layers';
const colors = [ const colors = [
'#ff0000', '#ff0000',
@@ -43,16 +44,35 @@ for (let color of colors) {
} }
// Get the color with the least amount of uses // Get the color with the least amount of uses
function getColor() { function getColor(fileId: string) {
let color = colors.reduce((a, b) => (colorCount[a] <= colorCount[b] ? a : b)); let color = colors.reduce((a, b) => (colorCount[a] <= colorCount[b] ? a : b));
colorCount[color]++; colorCount[color]++;
gpxColors.update((colors) => {
colors.set(fileId, color);
return colors;
});
return color; return color;
} }
function decrementColor(color: string) { function replaceColor(fileId: string, oldColor: string, newColor: string) {
if (colorCount.hasOwnProperty(oldColor)) {
colorCount[oldColor]--;
}
colorCount[newColor]++;
gpxColors.update((colors) => {
colors.set(fileId, newColor);
return colors;
});
}
function removeColor(fileId: string, color: string) {
if (colorCount.hasOwnProperty(color)) { if (colorCount.hasOwnProperty(color)) {
colorCount[color]--; colorCount[color]--;
} }
gpxColors.update((colors) => {
colors.delete(fileId);
return colors;
});
} }
export function getSvgForSymbol(symbol?: string | undefined, layerColor?: string | undefined) { export function getSvgForSymbol(symbol?: string | undefined, layerColor?: string | undefined) {
@@ -121,7 +141,7 @@ export class GPXLayer {
constructor(fileId: string, file: Readable<GPXFileWithStatistics | undefined>) { constructor(fileId: string, file: Readable<GPXFileWithStatistics | undefined>) {
this.fileId = fileId; this.fileId = fileId;
this.file = file; this.file = file;
this.layerColor = getColor(); this.layerColor = getColor(fileId);
this.unsubscribe.push( this.unsubscribe.push(
map.subscribe(($map) => { map.subscribe(($map) => {
if ($map) { if ($map) {
@@ -158,7 +178,7 @@ export class GPXLayer {
file._data.style.color && file._data.style.color &&
this.layerColor !== `#${file._data.style.color}` this.layerColor !== `#${file._data.style.color}`
) { ) {
decrementColor(this.layerColor); replaceColor(this.fileId, this.layerColor, `#${file._data.style.color}`);
this.layerColor = `#${file._data.style.color}`; this.layerColor = `#${file._data.style.color}`;
} }
@@ -176,20 +196,23 @@ export class GPXLayer {
} }
if (!_map.getLayer(this.fileId)) { if (!_map.getLayer(this.fileId)) {
_map.addLayer({ _map.addLayer(
id: this.fileId, {
type: 'line', id: this.fileId,
source: this.fileId, type: 'line',
layout: { source: this.fileId,
'line-join': 'round', layout: {
'line-cap': 'round', 'line-join': 'round',
'line-cap': 'round',
},
paint: {
'line-color': ['get', 'color'],
'line-width': ['get', 'width'],
'line-opacity': ['get', 'opacity'],
},
}, },
paint: { ANCHOR_LAYER_KEY.tracks
'line-color': ['get', 'color'], );
'line-width': ['get', 'width'],
'line-opacity': ['get', 'opacity'],
},
});
_map.on('click', this.fileId, this.layerOnClickBinded); _map.on('click', this.fileId, this.layerOnClickBinded);
_map.on('contextmenu', this.fileId, this.layerOnContextMenuBinded); _map.on('contextmenu', this.fileId, this.layerOnContextMenuBinded);
@@ -212,18 +235,21 @@ export class GPXLayer {
} }
if (!_map.getLayer(this.fileId + '-waypoints')) { if (!_map.getLayer(this.fileId + '-waypoints')) {
_map.addLayer({ _map.addLayer(
id: this.fileId + '-waypoints', {
type: 'symbol', id: this.fileId + '-waypoints',
source: this.fileId + '-waypoints', type: 'symbol',
layout: { source: this.fileId + '-waypoints',
'icon-image': ['get', 'icon'], layout: {
'icon-size': 0.3, 'icon-image': ['get', 'icon'],
'icon-anchor': 'bottom', 'icon-size': 0.3,
'icon-padding': 0, 'icon-anchor': 'bottom',
'icon-allow-overlap': true, 'icon-padding': 0,
'icon-allow-overlap': true,
},
}, },
}); ANCHOR_LAYER_KEY.waypoints
);
_map.on( _map.on(
'mouseenter', 'mouseenter',
@@ -272,7 +298,7 @@ export class GPXLayer {
'text-halo-color': 'white', 'text-halo-color': 'white',
}, },
}, },
_map.getLayer('distance-markers') ? 'distance-markers' : undefined ANCHOR_LAYER_KEY.directionMarkers
); );
} }
} else { } else {
@@ -364,7 +390,7 @@ export class GPXLayer {
this.unsubscribe.forEach((unsubscribe) => unsubscribe()); this.unsubscribe.forEach((unsubscribe) => unsubscribe());
decrementColor(this.layerColor); removeColor(this.fileId, this.layerColor);
} }
moveToFront() { moveToFront() {
@@ -373,13 +399,13 @@ export class GPXLayer {
return; return;
} }
if (_map.getLayer(this.fileId)) { if (_map.getLayer(this.fileId)) {
_map.moveLayer(this.fileId); _map.moveLayer(this.fileId, ANCHOR_LAYER_KEY.tracks);
} }
if (_map.getLayer(this.fileId + '-waypoints')) { if (_map.getLayer(this.fileId + '-waypoints')) {
_map.moveLayer(this.fileId + '-waypoints'); _map.moveLayer(this.fileId + '-waypoints', ANCHOR_LAYER_KEY.waypoints);
} }
if (_map.getLayer(this.fileId + '-direction')) { if (_map.getLayer(this.fileId + '-direction')) {
_map.moveLayer(this.fileId + '-direction'); _map.moveLayer(this.fileId + '-direction', ANCHOR_LAYER_KEY.directionMarkers);
} }
} }

View File

@@ -1,4 +1,5 @@
import { GPXFileStateCollectionObserver } from '$lib/logic/file-state'; import { GPXFileStateCollectionObserver } from '$lib/logic/file-state';
import { writable } from 'svelte/store';
import { GPXLayer } from './gpx-layer'; import { GPXLayer } from './gpx-layer';
export class GPXLayerCollection { export class GPXLayerCollection {
@@ -42,3 +43,4 @@ export class GPXLayerCollection {
} }
export const gpxLayers = new GPXLayerCollection(); export const gpxLayers = new GPXLayerCollection();
export const gpxColors = writable(new Map<string, string>());

View File

@@ -54,28 +54,27 @@
<Card.Root class="border-none shadow-md text-base p-2 max-w-[50dvw] gap-0"> <Card.Root class="border-none shadow-md text-base p-2 max-w-[50dvw] gap-0">
<Card.Header class="p-0 gap-0"> <Card.Header class="p-0 gap-0">
<Card.Title class="text-md"> <Card.Title class="text-md flex flex-row">
<div class="flex flex-row gap-3"> <div class="flex flex-col">
<div class="flex flex-col"> <p>{name}</p>
{name} <div class="text-muted-foreground text-xs font-normal">
<div class="text-muted-foreground text-xs font-normal"> {poi.item.lat.toFixed(6)}&deg; {poi.item.lon.toFixed(6)}&deg;
{poi.item.lat.toFixed(6)}&deg; {poi.item.lon.toFixed(6)}&deg;
</div>
</div> </div>
<Button
class="ml-auto"
variant="outline"
size="icon"
href="https://www.openstreetmap.org/edit?editor=id&{poi.item.type ??
'node'}={poi.item.id}"
target="_blank"
>
<PencilLine size="16" />
</Button>
</div> </div>
<Button
class="ml-auto"
variant="outline"
size="icon-sm"
href="https://www.openstreetmap.org/edit?editor=id&{poi.item.type ?? 'node'}={poi
.item.id}"
target="_blank"
>
<PencilLine size="16" />
</Button>
</Card.Title> </Card.Title>
</Card.Header> </Card.Header>
<Card.Content class="flex flex-col p-0 text-sm mt-1 whitespace-normal break-all"> <Card.Content class="flex flex-col gap-1 p-0 text-sm whitespace-normal break-all">
<ScrollArea class="flex flex-col max-h-[30dvh]"> <ScrollArea class="flex flex-col max-h-[30dvh]">
{#if tags.image || tags['image:0']} {#if tags.image || tags['image:0']}
<div class="w-full rounded-md overflow-clip my-2 max-w-96 mx-auto"> <div class="w-full rounded-md overflow-clip my-2 max-w-96 mx-auto">
@@ -100,8 +99,14 @@
{/each} {/each}
</div> </div>
</ScrollArea> </ScrollArea>
<Button class="mt-2" variant="outline" disabled={$selection.size === 0} onclick={addToFile}> <Button
<MapPin size="16" /> size="sm"
class="mt-1 justify-start"
variant="outline"
disabled={$selection.size === 0}
onclick={addToFile}
>
<MapPin size="14" />
{i18n._('toolbar.waypoint.add')} {i18n._('toolbar.waypoint.add')}
</Button> </Button>
</Card.Content> </Card.Content>

View File

@@ -6,6 +6,7 @@ import { overpassQueryData } from '$lib/assets/layers';
import { MapPopup } from '$lib/components/map/map-popup'; import { MapPopup } from '$lib/components/map/map-popup';
import { settings } from '$lib/logic/settings'; import { settings } from '$lib/logic/settings';
import { db } from '$lib/db'; import { db } from '$lib/db';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
const { currentOverpassQueries } = settings; const { currentOverpassQueries } = settings;
@@ -85,17 +86,20 @@ export class OverpassLayer {
} }
if (!this.map.getLayer('overpass')) { if (!this.map.getLayer('overpass')) {
this.map.addLayer({ this.map.addLayer(
id: 'overpass', {
type: 'symbol', id: 'overpass',
source: 'overpass', type: 'symbol',
layout: { source: 'overpass',
'icon-image': ['get', 'icon'], layout: {
'icon-size': 0.25, 'icon-image': ['get', 'icon'],
'icon-padding': 0, 'icon-size': 0.25,
'icon-allow-overlap': ['step', ['zoom'], false, 14, true], 'icon-padding': 0,
'icon-allow-overlap': ['step', ['zoom'], false, 14, true],
},
}, },
}); ANCHOR_LAYER_KEY.overpass
);
this.map.on('mouseenter', 'overpass', this.onHoverBinded); this.map.on('mouseenter', 'overpass', this.onHoverBinded);
this.map.on('click', 'overpass', this.onHoverBinded); this.map.on('click', 'overpass', this.onHoverBinded);

View File

@@ -20,6 +20,28 @@ let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = {
easing: () => 1, easing: () => 1,
}; };
const emptySource: mapboxgl.GeoJSONSourceSpecification = {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [],
},
};
export const ANCHOR_LAYER_KEY = {
mapillary: 'mapillary-end',
tracks: 'tracks-end',
directionMarkers: 'direction-markers-end',
distanceMarkers: 'distance-markers-end',
interactions: 'interactions-end',
overpass: 'overpass-end',
waypoints: 'waypoints-end',
};
const anchorLayers: mapboxgl.LayerSpecification[] = Object.values(ANCHOR_LAYER_KEY).map((id) => ({
id: id,
type: 'symbol',
source: 'empty-source',
}));
export class MapboxGLMap { export class MapboxGLMap {
private _map: Writable<mapboxgl.Map | null> = writable(null); private _map: Writable<mapboxgl.Map | null> = writable(null);
private _onLoadCallbacks: ((map: mapboxgl.Map) => void)[] = []; private _onLoadCallbacks: ((map: mapboxgl.Map) => void)[] = [];
@@ -29,19 +51,15 @@ export class MapboxGLMap {
return this._map.subscribe(run, invalidate); return this._map.subscribe(run, invalidate);
} }
init( init(language: string, hash: boolean, geocoder: boolean, geolocate: boolean) {
accessToken: string,
language: string,
hash: boolean,
geocoder: boolean,
geolocate: boolean
) {
const map = new mapboxgl.Map({ const map = new mapboxgl.Map({
container: 'map', container: 'map',
style: { style: {
version: 8, version: 8,
sources: {}, sources: {
layers: [], 'empty-source': emptySource,
},
layers: anchorLayers,
imports: [ imports: [
{ {
id: 'basemap', id: 'basemap',
@@ -50,11 +68,6 @@ export class MapboxGLMap {
{ {
id: 'overlays', id: 'overlays',
url: '', url: '',
data: {
version: 8,
sources: {},
layers: [],
},
}, },
], ],
}, },
@@ -212,16 +225,21 @@ export class MapboxGLMap {
const map = get(this._map); const map = get(this._map);
if (map) { if (map) {
const source = get(terrainSource); const source = get(terrainSource);
if (!map.getSource(source)) { try {
map.addSource(source, terrainSources[source]); if (!map.getSource(source)) {
} map.addSource(source, terrainSources[source]);
if (map.getPitch() > 0) { }
map.setTerrain({ if (map.getPitch() > 0) {
source: source, map.setTerrain({
exaggeration: 1, source: source,
}); exaggeration: 1,
} else { });
map.setTerrain(null); } else {
map.setTerrain(null);
}
} catch (e) {
// No reliable way to check if the map is ready to add sources and layers
return;
} }
} }
} }

View File

@@ -2,6 +2,7 @@ import mapboxgl, { type LayerSpecification, type VectorSourceSpecification } fro
import { Viewer, type ViewerBearingEvent } from 'mapillary-js/dist/mapillary.module'; import { Viewer, type ViewerBearingEvent } from 'mapillary-js/dist/mapillary.module';
import 'mapillary-js/dist/mapillary.css'; import 'mapillary-js/dist/mapillary.css';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor'; import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
const mapillarySource: VectorSourceSpecification = { const mapillarySource: VectorSourceSpecification = {
type: 'vector', type: 'vector',
@@ -99,10 +100,10 @@ export class MapillaryLayer {
this.map.addSource('mapillary', mapillarySource); this.map.addSource('mapillary', mapillarySource);
} }
if (!this.map.getLayer('mapillary-sequence')) { if (!this.map.getLayer('mapillary-sequence')) {
this.map.addLayer(mapillarySequenceLayer); this.map.addLayer(mapillarySequenceLayer, ANCHOR_LAYER_KEY.mapillary);
} }
if (!this.map.getLayer('mapillary-image')) { if (!this.map.getLayer('mapillary-image')) {
this.map.addLayer(mapillaryImageLayer); this.map.addLayer(mapillaryImageLayer, ANCHOR_LAYER_KEY.mapillary);
} }
this.map.on('style.load', this.addBinded); this.map.on('style.load', this.addBinded);
this.map.on('mouseenter', 'mapillary-image', this.onMouseEnterBinded); this.map.on('mouseenter', 'mapillary-image', this.onMouseEnterBinded);

View File

@@ -15,7 +15,7 @@
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { getURLForLanguage } from '$lib/utils'; import { getURLForLanguage } from '$lib/utils';
import { Trash2 } from '@lucide/svelte'; import { Trash2 } from '@lucide/svelte';
import { map } from '$lib/components/map/map'; import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import type { GeoJSONSource } from 'mapbox-gl'; import type { GeoJSONSource } from 'mapbox-gl';
import { selection } from '$lib/logic/selection'; import { selection } from '$lib/logic/selection';
import { fileActions } from '$lib/logic/file-actions'; import { fileActions } from '$lib/logic/file-actions';
@@ -63,15 +63,18 @@
}); });
} }
if (!$map.getLayer('rectangle')) { if (!$map.getLayer('rectangle')) {
$map.addLayer({ $map.addLayer(
id: 'rectangle', {
type: 'fill', id: 'rectangle',
source: 'rectangle', type: 'fill',
paint: { source: 'rectangle',
'fill-color': 'SteelBlue', paint: {
'fill-opacity': 0.5, 'fill-color': 'SteelBlue',
'fill-opacity': 0.5,
},
}, },
}); ANCHOR_LAYER_KEY.interactions
);
} }
} }
} }

View File

@@ -2,7 +2,6 @@
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import Help from '$lib/components/Help.svelte'; import Help from '$lib/components/Help.svelte';
import { MountainSnow } from '@lucide/svelte'; import { MountainSnow } from '@lucide/svelte';
import { map } from '$lib/components/map/map';
import { i18n } from '$lib/i18n.svelte'; import { i18n } from '$lib/i18n.svelte';
import { getURLForLanguage } from '$lib/utils'; import { getURLForLanguage } from '$lib/utils';
import { selection } from '$lib/logic/selection'; import { selection } from '$lib/logic/selection';
@@ -20,11 +19,7 @@
variant="outline" variant="outline"
class="whitespace-normal h-fit" class="whitespace-normal h-fit"
disabled={!validSelection} disabled={!validSelection}
onclick={() => { onclick={() => fileActions.addElevationToSelection()}
if ($map) {
fileActions.addElevationToSelection($map);
}
}}
> >
<MountainSnow size="16" class="shrink-0" /> <MountainSnow size="16" class="shrink-0" />
{i18n._('toolbar.elevation.button')} {i18n._('toolbar.elevation.button')}

View File

@@ -1,5 +1,5 @@
import { ListItem, ListTrackSegmentItem } from '$lib/components/file-list/file-list'; import { ListItem, ListTrackSegmentItem } from '$lib/components/file-list/file-list';
import { map } from '$lib/components/map/map'; import { ANCHOR_LAYER_KEY, map } from '$lib/components/map/map';
import { fileActions } from '$lib/logic/file-actions'; import { fileActions } from '$lib/logic/file-actions';
import { GPXFileStateCollectionObserver, type GPXFileState } from '$lib/logic/file-state'; import { GPXFileStateCollectionObserver, type GPXFileState } from '$lib/logic/file-state';
import { selection } from '$lib/logic/selection'; import { selection } from '$lib/logic/selection';
@@ -144,17 +144,18 @@ export class ReducedGPXLayerCollection {
}); });
} }
if (!map_.getLayer('simplified')) { if (!map_.getLayer('simplified')) {
map_.addLayer({ map_.addLayer(
id: 'simplified', {
type: 'line', id: 'simplified',
source: 'simplified', type: 'line',
paint: { source: 'simplified',
'line-color': 'white', paint: {
'line-width': 3, 'line-color': 'white',
'line-width': 3,
},
}, },
}); ANCHOR_LAYER_KEY.interactions
} else { );
map_.moveLayer('simplified');
} }
} }

View File

@@ -21,7 +21,7 @@
SquareArrowUpLeft, SquareArrowUpLeft,
SquareArrowOutDownRight, SquareArrowOutDownRight,
} from '@lucide/svelte'; } from '@lucide/svelte';
import { routingProfiles } from '$lib/components/toolbar/tools/routing/routing'; import { brouterProfiles } from '$lib/components/toolbar/tools/routing/routing';
import { i18n } from '$lib/i18n.svelte'; import { i18n } from '$lib/i18n.svelte';
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition';
import { import {
@@ -167,7 +167,7 @@
{i18n._(`toolbar.routing.activities.${$routingProfile}`)} {i18n._(`toolbar.routing.activities.${$routingProfile}`)}
</Select.Trigger> </Select.Trigger>
<Select.Content> <Select.Content>
{#each Object.keys(routingProfiles) as profile} {#each Object.keys(brouterProfiles) as profile}
<Select.Item value={profile} <Select.Item value={profile}
>{i18n._( >{i18n._(
`toolbar.routing.activities.${profile}` `toolbar.routing.activities.${profile}`

View File

@@ -731,7 +731,17 @@ export class RoutingControls {
try { try {
response = await route(targetCoordinates); response = await route(targetCoordinates);
} catch (e: any) { } catch (e: any) {
toast.error(i18n._(e.message, e.message)); if (e.message.includes('from-position not mapped in existing datafile')) {
toast.error(i18n._('toolbar.routing.error.from'));
} else if (e.message.includes('via1-position not mapped in existing datafile')) {
toast.error(i18n._('toolbar.routing.error.via'));
} else if (e.message.includes('to-position not mapped in existing datafile')) {
toast.error(i18n._('toolbar.routing.error.to'));
} else if (e.message.includes('Time-out')) {
toast.error(i18n._('toolbar.routing.error.timeout'));
} else {
toast.error(e.message);
}
return false; return false;
} }

View File

@@ -6,213 +6,37 @@ import { get } from 'svelte/store';
const { routing, routingProfile, privateRoads } = settings; const { routing, routingProfile, privateRoads } = settings;
export type RoutingProfile = { export const brouterProfiles: { [key: string]: string } = {
engine: 'graphhopper' | 'brouter'; bike: 'Trekking-dry',
profile: string; racing_bike: 'fastbike',
}; gravel_bike: 'gravel',
mountain_bike: 'MTB',
export const routingProfiles: { [key: string]: RoutingProfile } = { foot: 'Hiking-Alpine-SAC6',
bike: { engine: 'graphhopper', profile: 'bike' }, motorcycle: 'Car-FastEco',
racing_bike: { engine: 'graphhopper', profile: 'racingbike' }, water: 'river',
gravel_bike: { engine: 'graphhopper', profile: 'gravelbike' }, railway: 'rail',
mountain_bike: { engine: 'graphhopper', profile: 'mtb' },
foot: { engine: 'graphhopper', profile: 'foot' },
motorcycle: { engine: 'graphhopper', profile: 'motorcycle' },
water: { engine: 'brouter', profile: 'river' },
railway: { engine: 'brouter', profile: 'rail' },
}; };
export function route(points: Coordinates[]): Promise<TrackPoint[]> { export function route(points: Coordinates[]): Promise<TrackPoint[]> {
if (get(routing)) { if (get(routing)) {
const profile = routingProfiles[get(routingProfile)]; return getRoute(points, brouterProfiles[get(routingProfile)], get(privateRoads));
if (profile.engine === 'graphhopper') {
return getGraphHopperRoute(points, profile.profile, get(privateRoads));
} else {
return getBRouterRoute(points, profile.profile);
}
} else { } else {
return getIntermediatePoints(points); return getIntermediatePoints(points);
} }
} }
const graphhopperDetails = ['road_class', 'surface', 'hike_rating', 'mtb_rating']; async function getRoute(
const hikeRatingToSACScale: { [key: string]: string } = {
'1': 'hiking',
'2': 'mountain_hiking',
'3': 'demanding_mountain_hiking',
'4': 'alpine_hiking',
'5': 'demanding_alpine_hiking',
'6': 'difficult_alpine_hiking',
};
const mtbRatingToScale: { [key: string]: string } = {
'1': '0',
'2': '1',
'3': '2',
'4': '3',
'5': '4',
'6': '5',
'7': '6',
};
const graphhopperBlockPrivateCustomModels: { [key: string]: any } = {
bike: {
priority: [
{
if: 'bike_road_access == PRIVATE',
multiply_by: '0.0',
},
],
},
racingbike: {
priority: [
{
if: 'bike_road_access == PRIVATE',
multiply_by: '0.0',
},
],
},
gravelbike: {
priority: [
{
if: 'bike_road_access == PRIVATE',
multiply_by: '0.0',
},
],
},
mtb: {
priority: [
{
if: 'bike_road_access == PRIVATE',
multiply_by: '0.0',
},
],
},
foot: {
priority: [
{
if: 'foot_road_access == PRIVATE',
multiply_by: '0.0',
},
],
},
motorcycle: {
priority: [
{
if: 'road_access == PRIVATE',
multiply_by: '0.0',
},
],
},
};
async function getGraphHopperRoute(
points: Coordinates[], points: Coordinates[],
graphHopperProfile: string, brouterProfile: string,
privateRoads: boolean privateRoads: boolean
): Promise<TrackPoint[]> { ): Promise<TrackPoint[]> {
let response = await fetch('https://graphhopper.gpx.studio/route', { let url = `https://brouter.gpx.studio?lonlats=${points.map((point) => `${point.lon.toFixed(8)},${point.lat.toFixed(8)}`).join('|')}&profile=${brouterProfile + (privateRoads ? '-private' : '')}&format=geojson&alternativeidx=0`;
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
points: points.map((point) => [point.lon, point.lat]),
profile: graphHopperProfile,
elevation: true,
points_encoded: false,
details: graphhopperDetails,
custom_model: privateRoads
? {}
: graphhopperBlockPrivateCustomModels[graphHopperProfile] || {},
}),
});
if (!response.ok) {
const error = await response.json();
if (error.message.includes('Cannot find point 0')) {
throw new Error('toolbar.routing.error.from');
} else if (error.message.includes('Cannot find point 1')) {
if (points.length == 3) {
throw new Error('toolbar.routing.error.via');
} else {
throw new Error('toolbar.routing.error.to');
}
} else if (error.hints[0].details.includes('PointDistanceExceededException')) {
throw new Error('toolbar.routing.error.distance');
} else if (error.hints[0].details.includes('ConnectionNotFoundException')) {
throw new Error('toolbar.routing.error.connection');
} else {
throw new Error(error.message);
}
}
let json = await response.json();
let route: TrackPoint[] = [];
let coordinates = json.paths[0].points.coordinates;
let details = json.paths[0].details;
for (let i = 0; i < coordinates.length; i++) {
route.push(
new TrackPoint({
attributes: {
lat: coordinates[i][1],
lon: coordinates[i][0],
},
ele: coordinates[i][2] ?? (i > 0 ? route[i - 1].ele : 0),
extensions: {},
})
);
}
for (let key of graphhopperDetails) {
let detail = details[key];
for (let i = 0; i < detail.length; i++) {
for (let j = detail[i][0]; j < detail[i][1] + (i == detail.length - 1); j++) {
if (detail[i][2] !== undefined && detail[i][2] !== 'missing') {
if (key === 'road_class') {
route[j].setExtension('highway', detail[i][2]);
} else if (key === 'hike_rating') {
const sacScale = hikeRatingToSACScale[detail[i][2]];
if (sacScale) {
route[j].setExtension('sac_scale', sacScale);
}
} else if (key === 'mtb_rating') {
const mtbScale = mtbRatingToScale[detail[i][2]];
if (mtbScale) {
route[j].setExtension('mtb_scale', mtbScale);
}
} else if (key === 'surface' && detail[i][2] !== 'other') {
route[j].setExtension('surface', detail[i][2]);
}
}
}
}
}
return route;
}
async function getBRouterRoute(
points: Coordinates[],
brouterProfile: string
): Promise<TrackPoint[]> {
let url = `https://brouter.de/brouter?lonlats=${points.map((point) => `${point.lon.toFixed(8)},${point.lat.toFixed(8)}`).join('|')}&profile=${brouterProfile}&format=geojson&alternativeidx=0`;
let response = await fetch(url); let response = await fetch(url);
// Check if the response is ok
if (!response.ok) { if (!response.ok) {
const error = await response.text(); throw new Error(`${await response.text()}`);
if (error.includes('from-position not mapped in existing datafile')) {
throw new Error('toolbar.routing.error.from');
} else if (error.includes('via1-position not mapped in existing datafile')) {
throw new Error('toolbar.routing.error.via');
} else if (error.includes('to-position not mapped in existing datafile')) {
throw new Error('toolbar.routing.error.to');
} else if (error.includes('Time-out')) {
throw new Error('toolbar.routing.error.timeout');
} else {
throw new Error(error);
}
} }
let geojson = await response.json(); let geojson = await response.json();
@@ -228,13 +52,14 @@ async function getBRouterRoute(
let tags = messageIdx < messages.length ? getTags(messages[messageIdx][tagIdx]) : {}; let tags = messageIdx < messages.length ? getTags(messages[messageIdx][tagIdx]) : {};
for (let i = 0; i < coordinates.length; i++) { for (let i = 0; i < coordinates.length; i++) {
let coord = coordinates[i];
route.push( route.push(
new TrackPoint({ new TrackPoint({
attributes: { attributes: {
lat: coordinates[i][1], lat: coord[1],
lon: coordinates[i][0], lon: coord[0],
}, },
ele: coordinates[i][2] ?? (i > 0 ? route[i - 1].ele : 0), ele: coord[2] ?? (i > 0 ? route[i - 1].ele : 0),
}) })
); );

View File

@@ -8,6 +8,7 @@ import { get } from 'svelte/store';
import { fileStateCollection } from '$lib/logic/file-state'; import { fileStateCollection } from '$lib/logic/file-state';
import { fileActions } from '$lib/logic/file-actions'; import { fileActions } from '$lib/logic/file-actions';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor'; import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
export class SplitControls { export class SplitControls {
map: mapboxgl.Map; map: mapboxgl.Map;
@@ -108,24 +109,25 @@ export class SplitControls {
} }
if (!this.map.getLayer('split-controls')) { if (!this.map.getLayer('split-controls')) {
this.map.addLayer({ this.map.addLayer(
id: 'split-controls', {
type: 'symbol', id: 'split-controls',
source: 'split-controls', type: 'symbol',
layout: { source: 'split-controls',
'icon-image': 'split-control', layout: {
'icon-size': 0.25, 'icon-image': 'split-control',
'icon-padding': 0, 'icon-size': 0.25,
'icon-padding': 0,
},
filter: ['<=', ['get', 'minZoom'], ['zoom']],
}, },
filter: ['<=', ['get', 'minZoom'], ['zoom']], ANCHOR_LAYER_KEY.interactions
}); );
this.map.on('mouseenter', 'split-controls', this.layerOnMouseEnterBinded); this.map.on('mouseenter', 'split-controls', this.layerOnMouseEnterBinded);
this.map.on('mouseleave', 'split-controls', this.layerOnMouseLeaveBinded); this.map.on('mouseleave', 'split-controls', this.layerOnMouseLeaveBinded);
this.map.on('click', 'split-controls', this.layerOnClickBinded); this.map.on('click', 'split-controls', this.layerOnClickBinded);
} }
this.map.moveLayer('split-controls');
} catch (e) { } catch (e) {
// No reliable way to check if the map is ready to add sources and layers // No reliable way to check if the map is ready to add sources and layers
} }

View File

@@ -29,13 +29,13 @@ Pots arrossegar y deixar arxius directament des del seu sistema d'arxius cap a l
Crear una còpia dels arxius seleccionats. Crear una còpia dels arxius seleccionats.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Esborra
Delete the currently selected files. Esborra l'arxiu seleccinat.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Esborra-ho tot
Delete all files. Esborra tots els fitxers.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Exportar... ### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Exportar...

View File

@@ -1,5 +1,5 @@
Mapbox ist das Unternehmen, das einige der schönen Karten auf dieser Website zur Verfügung stellt. Mapbox stellt einige der auf dieser Website verwendeten Karten bereit.
Sie entwickeln auch die <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">Karten-Engine</a> welche **gpx.studio** unterstützt. Sie entwickeln auch die <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">Karten-Engine</a>, die **gpx.studio** unterstützt.
Wir sind äußerst glücklich und dankbar, Teil ihres <a href="https://mapbox.com/community" target="_blank">Community</a> Programms zu sein, das gemeinnützige Organisationen, Bildungseinrichtungen und Organisationen mit positivem Einfluss unterstützt. Wir sind froh und dankbar, Teil ihres <a href="https://mapbox.com/community" target="_blank">Community</a> Programms zu sein, das gemeinnützige Organisationen, Bildungseinrichtungen und Organisationen unterstützt.
Diese Partnerschaft ermöglicht es **gpx.studio**, von den Mapbox-Tools zu ermäßigten Preisen zu profitieren, was erheblich zur finanziellen Tragfähigkeit des Projekts beiträgt und es uns ermöglicht, die bestmögliche Benutzererfahrung zu bieten. Diese Partnerschaft ermöglicht es **gpx.studio**, von den Mapbox-Tools zu ermäßigten Preisen zu profitieren, was erheblich zur finanziellen Tragfähigkeit des Projekts beiträgt und es uns ermöglicht, die bestmögliche Benutzererfahrung zu bieten.

View File

@@ -35,7 +35,7 @@ Supprimer les fichiers sélectionnés.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Supprimer tout ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Supprimer tout
Supprimer toutes les fichiers. Supprimer tous les fichiers.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Exporter... ### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Exporter...

View File

@@ -50,7 +50,7 @@ Clicando com o botão direito em uma aba arquivo, você pode acessar as mesmas a
As mentioned in the [view options section](./menu/view), you can switch to a tree layout for the files list. As mentioned in the [view options section](./menu/view), you can switch to a tree layout for the files list.
This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map. This layout is ideal for managing a large number of open files, as it organizes them into a vertical list on the right side of the map.
In addition, the file tree view enables you to inspect the [tracks, segments, and points of interest](./gpx) contained inside the files through collapsible sections. Além disso, a exibição de árvore de arquivos permite que você inspecione as [faixas, segmentos, e pontos de interesse](./gpx) contidos dentro dos arquivos através de seções recolhidas.
Você também pode aplicar as [ações de edição](./menu/edit) e [ferramentas](./toolbar) para itens de arquivos internos. Você também pode aplicar as [ações de edição](./menu/edit) e [ferramentas](./toolbar) para itens de arquivos internos.
Além disso, você pode arrastar e soltar os itens internos para reordená-los, ou movê-los na hierarquia ou até mesmo para outro arquivo. Além disso, você pode arrastar e soltar os itens internos para reordená-los, ou movê-los na hierarquia ou até mesmo para outro arquivo.
@@ -105,6 +105,6 @@ Using the <kbd><ChartNoAxesColumn size="16" class="inline-block" style="margin-b
- **slope** information computed from the elevation data, or - **slope** information computed from the elevation data, or
- **surface** or **category** data coming from <a href="https://www.openstreetmap.org/" target="_blank">OpenStreetMap</a>'s <a href="https://wiki.openstreetmap.org/wiki/Key:surface" target="_blank">surface</a> and <a href="https://wiki.openstreetmap.org/wiki/Key:highway" target="_blank">highway</a> tags. - **surface** or **category** data coming from <a href="https://www.openstreetmap.org/" target="_blank">OpenStreetMap</a>'s <a href="https://wiki.openstreetmap.org/wiki/Key:surface" target="_blank">surface</a> and <a href="https://wiki.openstreetmap.org/wiki/Key:highway" target="_blank">highway</a> tags.
This is only available for files created with **gpx.studio**. Isso só está disponível para arquivos criados com **gpx.studio**.
If your selection includes it, you can also visualize: **speed**, **heart rate**, **cadence**, **temperature** and **power** data on the elevation profile. If your selection includes it, you can also visualize: **speed**, **heart rate**, **cadence**, **temperature** and **power** data on the elevation profile.

View File

@@ -35,7 +35,7 @@ Delete the currently selected files.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
Delete all files. Apagar todos os arquivos.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Exportar... ### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Exportar...

View File

@@ -2,7 +2,8 @@
import { HeartHandshake } from '@lucide/svelte'; import { HeartHandshake } from '@lucide/svelte';
</script> </script>
## <HeartHandshake size="18" class="inline-block align-baseline" /> Help keep the website free (and ad-free) ## <HeartHandshake size="18" class="inline-block align-baseline" />
Допоможіть нам залишати цей сайт безкоштовним (та без реклами)
Кожного разу, коли ви додаєте або переміщуєте GPS точки, наші сервери обчислюють найкращий маршрут на мережі доріг. Кожного разу, коли ви додаєте або переміщуєте GPS точки, наші сервери обчислюють найкращий маршрут на мережі доріг.
Ми також використовуємо API від <a href="https://mapbox.com" target="_blank">Mapbox</a> для зображення красивих карт, отримання даних висот та можливості пошуку місць. Ми також використовуємо API від <a href="https://mapbox.com" target="_blank">Mapbox</a> для зображення красивих карт, отримання даних висот та можливості пошуку місць.

View File

@@ -2,7 +2,7 @@
import { Languages } from '@lucide/svelte'; import { Languages } from '@lucide/svelte';
</script> </script>
## <Languages size="18" class="inline-block align-baseline" /> Translation ## <Languages size="18" class="inline-block align-baseline" /> Переклад
Сайт перекладається волонтерами з використанням платформи для спільного перекладу. Сайт перекладається волонтерами з використанням платформи для спільного перекладу.
Ви можете зробити свій внесок, додаючи або покращуючи переклади в нашому <a href="https://crowdin.com/project/gpxstudio" target="_blank">проєкті Crowdin</a>. Ви можете зробити свій внесок, додаючи або покращуючи переклади в нашому <a href="https://crowdin.com/project/gpxstudio" target="_blank">проєкті Crowdin</a>.

View File

@@ -47,6 +47,6 @@ Open the export dialog to save all files to your computer.
<DocsNote type="warning"> <DocsNote type="warning">
If your download does not start after clicking the download button, please check your browser settings to allow downloads from <b>gpx.studio</b>. Якщо завантаження не починається після натискання кнопки завантаження, будь ласка, перевірте налаштування браузера, щоб дозволити завантаження з <b>gpx.studio</b>.
</DocsNote> </DocsNote>

View File

@@ -31,7 +31,7 @@ Create a copy of the currently selected files.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
Delete the currently selected files. .
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all

View File

@@ -807,7 +807,7 @@ export const fileActions = {
}); });
}); });
}, },
addElevationToSelection: async (map: mapboxgl.Map) => { addElevationToSelection: async () => {
if (get(selection).size === 0) { if (get(selection).size === 0) {
return; return;
} }

View File

@@ -190,6 +190,8 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -28,7 +28,7 @@
"undo": "Desfer", "undo": "Desfer",
"redo": "Refer", "redo": "Refer",
"delete": "Elimina el track", "delete": "Elimina el track",
"delete_all": "Delete all", "delete_all": "Esborra-ho tot",
"select_all": "Seleccionar-ho tot", "select_all": "Seleccionar-ho tot",
"view": "Vista", "view": "Vista",
"elevation_profile": "Perfil delevacions", "elevation_profile": "Perfil delevacions",
@@ -80,7 +80,7 @@
"center": "Centrar", "center": "Centrar",
"open_in": "Obrir amb", "open_in": "Obrir amb",
"copy_coordinates": "Copiar coordenades", "copy_coordinates": "Copiar coordenades",
"edit_osm": "Edit in OpenStreetMap" "edit_osm": "Edita a OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -190,6 +190,8 @@
"from": "El punt d'inici és massa lluny de la via més propera", "from": "El punt d'inici és massa lluny de la via més propera",
"via": "El punt és massa lluny de la via més propera", "via": "El punt és massa lluny de la via més propera",
"to": "El punt final és massa lluny de la via més propera", "to": "El punt final és massa lluny de la via més propera",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "El càlcul de la ruta tarda més del compte, prova d'afegir punts més propers entre si" "timeout": "El càlcul de la ruta tarda més del compte, prova d'afegir punts més propers entre si"
} }
}, },
@@ -282,7 +284,7 @@
"update": "Actualitza la capa" "update": "Actualitza la capa"
}, },
"opacity": "Opacitat de la superposició", "opacity": "Opacitat de la superposició",
"terrain": "Terrain source", "terrain": "Terreny",
"label": { "label": {
"basemaps": "Mapes base", "basemaps": "Mapes base",
"overlays": "Capes", "overlays": "Capes",
@@ -356,7 +358,7 @@
"water": "Aigua", "water": "Aigua",
"shower": "Dutxa", "shower": "Dutxa",
"shelter": "Refugi", "shelter": "Refugi",
"cemetery": "Cemetery", "cemetery": "Cementiri",
"motorized": "Cotxes i motos", "motorized": "Cotxes i motos",
"fuel-station": "Gasolinera", "fuel-station": "Gasolinera",
"parking": "Aparcament", "parking": "Aparcament",

View File

@@ -190,6 +190,8 @@
"from": "Počáteční bod je příliš daleko od nejbližší cesty", "from": "Počáteční bod je příliš daleko od nejbližší cesty",
"via": "Průchozí bod je příliš daleko od nejbližší cesty", "via": "Průchozí bod je příliš daleko od nejbližší cesty",
"to": "Koncový bod je příliš daleko od nejbližší cesty", "to": "Koncový bod je příliš daleko od nejbližší cesty",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Výpočet trasy trval příliš dlouho, zkuste přidat body blíže k sobě" "timeout": "Výpočet trasy trval příliš dlouho, zkuste přidat body blíže k sobě"
} }
}, },
@@ -282,7 +284,7 @@
"update": "Aktualizovat vrstvu" "update": "Aktualizovat vrstvu"
}, },
"opacity": "Průhlednost překryvu", "opacity": "Průhlednost překryvu",
"terrain": "Terrain source", "terrain": "Zdroj terénu",
"label": { "label": {
"basemaps": "Základní mapy", "basemaps": "Základní mapy",
"overlays": "Překrytí", "overlays": "Překrytí",

View File

@@ -190,6 +190,8 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -21,14 +21,14 @@
"export_all": "Alle exportieren...", "export_all": "Alle exportieren...",
"export_options": "Export-Einstellungen", "export_options": "Export-Einstellungen",
"support_message": "Das Tool darf frei benutzt werden, aber es darf nicht woanders aufgesetzt werden. Bitte unterstützen Sie die Website, wenn Sie sie häufig benutzen. Vielen Dank!", "support_message": "Das Tool darf frei benutzt werden, aber es darf nicht woanders aufgesetzt werden. Bitte unterstützen Sie die Website, wenn Sie sie häufig benutzen. Vielen Dank!",
"support_button": "Hilf dabei, die Webseite kostenlos zu belassen", "support_button": "Hilf uns, die Website weiterhin kostenlos bereitzustellen",
"download_file": "Datei herunterladen", "download_file": "Datei herunterladen",
"download_files": "Dateien herunterladen", "download_files": "Dateien herunterladen",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"undo": "Rückgängig", "undo": "Rückgängig",
"redo": "Wiederholen", "redo": "Wiederholen",
"delete": "Löschen", "delete": "Löschen",
"delete_all": "Delete all", "delete_all": "Alle löschen",
"select_all": "Alle auswählen", "select_all": "Alle auswählen",
"view": "Ansicht", "view": "Ansicht",
"elevation_profile": "Höhenprofil", "elevation_profile": "Höhenprofil",
@@ -80,7 +80,7 @@
"center": "Zentrieren", "center": "Zentrieren",
"open_in": "Öffnen in", "open_in": "Öffnen in",
"copy_coordinates": "Koordinaten kopieren", "copy_coordinates": "Koordinaten kopieren",
"edit_osm": "Edit in OpenStreetMap" "edit_osm": "In OpenStreetMap bearbeiten"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -190,6 +190,8 @@
"from": "Der Startpunkt ist zu weit von der nächsten Straße entfernt", "from": "Der Startpunkt ist zu weit von der nächsten Straße entfernt",
"via": "Der Via-Punkt ist zu weit entfernt von der nächsten Straße", "via": "Der Via-Punkt ist zu weit entfernt von der nächsten Straße",
"to": "Der Endpunkt ist zu weit von der nächsten Straße entfernt", "to": "Der Endpunkt ist zu weit von der nächsten Straße entfernt",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route-Berechnung benötigte zu viel Zeit; versuche, die Punkte näher aneinander zu setzen" "timeout": "Route-Berechnung benötigte zu viel Zeit; versuche, die Punkte näher aneinander zu setzen"
} }
}, },
@@ -282,7 +284,7 @@
"update": "Layer aktualisieren" "update": "Layer aktualisieren"
}, },
"opacity": "Deckkraft der Überlagerung", "opacity": "Deckkraft der Überlagerung",
"terrain": "Terrain source", "terrain": "Geländequelle",
"label": { "label": {
"basemaps": "Basiskarte", "basemaps": "Basiskarte",
"overlays": "Ebenen", "overlays": "Ebenen",
@@ -326,7 +328,7 @@
"usgs": "USGS", "usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel", "bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite", "cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade", "mapterhornHillshade": "MapTiler Hillshade",
"openRailwayMap": "OpenRailwayMap", "openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Neigung", "swisstopoSlope": "swisstopo Neigung",
"swisstopoHiking": "swisstopo Wandern", "swisstopoHiking": "swisstopo Wandern",
@@ -356,7 +358,7 @@
"water": "Trinkwasser", "water": "Trinkwasser",
"shower": "Dusche", "shower": "Dusche",
"shelter": "Unterstand", "shelter": "Unterstand",
"cemetery": "Cemetery", "cemetery": "Friedhof",
"motorized": "Autos und Motorräder", "motorized": "Autos und Motorräder",
"fuel-station": "Tankstelle", "fuel-station": "Tankstelle",
"parking": "Parken", "parking": "Parken",

View File

@@ -190,6 +190,8 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -190,8 +190,6 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is to far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "El punto de inicio está demasiado lejos de la carretera más cercana", "from": "El punto de inicio está demasiado lejos de la carretera más cercana",
"via": "El punto de paso está demasiado lejos de la carretera más cercana", "via": "El punto de paso está demasiado lejos de la carretera más cercana",
"to": "El punto final está demasiado lejos de la carretera más cercana", "to": "El punto final está demasiado lejos de la carretera más cercana",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Calcular la ruta llevó demasiado tiempo, intente añadir puntos más cercanos entre ellos" "timeout": "Calcular la ruta llevó demasiado tiempo, intente añadir puntos más cercanos entre ellos"
} }
}, },
@@ -282,7 +284,7 @@
"update": "Actualizar capa" "update": "Actualizar capa"
}, },
"opacity": "Opacidad de la capa superpuesta", "opacity": "Opacidad de la capa superpuesta",
"terrain": "Terrain source", "terrain": "Origen del terreno",
"label": { "label": {
"basemaps": "Mapas base", "basemaps": "Mapas base",
"overlays": "Capas", "overlays": "Capas",

View File

@@ -190,6 +190,8 @@
"from": "Hasiera puntua errepide hurbilenetik oso hurrun dago", "from": "Hasiera puntua errepide hurbilenetik oso hurrun dago",
"via": "Puntua errepide hurbilenetik oso hurrun dago", "via": "Puntua errepide hurbilenetik oso hurrun dago",
"to": "Bukaera puntua errepide hurbilenetik oso hurrun dago", "to": "Bukaera puntua errepide hurbilenetik oso hurrun dago",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Ibilbidea kalkulatzea luzeegi joan da, saiatu hurbilago dauden puntuak gehitzen" "timeout": "Ibilbidea kalkulatzea luzeegi joan da, saiatu hurbilago dauden puntuak gehitzen"
} }
}, },
@@ -282,7 +284,7 @@
"update": "Eguneratu geruza" "update": "Eguneratu geruza"
}, },
"opacity": "Geruzaren opakutasuna", "opacity": "Geruzaren opakutasuna",
"terrain": "Terrain source", "terrain": "Lurrazala",
"label": { "label": {
"basemaps": "Oinarrizko mapak", "basemaps": "Oinarrizko mapak",
"overlays": "Geruzak", "overlays": "Geruzak",

View File

@@ -190,6 +190,8 @@
"from": "Aloituspiste on liian kaukana lähimmästä tiestä", "from": "Aloituspiste on liian kaukana lähimmästä tiestä",
"via": "Reittipiste on liian kaukana lähimmästä tiestä", "via": "Reittipiste on liian kaukana lähimmästä tiestä",
"to": "Päätepiste on liian kaukana lähimmästä tiestä", "to": "Päätepiste on liian kaukana lähimmästä tiestä",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Reitin laskenta kesti liian kauan. Tihennä reittipisteitä" "timeout": "Reitin laskenta kesti liian kauan. Tihennä reittipisteitä"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "Le point de départ est trop éloigné de la route la plus proche", "from": "Le point de départ est trop éloigné de la route la plus proche",
"via": "Le point de passage est trop éloigné de la route la plus proche", "via": "Le point de passage est trop éloigné de la route la plus proche",
"to": "Le point d'arrivée est trop éloigné de la route la plus proche", "to": "Le point d'arrivée est trop éloigné de la route la plus proche",
"distance": "Le point d'arrivée est trop loin du point de départ",
"connection": "Aucune connexion trouvée entre les points",
"timeout": "Le calcul de l'itinéraire a pris trop de temps, essayez d'ajouter des points plus rapprochés" "timeout": "Le calcul de l'itinéraire a pris trop de temps, essayez d'ajouter des points plus rapprochés"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "נקודת ההתחלה רחוקה מדיי מהכביש הקרוב ביותר", "from": "נקודת ההתחלה רחוקה מדיי מהכביש הקרוב ביותר",
"via": "נקודת המעבר רחוקה מדיי מהכביש הקרוב ביותר", "via": "נקודת המעבר רחוקה מדיי מהכביש הקרוב ביותר",
"to": "נקודת הסיום רחוקה מדיי מהכביש הקרוב ביותר", "to": "נקודת הסיום רחוקה מדיי מהכביש הקרוב ביותר",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "A kiindulási pont túl messze van a legközelebbi úttól", "from": "A kiindulási pont túl messze van a legközelebbi úttól",
"via": "A köztes pont túl messze van a legközelebbi úttól", "via": "A köztes pont túl messze van a legközelebbi úttól",
"to": "A végpont túl messze van a legközelebbi úttól", "to": "A végpont túl messze van a legközelebbi úttól",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Az útvonal kiszámítása túl sokáig tartott. Próbáljon meg közelebbi pontokat adni egymáshoz" "timeout": "Az útvonal kiszámítása túl sokáig tartott. Próbáljon meg közelebbi pontokat adni egymáshoz"
} }
}, },
@@ -235,13 +237,13 @@
"help_no_selection": "Select a file item to request elevation data." "help_no_selection": "Select a file item to request elevation data."
}, },
"waypoint": { "waypoint": {
"tooltip": "Create and edit points of interest", "tooltip": "PoI létrehozása és módosítása",
"icon": "Icon", "icon": "Ikon",
"link": "Link", "link": "Link",
"longitude": "Longitude", "longitude": "Földrajzi hosszúság",
"latitude": "Szélesség", "latitude": "Szélesség",
"create": "POI fájlba mentése", "create": "POI fájlba mentése",
"add": "Add point of interest to file", "add": "PoI fájlhoz adása",
"help": "Töltsd ki az űrlapot egy új POI létrehozásához, vagy kattints egy meglévőre a szerkesztéshez. Kattints a térképre a koordináták megadásához, vagy áthelyezéshez egérrel húzd el a POI-kat.", "help": "Töltsd ki az űrlapot egy új POI létrehozásához, vagy kattints egy meglévőre a szerkesztéshez. Kattints a térképre a koordináták megadásához, vagy áthelyezéshez egérrel húzd el a POI-kat.",
"help_no_selection": "Select a file to create or edit points of interest." "help_no_selection": "Select a file to create or edit points of interest."
}, },
@@ -249,8 +251,8 @@
"tooltip": "Reduce the number of GPS points", "tooltip": "Reduce the number of GPS points",
"tolerance": "Tűréshatár", "tolerance": "Tűréshatár",
"number_of_points": "GPS pontok száma", "number_of_points": "GPS pontok száma",
"button": "Minify", "button": "Minimalizálás",
"help": "Use the slider to choose the number of GPS points to keep.", "help": "",
"help_no_selection": "Select a trace to reduce the number of its GPS points." "help_no_selection": "Select a trace to reduce the number of its GPS points."
}, },
"clean": { "clean": {

View File

@@ -190,6 +190,8 @@
"from": "Titik awal terlalu jauh dari jalan terdekat", "from": "Titik awal terlalu jauh dari jalan terdekat",
"via": "Titik via terlalu jauh dari jalan terdekat", "via": "Titik via terlalu jauh dari jalan terdekat",
"to": "Titik akhir terlalu jauh dari jalan terdekat", "to": "Titik akhir terlalu jauh dari jalan terdekat",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Perhitungan rute memakan waktu terlalu lama, coba tambahkan titik-titik yang lebih dekat satu sama lain" "timeout": "Perhitungan rute memakan waktu terlalu lama, coba tambahkan titik-titik yang lebih dekat satu sama lain"
} }
}, },
@@ -491,7 +493,7 @@
"support_button": "Support gpx.studio on Ko-fi", "support_button": "Support gpx.studio on Ko-fi",
"route_planning": "Route planning", "route_planning": "Route planning",
"route_planning_description": "An intuitive interface to create itineraries tailored to each sport, based on OpenStreetMap data.", "route_planning_description": "An intuitive interface to create itineraries tailored to each sport, based on OpenStreetMap data.",
"file_processing": "Advanced file processing", "file_processing": "Pengolahan file lanjutan",
"file_processing_description": "A suite of tools for performing all common file processing tasks, and which can be applied to multiple files at once.", "file_processing_description": "A suite of tools for performing all common file processing tasks, and which can be applied to multiple files at once.",
"maps": "Global and local maps", "maps": "Global and local maps",
"maps_description": "A large collection of basemaps, overlays and points of interest to help you craft your next outdoor adventure, or visualize your latest achievement.", "maps_description": "A large collection of basemaps, overlays and points of interest to help you craft your next outdoor adventure, or visualize your latest achievement.",

View File

@@ -190,6 +190,8 @@
"from": "Il punto di partenza è troppo lontano dalla strada più vicina", "from": "Il punto di partenza è troppo lontano dalla strada più vicina",
"via": "Il punto di arrivo è troppo lontano dalla strada più vicina", "via": "Il punto di arrivo è troppo lontano dalla strada più vicina",
"to": "Il punto di arrivo è troppo lontano dalla strada più vicina", "to": "Il punto di arrivo è troppo lontano dalla strada più vicina",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Il calcolo del percorso ha richiesto troppo tempo, prova ad aggiungere punti più vicini" "timeout": "Il calcolo del percorso ha richiesto troppo tempo, prova ad aggiungere punti più vicini"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "Pradžios taškas yra per toli nuo artimiausio kelio", "from": "Pradžios taškas yra per toli nuo artimiausio kelio",
"via": "Tarpinis taškas yra per toli nuo artimiausio kelio", "via": "Tarpinis taškas yra per toli nuo artimiausio kelio",
"to": "Pabaigos taškas yra per toli nuo artimiausio kelio", "to": "Pabaigos taškas yra per toli nuo artimiausio kelio",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Maršruto skaičiavimas užtruko per ilgai, pabandykite pridėti taškus arčiau vienas kito" "timeout": "Maršruto skaičiavimas užtruko per ilgai, pabandykite pridėti taškus arčiau vienas kito"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -54,7 +54,7 @@
"mapillary": "Mapillary", "mapillary": "Mapillary",
"google": "Google", "google": "Google",
"toggle_street_view": "Street view", "toggle_street_view": "Street view",
"layers": "Kaart lagen...", "layers": "Kaartlagen...",
"distance_markers": "Afstandsmarkeringen", "distance_markers": "Afstandsmarkeringen",
"direction_markers": "Richtingspijlen", "direction_markers": "Richtingspijlen",
"help": "Help", "help": "Help",
@@ -190,6 +190,8 @@
"from": "Het startpunt ligt te ver van de dichtstbijzijnde weg", "from": "Het startpunt ligt te ver van de dichtstbijzijnde weg",
"via": "Het via punt ligt te ver van de dichtstbijzijnde weg", "via": "Het via punt ligt te ver van de dichtstbijzijnde weg",
"to": "Het eindpunt is te ver van de dichtstbijzijnde weg", "to": "Het eindpunt is te ver van de dichtstbijzijnde weg",
"distance": "Het eindpunt is te ver vanaf het startpunt",
"connection": "Geen verbinding gevonden tussen de punten",
"timeout": "Routeberekening heeft te lang geduurd, probeer punten dichter bij elkaar toe te voegen" "timeout": "Routeberekening heeft te lang geduurd, probeer punten dichter bij elkaar toe te voegen"
} }
}, },
@@ -282,7 +284,7 @@
"update": "Update laag" "update": "Update laag"
}, },
"opacity": "Laag Transparantie", "opacity": "Laag Transparantie",
"terrain": "Terrain source", "terrain": "Terrein bron",
"label": { "label": {
"basemaps": "Basis kaarten", "basemaps": "Basis kaarten",
"overlays": "Lagen", "overlays": "Lagen",

View File

@@ -28,7 +28,7 @@
"undo": "Angre", "undo": "Angre",
"redo": "Gjenta", "redo": "Gjenta",
"delete": "Slett", "delete": "Slett",
"delete_all": "Delete all", "delete_all": "Slett alle",
"select_all": "Velg alle", "select_all": "Velg alle",
"view": "Visning", "view": "Visning",
"elevation_profile": "Høydeprofil", "elevation_profile": "Høydeprofil",
@@ -80,7 +80,7 @@
"center": "Sentrer", "center": "Sentrer",
"open_in": "Åpne I", "open_in": "Åpne I",
"copy_coordinates": "Kopier koordinater", "copy_coordinates": "Kopier koordinater",
"edit_osm": "Edit in OpenStreetMap" "edit_osm": "Rediger i OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -190,6 +190,8 @@
"from": "Startpunktet er for langt unna nærmeste vei", "from": "Startpunktet er for langt unna nærmeste vei",
"via": "Via-punktet er for langt fra nærmeste vei", "via": "Via-punktet er for langt fra nærmeste vei",
"to": "Sluttpunktet er for langt unna nærmeste vei", "to": "Sluttpunktet er for langt unna nærmeste vei",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Ruteberegning tok for lang tid, prøv å legge til flere punkter" "timeout": "Ruteberegning tok for lang tid, prøv å legge til flere punkter"
} }
}, },
@@ -356,7 +358,7 @@
"water": "Vann", "water": "Vann",
"shower": "Dusj", "shower": "Dusj",
"shelter": "Ly", "shelter": "Ly",
"cemetery": "Cemetery", "cemetery": "Gravplass",
"motorized": "Biler og motorsykler", "motorized": "Biler og motorsykler",
"fuel-station": "Bensinstasjon", "fuel-station": "Bensinstasjon",
"parking": "Parkering", "parking": "Parkering",

View File

@@ -190,6 +190,8 @@
"from": "Punkt początkowy jest zbyt daleko od najbliższej drogi", "from": "Punkt początkowy jest zbyt daleko od najbliższej drogi",
"via": "Punkt przelotowy jest zbyt daleko od najbliższej drogi", "via": "Punkt przelotowy jest zbyt daleko od najbliższej drogi",
"to": "Punkt końcowy jest zbyt daleko od najbliższej drogi", "to": "Punkt końcowy jest zbyt daleko od najbliższej drogi",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Obliczanie trasy trwało zbyt długo, spróbuj dodać punkty bliżej siebie" "timeout": "Obliczanie trasy trwało zbyt długo, spróbuj dodać punkty bliżej siebie"
} }
}, },
@@ -282,7 +284,7 @@
"update": "Zaktualizuj warstwę" "update": "Zaktualizuj warstwę"
}, },
"opacity": "Przezroczystość nakładki", "opacity": "Przezroczystość nakładki",
"terrain": "Terrain source", "terrain": "Źródło danych terenowych",
"label": { "label": {
"basemaps": "Mapy bazowe", "basemaps": "Mapy bazowe",
"overlays": "Nakładki", "overlays": "Nakładki",

View File

@@ -190,6 +190,8 @@
"from": "O ponto de partida está muito longe da estrada mais próxima", "from": "O ponto de partida está muito longe da estrada mais próxima",
"via": "O ponto intermediário está muito longe da estrada mais próxima", "via": "O ponto intermediário está muito longe da estrada mais próxima",
"to": "O ponto de chegada está muito longe da estrada mais próxima", "to": "O ponto de chegada está muito longe da estrada mais próxima",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "O cálculo da rota demorou muito tempo, tente adicionar pontos mais próximos" "timeout": "O cálculo da rota demorou muito tempo, tente adicionar pontos mais próximos"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "O ponto de partida está muito longe da estrada mais próxima", "from": "O ponto de partida está muito longe da estrada mais próxima",
"via": "O ponto de partida está muito longe da estrada mais próxima", "via": "O ponto de partida está muito longe da estrada mais próxima",
"to": "O ponto final está muito longe do ponto de chegada", "to": "O ponto final está muito longe do ponto de chegada",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "O recalculo da demorou muito tempo, tente adicionar ponto mais próximos" "timeout": "O recalculo da demorou muito tempo, tente adicionar ponto mais próximos"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "Начальная точка слишком далеко от ближайшей дороги", "from": "Начальная точка слишком далеко от ближайшей дороги",
"via": "Точка маршрута слишком далеко от ближайшей дороги", "via": "Точка маршрута слишком далеко от ближайшей дороги",
"to": "Конечная точка слишком далеко от ближайшей дороги", "to": "Конечная точка слишком далеко от ближайшей дороги",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Расчет маршрута занял слишком много времени, попробуйте добавить точки ближе" "timeout": "Расчет маршрута занял слишком много времени, попробуйте добавить точки ближе"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "Početna tačka je predaleko od najbližeg puta", "from": "Početna tačka je predaleko od najbližeg puta",
"via": "Putna tačka je predaleko od najbližeg puta", "via": "Putna tačka je predaleko od najbližeg puta",
"to": "Krajnja tačka je predaleko od najbližeg puta", "to": "Krajnja tačka je predaleko od najbližeg puta",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Računanje rute je trajalo predugo, pokušajte da dodate putne tačke bliže jednu drugoj" "timeout": "Računanje rute je trajalo predugo, pokušajte da dodate putne tačke bliže jednu drugoj"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "Startpunkten är för långt bort från närmaste väg", "from": "Startpunkten är för långt bort från närmaste väg",
"via": "Ruttpunkten är för långt bort från närmaste väg", "via": "Ruttpunkten är för långt bort från närmaste väg",
"to": "Slutpunkten är för långt bort från närmaste väg", "to": "Slutpunkten är för långt bort från närmaste väg",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Ruttberäkningen tog för lång tid, försök att lägga punkterna närmare varandra" "timeout": "Ruttberäkningen tog för lång tid, försök att lägga punkterna närmare varandra"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "Başlangıç noktası en yakın yoldan çok uzak", "from": "Başlangıç noktası en yakın yoldan çok uzak",
"via": "Geçiş noktası en yakın yoldan çok uzak", "via": "Geçiş noktası en yakın yoldan çok uzak",
"to": "Bitiş noktası en yakın yoldan çok uzak", "to": "Bitiş noktası en yakın yoldan çok uzak",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Rota hesaplaması uzun sürdü, daha yakın noktalar eklemeyi deneyin" "timeout": "Rota hesaplaması uzun sürdü, daha yakın noktalar eklemeyi deneyin"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "Початкова точка знаходиться занадто далеко від найближчої дороги", "from": "Початкова точка знаходиться занадто далеко від найближчої дороги",
"via": "Проміжна точка знаходиться занадто далеко від найближчої дороги", "via": "Проміжна точка знаходиться занадто далеко від найближчої дороги",
"to": "Кінцева точка знаходиться занадто далеко від найближчої дороги", "to": "Кінцева точка знаходиться занадто далеко від найближчої дороги",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Розрахунок маршруту є занадто довгий, спробуйте додати ближчі точки" "timeout": "Розрахунок маршруту є занадто довгий, спробуйте додати ближчі точки"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "The start point is too far from the nearest road", "from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road", "via": "The via point is too far from the nearest road",
"to": "The end point is too far from the nearest road", "to": "The end point is too far from the nearest road",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Route calculation took too long, try adding points closer together" "timeout": "Route calculation took too long, try adding points closer together"
} }
}, },

View File

@@ -190,6 +190,8 @@
"from": "起点离最近的道路太远", "from": "起点离最近的道路太远",
"via": "途径点离最近的道路太远", "via": "途径点离最近的道路太远",
"to": "终点离最近的道路太远", "to": "终点离最近的道路太远",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "轨迹计算耗时太长,请尝试添加更靠近路网的点" "timeout": "轨迹计算耗时太长,请尝试添加更靠近路网的点"
} }
}, },
@@ -282,7 +284,7 @@
"update": "更新图层" "update": "更新图层"
}, },
"opacity": "图层透明度", "opacity": "图层透明度",
"terrain": "Terrain source", "terrain": "地形来源",
"label": { "label": {
"basemaps": "底图", "basemaps": "底图",
"overlays": "叠加层", "overlays": "叠加层",
@@ -326,7 +328,7 @@
"usgs": "USGS", "usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel", "bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite", "cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade", "mapterhornHillshade": "山体阴影",
"openRailwayMap": "OpenRailwayMap", "openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "Swisstopo Slope", "swisstopoSlope": "Swisstopo Slope",
"swisstopoHiking": "Swisstopo Hiking", "swisstopoHiking": "Swisstopo Hiking",