Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: API Option to Center Clusters on Points #228

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading