From 3921c2a80dee52855ad48dbd0c573af09f240d1a Mon Sep 17 00:00:00 2001 From: Kevin Chen Date: Mon, 6 Jun 2022 23:29:49 -0400 Subject: [PATCH 1/3] Draw hexagons --- ui/package-lock.json | 16 +++ ui/package.json | 1 + ui/src/App.tsx | 156 ++++++++++++++++------------- ui/src/Hexagons/index.tsx | 48 +++++++++ ui/src/ResolutionControl/index.tsx | 36 +++++++ ui/src/getPolygons.ts | 34 +++++++ 6 files changed, 223 insertions(+), 68 deletions(-) create mode 100644 ui/src/Hexagons/index.tsx create mode 100644 ui/src/ResolutionControl/index.tsx create mode 100644 ui/src/getPolygons.ts diff --git a/ui/package-lock.json b/ui/package-lock.json index 1da70c4..c2a4afc 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -21,6 +21,7 @@ "@types/node": "^17.0.39", "@types/react": "^18.0.11", "@types/react-dom": "^18.0.5", + "h3-js": "^3.7.2", "react": "^18.1.0", "react-dom": "^18.1.0", "react-scripts": "5.0.1", @@ -8623,6 +8624,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/h3-js": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-3.7.2.tgz", + "integrity": "sha512-LPjlHSwB9zQZrMqKloCZmmmt3yZzIK7nqPcXqwU93zT3TtYG6jP4tZBzAPouxut7lLjdFbMQ75wRBiKfpsnY7w==", + "engines": { + "node": ">=4", + "npm": ">=3", + "yarn": ">=1.3.0" + } + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -23041,6 +23052,11 @@ "duplexer": "^0.1.2" } }, + "h3-js": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/h3-js/-/h3-js-3.7.2.tgz", + "integrity": "sha512-LPjlHSwB9zQZrMqKloCZmmmt3yZzIK7nqPcXqwU93zT3TtYG6jP4tZBzAPouxut7lLjdFbMQ75wRBiKfpsnY7w==" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", diff --git a/ui/package.json b/ui/package.json index 7a287aa..5df15b8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -16,6 +16,7 @@ "@types/node": "^17.0.39", "@types/react": "^18.0.11", "@types/react-dom": "^18.0.5", + "h3-js": "^3.7.2", "react": "^18.1.0", "react-dom": "^18.1.0", "react-scripts": "5.0.1", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index a3314e7..d082d9a 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -6,6 +6,7 @@ import mapStyles from "./mapStyles.json"; import { ThemeProvider, createTheme } from "@mui/material/styles"; import { Box, + Stack, FormControlLabel, FormGroup, Grid, @@ -29,6 +30,9 @@ import { faker } from "@faker-js/faker"; import listDrivers from "./request/listDrivers"; import getNearestDrivers from "./request/getNearestDrivers"; import updateDriverLocations from "./request/updateDriverLocations"; +import { getPolygons } from "./getPolygons"; +import ResolutionControl from "./ResolutionControl"; +import Hexagons from "./Hexagons"; const darkTheme = createTheme({ palette: { @@ -77,6 +81,11 @@ function MyMap() { allIds: [], } as NormalizedDriverLocations); const [searchResults, setSearchResults] = React.useState([]); + const [resolution, setResolution] = React.useState(7); + const [pickupLocation, setPickupLocation] = React.useState({ + latitude: 40.72106092795603, + longitude: -73.95246141893465, + } as LatLng); const newDriverLocations = getDriverLocationsFromState( newDriverLocationsState @@ -96,10 +105,12 @@ function MyMap() { const lat = e.latLng?.lat() ?? 0; const lng = e.latLng?.lng() ?? 0; - const res = await getNearestDrivers({ + const point: LatLng = { latitude: lat, longitude: lng, - } as LatLng); + }; + setPickupLocation(point); + const res = await getNearestDrivers(point); setSearchResults(res?.results ?? []); }; @@ -120,74 +131,83 @@ function MyMap() { }; return ( - - - - setQueryMode(!queryMode)} - /> - } - label="Mode" - /> - - - - {queryMode ? ( - Click anywhere to rank nearby drivers - ) : ( - Click to add new drivers - )} - - - - () => - setFocusedDriverId(driverId)} - buildHandleMouseOut={(driverId: string) => () => - setFocusedDriverId("")} - queryMode={queryMode} - onUpload={() => - updateDriverLocations( - getDriverLocationsFromState(newDriverLocationsState) - ) - } - driverLocations={newDriverLocations} - /> - - - - - - - {driverLocations.map((dl: DriverLocation, i: number) => ( - sr.driver.driverId) - .includes(dl.driverId)} + + + + + + setQueryMode(!queryMode)} + /> + } + label="Mode" + /> + + + + {queryMode ? ( + Click anywhere to rank nearby drivers + ) : ( + Click to add new drivers + )} + + + + () => + setFocusedDriverId(driverId)} + buildHandleMouseOut={(driverId: string) => () => + setFocusedDriverId("")} + queryMode={queryMode} + onUpload={() => + updateDriverLocations( + getDriverLocationsFromState(newDriverLocationsState) + ) + } + driverLocations={newDriverLocations} + /> + + + + + + + {driverLocations.map((dl: DriverLocation, i: number) => ( + sr.driver.driverId) + .includes(dl.driverId)} + /> + ))} + {newDriverLocations.map((dl: DriverLocation, i: number) => ( + + ))} + - ))} - {newDriverLocations.map((dl: DriverLocation, i: number) => ( - - ))} - <> - - - + + + + - + ); } diff --git a/ui/src/Hexagons/index.tsx b/ui/src/Hexagons/index.tsx new file mode 100644 index 0000000..42c302b --- /dev/null +++ b/ui/src/Hexagons/index.tsx @@ -0,0 +1,48 @@ +import { getPolygons } from "../getPolygons"; +import { LatLng } from "../types"; +import { Polygon } from "@react-google-maps/api"; + +interface HexagonsProps { + pickupLocation: LatLng; + resolution: number; +} + +function buildOptions(k: number): google.maps.PolygonOptions { + const fillColor = k === 0 ? "lightblue" : k === 1 ? "lightblue" : "lightblue"; + return { + fillColor, + fillOpacity: 1, + strokeColor: "red", + strokeOpacity: 1, + strokeWeight: 2, + clickable: false, + draggable: false, + editable: false, + geodesic: false, + zIndex: 1, + }; +} + +export default function Hexagons(props: HexagonsProps) { + const { pickupLocation, resolution } = props; + const out = getPolygons(pickupLocation, resolution); + return ( + <> + {out.ring0.map((points: LatLng[]) => ( + + ))} + {out.ring1.map((points: LatLng[]) => ( + + ))} + {out.ring2.map((points: LatLng[]) => ( + + ))} + + ); +} + +function pointsToPaths(points: LatLng[]): google.maps.LatLngLiteral[] { + return points.map( + (p) => ({ lat: p.latitude, lng: p.longitude } as google.maps.LatLngLiteral) + ); +} diff --git a/ui/src/ResolutionControl/index.tsx b/ui/src/ResolutionControl/index.tsx new file mode 100644 index 0000000..687c34d --- /dev/null +++ b/ui/src/ResolutionControl/index.tsx @@ -0,0 +1,36 @@ +import { IconButton, Stack, Typography } from "@mui/material"; +import { Add as AddIcon, Remove as RemoveIcon } from "@mui/icons-material"; +import { Dispatch, SetStateAction } from "react"; + +interface ResolutionControlProps { + resolution: number; + setResolution: Dispatch>; +} + +const MAX_RESOLUTION = 10; +const MIN_RESOLUTION = 7; + +export default function ResolutionControl(props: ResolutionControlProps) { + const { resolution, setResolution } = props; + return ( + + { + if (resolution > MIN_RESOLUTION) setResolution(resolution - 1); + }} + > + + + {resolution} + { + if (resolution < MAX_RESOLUTION) setResolution(resolution + 1); + }} + > + + + + ); +} diff --git a/ui/src/getPolygons.ts b/ui/src/getPolygons.ts new file mode 100644 index 0000000..9df8ca3 --- /dev/null +++ b/ui/src/getPolygons.ts @@ -0,0 +1,34 @@ +import { LatLng } from "./types"; +import { geoToH3, H3Index, h3ToGeoBoundary, kRing } from "h3-js"; + +export interface getPolygonsOutput { + ring0: LatLng[][]; + ring1: LatLng[][]; + ring2: LatLng[][]; +} + +export function getPolygons(l: LatLng, res: number): getPolygonsOutput { + const index = geoToH3(l.latitude, l.longitude, res); + console.log("index", index); + + const indexes0 = kRing(index, 0); + const indexes1 = kRing(index, 1); + const indexes2 = kRing(index, 2); + + return { + ring0: indexes0.map(getIndexVertexes), + ring1: indexes1.map(getIndexVertexes), + ring2: indexes2.map(getIndexVertexes), + } as getPolygonsOutput; +} + +function getIndexVertexes(index: H3Index): LatLng[] { + const arr = h3ToGeoBoundary(index); + return arr.map( + (p: number[]) => + ({ + latitude: p[0], + longitude: p[1], + } as LatLng) + ); +} From 0f11eeff19aedcf9d5a02d8749e9acbcd4e76f52 Mon Sep 17 00:00:00 2001 From: Kevin Chen Date: Tue, 7 Jun 2022 23:30:48 -0400 Subject: [PATCH 2/3] display nearby drivers in list --- ui/src/App.tsx | 16 +++++++++------ ui/src/Hexagons/index.tsx | 30 +++++++++++++++++++++------- ui/src/Marker/index.tsx | 40 ++++++++++++++++++++++++++++++++------ ui/src/PointList/index.tsx | 32 ++++++++++++++++++++---------- 4 files changed, 89 insertions(+), 29 deletions(-) diff --git a/ui/src/App.tsx b/ui/src/App.tsx index d082d9a..3b4376d 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -69,7 +69,7 @@ function newDriverName(): string { function MyMap() { const apiKey = process.env.REACT_APP_GOOGLE_MAPS_API_KEY as string; const [focusedDriverId, setFocusedDriverId] = React.useState(""); - const [queryMode, setQueryMode] = React.useState(false); + const [queryMode, setQueryMode] = React.useState(true); const [driverLocationsState, setDriverLocationsState] = React.useState({ byId: {}, @@ -81,7 +81,7 @@ function MyMap() { allIds: [], } as NormalizedDriverLocations); const [searchResults, setSearchResults] = React.useState([]); - const [resolution, setResolution] = React.useState(7); + const [resolution, setResolution] = React.useState(9); const [pickupLocation, setPickupLocation] = React.useState({ latitude: 40.72106092795603, longitude: -73.95246141893465, @@ -131,7 +131,7 @@ function MyMap() { }; return ( - + @@ -189,15 +190,18 @@ function MyMap() { {driverLocations.map((dl: DriverLocation, i: number) => ( sr.driver.driverId) + .map((sr: SearchResult) => sr.driver.driverId) .includes(dl.driverId)} /> ))} {newDriverLocations.map((dl: DriverLocation, i: number) => ( - + ))} + {pickupLocation && ( + + )} { + console.log("polygon: ", polygon); + }; return ( <> {out.ring0.map((points: LatLng[]) => ( - + ))} {out.ring1.map((points: LatLng[]) => ( - + ))} {out.ring2.map((points: LatLng[]) => ( - + ))} ); @@ -43,6 +58,7 @@ export default function Hexagons(props: HexagonsProps) { function pointsToPaths(points: LatLng[]): google.maps.LatLngLiteral[] { return points.map( - (p) => ({ lat: p.latitude, lng: p.longitude } as google.maps.LatLngLiteral) + (p: LatLng) => + ({ lat: p.latitude, lng: p.longitude } as google.maps.LatLngLiteral) ); } diff --git a/ui/src/Marker/index.tsx b/ui/src/Marker/index.tsx index 83013b4..8e7adff 100644 --- a/ui/src/Marker/index.tsx +++ b/ui/src/Marker/index.tsx @@ -12,32 +12,60 @@ function getIconPath(element: React.ReactElement): string { } interface MyMarkerProps { - driverLocation: DriverLocation; + /** + * The marker's location + */ + location: LatLng; handleMouseOver?: (e: google.maps.MapMouseEvent) => void; + handleMouseOut?: (e: google.maps.MapMouseEvent) => void; handleClick?: (e: google.maps.MapMouseEvent) => void; + + /** + * Whether the marker is for the requested pickup location + */ + pickupLocation?: boolean; + /** + * Whether the marker is for a new driver location that will be created in bulk. + */ cached?: boolean; + /** + * Whether the marker represents a nearby driver to the requested pickup location. + */ isNear?: boolean; } export default function MyMarker(props: MyMarkerProps) { - const { cached, isNear, driverLocation, handleClick, handleMouseOver } = - props; - const color = isNear ? "green" : cached ? "orange" : "#FFD700"; - const p = driverLocation.currentLocation; + const { + pickupLocation, + cached, + isNear, + location: p, + handleClick, + handleMouseOver, + handleMouseOut, + } = props; + let color = "#FFD700"; + if (isNear) color = "green"; + if (cached) color = "orange"; + if (pickupLocation) color = "purple"; + if (pickupLocation) + console.log("rendering marker for pickup", pickupLocation, p); return ( console.log("marker unmount")} title={`(${p.latitude}, ${p.longitude})`} position={{ lat: p.latitude, lng: p.longitude }} opacity={1} onClick={handleClick} onMouseOver={handleMouseOver} + onMouseOut={handleMouseOut} icon={{ path: getIconPath(), fillColor: color, fillOpacity: 0.9, scale: 0.7, strokeColor: color, - strokeWeight: 1, + strokeWeight: 0.5, }} /> ); diff --git a/ui/src/PointList/index.tsx b/ui/src/PointList/index.tsx index 63fbfb7..bf9f107 100644 --- a/ui/src/PointList/index.tsx +++ b/ui/src/PointList/index.tsx @@ -9,7 +9,7 @@ import { Divider, Avatar, } from "@mui/material"; -import { DriverLocation } from "../types"; +import { DriverLocation, SearchResult } from "../types"; function stringToColor(string: string) { let hash = 0; @@ -74,6 +74,7 @@ function Item(props: ItemProps) { } interface DriverListProps { + searchResults: SearchResult[]; driverLocations: DriverLocation[]; onUpload?: MouseEventHandler; /** @@ -86,6 +87,7 @@ interface DriverListProps { export default function DriverList(props: DriverListProps) { const { + searchResults, driverLocations, onUpload: handleClick, queryMode, @@ -95,18 +97,28 @@ export default function DriverList(props: DriverListProps) { return ( - {queryMode ? "Drivers List" : "New Drivers List"} + {queryMode ? "Nearby Drivers List" : "New Drivers List"} - {driverLocations.map((p: DriverLocation, i: number) => ( - - ))} + {queryMode && + searchResults.map((sr: SearchResult, i: number) => ( + + ))} + {!queryMode && + driverLocations.map((p: DriverLocation, i: number) => ( + + ))} {!queryMode && ( From b72cdaa8a1226680103a5f46d98e33c6cf726503 Mon Sep 17 00:00:00 2001 From: Kevin Chen Date: Tue, 7 Jun 2022 23:47:13 -0400 Subject: [PATCH 3/3] useState --- ui/src/App.tsx | 17 ++++++++++------- ui/src/Hexagons/index.tsx | 26 ++++++++++++-------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 3b4376d..f48f8a3 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,6 +1,6 @@ import React, { ReactElement } from "react"; import "./App.css"; -import { GoogleMap, LoadScript } from "@react-google-maps/api"; +import { GoogleMap, LoadScript, Polygon } from "@react-google-maps/api"; import Marker from "./Marker"; import mapStyles from "./mapStyles.json"; import { ThemeProvider, createTheme } from "@mui/material/styles"; @@ -30,9 +30,9 @@ import { faker } from "@faker-js/faker"; import listDrivers from "./request/listDrivers"; import getNearestDrivers from "./request/getNearestDrivers"; import updateDriverLocations from "./request/updateDriverLocations"; -import { getPolygons } from "./getPolygons"; +import { getPolygons, getPolygonsOutput } from "./getPolygons"; import ResolutionControl from "./ResolutionControl"; -import Hexagons from "./Hexagons"; +import Hexagons, { pointsToPaths, buildOptions } from "./Hexagons"; const darkTheme = createTheme({ palette: { @@ -101,6 +101,7 @@ function MyMap() { getDriverLocations().catch(console.error); }); + const [polyOut, setPolyOut] = React.useState(); const getNearbyDrivers = async (e: google.maps.MapMouseEvent) => { const lat = e.latLng?.lat() ?? 0; const lng = e.latLng?.lng() ?? 0; @@ -110,6 +111,7 @@ function MyMap() { longitude: lng, }; setPickupLocation(point); + setPolyOut(getPolygons(pickupLocation, resolution)); const res = await getNearestDrivers(point); setSearchResults(res?.results ?? []); }; @@ -130,6 +132,10 @@ function MyMap() { ); }; + const onLoad = (polygon: google.maps.Polygon) => { + console.log("polygon: ", polygon); + }; + return ( )} - + {polyOut && } diff --git a/ui/src/Hexagons/index.tsx b/ui/src/Hexagons/index.tsx index bdccec9..8989ad9 100644 --- a/ui/src/Hexagons/index.tsx +++ b/ui/src/Hexagons/index.tsx @@ -1,13 +1,13 @@ -import { getPolygons } from "../getPolygons"; +import React from "react"; +import { getPolygons, getPolygonsOutput } from "../getPolygons"; import { LatLng } from "../types"; import { Polygon } from "@react-google-maps/api"; interface HexagonsProps { - pickupLocation: LatLng; - resolution: number; + polyOut: getPolygonsOutput; } -function buildOptions(k: number): google.maps.PolygonOptions { +export function buildOptions(k: number): google.maps.PolygonOptions { const fillColor = k === 0 ? "lightblue" : k === 1 ? "lightblue" : "lightblue"; return { fillColor, @@ -23,40 +23,38 @@ function buildOptions(k: number): google.maps.PolygonOptions { } as google.maps.PolygonOptions; } -export default function Hexagons(props: HexagonsProps) { - const { pickupLocation, resolution } = props; - const out = getPolygons(pickupLocation, resolution); +export default function Hexagons({ polyOut }: HexagonsProps) { const onLoad = (polygon: google.maps.Polygon) => { console.log("polygon: ", polygon); }; return ( <> - {out.ring0.map((points: LatLng[]) => ( + {polyOut?.ring0.map((points: LatLng[]) => ( - ))} - {out.ring1.map((points: LatLng[]) => ( + )) ?? null} + {polyOut?.ring1.map((points: LatLng[]) => ( - ))} - {out.ring2.map((points: LatLng[]) => ( + )) ?? null} + {polyOut?.ring2.map((points: LatLng[]) => ( - ))} + )) ?? null} ); } -function pointsToPaths(points: LatLng[]): google.maps.LatLngLiteral[] { +export function pointsToPaths(points: LatLng[]): google.maps.LatLngLiteral[] { return points.map( (p: LatLng) => ({ lat: p.latitude, lng: p.longitude } as google.maps.LatLngLiteral)