88 Commits

Author SHA1 Message Date
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

@@ -70,8 +70,8 @@ This project has been made possible thanks to the following open source projects
- [SortableJS](https://github.com/SortableJS/Sortable) — creating a sortable file tree
- Mapping:
- [Mapbox GL JS](https://github.com/mapbox/mapbox-gl-js) — beautiful and fast interactive maps
- [GraphHopper](https://github.com/graphhopper/graphhopper) — routing engine
- [OpenStreetMap](https://www.openstreetmap.org) — map data used by Mapbox and GraphHopper
- [brouter](https://github.com/abrensch/brouter) — routing engine
- [OpenStreetMap](https://www.openstreetmap.org) — map data used by Mapbox and brouter
- Search:
- [DocSearch](https://github.com/algolia/docsearch) — search engine for the documentation

View File

@@ -1398,7 +1398,10 @@ export class TrackPoint {
: undefined;
}
setExtension(key: string, value: string) {
setExtensions(extensions: Record<string, string>) {
if (Object.keys(extensions).length === 0) {
return;
}
if (!this.extensions) {
this.extensions = {};
}
@@ -1408,12 +1411,8 @@ export class TrackPoint {
if (!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]) => {
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",
"glyphs": "https://api.maptiler.com/fonts/{fontstack}/{range}.pbf?key={key}",
"layers": [
{
"id": "background",

View File

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

View File

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

View File

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

View File

@@ -41,6 +41,7 @@
<Button
size="sm"
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)}`}
target="_blank"
>

View File

@@ -3,7 +3,7 @@ import { gpxStatistics } from '$lib/logic/statistics';
import { getConvertedDistanceToKilometers } from '$lib/units';
import type { GeoJSONSource } from 'mapbox-gl';
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';
const { distanceMarkers, distanceUnits } = settings;
@@ -44,7 +44,8 @@ export class DistanceMarkers {
});
}
if (!map_.getLayer('distance-markers')) {
map_.addLayer({
map_.addLayer(
{
id: 'distance-markers',
type: 'symbol',
source: 'distance-markers',
@@ -79,9 +80,9 @@ export class DistanceMarkers {
'text-halo-width': 2,
'text-halo-color': 'white',
},
});
} else {
map_.moveLayer('distance-markers');
},
ANCHOR_LAYER_KEY.distanceMarkers
);
}
} else {
if (map_.getLayer('distance-markers')) {

View File

@@ -1,6 +1,6 @@
import { get, type Readable } from 'svelte/store';
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 {
ListTrackSegmentItem,
@@ -22,6 +22,7 @@ import { fileActionManager } from '$lib/logic/file-action-manager';
import { fileActions } from '$lib/logic/file-actions';
import { splitAs } from '$lib/components/toolbar/tools/scissors/scissors';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { gpxColors } from '$lib/components/map/gpx-layer/gpx-layers';
const colors = [
'#ff0000',
@@ -43,16 +44,35 @@ for (let color of colors) {
}
// 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));
colorCount[color]++;
gpxColors.update((colors) => {
colors.set(fileId, color);
return colors;
});
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)) {
colorCount[color]--;
}
gpxColors.update((colors) => {
colors.delete(fileId);
return colors;
});
}
export function getSvgForSymbol(symbol?: string | undefined, layerColor?: string | undefined) {
@@ -121,7 +141,7 @@ export class GPXLayer {
constructor(fileId: string, file: Readable<GPXFileWithStatistics | undefined>) {
this.fileId = fileId;
this.file = file;
this.layerColor = getColor();
this.layerColor = getColor(fileId);
this.unsubscribe.push(
map.subscribe(($map) => {
if ($map) {
@@ -158,7 +178,7 @@ export class GPXLayer {
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}`;
}
@@ -176,7 +196,8 @@ export class GPXLayer {
}
if (!_map.getLayer(this.fileId)) {
_map.addLayer({
_map.addLayer(
{
id: this.fileId,
type: 'line',
source: this.fileId,
@@ -189,7 +210,9 @@ export class GPXLayer {
'line-width': ['get', 'width'],
'line-opacity': ['get', 'opacity'],
},
});
},
ANCHOR_LAYER_KEY.tracks
);
_map.on('click', this.fileId, this.layerOnClickBinded);
_map.on('contextmenu', this.fileId, this.layerOnContextMenuBinded);
@@ -212,7 +235,8 @@ export class GPXLayer {
}
if (!_map.getLayer(this.fileId + '-waypoints')) {
_map.addLayer({
_map.addLayer(
{
id: this.fileId + '-waypoints',
type: 'symbol',
source: this.fileId + '-waypoints',
@@ -223,7 +247,9 @@ export class GPXLayer {
'icon-padding': 0,
'icon-allow-overlap': true,
},
});
},
ANCHOR_LAYER_KEY.waypoints
);
_map.on(
'mouseenter',
@@ -272,7 +298,7 @@ export class GPXLayer {
'text-halo-color': 'white',
},
},
_map.getLayer('distance-markers') ? 'distance-markers' : undefined
ANCHOR_LAYER_KEY.directionMarkers
);
}
} else {
@@ -364,7 +390,7 @@ export class GPXLayer {
this.unsubscribe.forEach((unsubscribe) => unsubscribe());
decrementColor(this.layerColor);
removeColor(this.fileId, this.layerColor);
}
moveToFront() {
@@ -373,13 +399,13 @@ export class GPXLayer {
return;
}
if (_map.getLayer(this.fileId)) {
_map.moveLayer(this.fileId);
_map.moveLayer(this.fileId, ANCHOR_LAYER_KEY.tracks);
}
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')) {
_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 { writable } from 'svelte/store';
import { GPXLayer } from './gpx-layer';
export class GPXLayerCollection {
@@ -42,3 +43,4 @@ export class 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.Header class="p-0 gap-0">
<Card.Title class="text-md">
<div class="flex flex-row gap-3">
<Card.Title class="text-md flex flex-row">
<div class="flex flex-col">
{name}
<p>{name}</p>
<div class="text-muted-foreground text-xs font-normal">
{poi.item.lat.toFixed(6)}&deg; {poi.item.lon.toFixed(6)}&deg;
</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}"
size="icon-sm"
href="https://www.openstreetmap.org/edit?editor=id&{poi.item.type ?? 'node'}={poi
.item.id}"
target="_blank"
>
<PencilLine size="16" />
</Button>
</div>
</Card.Title>
</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]">
{#if tags.image || tags['image:0']}
<div class="w-full rounded-md overflow-clip my-2 max-w-96 mx-auto">
@@ -100,8 +99,14 @@
{/each}
</div>
</ScrollArea>
<Button class="mt-2" variant="outline" disabled={$selection.size === 0} onclick={addToFile}>
<MapPin size="16" />
<Button
size="sm"
class="mt-1 justify-start"
variant="outline"
disabled={$selection.size === 0}
onclick={addToFile}
>
<MapPin size="14" />
{i18n._('toolbar.waypoint.add')}
</Button>
</Card.Content>

View File

@@ -6,6 +6,7 @@ import { overpassQueryData } from '$lib/assets/layers';
import { MapPopup } from '$lib/components/map/map-popup';
import { settings } from '$lib/logic/settings';
import { db } from '$lib/db';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
const { currentOverpassQueries } = settings;
@@ -85,7 +86,8 @@ export class OverpassLayer {
}
if (!this.map.getLayer('overpass')) {
this.map.addLayer({
this.map.addLayer(
{
id: 'overpass',
type: 'symbol',
source: 'overpass',
@@ -95,7 +97,9 @@ export class OverpassLayer {
'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('click', 'overpass', this.onHoverBinded);

View File

@@ -20,6 +20,28 @@ let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = {
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 {
private _map: Writable<mapboxgl.Map | null> = writable(null);
private _onLoadCallbacks: ((map: mapboxgl.Map) => void)[] = [];
@@ -29,19 +51,15 @@ export class MapboxGLMap {
return this._map.subscribe(run, invalidate);
}
init(
accessToken: string,
language: string,
hash: boolean,
geocoder: boolean,
geolocate: boolean
) {
init(language: string, hash: boolean, geocoder: boolean, geolocate: boolean) {
const map = new mapboxgl.Map({
container: 'map',
style: {
version: 8,
sources: {},
layers: [],
sources: {
'empty-source': emptySource,
},
layers: anchorLayers,
imports: [
{
id: 'basemap',
@@ -50,11 +68,6 @@ export class MapboxGLMap {
{
id: 'overlays',
url: '',
data: {
version: 8,
sources: {},
layers: [],
},
},
],
},
@@ -212,6 +225,7 @@ export class MapboxGLMap {
const map = get(this._map);
if (map) {
const source = get(terrainSource);
try {
if (!map.getSource(source)) {
map.addSource(source, terrainSources[source]);
}
@@ -223,6 +237,10 @@ export class MapboxGLMap {
} 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 'mapillary-js/dist/mapillary.css';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
const mapillarySource: VectorSourceSpecification = {
type: 'vector',
@@ -99,10 +100,10 @@ export class MapillaryLayer {
this.map.addSource('mapillary', mapillarySource);
}
if (!this.map.getLayer('mapillary-sequence')) {
this.map.addLayer(mapillarySequenceLayer);
this.map.addLayer(mapillarySequenceLayer, ANCHOR_LAYER_KEY.mapillary);
}
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('mouseenter', 'mapillary-image', this.onMouseEnterBinded);

View File

@@ -15,7 +15,7 @@
import { onDestroy, onMount } from 'svelte';
import { getURLForLanguage } from '$lib/utils';
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 { selection } from '$lib/logic/selection';
import { fileActions } from '$lib/logic/file-actions';
@@ -63,7 +63,8 @@
});
}
if (!$map.getLayer('rectangle')) {
$map.addLayer({
$map.addLayer(
{
id: 'rectangle',
type: 'fill',
source: 'rectangle',
@@ -71,7 +72,9 @@
'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 Help from '$lib/components/Help.svelte';
import { MountainSnow } from '@lucide/svelte';
import { map } from '$lib/components/map/map';
import { i18n } from '$lib/i18n.svelte';
import { getURLForLanguage } from '$lib/utils';
import { selection } from '$lib/logic/selection';
@@ -20,11 +19,7 @@
variant="outline"
class="whitespace-normal h-fit"
disabled={!validSelection}
onclick={() => {
if ($map) {
fileActions.addElevationToSelection($map);
}
}}
onclick={() => fileActions.addElevationToSelection()}
>
<MountainSnow size="16" class="shrink-0" />
{i18n._('toolbar.elevation.button')}

View File

@@ -1,5 +1,5 @@
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 { GPXFileStateCollectionObserver, type GPXFileState } from '$lib/logic/file-state';
import { selection } from '$lib/logic/selection';
@@ -144,7 +144,8 @@ export class ReducedGPXLayerCollection {
});
}
if (!map_.getLayer('simplified')) {
map_.addLayer({
map_.addLayer(
{
id: 'simplified',
type: 'line',
source: 'simplified',
@@ -152,9 +153,9 @@ export class ReducedGPXLayerCollection {
'line-color': 'white',
'line-width': 3,
},
});
} else {
map_.moveLayer('simplified');
},
ANCHOR_LAYER_KEY.interactions
);
}
}

View File

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

View File

@@ -731,7 +731,17 @@ export class RoutingControls {
try {
response = await route(targetCoordinates);
} 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;
}

View File

@@ -6,213 +6,37 @@ import { get } from 'svelte/store';
const { routing, routingProfile, privateRoads } = settings;
export type RoutingProfile = {
engine: 'graphhopper' | 'brouter';
profile: string;
};
export const routingProfiles: { [key: string]: RoutingProfile } = {
bike: { engine: 'graphhopper', profile: 'bike' },
racing_bike: { engine: 'graphhopper', profile: 'racingbike' },
gravel_bike: { engine: 'graphhopper', profile: 'gravelbike' },
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 const brouterProfiles: { [key: string]: string } = {
bike: 'Trekking-dry',
racing_bike: 'fastbike',
gravel_bike: 'gravel',
mountain_bike: 'MTB',
foot: 'Hiking-Alpine-SAC6',
motorcycle: 'Car-FastEco',
water: 'river',
railway: 'rail',
};
export function route(points: Coordinates[]): Promise<TrackPoint[]> {
if (get(routing)) {
const profile = routingProfiles[get(routingProfile)];
if (profile.engine === 'graphhopper') {
return getGraphHopperRoute(points, profile.profile, get(privateRoads));
} else {
return getBRouterRoute(points, profile.profile);
}
return getRoute(points, brouterProfiles[get(routingProfile)], get(privateRoads));
} else {
return getIntermediatePoints(points);
}
}
const graphhopperDetails = ['road_class', 'surface', 'hike_rating', 'mtb_rating'];
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(
async function getRoute(
points: Coordinates[],
graphHopperProfile: string,
brouterProfile: string,
privateRoads: boolean
): Promise<TrackPoint[]> {
let response = await fetch('https://graphhopper.gpx.studio/route', {
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 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`;
let response = await fetch(url);
// Check if the response is ok
if (!response.ok) {
const 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);
}
throw new Error(`${await response.text()}`);
}
let geojson = await response.json();
@@ -228,13 +52,14 @@ async function getBRouterRoute(
let tags = messageIdx < messages.length ? getTags(messages[messageIdx][tagIdx]) : {};
for (let i = 0; i < coordinates.length; i++) {
let coord = coordinates[i];
route.push(
new TrackPoint({
attributes: {
lat: coordinates[i][1],
lon: coordinates[i][0],
lat: coord[1],
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 { fileActions } from '$lib/logic/file-actions';
import { mapCursor, MapCursorState } from '$lib/logic/map-cursor';
import { ANCHOR_LAYER_KEY } from '$lib/components/map/map';
export class SplitControls {
map: mapboxgl.Map;
@@ -108,7 +109,8 @@ export class SplitControls {
}
if (!this.map.getLayer('split-controls')) {
this.map.addLayer({
this.map.addLayer(
{
id: 'split-controls',
type: 'symbol',
source: 'split-controls',
@@ -118,14 +120,14 @@ export class SplitControls {
'icon-padding': 0,
},
filter: ['<=', ['get', 'minZoom'], ['zoom']],
});
},
ANCHOR_LAYER_KEY.interactions
);
this.map.on('mouseenter', 'split-controls', this.layerOnMouseEnterBinded);
this.map.on('mouseleave', 'split-controls', this.layerOnMouseLeaveBinded);
this.map.on('click', 'split-controls', this.layerOnClickBinded);
}
this.map.moveLayer('split-controls');
} catch (e) {
// 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.
### <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...

View File

@@ -1,5 +1,5 @@
Mapbox ist das Unternehmen, das einige der schönen Karten auf dieser Website zur Verfügung stellt.
Sie entwickeln auch die <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">Karten-Engine</a> welche **gpx.studio** unterstützt.
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>, 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.

View File

@@ -35,7 +35,7 @@ Supprimer les fichiers sélectionnés.
### <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...

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.
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.
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
- **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.

View File

@@ -35,7 +35,7 @@ Delete the currently selected files.
### <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...

View File

@@ -2,7 +2,8 @@
import { HeartHandshake } from '@lucide/svelte';
</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 точки, наші сервери обчислюють найкращий маршрут на мережі доріг.
Ми також використовуємо API від <a href="https://mapbox.com" target="_blank">Mapbox</a> для зображення красивих карт, отримання даних висот та можливості пошуку місць.

View File

@@ -2,7 +2,7 @@
import { Languages } from '@lucide/svelte';
</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>.

View File

@@ -47,6 +47,6 @@ Open the export dialog to save all files to your computer.
<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>

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
Delete the currently selected files.
.
### <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) {
return;
}

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -28,7 +28,7 @@
"undo": "Desfer",
"redo": "Refer",
"delete": "Elimina el track",
"delete_all": "Delete all",
"delete_all": "Esborra-ho tot",
"select_all": "Seleccionar-ho tot",
"view": "Vista",
"elevation_profile": "Perfil delevacions",
@@ -80,7 +80,7 @@
"center": "Centrar",
"open_in": "Obrir amb",
"copy_coordinates": "Copiar coordenades",
"edit_osm": "Edit in OpenStreetMap"
"edit_osm": "Edita a OpenStreetMap"
},
"toolbar": {
"routing": {
@@ -190,6 +190,8 @@
"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",
"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"
}
},
@@ -282,7 +284,7 @@
"update": "Actualitza la capa"
},
"opacity": "Opacitat de la superposició",
"terrain": "Terrain source",
"terrain": "Terreny",
"label": {
"basemaps": "Mapes base",
"overlays": "Capes",
@@ -356,7 +358,7 @@
"water": "Aigua",
"shower": "Dutxa",
"shelter": "Refugi",
"cemetery": "Cemetery",
"cemetery": "Cementiri",
"motorized": "Cotxes i motos",
"fuel-station": "Gasolinera",
"parking": "Aparcament",

View File

@@ -190,6 +190,8 @@
"from": "Počáteční 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",
"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ě"
}
},
@@ -282,7 +284,7 @@
"update": "Aktualizovat vrstvu"
},
"opacity": "Průhlednost překryvu",
"terrain": "Terrain source",
"terrain": "Zdroj terénu",
"label": {
"basemaps": "Základní mapy",
"overlays": "Překrytí",

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -21,14 +21,14 @@
"export_all": "Alle exportieren...",
"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_button": "Hilf dabei, die Webseite kostenlos zu belassen",
"support_button": "Hilf uns, die Website weiterhin kostenlos bereitzustellen",
"download_file": "Datei herunterladen",
"download_files": "Dateien herunterladen",
"edit": "Bearbeiten",
"undo": "Rückgängig",
"redo": "Wiederholen",
"delete": "Löschen",
"delete_all": "Delete all",
"delete_all": "Alle löschen",
"select_all": "Alle auswählen",
"view": "Ansicht",
"elevation_profile": "Höhenprofil",
@@ -80,7 +80,7 @@
"center": "Zentrieren",
"open_in": "Öffnen in",
"copy_coordinates": "Koordinaten kopieren",
"edit_osm": "Edit in OpenStreetMap"
"edit_osm": "In OpenStreetMap bearbeiten"
},
"toolbar": {
"routing": {
@@ -190,6 +190,8 @@
"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",
"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"
}
},
@@ -282,7 +284,7 @@
"update": "Layer aktualisieren"
},
"opacity": "Deckkraft der Überlagerung",
"terrain": "Terrain source",
"terrain": "Geländequelle",
"label": {
"basemaps": "Basiskarte",
"overlays": "Ebenen",
@@ -326,7 +328,7 @@
"usgs": "USGS",
"bikerouterGravel": "bikerouter.de Gravel",
"cyclOSMlite": "CyclOSM Lite",
"mapterhornHillshade": "Mapterhorn Hillshade",
"mapterhornHillshade": "MapTiler Hillshade",
"openRailwayMap": "OpenRailwayMap",
"swisstopoSlope": "swisstopo Neigung",
"swisstopoHiking": "swisstopo Wandern",
@@ -356,7 +358,7 @@
"water": "Trinkwasser",
"shower": "Dusche",
"shelter": "Unterstand",
"cemetery": "Cemetery",
"cemetery": "Friedhof",
"motorized": "Autos und Motorräder",
"fuel-station": "Tankstelle",
"parking": "Parken",

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -190,8 +190,6 @@
"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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},
@@ -282,7 +284,7 @@
"update": "Actualizar capa"
},
"opacity": "Opacidad de la capa superpuesta",
"terrain": "Terrain source",
"terrain": "Origen del terreno",
"label": {
"basemaps": "Mapas base",
"overlays": "Capas",

View File

@@ -190,6 +190,8 @@
"from": "Hasiera puntua errepide hurbilenetik oso hurrun dago",
"via": "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"
}
},
@@ -282,7 +284,7 @@
"update": "Eguneratu geruza"
},
"opacity": "Geruzaren opakutasuna",
"terrain": "Terrain source",
"terrain": "Lurrazala",
"label": {
"basemaps": "Oinarrizko mapak",
"overlays": "Geruzak",

View File

@@ -190,6 +190,8 @@
"from": "Aloituspiste 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ä",
"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ä"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"to": "Le point d'arrivée est trop éloigné de la route la plus proche",
"distance": "The end point is too far from the start point",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"from": "נקודת ההתחלה רחוקה מדיי מהכביש הקרוב ביותר",
"via": "נקודת המעבר רחוקה מדיי מהכביש הקרוב ביותר",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},
@@ -235,13 +237,13 @@
"help_no_selection": "Select a file item to request elevation data."
},
"waypoint": {
"tooltip": "Create and edit points of interest",
"icon": "Icon",
"tooltip": "PoI létrehozása és módosítása",
"icon": "Ikon",
"link": "Link",
"longitude": "Longitude",
"longitude": "Földrajzi hosszúság",
"latitude": "Szélesség",
"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_no_selection": "Select a file to create or edit points of interest."
},
@@ -249,8 +251,8 @@
"tooltip": "Reduce the number of GPS points",
"tolerance": "Tűréshatár",
"number_of_points": "GPS pontok száma",
"button": "Minify",
"help": "Use the slider to choose the number of GPS points to keep.",
"button": "Minimalizálás",
"help": "",
"help_no_selection": "Select a trace to reduce the number of its GPS points."
},
"clean": {

View File

@@ -190,6 +190,8 @@
"from": "Titik awal terlalu jauh dari jalan terdekat",
"via": "Titik via 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"
}
},
@@ -491,7 +493,7 @@
"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": "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.",
"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.",

View File

@@ -190,6 +190,8 @@
"from": "Il punto di partenza è 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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"from": "Pradžios 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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -54,7 +54,7 @@
"mapillary": "Mapillary",
"google": "Google",
"toggle_street_view": "Street view",
"layers": "Kaart lagen...",
"layers": "Kaartlagen...",
"distance_markers": "Afstandsmarkeringen",
"direction_markers": "Richtingspijlen",
"help": "Help",
@@ -190,6 +190,8 @@
"from": "Het startpunt 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",
"distance": "The end point is too far from the start point",
"connection": "No connection found between the points",
"timeout": "Routeberekening heeft te lang geduurd, probeer punten dichter bij elkaar toe te voegen"
}
},
@@ -282,7 +284,7 @@
"update": "Update laag"
},
"opacity": "Laag Transparantie",
"terrain": "Terrain source",
"terrain": "Terrein bron",
"label": {
"basemaps": "Basis kaarten",
"overlays": "Lagen",

View File

@@ -28,7 +28,7 @@
"undo": "Angre",
"redo": "Gjenta",
"delete": "Slett",
"delete_all": "Delete all",
"delete_all": "Slett alle",
"select_all": "Velg alle",
"view": "Visning",
"elevation_profile": "Høydeprofil",
@@ -80,7 +80,7 @@
"center": "Sentrer",
"open_in": "Åpne I",
"copy_coordinates": "Kopier koordinater",
"edit_osm": "Edit in OpenStreetMap"
"edit_osm": "Rediger i OpenStreetMap"
},
"toolbar": {
"routing": {
@@ -190,6 +190,8 @@
"from": "Startpunktet er for langt unna nærmeste vei",
"via": "Via-punktet er for langt fra 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"
}
},
@@ -356,7 +358,7 @@
"water": "Vann",
"shower": "Dusj",
"shelter": "Ly",
"cemetery": "Cemetery",
"cemetery": "Gravplass",
"motorized": "Biler og motorsykler",
"fuel-station": "Bensinstasjon",
"parking": "Parkering",

View File

@@ -190,6 +190,8 @@
"from": "Punkt początkowy 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",
"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"
}
},
@@ -282,7 +284,7 @@
"update": "Zaktualizuj warstwę"
},
"opacity": "Przezroczystość nakładki",
"terrain": "Terrain source",
"terrain": "Źródło danych terenowych",
"label": {
"basemaps": "Mapy bazowe",
"overlays": "Nakładki",

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

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

View File

@@ -190,6 +190,8 @@
"from": "Početna 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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"from": "Başlangıç 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",
"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"
}
},

View File

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

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

@@ -190,6 +190,8 @@
"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",
"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"
}
},

View File

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