Initial scaffold: Vite + Svelte, MapLibre, Brouter routing, GPX export
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user