mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2025-09-01 08:12:32 +00:00
multiple axes
This commit is contained in:
@@ -297,31 +297,17 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
statistics.speed.total = statistics.distance.total / (statistics.time.total / 3600);
|
statistics.speed.total = statistics.distance.total / (statistics.time.total / 3600);
|
||||||
statistics.speed.moving = statistics.distance.moving / (statistics.time.moving / 3600);
|
statistics.speed.moving = statistics.distance.moving / (statistics.time.moving / 3600);
|
||||||
|
|
||||||
|
trkptStatistics.speed = distanceWindowSmoothingWithDistanceAccumulator(points, 200, (accumulated, start, end) => 3600 * accumulated / (points[end].time.getTime() - points[start].time.getTime()));
|
||||||
|
|
||||||
this.trkptStatistics = trkptStatistics;
|
this.trkptStatistics = trkptStatistics;
|
||||||
|
|
||||||
return statistics;
|
return statistics;
|
||||||
}
|
}
|
||||||
|
|
||||||
computeSmoothedElevation(): number[] {
|
computeSmoothedElevation(): number[] {
|
||||||
const ELEVATION_SMOOTHING_DISTANCE_THRESHOLD = 100;
|
|
||||||
|
|
||||||
let smoothed = [];
|
|
||||||
|
|
||||||
const points = this.trkpt;
|
const points = this.trkpt;
|
||||||
|
|
||||||
let start = 0, end = -1, cumul = 0;
|
let smoothed = distanceWindowSmoothing(points, 100, (index) => points[index].ele, (accumulated, start, end) => accumulated / (end - start + 1));
|
||||||
for (var i = 0; i < points.length; i++) {
|
|
||||||
while (start < i && distance(points[start].getCoordinates(), points[i].getCoordinates()) > ELEVATION_SMOOTHING_DISTANCE_THRESHOLD) {
|
|
||||||
cumul -= points[start].ele;
|
|
||||||
start++;
|
|
||||||
}
|
|
||||||
while (end + 1 < points.length && distance(points[i].getCoordinates(), points[end + 1].getCoordinates()) <= ELEVATION_SMOOTHING_DISTANCE_THRESHOLD) {
|
|
||||||
cumul += points[end + 1].ele;
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
|
|
||||||
smoothed.push(cumul / (end - start + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (points.length > 0) {
|
if (points.length > 0) {
|
||||||
smoothed[0] = points[0].ele;
|
smoothed[0] = points[0].ele;
|
||||||
@@ -332,26 +318,9 @@ export class TrackSegment extends GPXTreeLeaf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
computeSlope(): number[] {
|
computeSlope(): number[] {
|
||||||
let slope = [];
|
|
||||||
|
|
||||||
const SLOPE_DISTANCE_THRESHOLD = 100;
|
|
||||||
|
|
||||||
const points = this.trkpt;
|
const points = this.trkpt;
|
||||||
|
|
||||||
let start = 0, end = 0, windowDistance = 0;
|
return distanceWindowSmoothingWithDistanceAccumulator(points, 250, (accumulated, start, end) => accumulated > 1e-3 ? 100 * (points[end].ele - points[start].ele) / accumulated : 0);
|
||||||
for (var i = 0; i < points.length; i++) {
|
|
||||||
while (start < i && distance(points[start].getCoordinates(), points[i].getCoordinates()) > SLOPE_DISTANCE_THRESHOLD) {
|
|
||||||
windowDistance -= distance(points[start].getCoordinates(), points[start + 1].getCoordinates());
|
|
||||||
start++;
|
|
||||||
}
|
|
||||||
while (end + 1 < points.length && distance(points[i].getCoordinates(), points[end + 1].getCoordinates()) <= SLOPE_DISTANCE_THRESHOLD) {
|
|
||||||
windowDistance += distance(points[end].getCoordinates(), points[end + 1].getCoordinates());
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
slope[i] = windowDistance > 1e-3 ? 100 * (points[end].ele - points[start].ele) / windowDistance : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return slope;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reverse(originalNextTimestamp: Date | undefined, newPreviousTimestamp: Date | undefined): void {
|
reverse(originalNextTimestamp: Date | undefined, newPreviousTimestamp: Date | undefined): void {
|
||||||
@@ -528,4 +497,31 @@ function distance(coord1: Coordinates, coord2: Coordinates): number {
|
|||||||
const a = Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos((coord2.lon - coord1.lon) * rad);
|
const a = Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos((coord2.lon - coord1.lon) * rad);
|
||||||
const maxMeters = earthRadius * Math.acos(Math.min(a, 1));
|
const maxMeters = earthRadius * Math.acos(Math.min(a, 1));
|
||||||
return maxMeters;
|
return maxMeters;
|
||||||
|
}
|
||||||
|
|
||||||
|
function distanceWindowSmoothing(points: TrackPoint[], distanceWindow: number, accumulate: (index: number) => number, compute: (accumulated: number, start: number, end: number) => number, remove?: (index: number) => number): number[] {
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
let start = 0, end = 0, accumulated = 0;
|
||||||
|
for (var i = 0; i < points.length; i++) {
|
||||||
|
while (start < i && distance(points[start].getCoordinates(), points[i].getCoordinates()) > distanceWindow) {
|
||||||
|
if (remove) {
|
||||||
|
accumulated -= remove(start);
|
||||||
|
} else {
|
||||||
|
accumulated -= accumulate(start);
|
||||||
|
}
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
while (end < points.length && distance(points[i].getCoordinates(), points[end].getCoordinates()) <= distanceWindow) {
|
||||||
|
accumulated += accumulate(end);
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
result[i] = compute(accumulated, start, end - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function distanceWindowSmoothingWithDistanceAccumulator(points: TrackPoint[], distanceWindow: number, compute: (accumulated: number, start: number, end: number) => number): number[] {
|
||||||
|
return distanceWindowSmoothing(points, distanceWindow, (index) => index > 0 ? distance(points[index - 1].getCoordinates(), points[index].getCoordinates()) : 0, compute, (index) => distance(points[index].getCoordinates(), points[index + 1].getCoordinates()));
|
||||||
}
|
}
|
@@ -34,25 +34,50 @@
|
|||||||
display: true,
|
display: true,
|
||||||
text: 'Elevation (m)'
|
text: 'Elevation (m)'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
y1: {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'right',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Speed (km/h)'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
y2: {
|
||||||
|
type: 'linear',
|
||||||
|
position: 'right',
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Slope (%)'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
datasets: {
|
datasets: {
|
||||||
line: {
|
line: {
|
||||||
pointRadius: 0
|
pointRadius: 0,
|
||||||
|
tension: 0.4
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
interaction: {
|
interaction: {
|
||||||
mode: 'index',
|
mode: 'nearest',
|
||||||
|
axis: 'x',
|
||||||
intersect: false
|
intersect: false
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
position: 'right'
|
display: false
|
||||||
},
|
},
|
||||||
decimation: {
|
decimation: {
|
||||||
enabled: true
|
enabled: true
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
stacked: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -69,7 +94,30 @@
|
|||||||
y: point.ele ? point.ele : 0
|
y: point.ele ? point.ele : 0
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
normalized: true
|
normalized: true,
|
||||||
|
fill: true
|
||||||
|
};
|
||||||
|
chart.data.datasets[1] = {
|
||||||
|
label: 'Speed',
|
||||||
|
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||||
|
return {
|
||||||
|
x: trackPointsAndStatistics.statistics.distance[index],
|
||||||
|
y: trackPointsAndStatistics.statistics.speed[index]
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
normalized: true,
|
||||||
|
yAxisID: 'y1'
|
||||||
|
};
|
||||||
|
chart.data.datasets[2] = {
|
||||||
|
label: 'Slope',
|
||||||
|
data: trackPointsAndStatistics.points.map((point, index) => {
|
||||||
|
return {
|
||||||
|
x: trackPointsAndStatistics.statistics.distance[index],
|
||||||
|
y: trackPointsAndStatistics.statistics.slope[index]
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
normalized: true,
|
||||||
|
yAxisID: 'y2'
|
||||||
};
|
};
|
||||||
chart.options.scales.x['min'] = 0;
|
chart.options.scales.x['min'] = 0;
|
||||||
chart.options.scales.x['max'] = file.statistics.distance.total;
|
chart.options.scales.x['max'] = file.statistics.distance.total;
|
||||||
@@ -79,6 +127,6 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-full grow min-w-0 p-4">
|
<div class="h-full grow min-w-0 py-4">
|
||||||
<canvas bind:this={canvas}> </canvas>
|
<canvas bind:this={canvas}> </canvas>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -29,7 +29,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card.Root class="h-full overflow-hidden border-none">
|
<Card.Root class="h-full overflow-hidden border-none">
|
||||||
<Card.Content class="h-full flex flex-col flex-wrap gap-4 p-4 justify-center">
|
<Card.Content class="h-full flex flex-col flex-wrap gap-4 justify-center">
|
||||||
<GPXDataItem>
|
<GPXDataItem>
|
||||||
<span slot="data" class="flex flex-row items-center">
|
<span slot="data" class="flex flex-row items-center">
|
||||||
<Ruler size="18" class="mr-1" />
|
<Ruler size="18" class="mr-1" />
|
||||||
|
Reference in New Issue
Block a user