Skip to content

Commit 425f840

Browse files
committed
feat: better nearby pokemon display and randomization
1 parent 23c07c7 commit 425f840

File tree

5 files changed

+279
-110
lines changed

5 files changed

+279
-110
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
"leaflet-arrowheads": "^1.4.0",
153153
"leaflet.locatecontrol": "0.81.0",
154154
"lodash": "^4.17.21",
155+
"long": "^4.0.0",
155156
"moment-timezone": "^0.5.43",
156157
"mysql2": "3.11.0",
157158
"node-cache": "^5.1.2",

src/features/pokemon/PokemonTile.jsx

Lines changed: 145 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/* eslint-disable react/destructuring-assignment */
22
// @ts-check
33
import * as React from 'react'
4-
import { Marker, Popup, Circle } from 'react-leaflet'
4+
import { Marker, Popup, Circle, Polygon } from 'react-leaflet'
55
import { t } from 'i18next'
6+
import { S2CellId, S2LatLng } from 'nodes2ts'
67

78
import { useMarkerTimer } from '@hooks/useMarkerTimer'
8-
import { getOffset } from '@utils/offset'
9+
import { NEARBY_CELL_LEVEL, getOffset } from '@utils/offset'
910
import { getBadge } from '@utils/getBadge'
1011
import { basicEqualFn, useMemory } from '@store/useMemory'
1112
import { useStorage } from '@store/useStorage'
@@ -16,10 +17,14 @@ import { sendNotification } from '@services/desktopNotification'
1617
import { useMapStore } from '@store/useMapStore'
1718
import { TooltipWrapper } from '@components/ToolTipWrapper'
1819
import { getTimeUntil } from '@utils/getTimeUntil'
20+
import { normalizeCategory } from '@utils/normalizeCategory'
21+
import { getS2Polygon } from '@utils/getS2Polygon'
1922

2023
import { PokemonPopup } from './PokemonPopup'
2124
import { basicPokemonMarker, fancyPokemonMarker } from './pokemonMarker'
2225

26+
const INTERACTION_RANGE_COLOR = '#BA42F6'
27+
2328
/**
2429
*
2530
* @param {import('@rm/types').Pokemon} pkmn
@@ -110,18 +115,31 @@ const BasePokemonTile = (pkmn) => {
110115
]
111116
}, basicEqualFn)
112117

118+
const manualParams = useMemory((s) => s.manualParams)
119+
120+
const isNearbyStop = pkmn.seen_type === 'nearby_stop'
121+
const isLure = pkmn.seen_type?.includes('lure')
122+
const isNearbyCell = pkmn.seen_type === 'nearby_cell'
123+
124+
const isPopupOpen = React.useMemo(() => {
125+
if (!manualParams) return false
126+
if (normalizeCategory(manualParams.category) !== 'pokemon') {
127+
return false
128+
}
129+
return `${manualParams.id}` === `${pkmn.id}`
130+
}, [manualParams, pkmn.id])
131+
113132
/** @type {[number, number]} */
114-
const finalLocation = React.useMemo(
115-
() =>
116-
pkmn.seen_type?.startsWith('nearby') || pkmn.seen_type?.includes('lure')
117-
? getOffset(
118-
[pkmn.lat, pkmn.lon],
119-
pkmn.seen_type === 'nearby_cell' ? 0.0002 : 0.00015,
120-
pkmn.id,
121-
)
122-
: [pkmn.lat, pkmn.lon],
123-
[pkmn.seen_type, pkmn.lat, pkmn.lon],
124-
)
133+
const finalLocation = React.useMemo(() => {
134+
const seenType = pkmn.seen_type
135+
return seenType?.startsWith('nearby')
136+
? getOffset({
137+
coords: /** @type {[number, number]} */ ([pkmn.lat, pkmn.lon]),
138+
id: pkmn.id,
139+
seenType,
140+
})
141+
: [pkmn.lat, pkmn.lon]
142+
}, [pkmn.id, pkmn.seen_type, pkmn.lat, pkmn.lon])
125143

