20 Commits

Author SHA1 Message Date
vcoppe
595ea8e2d3 revert clone function switch 2025-12-26 14:17:23 +01:00
vcoppe
d3e733aa3e fix wpt colors 2025-12-24 16:34:40 +01:00
vcoppe
a011768d2d computeStatistics compatible with read-only segment 2025-12-24 16:12:44 +01:00
vcoppe
4b45b5d716 update bits-ui 2025-12-24 15:27:44 +01:00
vcoppe
ebe9681c12 avoid creating useless data 2025-12-24 13:31:04 +01:00
vcoppe
51c85e4cd5 fix wpt to segment matching 2025-12-24 13:07:22 +01:00
vcoppe
2e171dfbee speed up wpt to segment matching 2025-12-24 12:43:24 +01:00
vcoppe
a6a3917986 improve statistics tree performance 2025-12-24 12:21:27 +01:00
vcoppe
21f2448213 improve cloning performance 2025-12-24 12:03:36 +01:00
vcoppe
e7a1d0488b fix ts error 2025-12-24 10:23:15 +01:00
vcoppe
22b8e0edb4 update chartjs 2025-12-24 10:11:43 +01:00
vcoppe
d062a38e8f new mapbox version 2025-12-24 08:48:50 +01:00
vcoppe
affa59130f simplify filter for hiding layers 2025-12-24 08:48:21 +01:00
vcoppe
3c816567bc use explicit path for prettierrc file, closes #289 2025-12-23 17:34:34 +01:00
vcoppe
e92e48ffde New Crowdin updates (#290)
* New translations en.json (Spanish)

* New translations getting-started.mdx (Dutch)

* New translations edit.mdx (Dutch)

* New translations en.json (Basque)

* New translations file.mdx (Basque)

* New translations en.json (Chinese Simplified)

* New translations file.mdx (Chinese Simplified)
2025-12-18 17:41:09 +01:00
vcoppe
4ce7777b86 New Crowdin updates (#286)
* New translations en.json (Italian)

* New translations file.mdx (Italian)
2025-12-08 20:18:25 +01:00
vcoppe
bc130ad867 use current zoom for osm edit link 2025-12-08 20:17:57 +01:00
vcoppe
867b6a6ac7 New Crowdin updates (#285)
* New translations en.json (German)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations view.mdx (Spanish)

* New translations en.json (Belarusian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Korean)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Thai)

* New translations en.json (Latvian)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Serbian (Latin))

* New translations en.json (Chinese Simplified)

* New translations en.json (German)

* New translations en.json (Romanian)

* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Belarusian)

* New translations en.json (Catalan)

* New translations en.json (Czech)

* New translations en.json (Danish)

* New translations en.json (Greek)

* New translations en.json (Basque)

* New translations en.json (Finnish)

* New translations en.json (Hebrew)

* New translations en.json (Hungarian)

* New translations en.json (Italian)

* New translations en.json (Korean)

* New translations en.json (Lithuanian)

* New translations en.json (Dutch)

* New translations en.json (Norwegian)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Swedish)

* New translations en.json (Turkish)

* New translations en.json (Ukrainian)

* New translations en.json (Vietnamese)

* New translations en.json (Portuguese, Brazilian)

* New translations en.json (Indonesian)

* New translations en.json (Thai)

* New translations en.json (Latvian)

* New translations en.json (Chinese Traditional, Hong Kong)

* New translations en.json (Serbian (Latin))

* New translations en.json (French)

* New translations en.json (Czech)

* New translations en.json (Dutch)
2025-12-07 18:56:45 +01:00
vcoppe
e585fd084c edit in osm button in context menu, closes #282 2025-12-07 14:27:07 +01:00
vcoppe
b47bb4a771 fix overpass layers, and add cemeteries, closes #235 2025-12-07 14:11:31 +01:00
58 changed files with 582 additions and 413 deletions

View File

@@ -1,6 +0,0 @@
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
src/lib/components/ui
*.mdx

View File

@@ -25,7 +25,7 @@
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"postinstall": "npm run build", "postinstall": "npm run build",
"lint": "prettier --check . && eslint .", "lint": "prettier --check . --config ../.prettierrc && eslint .",
"format": "prettier --write ." "format": "prettier --write . --config ../.prettierrc"
} }
} }

View File

