feat: add button to exclude drawn areas from time range
This commit is contained in:
@@ -199,9 +199,7 @@ function PolylineWithArrows({ coords, showDirection }) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function Map({end, duration, slider, mapState, setMapState, setSubmenu, showDirection}) {
|
||||
const [data, loading] = useSensor('owntracks', 'OwnTracks', end, duration);
|
||||
|
||||
function Map({data, loading, end, duration, slider, mapState, setMapState, setSubmenu, showDirection, setDrawnItems}) {
|
||||
const range = useMemo(() => parseSlider(end, duration, slider), [end, duration, slider]);
|
||||
|
||||
const coords = useMemo(() => {
|
||||
@@ -246,14 +244,25 @@ function Map({end, duration, slider, mapState, setMapState, setSubmenu, showDire
|
||||
localStorage.setItem('api_key', api_key);
|
||||
}
|
||||
|
||||
const onRectangleDrawn = (e) => {
|
||||
const onCreated = (e) => {
|
||||
const { layer } = e;
|
||||
const bounds = layer.getBounds();
|
||||
console.log('Rectangle drawn. Bounds:', {
|
||||
northEast: bounds.getNorthEast(),
|
||||
southWest: bounds.getSouthWest(),
|
||||
setDrawnItems(items => [...items, {id: layer._leaflet_id, bounds: layer.getBounds()}]);
|
||||
};
|
||||
|
||||
const onEdited = (e) => {
|
||||
const { layers } = e;
|
||||
layers.eachLayer(layer => {
|
||||
setDrawnItems(items => items.map(item =>
|
||||
item.id === layer._leaflet_id ? { ...item, bounds: layer.getBounds() } : item
|
||||
));
|
||||
});
|
||||
// In the future, we can use these bounds to filter data or perform a search.
|
||||
};
|
||||
|
||||
const onDeleted = (e) => {
|
||||
const { layers } = e;
|
||||
const deletedIds = [];
|
||||
layers.eachLayer(layer => deletedIds.push(layer._leaflet_id));
|
||||
setDrawnItems(items => items.filter(item => !deletedIds.includes(item.id)));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -273,7 +282,9 @@ function Map({end, duration, slider, mapState, setMapState, setSubmenu, showDire
|
||||
<FeatureGroup>
|
||||
<EditControl
|
||||
position="topright"
|
||||
onCreated={onRectangleDrawn}
|
||||
onCreated={onCreated}
|
||||
onEdited={onEdited}
|
||||
onDeleted={onDeleted}
|
||||
draw={{
|
||||
rectangle: true,
|
||||
polyline: false,
|
||||
@@ -300,7 +311,7 @@ function Map({end, duration, slider, mapState, setMapState, setSubmenu, showDire
|
||||
);
|
||||
}
|
||||
|
||||
function Menu({duration, setDuration, end, setEnd, slider, setSlider, submenu, setSubmenu, showDirection, setShowDirection, setMapState, shareStart, shareEnd}) {
|
||||
function Menu({data, duration, setDuration, end, setEnd, slider, setSlider, submenu, setSubmenu, showDirection, setShowDirection, setMapState, shareStart, shareEnd, drawnItems}) {
|
||||
const [showRange, setShowRange] = useState(false);
|
||||
|
||||
const chooseDuration = (x) => {
|
||||
@@ -361,6 +372,80 @@ function Menu({duration, setDuration, end, setEnd, slider, setSlider, submenu, s
|
||||
setSubmenu(false);
|
||||
};
|
||||
|
||||
const excludeArea = () => {
|
||||
const drawnRectangles = drawnItems.map(item => item.bounds);
|
||||
if (!drawnRectangles.length || !data || !Array.isArray(data)) {
|
||||
if (!drawnRectangles.length) alert("Please draw one or more rectangles on the map first.");
|
||||
setSubmenu(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const isInsideExclusionZone = (lat, lon) => {
|
||||
for (const rect of drawnRectangles) {
|
||||
if (rect.contains([lat, lon])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const goodSegments = [];
|
||||
let currentSegment = null;
|
||||
|
||||
for (const point of data) {
|
||||
if (!point || typeof point.lat !== 'number' || typeof point.lon !== 'number' || !point.time) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isInside = isInsideExclusionZone(point.lat, point.lon);
|
||||
|
||||
if (!isInside) {
|
||||
if (!currentSegment) {
|
||||
currentSegment = { start: point.time, end: point.time };
|
||||
} else {
|
||||
currentSegment.end = point.time;
|
||||
}
|
||||
} else {
|
||||
if (currentSegment) {
|
||||
goodSegments.push(currentSegment);
|
||||
currentSegment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSegment) {
|
||||
goodSegments.push(currentSegment);
|
||||
}
|
||||
|
||||
if (!goodSegments.length) {
|
||||
alert("No time ranges found outside the selected area(s).");
|
||||
setSubmenu(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let longestSegment = goodSegments[0];
|
||||
for (let i = 1; i < goodSegments.length; i++) {
|
||||
const durationCurrent = moment(longestSegment.end).diff(moment(longestSegment.start));
|
||||
const durationNew = moment(goodSegments[i].end).diff(moment(goodSegments[i].start));
|
||||
if (durationNew > durationCurrent) {
|
||||
longestSegment = goodSegments[i];
|
||||
}
|
||||
}
|
||||
|
||||
const startUnix = moment(longestSegment.start).unix();
|
||||
const endUnix = moment(longestSegment.end).unix();
|
||||
const endOfWindowUnix = end.unix();
|
||||
|
||||
const newSliderStart = Math.round((startUnix - endOfWindowUnix) / duration.secs + duration.num);
|
||||
const newSliderEnd = Math.round((endUnix - endOfWindowUnix) / duration.secs + duration.num);
|
||||
|
||||
const clampedStart = Math.max(0, newSliderStart);
|
||||
const clampedEnd = Math.min(duration.num, newSliderEnd);
|
||||
|
||||
setSlider([clampedStart, clampedEnd]);
|
||||
setSubmenu(false);
|
||||
};
|
||||
|
||||
const range = parseSlider(end, duration, slider);
|
||||
const startDate = moment(end).subtract(...duration.delta);
|
||||
|
||||
@@ -530,6 +615,7 @@ function Menu({duration, setDuration, end, setEnd, slider, setSlider, submenu, s
|
||||
Show direction
|
||||
</label>
|
||||
<button onClick={recentreView}>Recentre view</button>
|
||||
<button onClick={excludeArea}>Exclude area</button>
|
||||
<button onClick={shareRange}>Share range</button>
|
||||
<button onClick={resetToDefaults}>Reset page</button>
|
||||
</>
|
||||
@@ -592,6 +678,9 @@ function App() {
|
||||
});
|
||||
const [submenu, setSubmenu] = useState(false);
|
||||
const [showDirection, setShowDirection] = useState(initialShowDirection);
|
||||
const [drawnItems, setDrawnItems] = useState([]);
|
||||
|
||||
const [data, loading] = useSensor('owntracks', 'OwnTracks', end, duration);
|
||||
|
||||
const shareStart = shareStartParam ? moment.unix(shareStartParam) : null;
|
||||
const shareEnd = shareEndParam ? moment.unix(shareEndParam) : null;
|
||||
@@ -644,6 +733,8 @@ function App() {
|
||||
setMapState={setMapState}
|
||||
shareStart={shareStart}
|
||||
shareEnd={shareEnd}
|
||||
data={data}
|
||||
drawnItems={drawnItems}
|
||||
/>
|
||||
|
||||
<Map
|
||||
@@ -654,6 +745,9 @@ function App() {
|
||||
setMapState={setMapState}
|
||||
setSubmenu={setSubmenu}
|
||||
showDirection={showDirection}
|
||||
data={data}
|
||||
loading={loading}
|
||||
setDrawnItems={setDrawnItems}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user