feat: support relocating

This commit is contained in:
arielherself 2023-11-27 12:21:22 +08:00
parent 80278d4c21
commit e80311a1b4
5 changed files with 112 additions and 48 deletions

View File

@ -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>

View File

@ -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);
});
}

View File

@ -1,5 +1,4 @@
export function post(type,method,args) {
let res = '';
let ft = async () => {
let response;
if(type==='GET'){

View File

@ -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>
);

View File

@ -5,26 +5,31 @@ 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: []};
}
render() {
const fillBlueOptions = { fillColor: 'blue' };
const pl=this.state.polylines;
const mks=this.state.markers.map((p, i) => (
<Marker key={`m`+i} interactive={false} position={[p[0], p[1]]} opacity={1.0} />
const fillBlueOptions = {fillColor: 'blue'};
const pl = this.state.polylines;
const mks = this.state.markers.map((p, i) => (
<Marker key={`m` + i} interactive={false} position={[p[0], p[1]]} opacity={1.0}/>
));
const cmks=this.state.candMarkers.map((p, i) => (
<Marker key={`c`+i} interactive={false} position={[p[0], p[1]]} opacity={0.5} />
const cmks = this.state.candMarkers.map((p, i) => (
<Marker key={`c` + i} interactive={false} position={[p[0], p[1]]} opacity={0.5}/>
));
return [
...mks.concat(cmks),
pl&&pl.length>1?<Polyline pathOptions={fillBlueOptions} positions={pl} />:<div/>
pl && pl.length > 1 ? <Polyline pathOptions={fillBlueOptions} positions={pl}/> : <div/>
];
}
@ -45,7 +50,7 @@ class Markers extends Component {
candEmpty: false,
polylines: prev.polylines,
}));
this.getFocus();
this.getFocus(true);
}
clearMarkers() {
@ -58,7 +63,7 @@ class Markers extends Component {
this.getFocus();
}
clearCandMarkers(){
clearCandMarkers() {
this.setState((prev) => ({
markers: prev.markers,
candMarkers: [],
@ -68,20 +73,23 @@ class Markers extends Component {
this.getFocus();
}
getFocus() {
let currentFocus=[];
getFocus(virtual=false) {
let currentFocus = [];
if (this.state.candMarkers.length) {
currentFocus=this.state.candMarkers.at(-1);
currentFocus = this.state.candMarkers.at(-1);
} else if (this.state.markers.length) {
currentFocus=this.state.markers.at(-1);
currentFocus = this.state.markers.at(-1);
} else {
currentFocus=AppleParkLoc;
currentFocus = defaultLocation;
}
this.props.focusUpdater(currentFocus);
if(virtual){
this.props.locator(currentFocus);
}
}
flushPolylines(pl){
this.setState((prev)=>({
flushPolylines(pl) {
this.setState((prev) => ({
markers: prev.markers,
candMarkers: prev.candMarkers,
candEmpty: prev.candEmpty,
@ -91,18 +99,21 @@ 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)=>{
post('POST', 'click', mks.current.state.markers).then((response) => {
// TODO: real functionality
const pl=JSON.parse(response.multipolyline);
const pl = JSON.parse(response.multipolyline);
mks.current.flushPolylines(pl);
}).catch((e)=>{
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,40 +185,69 @@ 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
}/>;
};
function ChangeView({center,zoom}){
const map=useMap();
map.setView(center,zoom);
function ChangeView({center, zoom}) {
const map = useMap();
map.setView(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}
<MapContainer style={{height: '100vh', width: '100vw',}} sx={{zIndex: 'fab'}} center={focus} zoom={zoom}
scrollWheelZoom={false}>
<ChangeView center={focus} zoom={zoom}/>
<TileLayer
attribution='&copy; <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>
);