diff --git a/src/components/Header.svelte b/src/components/Header.svelte index 33f4086..59e47ad 100644 --- a/src/components/Header.svelte +++ b/src/components/Header.svelte @@ -5,13 +5,15 @@ export interface Props { file: File | undefined; + trimSliderOpen: boolean; }
@@ -30,6 +32,9 @@ {#if file} + {/if} diff --git a/src/components/Map.svelte b/src/components/Map.svelte index f33d3b6..f648610 100644 --- a/src/components/Map.svelte +++ b/src/components/Map.svelte @@ -29,6 +29,8 @@ gpsPoints, gpsGaps, pointsOfInterest, + trimStart, + trimEnd, }: Props = $props(); let node = $state(); @@ -36,7 +38,9 @@ let basePolyline = $state(null); let segmentModalOpen = $state(false); let hiddenRideSegments = $state>(new Set()); - let polylineSegments = $derived(computeSegmentedLines(gpsGaps, gpsPoints.length, hiddenRideSegments)); + let polylineSegments = $derived( + computeSegmentedLines(gpsGaps, gpsPoints.length, hiddenRideSegments, trimStart, trimEnd), + ); let rideSegments = $derived>(new Set(polylineSegments.map((s) => s.segmentIdx))); $effect(() => { @@ -90,11 +94,18 @@ } }); - // ensure this is updated each time the visible rows change + // ensure this is updated each time the visible rows or trim range change visibleRows; - basePolyline = getBaseLine(gpsPoints, gpsGaps, hiddenRideSegments).addTo(map!); - travelledPolyline = getTravelledLine(gpsPoints, gpsGaps, selectedRowIndex, hiddenRideSegments).addTo(map!); + basePolyline = getBaseLine(gpsPoints, gpsGaps, hiddenRideSegments, trimStart, trimEnd).addTo(map!); + travelledPolyline = getTravelledLine( + gpsPoints, + gpsGaps, + selectedRowIndex, + hiddenRideSegments, + trimStart, + trimEnd, + ).addTo(map!); }); function setVisibleIndices() { @@ -107,6 +118,11 @@ ? new Array(gpsPoints.length).fill(true) : gpsPoints.map((point) => bounds.contains(point)); + // apply trim range + for (let i = 0; i < newVisiblePoints.length; i++) { + if (i < trimStart || i > trimEnd) newVisiblePoints[i] = false; + } + // hide hidden segments for (const { start, end, segmentIdx } of polylineSegments) { if (hiddenRideSegments.has(segmentIdx)) { @@ -188,9 +204,16 @@ attribution: '© OpenStreetMap', }).addTo(map); - // create lines - basePolyline = getBaseLine(gpsPoints, gpsGaps, hiddenRideSegments).addTo(map); - travelledPolyline = getTravelledLine(gpsPoints, gpsGaps, selectedRowIndex, hiddenRideSegments).addTo(map); + // create lines (respect trim range) + basePolyline = getBaseLine(gpsPoints, gpsGaps, hiddenRideSegments, trimStart, trimEnd).addTo(map); + travelledPolyline = getTravelledLine( + gpsPoints, + gpsGaps, + selectedRowIndex, + hiddenRideSegments, + trimStart, + trimEnd, + ).addTo(map); // fit ride in map const polylineBounds = basePolyline.getBounds(); diff --git a/src/components/Map.ts b/src/components/Map.ts index b42d23f..0855d63 100644 --- a/src/components/Map.ts +++ b/src/components/Map.ts @@ -21,6 +21,8 @@ export interface Props { gpsPoints: LatLngExpression[]; gpsGaps: GpsGap[]; pointsOfInterest: PointOfInterest[]; + trimStart: number; + trimEnd: number; } export { riderSvg }; @@ -141,16 +143,32 @@ export interface PolylineSegment { segmentIdx: number; } -export function computeSegmentedLines(gpsGaps: GpsGap[], maxLength: number, hiddenSegmentIndices: Set) { +export function computeSegmentedLines( + gpsGaps: GpsGap[], + maxLength: number, + hiddenSegmentIndices: Set, + trimStart = 0, + trimEnd = maxLength - 1, +) { const result: PolylineSegment[] = []; + const trimEndExclusive = trimEnd + 1; for (let segmentIdx = 0, i = 0; i < gpsGaps.length; ++i) { const { secondsElapsed, index: start } = gpsGaps[i]!; if (secondsElapsed > CHARGE_THRESHOLD_SECONDS) segmentIdx++; + const segStart = start; + const segEnd = gpsGaps[i + 1]?.index ?? maxLength; + + // clip to trim range + const clippedStart = Math.max(segStart, trimStart); + const clippedEnd = Math.min(segEnd, trimEndExclusive); + + if (clippedStart >= clippedEnd) continue; + if (!hiddenSegmentIndices.has(segmentIdx)) { result.push({ - start, - end: gpsGaps[i + 1]?.index ?? maxLength, + start: clippedStart, + end: clippedEnd, segmentIdx, }); } @@ -163,9 +181,17 @@ export function getBaseLine( gpsPoints: LatLngExpression[], gpsGaps: GpsGap[], hiddenSegmentIndices: Set, + trimStart = 0, + trimEnd = gpsPoints.length - 1, ): SegmentedPolyline { const values: { points: LatLngExpression[]; options: PolylineOptions }[] = []; - for (const { start, end, segmentIdx } of computeSegmentedLines(gpsGaps, gpsPoints.length, hiddenSegmentIndices)) { + for (const { start, end, segmentIdx } of computeSegmentedLines( + gpsGaps, + gpsPoints.length, + hiddenSegmentIndices, + trimStart, + trimEnd, + )) { values.push({ points: gpsPoints.slice(start, end), options: MapLineOptions[MapLine.Base](segmentIdx), @@ -180,15 +206,27 @@ export function getTravelledLine( gpsGaps: GpsGap[], limit: number, hiddenSegmentIndices: Set, + trimStart = 0, + trimEnd = gpsPoints.length - 1, ): SegmentedPolyline { const values: { points: LatLngExpression[]; options: PolylineOptions }[] = []; - for (const { start, end, segmentIdx } of computeSegmentedLines(gpsGaps, gpsPoints.length, hiddenSegmentIndices)) { - values.push({ - points: gpsPoints.slice(start, Math.min(limit, end)), - options: MapLineOptions[MapLine.Travelled](segmentIdx), - }); + for (const { start, end, segmentIdx } of computeSegmentedLines( + gpsGaps, + gpsPoints.length, + hiddenSegmentIndices, + trimStart, + trimEnd, + )) { + // ensure we don't include points past the selected index + const sliceEnd = Math.min(limit, end); + if (start < sliceEnd) { + values.push({ + points: gpsPoints.slice(start, sliceEnd), + options: MapLineOptions[MapLine.Travelled](segmentIdx), + }); + } - if (limit === end) { + if (limit <= end) { break; } } diff --git a/src/components/TrimSlider.svelte b/src/components/TrimSlider.svelte new file mode 100644 index 0000000..fbbdcf5 --- /dev/null +++ b/src/components/TrimSlider.svelte @@ -0,0 +1,219 @@ + + +{#if open} + +{/if} + + diff --git a/src/components/View.svelte b/src/components/View.svelte index abafdcb..1ba2e2a 100644 --- a/src/components/View.svelte +++ b/src/components/View.svelte @@ -3,6 +3,7 @@ import Map from './Map.svelte'; import Details from './Details.svelte'; import Header from './Header.svelte'; + import TrimSlider from './TrimSlider.svelte'; import { demoFile, demoRows } from '../lib/parse/float-control'; import type { DragEventHandler, EventHandler } from 'svelte/elements'; import { DataSource, type RowWithIndex } from '../lib/parse/types'; @@ -63,10 +64,23 @@ }, ); + /** whether trim slider overlay is open */ + let sliderOpen = $state(false); let banners = $state([]); - /** array-as-map of whether particular rows are visible or not */ - let visible = $state([]); + /** array-as-map of whether particular rows are visible from map/bounds */ + let visibleFromMap = $state([]); + /** trimming range start index */ + let trimStart = $state(0); + /** trimming range end index */ + let trimEnd = $derived(rows.length ? rows.length - 1 : 0); + + /** derived visible array after applying trim range to map-driven visibility */ + let visible = $derived.by(() => { + const s = Math.min(trimStart, trimEnd); + const e = Math.max(trimStart, trimEnd); + return visibleFromMap.map((v, i) => (v && i >= s && i <= e) ?? false); + }); /** filtered visible rows */ let visibleRows = $derived(rows.filter((_, i) => visible[i])); /** whether this ride appears to include footpad ADC telemetry */ @@ -92,7 +106,7 @@ let swapMapAndDetails = $state(false); const setVisible = (arrayAsMap: boolean[]) => { - visible = arrayAsMap; + visibleFromMap = arrayAsMap; if (!visible[selectedRowIndex]) { // it's not the best, but try to find a point on the line that's somewhat in the middle // this doesn't really work well if there are multiple lines going in different directions @@ -140,6 +154,10 @@ } rows = results.data; + // initialize visibility and trimming to full range for the newly loaded ride + visibleFromMap = new Array(rows.length).fill(true); + trimStart = 0; + trimEnd = rows.length ? rows.length - 1 : 0; selectedIndex = 0; stats = computeStats(rows, pointsOfInterest); @@ -236,7 +254,7 @@
{/snippet} -
+
+ + + +