126144
/** @type {(string | import('react').ReactElement)[]} */
127145
const extras = React.useMemo(() => {
@@ -140,6 +158,18 @@ const BasePokemonTile = (pkmn) => {
140158
useForcePopup(pkmn.id, markerRef)
141159
useMarkerTimer(pkmn.expire_timestamp, markerRef)
142160
const handlePopupOpen = useManualPopupTracker('pokemon', pkmn.id)
161+
162+
const nearbyCellPolygon = React.useMemo(() => {
163+
if (isNearbyCell) {
164+
return getS2Polygon(
165+
S2CellId.fromPoint(
166+
S2LatLng.fromDegrees(pkmn.lat, pkmn.lon).toPoint(),
167+
).parentL(NEARBY_CELL_LEVEL),
168+
)
169+
}
170+
return null
171+
}, [isNearbyCell, pkmn.lat, pkmn.lon])
172+
143173
sendNotification(
144174
pkmn.id,
145175
`${t(`poke_${pkmn.pokemon_id}`)}${
@@ -164,70 +194,110 @@ const BasePokemonTile = (pkmn) => {
164194
}
165195

166196
return (
167-
<Marker
168-
ref={setMarkerRef}
169-
zIndexOffset={
170-
(typeof pkmn.iv === 'number' ? pkmn.iv || 99 : 0) * 100 +
171-
40.96 -
172-
pkmn.bestPvp
173-
}
174-
position={finalLocation}
175-
icon={
176-
(pkmn.bestPvp !== null && pkmn.bestPvp < 4 && extras.length === 0) ||
177-
showGlow ||
178-
showWeather ||
179-
opacity < 1 ||
180-
pkmn.seen_type === 'nearby_cell'
181-
? fancyPokemonMarker({
182-
pkmn,
183-
iconUrl,
184-
iconSize,
185-
showGlow,
186-
showWeather,
187-
badge: extras.length ? null : badge,
188-
opacity,
189-
timeOfDay,
190-
})
191-
: basicPokemonMarker({ iconUrl, iconSize })
192-
}
193-
eventHandlers={{ popupopen: handlePopupOpen }}
194-
>
195-
<Popup position={finalLocation}>
196-
<PokemonPopup pokemon={pkmn} iconUrl={iconUrl} />
197-
</Popup>
198-
{(showTimer || timerOverride || extras.length > 0) && (
199-
<TooltipWrapper
200-
timers={showTimer || timerOverride ? [pkmn.expire_timestamp] : []}
201-
offset={[0, 14]}
202-
>
203-
{extras.length > 0 && (
204-
<div className="iv-badge flex-center">
205-
{extras.map((val, i) => (
206-
<span
207-
key={typeof val === 'string' ? val : val.key}
208-
className="flex-center"
209-
>
210-
{i ? <>&nbsp;|&nbsp;</> : null}
211-
{val}
212-
</span>
213-
))}
214-
</div>
215-
)}
216-
</TooltipWrapper>
217-
)}
218-
{showInteractionRange && configZoom && (
219-
<Circle center={finalLocation} radius={40} color="#BA42F6" weight={1} />
220-
)}
221-
{showSpacialRendRange && configZoom && (
197+
<>
198+
{isPopupOpen && !!nearbyCellPolygon ? (
199+
<Polygon
200+
positions={nearbyCellPolygon}
201+
pathOptions={{
202+
color: INTERACTION_RANGE_COLOR,
203+
weight: 1,
204+
opacity: 1,
205+
fillColor: INTERACTION_RANGE_COLOR,
206+
fillOpacity: 0.2,
207+
}}
208+
interactive={false}
209+
/>
210+
) : null}
211+
{isPopupOpen && isNearbyStop ? (
222212
<Circle
223-
center={finalLocation}
224-
radius={80}
225-
color="#4E893E"
226-
dashArray="5, 5"
227-
weight={1}
213+
center={[pkmn.lat, pkmn.lon]}
214+
radius={40}
215+
pathOptions={{
216+
color: INTERACTION_RANGE_COLOR,
217+
weight: 1,
218+
opacity: 1,
219+
fillColor: INTERACTION_RANGE_COLOR,
220+
fillOpacity: 0.2,
221+
}}
222+
interactive={false}
228223
/>
229-
)}
230-
</Marker>
224+
) : null}
225+
<Marker
226+
ref={setMarkerRef}
227+
zIndexOffset={
228+
(typeof pkmn.iv === 'number' ? pkmn.iv || 99 : 0) * 100 +
229+
40.96 -
230+
pkmn.bestPvp
231+
}
232+
position={finalLocation}
233+
icon={
234+
(pkmn.bestPvp !== null && pkmn.bestPvp < 4 && extras.length === 0) ||
235+
showGlow ||
236+
showWeather ||
237+
opacity < 1 ||
238+
pkmn.seen_type === 'nearby_cell'
239+
? fancyPokemonMarker({
240+
pkmn,
241+
iconUrl,
242+
iconSize,
243+
showGlow,
244+
showWeather,
245+
badge: extras.length ? null : badge,
246+
opacity,
247+
timeOfDay,
248+
})
249+
: basicPokemonMarker({ iconUrl, iconSize })
250+
}
251+
eventHandlers={{ popupopen: handlePopupOpen }}
252+
>
253+
<Popup position={finalLocation}>
254+
<PokemonPopup pokemon={pkmn} iconUrl={iconUrl} />
255+
</Popup>
256+
{(showTimer || timerOverride || extras.length > 0) && (
257+
<TooltipWrapper
258+
timers={showTimer || timerOverride ? [pkmn.expire_timestamp] : []}
259+
offset={[0, 14]}
260+
>
261+
{extras.length > 0 && (
262+
<div className="iv-badge flex-center">
263+
{extras.map((val, i) => (
264+
<span
265+
key={typeof val === 'string' ? val : val.key}
266+
className="flex-center"
267+
>
268+
{i ? <>&nbsp;|&nbsp;</> : null}
269+
{val}
270+
</span>
271+
))}
272+
</div>
273+
)}
274+
</TooltipWrapper>
275+
)}
276+
{!isNearbyStop &&
277+
!isNearbyCell &&
278+
(showInteractionRange || (isLure && isPopupOpen)) &&
279+
configZoom && (
280+
<Circle
281+
center={[pkmn.lat, pkmn.lon]}
282+
radius={40}
283+
color={INTERACTION_RANGE_COLOR}
284+
weight={1}
285+
/>
286+
)}
287+
{!isNearbyStop &&
288+
!isNearbyCell &&
289+
showSpacialRendRange &&
290+
configZoom && (
291+
<Circle
292+
center={[pkmn.lat, pkmn.lon]}
293+
radius={80}
294+
color="#4E893E"
295+
dashArray="5, 5"
296+
weight={1}
297+
/>
298+
)}
299+
</Marker>
300+
</>
231301
)
232302
}
233303

src/features/s2cell/GenerateCells.jsx

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
// @ts-check
22
import * as React from 'react'
3-
import {
4-
S2LatLng,
5-
S2RegionCoverer,
6-
S2LatLngRect,
7-
S2Cell,
8-
S2Point,
9-
} from 'nodes2ts'
3+
import { S2LatLng, S2RegionCoverer, S2LatLngRect } from 'nodes2ts'
104

115
import { useMemory } from '@store/useMemory'
126
import { useStorage } from '@store/useStorage'
137
import { Notification } from '@components/Notification'
148
import { getQueryArgs } from '@utils/getQueryArgs'
9+
import { getS2Polygon } from '@utils/getS2Polygon'
1510

1611
import { BaseCell } from './BaseCell'
1712

@@ -39,20 +34,16 @@ export function GenerateCells() {
3934
)
4035
regionCoverer.setMinLevel(level)
4136
regionCoverer.setMaxLevel(level)
42-
return regionCoverer.getCoveringCells(region).map((cell) => {
43-
const s2cell = new S2Cell(cell)
44-
/** @type {import('@rm/types').S2Polygon} */
45-
const poly = []
46-
for (let i = 0; i <= 3; i += 1) {
47-
const coordinate = s2cell.getVertex(i)
48-
const point = new S2Point(coordinate.x, coordinate.y, coordinate.z)
49-
const latLng = S2LatLng.fromPoint(point)
50-
poly.push([latLng.latDegrees, latLng.lngDegrees])
51-
}
52-
return {
53-
id: cell.id.toString(),
54-
coords: poly,
55-
}
37+
return regionCoverer.getCoveringCells(region).flatMap((cell) => {
38+
const coords = getS2Polygon(cell)
39+
return coords
40+
? [
41+
{
42+
id: cell.id.toString(),
43+
coords,
44+
},
45+
]
46+
: []
5647
})
5748
})
5849
}, [filter, location, zoom, color])

src/utils/getS2Polygon.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @ts-check
2+
import { S2Cell, S2CellId, S2LatLng, S2Point } from 'nodes2ts'
3+
4+
/**
5+
* Convert an S2 cell into an array of lat/lon pairs describing its polygon.
6+
*
7+
* @param {S2CellId | string} cellId
8+
* @returns {import('@rm/types').S2Polygon | null}
9+
*/
10+
export const getS2Polygon = (cellId) => {
11+
try {
12+
const id = typeof cellId === 'string' ? new S2CellId(cellId) : cellId
13+
const cell = new S2Cell(id)
14+
/** @type {import('@rm/types').S2Polygon} */
15+
const polygon = []
16+
for (let i = 0; i < 4; i += 1) {
17+
const vertex = cell.getVertex(i)
18+
const point = new S2Point(vertex.x, vertex.y, vertex.z)
19+
const latLng = S2LatLng.fromPoint(point)
20+
polygon.push([latLng.latDegrees, latLng.lngDegrees])
21+
}
22+
return polygon
23+
} catch {
24+
return null
25+
}
26+
}

0 commit comments

Comments
 (0)