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 e542fb6a53..b4bdf00855 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 @@ -20,49 +20,101 @@ import android.annotation.SuppressLint import android.content.Context import android.graphics.Bitmap import android.location.Location -import android.os.* -import androidx.annotation.IdRes -import androidx.annotation.Keep +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.Parcel import android.util.Log import android.view.Gravity import android.view.View import android.widget.FrameLayout import android.widget.RelativeLayout +import androidx.annotation.IdRes +import androidx.annotation.Keep import androidx.collection.LongSparseArray 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.GoogleMap 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.internal.ICancelableCallback +import com.google.android.gms.maps.internal.ILocationSourceDelegate +import com.google.android.gms.maps.internal.IOnCameraChangeListener +import com.google.android.gms.maps.internal.IOnCameraIdleListener +import com.google.android.gms.maps.internal.IOnCameraMoveCanceledListener +import com.google.android.gms.maps.internal.IOnCameraMoveListener +import com.google.android.gms.maps.internal.IOnCameraMoveStartedListener +import com.google.android.gms.maps.internal.IOnMapLoadedCallback +import com.google.android.gms.maps.internal.IOnMapReadyCallback +import com.google.android.gms.maps.internal.IOnMarkerDragListener +import com.google.android.gms.maps.internal.IProjectionDelegate +import com.google.android.gms.maps.internal.ISnapshotReadyCallback +import com.google.android.gms.maps.internal.IUiSettingsDelegate +import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.CircleOptions -import com.google.android.gms.maps.model.internal.* +import com.google.android.gms.maps.model.GroundOverlayOptions +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.MapStyleOptions +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.PolygonOptions +import com.google.android.gms.maps.model.PolylineOptions +import com.google.android.gms.maps.model.TileOverlayOptions +import com.google.android.gms.maps.model.internal.ICircleDelegate +import com.google.android.gms.maps.model.internal.IGroundOverlayDelegate +import com.google.android.gms.maps.model.internal.IMarkerDelegate +import com.google.android.gms.maps.model.internal.IPolygonDelegate +import com.google.android.gms.maps.model.internal.IPolylineDelegate +import com.google.android.gms.maps.model.internal.ITileOverlayDelegate import com.mapbox.mapboxsdk.LibraryLoader import com.mapbox.mapboxsdk.Mapbox import com.mapbox.mapboxsdk.R +import com.mapbox.mapboxsdk.WellKnownTileServer import com.mapbox.mapboxsdk.camera.CameraUpdate +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory import com.mapbox.mapboxsdk.constants.MapboxConstants import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions import com.mapbox.mapboxsdk.location.LocationComponentOptions +import com.mapbox.mapboxsdk.location.engine.LocationEngine import com.mapbox.mapboxsdk.location.modes.CameraMode import com.mapbox.mapboxsdk.location.modes.RenderMode import com.mapbox.mapboxsdk.maps.MapView import com.mapbox.mapboxsdk.maps.MapboxMap -import com.mapbox.mapboxsdk.maps.Style -import com.mapbox.mapboxsdk.plugins.annotation.* +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback import com.mapbox.mapboxsdk.plugins.annotation.Annotation +import com.mapbox.mapboxsdk.plugins.annotation.AnnotationManager +import com.mapbox.mapboxsdk.plugins.annotation.Fill +import com.mapbox.mapboxsdk.plugins.annotation.FillManager +import com.mapbox.mapboxsdk.plugins.annotation.FillOptions +import com.mapbox.mapboxsdk.plugins.annotation.Line +import com.mapbox.mapboxsdk.plugins.annotation.LineManager +import com.mapbox.mapboxsdk.plugins.annotation.LineOptions +import com.mapbox.mapboxsdk.plugins.annotation.OnSymbolDragListener +import com.mapbox.mapboxsdk.plugins.annotation.Options +import com.mapbox.mapboxsdk.plugins.annotation.Symbol +import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager +import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions import com.mapbox.mapboxsdk.style.layers.Property.LINE_CAP_ROUND -import com.google.android.gms.dynamic.unwrap -import com.google.android.gms.maps.GoogleMap -import com.mapbox.mapboxsdk.WellKnownTileServer +import org.microg.gms.maps.mapbox.model.AbstractMarker +import org.microg.gms.maps.mapbox.model.AnnotationType +import org.microg.gms.maps.mapbox.model.AnnotationType.FILL +import org.microg.gms.maps.mapbox.model.AnnotationType.LINE +import org.microg.gms.maps.mapbox.model.AnnotationType.SYMBOL +import org.microg.gms.maps.mapbox.model.BitmapDescriptorFactoryImpl +import org.microg.gms.maps.mapbox.model.CircleImpl +import org.microg.gms.maps.mapbox.model.GroundOverlayImpl import org.microg.gms.maps.mapbox.model.InfoWindow +import org.microg.gms.maps.mapbox.model.MarkerImpl +import org.microg.gms.maps.mapbox.model.Markup +import org.microg.gms.maps.mapbox.model.PolygonImpl +import org.microg.gms.maps.mapbox.model.PolylineImpl +import org.microg.gms.maps.mapbox.model.TileOverlayImpl import org.microg.gms.maps.mapbox.model.getInfoWindowViewFor -import com.mapbox.mapboxsdk.camera.CameraUpdateFactory -import com.mapbox.mapboxsdk.location.engine.* -import com.mapbox.mapboxsdk.maps.OnMapReadyCallback -import org.microg.gms.maps.mapbox.model.* +import org.microg.gms.maps.mapbox.utils.ComparablePair import org.microg.gms.maps.mapbox.utils.MultiArchLoader import org.microg.gms.maps.mapbox.utils.toGms import org.microg.gms.maps.mapbox.utils.toMapbox +import java.util.TreeMap import java.util.concurrent.atomic.AtomicBoolean private fun LongSparseArray.values() = (0 until size()).mapNotNull { valueAt(it) } @@ -99,17 +151,19 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG private var cameraIdleListener: IOnCameraIdleListener? = null private var markerDragListener: IOnMarkerDragListener? = null - var lineManager: LineManager? = null - val pendingLines = mutableSetOf>() + private val allocatedZLayers: TreeMap, String> = TreeMap() + + var lineManagers: MutableMap = mutableMapOf() + val pendingLines = mutableSetOf>>() var lineId = 0L - var fillManager: FillManager? = null - val pendingFills = mutableSetOf>() + var fillManagers: MutableMap = mutableMapOf() + val pendingFills = mutableSetOf>>() val circles = mutableMapOf() var fillId = 0L - var symbolManager: SymbolManager? = null - val pendingMarkers = mutableSetOf() + var symbolManagers: MutableMap = mutableMapOf() + val pendingMarkers = mutableSetOf>() val markers = mutableMapOf() var markerId = 0L @@ -302,9 +356,10 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG override fun addPolyline(options: PolylineOptions): IPolylineDelegate? { val line = PolylineImpl(this, "l${lineId++}", options) synchronized(this) { - val lineManager = lineManager + val lineManager = getManagerForZIndex(LINE, line.zIndex) + Log.d(TAG, "addPolyline zIndex=${line.zIndex}, manager=$lineManager") if (lineManager == null) { - pendingLines.add(line) + pendingLines.add(Pair(line.zIndex, line)) } else { line.update(lineManager) } @@ -316,16 +371,16 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG override fun addPolygon(options: PolygonOptions): IPolygonDelegate? { val fill = PolygonImpl(this, "p${fillId++}", options) synchronized(this) { - val fillManager = fillManager + val fillManager = getManagerForZIndex(FILL, fill.zIndex) if (fillManager == null) { - pendingFills.add(fill) + pendingFills.add(Pair(fill.zIndex, fill)) } else { fill.update(fillManager) } - val lineManager = lineManager + val lineManager = getManagerForZIndex(LINE, fill.zIndex) if (lineManager == null) { - pendingLines.addAll(fill.strokes) + pendingLines.addAll(fill.strokes.map { Pair(fill.zIndex, it) }) } else { for (stroke in fill.strokes) stroke.update(lineManager) } @@ -336,9 +391,9 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG override fun addMarker(options: MarkerOptions): IMarkerDelegate { val marker = MarkerImpl(this, "m${markerId++}", options) synchronized(this) { - val symbolManager = symbolManager + val symbolManager = getManagerForZIndex(SYMBOL, marker.zIndex) if (symbolManager == null) { - pendingMarkers.add(marker) + pendingMarkers.add(Pair(marker.zIndex, marker)) } else { marker.update(symbolManager) } @@ -359,15 +414,15 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG override fun addCircle(options: CircleOptions): ICircleDelegate { val circle = CircleImpl(this, "c${fillId++}", options) synchronized(this) { - val fillManager = fillManager + val fillManager = getManagerForZIndex(FILL, circle.zIndex) if (fillManager == null) { - pendingFills.add(circle) + pendingFills.add(Pair(circle.zIndex, circle)) } else { circle.update(fillManager) } - val lineManager = lineManager + val lineManager = getManagerForZIndex(LINE, circle.zIndex) if (lineManager == null) { - pendingLines.add(circle.line) + pendingLines.add(Pair(circle.zIndex, circle.line)) } else { circle.line.update(lineManager) } @@ -382,9 +437,9 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG } override fun clear() { - lineManager?.let { clear(it) } - fillManager?.let { clear(it) } - symbolManager?.let { clear(it) } + lineManagers.forEach { (_, v) -> clear(v) } + fillManagers.forEach { (_, v) -> clear(v) } + symbolManagers.forEach { (_, v) -> clear(v) } } fun > clear(manager: AnnotationManager<*, T, *, *, *, *>) { @@ -408,22 +463,22 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG } fun applyMapStyle() { - val lines = lineManager?.annotations?.values() - val fills = fillManager?.annotations?.values() - val symbols = symbolManager?.annotations?.values() - val update: (Style) -> Unit = { - lines?.let { runCatching { lineManager?.update(it) } } - fills?.let { runCatching { fillManager?.update(it) } } - symbols?.let { runCatching { symbolManager?.update(it) } } + map?.setStyle(getStyle(mapContext, storedMapType, mapStyle)) { + lineManagers.forEach { (_, manager) -> + val lines = manager.annotations.values() + runCatching { manager.update(lines) } + } + symbolManagers.forEach { (_, manager) -> + val symbols = manager.annotations.values() + runCatching { manager.update(symbols) } + } + fillManagers.forEach { (_, manager) -> + val fills = manager.annotations.values() + runCatching { manager.update(fills) } + } } - map?.setStyle( - getStyle(mapContext, storedMapType, mapStyle), - update - ) - map?.let { BitmapDescriptorFactoryImpl.registerMap(it) } - } override fun setWatermarkEnabled(watermark: Boolean) = afterInitialize { @@ -602,9 +657,150 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG private fun hasSymbolAt(latlng: com.mapbox.mapboxsdk.geometry.LatLng): Boolean { val point = map?.projection?.toScreenLocation(latlng) ?: return false - val features = map?.queryRenderedFeatures(point, symbolManager?.layerId) - ?: return false - return features.isNotEmpty() + return symbolManagers.values.any { manager -> + map + ?.queryRenderedFeatures(point, manager.layerId) + ?.isNotEmpty() + ?: false + } + } + + @Suppress("UNCHECKED_CAST") + fun , S : Options> getManagerForZIndex( + annoType: AnnotationType, + zIndex: Float + ): AnnotationManager<*, T, S, *, *, *>? { + Log.d("GmsMap", "Requested manager for $annoType at zIndex $zIndex") + + val manager = when (annoType) { + FILL -> fillManagers[zIndex] as AnnotationManager<*, T, S, *, *, *>? + SYMBOL -> symbolManagers[zIndex] as AnnotationManager<*, T, S, *, *, *>? + LINE -> lineManagers[zIndex] as AnnotationManager<*, T, S, *, *, *>? + } + if (manager != null) return manager + if (mapView == null || map == null || map?.style == null) return manager + + val keyMap = ComparablePair(-zIndex, annoType) + + synchronized(mapLock) { + val belowId = allocatedZLayers.lowerEntry(keyMap)?.value + var aboveId = allocatedZLayers.higherEntry(keyMap)?.value + if (aboveId == belowId) aboveId = null + + Log.d( + "GmsMap", + "Creating new manager for $annoType at zIndex $zIndex, below=$belowId, above=$aboveId" + ) + + val newManager = when (annoType) { + FILL -> FillManager( + mapView!!, + map!!, + map!!.style!!, + belowId, + aboveId + ).apply { + addClickListener { fill -> + try { + circles[fill.id]?.let { circle -> + if (circle.isClickable) { + circleClickListener?.let { + it.onCircleClick(circle) + return@addClickListener true + } + } + } + } catch (e: Exception) { + Log.w(TAG, e) + } + false + } + } + + SYMBOL -> SymbolManager( + mapView!!, + map!!, + map!!.style!!, + belowId, + aboveId + ).apply { + iconAllowOverlap = true + addClickListener { + val marker = markers[it.id] + try { + if (markers[it.id]?.let { markerClickListener?.onMarkerClick(it) } == true) { + return@addClickListener true + } + } catch (e: Exception) { + Log.w(TAG, e) + return@addClickListener false + } + + marker?.let { showInfoWindow(it) } == true + } + addDragListener(object : OnSymbolDragListener { + override fun onAnnotationDragStarted(annotation: Symbol?) { + try { + markers[annotation?.id]?.let { + markerDragListener?.onMarkerDragStart( + it + ) + } + } catch (e: Exception) { + Log.w(TAG, e) + } + } + + override fun onAnnotationDrag(annotation: Symbol?) { + try { + annotation?.let { symbol -> + markers[symbol.id]?.let { marker -> + marker.setPositionWhileDragging(symbol.latLng.toGms()) + markerDragListener?.onMarkerDrag(marker) + } + } + } catch (e: Exception) { + Log.w(TAG, e) + } + } + + override fun onAnnotationDragFinished(annotation: Symbol?) { + mapView?.post { + try { + markers[annotation?.id]?.let { + markerDragListener?.onMarkerDragEnd( + it + ) + } + } catch (e: Exception) { + Log.w(TAG, e) + } + } + } + }) + } + + LINE -> LineManager( + mapView!!, + map!!, + map!!.style!!, + belowId, + aboveId + ).apply { + lineCap = LINE_CAP_ROUND + } + } + + allocatedZLayers.put(keyMap, newManager.layerId) + + when (annoType) { + FILL -> fillManagers[zIndex] = newManager as FillManager + LINE -> lineManagers[zIndex] = newManager as LineManager + SYMBOL -> symbolManagers[zIndex] = newManager as SymbolManager + } + + return newManager as AnnotationManager<*, T, S, *, *, *>? + } } private fun initMap(map: MapboxMap) { @@ -698,86 +894,24 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG map.getStyle { mapView?.let { view -> if (loaded) return@let - val symbolManager: SymbolManager - val lineManager: LineManager - val fillManager: FillManager - - synchronized(mapLock) { - fillManager = FillManager(view, map, it) - symbolManager = SymbolManager(view, map, it) - lineManager = LineManager(view, map, it) - lineManager.lineCap = LINE_CAP_ROUND - - this.symbolManager = symbolManager - this.lineManager = lineManager - this.fillManager = fillManager - } - symbolManager.iconAllowOverlap = true - symbolManager.addClickListener { - val marker = markers[it.id] - try { - if (markers[it.id]?.let { markerClickListener?.onMarkerClick(it) } == true) { - return@addClickListener true - } - } catch (e: Exception) { - Log.w(TAG, e) - return@addClickListener false - } - - marker?.let { showInfoWindow(it) } == true - } - symbolManager.addDragListener(object : OnSymbolDragListener { - override fun onAnnotationDragStarted(annotation: Symbol?) { - try { - markers[annotation?.id]?.let { markerDragListener?.onMarkerDragStart(it) } - } catch (e: Exception) { - Log.w(TAG, e) - } - } - - override fun onAnnotationDrag(annotation: Symbol?) { - try { - annotation?.let { symbol -> - markers[symbol.id]?.let { marker -> - marker.setPositionWhileDragging(symbol.latLng.toGms()) - markerDragListener?.onMarkerDrag(marker) - } - } - } catch (e: Exception) { - Log.w(TAG, e) - } - } - override fun onAnnotationDragFinished(annotation: Symbol?) { - mapView?.post { - try { - markers[annotation?.id]?.let { markerDragListener?.onMarkerDragEnd(it) } - } catch (e: Exception) { - Log.w(TAG, e) - } - } - } - }) - fillManager.addClickListener { fill -> - try { - circles[fill.id]?.let { circle -> - if (circle.isClickable) { - circleClickListener?.let { - it.onCircleClick(circle) - return@addClickListener true - } - } - } - } catch (e: Exception) { - Log.w(TAG, e) - } - false + pendingFills.forEach { (zIndex, fill) -> + fill.update( + getManagerForZIndex(FILL, zIndex)!! + ) } - pendingFills.forEach { it.update(fillManager) } pendingFills.clear() - pendingLines.forEach { it.update(lineManager) } + pendingLines.forEach { (zIndex, line) -> + line.update( + getManagerForZIndex(LINE, zIndex)!! + ) + } pendingLines.clear() - pendingMarkers.forEach { it.update(symbolManager) } + pendingMarkers.forEach { (zIndex, marker) -> + marker.update( + getManagerForZIndex(SYMBOL, zIndex)!! + ) + } pendingMarkers.clear() pendingBitmaps.forEach { map -> it.addImage(map.key, map.value) } @@ -858,13 +992,13 @@ class GoogleMapImpl(context: Context, var options: GoogleMapOptions) : AbstractG override fun onDestroy() { Log.d(TAG, "onDestroy"); userOnInitializedCallbackList.clear() - lineManager?.onDestroy() - lineManager = null - fillManager?.onDestroy() - fillManager = null + lineManagers.forEach { (_, manager) -> manager.onDestroy() } + lineManagers.clear() + fillManagers.forEach { (_, manager) -> manager.onDestroy() } + fillManagers.clear() circles.clear() - symbolManager?.onDestroy() - symbolManager = null + symbolManagers.forEach { (_, manager) -> manager.onDestroy() } + symbolManagers.clear() currentInfoWindow?.close() pendingMarkers.clear() markers.clear() 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 index f4395633c6..764907204d 100644 --- 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 @@ -261,7 +261,7 @@ class LiteGoogleMapImpl(context: Context, var options: GoogleMapOptions) : Abstr PropertyFactory.lineCap(Property.LINE_CAP_ROUND) ) ).withSource( - GeoJsonSource(polyline.id, polyline.annotationOptions.geometry) + GeoJsonSource(polyline.id, polyline.baseAnnotationOptions.geometry) ) } @@ -281,7 +281,7 @@ class LiteGoogleMapImpl(context: Context, var options: GoogleMapOptions) : Abstr withProperties(PropertyFactory.linePattern(name)) styleBuilder.withImage(name, it.makeBitmap(circle.strokeColor, circle.strokeWidth, dpi)) } - }).withSource(GeoJsonSource("${circle.id}s", circle.line.annotationOptions.geometry)) + }).withSource(GeoJsonSource("${circle.id}s", circle.line.annotations.first().options.geometry)) } // Add markers diff --git a/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/AnnotationTracker.kt b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/AnnotationTracker.kt new file mode 100644 index 0000000000..6310c231d9 --- /dev/null +++ b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/AnnotationTracker.kt @@ -0,0 +1,9 @@ +package org.microg.gms.maps.mapbox.model + +import com.mapbox.mapboxsdk.plugins.annotation.Annotation +import com.mapbox.mapboxsdk.plugins.annotation.Options + +data class AnnotationTracker, S : Options>( + var options: S, + var annotation: T? = null +) diff --git a/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/AnnotationType.kt b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/AnnotationType.kt new file mode 100644 index 0000000000..1a811b01d3 --- /dev/null +++ b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/AnnotationType.kt @@ -0,0 +1,7 @@ +package org.microg.gms.maps.mapbox.model + +enum class AnnotationType { + LINE, + FILL, + SYMBOL +} \ No newline at end of file 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 5cc551212d..447e4b2ea4 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 @@ -21,7 +21,6 @@ import android.util.Log 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.model.Dash import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.PatternItem import com.google.android.gms.maps.model.internal.ICircleDelegate @@ -36,9 +35,11 @@ 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 org.microg.gms.maps.mapbox.getName import org.microg.gms.maps.mapbox.makeBitmap +import org.microg.gms.maps.mapbox.model.AnnotationType.FILL +import org.microg.gms.maps.mapbox.model.AnnotationType.LINE +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) @@ -64,31 +65,31 @@ abstract class AbstractCircle( internal var tag: Any? = null internal val line: Markup = object : Markup { - override var annotation: Line? = null - override val annotationOptions: LineOptions - get() = LineOptions() - .withGeometry( - LineString.fromLngLats( - makeOutlineLatLngs() - ) - ).withLineWidth(strokeWidth / dpiFactor()) - .withLineColor(ColorUtils.colorToRgbaString(strokeColor)) - .withLineOpacity(if (visible) 1f else 0f) - .apply { - strokePattern?.let { - withLinePattern(it.getName(strokeColor, strokeWidth)) - } - } + override var annotations: List> = listOf( + AnnotationTracker( + LineOptions() + .withGeometry( + LineString.fromLngLats( + makeOutlineLatLngs() + ) + ).withLineWidth(strokeWidth / dpiFactor()) + .withLineColor(ColorUtils.colorToRgbaString(strokeColor)) + .withLineOpacity(if (visible) 1f else 0f) + .apply { + strokePattern?.let { + withLinePattern(it.getName(strokeColor, strokeWidth)) + } + }) + ) override var removed: Boolean = false } val annotationOptions: FillOptions - get() = - FillOptions() - .withGeometry(makePolygon()) - .withFillColor(ColorUtils.colorToRgbaString(fillColor)) - .withFillOpacity(if (visible && !wrapsAroundPoles()) 1f else 0f) + get() = FillOptions() + .withGeometry(makePolygon()) + .withFillColor(ColorUtils.colorToRgbaString(fillColor)) + .withFillOpacity(if (visible && !wrapsAroundPoles()) 1f else 0f) internal abstract fun update() @@ -262,9 +263,13 @@ abstract class AbstractCircle( 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 annotations = + listOf(AnnotationTracker(annotationOptions)) override var removed: Boolean = false + private val annotation: Fill? + get() = annotations.firstOrNull()?.annotation + override fun update() { val polygon = makePolygon() @@ -275,7 +280,7 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options it.fillOpacity = if (visible && !wrapsAroundPoles()) 1f else 0f } - line.annotation?.let { + line.annotations.firstOrNull()?.annotation?.let { it.latLngs = makeOutlineLatLngs().map { point -> com.mapbox.mapboxsdk.geometry.LatLng( point.latitude(), @@ -288,22 +293,22 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options (strokePattern ?: emptyList()).let { pattern -> val bitmapName = pattern.getName(strokeColor, strokeWidth) map.addBitmap(bitmapName, pattern.makeBitmap(strokeColor, strokeWidth)) - line.annotation?.linePattern = bitmapName + line.annotations.firstOrNull()?.annotation?.linePattern = bitmapName } - map.lineManager?.let { line.update(it) } + map.getManagerForZIndex(LINE, zIndex)?.let { line.update(it) } it.setLineColor(strokeColor) } - map.fillManager?.let { update(it) } - map.lineManager?.let { line.update(it) } + map.getManagerForZIndex(FILL, zIndex)?.let { update(it) } + map.getManagerForZIndex(LINE, zIndex)?.let { line.update(it) } } override fun remove() { removed = true line.removed = true - map.fillManager?.let { update(it) } - map.lineManager?.let { line.update(it) } + map.getManagerForZIndex(FILL, zIndex)?.let { update(it) } + map.getManagerForZIndex(LINE, zIndex)?.let { line.update(it) } } 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 bbb89993c9..685f2388ff 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 @@ -20,17 +20,17 @@ 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.dynamic.unwrap import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions import com.google.android.gms.maps.model.internal.IMarkerDelegate 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 com.google.android.gms.maps.model.BitmapDescriptorFactory 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.model.AnnotationType.SYMBOL import org.microg.gms.maps.mapbox.utils.toMapbox abstract class AbstractMarker( @@ -170,12 +170,15 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options internal var rotation: Float = options.rotation override var draggable: Boolean = options.isDraggable - override var annotation: Symbol? = null + override var annotations = listOf(AnnotationTracker(annotationOptions)) override var removed: Boolean = false + private val annotation: Symbol? + get() = annotations.firstOrNull()?.annotation + override fun remove() { removed = true - map.symbolManager?.let { update(it) } + map.getManagerForZIndex(SYMBOL, zIndex)?.let { update(it) } } override fun update() { @@ -186,7 +189,7 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options it.symbolSortKey = zIndex icon?.applyTo(it, anchor, map.dpiFactor) } - map.symbolManager?.let { update(it) } + map.getManagerForZIndex(SYMBOL, zIndex)?.let { update(it) } } override fun update(manager: AnnotationManager<*, Symbol, SymbolOptions, *, *, *>) { @@ -233,7 +236,7 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options override fun setDraggable(draggable: Boolean) { this.draggable = draggable - map.symbolManager?.let { update(it) } + map.getManagerForZIndex(SYMBOL, zIndex)?.let { update(it) } } override fun isDraggable(): Boolean = draggable @@ -269,7 +272,7 @@ class MarkerImpl(private val map: GoogleMapImpl, private val id: String, options override fun setRotation(rotation: Float) { this.rotation = rotation annotation?.iconRotate = rotation - map.symbolManager?.let { update(it) } + map.getManagerForZIndex(SYMBOL, zIndex)?.let { update(it) } map.currentInfoWindow?.update() } 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 179c5a0b07..f4d03dc8d6 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 @@ -16,25 +16,25 @@ package org.microg.gms.maps.mapbox.model -import android.util.Log import com.mapbox.mapboxsdk.plugins.annotation.Annotation import com.mapbox.mapboxsdk.plugins.annotation.AnnotationManager import com.mapbox.mapboxsdk.plugins.annotation.Options interface Markup, S : Options> { - var annotation: T? - val annotationOptions: S + var annotations: List> var removed: Boolean fun update(manager: AnnotationManager<*, T, S, *, *, *>) { synchronized(this) { - if (removed && annotation != null) { - manager.delete(annotation) - annotation = null - } else if (annotation != null) { - manager.update(annotation) - } else if (!removed) { - annotation = manager.create(annotationOptions) + for (tracker in annotations) { + if (removed && tracker.annotation != null) { + manager.delete(tracker.annotation) + tracker.annotation = null + } else if (tracker.annotation != null) { + manager.update(tracker.annotation) + } else if (!removed) { + tracker.annotation = manager.create(tracker.options) + } } } } 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 c5c205ed23..e0a117652d 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 @@ -20,6 +20,7 @@ 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.model.AnnotationType.FILL import org.microg.gms.maps.mapbox.utils.toMapbox import org.microg.gms.utils.warnOnTransactionIssues @@ -203,12 +204,16 @@ class PolygonImpl(private val map: GoogleMapImpl, id: String, options: PolygonOp ) }).toMutableList() - override var annotation: Fill? = null + override var annotations = + listOf(AnnotationTracker(annotationOptions)) override var removed: Boolean = false + protected val annotation: Fill? + get() = annotations[0].annotation + override fun remove() { removed = true - map.fillManager?.let { update(it) } + map.getManagerForZIndex(FILL, zIndex)?.let { update(it) } super.remove() } @@ -219,7 +224,7 @@ class PolygonImpl(private val map: GoogleMapImpl, id: String, options: PolygonOp it.fillOpacity = if (visible) 1f else 0f it.latLngs = mutableListOf(points.map { it.toMapbox() }).plus(this.holes.map { it.map { it.toMapbox() } }) } - map.fillManager?.let { update(it) } + map.getManagerForZIndex(FILL, zIndex)?.let { update(it) } } override fun addPolyline(id: String, options: PolylineOptions) { 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 4405c21c77..18452471ca 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 @@ -9,17 +9,33 @@ import android.os.Parcel import com.google.android.gms.dynamic.IObjectWrapper import com.google.android.gms.dynamic.ObjectWrapper import com.google.android.gms.maps.model.Cap +import com.google.android.gms.maps.model.JointType import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.PatternItem import com.google.android.gms.maps.model.internal.IPolylineDelegate import com.mapbox.mapboxsdk.plugins.annotation.Line import com.mapbox.mapboxsdk.plugins.annotation.LineOptions +import com.mapbox.mapboxsdk.style.layers.Property.LINE_JOIN_BEVEL +import com.mapbox.mapboxsdk.style.layers.Property.LINE_JOIN_MITER +import com.mapbox.mapboxsdk.style.layers.Property.LINE_JOIN_ROUND 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.model.AnnotationType.LINE import org.microg.gms.maps.mapbox.utils.toMapbox import org.microg.gms.utils.warnOnTransactionIssues +import java.util.LinkedList +import kotlin.math.abs +import kotlin.math.acos +import kotlin.math.asin +import kotlin.math.atan2 +import kotlin.math.ceil +import kotlin.math.cos +import kotlin.math.max +import kotlin.math.sin +import kotlin.math.sqrt import com.google.android.gms.maps.model.PolylineOptions as GmsLineOptions +import com.mapbox.mapboxsdk.geometry.LatLng as MapboxLatLng abstract class AbstractPolylineImpl(private val id: String, options: GmsLineOptions, private val dpiFactor: Function0) : IPolylineDelegate.Stub() { internal var points: List = ArrayList(options.points) @@ -34,10 +50,15 @@ abstract class AbstractPolylineImpl(private val id: String, options: GmsLineOpti internal var endCap: Cap = options.endCap internal var geodesic = options.isGeodesic internal var zIndex = options.zIndex + internal var spans = options.spans - val annotationOptions: LineOptions + val baseAnnotationOptions: LineOptions get() = LineOptions() - .withLatLngs(points.map { it.toMapbox() }) + .withLineJoin(when (jointType) { + JointType.BEVEL -> LINE_JOIN_BEVEL + JointType.DEFAULT -> LINE_JOIN_MITER + else -> LINE_JOIN_ROUND + }) .withLineWidth(width / dpiFactor.invoke()) .withLineColor(ColorUtils.colorToRgbaString(color)) .withLineOpacity(if (visible) 1f else 0f) @@ -82,6 +103,7 @@ abstract class AbstractPolylineImpl(private val id: String, options: GmsLineOpti override fun setGeodesic(geod: Boolean) { this.geodesic = geod + update() } override fun isGeodesic(): Boolean = geodesic @@ -110,6 +132,7 @@ abstract class AbstractPolylineImpl(private val id: String, options: GmsLineOpti override fun setJointType(jointType: Int) { this.jointType = jointType + update() } override fun getJointType(): Int = jointType @@ -151,22 +174,185 @@ abstract class AbstractPolylineImpl(private val id: String, options: GmsLineOpti class PolylineImpl(private val map: GoogleMapImpl, id: String, options: GmsLineOptions) : AbstractPolylineImpl(id, options, { map.dpiFactor }), Markup { - override var annotation: Line? = null + override var annotations = computeAnnotations() override var removed: Boolean = false + private fun interpolateGeodesic(points: List): List { + val maxSegmentMeters = 20_000.0 + val curvatureBoost = 0.75 + + if (points.size <= 1) return points.toList() + + val r = 6_371_008.8 // mean Earth radius (meters) + + fun toVec(latDeg: Double, lonDeg: Double): DoubleArray { + val lat = Math.toRadians(latDeg) + val lon = Math.toRadians(lonDeg) + val cl = cos(lat) + return doubleArrayOf(cl * cos(lon), cl * sin(lon), sin(lat)) + } + + fun norm(v: DoubleArray): DoubleArray { + val m = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) + return doubleArrayOf(v[0] / m, v[1] / m, v[2] / m) + } + + fun toLatLng(v: DoubleArray): MapboxLatLng { + val x = v[0]; + val y = v[1]; + val z = v[2] + val lat = asin(z) + val lon = atan2(y, x) + // wrap to [-180,180) + var lonDeg = Math.toDegrees(lon) + lonDeg = ((lonDeg + 540.0) % 360.0) - 180.0 + return MapboxLatLng(Math.toDegrees(lat), lonDeg) + } + + fun centralAngle(a: DoubleArray, b: DoubleArray): Double { + val dot = (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]).coerceIn(-1.0, 1.0) + return acos(dot) + } + + val out = ArrayList(points.size * 4) + + for (i in 0 until points.lastIndex) { + val a = points[i] + val b = points[i + 1] + val va = norm(toVec(a.latitude, a.longitude)) + val vb = norm(toVec(b.latitude, b.longitude)) + val omega = centralAngle(va, vb) + + // Base segment count from distance + val distance = omega * r + var steps = max(1, ceil(distance / maxSegmentMeters).toInt()) + + // Heuristic curvature boost (how "curvy" it *looks* in Web-Mercator) + val meanLatRad = Math.toRadians((a.latitude + b.latitude) / 2.0) + val dLonRad = abs( + // shortest ∆lon across antimeridian + ((Math.toRadians(b.longitude - a.longitude) + Math.PI) % (2 * Math.PI)) - Math.PI + ) + val mercatorCurviness = abs(sin(meanLatRad)) * (dLonRad / Math.PI) // 0..1 + val boost = 1.0 + curvatureBoost * mercatorCurviness + steps = max(1, ceil(steps * boost).toInt()) + + // Emit points along the great-circle (slerp) + val sinOmega = sin(omega) + // Add first point (or skip if already added as previous segment's end) + if (i == 0) out.add(MapboxLatLng(a.latitude, a.longitude)) + + if (omega == 0.0 || sinOmega == 0.0) { + // identical points: skip interpolation + out.add(MapboxLatLng(b.latitude, b.longitude)) + continue + } + + for (k in 1..steps) { + val t = k.toDouble() / steps + val s1 = sin((1 - t) * omega) / sinOmega + val s2 = sin(t * omega) / sinOmega + val vx = s1 * va[0] + s2 * vb[0] + val vy = s1 * va[1] + s2 * vb[1] + val vz = s1 * va[2] + s2 * vb[2] + var p = toLatLng(doubleArrayOf(vx, vy, vz)) + + if (out.isNotEmpty() && abs(p.longitude - out.last().longitude) > 180) { + // Make sure the current point crosses the antimeridian not normalized, + // i.e. going from +179° to +181° instead of +179° to -179°. + // This avoids a long horizontal line across the map. + val lon = if (out.last().longitude > 0) p.longitude + 360 else p.longitude - 360 + p = MapboxLatLng(p.latitude, lon) + } + + // Avoid duplicating the joint point on the next segment + if (k < steps || i == points.lastIndex - 1) { + out.add(p) + } + } + } + return out + } + + private fun List.mapToGeodesicIfNeeded(): List { + if (!geodesic) return this + return interpolateGeodesic(this) + } + + private fun computeAnnotations(): List> { + val pointsQueue = LinkedList(points) + val result = mutableListOf>() + + for (span in spans) { + val spanPoints = mutableListOf() + + var i = 0 + while (i < span.segments && pointsQueue.isNotEmpty()) { + spanPoints.add(pointsQueue.removeFirst()) + i++ + } + + val options = baseAnnotationOptions + // TODO: implement gradient support + .withLineColor(ColorUtils.colorToRgbaString(span.style.color)) + .withLineOpacity(if (visible and span.style.isVisible) 1f else 0f) + .withLineWidth((span.style.width) / map.dpiFactor) + .withLatLngs( + spanPoints + .map { it.toMapbox() } + .mapToGeodesicIfNeeded() + ) + result.add(AnnotationTracker(options)) + } + + if (pointsQueue.isNotEmpty()) { + val options = baseAnnotationOptions + .withLatLngs( + pointsQueue + .map { it.toMapbox() } + .mapToGeodesicIfNeeded() + ) + result.add(AnnotationTracker(options)) + } + + return result + } + override fun remove() { removed = true - map.lineManager?.let { update(it) } + map.getManagerForZIndex(LINE, zIndex)?.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 + computeAnnotations().forEachIndexed { i, it -> + if (i < annotations.size) { + annotations[i].options = it.options + annotations[i].annotation?.apply { + latLngs = it.options.latLngs + lineWidth = it.options.lineWidth + lineColor = it.options.lineColor + lineOpacity = it.options.lineOpacity + lineJoin = it.options.lineJoin + } + } else { + annotations = annotations + it + } } - map.lineManager?.let { update(it) } + map.getManagerForZIndex(LINE, zIndex)?.let { update(it) } + } + + override fun setZIndex(zIndex: Float) { + val oldZIndex = this.zIndex + if (oldZIndex == zIndex) { + super.setZIndex(zIndex) + return + } + + removed = true + map.getManagerForZIndex(LINE, zIndex)?.let { update(it) } + super.setZIndex(zIndex) + removed = false + map.getManagerForZIndex(LINE, zIndex)?.let { update(it) } } companion object { diff --git a/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/utils/ComparablePair.kt b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/utils/ComparablePair.kt new file mode 100644 index 0000000000..ef423f275b --- /dev/null +++ b/play-services-maps/core/mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/utils/ComparablePair.kt @@ -0,0 +1,18 @@ +package org.microg.gms.maps.mapbox.utils + +data class ComparablePair, S : Comparable>(val first: T, val second: S) : + Comparable> { + override fun compareTo(other: ComparablePair): Int { + // Lexicographical order + val firstComparison = first.compareTo(other.first) + return if (firstComparison != 0) { + firstComparison + } else { + second.compareTo(other.second) + } + } + + override fun toString(): String { + return "($first, $second)" + } +} \ No newline at end of file diff --git a/play-services-maps/src/main/java/com/google/android/gms/maps/model/StrokeStyle.java b/play-services-maps/src/main/java/com/google/android/gms/maps/model/StrokeStyle.java index edc91583a3..392e226c26 100644 --- a/play-services-maps/src/main/java/com/google/android/gms/maps/model/StrokeStyle.java +++ b/play-services-maps/src/main/java/com/google/android/gms/maps/model/StrokeStyle.java @@ -20,4 +20,24 @@ public class StrokeStyle extends AutoSafeParcelable { private StampStyle stamp; public static final Creator CREATOR = new AutoCreator<>(StrokeStyle.class); + + public float getWidth() { + return width; + } + + public int getColor() { + return color; + } + + public int getToColor() { + return toColor; + } + + public boolean isVisible() { + return isVisible; + } + + public StampStyle getStamp() { + return stamp; + } }