Skip to content

Commit

Permalink
feat(google-maps): Implemented map feature functions (i.e. GeoJSON su…
Browse files Browse the repository at this point in the history
…pport)
  • Loading branch information
A1Jan committed Jun 19, 2023
1 parent fc91569 commit adb72af
Show file tree
Hide file tree
Showing 12 changed files with 756 additions and 4 deletions.
62 changes: 62 additions & 0 deletions google-maps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ export default MyMap;
* [`removeCircles(...)`](#removecircles)
* [`addPolylines(...)`](#addpolylines)
* [`removePolylines(...)`](#removepolylines)
* [`addFeatures(...)`](#addfeatures)
* [`getFeatureBounds(...)`](#getfeaturebounds)
* [`removeFeature(...)`](#removefeature)
* [`destroy()`](#destroy)
* [`setCamera(...)`](#setcamera)
* [`getMapType()`](#getmaptype)
Expand Down Expand Up @@ -500,6 +503,52 @@ removePolylines(ids: string[]) => Promise<void>
--------------------


### addFeatures(...)

```typescript
addFeatures(type: FeatureType, data: any, idPropertyName: string, styles: FeatureStyles) => Promise<string[]>
```

| Param | Type |
| -------------------- | ------------------------------------------------------- |
| **`type`** | <code><a href="#featuretype">FeatureType</a></code> |
| **`data`** | <code>any</code> |
| **`idPropertyName`** | <code>string</code> |
| **`styles`** | <code><a href="#featurestyles">FeatureStyles</a></code> |

**Returns:** <code>Promise&lt;string[]&gt;</code>

--------------------


### getFeatureBounds(...)

```typescript
getFeatureBounds(featureId: string) => Promise<LatLngBounds>
```

| Param | Type |
| --------------- | ------------------- |
| **`featureId`** | <code>string</code> |

**Returns:** <code>Promise&lt;LatLngBounds&gt;</code>

--------------------


### removeFeature(...)

```typescript
removeFeature(featureId: string) => Promise<void>
```

| Param | Type |
| --------------- | ------------------- |
| **`featureId`** | <code>string</code> |

--------------------


### destroy()

```typescript
Expand Down Expand Up @@ -970,6 +1019,11 @@ Describes the style for some region of a polyline.
| **`segments`** | <code>number</code> | The length of this span in number of segments. |


#### FeatureStyles

Feature styles, identified by the feature id


#### CameraConfig

Configuration properties for a Google Map Camera
Expand Down Expand Up @@ -1112,6 +1166,14 @@ but the current specification only allows X, Y, and (optionally) Z to be defined
### Enums


#### FeatureType

| Members | Value | Description |
| ------------- | ---------------------- | ----------- |
| **`Default`** | <code>'Default'</code> | Default |
| **`GeoJSON`** | <code>'GeoJSON'</code> | GeoJSON |


#### MapType

| Members | Value | Description |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,19 @@ import com.google.android.gms.maps.GoogleMap.*
import com.google.android.gms.maps.model.*
import com.google.maps.android.clustering.Cluster
import com.google.maps.android.clustering.ClusterManager
import com.google.maps.android.data.Feature
import com.google.maps.android.data.geojson.GeoJsonFeature
import com.google.maps.android.data.geojson.GeoJsonGeometryCollection
import com.google.maps.android.data.geojson.GeoJsonLayer
import com.google.maps.android.data.geojson.GeoJsonLineString
import com.google.maps.android.data.geojson.GeoJsonMultiLineString
import com.google.maps.android.data.geojson.GeoJsonMultiPoint
import com.google.maps.android.data.geojson.GeoJsonMultiPolygon
import com.google.maps.android.data.geojson.GeoJsonPoint
import com.google.maps.android.data.geojson.GeoJsonPolygon
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import org.json.JSONObject
import java.io.InputStream
import java.net.URL

Expand Down Expand Up @@ -45,7 +56,8 @@ class CapacitorGoogleMap(
private val markers = HashMap<String, CapacitorGoogleMapMarker>()
private val polygons = HashMap<String, CapacitorGoogleMapsPolygon>()
private val circles = HashMap<String, CapacitorGoogleMapsCircle>()
private val polylines = HashMap<String, CapacitorGoogleMapPolyline>()
private val polylines = HashMap<String, CapacitorGoogleMapPolyline>()
private val featureLayers = HashMap<String, CapacitorGoogleMapsFeatureLayer>()
private val markerIcons = HashMap<String, Bitmap>()
private var clusterManager: ClusterManager<CapacitorGoogleMapMarker>? = null

Expand Down Expand Up @@ -317,6 +329,75 @@ class CapacitorGoogleMap(
}
}

fun addFeatures(type: String, data: JSONObject, idPropertyName: String, styles: JSONObject, callback: (ids: Result<List<String>>) -> Unit) {
try {
googleMap ?: throw GoogleMapNotAvailable()
val featureIds: MutableList<String> = mutableListOf()

CoroutineScope(Dispatchers.Main).launch {
if (type == "GeoJSON") {
val tempLayer = GeoJsonLayer(null, data)
tempLayer.features.forEach {
try {
val layer = GeoJsonLayer(googleMap, JSONObject())
val featureLayer = CapacitorGoogleMapsFeatureLayer(layer, it, idPropertyName, styles)
layer.addLayerToMap()
featureIds.add(featureLayer.id)
featureLayers[featureLayer.id] = featureLayer
callback(Result.success(featureIds))
} catch (e: Exception) {
callback(Result.failure(e))
}
}
} else {
callback(Result.failure(Error("addFeatures: not supported for this feature type")))
}
}
} catch (e: GoogleMapsError) {
callback(Result.failure(e))
}
}

fun getFeatureBounds(featureId: String, callback: (bounds: Result<LatLngBounds?>) -> Unit) {
try {
CoroutineScope(Dispatchers.Main).launch {
val featurelayer = featureLayers[featureId]
var feature: Feature? = null;
featurelayer?.layer?.features?.forEach lit@ {
if (it.id == featurelayer.id) {
feature = it
return@lit
}
}
if (feature != null) {
try {
(feature as GeoJsonFeature).let {
callback(Result.success(it.boundingBoxFromGeometry()))
}
} catch (e: Exception) {
callback(Result.failure(Error("getFeatureBounds: not supported for this feature type")))
}
} else {
callback(Result.failure(Error("Could not find feature for feature id $featureId")))
}
}
} catch(e: Exception) {
callback(Result.failure(Error("Could not find feature layer")))
}
}

fun removeFeature(featureId: String, callback: (error: GoogleMapsError?) -> Unit) {
CoroutineScope(Dispatchers.Main).launch {
val featurelayer = featureLayers[featureId]
if (featurelayer != null) {
featurelayer.layer?.removeLayerFromMap()
featureLayers.remove(featureId)
}

callback(null)
}
}

private fun setClusterManagerRenderer(minClusterSize: Int?) {
clusterManager?.renderer = CapacitorClusterManagerRenderer(
delegate.bridge.context,
Expand Down Expand Up @@ -925,6 +1006,53 @@ class CapacitorGoogleMap(
return data
}


private fun GeoJsonFeature.boundingBoxFromGeometry(): LatLngBounds? {
val coordinates: MutableList<LatLng> = ArrayList()

if (this.hasGeometry()) {
when (geometry.geometryType) {
"Point" -> coordinates.add((geometry as GeoJsonPoint).coordinates)
"MultiPoint" -> {
val points = (geometry as GeoJsonMultiPoint).points
for (point in points) {
coordinates.add(point.coordinates)
}
}

"LineString" -> coordinates.addAll((geometry as GeoJsonLineString).coordinates)
"MultiLineString" -> {
val lines = (geometry as GeoJsonMultiLineString).lineStrings
for (line in lines) {
coordinates.addAll(line.coordinates)
}
}

"Polygon" -> {
val lists = (geometry as GeoJsonPolygon).coordinates
for (list in lists) {
coordinates.addAll(list)
}
}

"MultiPolygon" -> {
val polygons = (geometry as GeoJsonMultiPolygon).polygons
for (polygon in polygons) {
for (list in polygon.coordinates) {
coordinates.addAll(list)
}
}
}
}
}

val builder = LatLngBounds.builder()
for (latLng in coordinates) {
builder.include(latLng)
}
return builder.build()
}

override fun onMapClick(point: LatLng) {
val data = JSObject()
data.put("mapId", this@CapacitorGoogleMap.id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.capacitorjs.plugins.googlemaps

import android.graphics.Color
import com.google.maps.android.data.Feature
import com.google.maps.android.data.Layer
import com.google.maps.android.data.geojson.GeoJsonFeature
import com.google.maps.android.data.geojson.GeoJsonLayer
import org.json.JSONObject
import java.lang.Exception

class CapacitorGoogleMapsFeatureLayer(
layer: Layer,
feature: Feature,
idPropertyName: String,
styles: JSONObject
) {
var id: String = ""
var layer: Layer? = null

init {
(feature as? GeoJsonFeature)?.let {
val properties: HashMap<String, String> = hashMapOf()
for (propertyKey in feature.propertyKeys) {
properties[propertyKey] = feature.getProperty(propertyKey)
}
val feature =
GeoJsonFeature(
feature.geometry,
feature.getProperty(idPropertyName),
properties,
null
)
id = feature.id
this.layer = layer

val featureLayer = (layer as GeoJsonLayer);
featureLayer.addFeature(feature)

try {
featureLayer.defaultPolygonStyle.strokeColor =
processColor(styles.getStyle("strokeColor"), styles.getStyle("strokeOpacity"))
featureLayer.defaultPolygonStyle.strokeWidth = styles.getStyle("strokeWeight")
featureLayer.defaultPolygonStyle.fillColor =
processColor(styles.getStyle("fillColor"), styles.getStyle("fillOpacity"))
featureLayer.defaultPolygonStyle.isGeodesic = styles.getStyle("geodesic")
featureLayer.defaultLineStringStyle.color =
featureLayer.defaultPolygonStyle.strokeColor
featureLayer.defaultLineStringStyle.isGeodesic =
featureLayer.defaultPolygonStyle.isGeodesic
} catch (e: Exception) {
throw InvalidArgumentsError("Styles object contains invalid values")
}
}
}

private fun processColor(hex: String, opacity: Double): Int {
val colorInt = Color.parseColor(hex)

val alpha = (opacity * 255.0).toInt()
val red = Color.red(colorInt)
val green = Color.green(colorInt)
val blue = Color.blue(colorInt)

return Color.argb(alpha, red, green, blue)
}

private fun <T> JSONObject.getStyle(key: String) = this.getJSONObject(id).get(key) as T
}
Loading

0 comments on commit adb72af

Please sign in to comment.