Skip to content

Commit 3ae7a35

Browse files
committed
feat(google-maps): Implemented map feature functions (i.e. GeoJSON support)
1 parent 7f92c3d commit 3ae7a35

File tree

12 files changed

+784
-5
lines changed

12 files changed

+784
-5
lines changed

plugin/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ export default MyMap;
296296
* [`removeCircles(...)`](#removecircles)
297297
* [`addPolylines(...)`](#addpolylines)
298298
* [`removePolylines(...)`](#removepolylines)
299+
* [`addFeatures(...)`](#addfeatures)
300+
* [`getFeatureBounds(...)`](#getfeaturebounds)
301+
* [`removeFeature(...)`](#removefeature)
299302
* [`destroy()`](#destroy)
300303
* [`setCamera(...)`](#setcamera)
301304
* [`getMapType()`](#getmaptype)
@@ -527,6 +530,52 @@ removePolylines(ids: string[]) => Promise<void>
527530
--------------------
528531

529532

533+
### addFeatures(...)
534+
535+
```typescript
536+
addFeatures(type: FeatureType, data: any, idPropertyName?: string | undefined, styles?: FeatureStyles | undefined) => Promise<string[]>
537+
```
538+
539+
| Param | Type |
540+
| -------------------- | ------------------------------------------------------- |
541+
| **`type`** | <code><a href="#featuretype">FeatureType</a></code> |
542+
| **`data`** | <code>any</code> |
543+
| **`idPropertyName`** | <code>string</code> |
544+
| **`styles`** | <code><a href="#featurestyles">FeatureStyles</a></code> |
545+
546+
**Returns:** <code>Promise&lt;string[]&gt;</code>
547+
548+
--------------------
549+
550+
551+
### getFeatureBounds(...)
552+
553+
```typescript
554+
getFeatureBounds(featureId: string) => Promise<LatLngBounds>
555+
```
556+
557+
| Param | Type |
558+
| --------------- | ------------------- |
559+
| **`featureId`** | <code>string</code> |
560+
561+
**Returns:** <code>Promise&lt;LatLngBounds&gt;</code>
562+
563+
--------------------
564+
565+
566+
### removeFeature(...)
567+
568+
```typescript
569+
removeFeature(featureId: string) => Promise<void>
570+
```
571+
572+
| Param | Type |
573+
| --------------- | ------------------- |
574+
| **`featureId`** | <code>string</code> |
575+
576+
--------------------
577+
578+
530579
### destroy()
531580

532581
```typescript
@@ -1016,6 +1065,11 @@ Describes the style for some region of a polyline.
10161065
| **`segments`** | <code>number</code> | The length of this span in number of segments. |
10171066

10181067

1068+
#### FeatureStyles
1069+
1070+
Feature styles, identified by the feature id
1071+
1072+
10191073
#### CameraConfig
10201074

10211075
Configuration properties for a Google Map Camera
@@ -1158,6 +1212,14 @@ but the current specification only allows X, Y, and (optionally) Z to be defined
11581212
### Enums
11591213

11601214

1215+
#### FeatureType
1216+
1217+
| Members | Value | Description |
1218+
| ------------- | ---------------------- | ----------- |
1219+
| **`Default`** | <code>'Default'</code> | Default |
1220+
| **`GeoJSON`** | <code>'GeoJSON'</code> | GeoJSON |
1221+
1222+
11611223
#### MapType
11621224

11631225
| Members | Value | Description |

plugin/android/src/main/java/com/capacitorjs/plugins/googlemaps/CapacitorGoogleMap.kt

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,19 @@ import com.google.android.gms.maps.GoogleMap.*
1616
import com.google.android.gms.maps.model.*
1717
import com.google.maps.android.clustering.Cluster
1818
import com.google.maps.android.clustering.ClusterManager
19+
import com.google.maps.android.data.Feature
20+
import com.google.maps.android.data.geojson.GeoJsonFeature
21+
import com.google.maps.android.data.geojson.GeoJsonGeometryCollection
22+
import com.google.maps.android.data.geojson.GeoJsonLayer
23+
import com.google.maps.android.data.geojson.GeoJsonLineString
24+
import com.google.maps.android.data.geojson.GeoJsonMultiLineString
25+
import com.google.maps.android.data.geojson.GeoJsonMultiPoint
26+
import com.google.maps.android.data.geojson.GeoJsonMultiPolygon
27+
import com.google.maps.android.data.geojson.GeoJsonPoint
28+
import com.google.maps.android.data.geojson.GeoJsonPolygon
1929
import kotlinx.coroutines.*
2030
import kotlinx.coroutines.channels.Channel
31+
import org.json.JSONObject
2132
import java.io.InputStream
2233
import java.net.URL
2334

@@ -45,7 +56,8 @@ class CapacitorGoogleMap(
4556
private val markers = HashMap<String, CapacitorGoogleMapMarker>()
4657
private val polygons = HashMap<String, CapacitorGoogleMapsPolygon>()
4758
private val circles = HashMap<String, CapacitorGoogleMapsCircle>()
48-
private val polylines = HashMap<String, CapacitorGoogleMapPolyline>()
59+
private val polylines = HashMap<String, CapacitorGoogleMapPolyline>()
60+
private val featureLayers = HashMap<String, CapacitorGoogleMapsFeatureLayer>()
4961
private val markerIcons = HashMap<String, Bitmap>()
5062
private var clusterManager: ClusterManager<CapacitorGoogleMapMarker>? = null
5163

@@ -324,6 +336,78 @@ class CapacitorGoogleMap(
324336
}
325337
}
326338

339+
fun addFeatures(type: String, data: JSONObject, idPropertyName: String?, styles: JSONObject?, callback: (ids: Result<List<String>>) -> Unit) {
340+
try {
341+
googleMap ?: throw GoogleMapNotAvailable()
342+
val featureIds: MutableList<String> = mutableListOf()
343+
344+
CoroutineScope(Dispatchers.Main).launch {
345+
if (type == "GeoJSON") {
346+
val tempLayer = GeoJsonLayer(null, data)
347+
tempLayer.features.forEach {
348+
try {
349+
val layer = GeoJsonLayer(googleMap, JSONObject())
350+
val featureLayer = CapacitorGoogleMapsFeatureLayer(layer, it, idPropertyName, styles)
351+
layer.addLayerToMap()
352+
if (id != null) {
353+
featureIds.add(id)
354+
featureLayers[id] = featureLayer
355+
}
356+
callback(Result.success(featureIds))
357+
} catch (e: Exception) {
358+
callback(Result.failure(e))
359+
}
360+
}
361+
} else {
362+
callback(Result.failure(InvalidArgumentsError("addFeatures: not supported for this feature type")))
363+
}
364+
}
365+
} catch (e: GoogleMapsError) {
366+
callback(Result.failure(e))
367+
}
368+
}
369+
370+
fun getFeatureBounds(featureId: String, callback: (bounds: Result<LatLngBounds?>) -> Unit) {
371+
try {
372+
CoroutineScope(Dispatchers.Main).launch {
373+
val featurelayer = featureLayers[featureId]
374+
var feature: Feature? = null;
375+
featurelayer?.layer?.features?.forEach lit@ {
376+
if (it.id == featurelayer.id) {
377+
feature = it
378+
return@lit
379+
}
380+
}
381+
if (feature != null) {
382+
try {
383+
(feature as GeoJsonFeature).let {
384+
callback(Result.success(it.boundingBoxFromGeometry()))
385+
}
386+
} catch (e: Exception) {
387+
callback(Result.failure(InvalidArgumentsError("getFeatureBounds: not supported for this feature type")))
388+
}
389+
} else {
390+
callback(Result.failure(InvalidArgumentsError("Could not find feature for feature id $featureId")))
391+
}
392+
}
393+
} catch(e: Exception) {
394+
callback(Result.failure(InvalidArgumentsError("Could not find feature layer")))
395+
}
396+
}
397+
398+
fun removeFeature(featureId: String, callback: (error: GoogleMapsError?) -> Unit) {
399+
CoroutineScope(Dispatchers.Main).launch {
400+
val featurelayer = featureLayers[featureId]
401+
if (featurelayer != null) {
402+
featurelayer.layer?.removeLayerFromMap()
403+
featureLayers.remove(featureId)
404+
callback(null)
405+
} else {
406+
callback(InvalidArgumentsError("Could not find feature for feature id $featureId"))
407+
}
408+
}
409+
}
410+
327411
private fun setClusterManagerRenderer(minClusterSize: Int?) {
328412
clusterManager?.renderer = CapacitorClusterManagerRenderer(
329413
delegate.bridge.context,
@@ -941,6 +1025,52 @@ class CapacitorGoogleMap(
9411025
return data
9421026
}
9431027

1028+
private fun GeoJsonFeature.boundingBoxFromGeometry(): LatLngBounds? {
1029+
val coordinates: MutableList<LatLng> = ArrayList()
1030+
1031+
if (this.hasGeometry()) {
1032+
when (geometry.geometryType) {
1033+
"Point" -> coordinates.add((geometry as GeoJsonPoint).coordinates)
1034+
"MultiPoint" -> {
1035+
val points = (geometry as GeoJsonMultiPoint).points
1036+
for (point in points) {
1037+
coordinates.add(point.coordinates)
1038+
}
1039+
}
1040+
1041+
"LineString" -> coordinates.addAll((geometry as GeoJsonLineString).coordinates)
1042+
"MultiLineString" -> {
1043+
val lines = (geometry as GeoJsonMultiLineString).lineStrings
1044+
for (line in lines) {
1045+
coordinates.addAll(line.coordinates)
1046+
}
1047+
}
1048+
1049+
"Polygon" -> {
1050+
val lists = (geometry as GeoJsonPolygon).coordinates
1051+
for (list in lists) {
1052+
coordinates.addAll(list)
1053+
}
1054+
}
1055+
1056+
"MultiPolygon" -> {
1057+
val polygons = (geometry as GeoJsonMultiPolygon).polygons
1058+
for (polygon in polygons) {
1059+
for (list in polygon.coordinates) {
1060+
coordinates.addAll(list)
1061+
}
1062+
}
1063+
}
1064+
}
1065+
}
1066+
1067+
val builder = LatLngBounds.builder()
1068+
for (latLng in coordinates) {
1069+
builder.include(latLng)
1070+
}
1071+
return builder.build()
1072+
}
1073+
9441074
override fun onMapClick(point: LatLng) {
9451075
val data = JSObject()
9461076
data.put("mapId", this@CapacitorGoogleMap.id)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.capacitorjs.plugins.googlemaps
2+
3+
import android.graphics.Color
4+
import com.google.maps.android.data.Feature
5+
import com.google.maps.android.data.Layer
6+
import com.google.maps.android.data.geojson.GeoJsonFeature
7+
import com.google.maps.android.data.geojson.GeoJsonLayer
8+
import org.json.JSONObject
9+
import java.lang.Exception
10+
11+
class CapacitorGoogleMapsFeatureLayer(
12+
layer: Layer,
13+
feature: Feature,
14+
idPropertyName: String?,
15+
styles: JSONObject?
16+
) {
17+
var id: String? = null
18+
var layer: Layer? = null
19+
20+
init {
21+
(feature as? GeoJsonFeature)?.let {
22+
val properties: HashMap<String, String> = hashMapOf()
23+
for (propertyKey in feature.propertyKeys) {
24+
properties[propertyKey] = feature.getProperty(propertyKey)
25+
}
26+
if (idPropertyName != null) {
27+
id = feature.getProperty(idPropertyName)
28+
}
29+
val feature =
30+
GeoJsonFeature(
31+
feature.geometry,
32+
id,
33+
properties,
34+
null
35+
)
36+
this.layer = layer
37+
38+
val featureLayer = (layer as GeoJsonLayer);
39+
featureLayer.addFeature(feature)
40+
41+
if (styles != null) {
42+
try {
43+
featureLayer.defaultPolygonStyle.strokeColor =
44+
processColor(
45+
styles.getStyle("strokeColor"),
46+
styles.getStyle("strokeOpacity")
47+
)
48+
featureLayer.defaultPolygonStyle.strokeWidth = styles.getStyle("strokeWeight")
49+
featureLayer.defaultPolygonStyle.fillColor =
50+
processColor(styles.getStyle("fillColor"), styles.getStyle("fillOpacity"))
51+
featureLayer.defaultPolygonStyle.isGeodesic = styles.getStyle("geodesic")
52+
featureLayer.defaultLineStringStyle.color =
53+
featureLayer.defaultPolygonStyle.strokeColor
54+
featureLayer.defaultLineStringStyle.isGeodesic =
55+
featureLayer.defaultPolygonStyle.isGeodesic
56+
} catch (e: Exception) {
57+
throw InvalidArgumentsError("Styles object contains invalid values")
58+
}
59+
}
60+
}
61+
}
62+
63+
private fun processColor(hex: String, opacity: Double): Int {
64+
val colorInt = Color.parseColor(hex)
65+
66+
val alpha = (opacity * 255.0).toInt()
67+
val red = Color.red(colorInt)
68+
val green = Color.green(colorInt)
69+
val blue = Color.blue(colorInt)
70+
71+
return Color.argb(alpha, red, green, blue)
72+
}
73+
74+
private fun <T> JSONObject.getStyle(key: String) = this.getJSONObject(id).get(key) as T
75+
}

0 commit comments

Comments
 (0)