feat: support relocating
This commit is contained in:
parent
80278d4c21
commit
e80311a1b4
|
@ -7,8 +7,8 @@
|
|||
<list default="true" id="3c7078e7-6f30-4d92-9696-11496f9e6dff" name="Changes" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/api/click.js" beforeDir="false" afterPath="$PROJECT_DIR$/api/click.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/App.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/App.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/Networking.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/Networking.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/SimulateClick.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/SimulateClick.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/UMap.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/UMap.js" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
|
@ -83,7 +83,8 @@
|
|||
<workItem from="1700814808669" duration="47000" />
|
||||
<workItem from="1700822693197" duration="9493000" />
|
||||
<workItem from="1701004587189" duration="3578000" />
|
||||
<workItem from="1701042611758" duration="3355000" />
|
||||
<workItem from="1701042611758" duration="10222000" />
|
||||
<workItem from="1701056208994" duration="2524000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
|
|
23
api/click.js
23
api/click.js
|
@ -1,9 +1,22 @@
|
|||
const getArgs=(s)=>s.split('@@')
|
||||
import {post} from "../src/Networking";
|
||||
|
||||
export default function handler(req,res){
|
||||
const {body}=req;
|
||||
res.status(200).json({
|
||||
log: `Method: click\nArgs: ${body}\nStatus: returned the raw polylines`,
|
||||
multipolyline: body,
|
||||
const pts=JSON.parse(req.body);
|
||||
const latRange=pts.map((row)=>row[0]),
|
||||
lonRange=pts.map((row)=>row[1]);
|
||||
const minlon=Math.min(...lonRange),minlat=Math.min(...latRange),
|
||||
maxlon=Math.max(...lonRange),maxlat=Math.max(...latRange);
|
||||
const request_uri=`https://www.overpass-api.de/api/interpreter?data=[out:json];node[highway](${minlat},${minlon},${maxlat},${maxlon});out;`;
|
||||
const fetch_debug_response= fetch(request_uri).then((response)=>{
|
||||
return response.json();
|
||||
});
|
||||
fetch_debug_response.then((debug_response)=>{
|
||||
res.status(200).json({
|
||||
log: `Method: click\nArgs: ${pts}\nStatus: requested "${request_uri}", got response ${JSON.stringify(debug_response.elements)}`,
|
||||
multipolyline: JSON.stringify(pts),
|
||||
});
|
||||
}).catch(e=>{
|
||||
res.status(500);
|
||||
});
|
||||
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
export function post(type,method,args) {
|
||||
let res = '';
|
||||
let ft = async () => {
|
||||
let response;
|
||||
if(type==='GET'){
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import {Button, IconButton, ButtonGroup, Menu, MenuItem, Stack} from "@mui/joy";
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import React from "react";
|
||||
import {Add} from "@mui/icons-material";
|
||||
import {Add, LocationOn} from "@mui/icons-material";
|
||||
|
||||
export default function SimulateClick({isCandEmpty}) {
|
||||
export default function SimulateClick({isLocated,isCandEmpty,relocator}) {
|
||||
return (
|
||||
<ButtonGroup buttonFlex={1} aria-label="flex button group" sx={{zIndex: 'modal'}}>
|
||||
<Button disabled={isLocated} variant={'solid'} color={'warning'} startDecorator={<LocationOn />} onClick={relocator}>Relocate</Button>
|
||||
<Button disabled={isCandEmpty} variant={'solid'} color={'primary'} startDecorator={<Add />}>Add</Button>
|
||||
</ButtonGroup>
|
||||
);
|
||||
|
|
86
src/UMap.js
86
src/UMap.js
|
@ -5,9 +5,14 @@ import {useMapEvents} from 'react-leaflet/hooks';
|
|||
import {post} from './Networking';
|
||||
import {Autocomplete, CircularProgress, Sheet} from "@mui/joy";
|
||||
import SimulateClick from "./SimulateClick";
|
||||
import {NearMe} from "@mui/icons-material";
|
||||
|
||||
const defaultZoomLevel=16;
|
||||
const defaultLocationName = 'Apple Park';
|
||||
const defaultLocation = [37.334835049999995, -122.01139165956805];
|
||||
|
||||
const AppleParkLoc=[37.334835049999995,-122.01139165956805];
|
||||
class Markers extends Component {
|
||||
// A location is a [lat,lon] pair.
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {markers: [], candMarkers: [], candEmpty: true, polylines: []};
|
||||
|
@ -45,7 +50,7 @@ class Markers extends Component {
|
|||
candEmpty: false,
|
||||
polylines: prev.polylines,
|
||||
}));
|
||||
this.getFocus();
|
||||
this.getFocus(true);
|
||||
}
|
||||
|
||||
clearMarkers() {
|
||||
|
@ -68,16 +73,19 @@ class Markers extends Component {
|
|||
this.getFocus();
|
||||
}
|
||||
|
||||
getFocus() {
|
||||
getFocus(virtual=false) {
|
||||
let currentFocus = [];
|
||||
if (this.state.candMarkers.length) {
|
||||
currentFocus = this.state.candMarkers.at(-1);
|
||||
} else if (this.state.markers.length) {
|
||||
currentFocus = this.state.markers.at(-1);
|
||||
} else {
|
||||
currentFocus=AppleParkLoc;
|
||||
currentFocus = defaultLocation;
|
||||
}
|
||||
this.props.focusUpdater(currentFocus);
|
||||
if(virtual){
|
||||
this.props.locator(currentFocus);
|
||||
}
|
||||
}
|
||||
|
||||
flushPolylines(pl) {
|
||||
|
@ -91,17 +99,20 @@ class Markers extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
function MapClickHandler({mks}) {
|
||||
function MapClickHandler({mks,focusUpdater,locator,locker}) {
|
||||
const map = useMapEvents({
|
||||
click: (e) => {
|
||||
map.locate();
|
||||
const lat = e.latlng.lat, lng = e.latlng.lng;
|
||||
const {lat,lng}=e.latlng;
|
||||
console.info(`Clicking on ${lat} ${lng}`);
|
||||
mks.current.addMarker(lat, lng);
|
||||
post('POST', 'click', mks.current.state.markers).then((response) => {
|
||||
// TODO: real functionality
|
||||
const pl = JSON.parse(response.multipolyline);
|
||||
mks.current.flushPolylines(pl);
|
||||
focusUpdater([lat,lng]);
|
||||
locator([lat,lng]);
|
||||
locker(true);
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
// location.reload();
|
||||
|
@ -115,11 +126,21 @@ function MapClickHandler({mks}) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const LocationSearch = ({mks}) => {
|
||||
const LocationSearch = ({mks, focus, nb}) => {
|
||||
const [query, setQuery] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [suggestedLocations, setSuggestedLocations] = useState([]);
|
||||
const [controller, setController] = useState(new AbortController());
|
||||
const [everywhere,setEverywhere]=useState(false);
|
||||
|
||||
const SearchLogo=()=>{
|
||||
const cf=()=>setEverywhere(!everywhere);
|
||||
if(everywhere){
|
||||
return <SearchIcon onClick={cf}/>;
|
||||
} else{
|
||||
return <NearMe onClick={cf}/>;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => controller.abort();
|
||||
|
@ -137,7 +158,7 @@ const LocationSearch = ({mks}) => {
|
|||
return;
|
||||
}
|
||||
const response = await fetch(
|
||||
`https://nominatim.openstreetmap.org/search?format=json&q=${v}`,
|
||||
`https://nominatim.openstreetmap.org/search?format=json&q=${v}${everywhere?'':' '+focus[0]+','+focus[1]}`,
|
||||
{signal: newController.signal}
|
||||
);
|
||||
if (response.ok) {
|
||||
|
@ -164,9 +185,11 @@ const LocationSearch = ({mks}) => {
|
|||
}
|
||||
};
|
||||
|
||||
return <Autocomplete sx={{zIndex: 'snackbar'}} loading={loading} loadingText={"Searching..."} startDecorator={<SearchIcon/>}
|
||||
placeholder={'Find a location...'} onInputChange={handleSearch}
|
||||
options={suggestedLocations} isOptionEqualToValue={(option, value) => option.value === value.value} endDecorator={
|
||||
return <Autocomplete sx={{zIndex: 'snackbar'}} loading={loading} loadingText={"Searching..."}
|
||||
startDecorator={<SearchLogo/>}
|
||||
placeholder={everywhere?'Search everywhere...':`Search near${nb}`} onInputChange={handleSearch}
|
||||
options={suggestedLocations}
|
||||
isOptionEqualToValue={(option, value) => option.value === value.value} endDecorator={
|
||||
loading ? (
|
||||
<CircularProgress size="sm" sx={{bgcolor: 'background.surface'}}/>
|
||||
) : null
|
||||
|
@ -180,9 +203,35 @@ function ChangeView({center,zoom}){
|
|||
|
||||
export default function UMap() {
|
||||
const markersRef = useRef(null);
|
||||
const [focus, setFocus]=useState(AppleParkLoc);
|
||||
const zoom=16;
|
||||
const sf=(a)=>{setFocus(a);console.log(`triggered focus update, new focus is ${focus}`);};
|
||||
const [focus, setFocus] = useState(defaultLocation);
|
||||
const [located,setLocated]=useState(true);
|
||||
const [locatedFocus,setLocatedFocus]=useState(defaultLocation);
|
||||
const [nearbyName, setNearbyName] = useState(' ' + defaultLocationName);
|
||||
const [zoom,setZoom] = useState(defaultZoomLevel);
|
||||
const sf = (a) => {
|
||||
setFocus(a);
|
||||
console.log(`triggered focus update, new focus is ${focus}`);
|
||||
const ft = fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${a[0]}&lon=${a[1]}&zoom=${zoom}&addressdetails=0`).then(response => response.json());
|
||||
ft.then((response) => setNearbyName(' ' + response.name)).catch(() => setNearbyName('by'));
|
||||
};
|
||||
const relo=()=> {
|
||||
sf(locatedFocus);
|
||||
setLocated(true);
|
||||
};
|
||||
const ViewportChange = () => {
|
||||
const map = useMapEvents({
|
||||
dragend: (e) => {
|
||||
const center = e.target.getCenter();
|
||||
sf([center.lat, center.lng]);
|
||||
setLocated(false);
|
||||
},
|
||||
zoomend: (e)=>{
|
||||
setZoom(e.target.getZoom());
|
||||
sf(focus);
|
||||
}
|
||||
});
|
||||
return null;
|
||||
};
|
||||
return (
|
||||
<Sheet>
|
||||
<MapContainer style={{height: '100vh', width: '100vw',}} sx={{zIndex: 'fab'}} center={focus} zoom={zoom}
|
||||
|
@ -192,12 +241,13 @@ export default function UMap() {
|
|||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<Markers ref={markersRef} focusUpdater={sf}/>
|
||||
<MapClickHandler mks={markersRef}/>
|
||||
<Markers ref={markersRef} focusUpdater={sf} locator={setLocatedFocus} />
|
||||
<MapClickHandler mks={markersRef} focusUpdater={setFocus} locator={setLocatedFocus} locker={setLocated}/>
|
||||
<ViewportChange/>
|
||||
</MapContainer>
|
||||
<Sheet sx={{position: 'absolute', top: '20px', right: '10vw', zIndex: 'modal'}}>
|
||||
<SimulateClick isCandEmpty={markersRef.current?markersRef.current.state.candEmpty:true} />
|
||||
<LocationSearch mks={markersRef}/>
|
||||
<SimulateClick isLocated={located} relocator={relo} isCandEmpty={markersRef.current ? markersRef.current.state.candEmpty : true}/>
|
||||
<LocationSearch nb={nearbyName} mks={markersRef} focus={focus}/>
|
||||
</Sheet>
|
||||
</Sheet>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue