Skip to content

Commit a435508

Browse files
committed
chore(example): add clustering example
1 parent 25c1b52 commit a435508

File tree

8 files changed

+189
-10
lines changed

8 files changed

+189
-10
lines changed

example/ios/Podfile.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,8 @@ PODS:
18191819
- React-RCTFBReactNativeSpec
18201820
- ReactCommon/turbomodule/core
18211821
- SocketRocket
1822+
- react-native-clusterer (4.0.0):
1823+
- React-Core
18221824
- react-native-safe-area-context (5.6.1):
18231825
- boost
18241826
- DoubleConversion
@@ -2795,6 +2797,7 @@ DEPENDENCIES:
27952797
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
27962798
- React-Mapbuffer (from `../node_modules/react-native/ReactCommon`)
27972799
- React-microtasksnativemodule (from `../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)
2800+
- react-native-clusterer (from `../node_modules/react-native-clusterer`)
27982801
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
27992802
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
28002803
- React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
@@ -2928,6 +2931,8 @@ EXTERNAL SOURCES:
29282931
:path: "../node_modules/react-native/ReactCommon"
29292932
React-microtasksnativemodule:
29302933
:path: "../node_modules/react-native/ReactCommon/react/nativemodule/microtasks"
2934+
react-native-clusterer:
2935+
:path: "../node_modules/react-native-clusterer"
29312936
react-native-safe-area-context:
29322937
:path: "../node_modules/react-native-safe-area-context"
29332938
React-NativeModulesApple:
@@ -3052,6 +3057,7 @@ SPEC CHECKSUMS:
30523057
React-logger: 30adf849117e87cf86e88dca1824bb0f18f87e10
30533058
React-Mapbuffer: 2a5edca6905cb1b3a40fb7ed3f4496df4f1bc60e
30543059
React-microtasksnativemodule: 6d775fdf71445f58dbedbd66ed9cb08b48ae2797
3060+
react-native-clusterer: a9526b8fb1d6be10cd9a6d05d7d8b982da7c6abc
30553061
react-native-safe-area-context: ee1e8e2a7abf737a8d4d9d1a5686a7f2e7466236
30563062
React-NativeModulesApple: b2ee5b48020439fd81d1fd9cba40ebf0c3af5636
30573063
React-oscompat: 80ca388c4831481cd03a6b45ecfc82739ca9a95e

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@react-navigation/stack": "7.4.9",
1717
"react": "19.1.1",
1818
"react-native": "0.82.0",
19+
"react-native-clusterer": "4.0.0",
1920
"react-native-gesture-handler": "2.28.0",
2021
"react-native-google-maps-plus": "workspace:*",
2122
"react-native-nitro-modules": "0.30.0",

example/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import IndoorLevelMapScreen from './screens/IndoorLevelMapScreen';
2424
import CameraTestScreen from './screens/CameraTestScreen';
2525
import type { RootStackParamList } from './types/navigation';
2626
import SnapshotTestScreen from './screens/SnaptshotTestScreen';
27+
import ClusteringScreen from './screens/ClsuteringScreen';
2728

2829
const Stack = createStackNavigator<RootStackParamList>();
2930

@@ -112,6 +113,11 @@ export default function App() {
112113
component={SnapshotTestScreen}
113114
options={{ title: 'Snapshot test' }}
114115
/>
116+
<Stack.Screen
117+
name="Clustering"
118+
component={ClusteringScreen}
119+
options={{ title: 'Clustering test' }}
120+
/>
115121
<Stack.Screen
116122
name="Stress"
117123
component={StressTestScreen}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import React, { useRef, useState, useMemo } from 'react';
2+
import MapWrapper from '../components/MapWrapper';
3+
import ControlPanel from '../components/ControlPanel';
4+
import type {
5+
GoogleMapsViewRef,
6+
RNMarker,
7+
RNMarkerSvg,
8+
} from 'react-native-google-maps-plus';
9+
import { useClusterer } from 'react-native-clusterer';
10+
import type Supercluster from 'react-native-clusterer/lib/typescript/types';
11+
import { randomCoordinates } from '../utils/mapGenerators';
12+
13+
export default function ClusteringScreen() {
14+
const mapRef = useRef<GoogleMapsViewRef | null>(null);
15+
const [coordinates] = useState(
16+
Array.from({ length: 500 }, () =>
17+
randomCoordinates(37.7749, -122.4194, 0.2)
18+
)
19+
);
20+
const [region, setRegion] = useState({
21+
center: {
22+
latitude: 37.7749,
23+
longitude: -122.4194,
24+
},
25+
latitudeDelta: 0.4,
26+
longitudeDelta: 0.4,
27+
});
28+
29+
const mapDimensions = useMemo(() => ({ width: 400, height: 800 }), []);
30+
31+
const data = useMemo<
32+
Array<
33+
Supercluster.PointFeature<{
34+
id: string;
35+
svgIcon: RNMarkerSvg;
36+
}>
37+
>
38+
>(
39+
() =>
40+
coordinates.map((e, i) => {
41+
return {
42+
type: 'Feature',
43+
geometry: {
44+
type: 'Point',
45+
coordinates: [e.longitude, e.latitude],
46+
},
47+
properties: {
48+
id: `sf-${i}`,
49+
svgIcon: {
50+
width: 32,
51+
height: 44,
52+
svgString: `
53+
<svg xmlns="http://www.w3.org/2000/svg" width="66" height="88" viewBox="0 0 64 88">
54+
<path
55+
d="M32 2c-14.36 0-26 11.64-26 26 0 18.2 20.67 38.86 24.82 43.02a1.7 1.7 0 0 0 2.36 0C37.33 66.86 58 46.2 58 28 58 13.64 46.36 2 32 2z"
56+
fill="red"
57+
/>
58+
<circle cx="32" cy="28" r="10" fill="#FFFFFF" />
59+
<ellipse cx="32" cy="82" rx="14" ry="4" fill="#000000" opacity="0.15" />
60+
</svg>
61+
`,
62+
},
63+
},
64+
};
65+
}),
66+
[coordinates]
67+
);
68+
69+
const clusterRegion = useMemo(
70+
() => ({
71+
latitude: region.center.latitude,
72+
longitude: region.center.longitude,
73+
latitudeDelta: region.latitudeDelta,
74+
longitudeDelta: region.longitudeDelta,
75+
}),
76+
[region]
77+
);
78+
79+
const clusterOptions = useMemo(
80+
() => ({ radius: 60, maxZoom: 16, minZoom: 0 }),
81+
[]
82+
);
83+
84+
const [points] = useClusterer(
85+
data,
86+
mapDimensions,
87+
clusterRegion,
88+
clusterOptions
89+
);
90+
91+
const markers: RNMarker[] = useMemo(() => {
92+
return points.map((feature, i) => {
93+
const [lng, lat] = feature.geometry.coordinates as [number, number];
94+
const isCluster = 'cluster' in feature.properties;
95+
// @ts-ignore
96+
const count = feature.properties?.point_count ?? 0;
97+
const icon = isCluster
98+
? {
99+
width: 36,
100+
height: 36,
101+
svgString: `<svg viewBox="0 0 64 64" width="48" height="48" xmlns="http://www.w3.org/2000/svg">
102+
<circle cx="32" cy="32" r="28" fill="#7C4DFF"/>
103+
<text x="32" y="40" text-anchor="middle" font-size="22" font-family="Arial" fill="#fff" font-weight="bold">
104+
${count}
105+
</text>
106+
</svg>`,
107+
}
108+
: feature.properties.svgIcon;
109+
110+
console.log(feature);
111+
112+
return {
113+
id: feature.id?.toString() ?? i.toString(),
114+
coordinate: { latitude: lat, longitude: lng },
115+
iconSvg: icon,
116+
} as RNMarker;
117+
});
118+
}, [points]);
119+
120+
return (
121+
<MapWrapper mapRef={mapRef} markers={markers} onCameraChange={setRegion}>
122+
<ControlPanel mapRef={mapRef} buttons={[]} />
123+
</MapWrapper>
124+
);
125+
}

example/src/screens/HomeScreen.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const screens = [
1717
{ name: 'IndoorLevelMap', title: 'Indoor Level Map' },
1818
{ name: 'Camera', title: 'Camera Test' },
1919
{ name: 'Snapshot', title: 'Snapshot Test' },
20+
{ name: 'Clustering', title: 'Clustering' },
2021
{ name: 'Stress', title: 'Stress Test' },
2122
];
2223

example/src/types/navigation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type RootStackParamList = {
1313
IndoorLevelMap: undefined;
1414
Camera: undefined;
1515
Snapshot: undefined;
16+
Clustering: undefined;
1617
Stress: undefined;
1718
};
1819

example/src/utils/mapGenerators.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -204,16 +204,13 @@ export function makeRandomMarkerForStressTest(id: number): RNMarker {
204204
id: id.toString(),
205205
zIndex: id,
206206
coordinate: randomCoordinates(37.7749, -122.4194, 0.2),
207-
anchor: { x: 0.5, y: 1.0 },
208207
draggable: false,
209-
opacity: Math.random(),
210-
flat: customIcon,
211-
rotation: customIcon ? Math.random() * 180 : 0,
212-
title: `Marker title id: ${id}`,
213-
iconSvg: {
214-
width: (64 / 100) * 50,
215-
height: (88 / 100) * 50,
216-
svgString: makeSvgIcon(64, 88),
217-
},
208+
iconSvg: customIcon
209+
? {
210+
width: (64 / 100) * 50,
211+
height: (88 / 100) * 50,
212+
svgString: makeSvgIcon(64, 88),
213+
}
214+
: undefined,
218215
};
219216
}

yarn.lock

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2433,6 +2433,27 @@ __metadata:
24332433
languageName: node
24342434
linkType: hard
24352435

2436+
"@mapbox/geo-viewport@npm:^0.5.0":
2437+
version: 0.5.0
2438+
resolution: "@mapbox/geo-viewport@npm:0.5.0"
2439+
dependencies:
2440+
"@mapbox/sphericalmercator": ^1.2.0
2441+
checksum: 9cb990e177226acbdf7658f3367ed3158d8a77350afb0e1de71fa432e682a8aed4afe2f80d36872eb79666b54432fe6219797d50f2ab4152c51811235b7520a0
2442+
languageName: node
2443+
linkType: hard
2444+
2445+
"@mapbox/sphericalmercator@npm:^1.2.0":
2446+
version: 1.2.0
2447+
resolution: "@mapbox/sphericalmercator@npm:1.2.0"
2448+
bin:
2449+
bbox: bin/bbox.js
2450+
to4326: bin/to4326.js
2451+
to900913: bin/to900913.js
2452+
xyz: bin/xyz.js
2453+
checksum: 515cd9fcadc6626d6352001f6d9dba073fcd10433ceee37d13d4be7fc9a48930b4163b79f53151b71ff9a4410da77bd11c5ff62e3b9d6119bce9031c4d7dabc1
2454+
languageName: node
2455+
linkType: hard
2456+
24362457
"@napi-rs/wasm-runtime@npm:^0.2.11":
24372458
version: 0.2.12
24382459
resolution: "@napi-rs/wasm-runtime@npm:0.2.12"
@@ -3753,6 +3774,13 @@ __metadata:
37533774
languageName: node
37543775
linkType: hard
37553776

3777+
"@types/geojson@npm:^7946.0.8":
3778+
version: 7946.0.16
3779+
resolution: "@types/geojson@npm:7946.0.16"
3780+
checksum: d66e5e023f43b3e7121448117af1930af7d06410a32a585a8bc9c6bb5d97e0d656cd93d99e31fa432976c32e98d4b780f82bf1fd1acd20ccf952eb6b8e39edf2
3781+
languageName: node
3782+
linkType: hard
3783+
37563784
"@types/graceful-fs@npm:^4.1.3":
37573785
version: 4.1.9
37583786
resolution: "@types/graceful-fs@npm:4.1.9"
@@ -11624,6 +11652,19 @@ __metadata:
1162411652
languageName: node
1162511653
linkType: hard
1162611654

11655+
"react-native-clusterer@npm:4.0.0":
11656+
version: 4.0.0
11657+
resolution: "react-native-clusterer@npm:4.0.0"
11658+
dependencies:
11659+
"@mapbox/geo-viewport": ^0.5.0
11660+
"@types/geojson": ^7946.0.8
11661+
peerDependencies:
11662+
react: "*"
11663+
react-native: "*"
11664+
checksum: 4481c0e8aa92ce7fada43231137ea025de593169ab6fa943131666157ed72e64acce26a58334e9b7e67bdd8c9a07ff6ccd15728a474f4b0b574d7eddca5a6a10
11665+
languageName: node
11666+
linkType: hard
11667+
1162711668
"react-native-gesture-handler@npm:2.28.0":
1162811669
version: 2.28.0
1162911670
resolution: "react-native-gesture-handler@npm:2.28.0"
@@ -11658,6 +11699,7 @@ __metadata:
1165811699
react: 19.1.1
1165911700
react-native: 0.82.0
1166011701
react-native-builder-bob: 0.40.13
11702+
react-native-clusterer: 4.0.0
1166111703
react-native-gesture-handler: 2.28.0
1166211704
react-native-google-maps-plus: "workspace:*"
1166311705
react-native-monorepo-config: 0.2.2

0 commit comments

Comments
 (0)