diff --git a/mapper/src/App.js b/mapper/src/App.js index 769d256..2174ac5 100644 --- a/mapper/src/App.js +++ b/mapper/src/App.js @@ -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 { @@ -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 + @@ -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} /> );