Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(google-maps): Implemented map feature functions (i.e. GeoJSON support) #1605

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions google-maps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,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 @@ -527,6 +530,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 @@ -1016,6 +1065,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 @@ -1158,6 +1212,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 @@ -324,6 +336,78 @@ 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()
if (id != null) {
featureIds.add(id)
featureLayers[id] = featureLayer
}
callback(Result.success(featureIds))
} catch (e: Exception) {
callback(Result.failure(e))
}
}
} else {
callback(Result.failure(InvalidArgumentsError("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(InvalidArgumentsError("getFeatureBounds: not supported for this feature type")))
}
} else {
callback(Result.failure(InvalidArgumentsError("Could not find feature for feature id $featureId")))
}
}
} catch(e: Exception) {
callback(Result.failure(InvalidArgumentsError("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)
} else {
callback(InvalidArgumentsError("Could not find feature for feature id $featureId"))
}
}
}

private fun setClusterManagerRenderer(minClusterSize: Int?) {
clusterManager?.renderer = CapacitorClusterManagerRenderer(
delegate.bridge.context,
Expand Down Expand Up @@ -941,6 +1025,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", [email protected])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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? = null
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)
}
if (idPropertyName != null) {
id = feature.getProperty(idPropertyName)
}
val feature =
GeoJsonFeature(
feature.geometry,
id,
properties,
null
)
this.layer = layer

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

if (styles != null) {
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
Loading