Skip to content

Commit 35098bd

Browse files
committed
feat: add kml layer support
1 parent 4d2de6a commit 35098bd

File tree

8 files changed

+220
-2
lines changed

8 files changed

+220
-2
lines changed

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ import com.google.android.gms.maps.model.Polyline
2727
import com.google.android.gms.maps.model.PolylineOptions
2828
import com.google.android.gms.maps.model.TileOverlay
2929
import com.google.android.gms.maps.model.TileOverlayOptions
30+
import com.google.maps.android.data.kml.KmlLayer
3031
import com.rngooglemapsplus.extensions.toGooglePriority
3132
import com.rngooglemapsplus.extensions.toLocationErrorCode
33+
import java.io.ByteArrayInputStream
34+
import java.nio.charset.StandardCharsets
3235

3336
class GoogleMapsViewImpl(
3437
val reactContext: ThemedReactContext,
@@ -55,12 +58,14 @@ class GoogleMapsViewImpl(
5558
private val pendingPolygons = mutableListOf<Pair<String, PolygonOptions>>()
5659
private val pendingCircles = mutableListOf<Pair<String, CircleOptions>>()
5760
private val pendingHeatmaps = mutableListOf<Pair<String, TileOverlayOptions>>()
61+
private val pendingKmlLayers = mutableListOf<Pair<String, String>>()
5862

5963
private val markersById = mutableMapOf<String, Marker>()
6064
private val polylinesById = mutableMapOf<String, Polyline>()
6165
private val polygonsById = mutableMapOf<String, Polygon>()
6266
private val circlesById = mutableMapOf<String, Circle>()
6367
private val heatmapsById = mutableMapOf<String, TileOverlay>()
68+
private val kmlLayersById = mutableMapOf<String, KmlLayer>()
6469

6570
private var cameraMoveReason = -1
6671
private var lastSubmittedLocation: Location? = null
@@ -343,6 +348,13 @@ class GoogleMapsViewImpl(
343348
}
344349
pendingHeatmaps.clear()
345350
}
351+
352+
if (pendingKmlLayers.isNotEmpty()) {
353+
pendingKmlLayers.forEach { (id, string) ->
354+
internalAddKmlLayer(id, string)
355+
}
356+
pendingKmlLayers.clear()
357+
}
346358
}
347359

348360
var uiSettings: RNMapUiSettings? = null
@@ -825,6 +837,50 @@ class GoogleMapsViewImpl(
825837
pendingHeatmaps.clear()
826838
}
827839

