Skip to content
Draft
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
6 changes: 5 additions & 1 deletion config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,10 @@
"__name": "SCANNER_SCAN_NEXT_SCAN_NEXT_SLEEPTIME",
"__format": "number"
},
"nineCellScan": {
"__name": "SCANNER_SCAN_NEXT_NINE_CELL_SCAN",
"__format": "boolean"
},
"userCooldownSeconds": {
"__name": "SCANNER_SCAN_NEXT_USER_COOLDOWN_SECONDS",
"__format": "number"
Expand Down Expand Up @@ -2327,4 +2331,4 @@
}
}
}
}
}
1 change: 1 addition & 0 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@
"scanNextInstance": "scanNext",
"scanNextDevice": "Device01",
"scanNextSleeptime": 5,
"nineCellScan": false,
"userCooldownSeconds": 0,
"scanNextAreaRestriction": [],
"discordRoles": [],
Expand Down
1 change: 1 addition & 0 deletions packages/locales/lib/human/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@
"loading_invasions": "Fetching Invasions",
"scan_next": "Scan Location",
"scan_next_choose": "Drag and Drop the Marker to Set the Scan Location",
"scan_next_size_9x9": "9x9",
"scan_zone": "Scan an Area",
"scan_zone_choose": "Drag and Drop the Marker to Set the Scan Location and Choose the Size",
"scan_zone_size": "Size",
Expand Down
1 change: 1 addition & 0 deletions packages/types/lib/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export type Config<Client extends boolean = false> = DeepMerge<
discordRoles: string[]
telegramGroups: string[]
local: string[]
nineCellScan?: boolean
}
scanZone: {
discordRoles: string[]
Expand Down
2 changes: 2 additions & 0 deletions server/src/graphql/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ const resolvers = {
gymRadius: scanner.scanZone.scanZoneRadius.gym,
spacing: scanner.scanZone.scanZoneSpacing,
maxSize: scanner.scanZone.scanZoneMaxSize,
nineCellScan: false,
cooldown: scanner.scanZone.userCooldownSeconds,
refreshQueue: scanner.backendConfig.queueRefreshInterval,
enabled: scanner[mode].enabled,
Expand All @@ -424,6 +425,7 @@ const resolvers = {
showScanCount: scanner.scanNext.showScanCount,
showScanQueue: scanner.scanNext.showScanQueue,
cooldown: scanner.scanNext.userCooldownSeconds,
nineCellScan: scanner.scanNext.nineCellScan,
refreshQueue: scanner.backendConfig.queueRefreshInterval,
enabled: scanner[mode].enabled,
}
Expand Down
1 change: 1 addition & 0 deletions server/src/graphql/typeDefs/map.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ type ScannerConfig {
gymRadius: Int
spacing: Int
maxSize: Int
nineCellScan: Boolean
refreshQueue: Int
enabled: Boolean
}
Expand Down
1 change: 1 addition & 0 deletions src/features/scanner/ContextProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const DEFAULT = /** @type {import('./hooks/store').ScanConfig} */ ({
gymRadius: 750,
spacing: 1,
maxSize: 1,
nineCellScan: false,
cooldown: 1,
refreshQueue: 5,
})
Expand Down
10 changes: 6 additions & 4 deletions src/features/scanner/ScanOnDemand.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,22 +130,24 @@ function BaseScanOnDemand({ mode }) {
next.scanZoneSize !== prev.scanZoneSize ||
next.scanLocation.some((x, i) => x !== prev.scanLocation[i]))
) {
const scanCoords =
const { coords: scanCoords, mask: scanCircleMask } =
mode === 'scanZone'
? getScanZoneCoords(
next.scanLocation,
next.userRadius,
next.userSpacing,
next.scanZoneSize,
)
: getScanNextCoords(next.scanLocation, next.scanNextSize)
useScanStore.setState({ scanCoords })
: getScanNextCoords(next.scanLocation, next.scanNextSize, {
nineCellScan: config.nineCellScan,
})
useScanStore.setState({ scanCoords, scanCircleMask })
}
})
return () => {
subscription()
}
}, [mode])
}, [mode, config.nineCellScan])

