Skip to content

Commit

Permalink
Merge pull request #115 from TurtIeSocks/s2-cell-coverage
Browse files Browse the repository at this point in the history
Visualize S2 Cell Coverage
  • Loading branch information
TurtIeSocks authored Mar 19, 2023
2 parents 365aa59 + f3a66f6 commit 1967b78
Show file tree
Hide file tree
Showing 16 changed files with 258 additions and 31 deletions.
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"name": "koji",
"version": "0.6.8",
"version": "0.6.9",
"description": "Tool to make RDM routes",
"main": "server/dist/index.js",
"author": "TurtIeSocks <[email protected]>",
Expand Down
5 changes: 5 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ const router = createBrowserRouter([
element: <Map />,
errorElement: <ErrorPage error="500" />,
},
{
path: '/map/:lat/:lon/:zoom?',
element: <Map />,
errorElement: <ErrorPage error="500" />,
},
{
path: '/admin/*',
element: <AdminPanel />,
Expand Down
1 change: 1 addition & 0 deletions client/src/components/drawer/Layers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default function Layers() {
<DateTime field="last_seen" />
<Divider sx={{ my: 2 }} />
<ListSubheader disableGutters>S2 Cells</ListSubheader>
<Toggle field="fillCoveredCells" />
<ListItem>
<Select
fullWidth
Expand Down
2 changes: 2 additions & 0 deletions client/src/hooks/usePersist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export interface UsePersist {
route: [number, number][][]
}
s2cells: number[]
fillCoveredCells: boolean
save_to_db: boolean
save_to_scanner: boolean
skipRendering: boolean
Expand Down Expand Up @@ -91,6 +92,7 @@ export const usePersist = create(
data: 'bound',
sort_by: 'GeoHash',
s2cells: [],
fillCoveredCells: false,
radius: 70,
route_split_level: 1,
routing_chunk_size: 0,
Expand Down
2 changes: 2 additions & 0 deletions client/src/hooks/useShapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface UseShapes {
test: boolean
activeRoute: string
newRouteCount: number
s2cellCoverage: Record<string, string>
kojiRefCache: Record<string, DbOption>
firstPoint: keyof UseShapes['Point'] | null
lastPoint: keyof UseShapes['Point'] | null
Expand Down Expand Up @@ -81,6 +82,7 @@ export interface UseShapes {
export const useShapes = create<UseShapes>((set, get) => ({
activeRoute: 'new_route_0',
kojiRefCache: {},
s2cellCoverage: {},
newRouteCount: 0,
test: false,
firstPoint: null,
Expand Down
20 changes: 19 additions & 1 deletion client/src/hooks/useSyncGeojson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import useDeepCompareEffect from 'use-deep-compare-effect'

import { FeatureCollection } from '@assets/types'
import { useShapes } from '@hooks/useShapes'
import { UseShapes, useShapes } from '@hooks/useShapes'
import { s2Coverage } from '@services/fetches'

import { useStatic } from './useStatic'
import { useDbCache } from './useDbCache'
Expand All @@ -16,6 +17,7 @@ export default function useSyncGeojson() {
const multiPolygons = useShapes((s) => s.MultiPolygon)

const geojson = useStatic((s) => s.geojson)

const setStatic = useStatic((s) => s.setStatic)

if (process.env.NODE_ENV === 'development') {
Expand All @@ -28,6 +30,7 @@ export default function useSyncGeojson() {
features: [],
}
Object.values(points).forEach((point) => newGeojson.features.push(point))

Object.values(multiPoints).forEach((multiPoint) =>
newGeojson.features.push(multiPoint),
)
Expand All @@ -43,6 +46,21 @@ export default function useSyncGeojson() {
Object.values(multiPolygons).forEach((multiPolygon) =>
newGeojson.features.push(multiPolygon),
)
Promise.all(
Object.values(points).map((point) =>
s2Coverage(
point.id,
point.geometry.coordinates[1],
point.geometry.coordinates[0],
),
),
).then((results) => {
const s2cellCoverage: UseShapes['s2cellCoverage'] = {}
results.forEach((result) => {
Object.assign(s2cellCoverage, result)
})
useShapes.setState({ s2cellCoverage })
})
setStatic('geojson', newGeojson)
}, [
points,
Expand Down
15 changes: 11 additions & 4 deletions client/src/pages/map/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'
import { Box } from '@mui/material'
import { Pane } from 'react-leaflet'
import { useParams } from 'react-router'

import { usePersist } from '@hooks/usePersist'

Expand Down Expand Up @@ -28,6 +29,7 @@ import {
import { S2Cells } from './markers/S2'

export default function MapWrapper() {
const params = useParams()
const drawer = usePersist((s) => s.drawer)
const menuItem = usePersist((s) => s.menuItem)
const drawerWidth = menuItem === 'Geojson' ? 515 : 345
Expand All @@ -44,6 +46,10 @@ export default function MapWrapper() {
</ErrorBoundary>
<Main open={drawer} drawerWidth={drawerWidth}>
<Map
forcedLocation={
params.lat && params.lon ? [+params.lat, +params.lon] : undefined
}
forcedZoom={params.zoom ? +params.zoom : undefined}
style={{
position: 'absolute',
maxWidth: '100% !important',
Expand All @@ -55,10 +61,11 @@ export default function MapWrapper() {
right: 0,
}}
>
<Pane name="circles" style={{ zIndex: 503 }} />
<Pane name="lines" style={{ zIndex: 502 }} />
<Pane name="arrows" style={{ zIndex: 501 }} />
<Pane name="polygons" style={{ zIndex: 500 }} />
<Pane name="circles" style={{ zIndex: 504 }} />
<Pane name="lines" style={{ zIndex: 503 }} />
<Pane name="arrows" style={{ zIndex: 502 }} />
<Pane name="polygons" style={{ zIndex: 501 }} />
<Pane name="s2" style={{ zIndex: 500 }} />
<Markers category="pokestop" />
<Markers category="spawnpoint" />
<Markers category="gym" />
Expand Down
15 changes: 14 additions & 1 deletion client/src/pages/map/markers/Point.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { Feature, DbOption, KojiKey } from '@assets/types'
import { useShapes } from '@hooks/useShapes'
import { useStatic } from '@hooks/useStatic'
import { usePersist } from '@hooks/usePersist'
import { s2Coverage } from '@services/fetches'

import BasePopup from '../popups/Styled'
import { MemoPointPopup } from '../popups/Point'
Expand Down Expand Up @@ -41,14 +42,26 @@ export function KojiPoint({
circle.on('pm:remove', function remove() {
useShapes.getState().setters.remove(type, id)
})
circle.removeEventListener('pm:drag')
circle.on('pm:drag', async function drag({ layer }) {
if (layer instanceof L.Circle) {
const latlng = layer.getLatLng()
useShapes.setState({
s2cellCoverage: await s2Coverage(id, latlng.lat, latlng.lng),
})
}
})
circle.removeEventListener('pm:dragend')
circle.on('pm:dragend', function dragend({ layer }) {
circle.on('pm:dragend', async function dragend({ layer }) {
if (layer instanceof L.Circle) {
const { lat: newLat, lng: newLon } = circle.getLatLng()
useShapes.getState().setters.update(type, id, {
...useShapes.getState().Point[id],
geometry: { type: 'Point', coordinates: [newLon, newLat] },
})
useShapes.setState({
s2cellCoverage: await s2Coverage(id, newLat, newLon),
})
}
})
if (usePersist.getState().setActiveMode === 'hover') {
Expand Down
47 changes: 36 additions & 11 deletions client/src/pages/map/markers/S2.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
import * as React from 'react'
import { Polyline, useMap } from 'react-leaflet'
import { Polygon, Tooltip, useMap } from 'react-leaflet'
import { KojiResponse } from '@assets/types'
import { fetchWrapper } from '@services/fetches'
import { getMapBounds } from '@services/utils'
import { usePersist } from '@hooks/usePersist'
import { useShapes } from '@hooks/useShapes'
import { useStatic } from '@hooks/useStatic'

interface S2Response {
id: string
coords: [number, number][]
}

function BaseCell({ id, coords }: S2Response) {
function BaseCell({ id, coords, covered }: S2Response & { covered: boolean }) {
return (
<Polyline
key={id}
positions={[...coords, coords[0]]}
color="black"
<Polygon
key={`${id}${covered}`}
pmIgnore
snapIgnore
positions={coords}
color={covered ? 'red' : 'black'}
fillOpacity={covered ? 0.2 : 0}
weight={0.5}
/>
pane="s2"
>
{process.env.NODE_ENV === 'development' && (
<Tooltip direction="center">{id}</Tooltip>
)}
</Polygon>
)
}

const MemoBaseCell = React.memo(BaseCell, (prev, next) => prev.id === next.id)
const MemoBaseCell = React.memo(
BaseCell,
(prev, next) => prev.id === next.id && prev.covered === next.covered,
)

function S2Cell({
function S2Level({
level,
location,
}: {
Expand All @@ -32,6 +45,7 @@ function S2Cell({
}) {
const map = useMap()
const [data, setData] = React.useState<S2Response[]>([])
const covered = useShapes((s) => s.s2cellCoverage)

React.useEffect(() => {
const signal = new AbortController()
Expand All @@ -44,6 +58,17 @@ function S2Cell({
signal: signal.signal,
}).then((res) => {
if (res) {
if (res.data.length === 10_000) {
useStatic.setState({
networkStatus: {
message: `Loaded the maximum of ${Number(
10_000,
).toLocaleString()} Level ${level} S2 cells`,
severity: 'warning',
status: 200,
},
})
}
setData(res.data)
}
})
Expand All @@ -53,14 +78,14 @@ function S2Cell({
return (
<>
{data.map((cell) => (
<MemoBaseCell key={cell.id} {...cell} />
<MemoBaseCell key={cell.id} {...cell} covered={!!covered[cell.id]} />
))}
</>
)
}

const MemoS2Cell = React.memo(
S2Cell,
S2Level,
(prev, next) =>
prev.level === next.level &&
prev.location.every((v, i) => v === next.location[i]),
Expand Down
32 changes: 31 additions & 1 deletion client/src/services/fetches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
} from '@assets/types'
import { UsePersist, usePersist } from '@hooks/usePersist'
import { useStatic } from '@hooks/useStatic'
import { useShapes } from '@hooks/useShapes'
import { UseShapes, useShapes } from '@hooks/useShapes'
import { UseDbCache, useDbCache } from '@hooks/useDbCache'

import { fromSnakeCase, getRouteType } from './utils'
Expand Down Expand Up @@ -416,3 +416,33 @@ export async function save(
return null
}
}

export async function s2Coverage(id: Feature['id'], lat: number, lon: number) {
const { s2cells, radius, fillCoveredCells } = usePersist.getState()
if (fillCoveredCells) {
const s2cellCoverage: UseShapes['s2cellCoverage'] = Object.fromEntries(
Object.entries(useShapes.getState().s2cellCoverage).filter(
([, v]) => v !== id.toString(),
),
)
await Promise.allSettled(
s2cells.map(async (level) =>
fetchWrapper<KojiResponse<string[]>>('/api/v1/s2/circle-coverage', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ lat, lon, radius, level }),
}).then((res) => {
if (res) {
res.data.forEach((cell) => {
if (!s2cellCoverage[cell]) s2cellCoverage[cell] = id.toString()
})
}
}),
),
)
return s2cellCoverage
}
return {}
}
8 changes: 4 additions & 4 deletions server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion server/algorithms/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "algorithms"
version = "0.6.1"
version = "0.6.2"
edition = "2021"
publish = false

Expand All @@ -19,5 +19,6 @@ rand = "0.8.5"
rstar = "0.10.0"
s2 = "0.0.12"
serde = { version = "1.0.136", features = ["derive"] }
# serde_json = "1.0.94"
shuffle = "0.1.7"
# vrp-pragmatic = "1.18.3"
Loading

0 comments on commit 1967b78

Please sign in to comment.