diff --git a/play-services-api/src/main/java/com/google/android/gms/maps/GoogleMapOptions.java b/play-services-api/src/main/java/com/google/android/gms/maps/GoogleMapOptions.java index 405f31d11714a6344f88aac2d3cd0d05c47f0a4c..346660534db0792b25e5f90897952eef60b59eb5 100644 --- a/play-services-api/src/main/java/com/google/android/gms/maps/GoogleMapOptions.java +++ b/play-services-api/src/main/java/com/google/android/gms/maps/GoogleMapOptions.java @@ -46,7 +46,7 @@ public final class GoogleMapOptions extends AutoSafeParcelable { @SafeParceled(11) private boolean rotateGesturesEnabled = true; @SafeParceled(12) - private boolean liteMode = false; + private int liteMode = 0; @SafeParceled(14) private boolean mapToobarEnabled = false; @SafeParceled(15) @@ -79,8 +79,9 @@ public final class GoogleMapOptions extends AutoSafeParcelable { return boundsForCamera; } - public Boolean getLiteMode() { - return liteMode; + public boolean getLiteMode() { + // Is encoded as `-1` if null, `0` if false, `1` if true. The default is false. + return liteMode == 1; } public Boolean getMapToolbarEnabled() { diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/AbstractGoogleMap.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/AbstractGoogleMap.kt new file mode 100644 index 0000000000000000000000000000000000000000..57f09515fe20709d18c6f3306573106e16862af8 --- /dev/null +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/AbstractGoogleMap.kt @@ -0,0 +1,144 @@ +package org.microg.gms.maps.mapbox + +import android.content.Context +import android.location.Location +import android.os.Bundle +import android.util.DisplayMetrics +import android.util.Log +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper +import com.google.android.gms.maps.internal.* +import org.microg.gms.maps.MapsConstants +import org.microg.gms.maps.mapbox.model.AbstractMarker +import org.microg.gms.maps.mapbox.model.DefaultInfoWindowAdapter +import org.microg.gms.maps.mapbox.model.InfoWindow +import org.microg.gms.maps.mapbox.utils.MapContext + +fun getStyleUriByMapType(mapType: Int) = when (mapType) { + MapsConstants.MAP_TYPE_SATELLITE -> "mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi" + MapsConstants.MAP_TYPE_TERRAIN -> "mapbox://styles/mapbox/outdoors-v12" + MapsConstants.MAP_TYPE_HYBRID -> "mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi" + //MAP_TYPE_NONE, MAP_TYPE_NORMAL, + else -> "mapbox://styles/microg/cjui4020201oo1fmca7yuwbor" +} + +abstract class AbstractGoogleMap(context: Context) : IGoogleMapDelegate.Stub() { + + internal val mapContext = MapContext(context) + + val dpiFactor: Float + get() = mapContext.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT + + internal var currentInfoWindow: InfoWindow? = null + internal var infoWindowAdapter: IInfoWindowAdapter = DefaultInfoWindowAdapter(mapContext) + internal var onInfoWindowClickListener: IOnInfoWindowClickListener? = null + internal var onInfoWindowLongClickListener: IOnInfoWindowLongClickListener? = null + internal var onInfoWindowCloseListener: IOnInfoWindowCloseListener? = null + + internal var mapClickListener: IOnMapClickListener? = null + internal var mapLongClickListener: IOnMapLongClickListener? = null + internal var markerClickListener: IOnMarkerClickListener? = null + internal var circleClickListener: IOnCircleClickListener? = null + + + internal abstract fun showInfoWindow(marker: AbstractMarker): Boolean + + override fun setOnInfoWindowClickListener(listener: IOnInfoWindowClickListener?) { + onInfoWindowClickListener = listener + } + + override fun setInfoWindowLongClickListener(listener: IOnInfoWindowLongClickListener) { + onInfoWindowLongClickListener = listener + } + + override fun setInfoWindowCloseListener(listener: IOnInfoWindowCloseListener) { + onInfoWindowCloseListener = listener + } + + override fun setInfoWindowAdapter(adapter: IInfoWindowAdapter?) { + infoWindowAdapter = adapter ?: DefaultInfoWindowAdapter(mapContext) + } + + override fun setOnMapClickListener(listener: IOnMapClickListener?) { + mapClickListener = listener + } + + override fun setOnMapLongClickListener(listener: IOnMapLongClickListener?) { + mapLongClickListener = listener + } + + override fun setOnMarkerClickListener(listener: IOnMarkerClickListener?) { + markerClickListener = listener + } + + override fun setCircleClickListener(listener: IOnCircleClickListener?) { + circleClickListener = listener + } + + override fun getMyLocation(): Location? { + Log.d(TAG, "unimplemented Method: getMyLocation") + return null + } + + override fun setLocationSource(locationSource: ILocationSourceDelegate?) { + Log.d(TAG, "unimplemented Method: setLocationSource") + } + + override fun setOnMyLocationChangeListener(listener: IOnMyLocationChangeListener?) { + Log.d(TAG, "unimplemented Method: setOnMyLocationChangeListener") + } + + override fun setOnMyLocationButtonClickListener(listener: IOnMyLocationButtonClickListener?) { + Log.d(TAG, "unimplemented Method: setOnMyLocationButtonClickListener") + } + + override fun getTestingHelper(): IObjectWrapper { + Log.d(TAG, "unimplemented Method: getTestingHelper") + return ObjectWrapper.wrap(null) + } + + override fun isBuildingsEnabled(): Boolean { + Log.d(TAG, "unimplemented Method: isBuildingsEnabled") + return false + } + + override fun setBuildingsEnabled(buildings: Boolean) { + Log.d(TAG, "unimplemented Method: setBuildingsEnabled") + } + + override fun useViewLifecycleWhenInFragment(): Boolean { + Log.d(TAG, "unimplemented Method: useViewLifecycleWhenInFragment") + return false + } + + override fun onEnterAmbient(bundle: Bundle?) { + Log.d(TAG, "unimplemented Method: onEnterAmbient") + } + + override fun onExitAmbient() { + Log.d(TAG, "unimplemented Method: onExitAmbient") + } + + override fun isTrafficEnabled(): Boolean { + Log.d(TAG, "unimplemented Method: isTrafficEnabled") + return false + } + + override fun setTrafficEnabled(traffic: Boolean) { + Log.d(TAG, "unimplemented Method: setTrafficEnabled") + + } + + override fun isIndoorEnabled(): Boolean { + Log.d(TAG, "unimplemented Method: isIndoorEnabled") + return false + } + + override fun setIndoorEnabled(indoor: Boolean) { + Log.d(TAG, "unimplemented Method: setIndoorEnabled") + } + + companion object { + val TAG = "GmsMapAbstract" + } +} \ No newline at end of file diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/CameraBoundsWithSizeUpdate.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/CameraBoundsWithSizeUpdate.kt index c31bf22387989c8e47aaa1ac56e9c8308d7e0684..6d97b262f9b12f6ac84937e4ce437c7c66a34a52 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/CameraBoundsWithSizeUpdate.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/CameraBoundsWithSizeUpdate.kt @@ -17,16 +17,21 @@ package org.microg.gms.maps.mapbox import android.util.Log +import com.google.android.gms.maps.internal.IGoogleMapDelegate import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.camera.CameraUpdate import com.mapbox.mapboxsdk.geometry.LatLngBounds import com.mapbox.mapboxsdk.maps.MapboxMap import java.util.* -internal class CameraBoundsWithSizeUpdate(val bounds: LatLngBounds, val width: Int, val height: Int, val padding: IntArray) : CameraUpdate { +internal class CameraBoundsWithSizeUpdate(val bounds: LatLngBounds, val width: Int, val height: Int, val padding: IntArray) : LiteModeCameraUpdate, CameraUpdate { constructor(bounds: LatLngBounds, width: Int, height: Int, paddingLeft: Int, paddingTop: Int = paddingLeft, paddingRight: Int = paddingLeft, paddingBottom: Int = paddingTop) : this(bounds, width, height, intArrayOf(paddingLeft, paddingTop, paddingRight, paddingBottom)) {} + override fun getLiteModeCameraPosition(map: IGoogleMapDelegate) = null + + override fun getLiteModeCameraBounds() = bounds + override fun getCameraPosition(map: MapboxMap): CameraPosition? { val padding = this.padding.clone() diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/CameraUpdateFactory.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/CameraUpdateFactory.kt index 675429b90e72f0be427d5915e5b207f6936d5ec0..d5cd86d49b3653ef229e38375f3a7f0ec5f13b07 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/CameraUpdateFactory.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/CameraUpdateFactory.kt @@ -17,11 +17,13 @@ package org.microg.gms.maps.mapbox import android.graphics.Point +import android.graphics.PointF import android.os.Parcel import android.util.Log import com.google.android.gms.dynamic.IObjectWrapper import com.google.android.gms.dynamic.ObjectWrapper import com.google.android.gms.maps.internal.ICameraUpdateFactoryDelegate +import com.google.android.gms.maps.internal.IGoogleMapDelegate import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.LatLngBounds @@ -32,29 +34,40 @@ import org.microg.gms.maps.mapbox.utils.toMapbox class CameraUpdateFactoryImpl : ICameraUpdateFactoryDelegate.Stub() { - override fun zoomIn(): IObjectWrapper = ObjectWrapper.wrap(CameraUpdateFactory.zoomIn()) - override fun zoomOut(): IObjectWrapper = ObjectWrapper.wrap(CameraUpdateFactory.zoomOut()) + override fun zoomIn(): IObjectWrapper = ObjectWrapper.wrap(ZoomByCameraUpdate(1f)) + override fun zoomOut(): IObjectWrapper = ObjectWrapper.wrap(ZoomByCameraUpdate(-1f)) - override fun zoomTo(zoom: Float): IObjectWrapper = - ObjectWrapper.wrap(CameraUpdateFactory.zoomTo(zoom.toDouble() - 1.0)) + override fun zoomTo(zoom: Float): IObjectWrapper = ObjectWrapper.wrap(ZoomToCameraUpdate(zoom)) override fun zoomBy(zoomDelta: Float): IObjectWrapper = - ObjectWrapper.wrap(CameraUpdateFactory.zoomBy(zoomDelta.toDouble())) + ObjectWrapper.wrap(ZoomByCameraUpdate(zoomDelta)).also { + Log.d(TAG, "zoomBy") + } override fun zoomByWithFocus(zoomDelta: Float, x: Int, y: Int): IObjectWrapper = - ObjectWrapper.wrap(CameraUpdateFactory.zoomBy(zoomDelta.toDouble(), Point(x, y))) + ObjectWrapper.wrap(ZoomByWithFocusCameraUpdate(zoomDelta, x, y)).also { + Log.d(TAG, "zoomByWithFocus") + } override fun newCameraPosition(cameraPosition: CameraPosition): IObjectWrapper = - ObjectWrapper.wrap(CameraUpdateFactory.newCameraPosition(cameraPosition.toMapbox())) + ObjectWrapper.wrap(NewCameraPositionCameraUpdate(cameraPosition)).also { + Log.d(TAG, "newCameraPosition") + } override fun newLatLng(latLng: LatLng): IObjectWrapper = - ObjectWrapper.wrap(CameraUpdateFactory.newLatLng(latLng.toMapbox())) + ObjectWrapper.wrap(NewLatLngCameraUpdate(latLng)).also { + Log.d(TAG, "newLatLng") + } override fun newLatLngZoom(latLng: LatLng, zoom: Float): IObjectWrapper = - ObjectWrapper.wrap(CameraUpdateFactory.newLatLngZoom(latLng.toMapbox(), zoom.toDouble() - 1.0)) + ObjectWrapper.wrap(NewLatLngZoomCameraUpdate(latLng, zoom)).also { + Log.d(TAG, "newLatLngZoom") + } override fun newLatLngBounds(bounds: LatLngBounds, padding: Int): IObjectWrapper = - ObjectWrapper.wrap(CameraUpdateFactory.newLatLngBounds(bounds.toMapbox(), padding)) + ObjectWrapper.wrap(NewLatLngBoundsCameraUpdate(bounds, padding)).also { + Log.d(TAG, "newLatLngBounds") + } override fun scrollBy(x: Float, y: Float): IObjectWrapper { Log.d(TAG, "unimplemented Method: scrollBy") @@ -62,7 +75,9 @@ class CameraUpdateFactoryImpl : ICameraUpdateFactoryDelegate.Stub() { } override fun newLatLngBoundsWithSize(bounds: LatLngBounds, width: Int, height: Int, padding: Int): IObjectWrapper = - ObjectWrapper.wrap(CameraBoundsWithSizeUpdate(bounds.toMapbox(), width, height, padding)) + ObjectWrapper.wrap(CameraBoundsWithSizeUpdate(bounds.toMapbox(), width, height, padding)).also { + Log.d(TAG, "newLatLngBoundsWithSize") + } override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = if (super.onTransact(code, data, reply, flags)) { @@ -71,9 +86,11 @@ class CameraUpdateFactoryImpl : ICameraUpdateFactoryDelegate.Stub() { Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false } - private inner class NoCameraUpdate : CameraUpdate { + private inner class NoCameraUpdate : CameraUpdate, LiteModeCameraUpdate { override fun getCameraPosition(mapboxMap: MapboxMap): com.mapbox.mapboxsdk.camera.CameraPosition? = mapboxMap.cameraPosition + + override fun getLiteModeCameraPosition(map: IGoogleMapDelegate): CameraPosition = map.cameraPosition } companion object { @@ -81,4 +98,70 @@ class CameraUpdateFactoryImpl : ICameraUpdateFactoryDelegate.Stub() { } } +interface LiteModeCameraUpdate { + fun getLiteModeCameraPosition(map: IGoogleMapDelegate): CameraPosition? + + fun getLiteModeCameraBounds(): com.mapbox.mapboxsdk.geometry.LatLngBounds? = null +} + +class ZoomToCameraUpdate(private val zoom: Float) : LiteModeCameraUpdate, CameraUpdate { + override fun getLiteModeCameraPosition(map: IGoogleMapDelegate): CameraPosition = + CameraPosition.Builder(map.cameraPosition).zoom(zoom).build() + + override fun getCameraPosition(mapboxMap: MapboxMap): com.mapbox.mapboxsdk.camera.CameraPosition? = + CameraUpdateFactory.zoomTo(zoom.toDouble() - 1.0).getCameraPosition(mapboxMap) + +} + +class ZoomByCameraUpdate(private val delta: Float) : LiteModeCameraUpdate, CameraUpdate { + override fun getLiteModeCameraPosition(map: IGoogleMapDelegate): CameraPosition = + CameraPosition.Builder(map.cameraPosition).zoom(map.cameraPosition.zoom + delta).build() + + override fun getCameraPosition(mapboxMap: MapboxMap): com.mapbox.mapboxsdk.camera.CameraPosition? = + CameraUpdateFactory.zoomBy(delta.toDouble()).getCameraPosition(mapboxMap) + +} + +class ZoomByWithFocusCameraUpdate(private val delta: Float, private val x: Int, private val y: Int) : LiteModeCameraUpdate, + CameraUpdate { + override fun getLiteModeCameraPosition(map: IGoogleMapDelegate): CameraPosition = + CameraPosition.Builder(map.cameraPosition).zoom(map.cameraPosition.zoom + delta) + .target(map.projection.fromScreenLocation(ObjectWrapper.wrap(PointF(x.toFloat(), y.toFloat())))).build() + + override fun getCameraPosition(mapboxMap: MapboxMap): com.mapbox.mapboxsdk.camera.CameraPosition? = + CameraUpdateFactory.zoomBy(delta.toDouble(), Point(x, y)).getCameraPosition(mapboxMap) +} + +class NewCameraPositionCameraUpdate(private val cameraPosition: CameraPosition) : LiteModeCameraUpdate, CameraUpdate { + override fun getLiteModeCameraPosition(map: IGoogleMapDelegate): CameraPosition = this.cameraPosition + + override fun getCameraPosition(mapboxMap: MapboxMap): com.mapbox.mapboxsdk.camera.CameraPosition = + this.cameraPosition.toMapbox() +} + +class NewLatLngCameraUpdate(private val latLng: LatLng) : LiteModeCameraUpdate, CameraUpdate { + override fun getLiteModeCameraPosition(map: IGoogleMapDelegate): CameraPosition = + CameraPosition.Builder(map.cameraPosition).target(latLng).build() + + override fun getCameraPosition(mapboxMap: MapboxMap): com.mapbox.mapboxsdk.camera.CameraPosition? = + CameraUpdateFactory.newLatLng(latLng.toMapbox()).getCameraPosition(mapboxMap) +} + +class NewLatLngZoomCameraUpdate(private val latLng: LatLng, private val zoom: Float) : LiteModeCameraUpdate, CameraUpdate { + override fun getLiteModeCameraPosition(map: IGoogleMapDelegate): CameraPosition = + CameraPosition.Builder(map.cameraPosition).target(latLng).zoom(zoom).build() + + override fun getCameraPosition(mapboxMap: MapboxMap): com.mapbox.mapboxsdk.camera.CameraPosition? = + CameraUpdateFactory.newLatLngZoom(latLng.toMapbox(), zoom - 1.0).getCameraPosition(mapboxMap) +} + +class NewLatLngBoundsCameraUpdate(private val bounds: LatLngBounds, internal val padding: Int) : LiteModeCameraUpdate, + CameraUpdate { + + override fun getLiteModeCameraPosition(map: IGoogleMapDelegate): CameraPosition? = null + + override fun getLiteModeCameraBounds() = bounds.toMapbox() + override fun getCameraPosition(mapboxMap: MapboxMap): com.mapbox.mapboxsdk.camera.CameraPosition? = + CameraUpdateFactory.newLatLngBounds(bounds.toMapbox(), padding).getCameraPosition(mapboxMap) +} \ No newline at end of file diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt index 297dc873ae66a903dd9b90b5ffbe411e6c4189c7..7e33f085496f800f86557868a0d70f0a46972099 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt @@ -18,12 +18,9 @@ package org.microg.gms.maps.mapbox import android.annotation.SuppressLint import android.content.Context -import android.graphics.Point -import android.location.Location import android.os.* import androidx.annotation.IdRes import androidx.annotation.Keep -import android.util.DisplayMetrics import android.util.Log import android.view.Gravity import android.view.View @@ -54,14 +51,12 @@ import com.mapbox.mapboxsdk.plugins.annotation.Annotation import com.mapbox.mapboxsdk.style.layers.Property.LINE_CAP_ROUND import com.google.android.gms.dynamic.unwrap import com.mapbox.mapboxsdk.WellKnownTileServer -import org.microg.gms.maps.mapbox.model.DefaultInfoWindowAdapter import org.microg.gms.maps.mapbox.model.InfoWindow import org.microg.gms.maps.mapbox.model.getInfoWindowViewFor import com.mapbox.mapboxsdk.camera.CameraUpdateFactory import com.mapbox.mapboxsdk.maps.OnMapReadyCallback import org.microg.gms.maps.MapsConstants.* import org.microg.gms.maps.mapbox.model.* -import org.microg.gms.maps.mapbox.utils.MapContext import org.microg.gms.maps.mapbox.utils.MultiArchLoader import org.microg.gms.maps.mapbox.utils.toGms import org.microg.gms.maps.mapbox.utils.toMapbox @@ -78,13 +73,11 @@ fun runOnMainLooper(method: () -> Unit) { } } -class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) : IGoogleMapDelegate.Stub() { +class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractGoogleMap(context) { val view: FrameLayout var map: MapboxMap? = null private set - val dpiFactor: Float - get() = context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT private var mapView: MapView? = null private var created = false @@ -99,18 +92,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) private var cameraMoveCanceledListener: IOnCameraMoveCanceledListener? = null private var cameraMoveStartedListener: IOnCameraMoveStartedListener? = null private var cameraIdleListener: IOnCameraIdleListener? = null - private var mapClickListener: IOnMapClickListener? = null - private var mapLongClickListener: IOnMapLongClickListener? = null - private var markerClickListener: IOnMarkerClickListener? = null private var markerDragListener: IOnMarkerDragListener? = null - private var circleClickListener: IOnCircleClickListener? = null - - private var infoWindowAdapter: IInfoWindowAdapter = DefaultInfoWindowAdapter(MapContext(context)) - internal var onInfoWindowClickListener: IOnInfoWindowClickListener? = null - internal var onInfoWindowLongClickListener: IOnInfoWindowLongClickListener? = null - internal var onInfoWindowCloseListener: IOnInfoWindowCloseListener? = null - - var currentInfoWindow: InfoWindow? = null var lineManager: LineManager? = null val pendingLines = mutableSetOf>() @@ -134,7 +116,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) var locationEnabled: Boolean = false init { - val mapContext = MapContext(context) BitmapDescriptorFactoryImpl.initialize(mapContext.resources, context.resources) LibraryLoader.setLibraryLoader(MultiArchLoader(mapContext, context)) runOnMainLooper { @@ -290,6 +271,13 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } else { fill.update(fillManager) } + + val lineManager = lineManager + if (lineManager == null) { + pendingLines.addAll(fill.strokes) + } else { + for (stroke in fill.strokes) stroke.update(lineManager) + } } return fill } @@ -373,13 +361,11 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } // TODO: Serve map styles locally - when (storedMapType) { - MAP_TYPE_SATELLITE -> map?.setStyle(Style.Builder().fromUri("mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi"), update) - MAP_TYPE_TERRAIN -> map?.setStyle(Style.Builder().fromUri("mapbox://styles/mapbox/outdoors-v12"), update) - MAP_TYPE_HYBRID -> map?.setStyle(Style.Builder().fromUri("mapbox://styles/microg/cjxgloted25ap1ct4uex7m6hi"), update) - //MAP_TYPE_NONE, MAP_TYPE_NORMAL, - else -> map?.setStyle(Style.Builder().fromUrl("mapbox://styles/microg/cjui4020201oo1fmca7yuwbor"), update) - } + map?.setStyle( + Style.Builder().fromUri( + getStyleUriByMapType(storedMapType) + ), update + ) map?.let { BitmapDescriptorFactoryImpl.registerMap(it) } @@ -389,26 +375,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) it.uiSettings.isLogoEnabled = watermark } - override fun isTrafficEnabled(): Boolean { - Log.d(TAG, "unimplemented Method: isTrafficEnabled") - return false - } - - override fun setTrafficEnabled(traffic: Boolean) { - Log.d(TAG, "unimplemented Method: setTrafficEnabled") - - } - - override fun isIndoorEnabled(): Boolean { - Log.d(TAG, "unimplemented Method: isIndoorEnabled") - return false - } - - override fun setIndoorEnabled(indoor: Boolean) { - Log.d(TAG, "unimplemented Method: setIndoorEnabled") - - } - override fun isMyLocationEnabled(): Boolean { return locationEnabled } @@ -426,19 +392,9 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) Log.w(TAG, e) locationEnabled = false } - Unit } } - override fun getMyLocation(): Location? { - Log.d(TAG, "unimplemented Method: getMyLocation") - return null - } - - override fun setLocationSource(locationSource: ILocationSourceDelegate?) { - Log.d(TAG, "unimplemented Method: setLocationSource") - } - override fun setContentDescription(desc: String?) { mapView?.contentDescription = desc } @@ -449,85 +405,23 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) initializedCallbackList.add(it.getMapReadyCallback()) } - override fun getProjection(): IProjectionDelegate? = map?.projection?.let { + override fun getProjection(): IProjectionDelegate = map?.projection?.let { val experiment = try { map?.cameraPosition?.tilt == 0.0 && map?.cameraPosition?.bearing == 0.0 } catch (e: Exception) { Log.w(TAG, e); false } ProjectionImpl(it, experiment) - } ?: object : IProjectionDelegate.Stub() { // dummy projection if map not initialized - override fun fromScreenLocation(obj: IObjectWrapper?): LatLng { - Log.d(TAG, "Map not initialized when calling getProjection(). Cannot calculate fromScreenLocation") - return LatLng(0.0, 0.0) - } - - override fun toScreenLocation(latLng: LatLng?): IObjectWrapper { - Log.d(TAG, "Map not initialized when calling getProjection(). Cannot calculate toScreenLocation") - return ObjectWrapper.wrap(Point(0, 0)) - } - - override fun getVisibleRegion(): VisibleRegion { - Log.d(TAG, "Map not initialized when calling getProjection(). Cannot calculate getVisibleRegion") - return VisibleRegion(LatLngBounds(LatLng(0.0, 0.0), LatLng(0.0, 0.0))) - } - } + } ?: DummyProjection() override fun setOnCameraChangeListener(listener: IOnCameraChangeListener?) { cameraChangeListener = listener } - override fun setOnMapClickListener(listener: IOnMapClickListener?) { - mapClickListener = listener - } - - override fun setOnMapLongClickListener(listener: IOnMapLongClickListener?) { - mapLongClickListener = listener - } - - override fun setOnMarkerClickListener(listener: IOnMarkerClickListener?) { - markerClickListener = listener - } - override fun setOnMarkerDragListener(listener: IOnMarkerDragListener?) { markerDragListener = listener } - override fun setCircleClickListener(listener: IOnCircleClickListener?) { - circleClickListener = listener - } - - override fun setOnInfoWindowClickListener(listener: IOnInfoWindowClickListener?) { - onInfoWindowClickListener = listener - } - - override fun setInfoWindowLongClickListener(listener: IOnInfoWindowLongClickListener) { - onInfoWindowLongClickListener = listener - } - - override fun setInfoWindowCloseListener(listener: IOnInfoWindowCloseListener) { - onInfoWindowCloseListener = listener - } - - override fun setInfoWindowAdapter(adapter: IInfoWindowAdapter?) { - infoWindowAdapter = adapter ?: DefaultInfoWindowAdapter(MapContext(context)) - } - - override fun getTestingHelper(): IObjectWrapper? { - Log.d(TAG, "unimplemented Method: getTestingHelper") - return null - } - - override fun setOnMyLocationChangeListener(listener: IOnMyLocationChangeListener?) { - Log.d(TAG, "unimplemented Method: setOnMyLocationChangeListener") - - } - - override fun setOnMyLocationButtonClickListener(listener: IOnMyLocationButtonClickListener?) { - Log.d(TAG, "unimplemented Method: setOnMyLocationButtonClickListener") - - } - override fun snapshot(callback: ISnapshotReadyCallback, bitmap: IObjectWrapper?) { val map = map if (map == null) { @@ -556,15 +450,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) map.uiSettings.setAttributionMargins(left + ninetyTwoDp, top + fourDp, right + fourDp, bottom + fourDp) } - override fun isBuildingsEnabled(): Boolean { - Log.d(TAG, "unimplemented Method: isBuildingsEnabled") - return false - } - - override fun setBuildingsEnabled(buildings: Boolean) { - Log.d(TAG, "unimplemented Method: setBuildingsEnabled") - } - override fun setOnMapLoadedCallback(callback: IOnMapLoadedCallback?) { if (callback != null) { synchronized(mapLock) { @@ -604,7 +489,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun onCreate(savedInstanceState: Bundle?) { if (!created) { Log.d(TAG, "create"); - val mapView = MapView(MapContext(context)) + val mapView = MapView(mapContext) this.mapView = mapView view.addView(mapView) mapView.onCreate(savedInstanceState?.toMapbox()) @@ -786,7 +671,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) pendingMarkers.forEach { it.update(symbolManager) } pendingMarkers.clear() - val mapContext = MapContext(context) map.locationComponent.apply { activateLocationComponent(LocationComponentActivationOptions.builder(mapContext, it) .useSpecializedLocationLayer(true) @@ -813,8 +697,8 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } } - internal fun showInfoWindow(marker: MarkerImpl): Boolean { - infoWindowAdapter.getInfoWindowViewFor(marker, MapContext(context))?.let { infoView -> + override fun showInfoWindow(marker: AbstractMarker): Boolean { + infoWindowAdapter.getInfoWindowViewFor(marker, mapContext)?.let { infoView -> currentInfoWindow?.close() currentInfoWindow = InfoWindow(infoView, this, marker).also { infoWindow -> mapView?.let { infoWindow.open(it) } @@ -824,11 +708,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) return false } - override fun useViewLifecycleWhenInFragment(): Boolean { - Log.d(TAG, "unimplemented Method: useViewLifecycleWhenInFragment") - return false - } - override fun onResume() = mapView?.onResume() ?: Unit override fun onPause() = mapView?.onPause() ?: Unit override fun onDestroy() { @@ -872,13 +751,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) mapView?.onStop() } - override fun onEnterAmbient(bundle: Bundle?) { - Log.d(TAG, "unimplemented Method: onEnterAmbient") - } - - override fun onExitAmbient() { - Log.d(TAG, "unimplemented Method: onExitAmbient") - } override fun onLowMemory() = mapView?.onLowMemory() ?: Unit override fun onSaveInstanceState(outState: Bundle) { diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/LiteGoogleMap.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/LiteGoogleMap.kt new file mode 100644 index 0000000000000000000000000000000000000000..c7499947a5a3fbd98e7dfeb04a1ded130cc1f856 --- /dev/null +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/LiteGoogleMap.kt @@ -0,0 +1,662 @@ +package org.microg.gms.maps.mapbox + +import android.Manifest +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_VIEW +import android.content.pm.PackageManager +import android.graphics.PointF +import android.location.Location +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Log +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.annotation.UiThread +import androidx.core.app.ActivityCompat +import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper +import com.google.android.gms.dynamic.unwrap +import com.google.android.gms.maps.GoogleMapOptions +import com.google.android.gms.maps.internal.* +import com.google.android.gms.maps.model.* +import com.google.android.gms.maps.model.internal.* +import com.mapbox.mapboxsdk.Mapbox +import com.mapbox.mapboxsdk.WellKnownTileServer +import com.mapbox.mapboxsdk.location.engine.* +import com.mapbox.mapboxsdk.maps.Style +import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions +import com.mapbox.mapboxsdk.snapshotter.MapSnapshot +import com.mapbox.mapboxsdk.snapshotter.MapSnapshotter +import com.mapbox.mapboxsdk.style.layers.* +import com.mapbox.mapboxsdk.style.sources.GeoJsonSource +import com.mapbox.turf.TurfConstants.UNIT_METERS +import com.mapbox.turf.TurfMeasurement +import org.microg.gms.maps.mapbox.model.* +import org.microg.gms.maps.mapbox.utils.toGms +import org.microg.gms.maps.mapbox.utils.toMapbox +import org.microg.gms.maps.mapbox.utils.toPoint +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.math.max +import kotlin.math.roundToInt + +// From com.mapbox.mapboxsdk.location.LocationComponent +const val DEFAULT_INTERVAL_MILLIS = 1000L +const val DEFAULT_FASTEST_INTERVAL_MILLIS = 1000L + +class MetaSnapshot( + val snapshot: MapSnapshot, + val cameraPosition: CameraPosition, + val cameraBounds: com.mapbox.mapboxsdk.geometry.LatLngBounds?, + val width: Int, + val height: Int, + val paddingRight: Int, + val paddingTop: Int, + val dpi: Float +) { + fun latLngForPixelFixed(point: PointF) = snapshot.latLngForPixel( + PointF( + point.x / dpi, point.y / dpi + ) + ) +} + +class LiteGoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractGoogleMap(context) { + + internal val view: FrameLayout = FrameLayout(mapContext) + val map: ImageView + + private var created = false + + private var cameraPosition: CameraPosition = options.camera + private var cameraBounds: com.mapbox.mapboxsdk.geometry.LatLngBounds? = null + + private var mapType: Int = options.mapType + + private var currentSnapshotter: MapSnapshotter? = null + + private var lastSnapshot: MetaSnapshot? = null + + private var lastTouchPosition = PointF(0f, 0f) + + private val afterNextDrawCallback = mutableListOf<() -> Unit>() + private var cameraChangeListener: IOnCameraChangeListener? = null + + private var myLocationEnabled = false + private var myLocation: Location? = null + private var locationEngineProvider: LocationEngine = LocationEngineProvider.getBestLocationEngine(mapContext) + private val locationListener = object : LocationEngineCallback { + override fun onSuccess(result: LocationEngineResult?) { + this@LiteGoogleMapImpl.myLocation = result?.lastLocation + postUpdateSnapshot() + } + + override fun onFailure(exception: Exception) { + // same behavior as MapLibre's LocationComponent + Log.e(TAG, "Failed to obtain location update", exception) + } + } + + internal val markers: MutableList = mutableListOf() + internal val polygons: MutableList = mutableListOf() + internal val polylines: MutableList = mutableListOf() + internal val circles: MutableList = mutableListOf() + + private var nextObjectId = 0 + + private var showWatermark = true + + private val updatePosted = AtomicBoolean(false) + + init { + map = ImageView(mapContext).apply { + layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + } + + view.addView(map) + + view.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + postUpdateSnapshot() + currentInfoWindow?.update() + } + + BitmapDescriptorFactoryImpl.initialize(mapContext.resources, context.resources) + + // noinspection ClickableViewAccessibility; touch listener only has side effects + map.setOnTouchListener { _, event -> + lastTouchPosition = PointF(event.x + map.paddingLeft, event.y + map.paddingTop) + false + } + + map.setOnClickListener { + + // Test if clickable + if ((view.parent as View?)?.isClickable == false) return@setOnClickListener + + lastSnapshot?.let { meta -> + // Calculate marker hitboxes + for (marker in markers.filter { it.isVisible }) { + + marker.getIconDimensions()?.let { iconDimensions -> // consider only markers with icon + val anchorPoint = meta.snapshot.pixelForLatLng(marker.position.toMapbox()) + + val leftX = anchorPoint.x - marker.anchor[0] * iconDimensions[0] + val topY = anchorPoint.y - marker.anchor[1] * iconDimensions[1] + + if (lastTouchPosition.x >= leftX && lastTouchPosition.x <= leftX + iconDimensions[0] + && lastTouchPosition.y >= topY && lastTouchPosition.y <= topY + iconDimensions[1]) { + // Marker was clicked + if (markerClickListener?.onMarkerClick(marker) == true) { + currentInfoWindow?.close() + currentInfoWindow = null + return@setOnClickListener + } else if (showInfoWindow(marker)) { + return@setOnClickListener + } + } + } + } + + currentInfoWindow?.close() + currentInfoWindow = null + + // Test if circle was clicked + for (circle in circles.filter { it.isVisible && it.isClickable }) { + Log.d(TAG, "last touch ${lastTouchPosition.x}, ${lastTouchPosition.y}, turf ${TurfMeasurement.distance( + circle.center.toPoint(), + meta.latLngForPixelFixed(lastTouchPosition).toPoint(), + UNIT_METERS + )}, radius ${circle.radiusInMeters}") + if (TurfMeasurement.distance( + circle.center.toPoint(), + meta.latLngForPixelFixed(lastTouchPosition).toPoint(), + UNIT_METERS + ) <= circle.radiusInMeters) { + // Circle was clicked + circleClickListener?.onCircleClick(circle) + return@setOnClickListener + } + } + + val clickedPosition = meta.latLngForPixelFixed(lastTouchPosition) + val clickListenerConsumedClick = mapClickListener?.let { + it.onMapClick(clickedPosition.toGms()) + true + } ?: false + + if (clickListenerConsumedClick) return@setOnClickListener + + // else open external map at clicked location + val intent = + Intent(ACTION_VIEW, Uri.parse("geo:${clickedPosition.latitude},${clickedPosition.longitude}")) + + try { + context.startActivity(intent) + } catch (e: ActivityNotFoundException) { + Log.e(TAG, "No compatible mapping application installed. Not handling click.") + } + } + + + } + map.setOnLongClickListener { + mapLongClickListener?.onMapLongClick( + lastSnapshot?.latLngForPixelFixed(lastTouchPosition)?.toGms() ?: LatLng(0.0, 0.0) + ) + mapLongClickListener != null + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + if (!created) { + + Mapbox.getInstance(mapContext, BuildConfig.MAPBOX_KEY, WellKnownTileServer.Mapbox) + + if (savedInstanceState?.containsKey(BUNDLE_CAMERA_POSITION) == true) { + cameraPosition = savedInstanceState.getParcelable(BUNDLE_CAMERA_POSITION)!! + cameraBounds = savedInstanceState.getParcelable(BUNDLE_CAMERA_BOUNDS) + } + + postUpdateSnapshot() + + created = true + } + } + + internal fun postUpdateSnapshot() { + if (updatePosted.compareAndSet(false, true)) { + Handler(Looper.getMainLooper()).post { + updatePosted.set(false) + updateSnapshot() + } + } + } + + @UiThread + private fun updateSnapshot() { + + val cameraPosition = cameraPosition + val dpi = dpiFactor + + val cameraBounds = cameraBounds + + val pixelWidth = map.width + val pixelHeight = map.height + + val styleBuilder = Style.Builder().fromUri(getStyleUriByMapType(mapType)) + + // Add visible polygons (before polylines, so that they are drawn below their strokes) + for (polygon in polygons.filter { it.isVisible }) { + styleBuilder.withLayer( + FillLayer("l${polygon.id}", polygon.id).withProperties( + PropertyFactory.fillColor(polygon.fillColor) + ) + ).withSource( + GeoJsonSource(polygon.id, polygon.annotationOptions.geometry) + ) + } + + // Add visible polylines + for (polyline in polylines.filter { it.isVisible }) { + styleBuilder.withLayer( + LineLayer("l${polyline.id}", polyline.id).withProperties( + PropertyFactory.lineWidth(polyline.width), + PropertyFactory.lineColor(polyline.color), + PropertyFactory.lineCap(Property.LINE_CAP_ROUND) + ) + ).withSource( + GeoJsonSource(polyline.id, polyline.annotationOptions.geometry) + ) + } + + // Add circles + for (circle in circles.filter { it.isVisible }) { + styleBuilder.withLayer(FillLayer("l${circle.id}c", circle.id).withProperties( + PropertyFactory.fillColor(circle.fillColor) + )).withSource(GeoJsonSource(circle.id, circle.annotationOptions.geometry)) + + styleBuilder.withLayer(LineLayer("l${circle.id}s", "${circle.id}s").withProperties( + PropertyFactory.lineWidth(circle.strokeWidth), + PropertyFactory.lineColor(circle.strokeColor), + PropertyFactory.lineCap(Property.LINE_CAP_ROUND) + )).withSource(GeoJsonSource("${circle.id}s", circle.line.annotationOptions.geometry)) + } + + // Add markers + BitmapDescriptorFactoryImpl.put(styleBuilder) + for (marker in markers.filter { it.isVisible }) { + val layer = SymbolLayer("l${marker.id}", marker.id).withProperties( + PropertyFactory.symbolSortKey(marker.zIndex), + PropertyFactory.iconAllowOverlap(true) + ) + marker.icon?.applyTo(layer, marker.anchor, dpi) + styleBuilder.withLayer(layer).withSource( + GeoJsonSource(marker.id, marker.annotationOptions.geometry) + ) + } + + // Add location overlay + if (myLocationEnabled) myLocation?.let { + val indicator = mapContext.getDrawable(R.drawable.location_dot)!! + styleBuilder.withImage("locationIndicator", indicator) + val layer = SymbolLayer("location", "locationSource").withProperties( + PropertyFactory.iconAllowOverlap(true), + PropertyFactory.iconImage("locationIndicator"), + PropertyFactory.iconAnchor(Property.ICON_ANCHOR_TOP_LEFT), + PropertyFactory.iconOffset(arrayOf( + 0.5f * indicator.minimumWidth / dpi, 0.5f * indicator.minimumHeight / dpi + )) + ) + styleBuilder.withLayer(layer).withSource( + GeoJsonSource( + "locationSource", + SymbolOptions().withLatLng(com.mapbox.mapboxsdk.geometry.LatLng(it.latitude, it.longitude)).geometry + ) + ) + } + + val dpiWidth = max(pixelWidth / dpi, 1f).roundToInt() + val dpiHeight = max(pixelHeight / dpi, 1f).roundToInt() + + val snapshotter = MapSnapshotter( + mapContext, MapSnapshotter.Options(dpiWidth, dpiHeight) + .withCameraPosition(this@LiteGoogleMapImpl.cameraPosition.toMapbox()) + .apply { + // if camera bounds are set, overwrite camera position + cameraBounds?.let { withRegion(it) } + } + .withStyleBuilder(styleBuilder) + .withLogo(showWatermark) + .withPixelRatio(dpi) + ) + + synchronized(this) { + this.currentSnapshotter?.cancel() + this.currentSnapshotter = snapshotter + } + + snapshotter.start { + + val cameraPositionChanged = cameraPosition != lastSnapshot?.cameraPosition || (cameraBounds != lastSnapshot?.cameraBounds) + + lastSnapshot = MetaSnapshot( + it, cameraPosition, cameraBounds, pixelWidth, pixelHeight, view.paddingRight, view.paddingTop, dpi + ) + map.setImageBitmap(it.bitmap) + + for (callback in afterNextDrawCallback) callback() + afterNextDrawCallback.clear() + + if (cameraPositionChanged) { + // Notify apps that new projection is now available + cameraChangeListener?.onCameraChange(cameraPosition) + } + + currentInfoWindow?.update() + + synchronized(this) { + this.currentSnapshotter = null + } + + } + } + + fun getMapAsync(callback: IOnMapReadyCallback) { + if (lastSnapshot == null) { + Log.d(TAG, "Invoking callback instantly, as a snapshot is ready") + callback.onMapReady(this) + } else { + Log.d(TAG, "Delay callback invocation, as snapshot has not been rendered yet") + afterNextDrawCallback.add { callback.onMapReady(this) } + } + } + + override fun getCameraPosition(): CameraPosition = cameraPosition + + override fun getMaxZoomLevel() = 21f + + override fun getMinZoomLevel() = 1f + + override fun moveCamera(cameraUpdate: IObjectWrapper?): Unit = cameraUpdate.unwrap()?.let { + cameraPosition = it.getLiteModeCameraPosition(this) ?: cameraPosition + cameraBounds = it.getLiteModeCameraBounds() + + postUpdateSnapshot() + } ?: Unit + + override fun animateCamera(cameraUpdate: IObjectWrapper?) = moveCamera(cameraUpdate) + + override fun animateCameraWithCallback(cameraUpdate: IObjectWrapper?, callback: ICancelableCallback?) { + moveCamera(cameraUpdate) + Log.d(TAG, "animateCameraWithCallback: animation not possible in lite mode, invoking callback instantly") + callback?.onFinish() + } + + override fun animateCameraWithDurationAndCallback( + cameraUpdate: IObjectWrapper?, duration: Int, callback: ICancelableCallback? + ) = animateCameraWithCallback(cameraUpdate, callback) + + override fun stopAnimation() { + Log.d(TAG, "stopAnimation: animation not possible in lite mode") + } + + override fun addPolyline(options: PolylineOptions): IPolylineDelegate { + return LitePolylineImpl(this, "polyline${nextObjectId++}", options).also { polylines.add(it) } + } + + override fun addPolygon(options: PolygonOptions): IPolygonDelegate { + return LitePolygonImpl( + "polygon${nextObjectId++}", options, this + ).also { + polygons.add(it) + polylines.addAll(it.strokes) + postUpdateSnapshot() + } + } + + override fun addMarker(options: MarkerOptions): IMarkerDelegate { + return LiteMarkerImpl("marker${nextObjectId++}", options, this).also { + markers.add(it) + postUpdateSnapshot() + } + } + + override fun addGroundOverlay(options: GroundOverlayOptions?): IGroundOverlayDelegate? { + Log.d(TAG, "addGroundOverlay: not supported in lite mode") + return null + } + + override fun addTileOverlay(options: TileOverlayOptions?): ITileOverlayDelegate? { + Log.d(TAG, "addTileOverlay: not supported in lite mode") + return null + } + + override fun clear() { + polylines.clear() + polygons.clear() + markers.clear() + circles.clear() + postUpdateSnapshot() + } + + override fun getMapType(): Int { + return mapType + } + + override fun setMapType(type: Int) { + mapType = type + postUpdateSnapshot() + } + + override fun isTrafficEnabled(): Boolean { + Log.d(TAG, "isTrafficEnabled: traffic not supported in lite mode") + return false + } + + override fun setTrafficEnabled(traffic: Boolean) { + Log.d(TAG, "setTrafficEnabled: traffic not supported in lite mode") + } + + override fun isIndoorEnabled(): Boolean { + Log.d(TAG, "isIndoorEnabled: indoor not supported in lite mode") + return false + } + + override fun setIndoorEnabled(indoor: Boolean) { + Log.d(TAG, "setIndoorEnabled: indoor not supported in lite mode") + } + + override fun isMyLocationEnabled(): Boolean = myLocationEnabled + + override fun setMyLocationEnabled(myLocation: Boolean) { + if (!myLocationEnabled && myLocation) { + activateLocationProvider() + } else if (myLocationEnabled && !myLocation) { + deactivateLocationProvider() + } // else situation is unchanged + myLocationEnabled = myLocation + } + + private fun activateLocationProvider() { + // Activate only if sufficient permissions + if (ActivityCompat.checkSelfPermission( + mapContext, Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission( + mapContext, Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + locationEngineProvider.requestLocationUpdates( + LocationEngineRequest.Builder(DEFAULT_INTERVAL_MILLIS) + .setFastestInterval(DEFAULT_FASTEST_INTERVAL_MILLIS) + .setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY) + .build(), locationListener, Looper.getMainLooper() + ) + + } else { + Log.w(TAG, "Called setMyLocationEnabled(true) without sufficient permissions. Not showing location.") + } + } + + private fun deactivateLocationProvider() { + locationEngineProvider.removeLocationUpdates(locationListener) + } + + override fun getUiSettings(): IUiSettingsDelegate { + Log.d(TAG, "UI settings have no effect") + return UiSettingsCache() + } + + /** + * Gets a projection snapshot. This means that, in accordance to the docs, the projection object + * will represent the map as it is seen at the point in time that the projection is queried, and + * not updated later on. + */ + override fun getProjection(): IProjectionDelegate = lastSnapshot?.let { LiteProjection(it) } ?: DummyProjection() + + override fun setOnCameraChangeListener(listener: IOnCameraChangeListener?) { + cameraChangeListener = listener + } + + override fun setOnMarkerDragListener(listener: IOnMarkerDragListener?) { + Log.d(TAG, "setOnMarkerDragListener: marker drag is not supported in lite mode") + } + + override fun addCircle(options: CircleOptions): ICircleDelegate { + return LiteCircleImpl(this, "circle${nextObjectId++}", options).also { circles.add(it) } + } + + override fun snapshot(callback: ISnapshotReadyCallback?, bitmap: IObjectWrapper?) { + val lastSnapshot = lastSnapshot + if (lastSnapshot == null) { + afterNextDrawCallback.add { + callback?.onBitmapWrappedReady(ObjectWrapper.wrap(this@LiteGoogleMapImpl.lastSnapshot!!.snapshot.bitmap)) + } + } else { + callback?.onBitmapWrappedReady(ObjectWrapper.wrap(lastSnapshot.snapshot.bitmap)) + } + } + + override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) { + view.setPadding(left, top, right, bottom) + postUpdateSnapshot() + } + + override fun isBuildingsEnabled(): Boolean { + Log.d(TAG, "isBuildingsEnabled: never enabled in light mode") + return false + } + + override fun setBuildingsEnabled(buildings: Boolean) { + Log.d(TAG, "setBuildingsEnabled: cannot be enabled in light mode") + } + + override fun setOnMapLoadedCallback(callback: IOnMapLoadedCallback?) = callback?.let { onMapLoadedCallback -> + if (lastSnapshot != null) { + Log.d(TAG, "Invoking map loaded callback instantly, as a snapshot is ready") + onMapLoadedCallback.onMapLoaded() + } + else { + Log.d(TAG, "Delaying map loaded callback, as snapshot has not been taken yet") + afterNextDrawCallback.add { onMapLoadedCallback.onMapLoaded() } + } + Unit + } ?: Unit + + override fun setWatermarkEnabled(watermark: Boolean) { + showWatermark = watermark + } + + override fun showInfoWindow(marker: AbstractMarker): Boolean { + infoWindowAdapter.getInfoWindowViewFor(marker, mapContext)?.let { infoView -> + currentInfoWindow?.close() + currentInfoWindow = InfoWindow(infoView, this, marker).also { infoWindow -> + infoWindow.open(view) + } + return true + } + return false + } + + override fun onResume() { + if (myLocationEnabled) activateLocationProvider() + } + + override fun onPause() { + synchronized(this) { + currentSnapshotter?.cancel() + currentSnapshotter = null + } + deactivateLocationProvider() + } + + override fun onDestroy() { + view.removeView(map) + } + + override fun onLowMemory() { + } + + override fun onSaveInstanceState(outState: Bundle) { + outState.putParcelable(BUNDLE_CAMERA_POSITION, cameraPosition) + outState.putParcelable(BUNDLE_CAMERA_BOUNDS, cameraBounds) + } + + override fun setContentDescription(desc: String?) { + view.contentDescription = desc + } + + override fun setMapStyle(options: MapStyleOptions?): Boolean { + Log.d(TAG, "setMapStyle options: " + options?.getJson()) + return true + } + + override fun setMinZoomPreference(minZoom: Float) { + Log.d(TAG, "setMinZoomPreference: no interactivity in lite mode") + } + + override fun setMaxZoomPreference(maxZoom: Float) { + Log.d(TAG, "setMaxZoomPreference: no interactivity in lite mode") + } + + override fun resetMinMaxZoomPreference() { + Log.d(TAG, "resetMinMaxZoomPreference: no interactivity in lite mode") + } + + override fun setLatLngBoundsForCameraTarget(bounds: LatLngBounds?) { + Log.d(TAG, "setLatLngBoundsForCameraTarget: no interactivity in lite mode") + } + + override fun setCameraMoveStartedListener(listener: IOnCameraMoveStartedListener?) { + Log.d(TAG, "setCameraMoveStartedListener: event not supported in lite mode") + } + + override fun setCameraMoveListener(listener: IOnCameraMoveListener?) { + Log.d(TAG, "setCameraMoveListener: event not supported in lite mode") + + } + + override fun setCameraMoveCanceledListener(listener: IOnCameraMoveCanceledListener?) { + Log.d(TAG, "setCameraMoveCanceledListener: event not supported in lite mode") + } + + override fun setCameraIdleListener(listener: IOnCameraIdleListener?) { + Log.d(TAG, "setCameraIdleListener: event not supported in lite mode") + } + + override fun onStart() { + } + + override fun onStop() { + } + + companion object { + private val TAG = "GmsMapLite" + private val BUNDLE_CAMERA_POSITION = "camera" + private val BUNDLE_CAMERA_BOUNDS = "cameraBounds" + } +} \ No newline at end of file diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/MapFragment.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/MapFragment.kt index daf4bfb92e5987a087bafcb020536d9af31aeefb..69135ab29904e67093951dfd4b33b4065456712b 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/MapFragment.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/MapFragment.kt @@ -32,12 +32,18 @@ import com.google.android.gms.maps.internal.IOnMapReadyCallback class MapFragmentImpl(private val activity: Activity) : IMapFragmentDelegate.Stub() { - private var map: GoogleMapImpl? = null + private var map: IGoogleMapDelegate? = null private var options: GoogleMapOptions? = null override fun onInflate(activity: IObjectWrapper, options: GoogleMapOptions, savedInstanceState: Bundle?) { this.options = options - map?.options = options + map?.apply { + if (this is GoogleMapImpl) { + this.options = options + } else if (this is LiteGoogleMapImpl) { + this.options = options + } + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -47,7 +53,11 @@ class MapFragmentImpl(private val activity: Activity) : IMapFragmentDelegate.Stu if (options == null) { options = GoogleMapOptions() } - map = GoogleMapImpl(activity, options ?: GoogleMapOptions()) + if (options?.liteMode == true) { + map = LiteGoogleMapImpl(activity, options ?: GoogleMapOptions()) + } else { + map = GoogleMapImpl(activity, options ?: GoogleMapOptions()) + } } override fun onCreateView(layoutInflater: IObjectWrapper, container: IObjectWrapper, savedInstanceState: Bundle?): IObjectWrapper { @@ -56,13 +66,25 @@ class MapFragmentImpl(private val activity: Activity) : IMapFragmentDelegate.Stu } Log.d(TAG, "onCreateView: ${options?.camera?.target}") if (map == null) { - map = GoogleMapImpl(activity, options ?: GoogleMapOptions()) + map = if (options?.liteMode == true) { + LiteGoogleMapImpl(activity, options ?: GoogleMapOptions()) + } else { + GoogleMapImpl(activity, options ?: GoogleMapOptions()) + } + } + map!!.apply { + onCreate(savedInstanceState) + + val view = when (this) { + is GoogleMapImpl -> this.view + is LiteGoogleMapImpl -> this.view + else -> null + } + + val parent = view?.parent as ViewGroup? + parent?.removeView(view) + return ObjectWrapper.wrap(view) } - map!!.onCreate(savedInstanceState) - val view = map!!.view - val parent = view.parent as ViewGroup? - parent?.removeView(view) - return ObjectWrapper.wrap(view) } override fun getMap(): IGoogleMapDelegate? = map @@ -74,7 +96,10 @@ class MapFragmentImpl(private val activity: Activity) : IMapFragmentDelegate.Stu override fun onPause() = map?.onPause() ?: Unit override fun onLowMemory() = map?.onLowMemory() ?: Unit override fun isReady(): Boolean = this.map != null - override fun getMapAsync(callback: IOnMapReadyCallback) = map?.getMapAsync(callback) ?: Unit + override fun getMapAsync(callback: IOnMapReadyCallback) = map?.let { + if (it is GoogleMapImpl) it.getMapAsync(callback) + else if (it is LiteGoogleMapImpl) it.getMapAsync(callback) + } ?: Unit override fun onDestroyView() { map?.onDestroy() diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/MapView.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/MapView.kt index bef3cd83c36914d5fbc245d6036e6fcf43b45015..4ce82b13e40ec82445210b3f084ee0ebf2eef6ca 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/MapView.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/MapView.kt @@ -30,12 +30,17 @@ import com.google.android.gms.maps.internal.IOnMapReadyCallback class MapViewImpl(private val context: Context, options: GoogleMapOptions?) : IMapViewDelegate.Stub() { private val options: GoogleMapOptions = options ?: GoogleMapOptions() - private var map: GoogleMapImpl? = null + private var map: IGoogleMapDelegate? = null override fun onCreate(savedInstanceState: Bundle?) { Log.d(TAG, "onCreate: ${options?.camera?.target}") - map = GoogleMapImpl(context, options) - map!!.onCreate(savedInstanceState) + map = if (options.liteMode) { + LiteGoogleMapImpl(context, options) + } else { + GoogleMapImpl(context, options) + }.apply { + this.onCreate(savedInstanceState) + } } override fun getMap(): IGoogleMapDelegate? = map @@ -53,8 +58,23 @@ class MapViewImpl(private val context: Context, options: GoogleMapOptions?) : IM override fun onLowMemory() = map?.onLowMemory() ?: Unit override fun onSaveInstanceState(outState: Bundle) = map?.onSaveInstanceState(outState) ?: Unit - override fun getView(): IObjectWrapper = ObjectWrapper.wrap(map?.view) - override fun getMapAsync(callback: IOnMapReadyCallback) = map?.getMapAsync(callback) ?: Unit + override fun getView(): IObjectWrapper = ObjectWrapper.wrap( + map?.let { + when (it) { + is GoogleMapImpl -> it.view + is LiteGoogleMapImpl -> it.view + else -> null + } + } + ) + + override fun getMapAsync(callback: IOnMapReadyCallback) = map?.let { + when (it) { + is GoogleMapImpl -> it.getMapAsync(callback) + is LiteGoogleMapImpl -> it.getMapAsync(callback) + else -> null + } + } ?: Unit override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = if (super.onTransact(code, data, reply, flags)) { diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/Projection.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/Projection.kt index d4ed42f9131d2409e22cea4b805617f3f06ebbfa..0077c3e88bf7fe1f057cdd07fcc9f202312305a7 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/Projection.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/Projection.kt @@ -26,6 +26,7 @@ import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.VisibleRegion import com.mapbox.mapboxsdk.maps.Projection import com.google.android.gms.dynamic.unwrap +import com.google.android.gms.maps.model.LatLngBounds import org.microg.gms.maps.mapbox.utils.toGms import org.microg.gms.maps.mapbox.utils.toMapbox import kotlin.math.roundToInt @@ -78,3 +79,48 @@ class ProjectionImpl(private val projection: Projection, private val withoutTilt private val TAG = "GmsMapProjection" } } + +class LiteProjection(private val snapshot: MetaSnapshot) : IProjectionDelegate.Stub() { + + private fun fromScreenLocationAfterPadding(point: Point?): LatLng = + point?.let { snapshot.latLngForPixelFixed(PointF(point)).toGms() } ?: LatLng(0.0, 0.0) + + override fun fromScreenLocation(obj: IObjectWrapper?): LatLng = fromScreenLocationAfterPadding(obj.unwrap()?.let { + Point((it.x - snapshot.paddingRight), (it.y - snapshot.paddingRight)) + }) + + override fun toScreenLocation(latLng: LatLng?): IObjectWrapper = + ObjectWrapper.wrap(snapshot.snapshot.pixelForLatLng(latLng?.toMapbox()).let { + Point(it.x.roundToInt() + snapshot.paddingRight, it.y.roundToInt() + snapshot.paddingTop) + }) + + override fun getVisibleRegion(): VisibleRegion { + val nearLeft = fromScreenLocationAfterPadding(Point(0, snapshot.height)) + val nearRight = fromScreenLocationAfterPadding(Point(snapshot.width, snapshot.height)) + val farLeft = fromScreenLocationAfterPadding(Point(0, 0)) + val farRight = fromScreenLocationAfterPadding(Point(snapshot.width, 0)) + + return VisibleRegion(nearLeft, nearRight, farLeft, farRight, LatLngBounds(nearLeft, farRight)) + } +} + +class DummyProjection : IProjectionDelegate.Stub() { + override fun fromScreenLocation(obj: IObjectWrapper?): LatLng { + Log.d(TAG, "Map not initialized when calling getProjection(). Cannot calculate fromScreenLocation") + return LatLng(0.0, 0.0) + } + + override fun toScreenLocation(latLng: LatLng?): IObjectWrapper { + Log.d(TAG, "Map not initialized when calling getProjection(). Cannot calculate toScreenLocation") + return ObjectWrapper.wrap(Point(0, 0)) + } + + override fun getVisibleRegion(): VisibleRegion { + Log.d(TAG, "Map not initialized when calling getProjection(). Cannot calculate getVisibleRegion") + return VisibleRegion(LatLngBounds(LatLng(0.0, 0.0), LatLng(0.0, 0.0))) + } + + companion object { + private val TAG = "GmsMapDummyProjection" + } +} \ No newline at end of file diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/BitmapDescriptor.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/BitmapDescriptor.kt index f0dc0f882a429daa883419703cbc2378a8317368..48b16f7544eaf96a9f0e6a37cd2985256adc93d7 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/BitmapDescriptor.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/BitmapDescriptor.kt @@ -22,6 +22,8 @@ import android.util.Log import com.mapbox.mapboxsdk.plugins.annotation.Symbol import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions import com.mapbox.mapboxsdk.style.layers.Property.ICON_ANCHOR_TOP_LEFT +import com.mapbox.mapboxsdk.style.layers.PropertyFactory +import com.mapbox.mapboxsdk.style.layers.SymbolLayer import com.mapbox.mapboxsdk.utils.ColorUtils open class BitmapDescriptorImpl(private val id: String, internal val size: FloatArray) { @@ -34,6 +36,14 @@ open class BitmapDescriptorImpl(private val id: String, internal val size: Float symbol.iconOffset = PointF(-anchor[0] * size[0] / dpiFactor, -anchor[1] * size[1] / dpiFactor) symbol.iconImage = id } + + open fun applyTo(symbolLayer: SymbolLayer, anchor: FloatArray, dpiFactor: Float) { + symbolLayer.withProperties( + PropertyFactory.iconAnchor(ICON_ANCHOR_TOP_LEFT), + PropertyFactory.iconOffset(arrayOf(-anchor[0] * size[0] / dpiFactor, -anchor[1] * size[1] / dpiFactor)), + PropertyFactory.iconImage(id) + ) + } } class ColorBitmapDescriptorImpl(id: String, size: FloatArray, val hue: Float) : BitmapDescriptorImpl(id, size) { diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/BitmapDescriptorFactory.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/BitmapDescriptorFactory.kt index f4f517481463caad7390d7fc44c394f7d9cdb6c1..465230041d49d713532d22b49db1546108d5e94a 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/BitmapDescriptorFactory.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/BitmapDescriptorFactory.kt @@ -18,14 +18,13 @@ package org.microg.gms.maps.mapbox.model import android.content.res.Resources import android.graphics.* -import android.os.Handler -import android.os.Looper import android.os.Parcel import android.util.Log import com.google.android.gms.dynamic.IObjectWrapper import com.google.android.gms.dynamic.ObjectWrapper import com.google.android.gms.maps.model.internal.IBitmapDescriptorFactoryDelegate import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.maps.Style import org.microg.gms.maps.mapbox.R import org.microg.gms.maps.mapbox.runOnMainLooper @@ -57,6 +56,14 @@ object BitmapDescriptorFactoryImpl : IBitmapDescriptorFactoryDelegate.Stub() { // TODO: cleanup bitmaps? } + fun put(style: Style.Builder) { + synchronized(bitmaps) { + for (bitmap in bitmaps) { + style.withImage(bitmap.key, bitmap.value) + } + } + } + fun bitmapSize(id: String): FloatArray = bitmaps[id]?.let { floatArrayOf(it.width.toFloat(), it.height.toFloat()) } ?: floatArrayOf(0f, 0f) diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Circle.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Circle.kt index d0aa04dab38b3d015a80f7c0f3235a0166fe53fb..bbb6aaab2b05f06b39bd36a16c2e30613f391a6f 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Circle.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Circle.kt @@ -28,10 +28,13 @@ import com.mapbox.geojson.Point import com.mapbox.mapboxsdk.plugins.annotation.* import com.mapbox.mapboxsdk.utils.ColorUtils import com.mapbox.turf.TurfConstants +import com.mapbox.turf.TurfConstants.UNIT_METERS import com.mapbox.turf.TurfMeasurement import com.mapbox.turf.TurfMeta import com.mapbox.turf.TurfTransformation import org.microg.gms.maps.mapbox.GoogleMapImpl +import org.microg.gms.maps.mapbox.LiteGoogleMapImpl +import org.microg.gms.maps.mapbox.utils.toPoint import com.google.android.gms.maps.model.CircleOptions as GmsCircleOptions val NORTH_POLE: Point = Point.fromLngLat(0.0, 90.0) @@ -42,15 +45,18 @@ val SOUTH_POLE: Point = Point.fromLngLat(0.0, -90.0) */ const val CIRCLE_POLYGON_STEPS = 256 -class CircleImpl(private val map: GoogleMapImpl, private val id: String, options: GmsCircleOptions) : ICircleDelegate.Stub(), Markup { - private var center: LatLng = options.center - private var radius: Double = options.radius // in meters - private var strokeWidth: Float = options.strokeWidth - private var strokeColor: Int = options.strokeColor - private var fillColor: Int = options.fillColor - private var visible: Boolean = options.isVisible - private var clickable: Boolean = options.isClickable - private var tag: Any? = null +abstract class AbstractCircle( + private val id: String, options: GmsCircleOptions, private val dpiFactor: Function0 +) : ICircleDelegate.Stub() { + + internal var center: LatLng = options.center + internal var radiusInMeters: Double = options.radius // unlike MapLibre's circles, which only work with pixel radii + internal var strokeWidth: Float = options.strokeWidth + internal var strokeColor: Int = options.strokeColor + internal var fillColor: Int = options.fillColor + internal var visible: Boolean = options.isVisible + internal var clickable: Boolean = options.isClickable + internal var tag: Any? = null internal val line: Markup = object : Markup { override var annotation: Line? = null @@ -60,16 +66,14 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options LineString.fromLngLats( makeOutlineLatLngs() ) - ).withLineWidth(strokeWidth / map.dpiFactor) + ).withLineWidth(strokeWidth / dpiFactor()) .withLineColor(ColorUtils.colorToRgbaString(strokeColor)) .withLineOpacity(if (visible) 1f else 0f) - override val removed: Boolean = false + override var removed: Boolean = false } - override var annotation: Fill? = null - override var removed: Boolean = false - override val annotationOptions: FillOptions + val annotationOptions: FillOptions get() = FillOptions() .withGeometry(makePolygon()) @@ -77,23 +81,25 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options .withFillOutlineColor(ColorUtils.colorToRgbaString(strokeColor)) .withFillOpacity(if (visible && !wrapsAroundPoles()) 1f else 0f) - private fun makePolygon() = TurfTransformation.circle( - Point.fromLngLat(center.longitude, center.latitude), radius, CIRCLE_POLYGON_STEPS, TurfConstants.UNIT_METERS + internal abstract fun update() + + internal fun makePolygon() = TurfTransformation.circle( + Point.fromLngLat(center.longitude, center.latitude), radiusInMeters, CIRCLE_POLYGON_STEPS, TurfConstants.UNIT_METERS ) /** - * Google's "map renderer is unable to draw the circle fill if - * the circle encompasses either the North or South pole". + * Google's "map renderer is unable to draw the circle fill if the circle encompasses + * either the North or South pole" (though it does so incorrectly anyway) */ - private fun wrapsAroundPoles() = Point.fromLngLat(center.longitude, center.latitude).let { + internal fun wrapsAroundPoles() = center.toPoint().let { TurfMeasurement.distance( - it, NORTH_POLE - ) * 1000 < radius || TurfMeasurement.distance( - it, SOUTH_POLE - ) * 1000 < radius + it, NORTH_POLE, UNIT_METERS + ) < radiusInMeters || TurfMeasurement.distance( + it, SOUTH_POLE, UNIT_METERS + ) < radiusInMeters } - private fun makeOutlineLatLngs(): MutableList { + internal fun makeOutlineLatLngs(): MutableList { val pointList = TurfMeta.coordAll( makePolygon(), wrapsAroundPoles() ) @@ -101,9 +107,9 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options // We modify our lines such to match the way Mapbox / MapLibre draws them. // This results in a small gap somewhere in the line, but avoids an incorrect horizontal line. - val centerPoint = Point.fromLngLat(center.longitude, center.latitude) + val centerPoint = center.toPoint() - if (!centerPoint.equals(NORTH_POLE) && TurfMeasurement.distance(centerPoint, NORTH_POLE) * 1000 < radius) { + if (!centerPoint.equals(NORTH_POLE) && TurfMeasurement.distance(centerPoint, NORTH_POLE, UNIT_METERS) < radiusInMeters) { // Wraps around North Pole for (i in 0 until pointList.size) { // We want to have the north-most points at the start and end @@ -117,7 +123,7 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options } } - if (!centerPoint.equals(SOUTH_POLE) && TurfMeasurement.distance(centerPoint, SOUTH_POLE) * 1000 < radius) { + if (!centerPoint.equals(SOUTH_POLE) && TurfMeasurement.distance(centerPoint, SOUTH_POLE, UNIT_METERS) < radiusInMeters) { // Wraps around South Pole for (i in 0 until pointList.size) { // We want to have the south-most points at the start and end @@ -135,67 +141,40 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options return pointList } - private fun updateLatLngs() { - val polygon = makePolygon() - - // Extracts points from generated polygon in expected format - annotation?.latLngs = FillOptions().withGeometry(polygon).latLngs - - line.annotation?.latLngs = makeOutlineLatLngs().map { point -> - com.mapbox.mapboxsdk.geometry.LatLng( - point.latitude(), - point.longitude() - ) - } - - if (wrapsAroundPoles()) { - annotation?.fillOpacity = 0f - } - } - - override fun remove() { - removed = true - map.fillManager?.let { update(it) } - } - override fun getId(): String = id override fun setCenter(center: LatLng) { this.center = center - updateLatLngs() - map.fillManager?.let { update(it) } + update() + } override fun getCenter(): LatLng = center override fun setRadius(radius: Double) { - this.radius = radius - updateLatLngs() - map.fillManager?.let { update(it) } + this.radiusInMeters = radius + update() } - override fun getRadius(): Double = radius + override fun getRadius(): Double = radiusInMeters override fun setStrokeWidth(width: Float) { this.strokeWidth = width - line.annotation?.lineWidth = width / map.dpiFactor - map.lineManager?.let { line.update(it) } + update() } override fun getStrokeWidth(): Float = strokeWidth override fun setStrokeColor(color: Int) { this.strokeColor = color - line.annotation?.setLineColor(color) - map.lineManager?.let { line.update(it) } + update() } override fun getStrokeColor(): Int = strokeColor override fun setFillColor(color: Int) { this.fillColor = color - annotation?.setFillColor(color) - map.fillManager?.let { update(it) } + update() } override fun getFillColor(): Int = fillColor @@ -211,8 +190,7 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options override fun setVisible(visible: Boolean) { this.visible = visible - annotation?.fillOpacity = if (visible && !wrapsAroundPoles()) 1f else 0f - map.fillManager?.let { update(it) } + update() } override fun isVisible(): Boolean = visible @@ -229,20 +207,6 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options return clickable } - override fun update(manager: AnnotationManager<*, Fill, FillOptions, *, *, *>) { - synchronized(this) { - val id = annotation?.id - if (removed && id != null) { - map.circles.remove(id) - } - super.update(manager) - val annotation = annotation - if (annotation != null && id == null) { - map.circles[annotation.id] = this - } - } - } - override fun setStrokePattern(pattern: IObjectWrapper?) { Log.d(TAG, "unimplemented method: set stroke pattern") } @@ -274,13 +238,84 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options } override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = - if (super.onTransact(code, data, reply, flags)) { - true - } else { - Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + val TAG = "GmsMapAbstractCircle" + } +} + +class CircleImpl(private val map: GoogleMapImpl, private val id: String, options: GmsCircleOptions) : + AbstractCircle(id, options, { map.dpiFactor }), Markup { + + override var annotation: Fill? = null + override var removed: Boolean = false + + override fun update() { + val polygon = makePolygon() + + // Extracts points from generated polygon in expected format + annotation?.let { + it.latLngs = FillOptions().withGeometry(polygon).latLngs + it.setFillColor(fillColor) + it.fillOpacity = if (visible && !wrapsAroundPoles()) 1f else 0f + } + + line.annotation?.let { + it.latLngs = makeOutlineLatLngs().map { point -> + com.mapbox.mapboxsdk.geometry.LatLng( + point.latitude(), + point.longitude() + ) } + it.lineWidth = strokeWidth / map.dpiFactor + it.setLineColor(strokeColor) + } + + map.fillManager?.let { update(it) } + map.lineManager?.let { line.update(it) } + } + + override fun remove() { + removed = true + line.removed = true + map.fillManager?.let { update(it) } + map.lineManager?.let { line.update(it) } + } + + + override fun update(manager: AnnotationManager<*, Fill, FillOptions, *, *, *>) { + synchronized(this) { + val id = annotation?.id + if (removed && id != null) { + map.circles.remove(id) + } + super.update(manager) + val annotation = annotation + if (annotation != null && id == null) { + map.circles[annotation.id] = this + } + } + } + companion object { val TAG = "GmsMapCircle" } +} + +class LiteCircleImpl(private val map: LiteGoogleMapImpl, id: String, options: GmsCircleOptions) : + AbstractCircle(id, options, { map.dpiFactor }) { + override fun update() { + map.postUpdateSnapshot() + } + + override fun remove() { + map.circles.remove(this) + } + } \ No newline at end of file diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/InfoWindow.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/InfoWindow.kt index c5f00abbda088698e336c6b9e1bffcf1deca56bf..91b41f117a1b334692820949cf1da72275fcb07f 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/InfoWindow.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/InfoWindow.kt @@ -1,5 +1,6 @@ package org.microg.gms.maps.mapbox.model +import android.graphics.Point import android.graphics.PointF import android.view.LayoutInflater import android.view.View @@ -13,7 +14,7 @@ import com.google.android.gms.dynamic.unwrap import com.google.android.gms.maps.internal.IInfoWindowAdapter import com.google.android.gms.maps.model.internal.IMarkerDelegate import com.mapbox.android.gestures.Utils -import com.mapbox.mapboxsdk.maps.MapView +import org.microg.gms.maps.mapbox.AbstractGoogleMap import org.microg.gms.maps.mapbox.GoogleMapImpl import org.microg.gms.maps.mapbox.R import org.microg.gms.maps.mapbox.utils.MapContext @@ -60,7 +61,7 @@ fun IInfoWindowAdapter.getInfoWindowViewFor(marker: IMarkerDelegate, mapContext: } class InfoWindow internal constructor( - private val view: View, private val map: GoogleMapImpl, internal val marker: MarkerImpl + private val view: View, private val map: AbstractGoogleMap, internal val marker: AbstractMarker ) { private var coordinates: PointF = PointF(0f, 0f) var isVisible = false @@ -75,7 +76,7 @@ class InfoWindow internal constructor( } } - fun open(mapView: MapView) { + fun open(mapView: FrameLayout) { val layoutParams: FrameLayout.LayoutParams = FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT ) @@ -108,8 +109,15 @@ class InfoWindow internal constructor( * Updates the position of the displayed view. */ fun update() { - map.map?.projection?.toScreenLocation(marker.position.toMapbox())?.let { - coordinates = it + + if (map is GoogleMapImpl) { + map.map?.projection?.toScreenLocation(marker.position.toMapbox())?.let { + coordinates = it + } + } else { + map.projection.toScreenLocation(marker.position)?.let { + coordinates = PointF(it.unwrap()!!) + } } val iconDimensions = marker.getIconDimensions() @@ -117,9 +125,9 @@ class InfoWindow internal constructor( val height = iconDimensions?.get(1) ?: 0f view.x = - coordinates.x - view.measuredWidth / 2f + sin(Math.toRadians(marker.rotation.toDouble())).toFloat() * width * marker.getInfoWindowAnchor()[0] + coordinates.x - view.measuredWidth / 2f + sin(Math.toRadians(marker.rotation.toDouble())).toFloat() * width * marker.infoWindowAnchor[0] view.y = coordinates.y - view.measuredHeight - max( - height * cos(Math.toRadians(marker.rotation.toDouble())).toFloat() * marker.getInfoWindowAnchor()[1], 0f + height * cos(Math.toRadians(marker.rotation.toDouble())).toFloat() * marker.infoWindowAnchor[1], 0f ) } } diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Marker.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Marker.kt index 0d11c4c122c41f42a3d7e462aabfe53efe0f5b8e..a1423b6cae6791c9d5c9901e178f49c6a3fe5abd 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Marker.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Marker.kt @@ -27,45 +27,167 @@ import com.mapbox.mapboxsdk.plugins.annotation.AnnotationManager import com.mapbox.mapboxsdk.plugins.annotation.Symbol import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions import com.google.android.gms.dynamic.unwrap +import org.microg.gms.maps.mapbox.AbstractGoogleMap import org.microg.gms.maps.mapbox.GoogleMapImpl +import org.microg.gms.maps.mapbox.LiteGoogleMapImpl import org.microg.gms.maps.mapbox.utils.toMapbox -class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options: MarkerOptions) : IMarkerDelegate.Stub(), Markup { - private var position: LatLng = options.position - private var visible: Boolean = options.isVisible - private var rotation: Float = options.rotation - private var anchor: FloatArray = floatArrayOf(options.anchorU, options.anchorV) - private var infoWindowAnchor: FloatArray? = null - private var icon: BitmapDescriptorImpl? = options.icon?.remoteObject.unwrap() - private var alpha: Float = options.alpha - private var title: String? = options.title - private var snippet: String? = options.snippet - private var zIndex: Float = options.zIndex - private var draggable: Boolean = options.isDraggable - private var tag: IObjectWrapper? = null - - private var infoWindowShown = false - - override var annotation: Symbol? = null - override var removed: Boolean = false - override val annotationOptions: SymbolOptions +abstract class AbstractMarker( + private val id: String, options: MarkerOptions, private val map: AbstractGoogleMap +) : IMarkerDelegate.Stub() { + + internal var position: LatLng = options.position + internal var visible: Boolean = options.isVisible + internal var anchor: FloatArray = floatArrayOf(options.anchorU, options.anchorV) + internal var infoWindowAnchor: FloatArray = floatArrayOf(0.5f, 1f) + internal var icon: BitmapDescriptorImpl? = options.icon?.remoteObject.unwrap() + internal var alpha: Float = options.alpha + internal var title: String? = options.title + internal var snippet: String? = options.snippet + internal var zIndex: Float = options.zIndex + internal var tag: IObjectWrapper? = null + internal open var draggable = false + + val annotationOptions: SymbolOptions get() { val symbolOptions = SymbolOptions() - .withIconOpacity(if (visible) alpha else 0f) - .withIconRotate(rotation) - .withSymbolSortKey(zIndex) - .withDraggable(draggable) + .withIconOpacity(if (visible) alpha else 0f) + .withIconRotate(rotation) + .withSymbolSortKey(zIndex) + .withDraggable(draggable) position.let { symbolOptions.withLatLng(it.toMapbox()) } icon?.applyTo(symbolOptions, anchor, map.dpiFactor) return symbolOptions } + internal abstract fun update() + + override fun getId(): String = id + + override fun setPosition(position: LatLng?) { + this.position = position ?: return + update() + } + + override fun getPosition(): LatLng = position + + override fun setIcon(obj: IObjectWrapper?) { + obj.unwrap()?.let { icon -> + this.icon = icon + update() + } + } + + override fun setVisible(visible: Boolean) { + this.visible = visible + update() + } + + override fun setTitle(title: String?) { + this.title = title + update() + } + + override fun getTitle(): String? = title + + override fun getSnippet(): String? = snippet + + override fun isVisible(): Boolean = visible + + override fun setAnchor(x: Float, y: Float) { + anchor = floatArrayOf(x, y) + update() + } + + override fun setAlpha(alpha: Float) { + this.alpha = alpha + update() + } + + override fun getAlpha(): Float = alpha + + override fun setZIndex(zIndex: Float) { + this.zIndex = zIndex + update() + } + + override fun getZIndex(): Float = zIndex + + fun getIconDimensions(): FloatArray? { + return icon?.size + } + + override fun showInfoWindow() { + if (isInfoWindowShown) { + // Per docs, don't call `onWindowClose` if info window is re-opened programmatically + map.currentInfoWindow?.close(silent = true) + } + map.showInfoWindow(this) + } + + override fun hideInfoWindow() { + if (isInfoWindowShown) { + map.currentInfoWindow?.close() + map.currentInfoWindow = null + } + } + + override fun isInfoWindowShown(): Boolean { + return map.currentInfoWindow?.marker == this + } + + override fun setTag(obj: IObjectWrapper?) { + this.tag = obj + } + + override fun getTag(): IObjectWrapper? = tag ?: ObjectWrapper.wrap(null) + + override fun setSnippet(snippet: String?) { + this.snippet = snippet + } + + override fun equalsRemote(other: IMarkerDelegate?): Boolean = equals(other) + + override fun hashCodeRemote(): Int = hashCode() + + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + private val TAG = "GmsMapAbstractMarker" + } +} + +class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options: MarkerOptions) : + AbstractMarker(id, options, map), Markup { + + internal var rotation: Float = options.rotation + override var draggable: Boolean = options.isDraggable + + override var annotation: Symbol? = null + override var removed: Boolean = false + override fun remove() { removed = true map.symbolManager?.let { update(it) } } + override fun update() { + annotation?.let { + it.latLng = position.toMapbox() + it.isDraggable = draggable + it.iconOpacity = if (visible) alpha else 0f + it.symbolSortKey = zIndex + icon?.applyTo(it, anchor, map.dpiFactor) + } + map.symbolManager?.let { update(it) } + } + override fun update(manager: AnnotationManager<*, Symbol, SymbolOptions, *, *, *>) { synchronized(this) { val id = annotation?.id @@ -80,12 +202,8 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options } } - override fun getId(): String = id - override fun setPosition(position: LatLng?) { - this.position = position ?: return - annotation?.latLng = position.toMapbox() - map.symbolManager?.let { update(it) } + super.setPosition(position) map.currentInfoWindow?.update() } @@ -98,69 +216,33 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options map.currentInfoWindow?.update() } - override fun getPosition(): LatLng = position - override fun setTitle(title: String?) { - this.title = title + super.setTitle(title) map.currentInfoWindow?.let { if (it.marker == this) it.close() } } - override fun getTitle(): String? = title - override fun setSnippet(snippet: String?) { - this.snippet = snippet + super.setSnippet(snippet) map.currentInfoWindow?.let { if (it.marker == this) it.close() } } - override fun getSnippet(): String? = snippet - override fun setDraggable(draggable: Boolean) { this.draggable = draggable - annotation?.isDraggable = draggable map.symbolManager?.let { update(it) } } override fun isDraggable(): Boolean = draggable - override fun showInfoWindow() { - if (isInfoWindowShown) { - // Per docs, don't call `onWindowClose` if info window is re-opened programmatically - map.currentInfoWindow?.close(silent = true) - } - map.showInfoWindow(this) - } - - override fun hideInfoWindow() { - if (isInfoWindowShown) { - map.currentInfoWindow?.close() - map.currentInfoWindow = null - } - } - - override fun isInfoWindowShown(): Boolean { - return map.currentInfoWindow?.marker == this - } - - override fun setVisible(visible: Boolean) { - this.visible = visible - annotation?.iconOpacity = if (visible) alpha else 0f - map.symbolManager?.let { update(it) } - } - - override fun isVisible(): Boolean = visible - override fun equals(other: Any?): Boolean { if (this === other) return true if (other is IMarkerDelegate) return other.id == id return false } - override fun equalsRemote(other: IMarkerDelegate?): Boolean = equals(other) - override fun hashCode(): Int { return id.hashCode() } @@ -169,23 +251,9 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options return "$id ($title)" } - override fun hashCodeRemote(): Int = hashCode() - - override fun setIcon(obj: IObjectWrapper?) { - obj.unwrap()?.let { icon -> - this.icon = icon - annotation?.let { icon.applyTo(it, anchor, map.dpiFactor) } - } - map.symbolManager?.let { update(it) } - } - override fun setAnchor(x: Float, y: Float) { - anchor = floatArrayOf(x, y) - annotation?.let { icon?.applyTo(it, anchor, map.dpiFactor) } - map.symbolManager?.let { update(it) } - if (infoWindowAnchor == null) { - map.currentInfoWindow?.update() - } + super.setAnchor(x, y) + map.currentInfoWindow?.update() } override fun setFlat(flat: Boolean) { @@ -211,42 +279,55 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options map.currentInfoWindow?.update() } - internal fun getInfoWindowAnchor() = infoWindowAnchor ?: floatArrayOf(0.5f, 1f) + companion object { + private val TAG = "GmsMapMarker" + } +} - override fun setAlpha(alpha: Float) { - this.alpha = alpha - annotation?.iconOpacity = if (visible) alpha else 0f - map.symbolManager?.let { update(it) } +class LiteMarkerImpl(id: String, options: MarkerOptions, private val map: LiteGoogleMapImpl) : + AbstractMarker(id, options, map) { + override fun remove() { + map.markers.remove(this) + map.postUpdateSnapshot() } - override fun getAlpha(): Float = alpha + override fun update() { + map.postUpdateSnapshot() + } - override fun setZIndex(zIndex: Float) { - this.zIndex = zIndex - annotation?.symbolSortKey = zIndex - map.symbolManager?.let { update(it) } + override fun setDraggable(drag: Boolean) { + Log.d(TAG, "setDraggable: not available in lite mode") } - override fun getZIndex(): Float = zIndex + override fun isDraggable(): Boolean { + Log.d(TAG, "isDraggable: markers are never draggable in lite mode") + return false + } - override fun setTag(obj: IObjectWrapper?) { - this.tag = obj + override fun setFlat(flat: Boolean) { + Log.d(TAG, "setFlat: not available in lite mode") } - override fun getTag(): IObjectWrapper = tag ?: ObjectWrapper.wrap(null) + override fun isFlat(): Boolean { + Log.d(TAG, "isFlat: markers in lite mode can never be flat") + return false + } - override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = - if (super.onTransact(code, data, reply, flags)) { - true - } else { - Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false - } + override fun setRotation(rotation: Float) { + Log.d(TAG, "setRotation: not available in lite mode") + } - fun getIconDimensions(): FloatArray? { - return icon?.size + override fun getRotation(): Float { + Log.d(TAG, "setRotation: markers in lite mode can never be rotated") + return 0f + } + + override fun setInfoWindowAnchor(x: Float, y: Float) { + infoWindowAnchor = floatArrayOf(x, y) + map.currentInfoWindow?.update() } companion object { - private val TAG = "GmsMapMarker" + private val TAG = "GmsMapMarkerLite" } -} +} \ No newline at end of file diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Markup.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Markup.kt index c737afccd4af5be9ddaf052d55386d300ddc6eb1..179c5a0b07e2b228b79f2f938c05bb3c4c02bd81 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Markup.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Markup.kt @@ -24,7 +24,7 @@ import com.mapbox.mapboxsdk.plugins.annotation.Options interface Markup, S : Options> { var annotation: T? val annotationOptions: S - val removed: Boolean + var removed: Boolean fun update(manager: AnnotationManager<*, T, S, *, *, *>) { synchronized(this) { diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polygon.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polygon.kt index c0de5468eeb814e2a83afd5bb8910236c50b1140..f6f700538f12f3b9a9bb7d670e3fc6b5cd0ff0dc 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polygon.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polygon.kt @@ -7,68 +7,63 @@ package org.microg.gms.maps.mapbox.model import android.os.Parcel import android.util.Log +import androidx.annotation.CallSuper import com.google.android.gms.dynamic.IObjectWrapper +import com.google.android.gms.dynamic.ObjectWrapper import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.PatternItem import com.google.android.gms.maps.model.PolygonOptions import com.google.android.gms.maps.model.PolylineOptions import com.google.android.gms.maps.model.internal.IPolygonDelegate -import com.mapbox.mapboxsdk.plugins.annotation.AnnotationManager import com.mapbox.mapboxsdk.plugins.annotation.Fill import com.mapbox.mapboxsdk.plugins.annotation.FillOptions import com.mapbox.mapboxsdk.utils.ColorUtils import org.microg.gms.maps.mapbox.GoogleMapImpl +import org.microg.gms.maps.mapbox.LiteGoogleMapImpl import org.microg.gms.maps.mapbox.utils.toMapbox import org.microg.gms.utils.warnOnTransactionIssues -class PolygonImpl(private val map: GoogleMapImpl, private val id: String, options: PolygonOptions) : IPolygonDelegate.Stub(), Markup { - private var points = ArrayList(options.points.orEmpty()) - private var holes: List> = ArrayList(options.holes.map { ArrayList(it.orEmpty()) }) - private var fillColor = options.fillColor - private var strokeColor = options.strokeColor - private var strokeWidth = options.strokeWidth - private var strokeJointType = options.strokeJointType - private var strokePattern = ArrayList(options.strokePattern.orEmpty()) - private var visible: Boolean = options.isVisible - private var clickable: Boolean = options.isClickable - private var tag: IObjectWrapper? = null - - private var strokes = (listOf(PolylineImpl(map, "$id-stroke-main", PolylineOptions().color(strokeColor).width(strokeWidth).addAll(points))) - + holes.mapIndexed { idx, it -> PolylineImpl(map, "$id-stroke-hole-$idx", PolylineOptions().color(strokeColor).width(strokeWidth).addAll(it)) }).toMutableList() - - override var annotation: Fill? = null - override var removed: Boolean = false - override val annotationOptions: FillOptions +abstract class AbstractPolygon(private val id: String, options: PolygonOptions) : IPolygonDelegate.Stub() { + internal var points = ArrayList(options.points.orEmpty()) + internal var holes: List> = ArrayList(options.holes.map { ArrayList(it.orEmpty()) }) + internal var fillColor = options.fillColor + internal var strokeColor = options.strokeColor + internal var strokeWidth = options.strokeWidth + internal var strokeJointType = options.strokeJointType + internal var strokePattern = ArrayList(options.strokePattern.orEmpty()) + internal var visible: Boolean = options.isVisible + internal var clickable: Boolean = options.isClickable + internal var tag: IObjectWrapper? = null + + val annotationOptions: FillOptions get() = FillOptions() - .withLatLngs(mutableListOf(points.map { it.toMapbox() }).plus(holes.map { it.map { it.toMapbox() } })) - .withFillColor(ColorUtils.colorToRgbaString(fillColor)) - .withFillOpacity(if (visible) 1f else 0f) + .withLatLngs(mutableListOf(points.map { it.toMapbox() }).plus(holes.map { it.map { it.toMapbox() } })) + .withFillColor(ColorUtils.colorToRgbaString(fillColor)) + .withFillOpacity(if (visible) 1f else 0f) - override fun remove() { - removed = true - map.fillManager?.let { update(it) } - strokes.forEach { it.remove() } - } + internal abstract val strokes: MutableList + + internal abstract fun update() - override fun update(manager: AnnotationManager<*, Fill, FillOptions, *, *, *>) { - super.update(manager) - map.lineManager?.let { lineManager -> strokes.forEach { it.update(lineManager) } } + @CallSuper + override fun remove() { + for (stroke in strokes) stroke.remove() } override fun getId(): String = id override fun setPoints(points: List) { this.points = ArrayList(points) - annotation?.latLngs = mutableListOf(points.map { it.toMapbox() }).plus(holes.map { it.map { it.toMapbox() } }) - map.fillManager?.let { update(it) } - strokes[0].points = points + strokes[0].setPoints(points) + update() } override fun getPoints(): List = points + internal abstract fun addPolyline(id: String, options: PolylineOptions) + override fun setHoles(holes: List?) { this.holes = if (holes == null) emptyList() else ArrayList(holes.mapNotNull { if (it is List<*>) it.mapNotNull { if (it is LatLng) it else null }.let { if (it.isNotEmpty()) it else null } else null }) - annotation?.latLngs = mutableListOf(points.map { it.toMapbox() }).plus(this.holes.map { it.map { it.toMapbox() } }) while (strokes.size > this.holes.size + 1) { val last = strokes.last() last.remove() @@ -77,34 +72,40 @@ class PolygonImpl(private val map: GoogleMapImpl, private val id: String, option strokes.forEachIndexed { idx, it -> if (idx > 0) it.points = this.holes[idx - 1] } if (this.holes.size + 1 > strokes.size) { try { - strokes.addAll(this.holes.subList(strokes.size, this.holes.size - 1).mapIndexed { idx, it -> PolylineImpl(map, "$id-stroke-hole-${strokes.size + idx}", PolylineOptions().color(strokeColor).width(strokeWidth).addAll(it)) }) + this.holes.subList(strokes.size, this.holes.size - 1).mapIndexed { idx, it -> + addPolyline( + "$id-stroke-hole-${strokes.size + idx}", + PolylineOptions().color(strokeColor).width(strokeWidth).addAll(it) + ) + } } catch (e: Exception) { Log.w(TAG, e) } } - map.fillManager?.let { update(it) } } - override fun getHoles(): List = holes + override fun getHoles(): List> = holes + override fun setStrokeWidth(width: Float) { - this.strokeWidth = width - strokes.forEach { it.width = width } + strokeWidth = width + strokes.forEach { it.setWidth(width) } + update() } override fun getStrokeWidth(): Float = strokeWidth override fun setStrokeColor(color: Int) { - this.strokeColor = color - strokes.forEach { it.color = color } + strokeColor = color + strokes.forEach { it.setColor(color) } + update() } override fun getStrokeColor(): Int = strokeColor override fun setFillColor(color: Int) { - this.fillColor = color - annotation?.setFillColor(color) - map.fillManager?.let { update(it) } + fillColor = color + update() } override fun getFillColor(): Int = fillColor @@ -119,9 +120,8 @@ class PolygonImpl(private val map: GoogleMapImpl, private val id: String, option } override fun setVisible(visible: Boolean) { - this.visible = visible - annotation?.fillOpacity = if (visible) 1f else 0f - map.fillManager?.let { update(it) } + isVisible = visible + update() } override fun isVisible(): Boolean = visible @@ -135,22 +135,20 @@ class PolygonImpl(private val map: GoogleMapImpl, private val id: String, option return false } - override fun equalsRemote(other: IPolygonDelegate?): Boolean = equals(other) - - override fun hashCodeRemote(): Int = hashCode() - override fun setClickable(click: Boolean) { clickable = click } override fun setStrokeJointType(type: Int) { strokeJointType = type + update() } override fun getStrokeJointType(): Int = strokeJointType override fun setStrokePattern(items: MutableList?) { strokePattern = ArrayList(items.orEmpty()) + update() } override fun getStrokePattern(): MutableList = strokePattern @@ -159,21 +157,67 @@ class PolygonImpl(private val map: GoogleMapImpl, private val id: String, option tag = obj } - override fun getTag(): IObjectWrapper? = tag + override fun getTag(): IObjectWrapper = tag ?: ObjectWrapper.wrap(null) + + override fun equalsRemote(other: IPolygonDelegate?): Boolean = equals(other) + + override fun hashCodeRemote(): Int = hashCode() override fun hashCode(): Int { return id.hashCode() } + override fun equals(other: Any?): Boolean { + if (other is AbstractPolygon) { + return other.id == id + } + return false + } + override fun toString(): String { return id } - override fun equals(other: Any?): Boolean { - if (other is PolygonImpl) { - return other.id == id + companion object { + private val TAG = "GmsMapAbstractPolygon" + } +} + +class PolygonImpl(private val map: GoogleMapImpl, id: String, options: PolygonOptions) : + AbstractPolygon(id, options), Markup { + + + override val strokes = (listOf( + PolylineImpl( + map, "$id-stroke-main", PolylineOptions().color(strokeColor).width(strokeWidth).addAll(points) + ) + ) + holes.mapIndexed { idx, it -> + PolylineImpl( + map, "$id-stroke-hole-$idx", PolylineOptions().color(strokeColor).width(strokeWidth).addAll(it) + ) + }).toMutableList() + + override var annotation: Fill? = null + override var removed: Boolean = false + + override fun remove() { + removed = true + map.fillManager?.let { update(it) } + super.remove() + } + + override fun update() { + annotation?.let { + it.latLngs = mutableListOf(points.map { it.toMapbox() }).plus(holes.map { it.map { it.toMapbox() } }) + it.setFillColor(fillColor) + it.fillOpacity = if (visible) 1f else 0f + it.latLngs = mutableListOf(points.map { it.toMapbox() }).plus(this.holes.map { it.map { it.toMapbox() } }) } - return false + map.fillManager?.let { update(it) } + } + + override fun addPolyline(id: String, options: PolylineOptions) { + strokes.add(PolylineImpl(map, id, options)) } override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = warnOnTransactionIssues(code, reply, flags) { super.onTransact(code, data, reply, flags) } @@ -182,3 +226,31 @@ class PolygonImpl(private val map: GoogleMapImpl, private val id: String, option private val TAG = "GmsMapPolygon" } } + +class LitePolygonImpl(id: String, options: PolygonOptions, private val map: LiteGoogleMapImpl) : AbstractPolygon(id, options) { + + override val strokes: MutableList = (listOf( + LitePolylineImpl( + map, "$id-stroke-main", PolylineOptions().color(strokeColor).width(strokeWidth).addAll(points) + ) + ) + holes.mapIndexed { idx, it -> + LitePolylineImpl( + map, "$id-stroke-hole-$idx", PolylineOptions().color(strokeColor).width(strokeWidth).addAll(it) + ) + }).toMutableList() + + + override fun remove() { + super.remove() + map.polygons.remove(this) + map.postUpdateSnapshot() + } + + override fun update() { + map.postUpdateSnapshot() + } + + override fun addPolyline(id: String, options: PolylineOptions) { + strokes.add(LitePolylineImpl(map, id, options)) + } +} \ No newline at end of file diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polyline.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polyline.kt index f9a8f91b904cbf1e665e9c52b25adb54b844b440..2e16ddb165d00429179e02d8700113be5cb9df58 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polyline.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Polyline.kt @@ -24,51 +24,44 @@ import com.mapbox.mapboxsdk.plugins.annotation.Line import com.mapbox.mapboxsdk.plugins.annotation.LineOptions import com.mapbox.mapboxsdk.utils.ColorUtils import org.microg.gms.maps.mapbox.GoogleMapImpl +import org.microg.gms.maps.mapbox.LiteGoogleMapImpl import org.microg.gms.maps.mapbox.utils.toMapbox import com.google.android.gms.maps.model.PolylineOptions as GmsLineOptions -class PolylineImpl(private val map: GoogleMapImpl, private val id: String, options: GmsLineOptions) : IPolylineDelegate.Stub(), Markup { - private var points = ArrayList(options.points) - private var width = options.width - private var color = options.color - private var visible: Boolean = options.isVisible +abstract class AbstractPolylineImpl(private val id: String, options: GmsLineOptions, private val dpiFactor: Function0) : IPolylineDelegate.Stub() { + internal var points: List = ArrayList(options.points) + internal var width = options.width + internal var color = options.color + internal var visible: Boolean = options.isVisible - override var annotation: Line? = null - override var removed: Boolean = false - override val annotationOptions: LineOptions + val annotationOptions: LineOptions get() = LineOptions() - .withLatLngs(points.map { it.toMapbox() }) - .withLineWidth(width / map.dpiFactor) - .withLineColor(ColorUtils.colorToRgbaString(color)) - .withLineOpacity(if (visible) 1f else 0f) + .withLatLngs(points.map { it.toMapbox() }) + .withLineWidth(width / dpiFactor.invoke()) + .withLineColor(ColorUtils.colorToRgbaString(color)) + .withLineOpacity(if (visible) 1f else 0f) - override fun remove() { - removed = true - map.lineManager?.let { update(it) } - } + internal abstract fun update() override fun getId(): String = id override fun setPoints(points: List) { this.points = ArrayList(points) - annotation?.latLngs = points.map { it.toMapbox() } - map.lineManager?.let { update(it) } + update() } override fun getPoints(): List = points override fun setWidth(width: Float) { this.width = width - annotation?.lineWidth = width / map.dpiFactor - map.lineManager?.let { update(it) } + update() } override fun getWidth(): Float = width override fun setColor(color: Int) { this.color = color - annotation?.setLineColor(color) - map.lineManager?.let { update(it) } + update() } override fun getColor(): Int = color @@ -84,8 +77,7 @@ class PolylineImpl(private val map: GoogleMapImpl, private val id: String, optio override fun setVisible(visible: Boolean) { this.visible = visible - annotation?.lineOpacity = if (visible) 1f else 0f - map.lineManager?.let { update(it) } + update() } override fun isVisible(): Boolean = visible @@ -107,25 +99,63 @@ class PolylineImpl(private val map: GoogleMapImpl, private val id: String, optio return id.hashCode() } - override fun toString(): String { - return id - } - override fun equals(other: Any?): Boolean { - if (other is PolylineImpl) { + if (other is AbstractPolylineImpl) { return other.id == id } return false } + override fun toString(): String { + return id + } + override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean = - if (super.onTransact(code, data, reply, flags)) { - true - } else { - Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false - } + if (super.onTransact(code, data, reply, flags)) { + true + } else { + Log.d(TAG, "onTransact [unknown]: $code, $data, $flags"); false + } + + companion object { + const val TAG = "GmsPolylineAbstract" + } +} + +class PolylineImpl(private val map: GoogleMapImpl, id: String, options: GmsLineOptions) : + AbstractPolylineImpl(id, options, { map.dpiFactor }), Markup { + + override var annotation: Line? = null + override var removed: Boolean = false + + override fun remove() { + removed = true + map.lineManager?.let { update(it) } + } + + override fun update() { + annotation?.apply { + latLngs = points.map { it.toMapbox() } + lineWidth = width / map.dpiFactor + setLineColor(color) + lineOpacity = if (visible) 1f else 0f + } + map.lineManager?.let { update(it) } + } companion object { private val TAG = "GmsMapPolyline" } -} \ No newline at end of file +} + +class LitePolylineImpl(private val map: LiteGoogleMapImpl, id: String, options: GmsLineOptions) : + AbstractPolylineImpl(id, options, { map.dpiFactor }) { + override fun remove() { + map.polylines.remove(this) + map.postUpdateSnapshot() + } + + override fun update() { + map.postUpdateSnapshot() + } +} diff --git a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/utils/typeConverter.kt b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/utils/typeConverter.kt index 9664aea6f0556ca283aee329d7c358ac2298ed1c..aefa0d1cbac5090a95d793a4661b487fd872ca71 100644 --- a/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/utils/typeConverter.kt +++ b/play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/utils/typeConverter.kt @@ -18,6 +18,7 @@ package org.microg.gms.maps.mapbox.utils import android.os.Bundle import com.google.android.gms.maps.internal.ICancelableCallback +import com.mapbox.geojson.Point import com.mapbox.mapboxsdk.camera.CameraPosition import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.geometry.LatLngBounds @@ -31,6 +32,8 @@ import com.google.android.gms.maps.model.VisibleRegion as GmsVisibleRegion fun GmsLatLng.toMapbox(): LatLng = LatLng(latitude, longitude) +fun GmsLatLng.toPoint() = Point.fromLngLat(latitude, longitude) + fun GmsLatLngBounds.toMapbox(): LatLngBounds = LatLngBounds.from(this.northeast.latitude, this.northeast.longitude, this.southwest.latitude, this.southwest.longitude) @@ -68,6 +71,8 @@ fun Bundle.toMapbox(): Bundle { fun LatLng.toGms(): GmsLatLng = GmsLatLng(latitude, longitude) +fun LatLng.toPoint(): Point = Point.fromLngLat(latitude, longitude) + fun LatLngBounds.toGms(): GmsLatLngBounds = GmsLatLngBounds(southWest.toGms(), northEast.toGms()) fun CameraPosition.toGms(): GmsCameraPosition = diff --git a/play-services-maps-core-mapbox/src/main/res/drawable/location_dot.xml b/play-services-maps-core-mapbox/src/main/res/drawable/location_dot.xml new file mode 100644 index 0000000000000000000000000000000000000000..af953b62bb55533bdf85354f4b431b2cdafa79f2 --- /dev/null +++ b/play-services-maps-core-mapbox/src/main/res/drawable/location_dot.xml @@ -0,0 +1,5 @@ + + + +