Skip to content

Commit f4badbc

Browse files
committed
feat: add url tile overlay
1 parent b9aaa20 commit f4badbc

File tree

16 files changed

+310
-9
lines changed

16 files changed

+310
-9
lines changed

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,15 @@ class GoogleMapsViewImpl(
7777
private val pendingCircles = mutableListOf<Pair<String, CircleOptions>>()
7878
private val pendingHeatmaps = mutableListOf<Pair<String, TileOverlayOptions>>()
7979
private val pendingKmlLayers = mutableListOf<Pair<String, String>>()
80+
private val pendingUrlTilesOverlays = mutableListOf<Pair<String, TileOverlayOptions>>()
8081

8182
private val markersById = mutableMapOf<String, Marker>()
8283
private val polylinesById = mutableMapOf<String, Polyline>()
8384
private val polygonsById = mutableMapOf<String, Polygon>()
8485
private val circlesById = mutableMapOf<String, Circle>()
8586
private val heatmapsById = mutableMapOf<String, TileOverlay>()
8687
private val kmlLayersById = mutableMapOf<String, KmlLayer>()
88+
private val urlTileOverlaysById = mutableMapOf<String, TileOverlay>()
8789

8890
private var cameraMoveReason = -1
8991
private var lastSubmittedCameraPosition: CameraPosition? = null
@@ -249,6 +251,13 @@ class GoogleMapsViewImpl(
249251
}
250252
pendingKmlLayers.clear()
251253
}
254+
255+
if (pendingUrlTilesOverlays.isNotEmpty()) {
256+
pendingUrlTilesOverlays.forEach { (id, string) ->
257+
internalAddUrlTileOverlay(id, string)
258+
}
259+
pendingUrlTilesOverlays.clear()
260+
}
252261
}
253262

254263
val currentCamera: CameraPosition?
@@ -856,6 +865,48 @@ class GoogleMapsViewImpl(
856865
pendingKmlLayers.clear()
857866
}
858867

