add highway info to elevation profile, closes #65

This commit is contained in:
vcoppe
2024-10-03 18:14:01 +02:00
parent 0c16ddd534
commit d7a02f714a
11 changed files with 225 additions and 86 deletions

View File

@@ -0,0 +1,64 @@
export const surfaceColors: { [key: string]: string } = {
"missing": "#d1d1d1",
"paved": "#8c8c8c",
"unpaved": "#6b443a",
"asphalt": "#8c8c8c",
"concrete": "#8c8c8c",
"cobblestone": "#ffd991",
"paving_stones": "#8c8c8c",
"sett": "#ffd991",
"metal": "#8c8c8c",
"wood": "#6b443a",
"compacted": "#ffffa8",
"fine_gravel": "#ffffa8",
"gravel": "#ffffa8",
"pebblestone": "#ffffa8",
"rock": "#ffd991",
"dirt": "#ffffa8",
"ground": "#6b443a",
"earth": "#6b443a",
"mud": "#6b443a",
"sand": "#ffffc4",
"grass": "#61b55c",
"grass_paver": "#61b55c",
"clay": "#6b443a",
"stone": "#ffd991",
};
export const highwayColors: { [key: string]: string } = {
"missing": "#d1d1d1",
"residential": "#73b2ff",
"service": "#b3b3cc",
"track": "#946f43",
"unclassified": "#e0e0e0",
"footway": "#a3c989",
"tertiary": "#ffdd7f",
"path": "#a3c989",
"secondary": "#ffd75f",
"primary": "#ff6e5c",
"cycleway": "#ffbb6e",
"trunk": "#ff5e4d",
"living_street": "#9de2ff",
"motorway": "#ff4d33",
"motorway_link": "#ff947f",
"steps": "#8d91a2",
"road": "#e0e0e0",
"pedestrian": "#c1c8e4",
"trunk_link": "#ff947f",
"primary_link": "#ff8d7b",
"secondary_link": "#ffcb66",
"tertiary_link": "#ffdd8b",
"construction": "#e09a4a",
"bridleway": "#a3c989",
"platform": "#c4c4c4",
"proposed": "#e0e0e0",
"raceway": "#ff0000",
"rest_area": "#73b2ff",
"abandoned": "#e0e0e0",
"services": "#ffe066",
"corridor": "#c4c4c4",
"bus_stop": "#ffe6e6",
"busway": "#ffe6e6",
"elevator": "#c4c4c4",
"via_ferrata": "#8d91a2"
};

View File

@@ -1,26 +0,0 @@
export const surfaceColors: { [key: string]: string } = {
'missing': '#d1d1d1',
'paved': '#8c8c8c',
'unpaved': '#6b443a',
'asphalt': '#8c8c8c',
'concrete': '#8c8c8c',
'cobblestone': '#ffd991',
'paving_stones': '#8c8c8c',
'sett': '#ffd991',
'metal': '#8c8c8c',
'wood': '#6b443a',
'compacted': '#ffffa8',
'fine_gravel': '#ffffa8',
'gravel': '#ffffa8',
'pebblestone': '#ffffa8',
'rock': '#ffd991',
'dirt': '#ffffa8',
'ground': '#6b443a',
'earth': '#6b443a',
'mud': '#6b443a',
'sand': '#ffffc4',
'grass': '#61b55c',
'grass_paver': '#61b55c',
'clay': '#6b443a',
'stone': '#ffd991',
}

View File

