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=""> <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$/.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$/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/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" /> <change beforePath="$PROJECT_DIR$/src/UMap.js" beforeDir="false" afterPath="$PROJECT_DIR$/src/UMap.js" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
@ -83,7 +83,8 @@
<workItem from="1700814808669" duration="47000" /> <workItem from="1700814808669" duration="47000" />
<workItem from="1700822693197" duration="9493000" /> <workItem from="1700822693197" duration="9493000" />
<workItem from="1701004587189" duration="3578000" /> <workItem from="1701004587189" duration="3578000" />
<workItem from="1701042611758" duration="3355000" /> <workItem from="1701042611758" duration="10222000" />
<workItem from="1701056208994" duration="2524000" />
</task> </task>
<servers /> <servers />
</component> </component>

View File

@ -1,9 +1,22 @@
const getArgs=(s)=>s.split('@@') import {post} from "../src/Networking";
export default function handler(req,res){ export default function handler(req,res){
const {body}=req; const pts=JSON.parse(req.body);
res.status(200).json({ const latRange=pts.map((row)=>row[0]),
log: `Method: click\nArgs: ${body}\nStatus: returned the raw polylines`, lonRange=pts.map((row)=>row[1]);
multipolyline: body, 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) { export function post(type,method,args) {
let res = '';
let ft = async () => { let ft = async () => {
let response; let response;
if(type==='GET'){ if(type==='GET'){

View File

@ -1,11 +1,12 @@
import {Button, IconButton, ButtonGroup, Menu, MenuItem, Stack} from "@mui/joy"; import {Button, IconButton, ButtonGroup, Menu, MenuItem, Stack} from "@mui/joy";
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import React from "react"; 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 ( return (
<ButtonGroup buttonFlex={1} aria-label="flex button group" sx={{zIndex: 'modal'}}> <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> <Button disabled={isCandEmpty} variant={'solid'} color={'primary'} startDecorator={<Add />}>Add</Button>
</ButtonGroup> </ButtonGroup>
); );

View File

@ -5,9 +5,14 @@ import {useMapEvents} from 'react-leaflet/hooks';
import {post} from './Networking'; import {post} from './Networking';
import {Autocomplete, CircularProgress, Sheet} from "@mui/joy"; import {Autocomplete, CircularProgress, Sheet} from "@mui/joy";
import SimulateClick from "./SimulateClick"; 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 { class Markers extends Component {
// A location is a [lat,lon] pair.
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {markers: [], candMarkers: [], candEmpty: true, polylines: []}; this.state = {markers: [], candMarkers: [], candEmpty: true, polylines: []};
@ -45,7 +50,7 @@ class Markers extends Component {
candEmpty: false, candEmpty: false,
polylines: prev.polylines, polylines: prev.polylines,
})); }));
this.getFocus(); this.getFocus(true);
} }
clearMarkers() { clearMarkers() {
@ -68,16 +73,19 @@ class Markers extends Component {
this.getFocus(); this.getFocus();
} }
getFocus() { getFocus(virtual=false) {
let currentFocus = []; let currentFocus = [];
if (this.state.candMarkers.length) { if (this.state.candMarkers.length) {
currentFocus = this.state.candMarkers.at(-1); currentFocus = this.state.candMarkers.at(-1);
} else if (this.state.markers.length) { } else if (this.state.markers.length) {
currentFocus = this.state.markers.at(-1); currentFocus = this.state.markers.at(-1);
} else { } else {
currentFocus=AppleParkLoc; currentFocus = defaultLocation;
} }
this.props.focusUpdater(currentFocus); this.props.focusUpdater(currentFocus);
if(virtual){
this.props.locator(currentFocus);
}
} }
flushPolylines(pl) { flushPolylines(pl) {
@ -91,17 +99,20 @@ class Markers extends Component {
} }
} }
function MapClickHandler({mks}) { function MapClickHandler({mks,focusUpdater,locator,locker}) {
const map = useMapEvents({ const map = useMapEvents({
click: (e) => { click: (e) => {
map.locate(); map.locate();
const lat = e.latlng.lat, lng = e.latlng.lng; const {lat,lng}=e.latlng;
console.info(`Clicking on ${lat} ${lng}`); console.info(`Clicking on ${lat} ${lng}`);
mks.current.addMarker(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 // TODO: real functionality
const pl = JSON.parse(response.multipolyline); const pl = JSON.parse(response.multipolyline);
mks.current.flushPolylines(pl); mks.current.flushPolylines(pl);
focusUpdater([lat,lng]);
locator([lat,lng]);
locker(true);
}).catch((e) => { }).catch((e) => {
console.error(e); console.error(e);
// location.reload(); // location.reload();
@ -115,11 +126,21 @@ function MapClickHandler({mks}) {
return null; return null;
} }
const LocationSearch = ({mks}) => { const LocationSearch = ({mks, focus, nb}) => {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [suggestedLocations, setSuggestedLocations] = useState([]); const [suggestedLocations, setSuggestedLocations] = useState([]);
const [controller, setController] = useState(new AbortController()); 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(() => { useEffect(() => {
return () => controller.abort(); return () => controller.abort();
@ -137,7 +158,7 @@ const LocationSearch = ({mks}) => {
return; return;
} }
const response = await fetch( 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} {signal: newController.signal}
); );
if (response.ok) { if (response.ok) {
@ -164,9 +185,11 @@ const LocationSearch = ({mks}) => {
} }
}; };
return <Autocomplete sx={{zIndex: 'snackbar'}} loading={loading} loadingText={"Searching..."} startDecorator={<SearchIcon/>} return <Autocomplete sx={{zIndex: 'snackbar'}} loading={loading} loadingText={"Searching..."}
placeholder={'Find a location...'} onInputChange={handleSearch} startDecorator={<SearchLogo/>}
options={suggestedLocations} isOptionEqualToValue={(option, value) => option.value === value.value} endDecorator={ placeholder={everywhere?'Search everywhere...':`Search near${nb}`} onInputChange={handleSearch}
options={suggestedLocations}
isOptionEqualToValue={(option, value) => option.value === value.value} endDecorator={
loading ? ( loading ? (
<CircularProgress size="sm" sx={{bgcolor: 'background.surface'}}/> <CircularProgress size="sm" sx={{bgcolor: 'background.surface'}}/>
) : null ) : null
@ -180,9 +203,35 @@ function ChangeView({center,zoom}){
export default function UMap() { export default function UMap() {
const markersRef = useRef(null); const markersRef = useRef(null);
const [focus, setFocus]=useState(AppleParkLoc); const [focus, setFocus] = useState(defaultLocation);
const zoom=16; const [located,setLocated]=useState(true);
const sf=(a)=>{setFocus(a);console.log(`triggered focus update, new focus is ${focus}`);}; 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 ( return (
<Sheet> <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}
@ -192,12 +241,13 @@ export default function UMap() {
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/> />
<Markers ref={markersRef} focusUpdater={sf}/> <Markers ref={markersRef} focusUpdater={sf} locator={setLocatedFocus} />
<MapClickHandler mks={markersRef}/> <MapClickHandler mks={markersRef} focusUpdater={setFocus} locator={setLocatedFocus} locker={setLocated}/>
<ViewportChange/>
</MapContainer> </MapContainer>
<Sheet sx={{position: 'absolute', top: '20px', right: '10vw', zIndex: 'modal'}}> <Sheet sx={{position: 'absolute', top: '20px', right: '10vw', zIndex: 'modal'}}>
<SimulateClick isCandEmpty={markersRef.current?markersRef.current.state.candEmpty:true} /> <SimulateClick isLocated={located} relocator={relo} isCandEmpty={markersRef.current ? markersRef.current.state.candEmpty : true}/>
<LocationSearch mks={markersRef}/> <LocationSearch nb={nearbyName} mks={markersRef} focus={focus}/>
</Sheet> </Sheet>
</Sheet> </Sheet>
); );