868+
fun addUrlTileOverlay(
869+
id: String,
870+
opts: TileOverlayOptions,
871+
) {
872+
if (googleMap == null) {
873+
pendingUrlTilesOverlays.add(id to opts)
874+
return
875+
}
876+
877+
onUi {
878+
urlTileOverlaysById.remove(id)?.remove()
879+
}
880+
internalAddUrlTileOverlay(id, opts)
881+
}
882+
883+
private fun internalAddUrlTileOverlay(
884+
id: String,
885+
opts: TileOverlayOptions,
886+
) {
887+
onUi {
888+
val urlTile =
889+
googleMap?.addTileOverlay(opts)
890+
if (urlTile != null) {
891+
urlTileOverlaysById[id] = urlTile
892+
}
893+
}
894+
}
895+
896+
fun removeUrlTileOverlay(id: String) {
897+
onUi {
898+
urlTileOverlaysById.remove(id)?.remove()
899+
}
900+
}
901+
902+
fun clearUrlTileOverlays() {
903+
onUi {
904+
urlTileOverlaysById.values.forEach { it.remove() }
905+
}
906+
urlTileOverlaysById.clear()
907+
pendingUrlTilesOverlays.clear()
908+
}
909+
859910
fun destroyInternal() {
860911
if (destroyed) return
861912
destroyed = true
@@ -868,6 +919,7 @@ class GoogleMapsViewImpl(
868919
clearCircles()
869920
clearHeatmaps()
870921
clearKmlLayer()
922+
clearUrlTileOverlays()
871923
googleMap?.apply {
872924
setOnCameraMoveStartedListener(null)
873925
setOnCameraMoveListener(null)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.rngooglemapsplus
2+
3+
import com.google.android.gms.maps.model.TileOverlayOptions
4+
import com.google.android.gms.maps.model.UrlTileProvider
5+
import java.net.URL
6+
7+
class MapUrlTileOverlayBuilder {
8+
fun build(t: RNUrlTileOverlay): TileOverlayOptions {
9+
val provider =
10+
object : UrlTileProvider(
11+
t.tileSize.toInt(),
12+
t.tileSize.toInt(),
13+
) {
14+
override fun getTileUrl(
15+
x: Int,
16+
y: Int,
17+
zoom: Int,
18+
): URL? {
19+
val url =
20+
t.url
21+
.replace("{x}", x.toString())
22+
.replace("{y}", y.toString())
23+
.replace("{z}", zoom.toString())
24+
25+
return try {
26+
URL(url)
27+
} catch (e: Exception) {
28+
null
29+
}
30+
}
31+
}
32+
33+
val opts = TileOverlayOptions().tileProvider(provider)
34+
35+
t.fadeIn?.let { opts.fadeIn(it) }
36+
t.zIndex?.let { opts.zIndex(it.toFloat()) }
37+
t.opacity?.let { opts.transparency(1f - it.toFloat()) }
38+
return opts
39+
}
40+
}

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.rngooglemapsplus.extensions.polylineEquals
1414
import com.rngooglemapsplus.extensions.toCameraPosition
1515
import com.rngooglemapsplus.extensions.toCompressFormat
1616
import com.rngooglemapsplus.extensions.toFileExtension
17+
import com.rngooglemapsplus.extensions.toGoogleMapType
1718
import com.rngooglemapsplus.extensions.toLatLngBounds
1819
import com.rngooglemapsplus.extensions.toMapColorScheme
1920
import com.rngooglemapsplus.extensions.toSize
@@ -33,6 +34,7 @@ class RNGoogleMapsPlusView(
3334
private val polygonBuilder = MapPolygonBuilder()
3435
private val circleBuilder = MapCircleBuilder()
3536
private val heatmapBuilder = MapHeatmapBuilder()
37+
private val urlTileOverlayBuilder = MapUrlTileOverlayBuilder()
3638

3739
override val view =
3840
GoogleMapsViewImpl(context, locationHandler, playServiceHandler, markerBuilder)
@@ -128,9 +130,7 @@ class RNGoogleMapsPlusView(
128130
set(value) {
129131
if (field == value) return
130132
field = value
131-
value?.let {
132-
view.mapType = it.value
133-
}
133+
view.mapType = value?.toGoogleMapType()
134134
}
135135

136136
override var markers: Array<RNMarker>? = null
@@ -263,6 +263,20 @@ class RNGoogleMapsPlusView(
263263
view.addKmlLayer(id, next.kmlString)
264264
}
265265
}
266+
override var urlTileOverlays: Array<RNUrlTileOverlay>? = null
267+
set(value) {
268+
if (field.contentEquals(value)) return
269+
val prevById = field?.associateBy { it.id } ?: emptyMap()
270+
val nextById = value?.associateBy { it.id } ?: emptyMap()
271+
field = value
272+
(prevById.keys - nextById.keys).forEach { id ->
273+
view.removeUrlTileOverlay(id)
274+
}
275+
276+
nextById.forEach { (id, next) ->
277+
view.addUrlTileOverlay(id, urlTileOverlayBuilder.build(next))
278+
}
279+
}
266280

267281
override var locationConfig: RNLocationConfig? = null
268282
set(value) {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.rngooglemapsplus.extensions
2+
3+
import com.google.android.gms.maps.GoogleMap
4+
import com.rngooglemapsplus.RNMapType
5+
6+
fun RNMapType.toGoogleMapType(): Int =
7+
when (this) {
8+
RNMapType.NONE -> GoogleMap.MAP_TYPE_NONE
9+
RNMapType.NORMAL -> GoogleMap.MAP_TYPE_NORMAL
10+
RNMapType.HYBRID -> GoogleMap.MAP_TYPE_HYBRID
11+
RNMapType.SATELLITE -> GoogleMap.MAP_TYPE_SATELLITE
12+
RNMapType.TERRAIN -> GoogleMap.MAP_TYPE_TERRAIN
13+
}

example/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import type { RootStackParamList } from './types/navigation';
2626
import SnapshotTestScreen from './screens/SnaptshotTestScreen';
2727
import ClusteringScreen from './screens/ClsuteringScreen';
2828
import SvgMarkersScreen from './screens/SvgMarkersScreen';
29+
import UrlTileOverlay from './screens/UrlTileOverlay';
2930

3031
const Stack = createStackNavigator<RootStackParamList>();
3132

@@ -94,6 +95,11 @@ export default function App() {
9495
component={KmlLayerScreen}
9596
options={{ title: 'KML Layer' }}
9697
/>
98+
<Stack.Screen
99+
name="UrlTileOverlay"
100+
component={UrlTileOverlay}
101+
options={{ title: 'Url Tile Overlay' }}
102+
/>
97103
<Stack.Screen
98104
name="Location"
99105
component={LocationScreen}

example/src/components/maptConfigDialog/validator.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,15 @@ export const RNKMLayerValidator = object({
235235
kmlString: string(),
236236
});
237237

238+
export const RNUrlTileOverlayValidator = object({
239+
id: string(),
240+
zIndex: optional(number()),
241+
url: string(),
242+
tileSize: number(),
243+
opacity: optional(number()),
244+
fadeIn: optional(boolean()),
245+
});
246+
238247
export const RNIndoorLevelValidator = object({
239248
index: number(),
240249
name: optional(string()),
@@ -342,6 +351,3 @@ if (
342351
{ type: 'literal', schema: 'default' },
343352
];
344353
}
345-
346-
export type RNBasicMapConfigType =
347-
typeof RNBasicMapConfigValidator extends Struct<infer O, any> ? O : never;

example/src/screens/HomeScreen.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const screens = [
1313
{ name: 'Circles', title: 'Circles' },
1414
{ name: 'Heatmap', title: 'Heatmap' },
1515
{ name: 'KmlLayer', title: 'KML Layer' },
16+
{ name: 'UrlTileOverlay', title: 'Url Tile Overlay' },
1617
{ name: 'Location', title: 'Location & Permissions' },
1718
{ name: 'CustomStyle', title: 'Custom Map Style' },
1819
{ name: 'IndoorLevelMap', title: 'Indoor Level Map' },
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React, { useRef, useState } from 'react';
2+
import MapWrapper from '../components/MapWrapper';
3+
import type {
4+
GoogleMapsViewRef,
5+
RNUrlTileOverlay,
6+
} from 'react-native-google-maps-plus';
7+
import MapConfigDialog from '../components/maptConfigDialog/MapConfigDialog';
8+
import { useNavigation } from '@react-navigation/native';
9+
import { RNUrlTileOverlayValidator } from '../components/maptConfigDialog/validator';
10+
import { useHeaderButton } from '../hooks/useHeaderButton';
11+
import { makeUrlTileOverlay } from '../utils/mapGenerators';
12+
13+
export default function UrlTileOverlay() {
14+
const mapRef = useRef<GoogleMapsViewRef | null>(null);
15+
const navigation = useNavigation();
16+
const [urlTileOverlay, setUrlTileOverlay] = useState<
17+
RNUrlTileOverlay | undefined
18+
>(undefined);
19+
const [dialogVisible, setDialogVisible] = useState(true);
20+
21+
useHeaderButton(navigation, urlTileOverlay ? 'Edit' : 'Add', () =>
22+
setDialogVisible(true)
23+
);
24+
25+
return (
26+
<>
27+
<MapWrapper
28+
mapType={'none'}
29+
mapRef={mapRef}
30+
urlTileOverlays={urlTileOverlay ? [urlTileOverlay] : []}
31+
/>
32+
<MapConfigDialog<RNUrlTileOverlay>
33+
visible={dialogVisible}
34+
title="Edit KML layer"
35+
initialData={makeUrlTileOverlay(1)}
36+
validator={RNUrlTileOverlayValidator}
37+
onClose={() => setDialogVisible(false)}
38+
onSave={(c) => setUrlTileOverlay(c)}
39+
/>
40+
</>
41+
);
42+
}

example/src/types/navigation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type RootStackParamList = {
99
Circles: undefined;
1010
Heatmap: undefined;
1111
KmlLayer: undefined;
12+
UrlTileOverlay: undefined;
1213
Location: undefined;
1314
CustomStyle: undefined;
1415
IndoorLevelMap: undefined;

example/src/utils/mapGenerators.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
RNMarker,
55
RNPolygon,
66
RNPolyline,
7+
RNUrlTileOverlay,
78
} from 'react-native-google-maps-plus';
89

910
export function randomColor() {
@@ -170,6 +171,17 @@ export const makeHeatmap = (id: number): RNHeatmap => ({
170171
opacity: 1,
171172
});
172173

174+
export function makeUrlTileOverlay(id: number): RNUrlTileOverlay {
175+
return {
176+
id: id.toString(),
177+
zIndex: id,
178+
fadeIn: false,
179+
opacity: 1,
180+
tileSize: 256,
181+
url: '',
182+
};
183+
}
184+
173185
export function makeMarker(id: number): RNMarker {
174186
return {
175187
id: id.toString(),

0 commit comments

Comments
 (0)