840+
fun addKmlLayer(
841+
id: String,
842+
kmlString: String,
843+
) {
844+
if (googleMap == null) {
845+
pendingKmlLayers.add(id to kmlString)
846+
return
847+
}
848+
onUi {
849+
kmlLayersById.remove(id)?.removeLayerFromMap()
850+
}
851+
internalAddKmlLayer(id, kmlString)
852+
}
853+
854+
private fun internalAddKmlLayer(
855+
id: String,
856+
kmlString: String,
857+
) {
858+
onUi {
859+
try {
860+
val inputStream = ByteArrayInputStream(kmlString.toByteArray(StandardCharsets.UTF_8))
861+
val layer = KmlLayer(googleMap, inputStream, context)
862+
kmlLayersById[id] = layer
863+
layer.addLayerToMap()
864+
} catch (e: Exception) {
865+
// / ignore
866+
}
867+
}
868+
}
869+
870+
fun removeKmlLayer(id: String) {
871+
onUi {
872+
kmlLayersById.remove(id)?.removeLayerFromMap()
873+
}
874+
}
875+
876+
fun clearKmlLayer() {
877+
onUi {
878+
kmlLayersById.values.forEach { it.removeLayerFromMap() }
879+
}
880+
kmlLayersById.clear()
881+
pendingKmlLayers.clear()
882+
}
883+
828884
fun destroyInternal() {
829885
onUi {
830886
markerBuilder.cancelAllJobs()
@@ -833,6 +889,7 @@ class GoogleMapsViewImpl(
833889
clearPolygons()
834890
clearCircles()
835891
clearHeatmaps()
892+
clearKmlLayer()
836893
locationHandler.stop()
837894
googleMap?.apply {
838895
setOnCameraMoveStartedListener(null)

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,20 @@ class RNGoogleMapsPlusView(
234234
}
235235
}
236236

237+
override var kmlLayers: Array<RNKMLayer>? = null
238+
set(value) {
239+
if (field.contentEquals(value)) return
240+
val prevById = field?.associateBy { it.id } ?: emptyMap()
241+
val nextById = value?.associateBy { it.id } ?: emptyMap()
242+
field = value
243+
(prevById.keys - nextById.keys).forEach { id ->
244+
view.removeKmlLayer(id)
245+
}
246+
nextById.forEach { (id, next) ->
247+
view.addKmlLayer(id, next.kmlString)
248+
}
249+
}
250+
237251
override var locationConfig: RNLocationConfig? = null
238252
set(value) {
239253
if (field == value) return

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,7 +2358,7 @@ PODS:
23582358
- React-perflogger (= 0.82.0)
23592359
- React-utils (= 0.82.0)
23602360
- SocketRocket
2361-
- RNGoogleMapsPlus (1.1.0-dev.2):
2361+
- RNGoogleMapsPlus (1.1.0-dev.5):
23622362
- boost
23632363
- DoubleConversion
23642364
- fast_float
@@ -2713,7 +2713,7 @@ SPEC CHECKSUMS:
27132713
ReactAppDependencyProvider: c5c4f5280e4ae0f9f4a739c64c4260fe0b3edaf1
27142714
ReactCodegen: 3873d7ac09960375f7845384ff47d53e478462dc
27152715
ReactCommon: f5527f5d97a9957ab46eb5db78875d3579e03b97
2716-
RNGoogleMapsPlus: 9b638ea84ab0231430a8b5a109a20130ad4a722a
2716+
RNGoogleMapsPlus: cdea400ea1e69740d91e07dbb5882d93be4c0a77
27172717
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
27182718
SVGKit: 1ad7513f8c74d9652f94ed64ddecda1a23864dea
27192719
Yoga: ce55ebb197c21e22b6700cd36e3f36b7ec26e6f8

example/src/App.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,78 @@ const silverMapStyle: RNMapStyleElement[] = [
178178
},
179179
];
180180

181+
const kmlString = `
182+
<?xml version="1.0" encoding="UTF-8"?>
183+
<kml xmlns="http://www.opengis.net/kml/2.2">
184+
<Document>
185+
<name>Example KML Data</name>
186+
<description>Example with marker, polygon and circle shifted further northeast of San Francisco</description>
187+
188+
<Placemark>
189+
<name>Center Point</name>
190+
<Point>
191+
<coordinates>-122.4156,37.7781,0</coordinates>
192+
</Point>
193+
</Placemark>
194+
195+
<Placemark>
196+
<name>Example Polygon</name>
197+
<Style>
198+
<LineStyle>
199+
<color>ff0000ff</color>
200+
<width>2</width>
201+
</LineStyle>
202+
<PolyStyle>
203+
<color>7d00ff00</color>
204+
</PolyStyle>
205+
</Style>
206+
<Polygon>
207+
<outerBoundaryIs>
208+
<LinearRing>
209+
<coordinates>
210+
-122.4206,37.7826,0
211+
-122.4106,37.7826,0
212+
-122.4106,37.7746,0
213+
-122.4206,37.7746,0
214+
-122.4206,37.7826,0
215+
</coordinates>
216+
</LinearRing>
217+
</outerBoundaryIs>
218+
</Polygon>
219+
</Placemark>
220+
221+
<Placemark>
222+
<name>Approximate Circle</name>
223+
<Style>
224+
<LineStyle>
225+
<color>ffff0000</color>
226+
<width>2</width>
227+
</LineStyle>
228+
<PolyStyle>
229+
<color>3dff0000</color>
230+
</PolyStyle>
231+
</Style>
232+
<Polygon>
233+
<outerBoundaryIs>
234+
<LinearRing>
235+
<coordinates>
236+
-122.4156,37.7801,0
237+
-122.4136,37.7801,0
238+
-122.4136,37.7761,0
239+
-122.4156,37.7761,0
240+
-122.4176,37.7761,0
241+
-122.4176,37.7801,0
242+
-122.4156,37.7801,0
243+
</coordinates>
244+
</LinearRing>
245+
</outerBoundaryIs>
246+
</Polygon>
247+
</Placemark>
248+
249+
</Document>
250+
</kml>
251+
`.trim();
252+
181253
function makeSvgIcon(width: number, height: number): string {
182254
const color = randomColor();
183255
return `
@@ -369,6 +441,8 @@ export default function App() {
369441
Array.from({ length: 1 }, (_, i) => makeHeatmap(i + 1))
370442
);
371443

444+
const [kmlLayers] = useState([{ id: '21', zIndex: 1, kmlString }]);
445+
372446
useEffect(() => {
373447
if (!stressTest) return;
374448

@@ -543,6 +617,7 @@ export default function App() {
543617
polylines={polylines}
544618
circles={circles}
545619
heatmaps={heatmaps}
620+
kmlLayers={kmlLayers}
546621
/>
547622

548623
<ScrollView style={styles.scrollView}>

ios/GoogleMapViewImpl.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate {
1616
private var pendingPolygons: [(id: String, polygon: GMSPolygon)] = []
1717
private var pendingCircles: [(id: String, circle: GMSCircle)] = []
1818
private var pendingHeatmaps: [(id: String, heatmap: GMUHeatmapTileLayer)] = []
19+
private var pendingKmlLayers: [(id: String, kmlString: String)] = []
1920

2021
private var markersById: [String: GMSMarker] = [:]
2122
private var polylinesById: [String: GMSPolyline] = [:]
2223
private var polygonsById: [String: GMSPolygon] = [:]
2324
private var circlesById: [String: GMSCircle] = [:]
2425
private var heatmapsById: [String: GMUHeatmapTileLayer] = [:]
26+
private var kmlLayerById: [String: GMUGeometryRenderer] = [:]
2527

2628
private var cameraMoveReasonIsGesture: Bool = false
2729
private var lastSubmittedCameraPosition: GMSCameraPosition?
@@ -177,6 +179,12 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate {
177179
}
178180
pendingHeatmaps.removeAll()
179181
}
182+
if !pendingKmlLayers.isEmpty {
183+
pendingKmlLayers.forEach {
184+
addKmlLayerInternal(id: $0.id, kmlString: $0.kmlString)
185+
}
186+
pendingKmlLayers.removeAll()
187+
}
180188
}
181189

182190
var currentCamera: GMSCameraPosition? {
@@ -524,6 +532,42 @@ final class GoogleMapsViewImpl: UIView, GMSMapViewDelegate {
524532
pendingHeatmaps.removeAll()
525533
}
526534

535+
@MainActor
536+
func addKmlLayer(id: String, kmlString: String) {
537+
if mapView == nil {
538+
pendingKmlLayers.append((id, kmlString))
539+
return
540+
}
541+
kmlLayerById.removeValue(forKey: id).map { $0.clear() }
542+
addKmlLayerInternal(id: id, kmlString: kmlString)
543+
}
544+
545+
@MainActor
546+
private func addKmlLayerInternal(id: String, kmlString: String) {
547+
guard let data = kmlString.data(using: .utf8) else { return }
548+
let parser = GMUKMLParser(data: data)
549+
parser.parse()
550+
mapView.map { mapView in
551+
let renderer = GMUGeometryRenderer(
552+
map: mapView,
553+
geometries: parser.placemarks
554+
)
555+
renderer.render()
556+
}
557+
}
558+
559+
@MainActor
560+
func removeKmlLayer(id: String) {
561+
kmlLayerById.removeValue(forKey: id).map { $0.clear() }
562+
}
563+
564+
@MainActor
565+
func clearKmlLayers() {
566+
kmlLayerById.values.forEach { $0.clear() }
567+
kmlLayerById.removeAll()
568+
pendingKmlLayers.removeAll()
569+
}
570+
527571
func deinitInternal() {
528572
markerBuilder.cancelAllIconTasks()
529573
clearMarkers()

ios/RNGoogleMapsPlusView.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,27 @@ final class RNGoogleMapsPlusView: HybridRNGoogleMapsPlusViewSpec {
256256
}
257257
}
258258

259+
@MainActor
260+
var kmlLayers: [RNKMLayer]? {
261+
didSet {
262+
let prevById = Dictionary(
263+
(oldValue ?? []).map { ($0.id, $0) },
264+
uniquingKeysWith: { _, new in new }
265+
)
266+
let nextById = Dictionary(
267+
(kmlLayers ?? []).map { ($0.id, $0) },
268+
uniquingKeysWith: { _, new in new }
269+
)
270+
271+
let removed = Set(prevById.keys).subtracting(nextById.keys)
272+
removed.forEach { impl.removeKmlLayer(id: $0) }
273+
274+
for (id, next) in nextById {
275+
impl.addKmlLayer(id: id, kmlString: next.kmlString)
276+
}
277+
}
278+
}
279+
259280
@MainActor var locationConfig: RNLocationConfig? {
260281
didSet {
261282
impl.locationConfig = locationConfig

src/RNGoogleMapsPlusView.nitro.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
RNLocationConfig,
2424
RNMapZoomConfig,
2525
RNHeatmap,
26+
RNKMLayer,
2627
} from './types';
2728

2829
export interface RNGoogleMapsPlusViewProps extends HybridViewProps {
@@ -42,6 +43,7 @@ export interface RNGoogleMapsPlusViewProps extends HybridViewProps {
4243
polylines?: RNPolyline[];
4344
circles?: RNCircle[];
4445
heatmaps?: RNHeatmap[];
46+
kmlLayers?: RNKMLayer[];
4547
locationConfig?: RNLocationConfig;
4648
onMapError?: (error: RNMapErrorCode) => void;
4749
onMapReady?: (ready: boolean) => void;

src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ export type RNHeatmapGradient = {
199199
colorMapSize: number;
200200
};
201201

202+
export type RNKMLayer = {
203+
id: string;
204+
kmlString: string;
205+
};
206+
202207
export type RNLocationConfig = {
203208
android?: RNAndroidLocationConfig;
204209
ios?: RNIOSLocationConfig;

0 commit comments

Comments
 (0)