Skip to content

Commit

Permalink
Merge pull request #228 from TurtIeSocks/center-clusters
Browse files Browse the repository at this point in the history
feat: API Option to Center Clusters on Points
  • Loading branch information
TurtIeSocks authored Jul 25, 2024
2 parents 3a2a964 + 6271497 commit 34ea1de
Show file tree
Hide file tree
Showing 17 changed files with 438 additions and 71 deletions.
1 change: 1 addition & 0 deletions client/src/components/drawer/Routing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export default function RoutingTab() {
<Collapse in={!CLUSTERING_MODES.some((m) => m === cluster_mode)}>
<UserTextInput field="clustering_args" helperText="--x 1 --y abc" />
</Collapse>
<Toggle field="center_clusters" label="Center Clusters" />
</Collapse>

<Divider sx={{ my: 2 }} />
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 @@ -75,6 +75,7 @@ export interface UsePersist {
routing_args: string
clustering_args: string
bootstrapping_args: string
center_clusters: boolean
// generations: number | ''
// routing_time: number | ''
// devices: number | ''
Expand Down Expand Up @@ -121,6 +122,7 @@ export const usePersist = create(
radius: 70,
route_split_level: 0,
cluster_split_level: 0,
center_clusters: false,
// routing_chunk_size: 0,
calculation_mode: 'Radius',
s2_level: 15,
Expand Down
14 changes: 4 additions & 10 deletions client/src/hooks/usePixi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import { useEffect, useState } from 'react'
import geohash from 'ngeohash'
import seed from 'seedrandom'
import { shallow } from 'zustand/shallow'

import 'leaflet-pixi-overlay'
Expand All @@ -15,26 +14,21 @@ import * as PIXI from 'pixi.js'
import { useMap } from 'react-leaflet'

import { PixiMarker } from '@assets/types'
import { getDataPointColor } from '@services/utils'

import { ICON_SVG } from '../assets/constants'
import { usePersist } from './usePersist'

const colorMap: Map<string, string> = new Map()

PIXI.settings.FAIL_IF_MAJOR_PERFORMANCE_CAVEAT = false
PIXI.utils.skipHello()

const PIXILoader = PIXI.Loader.shared

function getHashSvg(hash: string) {
let color = colorMap.get(hash)
if (!color) {
const rng = seed(hash)
color = `#${rng().toString(16).slice(2, 8)}`
colorMap.set(hash, color)
}
return `<svg xmlns="http://www.w3.org/2000/svg" id="${hash}" width="15" height="15" viewBox="-2 -2 24 24">
<circle cx="10" cy="10" r="10" fill="${color}" fill-opacity="0.8" stroke="black" stroke-width="1" />
<circle cx="10" cy="10" r="10" fill="${getDataPointColor(
hash,
)}" fill-opacity="0.8" stroke="black" stroke-width="1" />
<circle cx="10" cy="10" r="1" fill="black" />
</svg>`
}
Expand Down
140 changes: 81 additions & 59 deletions client/src/pages/map/markers/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect } from 'react'
import { Circle } from 'react-leaflet'
import { Circle, useMap } from 'react-leaflet'
import geohash from 'ngeohash'
import { shallow } from 'zustand/shallow'

Expand All @@ -10,8 +10,12 @@ import { useStatic } from '@hooks/useStatic'
import useDeepCompareEffect from 'use-deep-compare-effect'
import { getMarkers } from '@services/fetches'
import { Category, PixiMarker } from '@assets/types'
import { getDataPointColor } from '@services/utils'

import StyledPopup from '../popups/Styled'
// import { GeohashMarker } from './Geohash'
import { GeohashMarker } from './Geohash'

const DEBUG_HASHES: string[] = []

export default function Markers({ category }: { category: Category }) {
const enabled = usePersist((s) => s[category], shallow)
Expand All @@ -20,10 +24,13 @@ export default function Markers({ category }: { category: Category }) {
const last_seen = usePersist((s) => s.last_seen)
const pokestopRange = usePersist((s) => s.pokestopRange)
const tth = usePersist((s) => s.tth)
const colorByGeoHash = usePersist((s) => s.colorByGeohash)
const geohashPrecision = usePersist((s) => s.geohashPrecision)

const updateButton = useStatic((s) => s.updateButton)
const bounds = useStatic((s) => s.bounds)
const geojson = useStatic((s) => s.geojson)
const showCenterCircle = useMap().getZoom() > 15

const [markers, setMarkers] = React.useState<PixiMarker[]>([])
const [focused, setFocused] = React.useState(true)
Expand Down Expand Up @@ -84,63 +91,78 @@ export default function Markers({ category }: { category: Category }) {
}
}, [memoSetFocused])

return nativeLeaflet ? (
return (
<>
{/* <GeohashMarker hash="u14cu1dtdx2s" /> */}
{markers.map((i) => {
const hash = geohash.encode(...i.p, 12)
return (
<React.Fragment key={hash}>
{pokestopRange && (
<Circle
center={i.p}
radius={70}
opacity={0.2}
fillOpacity={0.2}
fillColor="darkgreen"
color="green"
pane="dev_markers"
pmIgnore
snapIgnore
/>
)}
<Circle
center={i.p}
radius={ICON_RADIUS[i.i[0]]}
fillOpacity={0.8}
opacity={0.8}
fillColor={hash === 'u14cu1d9f6s1' ? 'pink' : ICON_COLOR[i.i[0]]}
color="black"
pane="dev_markers"
pmIgnore
snapIgnore
>
<StyledPopup>
<div>
Lat: {i.p[0]}
<br />
Lng: {i.p[1]}
<br />
Hash: {geohash.encode(...i.p, 9)}
<br />
Hash: {geohash.encode(...i.p, 12)}
</div>
</StyledPopup>
</Circle>
<Circle
center={i.p}
radius={1}
pathOptions={{
fillColor: 'black',
color: 'black',
}}
pane="dev_markers"
pmIgnore
snapIgnore
/>
</React.Fragment>
)
})}
{DEBUG_HASHES.map((hash) => (
<GeohashMarker key={hash} hash={hash} />
))}
{nativeLeaflet ? (
<>
{markers.map((i) => {
const uniqueHash = geohash.encode(...i.p, 12)
const groupHash = geohash.encode(...i.p, geohashPrecision)
return (
<React.Fragment
key={`${uniqueHash}${geohashPrecision}${colorByGeoHash}`}
>
{pokestopRange && (
<Circle
center={i.p}
radius={70}
opacity={0.2}
fillOpacity={0.2}
fillColor="darkgreen"
color="green"
pane="dev_markers"
pmIgnore
snapIgnore
/>
)}
<Circle
center={i.p}
radius={ICON_RADIUS[i.i[0]]}
fillOpacity={0.8}
opacity={0.8}
fillColor={
colorByGeoHash
? getDataPointColor(groupHash)
: ICON_COLOR[i.i[0]]
}
color="black"
pane="dev_markers"
pmIgnore
snapIgnore
>
<StyledPopup>
<div>
Lat: {i.p[0]}
<br />
Lng: {i.p[1]}
<br />
Hash: {groupHash}
<br />
Hash: {uniqueHash}
</div>
</StyledPopup>
</Circle>
{showCenterCircle && (
<Circle
center={i.p}
radius={1}
pathOptions={{
fillColor: 'black',
color: 'black',
}}
pane="dev_markers"
pmIgnore
snapIgnore
/>
)}
</React.Fragment>
)
})}
</>
) : null}
</>
) : null
)
}
2 changes: 2 additions & 0 deletions client/src/services/fetches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export async function clusteringRouting({
const {
mode,
radius,
center_clusters,
cluster_mode,
category: rawCategory,
min_points,
Expand Down Expand Up @@ -234,6 +235,7 @@ export async function clusteringRouting({
`${area.geometry.type}${area.id ? `-${area.id}` : ''}`,
last_seen: Math.floor((last_seen?.getTime?.() || 0) / 1000),
radius,
center_clusters,
min_points,
cluster_mode,
parent,
Expand Down
14 changes: 14 additions & 0 deletions client/src/services/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { capitalize } from '@mui/material'
import type { MultiPoint, MultiPolygon, Point, Polygon } from 'geojson'
import union from '@turf/union'
import bbox from '@turf/bbox'
import seed from 'seedrandom'

import { useStatic } from '@hooks/useStatic'
import booleanPointInPolygon from '@turf/boolean-point-in-polygon'
import { useShapes } from '@hooks/useShapes'
Expand Down Expand Up @@ -305,3 +307,15 @@ export function getPointColor(
? VECTOR_COLORS.GREEN
: VECTOR_COLORS.BLUE
}

const colorMap: Map<string, string> = new Map()
const rng = seed()

export function getDataPointColor(hash: string) {
let color = colorMap.get(hash)
if (!color) {
color = `#${rng().toString(16).slice(2, 8)}`
colorMap.set(hash, color)
}
return color
}
4 changes: 4 additions & 0 deletions docs/pages/api-reference/body.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ pub struct Args {
///
/// Default: `All`
pub tth: Option<SpawnpointTth>,
/// If true, attempts to center clusters based on the points they cover
///
/// Default: `false`
pub center_clusters: Option<bool>,
}
```

Expand Down
9 changes: 7 additions & 2 deletions server/algorithms/src/clustering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub fn main(
s2_size: u8,
collection: FeatureCollection,
clustering_args: &str,
center_clusters: bool,
) -> SingleVec {
if data_points.is_empty() {
return vec![];
Expand Down Expand Up @@ -84,9 +85,13 @@ pub fn main(
}
},
};

let clusters = if center_clusters {
sec::with_data(radius, data_points, &clusters)
} else {
clusters
};
stats.set_cluster_time(time);
stats.cluster_stats(radius, &data_points, &clusters);
stats.cluster_stats(radius, data_points, &clusters);
stats.set_score();

clusters
Expand Down
1 change: 1 addition & 0 deletions server/algorithms/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ mod project;
pub mod routing;
mod rtree;
pub mod s2;
mod sec;
pub mod stats;
pub mod utils;
Loading

0 comments on commit 34ea1de

Please sign in to comment.