67 lines
2.3 KiB
Svelte
67 lines
2.3 KiB
Svelte
<script>
|
|
// Renders a simple SVG elevation profile from a Brouter GeoJSON route feature.
|
|
let { route } = $props();
|
|
|
|
const PAD = { top: 8, right: 8, bottom: 20, left: 36 };
|
|
const H = 140;
|
|
|
|
let svgEl;
|
|
let W = $state(400);
|
|
|
|
$effect(() => {
|
|
if (!svgEl) return;
|
|
const ro = new ResizeObserver(e => { W = e[0].contentRect.width; });
|
|
ro.observe(svgEl);
|
|
return () => ro.disconnect();
|
|
});
|
|
|
|
let profile = $derived.by(() => {
|
|
if (!route) return null;
|
|
const coords = route.geometry?.coordinates ?? [];
|
|
if (coords.length < 2) return null;
|
|
|
|
// Build cumulative distance + elevation arrays
|
|
const pts = [];
|
|
let dist = 0;
|
|
for (let i = 0; i < coords.length; i++) {
|
|
if (i > 0) {
|
|
const dx = coords[i][0] - coords[i-1][0];
|
|
const dy = coords[i][1] - coords[i-1][1];
|
|
dist += Math.sqrt(dx*dx + dy*dy) * 111320; // rough metres
|
|
}
|
|
pts.push({ d: dist, e: coords[i][2] ?? 0 });
|
|
}
|
|
|
|
const totalDist = pts[pts.length - 1].d;
|
|
const eles = pts.map(p => p.e);
|
|
const minE = Math.min(...eles);
|
|
const maxE = Math.max(...eles);
|
|
const rangeE = maxE - minE || 1;
|
|
|
|
const iW = W - PAD.left - PAD.right;
|
|
const iH = H - PAD.top - PAD.bottom;
|
|
|
|
const toX = d => PAD.left + (d / totalDist) * iW;
|
|
const toY = e => PAD.top + iH - ((e - minE) / rangeE) * iH;
|
|
|
|
const polyline = pts.map(p => `${toX(p.d).toFixed(1)},${toY(p.e).toFixed(1)}`).join(' ');
|
|
const area = `${toX(0)},${(PAD.top + iH).toFixed(1)} ` + polyline + ` ${toX(totalDist)},${(PAD.top + iH).toFixed(1)}`;
|
|
|
|
return { polyline, area, minE, maxE, totalDist, iW, iH };
|
|
});
|
|
</script>
|
|
|
|
<svg bind:this={svgEl} width="100%" height={H} style="display:block">
|
|
{#if profile}
|
|
<!-- area fill -->
|
|
<polygon points={profile.area} fill="#e879a022" />
|
|
<!-- line -->
|
|
<polyline points={profile.polyline} fill="none" stroke="#e879a0" stroke-width="1.5" />
|
|
<!-- y labels -->
|
|
<text x={PAD.left - 4} y={PAD.top + 4} text-anchor="end" font-size="9" fill="#71717a">{profile.maxE.toFixed(0)}m</text>
|
|
<text x={PAD.left - 4} y={PAD.top + profile.iH} text-anchor="end" font-size="9" fill="#71717a">{profile.minE.toFixed(0)}m</text>
|
|
<!-- x label -->
|
|
<text x={PAD.left + profile.iW / 2} y={H - 4} text-anchor="middle" font-size="9" fill="#71717a">{(profile.totalDist / 1000).toFixed(1)} km</text>
|
|
{/if}
|
|
</svg>
|