@@ -16,9 +16,10 @@
Zap,
Circle,
Check,
ChartNoAxesColumn
ChartNoAxesColumn,
Construction
} from 'lucide-svelte';
import { surfaceColors } from '$lib/assets/surfaces';
import { surfaceColors, highwayColors } from '$lib/assets/colors';
import { _, locale } from 'svelte-i18n';
import {
getCadenceWithUnits,
@@ -43,7 +44,7 @@
export let gpxStatistics: Writable<GPXStatistics>;
export let slicedGPXStatistics: Writable<[GPXStatistics, number, number] | undefined>;
export let additionalDatasets: string[];
export let elevationFill: 'slope' | 'surface' | undefined;
export let elevationFill: 'slope' | 'surface' | 'highway' | undefined;
export let showControls: boolean = true;
const { distanceUnits, velocityUnits, temperatureUnits } = settings;
@@ -151,7 +152,10 @@
segment: point.slope.segment.toFixed(1),
length: getDistanceWithUnits(point.slope.length)
};
let surface = point.surface ? point.surface : 'unknown';
let surface = point.extensions.surface ? point.extensions.surface : 'unknown';
let highway = point.extensions.highway ? point.extensions.highway : 'unknown';
let sacScale = point.extensions.sac_scale;
let mtbScale = point.extensions['mtb:scale'];
let labels = [
` ${$_('quantities.distance')}: ${getDistanceWithUnits(point.x, false)}`,
@@ -164,6 +168,17 @@
);
}
if (elevationFill === 'highway') {
labels.push(
` ${$_('quantities.highway')}: ${$_(`toolbar.routing.highway.${highway}`)}${
sacScale ? ` (${$_(`toolbar.routing.sac_scale.${sacScale}`)})` : ''
}`
);
if (mtbScale) {
labels.push(` ${$_('toolbar.routing.mtb_scale')}: ${mtbScale}`);
}
}
if (point.time) {
labels.push(` ${$_('quantities.time')}: ${df.format(point.time)}`);
}
@@ -353,7 +368,7 @@
segment: data.local.slope.segment[index],
length: data.local.slope.length[index]
},
surface: point.getSurface(),
extensions: point.getExtensions(),
coordinates: point.getCoordinates(),
index: index
};
@@ -448,10 +463,15 @@
}
function surfaceFillCallback(context) {
let surface = context.p0.raw.surface;
let surface = context.p0.raw.extensions.surface;
return surfaceColors[surface] ? surfaceColors[surface] : surfaceColors.missing;
}
function highwayFillCallback(context) {
let highway = context.p0.raw.extensions.highway;
return highwayColors[highway] ? highwayColors[highway] : highwayColors.missing;
}
$: if (chart) {
if (elevationFill === 'slope') {
chart.data.datasets[0]['segment'] = {
@@ -461,6 +481,10 @@
chart.data.datasets[0]['segment'] = {
backgroundColor: surfaceFillCallback
};
} else if (elevationFill === 'highway') {
chart.data.datasets[0]['segment'] = {
backgroundColor: highwayFillCallback
};
} else {
chart.data.datasets[0]['segment'] = {};
}
@@ -551,7 +575,7 @@
<ChartNoAxesColumn size="18" />
</ButtonWithTooltip>
</Popover.Trigger>
<Popover.Content class="w-fit p-0 flex flex-col divide-y" side="left" sideOffset={-32}>
<Popover.Content class="w-fit p-0 flex flex-col divide-y" side="top" sideOffset={-32}>
<ToggleGroup.Root
class="flex flex-col items-start gap-0 p-1"
type="single"
@@ -582,6 +606,19 @@
<BrickWall size="15" class="mr-1" />
{$_('quantities.surface')}
</ToggleGroup.Item>
<ToggleGroup.Item
class="p-0 pr-1.5 h-6 w-full rounded flex justify-start data-[state=on]:bg-background data-[state=on]:hover:bg-accent hover:bg-accent hover:text-foreground"
value="highway"
variant="outline"
>
<div class="w-6 flex justify-center items-center">
{#if elevationFill === 'highway'}
<Circle class="h-1.5 w-1.5 fill-current text-current" />
{/if}
</div>
<Construction size="15" class="mr-1" />
{$_('quantities.highway')}
</ToggleGroup.Item>
</ToggleGroup.Root>
<ToggleGroup.Root
class="flex flex-col items-start gap-0 p-1"

View File

@@ -16,7 +16,7 @@
import {
Download,
Zap,
BrickWall,
Earth,
HeartPulse,
Orbit,
Thermometer,
@@ -31,19 +31,19 @@
let open = false;
let exportOptions: Record<string, boolean> = {
time: true,
surface: true,
hr: true,
cad: true,
atemp: true,
power: true
power: true,
extensions: true
};
let hide: Record<string, boolean> = {
time: false,
surface: false,
hr: false,
cad: false,
atemp: false,
power: false
power: false,
extensions: false
};
$: if ($exportState !== ExportState.NONE) {
@@ -63,11 +63,11 @@
}
hide.time = statistics.global.time.total === 0;
hide.surface = !Object.keys(statistics.global.surface).some((key) => key !== 'unknown');
hide.hr = statistics.global.hr.count === 0;
hide.cad = statistics.global.cad.count === 0;
hide.atemp = statistics.global.atemp.count === 0;
hide.power = statistics.global.power.count === 0;
hide.extensions = Object.keys(statistics.global.extensions).length === 0;
}
$: exclude = Object.keys(exportOptions).filter((key) => !exportOptions[key]);
@@ -144,11 +144,11 @@
{$_('quantities.time')}
</Label>
</div>
<div class="flex flex-row items-center gap-1.5 {hide.surface ? 'hidden' : ''}">
<Checkbox id="export-surface" bind:checked={exportOptions.surface} />
<Label for="export-surface" class="flex flex-row items-center gap-1">
<BrickWall size="16" />
{$_('quantities.surface')}
<div class="flex flex-row items-center gap-1.5 {hide.extensions ? 'hidden' : ''}">
<Checkbox id="export-extensions" bind:checked={exportOptions.extensions} />
<Label for="export-extensions" class="flex flex-row items-center gap-1">
<Earth size="16" />
{$_('quantities.osm_extensions')}
</Label>
</div>
<div class="flex flex-row items-center gap-1.5 {hide.hr ? 'hidden' : ''}">

View File

@@ -10,7 +10,7 @@ export type EmbeddingOptions = {
show: boolean;
height: number;
controls: boolean;
fill: 'slope' | 'surface' | undefined;
fill: 'slope' | 'surface' | 'highway' | undefined;
speed: boolean;
hr: boolean;
cad: boolean;

View File

@@ -142,7 +142,7 @@
let value = selected?.value;
if (value === 'none') {
options.elevation.fill = undefined;
} else if (value === 'slope' || value === 'surface') {
} else if (value === 'slope' || value === 'surface' || value === 'highway') {
options.elevation.fill = value;
}
}}
@@ -153,6 +153,7 @@
<Select.Content>
<Select.Item value="slope">{$_('quantities.slope')}</Select.Item>
<Select.Item value="surface">{$_('quantities.surface')}</Select.Item>
<Select.Item value="highway">{$_('quantities.highway')}</Select.Item>
<Select.Item value="none">{$_('embedding.none')}</Select.Item>
</Select.Content>
</Select.Root>

View File

@@ -65,7 +65,7 @@ async function getRoute(points: Coordinates[], brouterProfile: string, privateRo
const latIdx = messages[0].indexOf("Latitude");
const tagIdx = messages[0].indexOf("WayTags");
let messageIdx = 1;
let surface = messageIdx < messages.length ? getSurface(messages[messageIdx][tagIdx]) : undefined;
let tags = messageIdx < messages.length ? getTags(messages[messageIdx][tagIdx]) : {};
for (let i = 0; i < coordinates.length; i++) {
let coord = coordinates[i];
@@ -82,25 +82,25 @@ async function getRoute(points: Coordinates[], brouterProfile: string, privateRo
coordinates[i][1] == Number(messages[messageIdx][latIdx]) / 1000000) {
messageIdx++;
if (messageIdx == messages.length) surface = undefined;
else surface = getSurface(messages[messageIdx][tagIdx]);
if (messageIdx == messages.length) tags = {};
else tags = getTags(messages[messageIdx][tagIdx]);
}
if (surface) {
route[route.length - 1].setSurface(surface);
}
route[route.length - 1].setExtensions(tags);
}
return route;
}
function getSurface(message: string): string | undefined {
function getTags(message: string): { [key: string]: string } {
const fields = message.split(" ");
for (let i = 0; i < fields.length; i++) if (fields[i].startsWith("surface=")) {
return fields[i].substring(8);
let tags: { [key: string]: string } = {};
for (let i = 0; i < fields.length; i++) {
let tag = fields[i].split("=");
tags[tag[0]] = tag[1];
}
return undefined;
};
return tags;
}
function getIntermediatePoints(points: Coordinates[]): Promise<TrackPoint[]> {
let route: TrackPoint[] = [];

View File

@@ -3,7 +3,7 @@ title: Files and statistics
---
<script>
import { TriangleRight, BrickWall, Zap, HeartPulse, Orbit, Thermometer, SquareActivity } from 'lucide-svelte';
import { ChartNoAxesColumn } from 'lucide-svelte';
import DocsNote from '$lib/components/docs/DocsNote.svelte';
</script>
@@ -73,9 +73,9 @@ You can also use the mouse wheel to zoom in and out on the elevation profile, an
### Additional data
Using the button at the bottom-right of the elevation profile, you can optionally color the elevation profile by:
- **slope** <TriangleRight size="16" class="inline-block" style="margin-bottom: 2px" /> information computed from the elevation data, or
- **surface** <BrickWall size="16" class="inline-block" style="margin-bottom: 2px" /> data coming from <a href="https://www.openstreetmap.org/" target="_blank">OpenStreetMap</a>'s <a href="https://wiki.openstreetmap.org/wiki/Key:surface" target="_blank">surface</a> tags.
Using the <kbd><ChartNoAxesColumn size="16" class="inline-block" style="margin-bottom: 2px"/></kbd> button at the bottom-right of the elevation profile, you can optionally color the elevation profile by:
- **slope** information computed from the elevation data, or
- **surface** or **category** data coming from <a href="https://www.openstreetmap.org/" target="_blank">OpenStreetMap</a>'s <a href="https://wiki.openstreetmap.org/wiki/Key:surface" target="_blank">surface</a> and <a href="https://wiki.openstreetmap.org/wiki/Key:highway" target="_blank">highway</a> tags.
This is only available for files created with **gpx.studio**.
If your selection includes it, you can also visualize: **speed** <Zap size="16" class="inline-block" style="margin-bottom: 2px" />, **heart rate** <HeartPulse size="16" class="inline-block" style="margin-bottom: 2px" />, **cadence** <Orbit size="16" class="inline-block" style="margin-bottom: 2px" />, **temperature** <Thermometer size="16" class="inline-block" style="margin-bottom: 2px" />, and **power** <SquareActivity size="16" class="inline-block" style="margin-bottom: 2px" /> data on the elevation profile.
If your selection includes it, you can also visualize: **speed**, **heart rate**, **cadence**, **temperature** and **power** data on the elevation profile.

View File

@@ -139,6 +139,52 @@
"clay": "Clay",
"stone": "Stone"
},
"highway": {
"unknown": "Unknown",
"residential": "Residential",
"service": "Service",
"track": "Track",
"unclassified": "Unclassified",
"footway": "Footway",
"tertiary": "Tertiary",
"path": "Path",
"secondary": "Secondary",
"primary": "Primary",
"cycleway": "Cycleway",
"trunk": "Trunk",
"living_street": "Living Street",
"motorway": "Motorway",
"motorway_link": "Motorway Link",
"steps": "Steps",
"road": "Road",
"pedestrian": "Pedestrian",
"trunk_link": "Trunk Link",
"primary_link": "Primary Link",
"secondary_link": "Secondary Link",
"tertiary_link": "Tertiary Link",
"construction": "Construction",
"bridleway": "Bridleway",
"platform": "Platform",
"proposed": "Proposed",
"raceway": "Raceway",
"rest_area": "Rest Area",
"abandoned": "Abandoned",
"services": "Services",
"corridor": "Corridor",
"bus_stop": "Bus Stop",
"busway": "Busway",
"elevator": "Elevator",
"via_ferrata": "Via Ferrata"
},
"sac_scale": {
"hiking": "Hiking",
"mountain_hiking": "Mountain hiking",
"demanding_mountain_hiking": "Demanding mountain hiking",
"alpine_hiking": "Alpine hiking",
"demanding_alpine_hiking": "Demanding alpine hiking",
"difficult_alpine_hiking": "Difficult alpine hiking"
},
"mtb_scale": "MTB scale",
"error": {
"from": "The start point is too far from the nearest road",
"via": "The via point is too far from the nearest road",
@@ -355,9 +401,11 @@
"power": "Power",
"slope": "Slope",
"surface": "Surface",
"highway": "Category",
"time": "Time",
"moving": "Moving",
"total": "Total"
"total": "Total",
"osm_extensions": "OpenStreetMap data"
},
"units": {
"meters": "m",