Loading play-services-maps-core-mapbox/build.gradle +2 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,8 @@ dependencies { implementation("org.maplibre.gl:android-plugin-annotation-v9:1.0.0") { exclude group: 'com.google.android.gms' } implementation 'org.maplibre.gl:android-sdk-turf:5.9.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" } Loading play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt +15 −21 Original line number Diff line number Diff line Loading @@ -98,17 +98,13 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) private var markerDragListener: IOnMarkerDragListener? = null var lineManager: LineManager? = null val pendingLines = mutableSetOf<PolylineImpl>() val pendingLines = mutableSetOf<Markup<Line, LineOptions>>() var lineId = 0L var fillManager: FillManager? = null val pendingFills = mutableSetOf<PolygonImpl>() val pendingFills = mutableSetOf<Markup<Fill, FillOptions>>() var fillId = 0L var circleManager: CircleManager? = null val pendingCircles = mutableSetOf<CircleImpl>() var circleId = 0L var symbolManager: SymbolManager? = null val pendingMarkers = mutableSetOf<MarkerImpl>() val markers = mutableMapOf<Long, MarkerImpl>() Loading Loading @@ -302,20 +298,25 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } override fun addCircle(options: CircleOptions): ICircleDelegate? { val circle = CircleImpl(this, "c${circleId++}", options) val circle = CircleImpl(this, "c${fillId++}", options) synchronized(this) { val circleManager = circleManager if (circleManager == null) { pendingCircles.add(circle) val fillManager = fillManager if (fillManager == null) { pendingFills.add(circle) } else { circle.update(fillManager) } val lineManager = lineManager if (lineManager == null) { pendingLines.add(circle.line) } else { circle.update(circleManager) circle.line.update(lineManager) } } return circle } override fun clear() { circleManager?.let { clear(it) } lineManager?.let { clear(it) } fillManager?.let { clear(it) } symbolManager?.let { clear(it) } Loading @@ -342,12 +343,10 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } fun applyMapType() { val circles = circleManager?.annotations?.values() val lines = lineManager?.annotations?.values() val fills = fillManager?.annotations?.values() val symbols = symbolManager?.annotations?.values() val update: (Style) -> Unit = { circles?.let { runCatching { circleManager?.update(it) } } lines?.let { runCatching { lineManager?.update(it) } } fills?.let { runCatching { fillManager?.update(it) } } symbols?.let { runCatching { symbolManager?.update(it) } } Loading Loading @@ -649,11 +648,9 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) if (loaded) return@let val symbolManager: SymbolManager val lineManager: LineManager val circleManager: CircleManager val fillManager: FillManager synchronized(mapLock) { circleManager = CircleManager(view, map, it) fillManager = FillManager(view, map, it) symbolManager = SymbolManager(view, map, it) lineManager = LineManager(view, map, it) Loading @@ -661,7 +658,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) this.symbolManager = symbolManager this.lineManager = lineManager this.circleManager = circleManager this.fillManager = fillManager } symbolManager.iconAllowOverlap = true Loading Loading @@ -691,15 +687,15 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } override fun onAnnotationDragFinished(annotation: Symbol?) { mapView?.post { try { markers[annotation?.id]?.let { markerDragListener?.onMarkerDragEnd(it) } } catch (e: Exception) { Log.w(TAG, e) } } } }) pendingCircles.forEach { it.update(circleManager) } pendingCircles.clear() pendingFills.forEach { it.update(fillManager) } pendingFills.clear() pendingLines.forEach { it.update(lineManager) } Loading Loading @@ -743,8 +739,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun onPause() = mapView?.onPause() ?: Unit override fun onDestroy() { Log.d(TAG, "destroy") circleManager?.onDestroy() circleManager = null lineManager?.onDestroy() lineManager = null Loading play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Circle.kt +129 −29 Original line number Diff line number Diff line Loading @@ -20,76 +20,177 @@ import android.os.Parcel import android.util.Log import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.internal.ICircleDelegate import com.mapbox.mapboxsdk.plugins.annotation.Circle import com.mapbox.mapboxsdk.plugins.annotation.CircleOptions import com.mapbox.geojson.LineString 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.TurfMeasurement import com.mapbox.turf.TurfMeta import com.mapbox.turf.TurfTransformation import org.microg.gms.maps.mapbox.GoogleMapImpl import org.microg.gms.maps.mapbox.utils.toMapbox import com.google.android.gms.maps.model.CircleOptions as GmsCircleOptions class CircleImpl(private val map: GoogleMapImpl, private val id: String, options: GmsCircleOptions) : ICircleDelegate.Stub(), Markup<Circle, CircleOptions> { val NORTH_POLE = Point.fromLngLat(0.0, 90.0) val SOUTH_POLE = Point.fromLngLat(0.0, -90.0) /** * Amount of points to be used in the polygon that approximates the circle. */ const val CIRCLE_POLYGON_STEPS = 256 class CircleImpl(private val map: GoogleMapImpl, private val id: String, options: GmsCircleOptions) : ICircleDelegate.Stub(), Markup<Fill, FillOptions> { private var center: LatLng = options.center private var radius: Double = options.radius 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 override var annotation: Circle? = null internal val line: Markup<Line, LineOptions> = object : Markup<Line, LineOptions> { override var annotation: Line? = null override val annotationOptions: LineOptions get() = LineOptions() .withGeometry( LineString.fromLngLats( makeOutlineLatLngs() ) ).withLineWidth(strokeWidth / map.dpiFactor) .withLineColor(ColorUtils.colorToRgbaString(strokeColor)) .withLineOpacity(if (visible) 1f else 0f) override val removed: Boolean = false } override var annotation: Fill? = null override var removed: Boolean = false override val annotationOptions: CircleOptions get() = CircleOptions() .withLatLng(center.toMapbox()) .withCircleColor(ColorUtils.colorToRgbaString(fillColor)) .withCircleRadius(radius.toFloat()) .withCircleStrokeColor(ColorUtils.colorToRgbaString(strokeColor)) .withCircleStrokeWidth(strokeWidth / map.dpiFactor) .withCircleOpacity(if (visible) 1f else 0f) .withCircleStrokeOpacity(if (visible) 1f else 0f) override val annotationOptions: FillOptions get() = FillOptions() .withGeometry(makePolygon()) .withFillColor(ColorUtils.colorToRgbaString(fillColor)) .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 ) /** * Google's "map renderer is unable to draw the circle fill if * the circle encompasses either the North or South pole". */ private fun wrapsAroundPoles() = Point.fromLngLat(center.longitude, center.latitude).let { TurfMeasurement.distance( it, NORTH_POLE ) * 1000 < radius || TurfMeasurement.distance( it, SOUTH_POLE ) * 1000 < radius } private fun makeOutlineLatLngs() = TurfMeta.coordAll( makePolygon(), wrapsAroundPoles() ).let { // Circles around the poles are tricky to draw (https://github.com/mapbox/mapbox-gl-js/issues/11235). // 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) if (!centerPoint.equals(NORTH_POLE) && TurfMeasurement.distance(centerPoint, NORTH_POLE) * 1000 < radius) { // Wraps around North Pole for (i in 0 until it.size) { // We want to have the north-most points at the start and end if (it[0].latitude() > it[1].latitude() && it[it.size-1].latitude() > it[it.size-2].latitude()) { return@let it } else { // Cycle point list val zero = it.removeFirst() it.add(zero) } } } if (!centerPoint.equals(SOUTH_POLE) && TurfMeasurement.distance(centerPoint, SOUTH_POLE) * 1000 < radius) { // Wraps around South Pole for (i in 0 until it.size) { // We want to have the south-most points at the start and end if (it[0].latitude() < it[1].latitude() && it[it.size-1].latitude() < it[it.size-2].latitude()) { return@let it } else { // Cycle point list val last = it.removeAt(it.size - 1) it.add(0, last) } } } it } 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.circleManager?.let { update(it) } map.fillManager?.let { update(it) } } override fun getId(): String = id override fun setCenter(center: LatLng) { this.center = center annotation?.latLng = center.toMapbox() map.circleManager?.let { update(it) } updateLatLngs() map.fillManager?.let { update(it) } } override fun getCenter(): LatLng = center override fun setRadius(radius: Double) { this.radius = radius annotation?.circleRadius = radius.toFloat() map.circleManager?.let { update(it) } updateLatLngs() map.fillManager?.let { update(it) } } override fun getRadius(): Double = radius override fun setStrokeWidth(width: Float) { this.strokeWidth = width annotation?.circleStrokeWidth = width / map.dpiFactor map.circleManager?.let { update(it) } line.annotation?.lineWidth = width / map.dpiFactor map.lineManager?.let { line.update(it) } } override fun getStrokeWidth(): Float = strokeWidth override fun setStrokeColor(color: Int) { this.strokeColor = color annotation?.setCircleStrokeColor(color) map.circleManager?.let { update(it) } line.annotation?.setLineColor(color) map.lineManager?.let { line.update(it) } } override fun getStrokeColor(): Int = strokeColor override fun setFillColor(color: Int) { this.fillColor = color annotation?.setCircleColor(color) map.circleManager?.let { update(it) } annotation?.setFillColor(color) map.fillManager?.let { update(it) } } override fun getFillColor(): Int = fillColor Loading @@ -105,9 +206,8 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options override fun setVisible(visible: Boolean) { this.visible = visible annotation?.circleOpacity = if (visible) 1f else 0f annotation?.circleStrokeOpacity = if (visible) 1f else 0f map.circleManager?.let { update(it) } annotation?.fillOpacity = if (visible && !wrapsAroundPoles()) 1f else 0f map.fillManager?.let { update(it) } } override fun isVisible(): Boolean = visible Loading Loading
play-services-maps-core-mapbox/build.gradle +2 −0 Original line number Diff line number Diff line Loading @@ -26,6 +26,8 @@ dependencies { implementation("org.maplibre.gl:android-plugin-annotation-v9:1.0.0") { exclude group: 'com.google.android.gms' } implementation 'org.maplibre.gl:android-sdk-turf:5.9.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" } Loading
play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/GoogleMap.kt +15 −21 Original line number Diff line number Diff line Loading @@ -98,17 +98,13 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) private var markerDragListener: IOnMarkerDragListener? = null var lineManager: LineManager? = null val pendingLines = mutableSetOf<PolylineImpl>() val pendingLines = mutableSetOf<Markup<Line, LineOptions>>() var lineId = 0L var fillManager: FillManager? = null val pendingFills = mutableSetOf<PolygonImpl>() val pendingFills = mutableSetOf<Markup<Fill, FillOptions>>() var fillId = 0L var circleManager: CircleManager? = null val pendingCircles = mutableSetOf<CircleImpl>() var circleId = 0L var symbolManager: SymbolManager? = null val pendingMarkers = mutableSetOf<MarkerImpl>() val markers = mutableMapOf<Long, MarkerImpl>() Loading Loading @@ -302,20 +298,25 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } override fun addCircle(options: CircleOptions): ICircleDelegate? { val circle = CircleImpl(this, "c${circleId++}", options) val circle = CircleImpl(this, "c${fillId++}", options) synchronized(this) { val circleManager = circleManager if (circleManager == null) { pendingCircles.add(circle) val fillManager = fillManager if (fillManager == null) { pendingFills.add(circle) } else { circle.update(fillManager) } val lineManager = lineManager if (lineManager == null) { pendingLines.add(circle.line) } else { circle.update(circleManager) circle.line.update(lineManager) } } return circle } override fun clear() { circleManager?.let { clear(it) } lineManager?.let { clear(it) } fillManager?.let { clear(it) } symbolManager?.let { clear(it) } Loading @@ -342,12 +343,10 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } fun applyMapType() { val circles = circleManager?.annotations?.values() val lines = lineManager?.annotations?.values() val fills = fillManager?.annotations?.values() val symbols = symbolManager?.annotations?.values() val update: (Style) -> Unit = { circles?.let { runCatching { circleManager?.update(it) } } lines?.let { runCatching { lineManager?.update(it) } } fills?.let { runCatching { fillManager?.update(it) } } symbols?.let { runCatching { symbolManager?.update(it) } } Loading Loading @@ -649,11 +648,9 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) if (loaded) return@let val symbolManager: SymbolManager val lineManager: LineManager val circleManager: CircleManager val fillManager: FillManager synchronized(mapLock) { circleManager = CircleManager(view, map, it) fillManager = FillManager(view, map, it) symbolManager = SymbolManager(view, map, it) lineManager = LineManager(view, map, it) Loading @@ -661,7 +658,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) this.symbolManager = symbolManager this.lineManager = lineManager this.circleManager = circleManager this.fillManager = fillManager } symbolManager.iconAllowOverlap = true Loading Loading @@ -691,15 +687,15 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } override fun onAnnotationDragFinished(annotation: Symbol?) { mapView?.post { try { markers[annotation?.id]?.let { markerDragListener?.onMarkerDragEnd(it) } } catch (e: Exception) { Log.w(TAG, e) } } } }) pendingCircles.forEach { it.update(circleManager) } pendingCircles.clear() pendingFills.forEach { it.update(fillManager) } pendingFills.clear() pendingLines.forEach { it.update(lineManager) } Loading Loading @@ -743,8 +739,6 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun onPause() = mapView?.onPause() ?: Unit override fun onDestroy() { Log.d(TAG, "destroy") circleManager?.onDestroy() circleManager = null lineManager?.onDestroy() lineManager = null Loading
play-services-maps-core-mapbox/src/main/kotlin/org/microg/gms/maps/mapbox/model/Circle.kt +129 −29 Original line number Diff line number Diff line Loading @@ -20,76 +20,177 @@ import android.os.Parcel import android.util.Log import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.internal.ICircleDelegate import com.mapbox.mapboxsdk.plugins.annotation.Circle import com.mapbox.mapboxsdk.plugins.annotation.CircleOptions import com.mapbox.geojson.LineString 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.TurfMeasurement import com.mapbox.turf.TurfMeta import com.mapbox.turf.TurfTransformation import org.microg.gms.maps.mapbox.GoogleMapImpl import org.microg.gms.maps.mapbox.utils.toMapbox import com.google.android.gms.maps.model.CircleOptions as GmsCircleOptions class CircleImpl(private val map: GoogleMapImpl, private val id: String, options: GmsCircleOptions) : ICircleDelegate.Stub(), Markup<Circle, CircleOptions> { val NORTH_POLE = Point.fromLngLat(0.0, 90.0) val SOUTH_POLE = Point.fromLngLat(0.0, -90.0) /** * Amount of points to be used in the polygon that approximates the circle. */ const val CIRCLE_POLYGON_STEPS = 256 class CircleImpl(private val map: GoogleMapImpl, private val id: String, options: GmsCircleOptions) : ICircleDelegate.Stub(), Markup<Fill, FillOptions> { private var center: LatLng = options.center private var radius: Double = options.radius 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 override var annotation: Circle? = null internal val line: Markup<Line, LineOptions> = object : Markup<Line, LineOptions> { override var annotation: Line? = null override val annotationOptions: LineOptions get() = LineOptions() .withGeometry( LineString.fromLngLats( makeOutlineLatLngs() ) ).withLineWidth(strokeWidth / map.dpiFactor) .withLineColor(ColorUtils.colorToRgbaString(strokeColor)) .withLineOpacity(if (visible) 1f else 0f) override val removed: Boolean = false } override var annotation: Fill? = null override var removed: Boolean = false override val annotationOptions: CircleOptions get() = CircleOptions() .withLatLng(center.toMapbox()) .withCircleColor(ColorUtils.colorToRgbaString(fillColor)) .withCircleRadius(radius.toFloat()) .withCircleStrokeColor(ColorUtils.colorToRgbaString(strokeColor)) .withCircleStrokeWidth(strokeWidth / map.dpiFactor) .withCircleOpacity(if (visible) 1f else 0f) .withCircleStrokeOpacity(if (visible) 1f else 0f) override val annotationOptions: FillOptions get() = FillOptions() .withGeometry(makePolygon()) .withFillColor(ColorUtils.colorToRgbaString(fillColor)) .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 ) /** * Google's "map renderer is unable to draw the circle fill if * the circle encompasses either the North or South pole". */ private fun wrapsAroundPoles() = Point.fromLngLat(center.longitude, center.latitude).let { TurfMeasurement.distance( it, NORTH_POLE ) * 1000 < radius || TurfMeasurement.distance( it, SOUTH_POLE ) * 1000 < radius } private fun makeOutlineLatLngs() = TurfMeta.coordAll( makePolygon(), wrapsAroundPoles() ).let { // Circles around the poles are tricky to draw (https://github.com/mapbox/mapbox-gl-js/issues/11235). // 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) if (!centerPoint.equals(NORTH_POLE) && TurfMeasurement.distance(centerPoint, NORTH_POLE) * 1000 < radius) { // Wraps around North Pole for (i in 0 until it.size) { // We want to have the north-most points at the start and end if (it[0].latitude() > it[1].latitude() && it[it.size-1].latitude() > it[it.size-2].latitude()) { return@let it } else { // Cycle point list val zero = it.removeFirst() it.add(zero) } } } if (!centerPoint.equals(SOUTH_POLE) && TurfMeasurement.distance(centerPoint, SOUTH_POLE) * 1000 < radius) { // Wraps around South Pole for (i in 0 until it.size) { // We want to have the south-most points at the start and end if (it[0].latitude() < it[1].latitude() && it[it.size-1].latitude() < it[it.size-2].latitude()) { return@let it } else { // Cycle point list val last = it.removeAt(it.size - 1) it.add(0, last) } } } it } 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.circleManager?.let { update(it) } map.fillManager?.let { update(it) } } override fun getId(): String = id override fun setCenter(center: LatLng) { this.center = center annotation?.latLng = center.toMapbox() map.circleManager?.let { update(it) } updateLatLngs() map.fillManager?.let { update(it) } } override fun getCenter(): LatLng = center override fun setRadius(radius: Double) { this.radius = radius annotation?.circleRadius = radius.toFloat() map.circleManager?.let { update(it) } updateLatLngs() map.fillManager?.let { update(it) } } override fun getRadius(): Double = radius override fun setStrokeWidth(width: Float) { this.strokeWidth = width annotation?.circleStrokeWidth = width / map.dpiFactor map.circleManager?.let { update(it) } line.annotation?.lineWidth = width / map.dpiFactor map.lineManager?.let { line.update(it) } } override fun getStrokeWidth(): Float = strokeWidth override fun setStrokeColor(color: Int) { this.strokeColor = color annotation?.setCircleStrokeColor(color) map.circleManager?.let { update(it) } line.annotation?.setLineColor(color) map.lineManager?.let { line.update(it) } } override fun getStrokeColor(): Int = strokeColor override fun setFillColor(color: Int) { this.fillColor = color annotation?.setCircleColor(color) map.circleManager?.let { update(it) } annotation?.setFillColor(color) map.fillManager?.let { update(it) } } override fun getFillColor(): Int = fillColor Loading @@ -105,9 +206,8 @@ class CircleImpl(private val map: GoogleMapImpl, private val id: String, options override fun setVisible(visible: Boolean) { this.visible = visible annotation?.circleOpacity = if (visible) 1f else 0f annotation?.circleStrokeOpacity = if (visible) 1f else 0f map.circleManager?.let { update(it) } annotation?.fillOpacity = if (visible && !wrapsAroundPoles()) 1f else 0f map.fillManager?.let { update(it) } } override fun isVisible(): Boolean = visible Loading