Skip to content

Commit ddcfccf

Browse files
authored
feat: add heatmap support
2 parents 6593636 + 96a3a08 commit ddcfccf

17 files changed

+318
-38
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ React-native wrapper for android & IOS google maps sdk
2222
yarn add react-native-google-maps-plus react-native-nitro-modules
2323
```
2424

25-
Dependencies
25+
# Dependencies
2626

27-
This package builds on native SVG rendering libraries:
27+
This package builds on native libraries for SVG rendering and Google Maps integration:
2828

29-
iOS: [SVGKit](https://github.com/SVGKit/SVGKit)
30-
31-
Android: [AndroidSVG](https://bigbadaboom.github.io/androidsvg/)
29+
- **iOS**: [SVGKit](https://github.com/SVGKit/SVGKit)
30+
- **Android**: [AndroidSVG](https://bigbadaboom.github.io/androidsvg/)
31+
- **iOS Maps SDK**: [Google Maps SDK for iOS](https://developers.google.com/maps/documentation/ios-sdk)
32+
- **Android Maps SDK**: [Google Maps SDK for Android](https://developers.google.com/maps/documentation/android-sdk)
33+
- **Maps Utility Libraries**: [Google Maps Utils for iOS](https://developers.google.com/maps/documentation/ios-sdk/utility) and [Google Maps Utils for Android](https://developers.google.com/maps/documentation/android-sdk/utility)
3234

3335
These are automatically linked when you install the package, but you may need to clean/rebuild your native projects after first install.
3436

RNGoogleMapsPlus.podspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ Pod::Spec.new do |s|
2626
s.dependency 'React-jsi'
2727
s.dependency 'React-callinvoker'
2828

29-
s.dependency 'GoogleMaps', '10.3.0'
29+
s.dependency 'GoogleMaps', '10.4.0'
30+
s.dependency 'Google-Maps-iOS-Utils', '6.1.3'
3031
s.dependency 'SVGKit', '3.0.0'
3132

3233
load 'nitrogen/generated/ios/RNGoogleMapsPlus+autolinking.rb'

android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import com.google.android.gms.maps.model.Polygon
2525
import com.google.android.gms.maps.model.PolygonOptions
2626
import com.google.android.gms.maps.model.Polyline
2727
import com.google.android.gms.maps.model.PolylineOptions
28+
import com.google.android.gms.maps.model.TileOverlay
29+
import com.google.android.gms.maps.model.TileOverlayOptions
2830
import com.rngooglemapsplus.extensions.toGooglePriority
2931
import com.rngooglemapsplus.extensions.toLocationErrorCode
3032

@@ -52,11 +54,13 @@ class GoogleMapsViewImpl(
5254
private val pendingPolylines = mutableListOf<Pair<String, PolylineOptions>>()
5355
private val pendingPolygons = mutableListOf<Pair<String, PolygonOptions>>()
5456
private val pendingCircles = mutableListOf<Pair<String, CircleOptions>>()
57+
private val pendingHeatmaps = mutableListOf<Pair<String, TileOverlayOptions>>()
5558

5659
private val markersById = mutableMapOf<String, Marker>()
5760
private val polylinesById = mutableMapOf<String, Polyline>()
5861
private val polygonsById = mutableMapOf<String, Polygon>()
5962
private val circlesById = mutableMapOf<String, Circle>()
63+
private val heatmapsById = mutableMapOf<String, TileOverlay>()
6064

6165
private var cameraMoveReason = -1
6266
private var lastSubmittedLocation: Location? = null
@@ -332,6 +336,13 @@ class GoogleMapsViewImpl(
332336
}
333337
pendingCircles.clear()
334338
}
339+
340+
if (pendingHeatmaps.isNotEmpty()) {
341+
pendingHeatmaps.forEach { (id, opts) ->
342+
internalAddHeatmap(id, opts)
343+
}
344+
pendingHeatmaps.clear()
345+
}
335346
}
336347

337348
var uiSettings: RNMapUiSettings? = null
@@ -772,13 +783,56 @@ class GoogleMapsViewImpl(
772783
pendingCircles.clear()
773784
}
774785

786+
fun addHeatmap(
787+
id: String,
788+
opts: TileOverlayOptions,
789+
) {
790+
if (googleMap == null) {
791+
pendingHeatmaps.add(id to opts)
792+
return
793+
}
794+
795+
onUi {
796+
heatmapsById.remove(id)?.remove()
797+
}
798+
internalAddHeatmap(id, opts)
799+
}
800+
801+
private fun internalAddHeatmap(
802+
id: String,
803+
opts: TileOverlayOptions,
804+
) {
805+
onUi {
806+
val heatmap =
807+
googleMap?.addTileOverlay(opts)
808+
if (heatmap != null) {
809+
heatmapsById[id] = heatmap
810+
}
811+
}
812+
}
813+
814+
fun removeHeatmap(id: String) {
815+
onUi {
816+
heatmapsById.remove(id)?.remove()
817+
}
818+
}
819+
820+
fun clearHeatmaps() {
821+
onUi {
822+
heatmapsById.values.forEach { it.remove() }
823+
}
824+
circlesById.clear()
825+
pendingHeatmaps.clear()
826+
}
827+
775828
fun destroyInternal() {
776829
onUi {
777830
markerBuilder.cancelAllJobs()
778831
clearMarkers()
779832
clearPolylines()
780833
clearPolygons()
781834
clearCircles()
835+
clearHeatmaps()
782836
locationHandler.stop()
783837
googleMap?.apply {
784838
setOnCameraMoveStartedListener(null)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.rngooglemapsplus
2+
3+
import com.facebook.react.uimanager.PixelUtil.dpToPx
4+
import com.google.android.gms.maps.model.TileOverlayOptions
5+
import com.google.maps.android.heatmaps.Gradient
6+
import com.google.maps.android.heatmaps.HeatmapTileProvider
7+
import com.rngooglemapsplus.extensions.toColor
8+
import com.rngooglemapsplus.extensions.toWeightedLatLngs
9+
10+
class MapHeatmapBuilder {
11+
fun build(heatmap: RNHeatmap): TileOverlayOptions {
12+
val provider =
13+
HeatmapTileProvider
14+
.Builder()
15+
.apply {
16+
weightedData(heatmap.weightedData.toWeightedLatLngs())
17+
heatmap.radius?.let { radius(it.dpToPx().toInt().coerceIn(10, 50)) }
18+
heatmap.opacity?.let { opacity(it) }
19+
heatmap.gradient?.let {
20+
val colors = it.colors.map { c -> c.toColor() }.toIntArray()
21+
val startPoints = it.startPoints.map { p -> p.toFloat() }.toFloatArray()
22+
gradient(Gradient(colors, startPoints))
23+
}
24+
}.build()
25+
26+
return TileOverlayOptions().apply {
27+
tileProvider(provider)
28+
heatmap.zIndex?.let { zIndex(it.toFloat()) }
29+
}
30+
}
31+
}

android/src/main/java/com/rngooglemapsplus/RNGoogleMapsPlusView.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class RNGoogleMapsPlusView(
2525
private val polylineBuilder = MapPolylineBuilder()
2626
private val polygonBuilder = MapPolygonBuilder()
2727
private val circleBuilder = MapCircleBuilder()
28+
private val heatmapBuilder = MapHeatmapBuilder()
2829

2930
override val view =
3031
GoogleMapsViewImpl(context, locationHandler, playServiceHandler, markerBuilder)
@@ -218,6 +219,21 @@ class RNGoogleMapsPlusView(
218219
}
219220
}
220221

222+
override var heatmaps: Array<RNHeatmap>? = null
223+
set(value) {
224+
if (field.contentEquals(value)) return
225+
val prevById = field?.associateBy { it.id } ?: emptyMap()
226+
val nextById = value?.associateBy { it.id } ?: emptyMap()
227+
field = value
228+
(prevById.keys - nextById.keys).forEach { id ->
229+
view.removeHeatmap(id)
230+
}
231+
232+
nextById.forEach { (id, next) ->
233+
view.addHeatmap(id, heatmapBuilder.build(next))
234+
}
235+
}
236+
221237
override var locationConfig: RNLocationConfig? = null
222238
set(value) {
223239
if (field == value) return
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.rngooglemapsplus.extensions
2+
3+
import com.google.android.gms.maps.model.LatLng
4+
import com.google.maps.android.heatmaps.WeightedLatLng
5+
import com.rngooglemapsplus.RNHeatmapPoint
6+
7+
fun RNHeatmapPoint.toWeightedLatLng(): WeightedLatLng = WeightedLatLng(LatLng(latitude, longitude), weight)
8+
9+
fun Array<RNHeatmapPoint>.toWeightedLatLngs(): Collection<WeightedLatLng> = map { it.toWeightedLatLng() }

example/ios/Podfile.lock

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ PODS:
88
- FBLazyVector (0.82.0)
99
- fmt (11.0.2)
1010
- glog (0.3.5)
11-
- GoogleMaps (10.3.0):
12-
- GoogleMaps/Maps (= 10.3.0)
13-
- GoogleMaps/Maps (10.3.0)
11+
- Google-Maps-iOS-Utils (6.1.3):
12+
- GoogleMaps (~> 10.0)
13+
- GoogleMaps (10.4.0):
14+
- GoogleMaps/Maps (= 10.4.0)
15+
- GoogleMaps/Maps (10.4.0)
1416
- hermes-engine (0.82.0):
1517
- hermes-engine/Pre-built (= 0.82.0)
1618
- hermes-engine/Pre-built (0.82.0)
@@ -2362,7 +2364,8 @@ PODS:
23622364
- fast_float
23632365
- fmt
23642366
- glog
2365-
- GoogleMaps (= 10.3.0)
2367+
- Google-Maps-iOS-Utils (= 6.1.3)
2368+
- GoogleMaps (= 10.4.0)
23662369
- hermes-engine
23672370
- NitroModules
23682371
- RCT-Folly
@@ -2475,6 +2478,7 @@ DEPENDENCIES:
24752478
SPEC REPOS:
24762479
trunk:
24772480
- CocoaLumberjack
2481+
- Google-Maps-iOS-Utils
24782482
- GoogleMaps
24792483
- SocketRocket
24802484
- SVGKit
@@ -2640,7 +2644,8 @@ SPEC CHECKSUMS:
26402644
FBLazyVector: 41b4dd99afd0aad861444ee141abdedaa6c3d238
26412645
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
26422646
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
2643-
GoogleMaps: 197af8911284ddf36db063c74faee4c4ab15e9d9
2647+
Google-Maps-iOS-Utils: bed22fa703c919259b3901449434d60d994fae20
2648+
GoogleMaps: a40d3b1f511f0fa2036e7b08c920c33279b1d5fd
26442649
hermes-engine: 8642d8f14a548ab718ec112e9bebdfdd154138b5
26452650
NitroModules: 73c42f3089bd74f411172ea8e69024aac4a2ac9f
26462651
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
@@ -2708,7 +2713,7 @@ SPEC CHECKSUMS:
27082713
ReactAppDependencyProvider: c5c4f5280e4ae0f9f4a739c64c4260fe0b3edaf1
27092714
ReactCodegen: 3873d7ac09960375f7845384ff47d53e478462dc
27102715
ReactCommon: f5527f5d97a9957ab46eb5db78875d3579e03b97
2711-
RNGoogleMapsPlus: 4f8dd1f33c906f62e877f1aa65cd8ac5ec6b020b
2716+
RNGoogleMapsPlus: 9b638ea84ab0231430a8b5a109a20130ad4a722a
27122717
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
27132718
SVGKit: 1ad7513f8c74d9652f94ed64ddecda1a23864dea
27142719
Yoga: ce55ebb197c21e22b6700cd36e3f36b7ec26e6f8

example/src/App.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
RNRegion,
2020
RNLatLng,
2121
RNCircle,
22+
RNHeatmap,
2223
} from '../../src';
2324
import { GoogleMapsView, GoogleMapsModule } from '../../src';
2425
import { callback } from 'react-native-nitro-modules';
@@ -204,6 +205,16 @@ const randomCoordinates = (
204205
longitude: baseLng + (Math.random() - 0.5) * offset,
205206
});
206207

208+
const randomWeightedCoordinates = (
209+
baseLat: number,
210+
baseLng: number,
211+
offset = 0.01
212+
) => ({
213+
latitude: baseLat + (Math.random() - 0.5) * offset,
214+
longitude: baseLng + (Math.random() - 0.5) * offset,
215+
weight: Math.floor(Math.random() * (100 - 10 + 1)) + 10,
216+
});
217+
207218
const makePolygon = (id: number): RNPolygon => ({
208219
id: id.toString(),
209220
zIndex: id,
@@ -246,6 +257,27 @@ const makeCircle = (id: number): RNCircle => ({
246257
fillColor: '#0000ff',
247258
});
248259

260+
const makeHeatmap = (id: number): RNHeatmap => ({
261+
id: id.toString(),
262+
zIndex: id,
263+
weightedData: [
264+
randomWeightedCoordinates(37.7749, -122.4194, 0.02),
265+
randomWeightedCoordinates(37.7749, -122.4194, 0.03),
266+
randomWeightedCoordinates(37.7749, -122.4194, 0.05),
267+
randomWeightedCoordinates(37.7749, -122.4194, 0.01),
268+
randomWeightedCoordinates(37.7749, -122.4194, 0.08),
269+
randomWeightedCoordinates(37.7749, -122.4194, 0.03),
270+
randomWeightedCoordinates(37.7749, -122.4194, 0.09),
271+
],
272+
gradient: {
273+
colors: ['#00f', '#0ff', '#0f0', '#ff0', '#f00'],
274+
startPoints: [0.1, 0.3, 0.5, 0.7, 1],
275+
colorMapSize: 256,
276+
},
277+
radius: 100,
278+
opacity: 1,
279+
});
280+
249281
export const makeMarker = (id: number): RNMarker => ({
250282
id: id.toString(),
251283
zIndex: id,
@@ -333,6 +365,10 @@ export default function App() {
333365
Array.from({ length: 1 }, (_, i) => makeCircle(i + 1))
334366
);
335367

368+
const [heatmaps] = useState(
369+
Array.from({ length: 1 }, (_, i) => makeHeatmap(i + 1))
370+
);
371+
336372
useEffect(() => {
337373
if (!stressTest) return;
338374

@@ -506,6 +542,7 @@ export default function App() {
506542
polygons={polygons}
507543
polylines={polylines}
508544
circles={circles}
545+
heatmaps={heatmaps}
509546
/>
510547

511548
<ScrollView style={styles.scrollView}>

ios/.swiftlint.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ disabled_rules:
22
- file_length
33
- type_body_length
44
- cyclomatic_complexity
5+
- function_body_length
6+
- closure_parameter_position
57
- todo
68

79
identifier_name:

0 commit comments

Comments
 (0)