if (scanMode !== 'setLocation' || !config.scannerType) return null

Expand Down
4 changes: 4 additions & 0 deletions src/features/scanner/Shared.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,13 @@ export function ScanCircles({ radius }) {
const scanCoords = useScanStore((s) => s.scanCoords)
const userRadius = useScanStore((s) => s.userRadius)
const validCoords = useScanStore((s) => s.validCoords)
const scanCircleMask = useScanStore((s) => s.scanCircleMask)

const finalRadius = radius || userRadius
return scanCoords.map((coords, i) => {
if (scanCircleMask.length && !scanCircleMask[i]) {
return null
}
const finalColor =
finalRadius <= 70
? validCoords[i]
Expand Down
3 changes: 3 additions & 0 deletions src/features/scanner/hooks/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { createJSONStorage, persist } from 'zustand/middleware'
* gymRadius: number,
* spacing: number,
* maxSize: number,
* nineCellScan: boolean,
* cooldown: number,
* refreshQueue: number
* enabled: boolean,
Expand All @@ -25,6 +26,7 @@ import { createJSONStorage, persist } from 'zustand/middleware'
* queue: 'init' | '...' | number,
* scanLocation: [number, number],
* scanCoords: [number, number][],
* scanCircleMask: boolean[],
* validCoords: boolean[],
* scanNextSize: 'S' | 'M' | 'L' | 'XL',
* scanZoneSize: number,
Expand All @@ -43,6 +45,7 @@ export const useScanStore = create((set) => ({
queue: 'init',
scanLocation: [0, 0],
scanCoords: [],
scanCircleMask: [],
validCoords: [],
scanNextSize: 'S',
scanZoneSize: 1,
Expand Down
4 changes: 3 additions & 1 deletion src/features/scanner/scanNext/PopupContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { useTranslation } from 'react-i18next'
import { SCAN_SIZES } from '@assets/constants'

import { useScanStore } from '../hooks/store'
import { ConfigContext } from '../ContextProvider'

export function ScanNextPopup() {
const { t } = useTranslation()
const scanNextSize = useScanStore((s) => s.scanNextSize)
const { nineCellScan } = React.useContext(ConfigContext)

const setSize = React.useCallback(
(/** @type {typeof SCAN_SIZES[number]} */ size) => () => {
Expand All @@ -30,7 +32,7 @@ export function ScanNextPopup() {
color={size === scanNextSize ? 'primary' : 'secondary'}
variant={size === scanNextSize ? 'contained' : 'outlined'}
>
{t(size)}
{size === 'XL' && nineCellScan ? t('scan_next_size_9x9') : t(size)}
</Button>
))}
</ButtonGroup>
Expand Down
103 changes: 92 additions & 11 deletions src/features/scanner/scanNext/getCoords.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,116 @@
// @ts-check
import { point } from '@turf/helpers'
import destination from '@turf/destination'
import { S2CellId, S2LatLng } from 'nodes2ts'

/**
* @typedef {{ coords: import('../hooks/store').UseScanStore['scanCoords'], mask: boolean[] }} ScanCoordResult
*/

const OPTIONS = /** @type {const} */ ({ units: 'kilometers' })
const POKEMON_RADIUS = 70
const GYM_RADIUS = 750

const DISTANCE = {
const HEX_DISTANCE = {
M: POKEMON_RADIUS * 1.732,
XL: GYM_RADIUS * 1.732,
}

const NINE_CELL_LEVEL = 15
const NINE_CELL_SIZE = 9
const NINE_CELL_RADIUS = (NINE_CELL_SIZE - 1) / 2

/**
* Build a 9x9 grid of S2 level-15 cell centers around the selected point.
* @param {[number, number]} center
* @returns {ScanCoordResult}
*/
const getNineCellCoords = (center) => {
const latLng = S2LatLng.fromDegrees(center[0], center[1])
const baseCell = S2CellId.fromPoint(latLng.toPoint()).parentL(NINE_CELL_LEVEL)
const size = baseCell.getSizeIJ()
const baseIJO = baseCell.toIJOrientation()
const baseI = S2CellId.getI(baseIJO)
const baseJ = S2CellId.getJ(baseIJO)
const { face } = baseCell

const offsets = []
for (let di = -NINE_CELL_RADIUS; di <= NINE_CELL_RADIUS; di += 1) {
for (let dj = -NINE_CELL_RADIUS; dj <= NINE_CELL_RADIUS; dj += 1) {
offsets.push({
di,
dj,
isPerimeter:
Math.abs(di) === NINE_CELL_RADIUS ||
Math.abs(dj) === NINE_CELL_RADIUS,
manhattan: Math.abs(di) + Math.abs(dj),
chebyshev: Math.max(Math.abs(di), Math.abs(dj)),
})
}
}

offsets.sort((a, b) => {
if (a.manhattan !== b.manhattan) return a.manhattan - b.manhattan
if (a.chebyshev !== b.chebyshev) return a.chebyshev - b.chebyshev
if (a.di !== b.di) return a.di - b.di
return a.dj - b.dj
})

const coords = offsets.map(({ di, dj }) => {
const targetI = baseI + di * size
const targetJ = baseJ + dj * size
const sameFace =
targetI >= 0 &&
targetI < S2CellId.MAX_SIZE &&
targetJ >= 0 &&
targetJ < S2CellId.MAX_SIZE
const cell = (
sameFace
? S2CellId.fromFaceIJ(face, targetI, targetJ)
: S2CellId.fromFaceIJWrap(face, targetI, targetJ)
).parentL(NINE_CELL_LEVEL)
const pointLatLng = cell.toLatLng()
return [pointLatLng.latDegrees, pointLatLng.lngDegrees]
})

const mask = offsets.map(({ isPerimeter }) => isPerimeter)

return { coords, mask }
}

/**
* Get scan next coords
* @param {[number, number]} center
* @param {import('../hooks/store').UseScanStore['scanNextSize']} size
* @returns {import('../hooks/store').UseScanStore['scanCoords']}
* @param {{ nineCellScan?: boolean }} [options]
* @returns {ScanCoordResult}
*/
export const getScanNextCoords = (center, size) => {
export const getScanNextCoords = (center, size, options = {}) => {
if (size === 'XL' && options.nineCellScan) {
return getNineCellCoords(center)
}

const coords = [center]
if (size === 'S') return coords
if (size === 'S') {
return { coords, mask: [true] }
}

const distance = HEX_DISTANCE[size]
if (!distance) {
return { coords, mask: coords.map(() => true) }
}

const start = point([center[1], center[0]])
return coords.concat(
const extended = coords.concat(
[0, 60, 120, 180, 240, 300].map((bearing) => {
const [lon, lat] = destination(
start,
DISTANCE[size] / 1000,
bearing,
OPTIONS,
).geometry.coordinates
const [lon, lat] = destination(start, distance / 1000, bearing, OPTIONS)
.geometry.coordinates
return [lat, lon]
}),
)

return {
coords: extended,
mask: extended.map(() => true),
}
}
7 changes: 5 additions & 2 deletions src/features/scanner/scanZone/getCoords.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const BEARINGS = {
* @param {number} radius
* @param {number} spacing
* @param {import('../hooks/store').UseScanStore['scanZoneSize']} scanZoneSize
* @returns
* @returns {{ coords: import('../hooks/store').UseScanStore['scanCoords'], mask: boolean[] }}
*/
export const getScanZoneCoords = (center, radius, spacing, scanZoneSize) => {
const coords = [center]
Expand All @@ -43,5 +43,8 @@ export const getScanZoneCoords = (center, radius, spacing, scanZoneSize) => {
step += 1
}
}
return coords
return {
coords,
mask: coords.map(() => true),
}
}
1 change: 1 addition & 0 deletions src/services/queries/scanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const SCANNER_CONFIG = gql`
gymRadius
spacing
maxSize
nineCellScan
refreshQueue
enabled
}
Expand Down
Loading