mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-01-22 17:48:41 +00:00
Compare commits
12 Commits
8371229bf2
...
l10n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87ba333b83 | ||
|
|
b3b77d752b | ||
|
|
182ef1a51b | ||
|
|
06cf04d346 | ||
|
|
f0f1ecb2df | ||
|
|
2eb6ef6f03 | ||
|
|
f7c0805161 | ||
|
|
4e18e3c8a0 | ||
|
|
59f31caf26 | ||
|
|
f24956c58d | ||
|
|
9019317e5c | ||
|
|
2a0227c1de |
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
website/src/lib/components/ui
|
||||||
|
website/src/lib/docs/**/*.mdx
|
||||||
|
**/*.webmanifest
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 gpx.studio
|
Copyright (c) 2026 gpx.studio
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
474
gpx/src/gpx.ts
474
gpx/src/gpx.ts
@@ -1,4 +1,5 @@
|
|||||||
import { ramerDouglasPeucker } from './simplify';
|
import { ramerDouglasPeucker } from './simplify';
|
||||||
|
import { GPXStatistics, GPXStatisticsGroup, TrackPointLocalStatistics } from './statistics';
|
||||||
import {
|
import {
|
||||||
Coordinates,
|
Coordinates,
|
||||||
GPXFileAttributes,
|
GPXFileAttributes,
|
||||||
@@ -36,7 +37,6 @@ export abstract class GPXTreeElement<T extends GPXTreeElement<any>> {
|
|||||||
abstract getNumberOfTrackPoints(): number;
|
abstract getNumberOfTrackPoints(): number;
|
||||||
abstract getStartTimestamp(): Date | undefined;
|
abstract getStartTimestamp(): Date | undefined;
|
||||||
abstract getEndTimestamp(): Date | undefined;
|
abstract getEndTimestamp(): Date | undefined;
|
||||||
abstract getStatistics(): GPXStatistics;
|
|
||||||
abstract getSegments(): TrackSegment[];
|
abstract getSegments(): TrackSegment[];
|
||||||
abstract getTrackPoints(): TrackPoint[];
|
abstract getTrackPoints(): TrackPoint[];
|
||||||
|
|
||||||
@@ -76,14 +76,6 @@ abstract class GPXTreeNode<T extends GPXTreeElement<any>> extends GPXTreeElement
|
|||||||
return this.children[this.children.length - 1].getEndTimestamp();
|
return this.children[this.children.length - 1].getEndTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatistics(): GPXStatistics {
|
|
||||||
let statistics = new GPXStatistics();
|
|
||||||
for (let child of this.children) {
|
|
||||||
statistics.mergeWith(child.getStatistics());
|
|
||||||
}
|
|
||||||
return statistics;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSegments(): TrackSegment[] {
|
getSegments(): TrackSegment[] {
|
||||||
return this.children.flatMap((child) => child.getSegments());
|
return this.children.flatMap((child) => child.getSegments());
|
||||||
}
|
}
|
||||||
@@ -208,8 +200,16 @@ export class GPXFile extends GPXTreeNode<Track> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStatistics(): GPXStatisticsGroup {
|
||||||
|
let statistics = new GPXStatisticsGroup();
|
||||||
|
this.forEachSegment((segment) => {
|
||||||
|
statistics.add(segment.getStatistics());
|
||||||
|
});
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
|
||||||
getStyle(defaultColor?: string): MergedLineStyles {
|
getStyle(defaultColor?: string): MergedLineStyles {
|
||||||
return this.trk
|
const style = this.trk
|
||||||
.map((track) => track.getStyle())
|
.map((track) => track.getStyle())
|
||||||
.reduce(
|
.reduce(
|
||||||
(acc, style) => {
|
(acc, style) => {
|
||||||
@@ -219,8 +219,6 @@ export class GPXFile extends GPXTreeNode<Track> {
|
|||||||
!acc.color.includes(style['gpx_style:color'])
|
!acc.color.includes(style['gpx_style:color'])
|
||||||
) {
|
) {
|
||||||
acc.color.push(style['gpx_style:color']);
|
acc.color.push(style['gpx_style:color']);
|
||||||
} else if (defaultColor && !acc.color.includes(defaultColor)) {
|
|
||||||
acc.color.push(defaultColor);
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
style &&
|
style &&
|
||||||
@@ -244,6 +242,10 @@ export class GPXFile extends GPXTreeNode<Track> {
|
|||||||
width: [],
|
width: [],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
if (style.color.length === 0 && defaultColor) {
|
||||||
|
style.color.push(defaultColor);
|
||||||
|
}
|
||||||
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
clone(): GPXFile {
|
clone(): GPXFile {
|
||||||
@@ -818,7 +820,9 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
_computeStatistics(): GPXStatistics {
|
_computeStatistics(): GPXStatistics {
|
||||||
let statistics = new GPXStatistics();
|
let statistics = new GPXStatistics();
|
||||||
|
|
||||||
|
statistics.global.length = this.trkpt.length;
|
||||||
statistics.local.points = this.trkpt.slice(0);
|
statistics.local.points = this.trkpt.slice(0);
|
||||||
|
statistics.local.data = this.trkpt.map(() => new TrackPointLocalStatistics());
|
||||||
|
|
||||||
const points = this.trkpt;
|
const points = this.trkpt;
|
||||||
for (let i = 0; i < points.length; i++) {
|
for (let i = 0; i < points.length; i++) {
|
||||||
@@ -830,19 +834,18 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
statistics.global.distance.total += dist;
|
statistics.global.distance.total += dist;
|
||||||
}
|
}
|
||||||
|
|
||||||
statistics.local.distance.total.push(statistics.global.distance.total);
|
statistics.local.data[i].distance.total = statistics.global.distance.total;
|
||||||
|
|
||||||
// time
|
// time
|
||||||
if (points[i].time === undefined) {
|
if (points[i].time === undefined) {
|
||||||
statistics.local.time.total.push(0);
|
statistics.local.data[i].time.total = 0;
|
||||||
} else {
|
} else {
|
||||||
if (statistics.global.time.start === undefined) {
|
if (statistics.global.time.start === undefined) {
|
||||||
statistics.global.time.start = points[i].time;
|
statistics.global.time.start = points[i].time;
|
||||||
}
|
}
|
||||||
statistics.global.time.end = points[i].time;
|
statistics.global.time.end = points[i].time;
|
||||||
statistics.local.time.total.push(
|
statistics.local.data[i].time.total =
|
||||||
(points[i].time.getTime() - statistics.global.time.start.getTime()) / 1000
|
(points[i].time.getTime() - statistics.global.time.start.getTime()) / 1000;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// speed
|
// speed
|
||||||
@@ -857,8 +860,8 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statistics.local.distance.moving.push(statistics.global.distance.moving);
|
statistics.local.data[i].distance.moving = statistics.global.distance.moving;
|
||||||
statistics.local.time.moving.push(statistics.global.time.moving);
|
statistics.local.data[i].time.moving = statistics.global.time.moving;
|
||||||
|
|
||||||
// bounds
|
// bounds
|
||||||
statistics.global.bounds.southWest.lat = Math.min(
|
statistics.global.bounds.southWest.lat = Math.min(
|
||||||
@@ -958,13 +961,22 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
? statistics.global.distance.moving / (statistics.global.time.moving / 3600)
|
? statistics.global.distance.moving / (statistics.global.time.moving / 3600)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
statistics.local.speed = timeWindowSmoothing(points, 10000, (start, end) =>
|
timeWindowSmoothing(
|
||||||
|
points,
|
||||||
|
10000,
|
||||||
|
(start, end) =>
|
||||||
points[start].time && points[end].time
|
points[start].time && points[end].time
|
||||||
? (3600 *
|
? (3600 *
|
||||||
(statistics.local.distance.total[end] -
|
(statistics.local.data[end].distance.total -
|
||||||
statistics.local.distance.total[start])) /
|
statistics.local.data[start].distance.total)) /
|
||||||
Math.max((points[end].time.getTime() - points[start].time.getTime()) / 1000, 1)
|
Math.max(
|
||||||
: undefined
|
(points[end].time.getTime() - points[start].time.getTime()) / 1000,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
(value, index) => {
|
||||||
|
statistics.local.data[index].speed = value;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return statistics;
|
return statistics;
|
||||||
@@ -984,7 +996,13 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
let cumulEle = 0;
|
let cumulEle = 0;
|
||||||
let currentStart = start;
|
let currentStart = start;
|
||||||
let currentEnd = start;
|
let currentEnd = start;
|
||||||
let smoothedEle = distanceWindowSmoothing(start, end + 1, statistics, 0.1, (s, e) => {
|
let prevSmoothedEle = 0;
|
||||||
|
distanceWindowSmoothing(
|
||||||
|
start,
|
||||||
|
end + 1,
|
||||||
|
statistics,
|
||||||
|
0.1,
|
||||||
|
(s, e) => {
|
||||||
for (let i = currentStart; i < s; i++) {
|
for (let i = currentStart; i < s; i++) {
|
||||||
cumulEle -= this.trkpt[i].ele ?? 0;
|
cumulEle -= this.trkpt[i].ele ?? 0;
|
||||||
}
|
}
|
||||||
@@ -994,43 +1012,49 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
currentStart = s;
|
currentStart = s;
|
||||||
currentEnd = e + 1;
|
currentEnd = e + 1;
|
||||||
return cumulEle / (e - s + 1);
|
return cumulEle / (e - s + 1);
|
||||||
});
|
},
|
||||||
smoothedEle[0] = this.trkpt[start].ele ?? 0;
|
(smoothedEle, j) => {
|
||||||
smoothedEle[smoothedEle.length - 1] = this.trkpt[end].ele ?? 0;
|
if (j === start) {
|
||||||
|
smoothedEle = this.trkpt[start].ele ?? 0;
|
||||||
for (let j = start; j < end; j++) {
|
prevSmoothedEle = smoothedEle;
|
||||||
statistics.local.elevation.gain.push(statistics.global.elevation.gain);
|
} else if (j === end) {
|
||||||
statistics.local.elevation.loss.push(statistics.global.elevation.loss);
|
smoothedEle = this.trkpt[end].ele ?? 0;
|
||||||
|
}
|
||||||
const ele = smoothedEle[j - start + 1] - smoothedEle[j - start];
|
const ele = smoothedEle - prevSmoothedEle;
|
||||||
if (ele > 0) {
|
if (ele > 0) {
|
||||||
statistics.global.elevation.gain += ele;
|
statistics.global.elevation.gain += ele;
|
||||||
} else if (ele < 0) {
|
} else if (ele < 0) {
|
||||||
statistics.global.elevation.loss -= ele;
|
statistics.global.elevation.loss -= ele;
|
||||||
}
|
}
|
||||||
|
prevSmoothedEle = smoothedEle;
|
||||||
|
if (j < end) {
|
||||||
|
statistics.local.data[j].elevation.gain = statistics.global.elevation.gain;
|
||||||
|
statistics.local.data[j].elevation.loss = statistics.global.elevation.loss;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statistics.local.elevation.gain.push(statistics.global.elevation.gain);
|
);
|
||||||
statistics.local.elevation.loss.push(statistics.global.elevation.loss);
|
}
|
||||||
|
if (statistics.global.length > 0) {
|
||||||
|
statistics.local.data[statistics.global.length - 1].elevation.gain =
|
||||||
|
statistics.global.elevation.gain;
|
||||||
|
statistics.local.data[statistics.global.length - 1].elevation.loss =
|
||||||
|
statistics.global.elevation.loss;
|
||||||
|
}
|
||||||
|
|
||||||
let slope = [];
|
|
||||||
let length = [];
|
|
||||||
for (let i = 0; i < simplified.length - 1; i++) {
|
for (let i = 0; i < simplified.length - 1; i++) {
|
||||||
let start = simplified[i].point._data.index;
|
let start = simplified[i].point._data.index;
|
||||||
let end = simplified[i + 1].point._data.index;
|
let end = simplified[i + 1].point._data.index;
|
||||||
let dist =
|
let dist =
|
||||||
statistics.local.distance.total[end] - statistics.local.distance.total[start];
|
statistics.local.data[end].distance.total -
|
||||||
|
statistics.local.data[start].distance.total;
|
||||||
let ele = (simplified[i + 1].point.ele ?? 0) - (simplified[i].point.ele ?? 0);
|
let ele = (simplified[i + 1].point.ele ?? 0) - (simplified[i].point.ele ?? 0);
|
||||||
|
|
||||||
for (let j = start; j < end + (i + 1 === simplified.length - 1 ? 1 : 0); j++) {
|
for (let j = start; j < end + (i + 1 === simplified.length - 1 ? 1 : 0); j++) {
|
||||||
slope.push((0.1 * ele) / dist);
|
statistics.local.data[j].slope.segment = (0.1 * ele) / dist;
|
||||||
length.push(dist);
|
statistics.local.data[j].slope.length = dist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statistics.local.slope.segment = slope;
|
distanceWindowSmoothing(
|
||||||
statistics.local.slope.length = length;
|
|
||||||
statistics.local.slope.at = distanceWindowSmoothing(
|
|
||||||
0,
|
0,
|
||||||
this.trkpt.length,
|
this.trkpt.length,
|
||||||
statistics,
|
statistics,
|
||||||
@@ -1038,8 +1062,12 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
(start, end) => {
|
(start, end) => {
|
||||||
const ele = this.trkpt[end].ele - this.trkpt[start].ele || 0;
|
const ele = this.trkpt[end].ele - this.trkpt[start].ele || 0;
|
||||||
const dist =
|
const dist =
|
||||||
statistics.local.distance.total[end] - statistics.local.distance.total[start];
|
statistics.local.data[end].distance.total -
|
||||||
|
statistics.local.data[start].distance.total;
|
||||||
return dist > 0 ? (0.1 * ele) / dist : 0;
|
return dist > 0 ? (0.1 * ele) / dist : 0;
|
||||||
|
},
|
||||||
|
(value, index) => {
|
||||||
|
statistics.local.data[index].slope.at = value;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1289,13 +1317,7 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
) {
|
) {
|
||||||
let og = getOriginal(this); // Read as much as possible from the original object because it is faster
|
let og = getOriginal(this); // Read as much as possible from the original object because it is faster
|
||||||
let statistics = og._computeStatistics();
|
let statistics = og._computeStatistics();
|
||||||
let trkpt = withArtificialTimestamps(
|
let trkpt = withArtificialTimestamps(og.trkpt, totalTime, lastPoint, startTime, statistics);
|
||||||
og.trkpt,
|
|
||||||
totalTime,
|
|
||||||
lastPoint,
|
|
||||||
startTime,
|
|
||||||
statistics.local.slope.at
|
|
||||||
);
|
|
||||||
this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
|
this.trkpt = freeze(trkpt); // Pre-freeze the array, faster as well
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1304,6 +1326,7 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emptyExtensions: Record<string, string> = {};
|
||||||
export class TrackPoint {
|
export class TrackPoint {
|
||||||
[immerable] = true;
|
[immerable] = true;
|
||||||
|
|
||||||
@@ -1398,7 +1421,7 @@ export class TrackPoint {
|
|||||||
this.extensions['gpxtpx:TrackPointExtension'] &&
|
this.extensions['gpxtpx:TrackPointExtension'] &&
|
||||||
this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']
|
this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']
|
||||||
? this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']
|
? this.extensions['gpxtpx:TrackPointExtension']['gpxtpx:Extensions']
|
||||||
: {};
|
: emptyExtensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
toTrackPointType(exclude: string[] = []): TrackPointType {
|
toTrackPointType(exclude: string[] = []): TrackPointType {
|
||||||
@@ -1619,305 +1642,6 @@ export class Waypoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GPXStatistics {
|
|
||||||
global: {
|
|
||||||
distance: {
|
|
||||||
moving: number;
|
|
||||||
total: number;
|
|
||||||
};
|
|
||||||
time: {
|
|
||||||
start: Date | undefined;
|
|
||||||
end: Date | undefined;
|
|
||||||
moving: number;
|
|
||||||
total: number;
|
|
||||||
};
|
|
||||||
speed: {
|
|
||||||
moving: number;
|
|
||||||
total: number;
|
|
||||||
};
|
|
||||||
elevation: {
|
|
||||||
gain: number;
|
|
||||||
loss: number;
|
|
||||||
};
|
|
||||||
bounds: {
|
|
||||||
southWest: Coordinates;
|
|
||||||
northEast: Coordinates;
|
|
||||||
};
|
|
||||||
atemp: {
|
|
||||||
avg: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
hr: {
|
|
||||||
avg: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
cad: {
|
|
||||||
avg: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
power: {
|
|
||||||
avg: number;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
extensions: Record<string, Record<string, number>>;
|
|
||||||
};
|
|
||||||
local: {
|
|
||||||
points: TrackPoint[];
|
|
||||||
distance: {
|
|
||||||
moving: number[];
|
|
||||||
total: number[];
|
|
||||||
};
|
|
||||||
time: {
|
|
||||||
moving: number[];
|
|
||||||
total: number[];
|
|
||||||
};
|
|
||||||
speed: number[];
|
|
||||||
elevation: {
|
|
||||||
gain: number[];
|
|
||||||
loss: number[];
|
|
||||||
};
|
|
||||||
slope: {
|
|
||||||
at: number[];
|
|
||||||
segment: number[];
|
|
||||||
length: number[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.global = {
|
|
||||||
distance: {
|
|
||||||
moving: 0,
|
|
||||||
total: 0,
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
start: undefined,
|
|
||||||
end: undefined,
|
|
||||||
moving: 0,
|
|
||||||
total: 0,
|
|
||||||
},
|
|
||||||
speed: {
|
|
||||||
moving: 0,
|
|
||||||
total: 0,
|
|
||||||
},
|
|
||||||
elevation: {
|
|
||||||
gain: 0,
|
|
||||||
loss: 0,
|
|
||||||
},
|
|
||||||
bounds: {
|
|
||||||
southWest: {
|
|
||||||
lat: 90,
|
|
||||||
lon: 180,
|
|
||||||
},
|
|
||||||
northEast: {
|
|
||||||
lat: -90,
|
|
||||||
lon: -180,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
atemp: {
|
|
||||||
avg: 0,
|
|
||||||
count: 0,
|
|
||||||
},
|
|
||||||
hr: {
|
|
||||||
avg: 0,
|
|
||||||
count: 0,
|
|
||||||
},
|
|
||||||
cad: {
|
|
||||||
avg: 0,
|
|
||||||
count: 0,
|
|
||||||
},
|
|
||||||
power: {
|
|
||||||
avg: 0,
|
|
||||||
count: 0,
|
|
||||||
},
|
|
||||||
extensions: {},
|
|
||||||
};
|
|
||||||
this.local = {
|
|
||||||
points: [],
|
|
||||||
distance: {
|
|
||||||
moving: [],
|
|
||||||
total: [],
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
moving: [],
|
|
||||||
total: [],
|
|
||||||
},
|
|
||||||
speed: [],
|
|
||||||
elevation: {
|
|
||||||
gain: [],
|
|
||||||
loss: [],
|
|
||||||
},
|
|
||||||
slope: {
|
|
||||||
at: [],
|
|
||||||
segment: [],
|
|
||||||
length: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mergeWith(other: GPXStatistics): void {
|
|
||||||
this.local.points = this.local.points.concat(other.local.points);
|
|
||||||
|
|
||||||
this.local.distance.total = this.local.distance.total.concat(
|
|
||||||
other.local.distance.total.map((distance) => distance + this.global.distance.total)
|
|
||||||
);
|
|
||||||
this.local.distance.moving = this.local.distance.moving.concat(
|
|
||||||
other.local.distance.moving.map((distance) => distance + this.global.distance.moving)
|
|
||||||
);
|
|
||||||
this.local.time.total = this.local.time.total.concat(
|
|
||||||
other.local.time.total.map((time) => time + this.global.time.total)
|
|
||||||
);
|
|
||||||
this.local.time.moving = this.local.time.moving.concat(
|
|
||||||
other.local.time.moving.map((time) => time + this.global.time.moving)
|
|
||||||
);
|
|
||||||
this.local.elevation.gain = this.local.elevation.gain.concat(
|
|
||||||
other.local.elevation.gain.map((gain) => gain + this.global.elevation.gain)
|
|
||||||
);
|
|
||||||
this.local.elevation.loss = this.local.elevation.loss.concat(
|
|
||||||
other.local.elevation.loss.map((loss) => loss + this.global.elevation.loss)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.local.speed = this.local.speed.concat(other.local.speed);
|
|
||||||
this.local.slope.at = this.local.slope.at.concat(other.local.slope.at);
|
|
||||||
this.local.slope.segment = this.local.slope.segment.concat(other.local.slope.segment);
|
|
||||||
this.local.slope.length = this.local.slope.length.concat(other.local.slope.length);
|
|
||||||
|
|
||||||
this.global.distance.total += other.global.distance.total;
|
|
||||||
this.global.distance.moving += other.global.distance.moving;
|
|
||||||
|
|
||||||
this.global.time.start =
|
|
||||||
this.global.time.start !== undefined && other.global.time.start !== undefined
|
|
||||||
? new Date(
|
|
||||||
Math.min(this.global.time.start.getTime(), other.global.time.start.getTime())
|
|
||||||
)
|
|
||||||
: (this.global.time.start ?? other.global.time.start);
|
|
||||||
this.global.time.end =
|
|
||||||
this.global.time.end !== undefined && other.global.time.end !== undefined
|
|
||||||
? new Date(
|
|
||||||
Math.max(this.global.time.end.getTime(), other.global.time.end.getTime())
|
|
||||||
)
|
|
||||||
: (this.global.time.end ?? other.global.time.end);
|
|
||||||
|
|
||||||
this.global.time.total += other.global.time.total;
|
|
||||||
this.global.time.moving += other.global.time.moving;
|
|
||||||
|
|
||||||
this.global.speed.moving =
|
|
||||||
this.global.time.moving > 0
|
|
||||||
? this.global.distance.moving / (this.global.time.moving / 3600)
|
|
||||||
: 0;
|
|
||||||
this.global.speed.total =
|
|
||||||
this.global.time.total > 0
|
|
||||||
? this.global.distance.total / (this.global.time.total / 3600)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
this.global.elevation.gain += other.global.elevation.gain;
|
|
||||||
this.global.elevation.loss += other.global.elevation.loss;
|
|
||||||
|
|
||||||
this.global.bounds.southWest.lat = Math.min(
|
|
||||||
this.global.bounds.southWest.lat,
|
|
||||||
other.global.bounds.southWest.lat
|
|
||||||
);
|
|
||||||
this.global.bounds.southWest.lon = Math.min(
|
|
||||||
this.global.bounds.southWest.lon,
|
|
||||||
other.global.bounds.southWest.lon
|
|
||||||
);
|
|
||||||
this.global.bounds.northEast.lat = Math.max(
|
|
||||||
this.global.bounds.northEast.lat,
|
|
||||||
other.global.bounds.northEast.lat
|
|
||||||
);
|
|
||||||
this.global.bounds.northEast.lon = Math.max(
|
|
||||||
this.global.bounds.northEast.lon,
|
|
||||||
other.global.bounds.northEast.lon
|
|
||||||
);
|
|
||||||
|
|
||||||
this.global.atemp.avg =
|
|
||||||
(this.global.atemp.count * this.global.atemp.avg +
|
|
||||||
other.global.atemp.count * other.global.atemp.avg) /
|
|
||||||
Math.max(1, this.global.atemp.count + other.global.atemp.count);
|
|
||||||
this.global.atemp.count += other.global.atemp.count;
|
|
||||||
this.global.hr.avg =
|
|
||||||
(this.global.hr.count * this.global.hr.avg +
|
|
||||||
other.global.hr.count * other.global.hr.avg) /
|
|
||||||
Math.max(1, this.global.hr.count + other.global.hr.count);
|
|
||||||
this.global.hr.count += other.global.hr.count;
|
|
||||||
this.global.cad.avg =
|
|
||||||
(this.global.cad.count * this.global.cad.avg +
|
|
||||||
other.global.cad.count * other.global.cad.avg) /
|
|
||||||
Math.max(1, this.global.cad.count + other.global.cad.count);
|
|
||||||
this.global.cad.count += other.global.cad.count;
|
|
||||||
this.global.power.avg =
|
|
||||||
(this.global.power.count * this.global.power.avg +
|
|
||||||
other.global.power.count * other.global.power.avg) /
|
|
||||||
Math.max(1, this.global.power.count + other.global.power.count);
|
|
||||||
this.global.power.count += other.global.power.count;
|
|
||||||
Object.keys(other.global.extensions).forEach((extension) => {
|
|
||||||
if (this.global.extensions[extension] === undefined) {
|
|
||||||
this.global.extensions[extension] = {};
|
|
||||||
}
|
|
||||||
Object.keys(other.global.extensions[extension]).forEach((value) => {
|
|
||||||
if (this.global.extensions[extension][value] === undefined) {
|
|
||||||
this.global.extensions[extension][value] = 0;
|
|
||||||
}
|
|
||||||
this.global.extensions[extension][value] +=
|
|
||||||
other.global.extensions[extension][value];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
slice(start: number, end: number): GPXStatistics {
|
|
||||||
if (start < 0) {
|
|
||||||
start = 0;
|
|
||||||
} else if (start >= this.local.points.length) {
|
|
||||||
return new GPXStatistics();
|
|
||||||
}
|
|
||||||
if (end < start) {
|
|
||||||
return new GPXStatistics();
|
|
||||||
} else if (end >= this.local.points.length) {
|
|
||||||
end = this.local.points.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let statistics = new GPXStatistics();
|
|
||||||
|
|
||||||
statistics.local.points = this.local.points.slice(start, end + 1);
|
|
||||||
|
|
||||||
statistics.global.distance.total =
|
|
||||||
this.local.distance.total[end] - this.local.distance.total[start];
|
|
||||||
statistics.global.distance.moving =
|
|
||||||
this.local.distance.moving[end] - this.local.distance.moving[start];
|
|
||||||
|
|
||||||
statistics.global.time.start = this.local.points[start].time;
|
|
||||||
statistics.global.time.end = this.local.points[end].time;
|
|
||||||
|
|
||||||
statistics.global.time.total = this.local.time.total[end] - this.local.time.total[start];
|
|
||||||
statistics.global.time.moving = this.local.time.moving[end] - this.local.time.moving[start];
|
|
||||||
|
|
||||||
statistics.global.speed.moving =
|
|
||||||
statistics.global.time.moving > 0
|
|
||||||
? statistics.global.distance.moving / (statistics.global.time.moving / 3600)
|
|
||||||
: 0;
|
|
||||||
statistics.global.speed.total =
|
|
||||||
statistics.global.time.total > 0
|
|
||||||
? statistics.global.distance.total / (statistics.global.time.total / 3600)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
statistics.global.elevation.gain =
|
|
||||||
this.local.elevation.gain[end] - this.local.elevation.gain[start];
|
|
||||||
statistics.global.elevation.loss =
|
|
||||||
this.local.elevation.loss[end] - this.local.elevation.loss[start];
|
|
||||||
|
|
||||||
statistics.global.bounds.southWest.lat = this.global.bounds.southWest.lat;
|
|
||||||
statistics.global.bounds.southWest.lon = this.global.bounds.southWest.lon;
|
|
||||||
statistics.global.bounds.northEast.lat = this.global.bounds.northEast.lat;
|
|
||||||
statistics.global.bounds.northEast.lon = this.global.bounds.northEast.lon;
|
|
||||||
|
|
||||||
statistics.global.atemp = this.global.atemp;
|
|
||||||
statistics.global.hr = this.global.hr;
|
|
||||||
statistics.global.cad = this.global.cad;
|
|
||||||
statistics.global.power = this.global.power;
|
|
||||||
|
|
||||||
return statistics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const earthRadius = 6371008.8;
|
const earthRadius = 6371008.8;
|
||||||
export function distance(
|
export function distance(
|
||||||
coord1: TrackPoint | Coordinates,
|
coord1: TrackPoint | Coordinates,
|
||||||
@@ -1951,9 +1675,9 @@ export function getElevationDistanceFunction(statistics: GPXStatistics) {
|
|||||||
if (point1.ele === undefined || point2.ele === undefined || point3.ele === undefined) {
|
if (point1.ele === undefined || point2.ele === undefined || point3.ele === undefined) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let x1 = statistics.local.distance.total[point1._data.index] * 1000;
|
let x1 = statistics.local.data[point1._data.index].distance.total * 1000;
|
||||||
let x2 = statistics.local.distance.total[point2._data.index] * 1000;
|
let x2 = statistics.local.data[point2._data.index].distance.total * 1000;
|
||||||
let x3 = statistics.local.distance.total[point3._data.index] * 1000;
|
let x3 = statistics.local.data[point3._data.index].distance.total * 1000;
|
||||||
let y1 = point1.ele;
|
let y1 = point1.ele;
|
||||||
let y2 = point2.ele;
|
let y2 = point2.ele;
|
||||||
let y3 = point3.ele;
|
let y3 = point3.ele;
|
||||||
@@ -1972,10 +1696,9 @@ function windowSmoothing(
|
|||||||
right: number,
|
right: number,
|
||||||
distance: (index1: number, index2: number) => number,
|
distance: (index1: number, index2: number) => number,
|
||||||
window: number,
|
window: number,
|
||||||
compute: (start: number, end: number) => number
|
compute: (start: number, end: number) => number,
|
||||||
): number[] {
|
callback: (value: number, index: number) => void
|
||||||
let result = [];
|
): void {
|
||||||
|
|
||||||
let start = left;
|
let start = left;
|
||||||
for (var i = left; i < right; i++) {
|
for (var i = left; i < right; i++) {
|
||||||
while (start + 1 < i && distance(start, i) > window) {
|
while (start + 1 < i && distance(start, i) > window) {
|
||||||
@@ -1985,10 +1708,8 @@ function windowSmoothing(
|
|||||||
while (end < right && distance(i, end) <= window) {
|
while (end < right && distance(i, end) <= window) {
|
||||||
end++;
|
end++;
|
||||||
}
|
}
|
||||||
result.push(compute(start, end - 1));
|
callback(compute(start, end - 1), i);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function distanceWindowSmoothing(
|
function distanceWindowSmoothing(
|
||||||
@@ -1996,30 +1717,35 @@ function distanceWindowSmoothing(
|
|||||||
right: number,
|
right: number,
|
||||||
statistics: GPXStatistics,
|
statistics: GPXStatistics,
|
||||||
window: number,
|
window: number,
|
||||||
compute: (start: number, end: number) => number
|
compute: (start: number, end: number) => number,
|
||||||
): number[] {
|
callback: (value: number, index: number) => void
|
||||||
return windowSmoothing(
|
): void {
|
||||||
|
windowSmoothing(
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
(index1, index2) =>
|
(index1, index2) =>
|
||||||
statistics.local.distance.total[index2] - statistics.local.distance.total[index1],
|
statistics.local.data[index2].distance.total -
|
||||||
|
statistics.local.data[index1].distance.total,
|
||||||
window,
|
window,
|
||||||
compute
|
compute,
|
||||||
|
callback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function timeWindowSmoothing(
|
function timeWindowSmoothing(
|
||||||
points: TrackPoint[],
|
points: TrackPoint[],
|
||||||
window: number,
|
window: number,
|
||||||
compute: (start: number, end: number) => number
|
compute: (start: number, end: number) => number,
|
||||||
): number[] {
|
callback: (value: number, index: number) => void
|
||||||
return windowSmoothing(
|
): void {
|
||||||
|
windowSmoothing(
|
||||||
0,
|
0,
|
||||||
points.length,
|
points.length,
|
||||||
(index1, index2) =>
|
(index1, index2) =>
|
||||||
points[index2].time?.getTime() - points[index1].time?.getTime() || 2 * window,
|
points[index2].time?.getTime() - points[index1].time?.getTime() || 2 * window,
|
||||||
window,
|
window,
|
||||||
compute
|
compute,
|
||||||
|
callback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2071,14 +1797,14 @@ function withArtificialTimestamps(
|
|||||||
totalTime: number,
|
totalTime: number,
|
||||||
lastPoint: TrackPoint | undefined,
|
lastPoint: TrackPoint | undefined,
|
||||||
startTime: Date,
|
startTime: Date,
|
||||||
slope: number[]
|
statistics: GPXStatistics
|
||||||
): TrackPoint[] {
|
): TrackPoint[] {
|
||||||
let weight = [];
|
let weight = [];
|
||||||
let totalWeight = 0;
|
let totalWeight = 0;
|
||||||
|
|
||||||
for (let i = 0; i < points.length - 1; i++) {
|
for (let i = 0; i < points.length - 1; i++) {
|
||||||
let dist = distance(points[i].getCoordinates(), points[i + 1].getCoordinates());
|
let dist = distance(points[i].getCoordinates(), points[i + 1].getCoordinates());
|
||||||
let w = dist * (0.5 + 1 / (1 + Math.exp(-0.2 * slope[i])));
|
let w = dist * (0.5 + 1 / (1 + Math.exp(-0.2 * statistics.local.data[i].slope.at)));
|
||||||
weight.push(w);
|
weight.push(w);
|
||||||
totalWeight += w;
|
totalWeight += w;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './gpx';
|
export * from './gpx';
|
||||||
|
export * from './statistics';
|
||||||
export { Coordinates, LineStyleExtension, WaypointType } from './types';
|
export { Coordinates, LineStyleExtension, WaypointType } from './types';
|
||||||
export { parseGPX, buildGPX } from './io';
|
export { parseGPX, buildGPX } from './io';
|
||||||
export * from './simplify';
|
export * from './simplify';
|
||||||
|
|||||||
391
gpx/src/statistics.ts
Normal file
391
gpx/src/statistics.ts
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
import { TrackPoint } from './gpx';
|
||||||
|
import { Coordinates } from './types';
|
||||||
|
|
||||||
|
export class GPXGlobalStatistics {
|
||||||
|
length: number;
|
||||||
|
distance: {
|
||||||
|
moving: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
time: {
|
||||||
|
start: Date | undefined;
|
||||||
|
end: Date | undefined;
|
||||||
|
moving: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
speed: {
|
||||||
|
moving: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
elevation: {
|
||||||
|
gain: number;
|
||||||
|
loss: number;
|
||||||
|
};
|
||||||
|
bounds: {
|
||||||
|
southWest: Coordinates;
|
||||||
|
northEast: Coordinates;
|
||||||
|
};
|
||||||
|
atemp: {
|
||||||
|
avg: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
hr: {
|
||||||
|
avg: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
cad: {
|
||||||
|
avg: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
power: {
|
||||||
|
avg: number;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
extensions: Record<string, Record<string, number>>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.length = 0;
|
||||||
|
this.distance = {
|
||||||
|
moving: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
this.time = {
|
||||||
|
start: undefined,
|
||||||
|
end: undefined,
|
||||||
|
moving: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
this.speed = {
|
||||||
|
moving: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
this.elevation = {
|
||||||
|
gain: 0,
|
||||||
|
loss: 0,
|
||||||
|
};
|
||||||
|
this.bounds = {
|
||||||
|
southWest: {
|
||||||
|
lat: 90,
|
||||||
|
lon: 180,
|
||||||
|
},
|
||||||
|
northEast: {
|
||||||
|
lat: -90,
|
||||||
|
lon: -180,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.atemp = {
|
||||||
|
avg: 0,
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
this.hr = {
|
||||||
|
avg: 0,
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
this.cad = {
|
||||||
|
avg: 0,
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
this.power = {
|
||||||
|
avg: 0,
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
this.extensions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeWith(other: GPXGlobalStatistics): void {
|
||||||
|
this.length += other.length;
|
||||||
|
|
||||||
|
this.distance.total += other.distance.total;
|
||||||
|
this.distance.moving += other.distance.moving;
|
||||||
|
|
||||||
|
this.time.start =
|
||||||
|
this.time.start !== undefined && other.time.start !== undefined
|
||||||
|
? new Date(Math.min(this.time.start.getTime(), other.time.start.getTime()))
|
||||||
|
: (this.time.start ?? other.time.start);
|
||||||
|
this.time.end =
|
||||||
|
this.time.end !== undefined && other.time.end !== undefined
|
||||||
|
? new Date(Math.max(this.time.end.getTime(), other.time.end.getTime()))
|
||||||
|
: (this.time.end ?? other.time.end);
|
||||||
|
|
||||||
|
this.time.total += other.time.total;
|
||||||
|
this.time.moving += other.time.moving;
|
||||||
|
|
||||||
|
this.speed.moving =
|
||||||
|
this.time.moving > 0 ? this.distance.moving / (this.time.moving / 3600) : 0;
|
||||||
|
this.speed.total = this.time.total > 0 ? this.distance.total / (this.time.total / 3600) : 0;
|
||||||
|
|
||||||
|
this.elevation.gain += other.elevation.gain;
|
||||||
|
this.elevation.loss += other.elevation.loss;
|
||||||
|
|
||||||
|
this.bounds.southWest.lat = Math.min(this.bounds.southWest.lat, other.bounds.southWest.lat);
|
||||||
|
this.bounds.southWest.lon = Math.min(this.bounds.southWest.lon, other.bounds.southWest.lon);
|
||||||
|
this.bounds.northEast.lat = Math.max(this.bounds.northEast.lat, other.bounds.northEast.lat);
|
||||||
|
this.bounds.northEast.lon = Math.max(this.bounds.northEast.lon, other.bounds.northEast.lon);
|
||||||
|
|
||||||
|
this.atemp.avg =
|
||||||
|
(this.atemp.count * this.atemp.avg + other.atemp.count * other.atemp.avg) /
|
||||||
|
Math.max(1, this.atemp.count + other.atemp.count);
|
||||||
|
this.atemp.count += other.atemp.count;
|
||||||
|
this.hr.avg =
|
||||||
|
(this.hr.count * this.hr.avg + other.hr.count * other.hr.avg) /
|
||||||
|
Math.max(1, this.hr.count + other.hr.count);
|
||||||
|
this.hr.count += other.hr.count;
|
||||||
|
this.cad.avg =
|
||||||
|
(this.cad.count * this.cad.avg + other.cad.count * other.cad.avg) /
|
||||||
|
Math.max(1, this.cad.count + other.cad.count);
|
||||||
|
this.cad.count += other.cad.count;
|
||||||
|
this.power.avg =
|
||||||
|
(this.power.count * this.power.avg + other.power.count * other.power.avg) /
|
||||||
|
Math.max(1, this.power.count + other.power.count);
|
||||||
|
this.power.count += other.power.count;
|
||||||
|
|
||||||
|
Object.keys(other.extensions).forEach((extension) => {
|
||||||
|
if (this.extensions[extension] === undefined) {
|
||||||
|
this.extensions[extension] = {};
|
||||||
|
}
|
||||||
|
Object.keys(other.extensions[extension]).forEach((value) => {
|
||||||
|
if (this.extensions[extension][value] === undefined) {
|
||||||
|
this.extensions[extension][value] = 0;
|
||||||
|
}
|
||||||
|
this.extensions[extension][value] += other.extensions[extension][value];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TrackPointLocalStatistics {
|
||||||
|
distance: {
|
||||||
|
moving: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
time: {
|
||||||
|
moving: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
speed: number;
|
||||||
|
elevation: {
|
||||||
|
gain: number;
|
||||||
|
loss: number;
|
||||||
|
};
|
||||||
|
slope: {
|
||||||
|
at: number;
|
||||||
|
segment: number;
|
||||||
|
length: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.distance = {
|
||||||
|
moving: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
this.time = {
|
||||||
|
moving: 0,
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
this.speed = 0;
|
||||||
|
this.elevation = {
|
||||||
|
gain: 0,
|
||||||
|
loss: 0,
|
||||||
|
};
|
||||||
|
this.slope = {
|
||||||
|
at: 0,
|
||||||
|
segment: 0,
|
||||||
|
length: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GPXLocalStatistics {
|
||||||
|
points: TrackPoint[];
|
||||||
|
data: TrackPointLocalStatistics[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.points = [];
|
||||||
|
this.data = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TrackPointWithLocalStatistics = {
|
||||||
|
trkpt: TrackPoint;
|
||||||
|
} & TrackPointLocalStatistics;
|
||||||
|
|
||||||
|
export class GPXStatistics {
|
||||||
|
global: GPXGlobalStatistics;
|
||||||
|
local: GPXLocalStatistics;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.global = new GPXGlobalStatistics();
|
||||||
|
this.local = new GPXLocalStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
sliced(start: number, end: number): GPXGlobalStatistics {
|
||||||
|
if (start < 0) {
|
||||||
|
start = 0;
|
||||||
|
} else if (start >= this.global.length) {
|
||||||
|
return new GPXGlobalStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end < start) {
|
||||||
|
return new GPXGlobalStatistics();
|
||||||
|
} else if (end >= this.global.length) {
|
||||||
|
end = this.global.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start === 0 && end === this.global.length - 1) {
|
||||||
|
return this.global;
|
||||||
|
}
|
||||||
|
|
||||||
|
let statistics = new GPXGlobalStatistics();
|
||||||
|
|
||||||
|
statistics.length = end - start + 1;
|
||||||
|
|
||||||
|
statistics.distance.total =
|
||||||
|
this.local.data[end].distance.total - this.local.data[start].distance.total;
|
||||||
|
statistics.distance.moving =
|
||||||
|
this.local.data[end].distance.moving - this.local.data[start].distance.moving;
|
||||||
|
|
||||||
|
statistics.time.start = this.local.points[start].time;
|
||||||
|
statistics.time.end = this.local.points[end].time;
|
||||||
|
|
||||||
|
statistics.time.total = this.local.data[end].time.total - this.local.data[start].time.total;
|
||||||
|
statistics.time.moving =
|
||||||
|
this.local.data[end].time.moving - this.local.data[start].time.moving;
|
||||||
|
|
||||||
|
statistics.speed.moving =
|
||||||
|
statistics.time.moving > 0
|
||||||
|
? statistics.distance.moving / (statistics.time.moving / 3600)
|
||||||
|
: 0;
|
||||||
|
statistics.speed.total =
|
||||||
|
statistics.time.total > 0
|
||||||
|
? statistics.distance.total / (statistics.time.total / 3600)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
statistics.elevation.gain =
|
||||||
|
this.local.data[end].elevation.gain - this.local.data[start].elevation.gain;
|
||||||
|
statistics.elevation.loss =
|
||||||
|
this.local.data[end].elevation.loss - this.local.data[start].elevation.loss;
|
||||||
|
|
||||||
|
statistics.bounds.southWest.lat = this.global.bounds.southWest.lat;
|
||||||
|
statistics.bounds.southWest.lon = this.global.bounds.southWest.lon;
|
||||||
|
statistics.bounds.northEast.lat = this.global.bounds.northEast.lat;
|
||||||
|
statistics.bounds.northEast.lon = this.global.bounds.northEast.lon;
|
||||||
|
|
||||||
|
statistics.atemp = this.global.atemp;
|
||||||
|
statistics.hr = this.global.hr;
|
||||||
|
statistics.cad = this.global.cad;
|
||||||
|
statistics.power = this.global.power;
|
||||||
|
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GPXStatisticsGroup {
|
||||||
|
private _statistics: GPXStatistics[];
|
||||||
|
private _cumulative: GPXGlobalStatistics[];
|
||||||
|
private _slice: [number, number] | null = null;
|
||||||
|
global: GPXGlobalStatistics;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._statistics = [];
|
||||||
|
this._cumulative = [new GPXGlobalStatistics()];
|
||||||
|
this.global = new GPXGlobalStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
add(statistics: GPXStatistics | GPXStatisticsGroup): void {
|
||||||
|
if (statistics instanceof GPXStatisticsGroup) {
|
||||||
|
statistics._statistics.forEach((stats) => this._add(stats));
|
||||||
|
} else {
|
||||||
|
this._add(statistics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_add(statistics: GPXStatistics): void {
|
||||||
|
this._statistics.push(statistics);
|
||||||
|
const cumulative = new GPXGlobalStatistics();
|
||||||
|
cumulative.mergeWith(this._cumulative[this._cumulative.length - 1]);
|
||||||
|
cumulative.mergeWith(statistics.global);
|
||||||
|
this._cumulative.push(cumulative);
|
||||||
|
this.global.mergeWith(statistics.global);
|
||||||
|
}
|
||||||
|
|
||||||
|
sliced(start: number, end: number): GPXGlobalStatistics {
|
||||||
|
let sliced = new GPXGlobalStatistics();
|
||||||
|
for (let i = 0; i < this._statistics.length; i++) {
|
||||||
|
const statistics = this._statistics[i];
|
||||||
|
const cumulative = this._cumulative[i];
|
||||||
|
if (start < cumulative.length + statistics.global.length && end >= cumulative.length) {
|
||||||
|
const localStart = Math.max(0, start - cumulative.length);
|
||||||
|
const localEnd = Math.min(statistics.global.length - 1, end - cumulative.length);
|
||||||
|
sliced.mergeWith(statistics.sliced(localStart, localEnd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sliced;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTrackPoint(index: number): TrackPointWithLocalStatistics | undefined {
|
||||||
|
if (this._slice !== null) {
|
||||||
|
index += this._slice[0];
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this._statistics.length; i++) {
|
||||||
|
const statistics = this._statistics[i];
|
||||||
|
const cumulative = this._cumulative[i];
|
||||||
|
if (index < cumulative.length + statistics.global.length) {
|
||||||
|
return this._getTrackPoint(cumulative, statistics, index - cumulative.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getTrackPoint(
|
||||||
|
cumulative: GPXGlobalStatistics,
|
||||||
|
statistics: GPXStatistics,
|
||||||
|
index: number
|
||||||
|
): TrackPointWithLocalStatistics {
|
||||||
|
const point = statistics.local.points[index];
|
||||||
|
return {
|
||||||
|
trkpt: point,
|
||||||
|
distance: {
|
||||||
|
moving: statistics.local.data[index].distance.moving + cumulative.distance.moving,
|
||||||
|
total: statistics.local.data[index].distance.total + cumulative.distance.total,
|
||||||
|
},
|
||||||
|
time: {
|
||||||
|
moving: statistics.local.data[index].time.moving + cumulative.time.moving,
|
||||||
|
total: statistics.local.data[index].time.total + cumulative.time.total,
|
||||||
|
},
|
||||||
|
speed: statistics.local.data[index].speed,
|
||||||
|
elevation: {
|
||||||
|
gain: statistics.local.data[index].elevation.gain + cumulative.elevation.gain,
|
||||||
|
loss: statistics.local.data[index].elevation.loss + cumulative.elevation.loss,
|
||||||
|
},
|
||||||
|
slope: {
|
||||||
|
at: statistics.local.data[index].slope.at,
|
||||||
|
segment: statistics.local.data[index].slope.segment,
|
||||||
|
length: statistics.local.data[index].slope.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
forEachTrackPoint(
|
||||||
|
callback: (
|
||||||
|
point: TrackPoint,
|
||||||
|
distance: number,
|
||||||
|
speed: number,
|
||||||
|
slope: { at: number; segment: number; length: number },
|
||||||
|
index: number
|
||||||
|
) => void
|
||||||
|
): void {
|
||||||
|
for (let i = 0; i < this._statistics.length; i++) {
|
||||||
|
const statistics = this._statistics[i];
|
||||||
|
const cumulative = this._cumulative[i];
|
||||||
|
statistics.local.points.forEach((point, index) =>
|
||||||
|
callback(
|
||||||
|
point,
|
||||||
|
cumulative.distance.total + statistics.local.data[index].distance.total,
|
||||||
|
statistics.local.data[index].speed,
|
||||||
|
statistics.local.data[index].slope,
|
||||||
|
cumulative.length + index
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
src/lib/components/ui
|
|
||||||
src/lib/docs/**/*.mdx
|
|
||||||
**/*.webmanifest
|
|
||||||
@@ -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 . --config ../.prettierrc && eslint .",
|
"lint": "prettier --check . --config ../.prettierrc --ignore-path ../.prettierignore --ignore-path ./.gitignore && eslint .",
|
||||||
"format": "prettier --write . --config ../.prettierrc"
|
"format": "prettier --write . --config ../.prettierrc --ignore-path ../.prettierignore --ignore-path ./.gitignore"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lucide/svelte": "^0.544.0",
|
"@lucide/svelte": "^0.544.0",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
Binoculars,
|
Binoculars,
|
||||||
Toilet,
|
Toilet,
|
||||||
} from 'lucide-static';
|
} from 'lucide-static';
|
||||||
import { type StyleSpecification } from 'mapbox-gl';
|
import { type RasterDEMSourceSpecification, type StyleSpecification } from 'mapbox-gl';
|
||||||
import ignFrTopo from './custom/ign-fr-topo.json';
|
import ignFrTopo from './custom/ign-fr-topo.json';
|
||||||
import ignFrPlan from './custom/ign-fr-plan.json';
|
import ignFrPlan from './custom/ign-fr-plan.json';
|
||||||
import ignFrSatellite from './custom/ign-fr-satellite.json';
|
import ignFrSatellite from './custom/ign-fr-satellite.json';
|
||||||
@@ -368,6 +368,42 @@ export const overlays: { [key: string]: string | StyleSpecification } = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
bikerouterGravel: bikerouterGravel as StyleSpecification,
|
bikerouterGravel: bikerouterGravel as StyleSpecification,
|
||||||
|
openRailwayMap: {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
openRailwayMap: {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: ['https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png'],
|
||||||
|
tileSize: 256,
|
||||||
|
maxzoom: 19,
|
||||||
|
attribution:
|
||||||
|
'Data <a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</a>, Style: <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA 2.0</a> <a href="http://www.openrailwaymap.org/">OpenRailwayMap</a>',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
id: 'openRailwayMap',
|
||||||
|
type: 'raster',
|
||||||
|
source: 'openRailwayMap',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
mapterhornHillshade: {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
mapterhornHillshade: {
|
||||||
|
type: 'raster-dem',
|
||||||
|
url: 'https://tiles.mapterhorn.com/tilejson.json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
id: 'mapterhornHillshade',
|
||||||
|
type: 'hillshade',
|
||||||
|
source: 'mapterhornHillshade',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
swisstopoSlope: {
|
swisstopoSlope: {
|
||||||
version: 8,
|
version: 8,
|
||||||
sources: {
|
sources: {
|
||||||
@@ -799,8 +835,10 @@ export const overlayTree: LayerTreeType = {
|
|||||||
waymarkedTrailsHorseRiding: true,
|
waymarkedTrailsHorseRiding: true,
|
||||||
waymarkedTrailsWinter: true,
|
waymarkedTrailsWinter: true,
|
||||||
},
|
},
|
||||||
cyclOSMlite: true,
|
|
||||||
bikerouterGravel: true,
|
bikerouterGravel: true,
|
||||||
|
cyclOSMlite: true,
|
||||||
|
mapterhornHillshade: true,
|
||||||
|
openRailwayMap: true,
|
||||||
},
|
},
|
||||||
countries: {
|
countries: {
|
||||||
france: {
|
france: {
|
||||||
@@ -883,8 +921,10 @@ export const defaultOverlays: LayerTreeType = {
|
|||||||
waymarkedTrailsHorseRiding: false,
|
waymarkedTrailsHorseRiding: false,
|
||||||
waymarkedTrailsWinter: false,
|
waymarkedTrailsWinter: false,
|
||||||
},
|
},
|
||||||
cyclOSMlite: false,
|
|
||||||
bikerouterGravel: false,
|
bikerouterGravel: false,
|
||||||
|
cyclOSMlite: false,
|
||||||
|
mapterhornHillshade: false,
|
||||||
|
openRailwayMap: false,
|
||||||
},
|
},
|
||||||
countries: {
|
countries: {
|
||||||
france: {
|
france: {
|
||||||
@@ -1018,8 +1058,10 @@ export const defaultOverlayTree: LayerTreeType = {
|
|||||||
waymarkedTrailsHorseRiding: false,
|
waymarkedTrailsHorseRiding: false,
|
||||||
waymarkedTrailsWinter: false,
|
waymarkedTrailsWinter: false,
|
||||||
},
|
},
|
||||||
cyclOSMlite: false,
|
|
||||||
bikerouterGravel: false,
|
bikerouterGravel: false,
|
||||||
|
cyclOSMlite: false,
|
||||||
|
mapterhornHillshade: false,
|
||||||
|
openRailwayMap: false,
|
||||||
},
|
},
|
||||||
countries: {
|
countries: {
|
||||||
france: {
|
france: {
|
||||||
@@ -1411,3 +1453,18 @@ export const overpassQueryData: Record<string, OverpassQueryData> = {
|
|||||||
symbol: 'Anchor',
|
symbol: 'Anchor',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const terrainSources: { [key: string]: RasterDEMSourceSpecification } = {
|
||||||
|
'mapbox-dem': {
|
||||||
|
type: 'raster-dem',
|
||||||
|
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
|
||||||
|
tileSize: 512,
|
||||||
|
maxzoom: 14,
|
||||||
|
},
|
||||||
|
mapterhorn: {
|
||||||
|
type: 'raster-dem',
|
||||||
|
url: 'https://tiles.mapterhorn.com/tilejson.json',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultTerrainSource = 'mapbox-dem';
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
href="https://github.com/gpxstudio/gpx.studio/blob/main/LICENSE"
|
href="https://github.com/gpxstudio/gpx.studio/blob/main/LICENSE"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
MIT © 2025 gpx.studio
|
MIT © 2026 gpx.studio
|
||||||
</Button>
|
</Button>
|
||||||
<LanguageSelect class="w-40 mt-3" />
|
<LanguageSelect class="w-40 mt-3" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { MoveDownRight, MoveUpRight, Ruler, Timer, Zap } from '@lucide/svelte';
|
import { MoveDownRight, MoveUpRight, Ruler, Timer, Zap } from '@lucide/svelte';
|
||||||
|
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
import type { GPXStatistics } from 'gpx';
|
import type { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
|
||||||
import type { Readable } from 'svelte/store';
|
import type { Readable } from 'svelte/store';
|
||||||
import { settings } from '$lib/logic/settings';
|
import { settings } from '$lib/logic/settings';
|
||||||
|
|
||||||
@@ -18,14 +18,14 @@
|
|||||||
orientation,
|
orientation,
|
||||||
panelSize,
|
panelSize,
|
||||||
}: {
|
}: {
|
||||||
gpxStatistics: Readable<GPXStatistics>;
|
gpxStatistics: Readable<GPXStatisticsGroup>;
|
||||||
slicedGPXStatistics: Readable<[GPXStatistics, number, number] | undefined>;
|
slicedGPXStatistics: Readable<[GPXGlobalStatistics, number, number] | undefined>;
|
||||||
orientation: 'horizontal' | 'vertical';
|
orientation: 'horizontal' | 'vertical';
|
||||||
panelSize: number;
|
panelSize: number;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let statistics = $derived(
|
let statistics = $derived(
|
||||||
$slicedGPXStatistics !== undefined ? $slicedGPXStatistics[0] : $gpxStatistics
|
$slicedGPXStatistics !== undefined ? $slicedGPXStatistics[0] : $gpxStatistics.global
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -42,15 +42,15 @@
|
|||||||
<Tooltip label={i18n._('quantities.distance')}>
|
<Tooltip label={i18n._('quantities.distance')}>
|
||||||
<span class="flex flex-row items-center">
|
<span class="flex flex-row items-center">
|
||||||
<Ruler size="16" class="mr-1" />
|
<Ruler size="16" class="mr-1" />
|
||||||
<WithUnits value={statistics.global.distance.total} type="distance" />
|
<WithUnits value={statistics.distance.total} type="distance" />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={i18n._('quantities.elevation_gain_loss')}>
|
<Tooltip label={i18n._('quantities.elevation_gain_loss')}>
|
||||||
<span class="flex flex-row items-center">
|
<span class="flex flex-row items-center">
|
||||||
<MoveUpRight size="16" class="mr-1" />
|
<MoveUpRight size="16" class="mr-1" />
|
||||||
<WithUnits value={statistics.global.elevation.gain} type="elevation" />
|
<WithUnits value={statistics.elevation.gain} type="elevation" />
|
||||||
<MoveDownRight size="16" class="mx-1" />
|
<MoveDownRight size="16" class="mx-1" />
|
||||||
<WithUnits value={statistics.global.elevation.loss} type="elevation" />
|
<WithUnits value={statistics.elevation.loss} type="elevation" />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{#if panelSize > 120 || orientation === 'horizontal'}
|
{#if panelSize > 120 || orientation === 'horizontal'}
|
||||||
@@ -64,13 +64,9 @@
|
|||||||
>
|
>
|
||||||
<span class="flex flex-row items-center">
|
<span class="flex flex-row items-center">
|
||||||
<Zap size="16" class="mr-1" />
|
<Zap size="16" class="mr-1" />
|
||||||
<WithUnits
|
<WithUnits value={statistics.speed.moving} type="speed" showUnits={false} />
|
||||||
value={statistics.global.speed.moving}
|
|
||||||
type="speed"
|
|
||||||
showUnits={false}
|
|
||||||
/>
|
|
||||||
<span class="mx-1">/</span>
|
<span class="mx-1">/</span>
|
||||||
<WithUnits value={statistics.global.speed.total} type="speed" />
|
<WithUnits value={statistics.speed.total} type="speed" />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -83,9 +79,9 @@
|
|||||||
>
|
>
|
||||||
<span class="flex flex-row items-center">
|
<span class="flex flex-row items-center">
|
||||||
<Timer size="16" class="mr-1" />
|
<Timer size="16" class="mr-1" />
|
||||||
<WithUnits value={statistics.global.time.moving} type="time" />
|
<WithUnits value={statistics.time.moving} type="time" />
|
||||||
<span class="mx-1">/</span>
|
<span class="mx-1">/</span>
|
||||||
<WithUnits value={statistics.global.time.total} type="time" />
|
<WithUnits value={statistics.time.total} type="time" />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
Construction,
|
Construction,
|
||||||
} from '@lucide/svelte';
|
} from '@lucide/svelte';
|
||||||
import type { Readable, Writable } from 'svelte/store';
|
import type { Readable, Writable } from 'svelte/store';
|
||||||
import type { GPXStatistics } from 'gpx';
|
import type { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
|
||||||
import { settings } from '$lib/logic/settings';
|
import { settings } from '$lib/logic/settings';
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
import { ElevationProfile } from '$lib/components/elevation-profile/elevation-profile';
|
import { ElevationProfile } from '$lib/components/elevation-profile/elevation-profile';
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
elevationFill,
|
elevationFill,
|
||||||
showControls = true,
|
showControls = true,
|
||||||
}: {
|
}: {
|
||||||
gpxStatistics: Readable<GPXStatistics>;
|
gpxStatistics: Readable<GPXStatisticsGroup>;
|
||||||
slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
|
slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined>;
|
||||||
additionalDatasets: Writable<string[]>;
|
additionalDatasets: Writable<string[]>;
|
||||||
elevationFill: Writable<'slope' | 'surface' | 'highway' | undefined>;
|
elevationFill: Writable<'slope' | 'surface' | 'highway' | undefined>;
|
||||||
showControls?: boolean;
|
showControls?: boolean;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import Chart, {
|
|||||||
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';
|
||||||
import type { GPXStatistics } from 'gpx';
|
import type { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
|
||||||
import { mode } from 'mode-watcher';
|
import { mode } from 'mode-watcher';
|
||||||
import { getHighwayColor, getSlopeColor, getSurfaceColor } from '$lib/assets/colors';
|
import { getHighwayColor, getSlopeColor, getSurfaceColor } from '$lib/assets/colors';
|
||||||
|
|
||||||
@@ -54,14 +54,14 @@ export class ElevationProfile {
|
|||||||
private _dragging = false;
|
private _dragging = false;
|
||||||
private _panning = false;
|
private _panning = false;
|
||||||
|
|
||||||
private _gpxStatistics: Readable<GPXStatistics>;
|
private _gpxStatistics: Readable<GPXStatisticsGroup>;
|
||||||
private _slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
|
private _slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined>;
|
||||||
private _additionalDatasets: Readable<string[]>;
|
private _additionalDatasets: Readable<string[]>;
|
||||||
private _elevationFill: Readable<'slope' | 'surface' | 'highway' | undefined>;
|
private _elevationFill: Readable<'slope' | 'surface' | 'highway' | undefined>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
gpxStatistics: Readable<GPXStatistics>,
|
gpxStatistics: Readable<GPXStatisticsGroup>,
|
||||||
slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>,
|
slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined>,
|
||||||
additionalDatasets: Readable<string[]>,
|
additionalDatasets: Readable<string[]>,
|
||||||
elevationFill: Readable<'slope' | 'surface' | 'highway' | undefined>,
|
elevationFill: Readable<'slope' | 'surface' | 'highway' | undefined>,
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
@@ -342,7 +342,7 @@ export class ElevationProfile {
|
|||||||
if (evt.x - rect.left <= this._chart.chartArea.left) {
|
if (evt.x - rect.left <= this._chart.chartArea.left) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (evt.x - rect.left >= this._chart.chartArea.right) {
|
} else if (evt.x - rect.left >= this._chart.chartArea.right) {
|
||||||
return get(this._gpxStatistics).local.points.length - 1;
|
return this._chart.data.datasets[0].data.length - 1;
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -375,7 +375,7 @@ export class ElevationProfile {
|
|||||||
startIndex = endIndex;
|
startIndex = endIndex;
|
||||||
} else if (startIndex !== endIndex) {
|
} else if (startIndex !== endIndex) {
|
||||||
this._slicedGPXStatistics.set([
|
this._slicedGPXStatistics.set([
|
||||||
get(this._gpxStatistics).slice(
|
get(this._gpxStatistics).sliced(
|
||||||
Math.min(startIndex, endIndex),
|
Math.min(startIndex, endIndex),
|
||||||
Math.max(startIndex, endIndex)
|
Math.max(startIndex, endIndex)
|
||||||
),
|
),
|
||||||
@@ -410,117 +410,89 @@ export class ElevationProfile {
|
|||||||
velocity: get(velocityUnits),
|
velocity: get(velocityUnits),
|
||||||
temperature: get(temperatureUnits),
|
temperature: get(temperatureUnits),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const datasets: Array<Array<any>> = [[], [], [], [], [], []];
|
||||||
|
data.forEachTrackPoint((trkpt, distance, speed, slope, index) => {
|
||||||
|
datasets[0].push({
|
||||||
|
x: getConvertedDistance(distance, units.distance),
|
||||||
|
y: trkpt.ele ? getConvertedElevation(trkpt.ele, units.distance) : 0,
|
||||||
|
time: trkpt.time,
|
||||||
|
slope: slope,
|
||||||
|
extensions: trkpt.getExtensions(),
|
||||||
|
coordinates: trkpt.getCoordinates(),
|
||||||
|
index: index,
|
||||||
|
});
|
||||||
|
if (data.global.time.total > 0) {
|
||||||
|
datasets[1].push({
|
||||||
|
x: getConvertedDistance(distance, units.distance),
|
||||||
|
y: getConvertedVelocity(speed, units.velocity, units.distance),
|
||||||
|
index: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.global.hr.count > 0) {
|
||||||
|
datasets[2].push({
|
||||||
|
x: getConvertedDistance(distance, units.distance),
|
||||||
|
y: trkpt.getHeartRate(),
|
||||||
|
index: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.global.cad.count > 0) {
|
||||||
|
datasets[3].push({
|
||||||
|
x: getConvertedDistance(distance, units.distance),
|
||||||
|
y: trkpt.getCadence(),
|
||||||
|
index: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.global.atemp.count > 0) {
|
||||||
|
datasets[4].push({
|
||||||
|
x: getConvertedDistance(distance, units.distance),
|
||||||
|
y: getConvertedTemperature(trkpt.getTemperature(), units.temperature),
|
||||||
|
index: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data.global.power.count > 0) {
|
||||||
|
datasets[5].push({
|
||||||
|
x: getConvertedDistance(distance, units.distance),
|
||||||
|
y: trkpt.getPower(),
|
||||||
|
index: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this._chart.data.datasets[0] = {
|
this._chart.data.datasets[0] = {
|
||||||
label: i18n._('quantities.elevation'),
|
label: i18n._('quantities.elevation'),
|
||||||
data: data.local.points.map((point, index) => {
|
data: datasets[0],
|
||||||
return {
|
|
||||||
x: getConvertedDistance(data.local.distance.total[index], units.distance),
|
|
||||||
y: point.ele ? getConvertedElevation(point.ele, units.distance) : 0,
|
|
||||||
time: point.time,
|
|
||||||
slope: {
|
|
||||||
at: data.local.slope.at[index],
|
|
||||||
segment: data.local.slope.segment[index],
|
|
||||||
length: data.local.slope.length[index],
|
|
||||||
},
|
|
||||||
extensions: point.getExtensions(),
|
|
||||||
coordinates: point.getCoordinates(),
|
|
||||||
index: index,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
normalized: true,
|
normalized: true,
|
||||||
fill: 'start',
|
fill: 'start',
|
||||||
order: 1,
|
order: 1,
|
||||||
segment: {},
|
segment: {},
|
||||||
};
|
};
|
||||||
this._chart.data.datasets[1] = {
|
this._chart.data.datasets[1] = {
|
||||||
data:
|
data: datasets[1],
|
||||||
data.global.time.total > 0
|
|
||||||
? data.local.points.map((point, index) => {
|
|
||||||
return {
|
|
||||||
x: getConvertedDistance(
|
|
||||||
data.local.distance.total[index],
|
|
||||||
units.distance
|
|
||||||
),
|
|
||||||
y: getConvertedVelocity(
|
|
||||||
data.local.speed[index],
|
|
||||||
units.velocity,
|
|
||||||
units.distance
|
|
||||||
),
|
|
||||||
index: index,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [],
|
|
||||||
normalized: true,
|
normalized: true,
|
||||||
yAxisID: 'yspeed',
|
yAxisID: 'yspeed',
|
||||||
};
|
};
|
||||||
this._chart.data.datasets[2] = {
|
this._chart.data.datasets[2] = {
|
||||||
data:
|
data: datasets[2],
|
||||||
data.global.hr.count > 0
|
|
||||||
? data.local.points.map((point, index) => {
|
|
||||||
return {
|
|
||||||
x: getConvertedDistance(
|
|
||||||
data.local.distance.total[index],
|
|
||||||
units.distance
|
|
||||||
),
|
|
||||||
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: datasets[3],
|
||||||
data.global.cad.count > 0
|
|
||||||
? data.local.points.map((point, index) => {
|
|
||||||
return {
|
|
||||||
x: getConvertedDistance(
|
|
||||||
data.local.distance.total[index],
|
|
||||||
units.distance
|
|
||||||
),
|
|
||||||
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: datasets[4],
|
||||||
data.global.atemp.count > 0
|
|
||||||
? data.local.points.map((point, index) => {
|
|
||||||
return {
|
|
||||||
x: getConvertedDistance(
|
|
||||||
data.local.distance.total[index],
|
|
||||||
units.distance
|
|
||||||
),
|
|
||||||
y: getConvertedTemperature(point.getTemperature(), units.temperature),
|
|
||||||
index: index,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [],
|
|
||||||
normalized: true,
|
normalized: true,
|
||||||
yAxisID: 'yatemp',
|
yAxisID: 'yatemp',
|
||||||
};
|
};
|
||||||
this._chart.data.datasets[5] = {
|
this._chart.data.datasets[5] = {
|
||||||
data:
|
data: datasets[5],
|
||||||
data.global.power.count > 0
|
|
||||||
? data.local.points.map((point, index) => {
|
|
||||||
return {
|
|
||||||
x: getConvertedDistance(
|
|
||||||
data.local.distance.total[index],
|
|
||||||
units.distance
|
|
||||||
),
|
|
||||||
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(
|
this._chart.options.scales!.x!['max'] = getConvertedDistance(
|
||||||
data.global.distance.total,
|
data.global.distance.total,
|
||||||
@@ -618,10 +590,12 @@ export class ElevationProfile {
|
|||||||
|
|
||||||
const gpxStatistics = get(this._gpxStatistics);
|
const gpxStatistics = get(this._gpxStatistics);
|
||||||
let startPixel = this._chart.scales.x.getPixelForValue(
|
let startPixel = this._chart.scales.x.getPixelForValue(
|
||||||
getConvertedDistance(gpxStatistics.local.distance.total[startIndex])
|
getConvertedDistance(
|
||||||
|
gpxStatistics.getTrackPoint(startIndex)?.distance.total ?? 0
|
||||||
|
)
|
||||||
);
|
);
|
||||||
let endPixel = this._chart.scales.x.getPixelForValue(
|
let endPixel = this._chart.scales.x.getPixelForValue(
|
||||||
getConvertedDistance(gpxStatistics.local.distance.total[endIndex])
|
getConvertedDistance(gpxStatistics.getTrackPoint(endIndex)?.distance.total ?? 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
selectionContext.fillRect(
|
selectionContext.fillRect(
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
SquareActivity,
|
SquareActivity,
|
||||||
} from '@lucide/svelte';
|
} from '@lucide/svelte';
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
import { GPXStatistics } from 'gpx';
|
import { GPXGlobalStatistics } from 'gpx';
|
||||||
import { ListRootItem } from '$lib/components/file-list/file-list';
|
import { ListRootItem } from '$lib/components/file-list/file-list';
|
||||||
import { fileStateCollection } from '$lib/logic/file-state';
|
import { fileStateCollection } from '$lib/logic/file-state';
|
||||||
import { selection } from '$lib/logic/selection';
|
import { selection } from '$lib/logic/selection';
|
||||||
@@ -48,24 +48,24 @@
|
|||||||
extensions: false,
|
extensions: false,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
let statistics = $gpxStatistics;
|
let statistics = $gpxStatistics.global;
|
||||||
if (exportState.current === ExportState.ALL) {
|
if (exportState.current === ExportState.ALL) {
|
||||||
statistics = Array.from(get(fileStateCollection).values())
|
statistics = Array.from(get(fileStateCollection).values())
|
||||||
.map((file) => file.statistics)
|
.map((file) => file.statistics)
|
||||||
.reduce((acc, cur) => {
|
.reduce((acc, cur) => {
|
||||||
if (cur !== undefined) {
|
if (cur !== undefined) {
|
||||||
acc.mergeWith(cur.getStatisticsFor(new ListRootItem()));
|
acc.mergeWith(cur.getStatisticsFor(new ListRootItem()).global);
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, new GPXStatistics());
|
}, new GPXGlobalStatistics());
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
time: statistics.global.time.total === 0,
|
time: statistics.time.total === 0,
|
||||||
hr: statistics.global.hr.count === 0,
|
hr: statistics.hr.count === 0,
|
||||||
cad: statistics.global.cad.count === 0,
|
cad: statistics.cad.count === 0,
|
||||||
atemp: statistics.global.atemp.count === 0,
|
atemp: statistics.atemp.count === 0,
|
||||||
power: statistics.global.power.count === 0,
|
power: statistics.power.count === 0,
|
||||||
extensions: Object.keys(statistics.global.extensions).length === 0,
|
extensions: Object.keys(statistics.extensions).length === 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -72,18 +72,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let style = node.getStyle(defaultColor);
|
let style = node.getStyle(defaultColor);
|
||||||
style.color.forEach((c) => {
|
colors = style.color;
|
||||||
if (!colors.includes(c)) {
|
|
||||||
colors.push(c);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (node instanceof Track) {
|
} else if (node instanceof Track) {
|
||||||
let style = node.getStyle();
|
let style = node.getStyle();
|
||||||
if (style) {
|
if (
|
||||||
if (style['gpx_style:color'] && !colors.includes(style['gpx_style:color'])) {
|
style &&
|
||||||
|
style['gpx_style:color'] &&
|
||||||
|
!colors.includes(style['gpx_style:color'])
|
||||||
|
) {
|
||||||
colors.push(style['gpx_style:color']);
|
colors.push(style['gpx_style:color']);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (colors.length === 0) {
|
if (colors.length === 0) {
|
||||||
let layer = gpxLayers.getLayer(item.getFileId());
|
let layer = gpxLayers.getLayer(item.getFileId());
|
||||||
if (layer) {
|
if (layer) {
|
||||||
|
|||||||
@@ -101,23 +101,17 @@ export class DistanceMarkers {
|
|||||||
getDistanceMarkersGeoJSON(): GeoJSON.FeatureCollection {
|
getDistanceMarkersGeoJSON(): GeoJSON.FeatureCollection {
|
||||||
let statistics = get(gpxStatistics);
|
let statistics = get(gpxStatistics);
|
||||||
|
|
||||||
let features = [];
|
let features: GeoJSON.Feature[] = [];
|
||||||
let currentTargetDistance = 1;
|
let currentTargetDistance = 1;
|
||||||
for (let i = 0; i < statistics.local.distance.total.length; i++) {
|
statistics.forEachTrackPoint((trkpt, dist) => {
|
||||||
if (
|
if (dist >= getConvertedDistanceToKilometers(currentTargetDistance)) {
|
||||||
statistics.local.distance.total[i] >=
|
|
||||||
getConvertedDistanceToKilometers(currentTargetDistance)
|
|
||||||
) {
|
|
||||||
let distance = currentTargetDistance.toFixed(0);
|
let distance = currentTargetDistance.toFixed(0);
|
||||||
let level = levels.find((level) => currentTargetDistance % level === 0) || 1;
|
let level = levels.find((level) => currentTargetDistance % level === 0) || 1;
|
||||||
features.push({
|
features.push({
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'Point',
|
type: 'Point',
|
||||||
coordinates: [
|
coordinates: [trkpt.getLongitude(), trkpt.getLatitude()],
|
||||||
statistics.local.points[i].getLongitude(),
|
|
||||||
statistics.local.points[i].getLatitude(),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
distance,
|
distance,
|
||||||
@@ -126,7 +120,7 @@ export class DistanceMarkers {
|
|||||||
} as GeoJSON.Feature);
|
} as GeoJSON.Feature);
|
||||||
currentTargetDistance += 1;
|
currentTargetDistance += 1;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
|
|||||||
@@ -34,13 +34,20 @@ export class StartEndMarkers {
|
|||||||
if (!map_) return;
|
if (!map_) return;
|
||||||
|
|
||||||
const tool = get(currentTool);
|
const tool = get(currentTool);
|
||||||
const statistics = get(slicedGPXStatistics)?.[0] ?? get(gpxStatistics);
|
const statistics = get(gpxStatistics);
|
||||||
|
const slicedStatistics = get(slicedGPXStatistics);
|
||||||
const hidden = get(allHidden);
|
const hidden = get(allHidden);
|
||||||
if (statistics.local.points.length > 0 && tool !== Tool.ROUTING && !hidden) {
|
if (statistics.global.length > 0 && tool !== Tool.ROUTING && !hidden) {
|
||||||
this.start.setLngLat(statistics.local.points[0].getCoordinates()).addTo(map_);
|
this.start
|
||||||
|
.setLngLat(
|
||||||
|
statistics.getTrackPoint(slicedStatistics?.[1] ?? 0)!.trkpt.getCoordinates()
|
||||||
|
)
|
||||||
|
.addTo(map_);
|
||||||
this.end
|
this.end
|
||||||
.setLngLat(
|
.setLngLat(
|
||||||
statistics.local.points[statistics.local.points.length - 1].getCoordinates()
|
statistics
|
||||||
|
.getTrackPoint(slicedStatistics?.[2] ?? statistics.global.length - 1)!
|
||||||
|
.trkpt.getCoordinates()
|
||||||
)
|
)
|
||||||
.addTo(map_);
|
.addTo(map_);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
overlays,
|
overlays,
|
||||||
overlayTree,
|
overlayTree,
|
||||||
overpassTree,
|
overpassTree,
|
||||||
|
terrainSources,
|
||||||
} from '$lib/assets/layers';
|
} from '$lib/assets/layers';
|
||||||
import { getLayers, isSelected, toggle } from '$lib/components/map/layer-control/utils';
|
import { getLayers, isSelected, toggle } from '$lib/components/map/layer-control/utils';
|
||||||
import { i18n } from '$lib/i18n.svelte';
|
import { i18n } from '$lib/i18n.svelte';
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
currentOverpassQueries,
|
currentOverpassQueries,
|
||||||
customLayers,
|
customLayers,
|
||||||
opacities,
|
opacities,
|
||||||
|
terrainSource,
|
||||||
} = settings;
|
} = settings;
|
||||||
|
|
||||||
const { isLayerFromExtension, getLayerName } = extensionAPI;
|
const { isLayerFromExtension, getLayerName } = extensionAPI;
|
||||||
@@ -233,6 +235,23 @@
|
|||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Accordion.Content>
|
</Accordion.Content>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
|
<Accordion.Item value="terrain-source">
|
||||||
|
<Accordion.Trigger>{i18n._('layers.terrain')}</Accordion.Trigger>
|
||||||
|
<Accordion.Content class="flex flex-col gap-3 overflow-visible">
|
||||||
|
<Select.Root bind:value={$terrainSource} type="single">
|
||||||
|
<Select.Trigger class="mr-1 w-full" size="sm">
|
||||||
|
{i18n._(`layers.label.${$terrainSource}`)}
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content class="h-fit max-h-[40dvh] overflow-y-auto">
|
||||||
|
{#each Object.keys(terrainSources) as id}
|
||||||
|
<Select.Item value={id}>
|
||||||
|
{i18n._(`layers.label.${id}`)}
|
||||||
|
</Select.Item>
|
||||||
|
{/each}
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Root>
|
||||||
|
</Accordion.Content>
|
||||||
|
</Accordion.Item>
|
||||||
</Accordion.Root>
|
</Accordion.Root>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Sheet.Header>
|
</Sheet.Header>
|
||||||
|
|||||||
@@ -3,8 +3,16 @@ import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';
|
|||||||
import { get, writable, type Writable } from 'svelte/store';
|
import { get, writable, type Writable } from 'svelte/store';
|
||||||
import { settings } from '$lib/logic/settings';
|
import { settings } from '$lib/logic/settings';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
|
import { terrainSources } from '$lib/assets/layers';
|
||||||
|
|
||||||
const { treeFileView, elevationProfile, bottomPanelSize, rightPanelSize, distanceUnits } = settings;
|
const {
|
||||||
|
treeFileView,
|
||||||
|
elevationProfile,
|
||||||
|
bottomPanelSize,
|
||||||
|
rightPanelSize,
|
||||||
|
distanceUnits,
|
||||||
|
terrainSource,
|
||||||
|
} = settings;
|
||||||
|
|
||||||
let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = {
|
let fitBoundsOptions: mapboxgl.MapOptions['fitBoundsOptions'] = {
|
||||||
maxZoom: 15,
|
maxZoom: 15,
|
||||||
@@ -123,34 +131,14 @@ export class MapboxGLMap {
|
|||||||
});
|
});
|
||||||
map.addControl(scaleControl);
|
map.addControl(scaleControl);
|
||||||
map.on('style.load', () => {
|
map.on('style.load', () => {
|
||||||
map.addSource('mapbox-dem', {
|
|
||||||
type: 'raster-dem',
|
|
||||||
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
|
|
||||||
tileSize: 512,
|
|
||||||
maxzoom: 14,
|
|
||||||
});
|
|
||||||
if (map.getPitch() > 0) {
|
|
||||||
map.setTerrain({
|
|
||||||
source: 'mapbox-dem',
|
|
||||||
exaggeration: 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
map.setFog({
|
map.setFog({
|
||||||
color: 'rgb(186, 210, 235)',
|
color: 'rgb(186, 210, 235)',
|
||||||
'high-color': 'rgb(36, 92, 223)',
|
'high-color': 'rgb(36, 92, 223)',
|
||||||
'horizon-blend': 0.1,
|
'horizon-blend': 0.1,
|
||||||
'space-color': 'rgb(156, 240, 255)',
|
'space-color': 'rgb(156, 240, 255)',
|
||||||
});
|
});
|
||||||
map.on('pitch', () => {
|
map.on('pitch', this.setTerrain.bind(this));
|
||||||
if (map.getPitch() > 0) {
|
this.setTerrain();
|
||||||
map.setTerrain({
|
|
||||||
source: 'mapbox-dem',
|
|
||||||
exaggeration: 1,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
map.setTerrain(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
map.on('style.import.load', () => {
|
map.on('style.import.load', () => {
|
||||||
const basemap = map.getStyle().imports?.find((imprt) => imprt.id === 'basemap');
|
const basemap = map.getStyle().imports?.find((imprt) => imprt.id === 'basemap');
|
||||||
@@ -162,6 +150,7 @@ export class MapboxGLMap {
|
|||||||
this._map.set(map); // only set the store after the map has loaded
|
this._map.set(map); // only set the store after the map has loaded
|
||||||
window._map = map; // entry point for extensions
|
window._map = map; // entry point for extensions
|
||||||
this.resize();
|
this.resize();
|
||||||
|
this.setTerrain();
|
||||||
scaleControl.setUnit(get(distanceUnits));
|
scaleControl.setUnit(get(distanceUnits));
|
||||||
|
|
||||||
this._onLoadCallbacks.forEach((callback) => callback(map));
|
this._onLoadCallbacks.forEach((callback) => callback(map));
|
||||||
@@ -177,6 +166,7 @@ export class MapboxGLMap {
|
|||||||
scaleControl.setUnit(units);
|
scaleControl.setUnit(units);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
this._unsubscribes.push(terrainSource.subscribe(() => this.setTerrain()));
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad(callback: (map: mapboxgl.Map) => void) {
|
onLoad(callback: (map: mapboxgl.Map) => void) {
|
||||||
@@ -217,6 +207,24 @@ export class MapboxGLMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTerrain() {
|
||||||
|
const map = get(this._map);
|
||||||
|
if (map) {
|
||||||
|
const source = get(terrainSource);
|
||||||
|
if (!map.getSource(source)) {
|
||||||
|
map.addSource(source, terrainSources[source]);
|
||||||
|
}
|
||||||
|
if (map.getPitch() > 0) {
|
||||||
|
map.setTerrain({
|
||||||
|
source: source,
|
||||||
|
exaggeration: 1,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
map.setTerrain(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const map = new MapboxGLMap();
|
export const map = new MapboxGLMap();
|
||||||
|
|||||||
@@ -28,17 +28,15 @@ export class ReducedGPXLayer {
|
|||||||
|
|
||||||
update() {
|
update() {
|
||||||
const file = this._fileState.file;
|
const file = this._fileState.file;
|
||||||
const stats = this._fileState.statistics;
|
if (!file) {
|
||||||
if (!file || !stats) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
file.forEachSegment((segment, trackIndex, segmentIndex) => {
|
||||||
let segmentItem = new ListTrackSegmentItem(file._data.id, trackIndex, segmentIndex);
|
let segmentItem = new ListTrackSegmentItem(file._data.id, trackIndex, segmentIndex);
|
||||||
let statistics = stats.getStatisticsFor(segmentItem);
|
|
||||||
this._updateSimplified(segmentItem.getFullId(), [
|
this._updateSimplified(segmentItem.getFullId(), [
|
||||||
segmentItem,
|
segmentItem,
|
||||||
statistics.local.points.length,
|
segment.trkpt.length,
|
||||||
ramerDouglasPeucker(statistics.local.points, minTolerance),
|
ramerDouglasPeucker(segment.trkpt, minTolerance),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -793,24 +793,25 @@ export class RoutingControls {
|
|||||||
replacingDistance +=
|
replacingDistance +=
|
||||||
distance(response[i - 1].getCoordinates(), response[i].getCoordinates()) / 1000;
|
distance(response[i - 1].getCoordinates(), response[i].getCoordinates()) / 1000;
|
||||||
}
|
}
|
||||||
|
let startAnchorStats = stats.getTrackPoint(anchors[0].point._data.index)!;
|
||||||
|
let endAnchorStats = stats.getTrackPoint(
|
||||||
|
anchors[anchors.length - 1].point._data.index
|
||||||
|
)!;
|
||||||
|
|
||||||
let replacedDistance =
|
let replacedDistance =
|
||||||
stats.local.distance.moving[anchors[anchors.length - 1].point._data.index] -
|
endAnchorStats.distance.moving - startAnchorStats.distance.moving;
|
||||||
stats.local.distance.moving[anchors[0].point._data.index];
|
|
||||||
|
|
||||||
let newDistance = stats.global.distance.moving + replacingDistance - replacedDistance;
|
let newDistance = stats.global.distance.moving + replacingDistance - replacedDistance;
|
||||||
let newTime = (newDistance / stats.global.speed.moving) * 3600;
|
let newTime = (newDistance / stats.global.speed.moving) * 3600;
|
||||||
|
|
||||||
let remainingTime =
|
let remainingTime =
|
||||||
stats.global.time.moving -
|
stats.global.time.moving -
|
||||||
(stats.local.time.moving[anchors[anchors.length - 1].point._data.index] -
|
(endAnchorStats.time.moving - startAnchorStats.time.moving);
|
||||||
stats.local.time.moving[anchors[0].point._data.index]);
|
|
||||||
let replacingTime = newTime - remainingTime;
|
let replacingTime = newTime - remainingTime;
|
||||||
|
|
||||||
if (replacingTime <= 0) {
|
if (replacingTime <= 0) {
|
||||||
// Fallback to simple time difference
|
// Fallback to simple time difference
|
||||||
replacingTime =
|
replacingTime = endAnchorStats.time.total - startAnchorStats.time.total;
|
||||||
stats.local.time.total[anchors[anchors.length - 1].point._data.index] -
|
|
||||||
stats.local.time.total[anchors[0].point._data.index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
speed = (replacingDistance / replacingTime) * 3600;
|
speed = (replacingDistance / replacingTime) * 3600;
|
||||||
@@ -820,9 +821,7 @@ export class RoutingControls {
|
|||||||
let endIndex = anchors[anchors.length - 1].point._data.index;
|
let endIndex = anchors[anchors.length - 1].point._data.index;
|
||||||
startTime = new Date(
|
startTime = new Date(
|
||||||
(segment.trkpt[endIndex].time?.getTime() ?? 0) -
|
(segment.trkpt[endIndex].time?.getTime() ?? 0) -
|
||||||
(replacingTime +
|
(replacingTime + endAnchorStats.time.total - endAnchorStats.time.moving) *
|
||||||
stats.local.time.total[endIndex] -
|
|
||||||
stats.local.time.moving[endIndex]) *
|
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,10 @@
|
|||||||
|
|
||||||
let validSelection = $derived(
|
let validSelection = $derived(
|
||||||
$selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']) &&
|
$selection.hasAnyChildren(new ListRootItem(), true, ['waypoints']) &&
|
||||||
$gpxStatistics.local.points.length > 0
|
$gpxStatistics.global.length > 0
|
||||||
);
|
);
|
||||||
let maxSliderValue = $derived(
|
let maxSliderValue = $derived(
|
||||||
validSelection && $gpxStatistics.local.points.length > 0
|
validSelection && $gpxStatistics.global.length > 0 ? $gpxStatistics.global.length - 1 : 1
|
||||||
? $gpxStatistics.local.points.length - 1
|
|
||||||
: 1
|
|
||||||
);
|
);
|
||||||
let sliderValues = $derived([0, maxSliderValue]);
|
let sliderValues = $derived([0, maxSliderValue]);
|
||||||
let canCrop = $derived(sliderValues[0] != 0 || sliderValues[1] != maxSliderValue);
|
let canCrop = $derived(sliderValues[0] != 0 || sliderValues[1] != maxSliderValue);
|
||||||
@@ -45,7 +43,7 @@
|
|||||||
function updateSlicedGPXStatistics() {
|
function updateSlicedGPXStatistics() {
|
||||||
if (validSelection && canCrop) {
|
if (validSelection && canCrop) {
|
||||||
$slicedGPXStatistics = [
|
$slicedGPXStatistics = [
|
||||||
get(gpxStatistics).slice(sliderValues[0], sliderValues[1]),
|
get(gpxStatistics).sliced(sliderValues[0], sliderValues[1]),
|
||||||
sliderValues[0],
|
sliderValues[0],
|
||||||
sliderValues[1],
|
sliderValues[1],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Mapbox ist das Unternehmen, das einige der schönen Karten auf dieser Website zur Verfügung stellt.
|
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.
|
Sie entwickeln auch die <a href="https://github.com/mapbox/mapbox-gl-js" target="_blank">Karten-Engine</a> welche **gpx.studio** unterstützt.
|
||||||
|
|
||||||
Wir sind äusserst 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 ä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.
|
||||||
Diese Partnerschaft ermöglicht es **gpx.studio**, von den Mapbox-Tools zu ermäßigten Preisen zu profitieren, was erheblich zur finanziellen Tragfähigkeit des Projekts beiträgt und es uns ermöglicht, die bestmögliche Benutzererfahrung zu bieten.
|
Diese Partnerschaft ermöglicht es **gpx.studio**, von den Mapbox-Tools zu ermäßigten Preisen zu profitieren, was erheblich zur finanziellen Tragfähigkeit des Projekts beiträgt und es uns ermöglicht, die bestmögliche Benutzererfahrung zu bieten.
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Facendo clic destro su una scheda file, è possibile accedere alle stesse azioni
|
|||||||
|
|
||||||
Come accennato nella [sezione opzioni di visualizzazione](./menu/view), è possibile passare a un layout ad albero per l'elenco dei file.
|
Come accennato nella [sezione opzioni di visualizzazione](./menu/view), è possibile passare a un layout ad albero per l'elenco dei file.
|
||||||
Questo layout è ideale per gestire un gran numero di file aperti, organizzandoli in una lista verticale sul lato destro della mappa.
|
Questo layout è ideale per gestire un gran numero di file aperti, organizzandoli in una lista verticale sul lato destro della mappa.
|
||||||
Inoltre, la vista ad albero dei file consente di ispezionare [tracce, segmenti e punti di interesse](./gpx) all'interno dei file attraverso sezioni espandibili.
|
Inoltre, la vista ad albero dei file consente d'ispezionare [tracce, segmenti e punti di interesse](./gpx) all'interno dei file attraverso sezioni espandibili.
|
||||||
|
|
||||||
Puoi anche applicare [modifiche](./menu/edit) e [strumenti](./toolbar) agli elementi interni del file.
|
Puoi anche applicare [modifiche](./menu/edit) e [strumenti](./toolbar) agli elementi interni del file.
|
||||||
Inoltre, è possibile trascinare e rilasciare gli elementi per riordinarli, o spostarli nella gerarchia o anche in un altro file.
|
Inoltre, è possibile trascinare e rilasciare gli elementi per riordinarli, o spostarli nella gerarchia o anche in un altro file.
|
||||||
@@ -78,7 +78,7 @@ Quando si passa sopra il profilo di elevazione, un suggerimento mostrerà le sta
|
|||||||
Per ottenere le statistiche per una sezione specifica del profilo di elevazione, è possibile trascinare un rettangolo di selezione sul profilo.
|
Per ottenere le statistiche per una sezione specifica del profilo di elevazione, è possibile trascinare un rettangolo di selezione sul profilo.
|
||||||
Fare clic sul profilo per resettare la selezione.
|
Fare clic sul profilo per resettare la selezione.
|
||||||
|
|
||||||
È inoltre possibile utilizzare la rotellina del mouse per ingrandire e rimpicciolire sul profilo di elevazione, e spostarsi a sinistra e a destra trascinando il profilo tenendo premuto il tasto <kbd>Maiusc</kbd>.
|
È inoltre possibile utilizzare la rotellina del mouse per ingrandire e rimpicciolire sul profilo di elevazione, e spostarsi a sinistra e a destra trascinando il profilo tenendo premuto il tasto <kbd>Maiuscolo</kbd>.
|
||||||
|
|
||||||
<div class="h-48 w-full">
|
<div class="h-48 w-full">
|
||||||
<ElevationProfile
|
<ElevationProfile
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Queste sono organizzate in una struttura gerarchica, con le tracce stesse al liv
|
|||||||
- Una **traccia** è composta da una sequenza di segmenti scollegati.
|
- Una **traccia** è composta da una sequenza di segmenti scollegati.
|
||||||
Inoltre, può contenere metadati come un **nome**, una **descrizione**, e **proprietà di visualizzazione**.
|
Inoltre, può contenere metadati come un **nome**, una **descrizione**, e **proprietà di visualizzazione**.
|
||||||
- Un **segmento** è una sequenza di punti GPS che formano un percorso continuo.
|
- Un **segmento** è una sequenza di punti GPS che formano un percorso continuo.
|
||||||
- Un **punto GPS** è una posizione con una latitudine, una longitudine, ed eventualmente un timestamp e un'altitudine.
|
- Un **punto GPS** è una posizione con una latitudine, una longitudine, ed eventualmente una marcatura temporale e un'altitudine.
|
||||||
Alcuni dispositivi memorizzano anche informazioni aggiuntive come frequenza cardiaca, cadenza, temperatura e potenza.
|
Alcuni dispositivi memorizzano anche informazioni aggiuntive come frequenza cardiaca, cadenza, temperatura e potenza.
|
||||||
|
|
||||||
Nella maggior parte dei casi, i file GPX contengono una singola traccia con un singolo segmento.
|
Nella maggior parte dei casi, i file GPX contengono una singola traccia con un singolo segmento.
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
## <HeartHandshake size="18" class="inline-block align-baseline" /> Aiuta a mantenere il sito gratuito (e senza pubblicità)
|
## <HeartHandshake size="18" class="inline-block align-baseline" /> Aiuta a mantenere il sito gratuito (e senza pubblicità)
|
||||||
|
|
||||||
Ogni volta che aggiungi o sposti i punti GPS, i nostri server calcolano il percorso migliore sulla rete stradale.
|
Ogni volta che aggiungi o sposti i punti GPS, i nostri server calcolano il percorso migliore sulla rete stradale.
|
||||||
Utilizziamo anche le API di <a href="https://mapbox.com" target="_blank">Mapbox</a> per visualizzare mappe gradevoli, recuperare i dati altimetrici e consentire la ricerca di luoghi.
|
Utilizziamo anche le API di <a href="https://mapbox.com" target="_blank">Mapbox</a> per visualizzare mappe stupende, recuperare i dati altimetrici e consentire la ricerca di luoghi.
|
||||||
|
|
||||||
Sfortunatamente, questo è costoso.
|
Sfortunatamente, fare tutto ciò è costoso.
|
||||||
Se ti piace utilizzare questo strumento e lo trovi utile, per favore considera di fare una piccola donazione per aiutare a mantenere il sito web gratuito e senza pubblicità.
|
Se ti piace utilizzare questo strumento e lo trovi utile, per favore considera di fare una piccola donazione per aiutare a mantenere il sito web gratuito e senza pubblicità.
|
||||||
|
|
||||||
Grazie mille per il vostro supporto! ❤️
|
Grazie mille per il vostro supporto! ❤️
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Create a copy of the currently selected files.
|
|||||||
|
|
||||||
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
|
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete
|
||||||
|
|
||||||
Delete the currently selected files.
|
.
|
||||||
|
|
||||||
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
|
### <FileX size="16" class="inline-block" style="margin-bottom: 2px" /> Delete all
|
||||||
|
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export const fileActions = {
|
|||||||
reverseSelection: () => {
|
reverseSelection: () => {
|
||||||
if (
|
if (
|
||||||
!get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints']) ||
|
!get(selection).hasAnyChildren(new ListRootItem(), true, ['waypoints']) ||
|
||||||
get(gpxStatistics).local.points?.length <= 1
|
get(gpxStatistics).global.length <= 1
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -345,19 +345,20 @@ export const fileActions = {
|
|||||||
let startTime: Date | undefined = undefined;
|
let startTime: Date | undefined = undefined;
|
||||||
if (speed !== undefined) {
|
if (speed !== undefined) {
|
||||||
if (
|
if (
|
||||||
statistics.local.points.length > 0 &&
|
statistics.global.length > 0 &&
|
||||||
statistics.local.points[0].time !== undefined
|
statistics.getTrackPoint(0)!.trkpt.time !== undefined
|
||||||
) {
|
) {
|
||||||
startTime = statistics.local.points[0].time;
|
startTime = statistics.getTrackPoint(0)!.trkpt.time;
|
||||||
} else {
|
} else {
|
||||||
let index = statistics.local.points.findIndex(
|
for (let i = 0; i < statistics.global.length; i++) {
|
||||||
(point) => point.time !== undefined
|
const point = statistics.getTrackPoint(i)!;
|
||||||
);
|
if (point.trkpt.time !== undefined) {
|
||||||
if (index !== -1 && statistics.local.points[index].time) {
|
|
||||||
startTime = new Date(
|
startTime = new Date(
|
||||||
statistics.local.points[index].time.getTime() -
|
point.trkpt.time.getTime() -
|
||||||
(1000 * 3600 * statistics.local.distance.total[index]) / speed
|
(1000 * 3600 * point.distance.total) / speed
|
||||||
);
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
defaultOverlayTree,
|
defaultOverlayTree,
|
||||||
defaultOverpassQueries,
|
defaultOverpassQueries,
|
||||||
defaultOverpassTree,
|
defaultOverpassTree,
|
||||||
|
defaultTerrainSource,
|
||||||
type CustomLayer,
|
type CustomLayer,
|
||||||
} from '$lib/assets/layers';
|
} from '$lib/assets/layers';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
@@ -154,6 +155,7 @@ export const settings = {
|
|||||||
customLayers: new Setting<Record<string, CustomLayer>>('customLayers', {}),
|
customLayers: new Setting<Record<string, CustomLayer>>('customLayers', {}),
|
||||||
customBasemapOrder: new Setting<string[]>('customBasemapOrder', []),
|
customBasemapOrder: new Setting<string[]>('customBasemapOrder', []),
|
||||||
customOverlayOrder: new Setting<string[]>('customOverlayOrder', []),
|
customOverlayOrder: new Setting<string[]>('customOverlayOrder', []),
|
||||||
|
terrainSource: new Setting('terrainSource', defaultTerrainSource),
|
||||||
directionMarkers: new Setting('directionMarkers', false),
|
directionMarkers: new Setting('directionMarkers', false),
|
||||||
distanceMarkers: new Setting('distanceMarkers', false),
|
distanceMarkers: new Setting('distanceMarkers', false),
|
||||||
streetViewSource: new Setting('streetViewSource', 'mapillary'),
|
streetViewSource: new Setting('streetViewSource', 'mapillary'),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ListItem, ListLevel } from '$lib/components/file-list/file-list';
|
import { ListItem, ListLevel } from '$lib/components/file-list/file-list';
|
||||||
import { GPXFile, GPXStatistics, type Track } from 'gpx';
|
import { GPXFile, GPXStatistics, GPXStatisticsGroup, type Track } from 'gpx';
|
||||||
|
|
||||||
export class GPXStatisticsTree {
|
export class GPXStatisticsTree {
|
||||||
level: ListLevel;
|
level: ListLevel;
|
||||||
@@ -21,35 +21,26 @@ export class GPXStatisticsTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatisticsFor(item: ListItem): GPXStatistics {
|
getStatisticsFor(item: ListItem): GPXStatisticsGroup {
|
||||||
let statistics = [];
|
let statistics = new GPXStatisticsGroup();
|
||||||
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.push(this.statistics[key]);
|
statistics.add(this.statistics[key]);
|
||||||
} else {
|
} else {
|
||||||
statistics.push(this.statistics[key].getStatisticsFor(item));
|
statistics.add(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.push(child);
|
statistics.add(child);
|
||||||
} else if (child !== undefined) {
|
} else if (child !== undefined) {
|
||||||
statistics.push(child.getStatisticsFor(item));
|
statistics.add(child.getStatisticsFor(item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (statistics.length === 0) {
|
return statistics;
|
||||||
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 };
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { selection } from '$lib/logic/selection';
|
import { selection } from '$lib/logic/selection';
|
||||||
import { GPXStatistics } from 'gpx';
|
import { GPXGlobalStatistics, GPXStatisticsGroup } from 'gpx';
|
||||||
import { fileStateCollection, GPXFileState } from '$lib/logic/file-state';
|
import { fileStateCollection, GPXFileState } from '$lib/logic/file-state';
|
||||||
import {
|
import {
|
||||||
ListFileItem,
|
ListFileItem,
|
||||||
@@ -12,7 +12,7 @@ import { settings } from '$lib/logic/settings';
|
|||||||
const { fileOrder } = settings;
|
const { fileOrder } = settings;
|
||||||
|
|
||||||
export class SelectedGPXStatistics {
|
export class SelectedGPXStatistics {
|
||||||
private _statistics: Writable<GPXStatistics>;
|
private _statistics: Writable<GPXStatisticsGroup>;
|
||||||
private _files: Map<
|
private _files: Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -22,18 +22,21 @@ export class SelectedGPXStatistics {
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._statistics = writable(new GPXStatistics());
|
this._statistics = writable(new GPXStatisticsGroup());
|
||||||
this._files = new Map();
|
this._files = new Map();
|
||||||
selection.subscribe(() => this.update());
|
selection.subscribe(() => this.update());
|
||||||
fileOrder.subscribe(() => this.update());
|
fileOrder.subscribe(() => this.update());
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(run: (value: GPXStatistics) => void, invalidate?: (value?: GPXStatistics) => void) {
|
subscribe(
|
||||||
|
run: (value: GPXStatisticsGroup) => void,
|
||||||
|
invalidate?: (value?: GPXStatisticsGroup) => void
|
||||||
|
) {
|
||||||
return this._statistics.subscribe(run, invalidate);
|
return this._statistics.subscribe(run, invalidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
let statistics = new GPXStatistics();
|
let statistics = new GPXStatisticsGroup();
|
||||||
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
selection.applyToOrderedSelectedItemsFromFile((fileId, level, items) => {
|
||||||
let stats = fileStateCollection.getStatistics(fileId);
|
let stats = fileStateCollection.getStatistics(fileId);
|
||||||
if (stats) {
|
if (stats) {
|
||||||
@@ -43,7 +46,7 @@ export class SelectedGPXStatistics {
|
|||||||
!(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) ||
|
!(item instanceof ListWaypointItem || item instanceof ListWaypointsItem) ||
|
||||||
first
|
first
|
||||||
) {
|
) {
|
||||||
statistics.mergeWith(stats.getStatisticsFor(item));
|
statistics.add(stats.getStatisticsFor(item));
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -76,7 +79,7 @@ export class SelectedGPXStatistics {
|
|||||||
|
|
||||||
export const gpxStatistics = new SelectedGPXStatistics();
|
export const gpxStatistics = new SelectedGPXStatistics();
|
||||||
|
|
||||||
export const slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined> =
|
export const slicedGPXStatistics: Writable<[GPXGlobalStatistics, number, number] | undefined> =
|
||||||
writable(undefined);
|
writable(undefined);
|
||||||
|
|
||||||
gpxStatistics.subscribe(() => {
|
gpxStatistics.subscribe(() => {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Railway Station",
|
"railway-station": "Railway Station",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Bus Stop",
|
"bus-stop": "Bus Stop",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Actualitza la capa"
|
"update": "Actualitza la capa"
|
||||||
},
|
},
|
||||||
"opacity": "Opacitat de la superposició",
|
"opacity": "Opacitat de la superposició",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Mapes base",
|
"basemaps": "Mapes base",
|
||||||
"overlays": "Capes",
|
"overlays": "Capes",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Estació de tren",
|
"railway-station": "Estació de tren",
|
||||||
"tram-stop": "Parada de tramvia",
|
"tram-stop": "Parada de tramvia",
|
||||||
"bus-stop": "Parada d'autobús",
|
"bus-stop": "Parada d'autobús",
|
||||||
"ferry": "Ferri"
|
"ferry": "Ferri",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Aktualizovat vrstvu"
|
"update": "Aktualizovat vrstvu"
|
||||||
},
|
},
|
||||||
"opacity": "Průhlednost překryvu",
|
"opacity": "Průhlednost překryvu",
|
||||||
|
"terrain": "Zdroj terénu",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Základní mapy",
|
"basemaps": "Základní mapy",
|
||||||
"overlays": "Překrytí",
|
"overlays": "Překrytí",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Vrstevnice",
|
"swisstopoSlope": "swisstopo Vrstevnice",
|
||||||
"swisstopoHiking": "swisstopo Turistická",
|
"swisstopoHiking": "swisstopo Turistická",
|
||||||
"swisstopoHikingClosures": "swisstopo Turistické uzávěry",
|
"swisstopoHikingClosures": "swisstopo Turistické uzávěry",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Železniční stanice",
|
"railway-station": "Železniční stanice",
|
||||||
"tram-stop": "Zastávka tramvaje",
|
"tram-stop": "Zastávka tramvaje",
|
||||||
"bus-stop": "Autobusová zastávka",
|
"bus-stop": "Autobusová zastávka",
|
||||||
"ferry": "Trajekt"
|
"ferry": "Trajekt",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Railway Station",
|
"railway-station": "Railway Station",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Bus Stop",
|
"bus-stop": "Bus Stop",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Layer aktualisieren"
|
"update": "Layer aktualisieren"
|
||||||
},
|
},
|
||||||
"opacity": "Deckkraft der Überlagerung",
|
"opacity": "Deckkraft der Überlagerung",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basiskarte",
|
"basemaps": "Basiskarte",
|
||||||
"overlays": "Ebenen",
|
"overlays": "Ebenen",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Neigung",
|
"swisstopoSlope": "swisstopo Neigung",
|
||||||
"swisstopoHiking": "swisstopo Wandern",
|
"swisstopoHiking": "swisstopo Wandern",
|
||||||
"swisstopoHikingClosures": "swisstopo Wanderungen Schließungen",
|
"swisstopoHikingClosures": "swisstopo Wanderungen Schließungen",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Bahnhof",
|
"railway-station": "Bahnhof",
|
||||||
"tram-stop": "Straßenbahnhaltestelle",
|
"tram-stop": "Straßenbahnhaltestelle",
|
||||||
"bus-stop": "Bushaltestelle",
|
"bus-stop": "Bushaltestelle",
|
||||||
"ferry": "Fähre"
|
"ferry": "Fähre",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
@@ -473,7 +478,7 @@
|
|||||||
},
|
},
|
||||||
"homepage": {
|
"homepage": {
|
||||||
"website": "Webseite",
|
"website": "Webseite",
|
||||||
"home": "Zuhause",
|
"home": "Startseite",
|
||||||
"app": "App",
|
"app": "App",
|
||||||
"contact": "Kontakt",
|
"contact": "Kontakt",
|
||||||
"reddit": "Reddit",
|
"reddit": "Reddit",
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Railway Station",
|
"railway-station": "Railway Station",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Bus Stop",
|
"bus-stop": "Bus Stop",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Railway Station",
|
"railway-station": "Railway Station",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Bus Stop",
|
"bus-stop": "Bus Stop",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"home_title": "el editor online de archivos GPX",
|
"home_title": "el editor online de archivos GPX",
|
||||||
"app_title": "app",
|
"app_title": "app",
|
||||||
"embed_title": "El editor online de archivos GPX",
|
"embed_title": " editor online de archivos GPX",
|
||||||
"help_title": "ayuda",
|
"help_title": "ayuda",
|
||||||
"404_title": "página no encontrada",
|
"404_title": "página no encontrada",
|
||||||
"description": "Mira, edita y crea archivos GPX online con planificación avanzada de rutas y herramientas de procesamiento de archivos, bonitos mapas y visualizaciones detalladas de datos."
|
"description": "Mira, edita y crea archivos GPX online con planificación avanzada de rutas y herramientas de procesamiento de archivos, bonitos mapas y visualizaciones detalladas de datos."
|
||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Actualizar capa"
|
"update": "Actualizar capa"
|
||||||
},
|
},
|
||||||
"opacity": "Opacidad de la capa superpuesta",
|
"opacity": "Opacidad de la capa superpuesta",
|
||||||
|
"terrain": "Origen del terreno",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Mapas base",
|
"basemaps": "Mapas base",
|
||||||
"overlays": "Capas",
|
"overlays": "Capas",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "Gravel bikerouter.de",
|
"bikerouterGravel": "Gravel bikerouter.de",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Senderismo",
|
"swisstopoHiking": "swisstopo Senderismo",
|
||||||
"swisstopoHikingClosures": "swisstopo Rutas Senderismo",
|
"swisstopoHikingClosures": "swisstopo Rutas Senderismo",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Estación de tren",
|
"railway-station": "Estación de tren",
|
||||||
"tram-stop": "Parada de tranvía",
|
"tram-stop": "Parada de tranvía",
|
||||||
"bus-stop": "Parada de autobús",
|
"bus-stop": "Parada de autobús",
|
||||||
"ferry": "Ferri"
|
"ferry": "Ferri",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Eguneratu geruza"
|
"update": "Eguneratu geruza"
|
||||||
},
|
},
|
||||||
"opacity": "Geruzaren opakutasuna",
|
"opacity": "Geruzaren opakutasuna",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Oinarrizko mapak",
|
"basemaps": "Oinarrizko mapak",
|
||||||
"overlays": "Geruzak",
|
"overlays": "Geruzak",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Malda",
|
"swisstopoSlope": "swisstopo Malda",
|
||||||
"swisstopoHiking": "swisstopo Mendi ibilaldiak",
|
"swisstopoHiking": "swisstopo Mendi ibilaldiak",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Tren geltokia",
|
"railway-station": "Tren geltokia",
|
||||||
"tram-stop": "Tranbia geltokia",
|
"tram-stop": "Tranbia geltokia",
|
||||||
"bus-stop": "Autobus geltokia",
|
"bus-stop": "Autobus geltokia",
|
||||||
"ferry": "Ferria"
|
"ferry": "Ferria",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Päivitä karttataso"
|
"update": "Päivitä karttataso"
|
||||||
},
|
},
|
||||||
"opacity": "Peitetason läpinäkyvyys",
|
"opacity": "Peitetason läpinäkyvyys",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Taustakartat",
|
"basemaps": "Taustakartat",
|
||||||
"overlays": "Peitetasot",
|
"overlays": "Peitetasot",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Rinnekaltevuus",
|
"swisstopoSlope": "swisstopo Rinnekaltevuus",
|
||||||
"swisstopoHiking": "swisstopo Retkeilyreitit",
|
"swisstopoHiking": "swisstopo Retkeilyreitit",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Rautatieasemat",
|
"railway-station": "Rautatieasemat",
|
||||||
"tram-stop": "Raitiovaunupysäkit",
|
"tram-stop": "Raitiovaunupysäkit",
|
||||||
"bus-stop": "Linja-autopysäkit",
|
"bus-stop": "Linja-autopysäkit",
|
||||||
"ferry": "Lautat"
|
"ferry": "Lautat",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Mettre à jour la couche"
|
"update": "Mettre à jour la couche"
|
||||||
},
|
},
|
||||||
"opacity": "Opacité de la surcouche",
|
"opacity": "Opacité de la surcouche",
|
||||||
|
"terrain": "Source du relief",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Fonds de carte",
|
"basemaps": "Fonds de carte",
|
||||||
"overlays": "Surcouches",
|
"overlays": "Surcouches",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Relief",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Pente",
|
"swisstopoSlope": "swisstopo Pente",
|
||||||
"swisstopoHiking": "swisstopo Randonnée",
|
"swisstopoHiking": "swisstopo Randonnée",
|
||||||
"swisstopoHikingClosures": "swisstopo Fermetures de randonnée",
|
"swisstopoHikingClosures": "swisstopo Fermetures de randonnée",
|
||||||
@@ -350,7 +353,7 @@
|
|||||||
"eat-and-drink": "Nourriture et boissons",
|
"eat-and-drink": "Nourriture et boissons",
|
||||||
"amenities": "Commodités",
|
"amenities": "Commodités",
|
||||||
"toilets": "Toilettes",
|
"toilets": "Toilettes",
|
||||||
"water": "Cours d'eau",
|
"water": "Eau potable",
|
||||||
"shower": "Douche",
|
"shower": "Douche",
|
||||||
"shelter": "Abri",
|
"shelter": "Abri",
|
||||||
"cemetery": "Cimetière",
|
"cemetery": "Cimetière",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Gare",
|
"railway-station": "Gare",
|
||||||
"tram-stop": "Arrêt de tram",
|
"tram-stop": "Arrêt de tram",
|
||||||
"bus-stop": "Arrêt de bus",
|
"bus-stop": "Arrêt de bus",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
@@ -443,7 +448,7 @@
|
|||||||
"convenience_store": "Épicerie",
|
"convenience_store": "Épicerie",
|
||||||
"crossing": "Croisement",
|
"crossing": "Croisement",
|
||||||
"department_store": "Grand magasin",
|
"department_store": "Grand magasin",
|
||||||
"drinking_water": "Cours d'eau",
|
"drinking_water": "Eau potable",
|
||||||
"exit": "Sortie",
|
"exit": "Sortie",
|
||||||
"lodge": "Refuge",
|
"lodge": "Refuge",
|
||||||
"lodging": "Hébergement",
|
"lodging": "Hébergement",
|
||||||
@@ -468,7 +473,7 @@
|
|||||||
"summit": "Sommet",
|
"summit": "Sommet",
|
||||||
"telephone": "Téléphone",
|
"telephone": "Téléphone",
|
||||||
"tunnel": "Tunnel",
|
"tunnel": "Tunnel",
|
||||||
"water_source": "Source d'eau"
|
"water_source": "Point d'eau"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"homepage": {
|
"homepage": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Railway Station",
|
"railway-station": "Railway Station",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Bus Stop",
|
"bus-stop": "Bus Stop",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Réteg feltöltése"
|
"update": "Réteg feltöltése"
|
||||||
},
|
},
|
||||||
"opacity": "Átfedés átlátszósága",
|
"opacity": "Átfedés átlátszósága",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Alaptérkép",
|
"basemaps": "Alaptérkép",
|
||||||
"overlays": "Térkép rétegek",
|
"overlays": "Térkép rétegek",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "kerékpár és terepkerékpár út",
|
"bikerouterGravel": "kerékpár és terepkerékpár út",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Lejtő",
|
"swisstopoSlope": "swisstopo Lejtő",
|
||||||
"swisstopoHiking": "swisstopo Túra",
|
"swisstopoHiking": "swisstopo Túra",
|
||||||
"swisstopoHikingClosures": "swisstopo túralezárások",
|
"swisstopoHikingClosures": "swisstopo túralezárások",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Vasútállomás",
|
"railway-station": "Vasútállomás",
|
||||||
"tram-stop": "Villamos megálló",
|
"tram-stop": "Villamos megálló",
|
||||||
"bus-stop": "Buszmegálló",
|
"bus-stop": "Buszmegálló",
|
||||||
"ferry": "Komp"
|
"ferry": "Komp",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Perbarui lapisan"
|
"update": "Perbarui lapisan"
|
||||||
},
|
},
|
||||||
"opacity": "Opasitas Overlay",
|
"opacity": "Opasitas Overlay",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Peta dasar",
|
"basemaps": "Peta dasar",
|
||||||
"overlays": "Overlay",
|
"overlays": "Overlay",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Kemiringan",
|
"swisstopoSlope": "swisstopo Kemiringan",
|
||||||
"swisstopoHiking": "swisstopo Pendakian",
|
"swisstopoHiking": "swisstopo Pendakian",
|
||||||
"swisstopoHikingClosures": "Penutupan Jalur Pendakian swisstopo",
|
"swisstopoHikingClosures": "Penutupan Jalur Pendakian swisstopo",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Stasiun kereta api",
|
"railway-station": "Stasiun kereta api",
|
||||||
"tram-stop": "Halt trem",
|
"tram-stop": "Halt trem",
|
||||||
"bus-stop": "Pemberhentian Bus",
|
"bus-stop": "Pemberhentian Bus",
|
||||||
"ferry": "Feri"
|
"ferry": "Feri",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -34,8 +34,8 @@
|
|||||||
"elevation_profile": "Profilo altimetrico",
|
"elevation_profile": "Profilo altimetrico",
|
||||||
"tree_file_view": "Vista ad albero",
|
"tree_file_view": "Vista ad albero",
|
||||||
"switch_basemap": "Passa alla mappa di base precedente",
|
"switch_basemap": "Passa alla mappa di base precedente",
|
||||||
"toggle_overlays": "Attiva/disattiva le sovrapposizioni",
|
"toggle_overlays": "Attiva / disattiva le sovrapposizioni",
|
||||||
"toggle_3d": "Attiva/disattiva 3D",
|
"toggle_3d": "Attiva / disattiva 3D",
|
||||||
"settings": "Impostazioni",
|
"settings": "Impostazioni",
|
||||||
"distance_units": "Unità distanza",
|
"distance_units": "Unità distanza",
|
||||||
"metric": "Metrico",
|
"metric": "Metrico",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"street_view_source": "Sorgente della vista stradale",
|
"street_view_source": "Sorgente della vista stradale",
|
||||||
"mapillary": "Mapillary",
|
"mapillary": "Mapillary",
|
||||||
"google": "Google",
|
"google": "Google",
|
||||||
"toggle_street_view": "Street View",
|
"toggle_street_view": "Vista stradale",
|
||||||
"layers": "Livelli della mappa...",
|
"layers": "Livelli della mappa...",
|
||||||
"distance_markers": "Indicatori di distanza",
|
"distance_markers": "Indicatori di distanza",
|
||||||
"direction_markers": "Frecce direzionali",
|
"direction_markers": "Frecce direzionali",
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
"tooltip": "Pianifica o modifica un percorsoo",
|
"tooltip": "Pianifica o modifica un percorsoo",
|
||||||
"activity": "Attività",
|
"activity": "Attività",
|
||||||
"use_routing": "Instradamento",
|
"use_routing": "Instradamento",
|
||||||
"use_routing_tooltip": "Collega i punti di ancoraggio tramite la rete stradale o in linea retta se disabilitato",
|
"use_routing_tooltip": "Collega i punti di ancoraggio tramite la rete stradale (o in linea retta se disabilitato)",
|
||||||
"allow_private": "Consenti strade private",
|
"allow_private": "Consenti strade private",
|
||||||
"reverse": {
|
"reverse": {
|
||||||
"button": "Inverti la traccia",
|
"button": "Inverti la traccia",
|
||||||
@@ -235,18 +235,18 @@
|
|||||||
"help_no_selection": "Seleziona un file per richiedere i dati di altitudine."
|
"help_no_selection": "Seleziona un file per richiedere i dati di altitudine."
|
||||||
},
|
},
|
||||||
"waypoint": {
|
"waypoint": {
|
||||||
"tooltip": "Creare e modificare punti di interesse",
|
"tooltip": "Creare e modificare punti d'interesse",
|
||||||
"icon": "Icona",
|
"icon": "Icona",
|
||||||
"link": "Collegamento",
|
"link": "Collegamento",
|
||||||
"longitude": "Longitudine",
|
"longitude": "Longitudine",
|
||||||
"latitude": "Latitudine",
|
"latitude": "Latitudine",
|
||||||
"create": "Creare un punto di interesse",
|
"create": "Creare un punto d'interesse",
|
||||||
"add": "Aggiungi punto di interesse al file",
|
"add": "Aggiungi punto d'interesse al file",
|
||||||
"help": "Compila il modulo per creare un nuovo punto di interesse, oppure fai clic su uno esistente per modificarlo. Fare clic sulla mappa per inserire le coordinate o trascinare i punti di interesse per spostarli.",
|
"help": "Compila il modulo per creare un nuovo punto d'interesse, oppure fai clic su uno esistente per modificarlo. Fare clic sulla mappa per inserire le coordinate o trascinare i punti d'interesse per spostarli.",
|
||||||
"help_no_selection": "Selezionare un file per creare o modificare punti di interesse."
|
"help_no_selection": "Selezionare un file per creare o modificare punti d'interesse."
|
||||||
},
|
},
|
||||||
"reduce": {
|
"reduce": {
|
||||||
"tooltip": "Riduci il numero di punti della traccia",
|
"tooltip": "Riduci il numero di punti GPS",
|
||||||
"tolerance": "Tolleranza",
|
"tolerance": "Tolleranza",
|
||||||
"number_of_points": "Numero di punti GPS",
|
"number_of_points": "Numero di punti GPS",
|
||||||
"button": "Minimizza",
|
"button": "Minimizza",
|
||||||
@@ -254,14 +254,14 @@
|
|||||||
"help_no_selection": "Selezionare una traccia per ridurre il numero dei suoi punti GPS."
|
"help_no_selection": "Selezionare una traccia per ridurre il numero dei suoi punti GPS."
|
||||||
},
|
},
|
||||||
"clean": {
|
"clean": {
|
||||||
"tooltip": "Pulire i punti GPS e i punti di interesse con una selezione rettangolare",
|
"tooltip": "Pulire i punti GPS e i punti d'interesse con una selezione rettangolare",
|
||||||
"delete_trackpoints": "Eliminare punti GPS",
|
"delete_trackpoints": "Eliminare punti GPS",
|
||||||
"delete_waypoints": "Cancella punti d'interesse",
|
"delete_waypoints": "Cancella punti d'interesse",
|
||||||
"delete_inside": "Elimina all'interno della selezione",
|
"delete_inside": "Elimina all'interno della selezione",
|
||||||
"delete_outside": "Elimina fuori dalla selezione",
|
"delete_outside": "Elimina fuori dalla selezione",
|
||||||
"button": "Elimina",
|
"button": "Elimina",
|
||||||
"help": "Selezionare un'area rettangolare sulla mappa per rimuovere i punti GPS e i punti di interesse.",
|
"help": "Selezionare un'area rettangolare sulla mappa per rimuovere i punti GPS e i punti d'interesse.",
|
||||||
"help_no_selection": "Seleziona una traccia per pulire i punti GPS e i punti di interesse."
|
"help_no_selection": "Seleziona una traccia per pulire i punti GPS e i punti d'interesse."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layers": {
|
"layers": {
|
||||||
@@ -273,7 +273,7 @@
|
|||||||
"new": "Nuovo livello personalizzato",
|
"new": "Nuovo livello personalizzato",
|
||||||
"edit": "Modifica livello personalizzato",
|
"edit": "Modifica livello personalizzato",
|
||||||
"urls": "URL(s)",
|
"urls": "URL(s)",
|
||||||
"url_placeholder": "WMTS, WMS o Mapbox stile JSON",
|
"url_placeholder": "WMTS, WMS o JSON in stile Mapbox",
|
||||||
"max_zoom": "Zoom massimo",
|
"max_zoom": "Zoom massimo",
|
||||||
"layer_type": "Tipo del layer",
|
"layer_type": "Tipo del layer",
|
||||||
"basemap": "Mappa Base",
|
"basemap": "Mappa Base",
|
||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Aggiorna livello"
|
"update": "Aggiorna livello"
|
||||||
},
|
},
|
||||||
"opacity": "Opacità di sovrapposizione",
|
"opacity": "Opacità di sovrapposizione",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Mappe di base",
|
"basemaps": "Mappe di base",
|
||||||
"overlays": "Sovrapposizioni",
|
"overlays": "Sovrapposizioni",
|
||||||
@@ -309,7 +310,7 @@
|
|||||||
"linz": "LINZ Topo",
|
"linz": "LINZ Topo",
|
||||||
"linzTopo": "LINZ Topo50",
|
"linzTopo": "LINZ Topo50",
|
||||||
"swisstopoRaster": "swisstopo Raster",
|
"swisstopoRaster": "swisstopo Raster",
|
||||||
"swisstopoVector": "Swisstopo Vector",
|
"swisstopoVector": "swisstopo Vector",
|
||||||
"swisstopoSatellite": "swisstopo Satellite",
|
"swisstopoSatellite": "swisstopo Satellite",
|
||||||
"ignBe": "IGN Topo",
|
"ignBe": "IGN Topo",
|
||||||
"ignFrPlan": "IGN Plan",
|
"ignFrPlan": "IGN Plan",
|
||||||
@@ -318,25 +319,27 @@
|
|||||||
"ignFrSatellite": "Satellitare IGN",
|
"ignFrSatellite": "Satellitare IGN",
|
||||||
"ignEs": "IGN Topo",
|
"ignEs": "IGN Topo",
|
||||||
"ignEsSatellite": "Satellitare IGN",
|
"ignEsSatellite": "Satellitare IGN",
|
||||||
"ordnanceSurvey": "Sondaggio Ordnance",
|
"ordnanceSurvey": "Ordnance Survey",
|
||||||
"norwayTopo": "Topografisk Norgeskart 4",
|
"norwayTopo": "Topografisk Norgeskart 4",
|
||||||
"finlandTopo": "Carta topografica del vecchio Catasto svedese",
|
"finlandTopo": "Lantmäteriverket Terrängkarta",
|
||||||
"bgMountains": "BGMountains",
|
"bgMountains": "BGMountains",
|
||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
"swisstopoSlope": "Carta topografica Svizzera Pendenza",
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
"swisstopoHiking": "Carta topografica Svedese Escursione",
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoHikingClosures": "Carta topografica Svizzera Fine escursione",
|
"swisstopoSlope": "swisstopo Pendenza",
|
||||||
"swisstopoCycling": "Carta topografica Svizzera Ciclabile",
|
"swisstopoHiking": "swisstopo Escursione",
|
||||||
"swisstopoCyclingClosures": "Carta topografica Svizzera fine ciclabile",
|
"swisstopoHikingClosures": "swisstopo Fine escursione",
|
||||||
"swisstopoMountainBike": "Carta topografica Svizzera MTB",
|
"swisstopoCycling": "swisstopo Ciclabile",
|
||||||
"swisstopoMountainBikeClosures": "Carta topografica Svizzera fine MTB",
|
"swisstopoCyclingClosures": "swisstopo Fine ciclabile",
|
||||||
"swisstopoSkiTouring": "Carta topografica Svizzera pista sci",
|
"swisstopoMountainBike": "swisstopo MTB",
|
||||||
|
"swisstopoMountainBikeClosures": "swisstopo Fine MTB",
|
||||||
|
"swisstopoSkiTouring": "swisstopo Sci Alpinismo",
|
||||||
"ignFrCadastre": "IGN Catasto",
|
"ignFrCadastre": "IGN Catasto",
|
||||||
"ignSlope": "Pendenza IGN",
|
"ignSlope": "IGN Pendenza",
|
||||||
"ignSkiTouring": "IGN Sciescursionismo",
|
"ignSkiTouring": "IGN Sci Alpinismo",
|
||||||
"waymarked_trails": "Waymarked Trails",
|
"waymarked_trails": "Sentieri Segnalati",
|
||||||
"waymarkedTrailsHiking": "Escursionismo",
|
"waymarkedTrailsHiking": "Escursionismo",
|
||||||
"waymarkedTrailsCycling": "Ciclismo",
|
"waymarkedTrailsCycling": "Ciclismo",
|
||||||
"waymarkedTrailsMTB": "MTB",
|
"waymarkedTrailsMTB": "MTB",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Stazione ferroviaria",
|
"railway-station": "Stazione ferroviaria",
|
||||||
"tram-stop": "Fermata del tram",
|
"tram-stop": "Fermata del tram",
|
||||||
"bus-stop": "Fermata dell'autobus",
|
"bus-stop": "Fermata dell'autobus",
|
||||||
"ferry": "Traghetto"
|
"ferry": "Traghetto",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
@@ -406,11 +411,11 @@
|
|||||||
"feet": "ft",
|
"feet": "ft",
|
||||||
"kilometers": "km",
|
"kilometers": "km",
|
||||||
"miles": "mi",
|
"miles": "mi",
|
||||||
"nautical_miles": "nm",
|
"nautical_miles": "NM",
|
||||||
"celsius": "°C",
|
"celsius": "°C",
|
||||||
"fahrenheit": "°F",
|
"fahrenheit": "°F",
|
||||||
"kilometers_per_hour": "km/h",
|
"kilometers_per_hour": "km/h",
|
||||||
"miles_per_hour": "mph",
|
"miles_per_hour": "mi/h",
|
||||||
"minutes_per_kilometer": "min/km",
|
"minutes_per_kilometer": "min/km",
|
||||||
"minutes_per_mile": "min/mi",
|
"minutes_per_mile": "min/mi",
|
||||||
"minutes_per_nautical_mile": "min/nm",
|
"minutes_per_nautical_mile": "min/nm",
|
||||||
@@ -426,8 +431,8 @@
|
|||||||
"tracks": "Tracce",
|
"tracks": "Tracce",
|
||||||
"segment": "Segmento",
|
"segment": "Segmento",
|
||||||
"segments": "Segmenti",
|
"segments": "Segmenti",
|
||||||
"waypoint": "Punto di interesse",
|
"waypoint": "Punto d'interesse",
|
||||||
"waypoints": "Punti di interesse",
|
"waypoints": "Punti d'interesse",
|
||||||
"symbol": {
|
"symbol": {
|
||||||
"alert": "Avviso",
|
"alert": "Avviso",
|
||||||
"anchor": "Ancora",
|
"anchor": "Ancora",
|
||||||
@@ -483,13 +488,13 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"contribute": "Contribuire",
|
"contribute": "Contribuire",
|
||||||
"supported_by": "supportato da",
|
"supported_by": "supportato da",
|
||||||
"support_button": "Supporto di gpx.studio su Ko-fi",
|
"support_button": "Supporta gpx.studio su Ko-fi",
|
||||||
"route_planning": "Pianificazione del percorso",
|
"route_planning": "Pianificazione del percorso",
|
||||||
"route_planning_description": "Un'interfaccia intuitiva per creare itinerari su misura per ogni sport, basati sui dati OpenStreetMap.",
|
"route_planning_description": "Un'interfaccia intuitiva per creare itinerari su misura per ogni sport, basata sui dati OpenStreetMap.",
|
||||||
"file_processing": "Elaborazione avanzata dei file",
|
"file_processing": "Elaborazione avanzata dei file",
|
||||||
"file_processing_description": "Una serie di strumenti per eseguire tutte le attività comuni di elaborazione dei file e che possono essere applicati a più file contemporaneamente.",
|
"file_processing_description": "Una serie di strumenti per eseguire tutte le attività comuni di elaborazione dei file e che possono essere applicati a più file contemporaneamente.",
|
||||||
"maps": "Mappe globali e locali",
|
"maps": "Mappe globali e locali",
|
||||||
"maps_description": "Una vasta collezione di mappe di base, sovrapposizioni e punti d'interesse per aiutarti a creare la tua prossima avventura all'aperto o visualizzare il tuo ultimo risultato.",
|
"maps_description": "Una vasta collezione di mappe di base, sovrapposizioni e punti d'interesse per aiutarti a creare la tua prossima avventura all'aperto o visualizzare la tua ultima impresa.",
|
||||||
"data_visualization": "Visualizzazione dei dati",
|
"data_visualization": "Visualizzazione dei dati",
|
||||||
"data_visualization_description": "Un profilo di elevazione interattivo con statistiche dettagliate per analizzare attività registrate e obiettivi futuri.",
|
"data_visualization_description": "Un profilo di elevazione interattivo con statistiche dettagliate per analizzare attività registrate e obiettivi futuri.",
|
||||||
"identity": "Gratuito, senza pubblicità e open source",
|
"identity": "Gratuito, senza pubblicità e open source",
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "레이어 갱신"
|
"update": "레이어 갱신"
|
||||||
},
|
},
|
||||||
"opacity": "오버레이 투명도",
|
"opacity": "오버레이 투명도",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "배경 지도",
|
"basemaps": "배경 지도",
|
||||||
"overlays": "오버레이",
|
"overlays": "오버레이",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "철도역",
|
"railway-station": "철도역",
|
||||||
"tram-stop": "트램 정류장",
|
"tram-stop": "트램 정류장",
|
||||||
"bus-stop": "버스 정류장",
|
"bus-stop": "버스 정류장",
|
||||||
"ferry": "페리"
|
"ferry": "페리",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Naujinti sluoksnį"
|
"update": "Naujinti sluoksnį"
|
||||||
},
|
},
|
||||||
"opacity": "Sluoksnio skaidrumas",
|
"opacity": "Sluoksnio skaidrumas",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Pagrindo žemėlapiai",
|
"basemaps": "Pagrindo žemėlapiai",
|
||||||
"overlays": "Sluoksniai",
|
"overlays": "Sluoksniai",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Geležinkelio stotis",
|
"railway-station": "Geležinkelio stotis",
|
||||||
"tram-stop": "Tramvajaus stotelė",
|
"tram-stop": "Tramvajaus stotelė",
|
||||||
"bus-stop": "Autobusų stotelė",
|
"bus-stop": "Autobusų stotelė",
|
||||||
"ferry": "Keltas"
|
"ferry": "Keltas",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Railway Station",
|
"railway-station": "Railway Station",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Bus Stop",
|
"bus-stop": "Bus Stop",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update laag"
|
"update": "Update laag"
|
||||||
},
|
},
|
||||||
"opacity": "Laag Transparantie",
|
"opacity": "Laag Transparantie",
|
||||||
|
"terrain": "Terrein bron",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basis kaarten",
|
"basemaps": "Basis kaarten",
|
||||||
"overlays": "Lagen",
|
"overlays": "Lagen",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Grind",
|
"bikerouterGravel": "bikerouter.de Grind",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Helling",
|
"swisstopoSlope": "swisstopo Helling",
|
||||||
"swisstopoHiking": "swisstopo Wandelen",
|
"swisstopoHiking": "swisstopo Wandelen",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Sluiting",
|
"swisstopoHikingClosures": "swisstopo Hiking Sluiting",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Treinstation",
|
"railway-station": "Treinstation",
|
||||||
"tram-stop": "Tramhalte",
|
"tram-stop": "Tramhalte",
|
||||||
"bus-stop": "Bushalte",
|
"bus-stop": "Bushalte",
|
||||||
"ferry": "Veerboot"
|
"ferry": "Veerboot",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Oppdater lag"
|
"update": "Oppdater lag"
|
||||||
},
|
},
|
||||||
"opacity": "Gjennomsiktighet for overlegg",
|
"opacity": "Gjennomsiktighet for overlegg",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basiskart",
|
"basemaps": "Basiskart",
|
||||||
"overlays": "Overlag",
|
"overlays": "Overlag",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "sykkelrute Grus",
|
"bikerouterGravel": "sykkelrute Grus",
|
||||||
"cyclOSMlite": "SyklOSM Lite",
|
"cyclOSMlite": "SyklOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopografisk helningskart",
|
"swisstopoSlope": "swisstopografisk helningskart",
|
||||||
"swisstopoHiking": "swisstopografisk Fottur",
|
"swisstopoHiking": "swisstopografisk Fottur",
|
||||||
"swisstopoHikingClosures": "swisstopografi Stengte turstier",
|
"swisstopoHikingClosures": "swisstopografi Stengte turstier",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Jernbanestasjon",
|
"railway-station": "Jernbanestasjon",
|
||||||
"tram-stop": "Trikkestopp",
|
"tram-stop": "Trikkestopp",
|
||||||
"bus-stop": "Bussholdeplass",
|
"bus-stop": "Bussholdeplass",
|
||||||
"ferry": "Ferge"
|
"ferry": "Ferge",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Zaktualizuj warstwę"
|
"update": "Zaktualizuj warstwę"
|
||||||
},
|
},
|
||||||
"opacity": "Przezroczystość nakładki",
|
"opacity": "Przezroczystość nakładki",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Mapy bazowe",
|
"basemaps": "Mapy bazowe",
|
||||||
"overlays": "Nakładki",
|
"overlays": "Nakładki",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Stoki",
|
"swisstopoSlope": "swisstopo Stoki",
|
||||||
"swisstopoHiking": "swisstopo Szlaki Turystyczne",
|
"swisstopoHiking": "swisstopo Szlaki Turystyczne",
|
||||||
"swisstopoHikingClosures": "swisstopo Zamknięcia Szlaków",
|
"swisstopoHikingClosures": "swisstopo Zamknięcia Szlaków",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Stacja kolejowa",
|
"railway-station": "Stacja kolejowa",
|
||||||
"tram-stop": "Przystanek tramwajowy",
|
"tram-stop": "Przystanek tramwajowy",
|
||||||
"bus-stop": "Przystanek autobusowy",
|
"bus-stop": "Przystanek autobusowy",
|
||||||
"ferry": "Prom"
|
"ferry": "Prom",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Atualizar camada"
|
"update": "Atualizar camada"
|
||||||
},
|
},
|
||||||
"opacity": "Opacidade de sobreposição",
|
"opacity": "Opacidade de sobreposição",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Mapa base",
|
"basemaps": "Mapa base",
|
||||||
"overlays": "Sobreposições",
|
"overlays": "Sobreposições",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Estações ferroviárias",
|
"railway-station": "Estações ferroviárias",
|
||||||
"tram-stop": "Parada de bonde",
|
"tram-stop": "Parada de bonde",
|
||||||
"bus-stop": "Parada de Ônibus",
|
"bus-stop": "Parada de Ônibus",
|
||||||
"ferry": "Balsa"
|
"ferry": "Balsa",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Atualizar camada"
|
"update": "Atualizar camada"
|
||||||
},
|
},
|
||||||
"opacity": "Opacidade da sobreposição",
|
"opacity": "Opacidade da sobreposição",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Mapas base",
|
"basemaps": "Mapas base",
|
||||||
"overlays": "Sobreposições",
|
"overlays": "Sobreposições",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Estações ferroviárias",
|
"railway-station": "Estações ferroviárias",
|
||||||
"tram-stop": "Parada de bonde",
|
"tram-stop": "Parada de bonde",
|
||||||
"bus-stop": "Parada de Ônibus",
|
"bus-stop": "Parada de Ônibus",
|
||||||
"ferry": "Balsa"
|
"ferry": "Balsa",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Actualizează stratul"
|
"update": "Actualizează stratul"
|
||||||
},
|
},
|
||||||
"opacity": "Opacitatea overlay-ului",
|
"opacity": "Opacitatea overlay-ului",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Hărți de bază",
|
"basemaps": "Hărți de bază",
|
||||||
"overlays": "Suprapuneri",
|
"overlays": "Suprapuneri",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Gară",
|
"railway-station": "Gară",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Stație de autobuz",
|
"bus-stop": "Stație de autobuz",
|
||||||
"ferry": "Feribot"
|
"ferry": "Feribot",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Обновить слой"
|
"update": "Обновить слой"
|
||||||
},
|
},
|
||||||
"opacity": "Прозрачность наложения",
|
"opacity": "Прозрачность наложения",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Основные карты",
|
"basemaps": "Основные карты",
|
||||||
"overlays": "Наложения",
|
"overlays": "Наложения",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Железнодорожная станция",
|
"railway-station": "Железнодорожная станция",
|
||||||
"tram-stop": "Трамвайная остановка",
|
"tram-stop": "Трамвайная остановка",
|
||||||
"bus-stop": "Автобусная остановка",
|
"bus-stop": "Автобусная остановка",
|
||||||
"ferry": "Паром"
|
"ferry": "Паром",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Ažurirajte sloj"
|
"update": "Ažurirajte sloj"
|
||||||
},
|
},
|
||||||
"opacity": "Providnost preklapanja",
|
"opacity": "Providnost preklapanja",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Osnovne mape",
|
"basemaps": "Osnovne mape",
|
||||||
"overlays": "Preklapanja",
|
"overlays": "Preklapanja",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Železnička stanica",
|
"railway-station": "Železnička stanica",
|
||||||
"tram-stop": "Tramvajsko stajalište",
|
"tram-stop": "Tramvajsko stajalište",
|
||||||
"bus-stop": "Autobusko stajalište",
|
"bus-stop": "Autobusko stajalište",
|
||||||
"ferry": "Trajekt"
|
"ferry": "Trajekt",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Baskartor",
|
"basemaps": "Baskartor",
|
||||||
"overlays": "Lager",
|
"overlays": "Lager",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Järnvägsstation",
|
"railway-station": "Järnvägsstation",
|
||||||
"tram-stop": "Spårvagnshållplats",
|
"tram-stop": "Spårvagnshållplats",
|
||||||
"bus-stop": "Busshållplats",
|
"bus-stop": "Busshållplats",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Railway Station",
|
"railway-station": "Railway Station",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Bus Stop",
|
"bus-stop": "Bus Stop",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Katman güncelle"
|
"update": "Katman güncelle"
|
||||||
},
|
},
|
||||||
"opacity": "Katman şeffaflığı",
|
"opacity": "Katman şeffaflığı",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Temel haritalar",
|
"basemaps": "Temel haritalar",
|
||||||
"overlays": "Katmanlar",
|
"overlays": "Katmanlar",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Eğim",
|
"swisstopoSlope": "swisstopo Eğim",
|
||||||
"swisstopoHiking": "swisstopo Yürüyüş",
|
"swisstopoHiking": "swisstopo Yürüyüş",
|
||||||
"swisstopoHikingClosures": "swisstopo Yürüyüş Sonu",
|
"swisstopoHikingClosures": "swisstopo Yürüyüş Sonu",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Tren istasyonu",
|
"railway-station": "Tren istasyonu",
|
||||||
"tram-stop": "Tramvay Durağı",
|
"tram-stop": "Tramvay Durağı",
|
||||||
"bus-stop": "Otobüs Durağı",
|
"bus-stop": "Otobüs Durağı",
|
||||||
"ferry": "Feribot"
|
"ferry": "Feribot",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Оновити шар"
|
"update": "Оновити шар"
|
||||||
},
|
},
|
||||||
"opacity": "Непрозорість накладання",
|
"opacity": "Непрозорість накладання",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Базові карти",
|
"basemaps": "Базові карти",
|
||||||
"overlays": "Накладання",
|
"overlays": "Накладання",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Залізнична Станція",
|
"railway-station": "Залізнична Станція",
|
||||||
"tram-stop": "Трамвайна Зупинка",
|
"tram-stop": "Трамвайна Зупинка",
|
||||||
"bus-stop": "Автобусна Зупинка",
|
"bus-stop": "Автобусна Зупинка",
|
||||||
"ferry": "Пором"
|
"ferry": "Пором",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Railway Station",
|
"railway-station": "Railway Station",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Bus Stop",
|
"bus-stop": "Bus Stop",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "Update layer"
|
"update": "Update layer"
|
||||||
},
|
},
|
||||||
"opacity": "Overlay opacity",
|
"opacity": "Overlay opacity",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "Basemaps",
|
"basemaps": "Basemaps",
|
||||||
"overlays": "Overlays",
|
"overlays": "Overlays",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "swisstopo Slope",
|
"swisstopoSlope": "swisstopo Slope",
|
||||||
"swisstopoHiking": "swisstopo Hiking",
|
"swisstopoHiking": "swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "Railway Station",
|
"railway-station": "Railway Station",
|
||||||
"tram-stop": "Tram Stop",
|
"tram-stop": "Tram Stop",
|
||||||
"bus-stop": "Bus Stop",
|
"bus-stop": "Bus Stop",
|
||||||
"ferry": "Ferry"
|
"ferry": "Ferry",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
@@ -282,6 +282,7 @@
|
|||||||
"update": "更新图层"
|
"update": "更新图层"
|
||||||
},
|
},
|
||||||
"opacity": "图层透明度",
|
"opacity": "图层透明度",
|
||||||
|
"terrain": "Terrain source",
|
||||||
"label": {
|
"label": {
|
||||||
"basemaps": "底图",
|
"basemaps": "底图",
|
||||||
"overlays": "叠加层",
|
"overlays": "叠加层",
|
||||||
@@ -325,6 +326,8 @@
|
|||||||
"usgs": "USGS",
|
"usgs": "USGS",
|
||||||
"bikerouterGravel": "bikerouter.de Gravel",
|
"bikerouterGravel": "bikerouter.de Gravel",
|
||||||
"cyclOSMlite": "CyclOSM Lite",
|
"cyclOSMlite": "CyclOSM Lite",
|
||||||
|
"mapterhornHillshade": "Mapterhorn Hillshade",
|
||||||
|
"openRailwayMap": "OpenRailwayMap",
|
||||||
"swisstopoSlope": "Swisstopo Slope",
|
"swisstopoSlope": "Swisstopo Slope",
|
||||||
"swisstopoHiking": "Swisstopo Hiking",
|
"swisstopoHiking": "Swisstopo Hiking",
|
||||||
"swisstopoHikingClosures": "Swisstopo Hiking Closures",
|
"swisstopoHikingClosures": "Swisstopo Hiking Closures",
|
||||||
@@ -377,7 +380,9 @@
|
|||||||
"railway-station": "火车站",
|
"railway-station": "火车站",
|
||||||
"tram-stop": "有轨电车站",
|
"tram-stop": "有轨电车站",
|
||||||
"bus-stop": "小型公交站台",
|
"bus-stop": "小型公交站台",
|
||||||
"ferry": "渡口"
|
"ferry": "渡口",
|
||||||
|
"mapbox-dem": "Mapbox DEM",
|
||||||
|
"mapterhorn": "Mapterhorn"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
|
|||||||
Reference in New Issue
Block a user