feat: Display total polyline distance in menu with unit conversion
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
@@ -222,44 +222,7 @@ function PolylineWithArrows({ coords, showDirection, showPoints }) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Map({data, loading, end, duration, slider, mapState, setMapState, setSubmenu, showDirection, showPoints, setDrawnItems}) {
|
function Map({data, loading, coords, mapState, setMapState, setSubmenu, showDirection, showPoints, setDrawnItems}) {
|
||||||
const range = useMemo(() => parseSlider(end, duration, slider), [end, duration, slider]);
|
|
||||||
|
|
||||||
const coords = useMemo(() => {
|
|
||||||
// 1. Guard against invalid top-level data
|
|
||||||
if (!Array.isArray(data)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = [];
|
|
||||||
const [startTime, endTime] = range;
|
|
||||||
|
|
||||||
// 2. Loop through the data
|
|
||||||
for (const point of data) {
|
|
||||||
// 3. Guard against malformed points
|
|
||||||
if (!point || typeof point !== 'object') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { lat, lon, time } = point;
|
|
||||||
|
|
||||||
// 4. Guard against invalid time
|
|
||||||
if (typeof time !== 'string' || time.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Guard against invalid coordinates (null, undefined, NaN, non-number)
|
|
||||||
if (typeof lat !== 'number' || typeof lon !== 'number' || !isFinite(lat) || !isFinite(lon)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Now that all data is known to be valid, filter by time
|
|
||||||
if (time >= startTime && time <= endTime) {
|
|
||||||
result.push([lat, lon]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}, [data, range]);
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -342,7 +305,7 @@ function Map({data, loading, end, duration, slider, mapState, setMapState, setSu
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, submenu, setSubmenu, showDirection, setShowDirection, showPoints, setShowPoints, setMapState, shareStart, shareEnd, drawnItems}) {
|
function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, submenu, setSubmenu, showDirection, setShowDirection, showPoints, setShowPoints, setMapState, shareStart, shareEnd, drawnItems, coords}) {
|
||||||
const [showRange, setShowRange] = useState(false);
|
const [showRange, setShowRange] = useState(false);
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
const [searchResults, setSearchResults] = useState(null);
|
const [searchResults, setSearchResults] = useState(null);
|
||||||
@@ -648,6 +611,25 @@ function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, subm
|
|||||||
setSearchResults(newResults);
|
setSearchResults(newResults);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const totalDistance = useMemo(() => {
|
||||||
|
if (!coords || coords.length < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let distance = 0;
|
||||||
|
for (let i = 0; i < coords.length - 1; i++) {
|
||||||
|
const p1 = leaflet.latLng(coords[i]);
|
||||||
|
const p2 = leaflet.latLng(coords[i+1]);
|
||||||
|
distance += p1.distanceTo(p2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distance < 1000) {
|
||||||
|
return `${distance.toFixed(0)} m`;
|
||||||
|
} else {
|
||||||
|
return `${(distance / 1000).toFixed(2)} km`;
|
||||||
|
}
|
||||||
|
}, [coords]);
|
||||||
|
|
||||||
const range = parseSlider(end, duration, slider);
|
const range = parseSlider(end, duration, slider);
|
||||||
const startDate = moment(end).subtract(...duration.delta);
|
const startDate = moment(end).subtract(...duration.delta);
|
||||||
|
|
||||||
@@ -739,7 +721,7 @@ function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, subm
|
|||||||
return (
|
return (
|
||||||
<div className='menu'>
|
<div className='menu'>
|
||||||
{(showRange || !!submenu) && <div className='range'>
|
{(showRange || !!submenu) && <div className='range'>
|
||||||
{rangeTime(range[0])} - {rangeTime(range[1])} <span style={{ whiteSpace: 'nowrap' }}>({rangeDelta(range)})</span>
|
{rangeTime(range[0])} - {rangeTime(range[1])} <span style={{ whiteSpace: 'nowrap' }}>({rangeDelta(range)})</span>{totalDistance && <span style={{ whiteSpace: 'nowrap' }}> ({totalDistance})</span>}
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
<div className='time-slider'>
|
<div className='time-slider'>
|
||||||
@@ -968,6 +950,44 @@ function App() {
|
|||||||
|
|
||||||
const [data, loading] = useSensor('owntracks', 'OwnTracks', end, duration);
|
const [data, loading] = useSensor('owntracks', 'OwnTracks', end, duration);
|
||||||
|
|
||||||
|
const range = useMemo(() => parseSlider(end, duration, slider), [end, duration, slider]);
|
||||||
|
|
||||||
|
const coords = useMemo(() => {
|
||||||
|
// 1. Guard against invalid top-level data
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
const [startTime, endTime] = range;
|
||||||
|
|
||||||
|
// 2. Loop through the data
|
||||||
|
for (const point of data) {
|
||||||
|
// 3. Guard against malformed points
|
||||||
|
if (!point || typeof point !== 'object') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { lat, lon, time } = point;
|
||||||
|
|
||||||
|
// 4. Guard against invalid time
|
||||||
|
if (typeof time !== 'string' || time.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Guard against invalid coordinates (null, undefined, NaN, non-number)
|
||||||
|
if (typeof lat !== 'number' || typeof lon !== 'number' || !isFinite(lat) || !isFinite(lon)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Now that all data is known to be valid, filter by time
|
||||||
|
if (time >= startTime && time <= endTime) {
|
||||||
|
result.push([lat, lon]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, [data, range]);
|
||||||
|
|
||||||
const shareStart = shareStartParam ? moment.unix(shareStartParam) : null;
|
const shareStart = shareStartParam ? moment.unix(shareStartParam) : null;
|
||||||
const shareEnd = shareEndParam ? moment.unix(shareEndParam) : null;
|
const shareEnd = shareEndParam ? moment.unix(shareEndParam) : null;
|
||||||
|
|
||||||
@@ -1026,12 +1046,10 @@ function App() {
|
|||||||
shareEnd={shareEnd}
|
shareEnd={shareEnd}
|
||||||
data={data}
|
data={data}
|
||||||
drawnItems={drawnItems}
|
drawnItems={drawnItems}
|
||||||
|
coords={coords}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Map
|
<Map
|
||||||
end={end}
|
|
||||||
duration={duration}
|
|
||||||
slider={slider}
|
|
||||||
mapState={mapState}
|
mapState={mapState}
|
||||||
setMapState={setMapState}
|
setMapState={setMapState}
|
||||||
setSubmenu={setSubmenu}
|
setSubmenu={setSubmenu}
|
||||||
@@ -1040,6 +1058,7 @@ function App() {
|
|||||||
data={data}
|
data={data}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
setDrawnItems={setDrawnItems}
|
setDrawnItems={setDrawnItems}
|
||||||
|
coords={coords}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user