@@ -148,7 +148,9 @@ export class GPXFile extends GPXTreeNode<Track> {
}, },
}, },
}; };
this.wpt = gpx.wpt ? gpx.wpt.map((waypoint) => new Waypoint(waypoint)) : []; this.wpt = gpx.wpt
? gpx.wpt.map((waypoint, index) => new Waypoint(waypoint, index))
: [];
this.trk = gpx.trk ? gpx.trk.map((track) => new Track(track)) : []; this.trk = gpx.trk ? gpx.trk.map((track) => new Track(track)) : [];
if (gpx.rte && gpx.rte.length > 0) { if (gpx.rte && gpx.rte.length > 0) {
this.trk = this.trk.concat(gpx.rte.map((route) => convertRouteToTrack(route))); this.trk = this.trk.concat(gpx.rte.map((route) => convertRouteToTrack(route)));
@@ -186,9 +188,6 @@ export class GPXFile extends GPXTreeNode<Track> {
segment._data['segmentIndex'] = segmentIndex; segment._data['segmentIndex'] = segmentIndex;
}); });
}); });
this.wpt.forEach((waypoint, waypointIndex) => {
waypoint._data['index'] = waypointIndex;
});
} }
get children(): Array<Track> { get children(): Array<Track> {
@@ -807,7 +806,7 @@ export class TrackSegment extends GPXTreeLeaf {
constructor(segment?: (TrackSegmentType & { _data?: any }) | TrackSegment) { constructor(segment?: (TrackSegmentType & { _data?: any }) | TrackSegment) {
super(); super();
if (segment) { if (segment) {
this.trkpt = segment.trkpt.map((point) => new TrackPoint(point)); this.trkpt = segment.trkpt.map((point, index) => new TrackPoint(point, index));
if (segment.hasOwnProperty('_data')) { if (segment.hasOwnProperty('_data')) {
this._data = segment._data; this._data = segment._data;
} }
@@ -819,12 +818,10 @@ export class TrackSegment extends GPXTreeLeaf {
_computeStatistics(): GPXStatistics { _computeStatistics(): GPXStatistics {
let statistics = new GPXStatistics(); let statistics = new GPXStatistics();
statistics.local.points = this.trkpt.map((point) => point); statistics.local.points = this.trkpt.slice(0);
const points = this.trkpt; const points = this.trkpt;
for (let i = 0; i < points.length; i++) { for (let i = 0; i < points.length; i++) {
points[i]._data['index'] = i;
// distance // distance
let dist = 0; let dist = 0;
if (i > 0) { if (i > 0) {
@@ -1317,7 +1314,7 @@ export class TrackPoint {
_data: { [key: string]: any } = {}; _data: { [key: string]: any } = {};
constructor(point: (TrackPointType & { _data?: any }) | TrackPoint) { constructor(point: (TrackPointType & { _data?: any }) | TrackPoint, index?: number) {
this.attributes = point.attributes; this.attributes = point.attributes;
this.ele = point.ele; this.ele = point.ele;
this.time = point.time; this.time = point.time;
@@ -1325,6 +1322,9 @@ export class TrackPoint {
if (point.hasOwnProperty('_data')) { if (point.hasOwnProperty('_data')) {
this._data = point._data; this._data = point._data;
} }
if (index !== undefined) {
this._data.index = index;
}
} }
getCoordinates(): Coordinates { getCoordinates(): Coordinates {
@@ -1468,11 +1468,18 @@ export class TrackPoint {
clone(): TrackPoint { clone(): TrackPoint {
return new TrackPoint({ return new TrackPoint({
attributes: cloneJSON(this.attributes), attributes: {
lat: this.attributes.lat,
lon: this.attributes.lon,
},
ele: this.ele, ele: this.ele,
time: this.time ? new Date(this.time.getTime()) : undefined, time: this.time ? new Date(this.time.getTime()) : undefined,
extensions: cloneJSON(this.extensions), extensions: this.extensions ? cloneJSON(this.extensions) : undefined,
_data: cloneJSON(this._data), _data: {
index: this._data?.index,
anchor: this._data?.anchor,
zoom: this._data?.zoom,
},
}); });
} }
} }
@@ -1491,7 +1498,7 @@ export class Waypoint {
type?: string; type?: string;
_data: { [key: string]: any } = {}; _data: { [key: string]: any } = {};
constructor(waypoint: (WaypointType & { _data?: any }) | Waypoint) { constructor(waypoint: (WaypointType & { _data?: any }) | Waypoint, index?: number) {
this.attributes = waypoint.attributes; this.attributes = waypoint.attributes;
this.ele = waypoint.ele; this.ele = waypoint.ele;
this.time = waypoint.time; this.time = waypoint.time;
@@ -1510,6 +1517,9 @@ export class Waypoint {
if (waypoint.hasOwnProperty('_data')) { if (waypoint.hasOwnProperty('_data')) {
this._data = waypoint._data; this._data = waypoint._data;
} }
if (index !== undefined) {
this._data.index = index;
}
} }
getCoordinates(): Coordinates { getCoordinates(): Coordinates {
@@ -1557,7 +1567,10 @@ export class Waypoint {
clone(): Waypoint { clone(): Waypoint {
return new Waypoint({ return new Waypoint({
attributes: cloneJSON(this.attributes), attributes: {
lat: this.attributes.lat,
lon: this.attributes.lon,
},
ele: this.ele, ele: this.ele,
time: this.time ? new Date(this.time.getTime()) : undefined, time: this.time ? new Date(this.time.getTime()) : undefined,
name: this.name, name: this.name,

View File

@@ -59,13 +59,13 @@ function ramerDouglasPeuckerRecursive(
} }
export function crossarcDistance( export function crossarcDistance(
point1: TrackPoint, point1: TrackPoint | Coordinates,
point2: TrackPoint, point2: TrackPoint | Coordinates,
point3: TrackPoint | Coordinates point3: TrackPoint | Coordinates
): number { ): number {
return crossarc( return crossarc(
point1.getCoordinates(), point1 instanceof TrackPoint ? point1.getCoordinates() : point1,
point2.getCoordinates(), point2 instanceof TrackPoint ? point2.getCoordinates() : point2,
point3 instanceof TrackPoint ? point3.getCoordinates() : point3 point3 instanceof TrackPoint ? point3.getCoordinates() : point3
); );
} }

3
website/.prettierignore Normal file
View File

@@ -0,0 +1,3 @@
src/lib/components/ui
src/lib/docs/**/*.mdx
**/*.webmanifest

View File

@@ -1,17 +1,17 @@
{ {
"$schema": "https://shadcn-svelte.com/schema.json", "$schema": "https://shadcn-svelte.com/schema.json",
"style": "default", "style": "default",
"tailwind": { "tailwind": {
"css": "src/app.css", "css": "src/app.css",
"baseColor": "slate" "baseColor": "slate"
}, },
"aliases": { "aliases": {
"components": "$lib/components", "components": "$lib/components",
"utils": "$lib/utils", "utils": "$lib/utils",
"ui": "$lib/components/ui", "ui": "$lib/components/ui",
"hooks": "$lib/hooks", "hooks": "$lib/hooks",
"lib": "$lib" "lib": "$lib"
}, },
"typescript": true, "typescript": true,
"registry": "https://shadcn-svelte.com/registry" "registry": "https://shadcn-svelte.com/registry"
} }

View File

@@ -14,7 +14,7 @@
"@mapbox/sphericalmercator": "^2.0.1", "@mapbox/sphericalmercator": "^2.0.1",
"@mapbox/tilebelt": "^2.0.2", "@mapbox/tilebelt": "^2.0.2",
"@types/mapbox__sphericalmercator": "^1.2.3", "@types/mapbox__sphericalmercator": "^1.2.3",
"chart.js": "^4.4.9", "chart.js": "^4.5.1",
"chartjs-plugin-zoom": "^2.2.0", "chartjs-plugin-zoom": "^2.2.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dexie": "^4.0.11", "dexie": "^4.0.11",
@@ -22,7 +22,7 @@
"gpx": "file:../gpx", "gpx": "file:../gpx",
"immer": "^10.1.1", "immer": "^10.1.1",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"mapbox-gl": "^3.16.0", "mapbox-gl": "^3.17.0",
"mapillary-js": "^4.1.2", "mapillary-js": "^4.1.2",
"png.js": "^0.2.1", "png.js": "^0.2.1",
"sanitize-html": "^2.17.0", "sanitize-html": "^2.17.0",
@@ -47,7 +47,7 @@
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.33.1", "@typescript-eslint/eslint-plugin": "^8.33.1",
"@typescript-eslint/parser": "^8.33.1", "@typescript-eslint/parser": "^8.33.1",
"bits-ui": "^2.12.0", "bits-ui": "^2.14.4",
"eslint": "^9.28.0", "eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.9.1", "eslint-plugin-svelte": "^3.9.1",
@@ -3241,9 +3241,9 @@
] ]
}, },
"node_modules/bits-ui": { "node_modules/bits-ui": {
"version": "2.12.0", "version": "2.14.4",
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.12.0.tgz", "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.14.4.tgz",
"integrity": "sha512-8NF4ILNyAJlIxDXpl/akGXGBV5QmZAe+8gTfPttM5P6/+LrijumcSfFXY5cr4QkXwTmLA7H5stYpbgJf2XFJvg==", "integrity": "sha512-W6kenhnbd/YVvur+DKkaVJ6GldE53eLewur5AhUCqslYQ0vjZr8eWlOfwZnMiPB+PF5HMVqf61vXBvmyrAmPWg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3664,9 +3664,9 @@
} }
}, },
"node_modules/chart.js": { "node_modules/chart.js": {
"version": "4.4.9", "version": "4.5.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@kurkle/color": "^0.3.0" "@kurkle/color": "^0.3.0"
@@ -6069,12 +6069,14 @@
} }
}, },
"node_modules/mapbox-gl": { "node_modules/mapbox-gl": {
"version": "3.16.0", "version": "3.17.0",
"resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.16.0.tgz", "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-3.17.0.tgz",
"integrity": "sha512-rluV1Zp/0oHf1Y9BV+nePRNnKyTdljko3E19CzO5rBqtQaNUYS0ePCMPRtxOuWRwSdKp3f9NWJkOCjemM8nmjw==", "integrity": "sha512-nCrDKRlr5di6xUksUDslNWwxroJ5yv1hT8pyVFtcpWJOOKsYQxF/wOFTMie8oxMnXeFkrz1Tl1TwA1XN1yX0KA==",
"license": "SEE LICENSE IN LICENSE.txt", "license": "SEE LICENSE IN LICENSE.txt",
"workspaces": [ "workspaces": [
"src/style-spec", "src/style-spec",
"test/build/vite",
"test/build/webpack",
"test/build/typings" "test/build/typings"
], ],
"dependencies": { "dependencies": {
@@ -6102,7 +6104,6 @@
"pbf": "^4.0.1", "pbf": "^4.0.1",
"potpack": "^2.0.0", "potpack": "^2.0.0",
"quickselect": "^3.0.0", "quickselect": "^3.0.0",
"serialize-to-js": "^3.1.2",
"supercluster": "^8.0.1", "supercluster": "^8.0.1",
"tinyqueue": "^3.0.0" "tinyqueue": "^3.0.0"
} }
@@ -7634,14 +7635,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/serialize-to-js": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/serialize-to-js/-/serialize-to-js-3.1.2.tgz",
"integrity": "sha512-owllqNuDDEimQat7EPG0tH7JjO090xKNzUtYz6X+Sk2BXDnOCilDdNLwjWeFywG9xkJul1ULvtUQa9O4pUaY0w==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/set-cookie-parser": { "node_modules/set-cookie-parser": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz",

View File

@@ -10,8 +10,8 @@
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .", "lint": "prettier --check . --config ../.prettierrc && eslint .",
"format": "prettier --write ." "format": "prettier --write . --config ../.prettierrc"
}, },
"devDependencies": { "devDependencies": {
"@lucide/svelte": "^0.544.0", "@lucide/svelte": "^0.544.0",
@@ -31,7 +31,7 @@
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.33.1", "@typescript-eslint/eslint-plugin": "^8.33.1",
"@typescript-eslint/parser": "^8.33.1", "@typescript-eslint/parser": "^8.33.1",
"bits-ui": "^2.12.0", "bits-ui": "^2.14.4",
"eslint": "^9.28.0", "eslint": "^9.28.0",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.9.1", "eslint-plugin-svelte": "^3.9.1",
@@ -66,7 +66,7 @@
"@mapbox/sphericalmercator": "^2.0.1", "@mapbox/sphericalmercator": "^2.0.1",
"@mapbox/tilebelt": "^2.0.2", "@mapbox/tilebelt": "^2.0.2",
"@types/mapbox__sphericalmercator": "^1.2.3", "@types/mapbox__sphericalmercator": "^1.2.3",
"chart.js": "^4.4.9", "chart.js": "^4.5.1",
"chartjs-plugin-zoom": "^2.2.0", "chartjs-plugin-zoom": "^2.2.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dexie": "^4.0.11", "dexie": "^4.0.11",
@@ -74,7 +74,7 @@
"gpx": "file:../gpx", "gpx": "file:../gpx",
"immer": "^10.1.1", "immer": "^10.1.1",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"mapbox-gl": "^3.16.0", "mapbox-gl": "^3.17.0",
"mapillary-js": "^4.1.2", "mapillary-js": "^4.1.2",
"png.js": "^0.2.1", "png.js": "^0.2.1",
"sanitize-html": "^2.17.0", "sanitize-html": "^2.17.0",

View File

@@ -1,126 +1,126 @@
@import "tailwindcss"; @import 'tailwindcss';
@import "tw-animate-css"; @import 'tw-animate-css';
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
:root { :root {
--background: hsl(0 0% 100%) /* <- Wrap in HSL */; --background: hsl(0 0% 100%) /* <- Wrap in HSL */;
--foreground: hsl(240 10% 3.9%); --foreground: hsl(240 10% 3.9%);
--muted: hsl(240 4.8% 95.9%); --muted: hsl(240 4.8% 95.9%);
--muted-foreground: hsl(240 3.8% 46.1%); --muted-foreground: hsl(240 3.8% 46.1%);
--popover: hsl(0 0% 100%); --popover: hsl(0 0% 100%);
--popover-foreground: hsl(240 10% 3.9%); --popover-foreground: hsl(240 10% 3.9%);
--card: hsl(0 0% 100%); --card: hsl(0 0% 100%);
--card-foreground: hsl(240 10% 3.9%); --card-foreground: hsl(240 10% 3.9%);
--border: hsl(240 5.9% 90%); --border: hsl(240 5.9% 90%);
--input: hsl(240 5.9% 90%); --input: hsl(240 5.9% 90%);
--primary: hsl(240 5.9% 10%); --primary: hsl(240 5.9% 10%);
--primary-foreground: hsl(0 0% 98%); --primary-foreground: hsl(0 0% 98%);
--secondary: hsl(240 4.8% 95.9%); --secondary: hsl(240 4.8% 95.9%);
--secondary-foreground: hsl(240 5.9% 10%); --secondary-foreground: hsl(240 5.9% 10%);
--accent: hsl(240 4.8% 95.9%); --accent: hsl(240 4.8% 95.9%);
--accent-foreground: hsl(240 5.9% 10%); --accent-foreground: hsl(240 5.9% 10%);
--destructive: hsl(0 72.2% 50.6%); --destructive: hsl(0 72.2% 50.6%);
--destructive-foreground: hsl(0 0% 98%); --destructive-foreground: hsl(0 0% 98%);
--ring: hsl(240 10% 3.9%); --ring: hsl(240 10% 3.9%);
--sidebar: hsl(0 0% 98%); --sidebar: hsl(0 0% 98%);
--sidebar-foreground: hsl(240 5.3% 26.1%); --sidebar-foreground: hsl(240 5.3% 26.1%);
--sidebar-primary: hsl(240 5.9% 10%); --sidebar-primary: hsl(240 5.9% 10%);
--sidebar-primary-foreground: hsl(0 0% 98%); --sidebar-primary-foreground: hsl(0 0% 98%);
--sidebar-accent: hsl(240 4.8% 95.9%); --sidebar-accent: hsl(240 4.8% 95.9%);
--sidebar-accent-foreground: hsl(240 5.9% 10%); --sidebar-accent-foreground: hsl(240 5.9% 10%);
--sidebar-border: hsl(220 13% 91%); --sidebar-border: hsl(220 13% 91%);
--sidebar-ring: hsl(217.2 91.2% 59.8%); --sidebar-ring: hsl(217.2 91.2% 59.8%);
--support: rgb(220 15 130); --support: rgb(220 15 130);
--link: rgb(0 110 180); --link: rgb(0 110 180);
--selection: hsl(240 4.8% 93%); --selection: hsl(240 4.8% 93%);
--radius: 0.5rem; --radius: 0.5rem;
} }
.dark { .dark {
--background: hsl(240 10% 3.9%); --background: hsl(240 10% 3.9%);
--foreground: hsl(0 0% 98%); --foreground: hsl(0 0% 98%);
--muted: hsl(240 3.7% 15.9%); --muted: hsl(240 3.7% 15.9%);
--muted-foreground: hsl(240 5% 64.9%); --muted-foreground: hsl(240 5% 64.9%);
--popover: hsl(240 10% 3.9%); --popover: hsl(240 10% 3.9%);
--popover-foreground: hsl(0 0% 98%); --popover-foreground: hsl(0 0% 98%);
--card: hsl(240 10% 3.9%); --card: hsl(240 10% 3.9%);
--card-foreground: hsl(0 0% 98%); --card-foreground: hsl(0 0% 98%);
--border: hsl(240 3.7% 15.9%); --border: hsl(240 3.7% 15.9%);
--input: hsl(240 3.7% 15.9%); --input: hsl(240 3.7% 15.9%);
--primary: hsl(0 0% 98%); --primary: hsl(0 0% 98%);
--primary-foreground: hsl(240 5.9% 10%); --primary-foreground: hsl(240 5.9% 10%);
--secondary: hsl(240 3.7% 15.9%); --secondary: hsl(240 3.7% 15.9%);
--secondary-foreground: hsl(0 0% 98%); --secondary-foreground: hsl(0 0% 98%);
--accent: hsl(240 3.7% 15.9%); --accent: hsl(240 3.7% 15.9%);
--accent-foreground: hsl(0 0% 98%); --accent-foreground: hsl(0 0% 98%);
--destructive: hsl(0 62.8% 30.6%); --destructive: hsl(0 62.8% 30.6%);
--destructive-foreground: hsl(0 0% 98%); --destructive-foreground: hsl(0 0% 98%);
--ring: hsl(240 4.9% 83.9%); --ring: hsl(240 4.9% 83.9%);
--sidebar: hsl(240 5.9% 10%); --sidebar: hsl(240 5.9% 10%);
--sidebar-foreground: hsl(240 4.8% 95.9%); --sidebar-foreground: hsl(240 4.8% 95.9%);
--sidebar-primary: hsl(224.3 76.3% 48%); --sidebar-primary: hsl(224.3 76.3% 48%);
--sidebar-primary-foreground: hsl(0 0% 100%); --sidebar-primary-foreground: hsl(0 0% 100%);
--sidebar-accent: hsl(240 3.7% 15.9%); --sidebar-accent: hsl(240 3.7% 15.9%);
--sidebar-accent-foreground: hsl(240 4.8% 95.9%); --sidebar-accent-foreground: hsl(240 4.8% 95.9%);
--sidebar-border: hsl(240 3.7% 15.9%); --sidebar-border: hsl(240 3.7% 15.9%);
--sidebar-ring: hsl(217.2 91.2% 59.8%); --sidebar-ring: hsl(217.2 91.2% 59.8%);
--support: rgb(255 110 190); --support: rgb(255 110 190);
--link: rgb(80 190 255); --link: rgb(80 190 255);
--selection: hsl(240 3.7% 22%); --selection: hsl(240 3.7% 22%);
} }
@theme inline { @theme inline {
/* Radius (for rounded-*) */ /* Radius (for rounded-*) */
--radius-sm: calc(var(--radius) - 4px); --radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px); --radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius); --radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px); --radius-xl: calc(var(--radius) + 4px);
/* Colors */ /* Colors */
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--color-muted: var(--muted); --color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground); --color-muted-foreground: var(--muted-foreground);
--color-popover: var(--popover); --color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground); --color-popover-foreground: var(--popover-foreground);
--color-card: var(--card); --color-card: var(--card);
--color-card-foreground: var(--card-foreground); --color-card-foreground: var(--card-foreground);
--color-border: var(--border); --color-border: var(--border);
--color-input: var(--input); --color-input: var(--input);
--color-primary: var(--primary); --color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground); --color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary); --color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground); --color-secondary-foreground: var(--secondary-foreground);
--color-accent: var(--accent); --color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground); --color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive); --color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground); --color-destructive-foreground: var(--destructive-foreground);
--color-ring: var(--ring); --color-ring: var(--ring);
--color-radius: var(--radius); --color-radius: var(--radius);
--color-sidebar: var(--sidebar); --color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border); --color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring); --color-sidebar-ring: var(--sidebar-ring);
--color-support: var(--support); --color-support: var(--support);
--color-link: var(--link); --color-link: var(--link);
--breakpoint-xs: 540px; --breakpoint-xs: 540px;
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }

View File

@@ -836,6 +836,7 @@ export const overpassTree: LayerTreeType = {
shower: true, shower: true,
shelter: true, shelter: true,
barrier: true, barrier: true,
cemetery: true,
}, },
tourism: { tourism: {
attraction: true, attraction: true,
@@ -919,6 +920,7 @@ export const defaultOverpassQueries: LayerTreeType = {
shower: false, shower: false,
shelter: false, shelter: false,
barrier: false, barrier: false,
cemetery: false,
}, },
tourism: { tourism: {
attraction: false, attraction: false,
@@ -1053,6 +1055,7 @@ export const defaultOverpassTree: LayerTreeType = {
shower: false, shower: false,
shelter: false, shelter: false,
barrier: false, barrier: false,
cemetery: false,
}, },
tourism: { tourism: {
attraction: false, attraction: false,
@@ -1099,9 +1102,7 @@ type OverpassQueryData = {
svg: string; svg: string;
color: string; color: string;
}; };
tags: tags: Record<string, string | string[]> | Record<string, string | string[]>[];
| Record<string, string | boolean | string[]>
| Record<string, string | boolean | string[]>[];
symbol?: string; symbol?: string;
}; };
@@ -1182,6 +1183,20 @@ export const overpassQueryData: Record<string, OverpassQueryData> = {
}, },
symbol: 'Shelter', symbol: 'Shelter',
}, },
cemetery: {
icon: {
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 17v-10a6 5 0 1 1 12 0v10"/><path d="M 4 21 a 1 1 0 0 0 1 1 h 14 a 1 1 0 0 0 1-1 v -1 a 2 2 0 0 0-2-2 H6 a 2 2 0 0 0-2 2 z"/></svg>',
color: '#000000',
},
tags: [
{
landuse: 'cemetery',
},
{
amenity: 'grave_yard',
},
],
},
'fuel-station': { 'fuel-station': {
icon: { icon: {
svg: Fuel, svg: Fuel,
@@ -1218,7 +1233,25 @@ export const overpassQueryData: Record<string, OverpassQueryData> = {
color: '#000000', color: '#000000',
}, },
tags: { tags: {
barrier: true, barrier: [
'bar',
'barrier_board',
'block',
'chain',
'cycle_barrier',
'gate',
'hampshire_gate',
'horse_stile',
'kissing_gate',
'lift_gate',
'motorcycle_barrier',
'sliding_beam',
'sliding_gate',
'stile',
'swing_gate',
'turnstile',
'wicket_gate',
],
}, },
}, },
attraction: { attraction: {

View File

@@ -538,6 +538,7 @@
let targetInput = let targetInput =
e && e &&
e.target && e.target &&
e.target instanceof HTMLElement &&
(e.target.tagName === 'INPUT' || (e.target.tagName === 'INPUT' ||
e.target.tagName === 'TEXTAREA' || e.target.tagName === 'TEXTAREA' ||
e.target.tagName === 'SELECT' || e.target.tagName === 'SELECT' ||

View File

@@ -14,7 +14,12 @@ import {
getTemperatureWithUnits, getTemperatureWithUnits,
getVelocityWithUnits, getVelocityWithUnits,
} from '$lib/units'; } from '$lib/units';
import Chart from 'chart.js/auto'; import Chart, {
type ChartEvent,
type ChartOptions,
type ScriptableLineSegmentContext,
type TooltipItem,
} from 'chart.js/auto';
import mapboxgl from 'mapbox-gl'; import mapboxgl from 'mapbox-gl';
import { get, type Readable, type Writable } from 'svelte/store'; import { get, type Readable, type Writable } from 'svelte/store';
import { map } from '$lib/components/map/map'; import { map } from '$lib/components/map/map';
@@ -27,6 +32,20 @@ const { distanceUnits, velocityUnits, temperatureUnits } = settings;
Chart.defaults.font.family = Chart.defaults.font.family =
'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; // Tailwind CSS font 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; // Tailwind CSS font
interface ElevationProfilePoint {
x: number;
y: number;
time?: Date;
slope: {
at: number;
segment: number;
length: number;
};
extensions: Record<string, any>;
coordinates: [number, number];
index: number;
}
export class ElevationProfile { export class ElevationProfile {
private _chart: Chart | null = null; private _chart: Chart | null = null;
private _canvas: HTMLCanvasElement; private _canvas: HTMLCanvasElement;
@@ -90,7 +109,7 @@ export class ElevationProfile {
} }
initialize() { initialize() {
let options = { let options: ChartOptions<'line'> = {
animation: false, animation: false,
parsing: false, parsing: false,
maintainAspectRatio: false, maintainAspectRatio: false,
@@ -98,8 +117,8 @@ export class ElevationProfile {
x: { x: {
type: 'linear', type: 'linear',
ticks: { ticks: {
callback: function (value: number) { callback: function (value: number | string) {
return `${value.toFixed(1).replace(/\.0+$/, '')} ${getDistanceUnits()}`; return `${(value as number).toFixed(1).replace(/\.0+$/, '')} ${getDistanceUnits()}`;
}, },
align: 'inner', align: 'inner',
maxRotation: 0, maxRotation: 0,
@@ -108,8 +127,8 @@ export class ElevationProfile {
y: { y: {
type: 'linear', type: 'linear',
ticks: { ticks: {
callback: function (value: number) { callback: function (value: number | string) {
return getElevationWithUnits(value, false); return getElevationWithUnits(value as number, false);
}, },
}, },
}, },
@@ -140,8 +159,8 @@ export class ElevationProfile {
title: () => { title: () => {
return ''; return '';
}, },
label: (context: Chart.TooltipContext) => { label: (context: TooltipItem<'line'>) => {
let point = context.raw; let point = context.raw as ElevationProfilePoint;
if (context.datasetIndex === 0) { if (context.datasetIndex === 0) {
const map_ = get(map); const map_ = get(map);
if (map_ && this._marker) { if (map_ && this._marker) {
@@ -165,10 +184,10 @@ export class ElevationProfile {
return `${i18n._('quantities.power')}: ${getPowerWithUnits(point.y)}`; return `${i18n._('quantities.power')}: ${getPowerWithUnits(point.y)}`;
} }
}, },
afterBody: (contexts: Chart.TooltipContext[]) => { afterBody: (contexts: TooltipItem<'line'>[]) => {
let context = contexts.filter((context) => context.datasetIndex === 0); let context = contexts.filter((context) => context.datasetIndex === 0);
if (context.length === 0) return; if (context.length === 0) return;
let point = context[0].raw; let point = context[0].raw as ElevationProfilePoint;
let slope = { let slope = {
at: point.slope.at.toFixed(1), at: point.slope.at.toFixed(1),
segment: point.slope.segment.toFixed(1), segment: point.slope.segment.toFixed(1),
@@ -227,6 +246,7 @@ export class ElevationProfile {
onPanStart: () => { onPanStart: () => {
this._panning = true; this._panning = true;
this._slicedGPXStatistics.set(undefined); this._slicedGPXStatistics.set(undefined);
return true;
}, },
onPanComplete: () => { onPanComplete: () => {
this._panning = false; this._panning = false;
@@ -238,13 +258,13 @@ export class ElevationProfile {
}, },
mode: 'x', mode: 'x',
onZoomStart: ({ chart, event }: { chart: Chart; event: any }) => { onZoomStart: ({ chart, event }: { chart: Chart; event: any }) => {
if (!this._chart) {
return false;
}
const maxZoom = this._chart.getInitialScaleBounds()?.x?.max ?? 0;
if ( if (
event.deltaY < 0 && event.deltaY < 0 &&
Math.abs( Math.abs(maxZoom / this._chart.getZoomLevel()) < 0.01
this._chart.getInitialScaleBounds().x.max /
this._chart.options.plugins.zoom.limits.x.minRange -
this._chart.getZoomLevel()
) < 0.01
) { ) {
// Disable wheel pan if zoomed in to the max, and zooming in // Disable wheel pan if zoomed in to the max, and zooming in
return false; return false;
@@ -262,7 +282,6 @@ export class ElevationProfile {
}, },
}, },
}, },
stacked: false,
onResize: () => { onResize: () => {
this.updateOverlay(); this.updateOverlay();
}, },
@@ -270,7 +289,7 @@ export class ElevationProfile {
let datasets: string[] = ['speed', 'hr', 'cad', 'atemp', 'power']; let datasets: string[] = ['speed', 'hr', 'cad', 'atemp', 'power'];
datasets.forEach((id) => { datasets.forEach((id) => {
options.scales[`y${id}`] = { options.scales![`y${id}`] = {
type: 'linear', type: 'linear',
position: 'right', position: 'right',
grid: { grid: {
@@ -291,7 +310,7 @@ export class ElevationProfile {
{ {
id: 'toggleMarker', id: 'toggleMarker',
events: ['mouseout'], events: ['mouseout'],
afterEvent: (chart: Chart, args: { event: Chart.ChartEvent }) => { afterEvent: (chart: Chart, args: { event: ChartEvent }) => {
if (args.event.type === 'mouseout') { if (args.event.type === 'mouseout') {
const map_ = get(map); const map_ = get(map);
if (map_ && this._marker) { if (map_ && this._marker) {
@@ -305,7 +324,7 @@ export class ElevationProfile {
let startIndex = 0; let startIndex = 0;
let endIndex = 0; let endIndex = 0;
const getIndex = (evt) => { const getIndex = (evt: PointerEvent) => {
if (!this._chart) { if (!this._chart) {
return undefined; return undefined;
} }
@@ -329,16 +348,16 @@ export class ElevationProfile {
} }
} }
let point = points.find((point) => point.element.raw); const point = points.find((point) => (point.element as any).raw);
if (point) { if (point) {
return point.element.raw.index; return (point.element as any).raw.index;
} else { } else {
return points[0].index; return points[0].index;
} }
}; };
let dragStarted = false; let dragStarted = false;
const onMouseDown = (evt) => { const onMouseDown = (evt: PointerEvent) => {
if (evt.shiftKey) { if (evt.shiftKey) {
// Panning interaction // Panning interaction
return; return;
@@ -347,7 +366,7 @@ export class ElevationProfile {
this._canvas.style.cursor = 'col-resize'; this._canvas.style.cursor = 'col-resize';
startIndex = getIndex(evt); startIndex = getIndex(evt);
}; };
const onMouseMove = (evt) => { const onMouseMove = (evt: PointerEvent) => {
if (dragStarted) { if (dragStarted) {
this._dragging = true; this._dragging = true;
endIndex = getIndex(evt); endIndex = getIndex(evt);
@@ -367,7 +386,7 @@ export class ElevationProfile {
} }
} }
}; };
const onMouseUp = (evt) => { const onMouseUp = (evt: PointerEvent) => {
dragStarted = false; dragStarted = false;
this._dragging = false; this._dragging = false;
this._canvas.style.cursor = ''; this._canvas.style.cursor = '';
@@ -409,62 +428,77 @@ export class ElevationProfile {
segment: {}, segment: {},
}; };
this._chart.data.datasets[1] = { this._chart.data.datasets[1] = {
data: data.local.points.map((point, index) => { data:
return { data.global.time.total > 0
x: getConvertedDistance(data.local.distance.total[index]), ? data.local.points.map((point, index) => {
y: getConvertedVelocity(data.local.speed[index]), return {
index: index, x: getConvertedDistance(data.local.distance.total[index]),
}; y: getConvertedVelocity(data.local.speed[index]),
}), index: index,
};
})
: [],
normalized: true, normalized: true,
yAxisID: 'yspeed', yAxisID: 'yspeed',
}; };
this._chart.data.datasets[2] = { this._chart.data.datasets[2] = {
data: data.local.points.map((point, index) => { data:
return { data.global.hr.count > 0
x: getConvertedDistance(data.local.distance.total[index]), ? data.local.points.map((point, index) => {
y: point.getHeartRate(), return {
index: index, x: getConvertedDistance(data.local.distance.total[index]),
}; y: point.getHeartRate(),
}), index: index,
};
})
: [],
normalized: true, normalized: true,
yAxisID: 'yhr', yAxisID: 'yhr',
}; };
this._chart.data.datasets[3] = { this._chart.data.datasets[3] = {
data: data.local.points.map((point, index) => { data:
return { data.global.cad.count > 0
x: getConvertedDistance(data.local.distance.total[index]), ? data.local.points.map((point, index) => {
y: point.getCadence(), return {
index: index, x: getConvertedDistance(data.local.distance.total[index]),
}; y: point.getCadence(),
}), index: index,
};
})
: [],
normalized: true, normalized: true,
yAxisID: 'ycad', yAxisID: 'ycad',
}; };
this._chart.data.datasets[4] = { this._chart.data.datasets[4] = {
data: data.local.points.map((point, index) => { data:
return { data.global.atemp.count > 0
x: getConvertedDistance(data.local.distance.total[index]), ? data.local.points.map((point, index) => {
y: getConvertedTemperature(point.getTemperature()), return {
index: index, x: getConvertedDistance(data.local.distance.total[index]),
}; y: getConvertedTemperature(point.getTemperature()),
}), index: index,
};
})
: [],
normalized: true, normalized: true,
yAxisID: 'yatemp', yAxisID: 'yatemp',
}; };
this._chart.data.datasets[5] = { this._chart.data.datasets[5] = {
data: data.local.points.map((point, index) => { data:
return { data.global.power.count > 0
x: getConvertedDistance(data.local.distance.total[index]), ? data.local.points.map((point, index) => {
y: point.getPower(), return {
index: index, x: getConvertedDistance(data.local.distance.total[index]),
}; y: point.getPower(),
}), index: index,
};
})
: [],
normalized: true, normalized: true,
yAxisID: 'ypower', yAxisID: 'ypower',
}; };
this._chart.options.scales.x['min'] = 0; this._chart.options.scales!.x!['min'] = 0;
this._chart.options.scales.x['max'] = getConvertedDistance(data.global.distance.total); this._chart.options.scales!.x!['max'] = getConvertedDistance(data.global.distance.total);
this.setVisibility(); this.setVisibility();
this.setFill(); this.setFill();
@@ -513,21 +547,24 @@ export class ElevationProfile {
return; return;
} }
const elevationFill = get(this._elevationFill); const elevationFill = get(this._elevationFill);
const dataset = this._chart.data.datasets[0];
let segment: any = {};
if (elevationFill === 'slope') { if (elevationFill === 'slope') {
this._chart.data.datasets[0]['segment'] = { segment = {
backgroundColor: this.slopeFillCallback, backgroundColor: this.slopeFillCallback,
}; };
} else if (elevationFill === 'surface') { } else if (elevationFill === 'surface') {
this._chart.data.datasets[0]['segment'] = { segment = {
backgroundColor: this.surfaceFillCallback, backgroundColor: this.surfaceFillCallback,
}; };
} else if (elevationFill === 'highway') { } else if (elevationFill === 'highway') {
this._chart.data.datasets[0]['segment'] = { segment = {
backgroundColor: this.highwayFillCallback, backgroundColor: this.highwayFillCallback,
}; };
} else { } else {
this._chart.data.datasets[0]['segment'] = {}; segment = {};
} }
Object.assign(dataset, { segment });
} }
updateOverlay() { updateOverlay() {
@@ -575,19 +612,22 @@ export class ElevationProfile {
} }
} }
slopeFillCallback(context) { slopeFillCallback(context: ScriptableLineSegmentContext & { p0: { raw: any } }) {
return getSlopeColor(context.p0.raw.slope.segment); const point = context.p0.raw as ElevationProfilePoint;
return getSlopeColor(point.slope.segment);
} }
surfaceFillCallback(context) { surfaceFillCallback(context: ScriptableLineSegmentContext & { p0: { raw: any } }) {
return getSurfaceColor(context.p0.raw.extensions.surface); const point = context.p0.raw as ElevationProfilePoint;
return getSurfaceColor(point.extensions.surface);
} }
highwayFillCallback(context) { highwayFillCallback(context: ScriptableLineSegmentContext & { p0: { raw: any } }) {
const point = context.p0.raw as ElevationProfilePoint;
return getHighwayColor( return getHighwayColor(
context.p0.raw.extensions.highway, point.extensions.highway,
context.p0.raw.extensions.sac_scale, point.extensions.sac_scale,
context.p0.raw.extensions.mtb_scale point.extensions.mtb_scale
); );
} }

View File

@@ -16,7 +16,8 @@
</script> </script>
<Button <Button
class="p-1 has-[>svg]:px-2 h-8 justify-start {className}" size="sm"
class="justify-start {className}"
variant="outline" variant="outline"
onclick={() => { onclick={() => {
navigator.clipboard.writeText( navigator.clipboard.writeText(

View File

@@ -1,11 +1,13 @@
<script lang="ts"> <script lang="ts">
import type { TrackPoint } from 'gpx'; import type { TrackPoint } from 'gpx';
import { Button } from '$lib/components/ui/button';
import CopyCoordinates from '$lib/components/map/gpx-layer/CopyCoordinates.svelte'; import CopyCoordinates from '$lib/components/map/gpx-layer/CopyCoordinates.svelte';
import * as Card from '$lib/components/ui/card'; import * as Card from '$lib/components/ui/card';
import WithUnits from '$lib/components/WithUnits.svelte'; import WithUnits from '$lib/components/WithUnits.svelte';
import { Compass, Mountain, Timer } from '@lucide/svelte'; import { Compass, Earth, Mountain, Timer } from '@lucide/svelte';
import { i18n } from '$lib/i18n.svelte'; import { i18n } from '$lib/i18n.svelte';
import type { PopupItem } from '$lib/components/map/map-popup'; import type { PopupItem } from '$lib/components/map/map-popup';
import { map } from '$lib/components/map/map';
let { trackpoint }: { trackpoint: PopupItem<TrackPoint> } = $props(); let { trackpoint }: { trackpoint: PopupItem<TrackPoint> } = $props();
</script> </script>
@@ -35,5 +37,16 @@
onCopy={() => trackpoint.hide?.()} onCopy={() => trackpoint.hide?.()}
class="mt-0.5" class="mt-0.5"
/> />
{#if trackpoint.fileId === undefined}
<Button
size="sm"
variant="outline"
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"
>
<Earth size="14" />
{i18n._('menu.edit_osm')}
</Button>
{/if}
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>

View File

@@ -1,5 +1,5 @@
import { get, type Readable } from 'svelte/store'; import { get, type Readable } from 'svelte/store';
import mapboxgl from 'mapbox-gl'; import mapboxgl, { type FilterSpecification } from 'mapbox-gl';
import { map } from '$lib/components/map/map'; import { map } from '$lib/components/map/map';
import { waypointPopup, trackpointPopup } from './gpx-layer-popup'; import { waypointPopup, trackpointPopup } from './gpx-layer-popup';
import { import {
@@ -153,8 +153,6 @@ export class GPXLayer {
return; return;
} }
this.loadIcons();
if ( if (
file._data.style && file._data.style &&
file._data.style.color && file._data.style.color &&
@@ -164,6 +162,8 @@ export class GPXLayer {
this.layerColor = `#${file._data.style.color}`; this.layerColor = `#${file._data.style.color}`;
} }
this.loadIcons();
try { try {
let source = _map.getSource(this.fileId) as mapboxgl.GeoJSONSource | undefined; let source = _map.getSource(this.fileId) as mapboxgl.GeoJSONSource | undefined;
if (source) { if (source) {
@@ -281,25 +281,23 @@ export class GPXLayer {
} }
} }
let visibleSegments: [number, number][] = []; let visibleTrackSegmentIds: string[] = [];
file.forEachSegment((segment, trackIndex, segmentIndex) => { file.forEachSegment((segment, trackIndex, segmentIndex) => {
if (!segment._data.hidden) { if (!segment._data.hidden) {
visibleSegments.push([trackIndex, segmentIndex]); visibleTrackSegmentIds.push(`${trackIndex}-${segmentIndex}`);
} }
}); });
const segmentFilter: FilterSpecification = [
'in',
['get', 'trackSegmentId'],
['literal', visibleTrackSegmentIds],
];
_map.setFilter( _map.setFilter(this.fileId, segmentFilter, { validate: false });
this.fileId,
[ if (_map.getLayer(this.fileId + '-direction')) {
'any', _map.setFilter(this.fileId + '-direction', segmentFilter, { validate: false });
...visibleSegments.map(([trackIndex, segmentIndex]) => [ }
'all',
['==', 'trackIndex', trackIndex],
['==', 'segmentIndex', segmentIndex],
]),
],
{ validate: false }
);
let visibleWaypoints: number[] = []; let visibleWaypoints: number[] = [];
file.wpt.forEach((waypoint, waypointIndex) => { file.wpt.forEach((waypoint, waypointIndex) => {
@@ -313,21 +311,6 @@ export class GPXLayer {
['in', ['get', 'waypointIndex'], ['literal', visibleWaypoints]], ['in', ['get', 'waypointIndex'], ['literal', visibleWaypoints]],
{ validate: false } { validate: false }
); );
if (_map.getLayer(this.fileId + '-direction')) {
_map.setFilter(
this.fileId + '-direction',
[
'any',
...visibleSegments.map(([trackIndex, segmentIndex]) => [
'all',
['==', 'trackIndex', trackIndex],
['==', 'segmentIndex', segmentIndex],
]),
],
{ validate: false }
);
}
} 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
return; return;
@@ -686,6 +669,7 @@ export class GPXLayer {
} }
feature.properties.trackIndex = trackIndex; feature.properties.trackIndex = trackIndex;
feature.properties.segmentIndex = segmentIndex; feature.properties.segmentIndex = segmentIndex;
feature.properties.trackSegmentId = `${trackIndex}-${segmentIndex}`;
segmentIndex++; segmentIndex++;
if (segmentIndex >= file.trk[trackIndex].trkseg.length) { if (segmentIndex >= file.trk[trackIndex].trkseg.length) {
@@ -718,7 +702,7 @@ export class GPXLayer {
properties: { properties: {
fileId: this.fileId, fileId: this.fileId,
waypointIndex: index, waypointIndex: index,
icon: `${this.fileId}-waypoint-${getSymbolKey(waypoint.sym) ?? 'default'}`, icon: `waypoint-${getSymbolKey(waypoint.sym) ?? 'default'}-${this.layerColor}`,
}, },
}); });
}); });
@@ -739,7 +723,7 @@ export class GPXLayer {
}); });
symbols.forEach((symbol) => { symbols.forEach((symbol) => {
const iconId = `${this.fileId}-waypoint-${symbol ?? 'default'}`; const iconId = `waypoint-${symbol ?? 'default'}-${this.layerColor}`;
if (!_map.hasImage(iconId)) { if (!_map.hasImage(iconId)) {
let icon = new Image(100, 100); let icon = new Image(100, 100);
icon.onload = () => { icon.onload = () => {

View File

@@ -285,10 +285,12 @@ function getQuery(query: string) {
} }
} }
function getQueryItem(tags: Record<string, string | boolean | string[]>) { function getQueryItem(tags: Record<string, string | string[]>) {
let arrayEntry = Object.values(tags).find((value) => Array.isArray(value)); let arrayEntry = Object.entries(tags).find((entry): entry is [string, string[]] =>
Array.isArray(entry[1])
);
if (arrayEntry !== undefined) { if (arrayEntry !== undefined) {
return arrayEntry return arrayEntry[1]
.map( .map(
(val) => (val) =>
`nwr${Object.entries(tags) `nwr${Object.entries(tags)
@@ -311,7 +313,7 @@ function belongsToQuery(element: any, query: string) {
} }
} }
function belongsToQueryItem(element: any, tags: Record<string, string | boolean | string[]>) { function belongsToQueryItem(element: any, tags: Record<string, string | string[]>) {
return Object.entries(tags).every(([tag, value]) => return Object.entries(tags).every(([tag, value]) =>
Array.isArray(value) ? value.includes(element.tags[tag]) : element.tags[tag] === value Array.isArray(value) ? value.includes(element.tags[tag]) : element.tags[tag] === value
); );

View File

@@ -1,5 +1,5 @@
--- ---
title: View options title: Opciones de vista
--- ---
<script lang="ts"> <script lang="ts">

View File

@@ -29,13 +29,13 @@ Beste era batez, fitxategiak zuzenean arrastatu eta jaregin ditzakezu zure fitxa
Sortu hautatutako fitxategien kopia bat. Sortu hautatutako fitxategien kopia bat.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Ezabatu
Delete the currently selected files. Ezabatu hautatutako fitxategiak.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Ezabatu guztiak
Delete all files. Ezabatu fitxategi guztiak.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Esportatu... ### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Esportatu...

View File

@@ -29,13 +29,13 @@ cÈ inoltre possibile trascinare i file direttamente dal file system del tuo Pc
Crea una copia dei file attualmente selezionati. Crea una copia dei file attualmente selezionati.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" />Elimina
Delete the currently selected files. Elimina i file attualmente selezionati.
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all ### <FileX size="16" class="inline-block" style="margin-bottom: 2px" />Cancella tutto
Delete all files. Elimina tutti i file.
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Esporta... ### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> Esporta...

View File

@@ -14,7 +14,7 @@ Deze handleiding zal je door alle componenten en gereedschappen van de interface
<DocsImage src="getting-started/interface" alt="De gpx.studio interface." /> <DocsImage src="getting-started/interface" alt="De gpx.studio interface." />
Zoals weergegeven in bovenstaande scherm, is de interface verdeeld in vier hoofddelen rond de kaart. Zoals weergegeven in bovenstaande scherm, is de interface verdeeld in vier hoofddelen rond de kaart.
Voordat we in de details van elke sectie duiken, hebben we een snel overzicht van de interface. Voordat we in de details van elke sectie duiken, eerst een snel overzicht van de interface.
## Menu ## Menu

View File

@@ -83,7 +83,7 @@ Deze actie is alleen beschikbaar wanneer de verticale indeling van de bestandsli
### <ClipboardPaste size="16" class="inline-block" style="margin-bottom: 2px" /> Plakken ### <ClipboardPaste size="16" class="inline-block" style="margin-bottom: 2px" /> Plakken
Plak de bestandsitems van het klembord naar het huidige hiërarchie niveau indien compatibel. Plak de bestandsitems van het klembord naar het huidige hiërarchieniveau indien compatibel.
<DocsNote> <DocsNote>

View File

@@ -33,9 +33,9 @@ title: 文件
Delete the currently selected files. 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 files. 删除全部文件。
### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> 导出... ### <Download size="16" class="inline-block" style="margin-bottom: 2px" /> 导出...

View File

@@ -17,7 +17,6 @@ import {
import { i18n } from '$lib/i18n.svelte'; import { i18n } from '$lib/i18n.svelte';
import { freeze, type WritableDraft } from 'immer'; import { freeze, type WritableDraft } from 'immer';
import { import {
distance,
GPXFile, GPXFile,
parseGPX, parseGPX,
Track, Track,
@@ -30,7 +29,7 @@ import {
} from 'gpx'; } from 'gpx';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { settings } from '$lib/logic/settings'; import { settings } from '$lib/logic/settings';
import { getClosestLinePoint, getElevation } from '$lib/utils'; import { getClosestLinePoint, getClosestTrackSegments, getElevation } from '$lib/utils';
import { gpxStatistics } from '$lib/logic/statistics'; import { gpxStatistics } from '$lib/logic/statistics';
import { boundsManager } from './bounds'; import { boundsManager } from './bounds';
@@ -453,34 +452,13 @@ export const fileActions = {
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => { selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
if (level === ListLevel.FILE) { if (level === ListLevel.FILE) {
let file = fileStateCollection.getFile(fileId); let file = fileStateCollection.getFile(fileId);
if (file) { let statistics = fileStateCollection.getStatistics(fileId);
if (file && statistics) {
if (file.trk.length > 1) { if (file.trk.length > 1) {
let fileIds = getFileIds(file.trk.length); let fileIds = getFileIds(file.trk.length);
let closest = file.wpt.map((wpt, wptIndex) => { let closest = file.wpt.map((wpt) =>
return { getClosestTrackSegments(file, statistics, wpt.getCoordinates())
wptIndex: wptIndex, );
index: [0],
distance: Number.MAX_VALUE,
};
});
file.trk.forEach((track, index) => {
track.getSegments().forEach((segment) => {
segment.trkpt.forEach((point) => {
file.wpt.forEach((wpt, wptIndex) => {
let dist = distance(
point.getCoordinates(),
wpt.getCoordinates()
);
if (dist < closest[wptIndex].distance) {
closest[wptIndex].distance = dist;
closest[wptIndex].index = [index];
} else if (dist === closest[wptIndex].distance) {
closest[wptIndex].index.push(index);
}
});
});
});
});
file.trk.forEach((track, index) => { file.trk.forEach((track, index) => {
let newFile = file.clone(); let newFile = file.clone();
let tracks = track.trkseg.map((segment, segmentIndex) => { let tracks = track.trkseg.map((segment, segmentIndex) => {
@@ -495,9 +473,11 @@ export const fileActions = {
newFile.replaceWaypoints( newFile.replaceWaypoints(
0, 0,
file.wpt.length - 1, file.wpt.length - 1,
closest file.wpt.filter((wpt, wptIndex) =>
.filter((c) => c.index.includes(index)) closest[wptIndex].some(
.map((c) => file.wpt[c.wptIndex]) ([trackIndex, segmentIndex]) => trackIndex === index
)
)
); );
newFile._data.id = fileIds[index]; newFile._data.id = fileIds[index];
newFile.metadata.name = newFile.metadata.name =
@@ -506,29 +486,9 @@ export const fileActions = {
}); });
} else if (file.trk.length === 1) { } else if (file.trk.length === 1) {
let fileIds = getFileIds(file.trk[0].trkseg.length); let fileIds = getFileIds(file.trk[0].trkseg.length);
let closest = file.wpt.map((wpt, wptIndex) => { let closest = file.wpt.map((wpt) =>
return { getClosestTrackSegments(file, statistics, wpt.getCoordinates())
wptIndex: wptIndex, );
index: [0],
distance: Number.MAX_VALUE,
};
});
file.trk[0].trkseg.forEach((segment, index) => {
segment.trkpt.forEach((point) => {
file.wpt.forEach((wpt, wptIndex) => {
let dist = distance(
point.getCoordinates(),
wpt.getCoordinates()
);
if (dist < closest[wptIndex].distance) {
closest[wptIndex].distance = dist;
closest[wptIndex].index = [index];
} else if (dist === closest[wptIndex].distance) {
closest[wptIndex].index.push(index);
}
});
});
});
file.trk[0].trkseg.forEach((segment, index) => { file.trk[0].trkseg.forEach((segment, index) => {
let newFile = file.clone(); let newFile = file.clone();
newFile.replaceTrackSegments(0, 0, file.trk[0].trkseg.length - 1, [ newFile.replaceTrackSegments(0, 0, file.trk[0].trkseg.length - 1, [
@@ -537,9 +497,11 @@ export const fileActions = {
newFile.replaceWaypoints( newFile.replaceWaypoints(
0, 0,
file.wpt.length - 1, file.wpt.length - 1,
closest file.wpt.filter((wpt, wptIndex) =>
.filter((c) => c.index.includes(index)) closest[wptIndex].some(
.map((c) => file.wpt[c.wptIndex]) ([trackIndex, segmentIndex]) => segmentIndex === index
)
)
); );
newFile._data.id = fileIds[index]; newFile._data.id = fileIds[index];
newFile.metadata.name = `${file.trk[0].name ?? file.metadata.name} (${index + 1})`; newFile.metadata.name = `${file.trk[0].name ?? file.metadata.name} (${index + 1})`;

View File

@@ -22,25 +22,34 @@ export class GPXStatisticsTree {
} }
getStatisticsFor(item: ListItem): GPXStatistics { getStatisticsFor(item: ListItem): GPXStatistics {
let statistics = new GPXStatistics(); let statistics = [];
let id = item.getIdAtLevel(this.level); let id = item.getIdAtLevel(this.level);
if (id === undefined || id === 'waypoints') { if (id === undefined || id === 'waypoints') {
Object.keys(this.statistics).forEach((key) => { Object.keys(this.statistics).forEach((key) => {
if (this.statistics[key] instanceof GPXStatistics) { if (this.statistics[key] instanceof GPXStatistics) {
statistics.mergeWith(this.statistics[key]); statistics.push(this.statistics[key]);
} else { } else {
statistics.mergeWith(this.statistics[key].getStatisticsFor(item)); statistics.push(this.statistics[key].getStatisticsFor(item));
} }
}); });
} else { } else {
let child = this.statistics[id]; let child = this.statistics[id];
if (child instanceof GPXStatistics) { if (child instanceof GPXStatistics) {
statistics.mergeWith(child); statistics.push(child);
} else if (child !== undefined) { } else if (child !== undefined) {
statistics.mergeWith(child.getStatisticsFor(item)); statistics.push(child.getStatisticsFor(item));
} }
} }
return statistics; if (statistics.length === 0) {
return new GPXStatistics();
} else if (statistics.length === 1) {
return statistics[0];
} else {
return statistics.reduce((acc, curr) => {
acc.mergeWith(curr);
return acc;
}, new GPXStatistics());
}
} }
} }
export type GPXFileWithStatistics = { file: GPXFile; statistics: GPXStatisticsTree }; export type GPXFileWithStatistics = { file: GPXFile; statistics: GPXStatisticsTree };

View File

@@ -2,11 +2,13 @@ import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import { base } from '$app/paths'; import { base } from '$app/paths';
import { languages } from '$lib/languages'; import { languages } from '$lib/languages';
import { TrackPoint, Waypoint, type Coordinates, crossarcDistance, distance } from 'gpx'; import { TrackPoint, Waypoint, type Coordinates, crossarcDistance, distance, GPXFile } from 'gpx';
import mapboxgl from 'mapbox-gl'; import mapboxgl from 'mapbox-gl';
import { pointToTile, pointToTileFraction } from '@mapbox/tilebelt'; import { pointToTile, pointToTileFraction } from '@mapbox/tilebelt';
import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public'; import { PUBLIC_MAPBOX_TOKEN } from '$env/static/public';
import PNGReader from 'png.js'; import PNGReader from 'png.js';
import type { GPXStatisticsTree } from '$lib/logic/statistics-tree';
import { ListTrackSegmentItem } from '$lib/components/file-list/file-list';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
@@ -47,6 +49,59 @@ export function getClosestLinePoint(
return closest; return closest;
} }
export function getClosestTrackSegments(
file: GPXFile,
statistics: GPXStatisticsTree,
point: Coordinates
): [number, number][] {
let segmentBoundsDistances: [number, number, number][] = [];
file.forEachSegment((segment, trackIndex, segmentIndex) => {
let segmentStatistics = statistics.getStatisticsFor(
new ListTrackSegmentItem(file._data.id, trackIndex, segmentIndex)
);
let segmentBounds = segmentStatistics.global.bounds;
let northEast = segmentBounds.northEast;
let southWest = segmentBounds.southWest;
let bounds = new mapboxgl.LngLatBounds(southWest, northEast);
if (bounds.contains(point)) {
segmentBoundsDistances.push([0, trackIndex, segmentIndex]);
} else {
let northWest: Coordinates = { lat: northEast.lat, lon: southWest.lon };
let southEast: Coordinates = { lat: southWest.lat, lon: northEast.lon };
let distanceToBounds = Math.min(
crossarcDistance(northWest, northEast, point),
crossarcDistance(northEast, southEast, point),
crossarcDistance(southEast, southWest, point),
crossarcDistance(southWest, northWest, point)
);
segmentBoundsDistances.push([distanceToBounds, trackIndex, segmentIndex]);
}
});
segmentBoundsDistances.sort((a, b) => a[0] - b[0]);
let closest: { distance: number; indices: [number, number][] } = {
distance: Number.MAX_VALUE,
indices: [],
};
for (let s = 0; s < segmentBoundsDistances.length; s++) {
if (segmentBoundsDistances[s][0] > closest.distance) {
break;
}
const segment = file.getSegment(segmentBoundsDistances[s][1], segmentBoundsDistances[s][2]);
segment.trkpt.forEach((pt) => {
let dist = distance(pt.getCoordinates(), point);
if (dist < closest.distance) {
closest.distance = dist;
closest.indices = [[segmentBoundsDistances[s][1], segmentBoundsDistances[s][2]]];
} else if (dist === closest.distance) {
closest.indices.push([segmentBoundsDistances[s][1], segmentBoundsDistances[s][2]]);
}
});
}
return closest.indices;
}
export function getElevation( export function getElevation(
points: (TrackPoint | Waypoint | Coordinates)[], points: (TrackPoint | Waypoint | Coordinates)[],
ELEVATION_ZOOM: number = 13, ELEVATION_ZOOM: number = 13,

View File

@@ -79,7 +79,8 @@
"unhide": "Паказаць", "unhide": "Паказаць",
"center": "Center", "center": "Center",
"open_in": "Адчыніць у", "open_in": "Адчыніць у",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Veure", "unhide": "Veure",
"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"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Aigua", "water": "Aigua",
"shower": "Dutxa", "shower": "Dutxa",
"shelter": "Refugi", "shelter": "Refugi",
"cemetery": "Cemetery",
"motorized": "Cotxes i motos", "motorized": "Cotxes i motos",
"fuel-station": "Gasolinera", "fuel-station": "Gasolinera",
"parking": "Aparcament", "parking": "Aparcament",

View File

@@ -79,7 +79,8 @@
"unhide": "Zobrazit skryté", "unhide": "Zobrazit skryté",
"center": "Vycentrovat", "center": "Vycentrovat",
"open_in": "Otevřít v", "open_in": "Otevřít v",
"copy_coordinates": "Zkopírovat souřadnice" "copy_coordinates": "Zkopírovat souřadnice",
"edit_osm": "Upravit v OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Voda", "water": "Voda",
"shower": "Sprcha", "shower": "Sprcha",
"shelter": "Přístřeší", "shelter": "Přístřeší",
"cemetery": "Hřbitov",
"motorized": "Automobily a motocykly", "motorized": "Automobily a motocykly",
"fuel-station": "Čerpací stanice", "fuel-station": "Čerpací stanice",
"parking": "Parkoviště", "parking": "Parkoviště",

View File

@@ -79,7 +79,8 @@
"unhide": "Unhide", "unhide": "Unhide",
"center": "Center", "center": "Center",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Kopier koordinater" "copy_coordinates": "Kopier koordinater",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Einblenden", "unhide": "Einblenden",
"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"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Trinkwasser", "water": "Trinkwasser",
"shower": "Dusche", "shower": "Dusche",
"shelter": "Unterstand", "shelter": "Unterstand",
"cemetery": "Cemetery",
"motorized": "Autos und Motorräder", "motorized": "Autos und Motorräder",
"fuel-station": "Tankstelle", "fuel-station": "Tankstelle",
"parking": "Parken", "parking": "Parken",

View File

@@ -79,7 +79,8 @@
"unhide": "Unhide", "unhide": "Unhide",
"center": "Center", "center": "Center",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Unhide", "unhide": "Unhide",
"center": "Center", "center": "Center",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -36,7 +36,7 @@
"switch_basemap": "Cambiar al mapa base anterior", "switch_basemap": "Cambiar al mapa base anterior",
"toggle_overlays": "Alternar capas", "toggle_overlays": "Alternar capas",
"toggle_3d": "Alternar 3D", "toggle_3d": "Alternar 3D",
"settings": "Configuraciones", "settings": "Configuración",
"distance_units": "Unidades de distancia", "distance_units": "Unidades de distancia",
"metric": "Métrico", "metric": "Métrico",
"imperial": "Imperial", "imperial": "Imperial",
@@ -79,7 +79,8 @@
"unhide": "Mostrar", "unhide": "Mostrar",
"center": "Centrar", "center": "Centrar",
"open_in": "Abrir en", "open_in": "Abrir en",
"copy_coordinates": "Copiar coordenadas" "copy_coordinates": "Copiar coordenadas",
"edit_osm": "Editar en OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Agua", "water": "Agua",
"shower": "Ducha", "shower": "Ducha",
"shelter": "Refugio", "shelter": "Refugio",
"cemetery": "Cementerio",
"motorized": "Coches y motos", "motorized": "Coches y motos",
"fuel-station": "Gasolinera", "fuel-station": "Gasolinera",
"parking": "Aparcamiento", "parking": "Aparcamiento",

View File

@@ -28,7 +28,7 @@
"undo": "Desegin", "undo": "Desegin",
"redo": "Berregin", "redo": "Berregin",
"delete": "Ezabatu", "delete": "Ezabatu",
"delete_all": "Delete all", "delete_all": "Ezabatu guztiak",
"select_all": "Hautatu dena", "select_all": "Hautatu dena",
"view": "Ikusi", "view": "Ikusi",
"elevation_profile": "Altuera profila", "elevation_profile": "Altuera profila",
@@ -79,7 +79,8 @@
"unhide": "Erakutsi", "unhide": "Erakutsi",
"center": "Erdiratu", "center": "Erdiratu",
"open_in": "Ireki hemen", "open_in": "Ireki hemen",
"copy_coordinates": "Kopiatu koordenatuak" "copy_coordinates": "Kopiatu koordenatuak",
"edit_osm": "Editatu OpenStreeMapen"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Ura", "water": "Ura",
"shower": "Dutxa", "shower": "Dutxa",
"shelter": "Babeslekua", "shelter": "Babeslekua",
"cemetery": "Hilerria",
"motorized": "Kotxeak eta motorrak", "motorized": "Kotxeak eta motorrak",
"fuel-station": "Gasolindegia", "fuel-station": "Gasolindegia",
"parking": "Aparkalekua", "parking": "Aparkalekua",

View File

@@ -79,7 +79,8 @@
"unhide": "Näytä", "unhide": "Näytä",
"center": "Keskitä", "center": "Keskitä",
"open_in": "Avaa", "open_in": "Avaa",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Afficher", "unhide": "Afficher",
"center": "Centrer", "center": "Centrer",
"open_in": "Ouvrir avec", "open_in": "Ouvrir avec",
"copy_coordinates": "Copier les coordonnées" "copy_coordinates": "Copier les coordonnées",
"edit_osm": "Éditer dans OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Cours d'eau", "water": "Cours d'eau",
"shower": "Douche", "shower": "Douche",
"shelter": "Abri", "shelter": "Abri",
"cemetery": "Cimetière",
"motorized": "Voitures et motos", "motorized": "Voitures et motos",
"fuel-station": "Station-service", "fuel-station": "Station-service",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Unhide", "unhide": "Unhide",
"center": "Center", "center": "Center",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "גשם", "shower": "גשם",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Felfedés ", "unhide": "Felfedés ",
"center": "Középre ", "center": "Középre ",
"open_in": "Megnyitás itt ", "open_in": "Megnyitás itt ",
"copy_coordinates": "Koordináták másolása" "copy_coordinates": "Koordináták másolása",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Víz", "water": "Víz",
"shower": "Zuhanyozó", "shower": "Zuhanyozó",
"shelter": "Menedék", "shelter": "Menedék",
"cemetery": "Cemetery",
"motorized": "Autók és Motorok", "motorized": "Autók és Motorok",
"fuel-station": "Benzinkút", "fuel-station": "Benzinkút",
"parking": "Parkoló", "parking": "Parkoló",

View File

@@ -79,7 +79,8 @@
"unhide": "Tampilkan", "unhide": "Tampilkan",
"center": "Tengah", "center": "Tengah",
"open_in": "Buka di", "open_in": "Buka di",
"copy_coordinates": "Salin koordinat" "copy_coordinates": "Salin koordinat",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Air", "water": "Air",
"shower": "Mandi", "shower": "Mandi",
"shelter": "Penampungan", "shelter": "Penampungan",
"cemetery": "Cemetery",
"motorized": "Mobil dan Motor", "motorized": "Mobil dan Motor",
"fuel-station": "Stasiun bahan bakar", "fuel-station": "Stasiun bahan bakar",
"parking": "Parkir", "parking": "Parkir",

View File

@@ -28,7 +28,7 @@
"undo": "Annulla", "undo": "Annulla",
"redo": "Ripeti", "redo": "Ripeti",
"delete": "Elimina", "delete": "Elimina",
"delete_all": "Delete all", "delete_all": "Cancella tutto",
"select_all": "Seleziona tutto", "select_all": "Seleziona tutto",
"view": "Visualizza", "view": "Visualizza",
"elevation_profile": "Profilo altimetrico", "elevation_profile": "Profilo altimetrico",
@@ -79,7 +79,8 @@
"unhide": "Mostra", "unhide": "Mostra",
"center": "Centra", "center": "Centra",
"open_in": "Apri con", "open_in": "Apri con",
"copy_coordinates": "Copia le coordinate" "copy_coordinates": "Copia le coordinate",
"edit_osm": "Modifica in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Acqua", "water": "Acqua",
"shower": "Doccia", "shower": "Doccia",
"shelter": "Riparo", "shelter": "Riparo",
"cemetery": "Cimitero",
"motorized": "Auto e Motocicli", "motorized": "Auto e Motocicli",
"fuel-station": "Stazione di Rifornimento", "fuel-station": "Stazione di Rifornimento",
"parking": "Parcheggio", "parking": "Parcheggio",

View File

@@ -79,7 +79,8 @@
"unhide": "표시", "unhide": "표시",
"center": "중앙", "center": "중앙",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "대피소", "shelter": "대피소",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "주유소", "fuel-station": "주유소",
"parking": "주차장", "parking": "주차장",

View File

@@ -79,7 +79,8 @@
"unhide": "Rodyti", "unhide": "Rodyti",
"center": "Center", "center": "Center",
"open_in": "Atverti naudojant", "open_in": "Atverti naudojant",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Vanduo", "water": "Vanduo",
"shower": "Dušas", "shower": "Dušas",
"shelter": "Prieglauda", "shelter": "Prieglauda",
"cemetery": "Cemetery",
"motorized": "Automobiliai ir motociklai", "motorized": "Automobiliai ir motociklai",
"fuel-station": "Degalinė", "fuel-station": "Degalinė",
"parking": "Automobilių stovėjimo aikštelė", "parking": "Automobilių stovėjimo aikštelė",

View File

@@ -79,7 +79,8 @@
"unhide": "Unhide", "unhide": "Unhide",
"center": "Center", "center": "Center",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Maak zichtbaar", "unhide": "Maak zichtbaar",
"center": "Midden", "center": "Midden",
"open_in": "Openen in", "open_in": "Openen in",
"copy_coordinates": "Coördinaten kopiëren" "copy_coordinates": "Coördinaten kopiëren",
"edit_osm": "Bewerken in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Douche", "shower": "Douche",
"shelter": "Schuilplaats", "shelter": "Schuilplaats",
"cemetery": "Begraafplaats",
"motorized": "Auto's en Motorfietsen", "motorized": "Auto's en Motorfietsen",
"fuel-station": "Tankstation", "fuel-station": "Tankstation",
"parking": "Parkeren", "parking": "Parkeren",

View File

@@ -79,7 +79,8 @@
"unhide": "Vis", "unhide": "Vis",
"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"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Vann", "water": "Vann",
"shower": "Dusj", "shower": "Dusj",
"shelter": "Ly", "shelter": "Ly",
"cemetery": "Cemetery",
"motorized": "Biler og motorsykler", "motorized": "Biler og motorsykler",
"fuel-station": "Bensinstasjon", "fuel-station": "Bensinstasjon",
"parking": "Parkering", "parking": "Parkering",

View File

@@ -79,7 +79,8 @@
"unhide": "Pokaż", "unhide": "Pokaż",
"center": "Wyśrodkuj", "center": "Wyśrodkuj",
"open_in": "Otwórz w", "open_in": "Otwórz w",
"copy_coordinates": "Kopiuj współrzędne" "copy_coordinates": "Kopiuj współrzędne",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Woda", "water": "Woda",
"shower": "Prysznic", "shower": "Prysznic",
"shelter": "Schronienie", "shelter": "Schronienie",
"cemetery": "Cemetery",
"motorized": "Samochody i motocykle", "motorized": "Samochody i motocykle",
"fuel-station": "Stacja paliw", "fuel-station": "Stacja paliw",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Mostrar", "unhide": "Mostrar",
"center": "Centralizar", "center": "Centralizar",
"open_in": "Abrir em", "open_in": "Abrir em",
"copy_coordinates": "Copiar coordenadas" "copy_coordinates": "Copiar coordenadas",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Água", "water": "Água",
"shower": "Chuveiro", "shower": "Chuveiro",
"shelter": "Abrigo", "shelter": "Abrigo",
"cemetery": "Cemetery",
"motorized": "Carros e Motocicletas", "motorized": "Carros e Motocicletas",
"fuel-station": "Postos de combustível", "fuel-station": "Postos de combustível",
"parking": "Estacionamento", "parking": "Estacionamento",

View File

@@ -79,7 +79,8 @@
"unhide": "Mostrar", "unhide": "Mostrar",
"center": "Centro", "center": "Centro",
"open_in": "Abrir em", "open_in": "Abrir em",
"copy_coordinates": "Copiar coordenadas" "copy_coordinates": "Copiar coordenadas",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Água", "water": "Água",
"shower": "Chuveiro", "shower": "Chuveiro",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Carros e Motocicletas", "motorized": "Carros e Motocicletas",
"fuel-station": "Postos de combustível", "fuel-station": "Postos de combustível",
"parking": "Estacionamento", "parking": "Estacionamento",

View File

@@ -79,7 +79,8 @@
"unhide": "Dezvăluie", "unhide": "Dezvăluie",
"center": "Center", "center": "Center",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Copiază coordonatele" "copy_coordinates": "Copiază coordonatele",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Отобразить", "unhide": "Отобразить",
"center": "По центру", "center": "По центру",
"open_in": "Открыть в", "open_in": "Открыть в",
"copy_coordinates": "Скопировать координаты" "copy_coordinates": "Скопировать координаты",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Вода", "water": "Вода",
"shower": "Душ", "shower": "Душ",
"shelter": "Укрытие", "shelter": "Укрытие",
"cemetery": "Cemetery",
"motorized": "Автомобили и мотоциклы", "motorized": "Автомобили и мотоциклы",
"fuel-station": "Заправочная станция", "fuel-station": "Заправочная станция",
"parking": "Парковка", "parking": "Парковка",

View File

@@ -79,7 +79,8 @@
"unhide": "Prikaži", "unhide": "Prikaži",
"center": "Centar", "center": "Centar",
"open_in": "Otvorite u", "open_in": "Otvorite u",
"copy_coordinates": "Kopiraj koordinate" "copy_coordinates": "Kopiraj koordinate",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Voda", "water": "Voda",
"shower": "Tuš", "shower": "Tuš",
"shelter": "Sklonište", "shelter": "Sklonište",
"cemetery": "Cemetery",
"motorized": "Automobili i motocikli", "motorized": "Automobili i motocikli",
"fuel-station": "Benzinska stanica", "fuel-station": "Benzinska stanica",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Visa", "unhide": "Visa",
"center": "Center", "center": "Center",
"open_in": "Öppna i", "open_in": "Öppna i",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Dusch", "shower": "Dusch",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Bensinstation", "fuel-station": "Bensinstation",
"parking": "Parkering", "parking": "Parkering",

View File

@@ -79,7 +79,8 @@
"unhide": "Unhide", "unhide": "Unhide",
"center": "Center", "center": "Center",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Göster", "unhide": "Göster",
"center": "Merkez", "center": "Merkez",
"open_in": "Uygulamada Aç", "open_in": "Uygulamada Aç",
"copy_coordinates": "Koordinatları kopyala" "copy_coordinates": "Koordinatları kopyala",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Su", "water": "Su",
"shower": "Duş", "shower": "Duş",
"shelter": "Barınak", "shelter": "Barınak",
"cemetery": "Cemetery",
"motorized": "Araba ve Motosiklet", "motorized": "Araba ve Motosiklet",
"fuel-station": "Benzin istasyonu", "fuel-station": "Benzin istasyonu",
"parking": "Otopark", "parking": "Otopark",

View File

@@ -79,7 +79,8 @@
"unhide": "Показати", "unhide": "Показати",
"center": "Центр", "center": "Центр",
"open_in": "Відкрити в", "open_in": "Відкрити в",
"copy_coordinates": "Копіювати координати" "copy_coordinates": "Копіювати координати",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Вода", "water": "Вода",
"shower": "Душ", "shower": "Душ",
"shelter": "Укриття", "shelter": "Укриття",
"cemetery": "Cemetery",
"motorized": "Автомобілі та Мотоцикли", "motorized": "Автомобілі та Мотоцикли",
"fuel-station": "Паливна станція", "fuel-station": "Паливна станція",
"parking": "Парковка", "parking": "Парковка",

View File

@@ -79,7 +79,8 @@
"unhide": "Unhide", "unhide": "Unhide",
"center": "Center", "center": "Center",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Nước", "water": "Nước",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -79,7 +79,8 @@
"unhide": "Unhide", "unhide": "Unhide",
"center": "Center", "center": "Center",
"open_in": "Open in", "open_in": "Open in",
"copy_coordinates": "Copy coordinates" "copy_coordinates": "Copy coordinates",
"edit_osm": "Edit in OpenStreetMap"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "Water", "water": "Water",
"shower": "Shower", "shower": "Shower",
"shelter": "Shelter", "shelter": "Shelter",
"cemetery": "Cemetery",
"motorized": "Cars and Motorcycles", "motorized": "Cars and Motorcycles",
"fuel-station": "Fuel Station", "fuel-station": "Fuel Station",
"parking": "Parking", "parking": "Parking",

View File

@@ -28,7 +28,7 @@
"undo": "撤销", "undo": "撤销",
"redo": "恢复", "redo": "恢复",
"delete": "删除", "delete": "删除",
"delete_all": "", "delete_all": "全部删除",
"select_all": "全选", "select_all": "全选",
"view": "显示", "view": "显示",
"elevation_profile": "海拔剖面图", "elevation_profile": "海拔剖面图",
@@ -79,7 +79,8 @@
"unhide": "显示", "unhide": "显示",
"center": "居中", "center": "居中",
"open_in": "打开于", "open_in": "打开于",
"copy_coordinates": "复制坐标" "copy_coordinates": "复制坐标",
"edit_osm": "在 OpenStreetMap 中编辑"
}, },
"toolbar": { "toolbar": {
"routing": { "routing": {
@@ -352,6 +353,7 @@
"water": "饮用水", "water": "饮用水",
"shower": "淋浴", "shower": "淋浴",
"shelter": "庇护所", "shelter": "庇护所",
"cemetery": "墓地",
"motorized": "汽车和摩托车", "motorized": "汽车和摩托车",
"fuel-station": "加油站", "fuel-station": "加油站",
"parking": "停车场", "parking": "停车场",