feat: encode map position and zoom in URL
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import * as leaflet from 'leaflet';
|
import * as leaflet from 'leaflet';
|
||||||
import { MapContainer, Polyline, TileLayer, useMap } from 'react-leaflet';
|
import { MapContainer, Polyline, TileLayer, useMap, useMapEvents } from 'react-leaflet';
|
||||||
import Datetime from 'react-datetime';
|
import Datetime from 'react-datetime';
|
||||||
import 'react-datetime/css/react-datetime.css';
|
import 'react-datetime/css/react-datetime.css';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
@@ -71,14 +71,39 @@ function useSensor(measurement, name, end, duration) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function ChangeView({ center, zoom }) {
|
||||||
|
const map = useMap();
|
||||||
|
map.setView(center, zoom);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function Map({end, duration, slider}) {
|
function MapEvents({ onMapChange }) {
|
||||||
|
const map = useMapEvents({
|
||||||
|
moveend: () => {
|
||||||
|
const center = map.getCenter();
|
||||||
|
onMapChange({
|
||||||
|
zoom: map.getZoom(),
|
||||||
|
center: [center.lat, center.lng],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Map({end, duration, slider, mapState, setMapState}) {
|
||||||
const [data, loading] = useSensor('owntracks', 'OwnTracks', end, duration);
|
const [data, loading] = useSensor('owntracks', 'OwnTracks', end, duration);
|
||||||
|
|
||||||
const range = parseSlider(end, duration, slider);
|
const range = parseSlider(end, duration, slider);
|
||||||
|
|
||||||
const coords = data.length ? data.filter(x => !range || (x.time >= range[0] && x.time <= range[1])).map(({ lat, lon }) => [lat, lon]).filter(([lat, lon]) => lat !== null || lon !== null) : [];
|
const coords = data.length ? data.filter(x => !range || (x.time >= range[0] && x.time <= range[1])).map(({ lat, lon }) => [lat, lon]).filter(([lat, lon]) => lat !== null || lon !== null) : [];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// If URL provides no center, set it from the data once it's loaded.
|
||||||
|
if (mapState.center === null && coords.length > 0) {
|
||||||
|
setMapState(prev => ({ ...prev, center: coords[coords.length - 1] }));
|
||||||
|
}
|
||||||
|
}, [coords, mapState.center, setMapState]);
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const api_key = e.target[0].value;
|
const api_key = e.target[0].value;
|
||||||
@@ -91,13 +116,17 @@ function Map({end, duration, slider}) {
|
|||||||
<p>Loading...</p>
|
<p>Loading...</p>
|
||||||
:
|
:
|
||||||
coords.length ?
|
coords.length ?
|
||||||
<MapContainer center={coords[coords.length-1]} zoom={13} scrollWheelZoom={true} style={{ width: '100%', height: 'calc(100vh - 2.5rem)' }}>
|
(mapState.center &&
|
||||||
<TileLayer
|
<MapContainer center={mapState.center} zoom={mapState.zoom} scrollWheelZoom={true} style={{ width: '100%', height: 'calc(100vh - 2.5rem)' }}>
|
||||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
<ChangeView center={mapState.center} zoom={mapState.zoom} />
|
||||||
url='https://maptiles.p.rapidapi.com/en/map/v1/{z}/{x}/{y}.png?rapidapi-key=4375b0b1d8msh0c9e7fa3efb9adfp1769dfjsnd603a0387fea'
|
<MapEvents onMapChange={setMapState} />
|
||||||
/>
|
<TileLayer
|
||||||
<Polyline pathOptions={{color: 'blue'}} positions={coords} />
|
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
</MapContainer>
|
url='https://maptiles.p.rapidapi.com/en/map/v1/{z}/{x}/{y}.png?rapidapi-key=4375b0b1d8msh0c9e7fa3efb9adfp1769dfjsnd603a0387fea'
|
||||||
|
/>
|
||||||
|
<Polyline pathOptions={{color: 'blue'}} positions={coords} />
|
||||||
|
</MapContainer>
|
||||||
|
)
|
||||||
:
|
:
|
||||||
<>
|
<>
|
||||||
<p>No data</p>
|
<p>No data</p>
|
||||||
@@ -284,6 +313,9 @@ function App() {
|
|||||||
const initialDurationId = params.get('duration');
|
const initialDurationId = params.get('duration');
|
||||||
const initialEndTimestamp = params.get('end');
|
const initialEndTimestamp = params.get('end');
|
||||||
const initialSliderValue = params.get('slider');
|
const initialSliderValue = params.get('slider');
|
||||||
|
const initialLat = params.get('lat');
|
||||||
|
const initialLng = params.get('lng');
|
||||||
|
const initialZoom = params.get('zoom');
|
||||||
|
|
||||||
const initialDuration = (initialDurationId && durations[parseInt(initialDurationId, 10)]) ? durations[parseInt(initialDurationId, 10)] : durations[0];
|
const initialDuration = (initialDurationId && durations[parseInt(initialDurationId, 10)]) ? durations[parseInt(initialDurationId, 10)] : durations[0];
|
||||||
const initialEnd = initialEndTimestamp ? moment.unix(initialEndTimestamp) : moment();
|
const initialEnd = initialEndTimestamp ? moment.unix(initialEndTimestamp) : moment();
|
||||||
@@ -292,6 +324,10 @@ function App() {
|
|||||||
const [duration, setDuration] = useState(initialDuration);
|
const [duration, setDuration] = useState(initialDuration);
|
||||||
const [end, setEnd] = useState(initialEnd);
|
const [end, setEnd] = useState(initialEnd);
|
||||||
const [slider, setSlider] = useState(initialSlider);
|
const [slider, setSlider] = useState(initialSlider);
|
||||||
|
const [mapState, setMapState] = useState({
|
||||||
|
center: (initialLat && initialLng) ? [parseFloat(initialLat), parseFloat(initialLng)] : null,
|
||||||
|
zoom: initialZoom ? parseInt(initialZoom, 10) : 13,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = setTimeout(() => {
|
const handler = setTimeout(() => {
|
||||||
@@ -299,13 +335,18 @@ function App() {
|
|||||||
params.set('duration', duration.id);
|
params.set('duration', duration.id);
|
||||||
params.set('end', end.unix());
|
params.set('end', end.unix());
|
||||||
params.set('slider', slider.join(','));
|
params.set('slider', slider.join(','));
|
||||||
|
if (mapState.center) {
|
||||||
|
params.set('lat', mapState.center[0].toFixed(5));
|
||||||
|
params.set('lng', mapState.center[1].toFixed(5));
|
||||||
|
params.set('zoom', mapState.zoom);
|
||||||
|
}
|
||||||
window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`);
|
window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(handler);
|
clearTimeout(handler);
|
||||||
};
|
};
|
||||||
}, [duration, end, slider]);
|
}, [duration, end, slider, mapState]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -322,6 +363,8 @@ function App() {
|
|||||||
end={end}
|
end={end}
|
||||||
duration={duration}
|
duration={duration}
|
||||||
slider={slider}
|
slider={slider}
|
||||||
|
mapState={mapState}
|
||||||
|
setMapState={setMapState}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user