Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions android/src/main/java/com/rngooglemapsplus/GoogleMapsViewImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ import com.google.android.gms.maps.model.TileOverlayOptions
import com.google.maps.android.data.kml.KmlLayer
import com.margelo.nitro.core.Promise
import com.rngooglemapsplus.extensions.encode
import com.rngooglemapsplus.extensions.onUi
import com.rngooglemapsplus.extensions.onUiSync
import com.rngooglemapsplus.extensions.toGooglePriority
import com.rngooglemapsplus.extensions.toLatLng
import com.rngooglemapsplus.extensions.toLocationErrorCode
Expand All @@ -53,7 +51,6 @@ import com.rngooglemapsplus.extensions.toRnCamera
import com.rngooglemapsplus.extensions.toRnLatLng
import com.rngooglemapsplus.extensions.toRnLocation
import com.rngooglemapsplus.extensions.toRnRegion
import com.rngooglemapsplus.extensions.withPaddingPixels
import idTag
import tagData
import java.io.ByteArrayInputStream
Expand Down Expand Up @@ -451,27 +448,25 @@ class GoogleMapsViewImpl(
) = onUi {
if (coordinates.isEmpty()) return@onUi

val w = mapView?.width ?: 0
val h = mapView?.height ?: 0
val bounds =
LatLngBounds
.builder()
.apply {
coordinates.forEach { include(it.toLatLng()) }
}.build()

val builder = LatLngBounds.builder()
coordinates.forEach { coord -> builder.include(coord.toLatLng()) }
val previousMapPadding = mapPadding
mapPadding = padding

val baseBounds = builder.build()
val paddedBounds = baseBounds.withPaddingPixels(w, h, padding)

val adjustedWidth =
(w - padding.left.dpToPx() - padding.right.dpToPx()).toInt().coerceAtLeast(0)
val adjustedHeight =
(h - padding.top.dpToPx() - padding.bottom.dpToPx()).toInt().coerceAtLeast(0)

val update = CameraUpdateFactory.newLatLngBounds(paddedBounds, adjustedWidth, adjustedHeight, 0)
val update = CameraUpdateFactory.newLatLngBounds(bounds, 0)

if (animated) {
googleMap?.animateCamera(update, durationMs, null)
} else {
googleMap?.moveCamera(update)
}

mapPadding = previousMapPadding
}

fun setCameraBounds(bounds: LatLngBounds?) =
Expand Down Expand Up @@ -752,7 +747,7 @@ class GoogleMapsViewImpl(
kmlLayersById[id] = layer
layer.addLayerToMap()
} catch (_: Exception) {
// ignore
mapsLog("kml layer parse failed: id=$id")
}
}

Expand Down Expand Up @@ -837,9 +832,15 @@ class GoogleMapsViewImpl(
setOnMyLocationClickListener(null)
setOnMyLocationButtonClickListener(null)
setInfoWindowAdapter(null)
isTrafficEnabled = false
isIndoorEnabled = false
myLocationEnabled = false
setLocationSource(null)
setLatLngBoundsForCameraTarget(null)
}
googleMap = null
mapView?.removeAllViews()
mapView = null
super.removeAllViews()
reactContext.unregisterComponentCallbacks(componentCallbacks)
}
Expand Down Expand Up @@ -968,5 +969,5 @@ class GoogleMapsViewImpl(

override fun getInfoContents(marker: Marker): View? = null

override fun getInfoWindow(marker: Marker): View? = markerBuilder.buildInfoWindow(marker.tagData.iconSvg)
override fun getInfoWindow(marker: Marker): View? = markerBuilder.buildInfoWindow(marker.tagData)
}
4 changes: 2 additions & 2 deletions android/src/main/java/com/rngooglemapsplus/LocationHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ class LocationHandler(
}

fun showLocationDialog() {
UiThreadUtil.runOnUiThread {
val activity = context.currentActivity ?: run { return@runOnUiThread }
onUi {
val activity = context.currentActivity ?: run { return@onUi }

val lr =
if (Build.VERSION.SDK_INT >= 31) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.google.android.gms.maps.model.Circle
import com.google.android.gms.maps.model.CircleOptions
import com.rngooglemapsplus.extensions.centerEquals
import com.rngooglemapsplus.extensions.onUi
import com.rngooglemapsplus.extensions.toColor
import com.rngooglemapsplus.extensions.toLatLng

Expand Down
15 changes: 14 additions & 1 deletion android/src/main/java/com/rngooglemapsplus/MapHelper.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.rngooglemapsplus.extensions
package com.rngooglemapsplus

import com.facebook.react.bridge.UiThreadUtil
import kotlinx.coroutines.CompletableDeferred
Expand All @@ -20,3 +20,16 @@ inline fun <T> onUiSync(crossinline block: () -> T): T {
}
return runBlocking { result.await() }
}

private const val MAPS_LOG_TAG = "react-native-google-maps-plus"

fun mapsLog(msg: String) {
android.util.Log.w(MAPS_LOG_TAG, msg)
}

fun mapsLog(
msg: String,
t: Throwable,
) {
android.util.Log.w(MAPS_LOG_TAG, msg, t)
}
146 changes: 102 additions & 44 deletions android/src/main/java/com/rngooglemapsplus/MapMarkerBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.widget.LinearLayout
import androidx.core.graphics.createBitmap
import com.caverock.androidsvg.SVG
import com.caverock.androidsvg.SVGExternalFileResolver
import com.caverock.androidsvg.SVGParseException
import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.facebook.react.uimanager.ThemedReactContext
import com.google.android.gms.maps.model.BitmapDescriptor
Expand All @@ -24,21 +25,21 @@ import com.rngooglemapsplus.extensions.coordinatesEquals
import com.rngooglemapsplus.extensions.infoWindowAnchorEquals
import com.rngooglemapsplus.extensions.markerInfoWindowStyleEquals
import com.rngooglemapsplus.extensions.markerStyleEquals
import com.rngooglemapsplus.extensions.onUi
import com.rngooglemapsplus.extensions.styleHash
import com.rngooglemapsplus.extensions.toLatLng
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLDecoder
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.coroutineContext
import kotlin.coroutines.cancellation.CancellationException

class MapMarkerBuilder(
val context: ThemedReactContext,
Expand Down Expand Up @@ -117,6 +118,8 @@ class MapMarkerBuilder(

else -> null
}
}.onFailure {
mapsLog("external svg resolve failed")
}.getOrNull()
}

Expand All @@ -140,7 +143,7 @@ class MapMarkerBuilder(
try {
return Typeface.createFromAsset(assetManager, path)
} catch (_: Throwable) {
// / ignore
mapsLog("font resolve failed: $path")
}
}

Expand Down Expand Up @@ -264,32 +267,40 @@ class MapMarkerBuilder(
scope.launch {
try {
ensureActive()
val bmp = renderBitmap(m)
val renderResult = renderBitmap(m.iconSvg, m.id)

if (bmp == null) {
withContext(Dispatchers.Main) { onReady(null) }
if (renderResult?.bitmap == null) {
withContext(Dispatchers.Main) {
ensureActive()
onReady(createFallbackDescriptor())
}
return@launch
}

ensureActive()
val desc = BitmapDescriptorFactory.fromBitmap(bmp)
val desc = BitmapDescriptorFactory.fromBitmap(renderResult.bitmap)

iconCache.put(key, desc)
bmp.recycle()
if (!renderResult.isFallback) {
iconCache.put(key, desc)
}
renderResult.bitmap.recycle()

withContext(Dispatchers.Main) {
ensureActive()
onReady(desc)
}
} catch (_: OutOfMemoryError) {
mapsLog("markerId=${m.id} buildIconAsync out of memory")
clearIconCache()
withContext(Dispatchers.Main) {
ensureActive()
onReady(null)
onReady(createFallbackDescriptor())
}
} catch (_: Throwable) {
mapsLog("markerId=${m.id} buildIconAsync failed")
withContext(Dispatchers.Main) {
ensureActive()
onReady(null)
onReady(createFallbackDescriptor())
}
} finally {
jobsById.remove(m.id)
Expand Down Expand Up @@ -317,8 +328,22 @@ class MapMarkerBuilder(
iconCache.evictAll()
}

fun buildInfoWindow(iconSvg: RNMarkerSvg?): ImageView? {
val iconSvg = iconSvg ?: return null
fun buildInfoWindow(markerTag: MarkerTag): ImageView? {
val iconSvg = markerTag.iconSvg ?: return null

val wPx =
markerTag.iconSvg.width
.dpToPx()
.toInt()
val hPx =
markerTag.iconSvg.height
.dpToPx()
.toInt()

if (wPx <= 0 || hPx <= 0) {
mapsLog("markerId=${markerTag.id} invalid svg size")
return ImageView(context)
}

val svgView =
ImageView(context).apply {
Expand All @@ -330,40 +355,73 @@ class MapMarkerBuilder(
}

try {
val svg = SVG.getFromString(iconSvg.svgString)
svg.setDocumentWidth(iconSvg.width.dpToPx())
svg.setDocumentHeight(iconSvg.height.dpToPx())
val svg =
SVG.getFromString(iconSvg.svgString).apply {
documentWidth = wPx.toFloat()
documentHeight = hPx.toFloat()
}
val drawable = PictureDrawable(svg.renderToPicture())
svgView.setImageDrawable(drawable)
} catch (e: Exception) {
return null
} catch (_: Exception) {
mapsLog("markerId=${markerTag.id} infoWindow: svg render failed")
return ImageView(context)
}

return svgView
}

private suspend fun renderBitmap(m: RNMarker): Bitmap? {
m.iconSvg ?: return null
private fun createFallbackBitmap(): Bitmap =
createBitmap(1, 1, Bitmap.Config.ARGB_8888).apply {
setHasAlpha(true)
}

private fun createFallbackDescriptor(): BitmapDescriptor {
val bmp = createFallbackBitmap()
return BitmapDescriptorFactory.fromBitmap(bmp).also {
bmp.recycle()
}
}

private data class RenderBitmapResult(
val bitmap: Bitmap,
val isFallback: Boolean,
)

private suspend fun renderBitmap(
iconSvg: RNMarkerSvg,
markerId: String,
): RenderBitmapResult? {
val wPx =
iconSvg.width
.dpToPx()
.toInt()
val hPx =
iconSvg.height
.dpToPx()
.toInt()

if (wPx <= 0 || hPx <= 0) {
mapsLog("markerId=$markerId invalid svg size")
return RenderBitmapResult(createFallbackBitmap(), true)
}

var bmp: Bitmap? = null
try {
coroutineContext.ensureActive()
val svg = SVG.getFromString(m.iconSvg.svgString)

val wPx =
m.iconSvg.width
.dpToPx()
.toInt()
val hPx =
m.iconSvg.height
.dpToPx()
.toInt()

coroutineContext.ensureActive()
svg.setDocumentWidth(wPx.toFloat())
svg.setDocumentHeight(hPx.toFloat())

coroutineContext.ensureActive()
val svg =
try {
SVG.getFromString(iconSvg.svgString).apply {
documentWidth = wPx.toFloat()
documentHeight = hPx.toFloat()
}
} catch (_: SVGParseException) {
mapsLog("markerId=$markerId icon: svg parse failed")
return RenderBitmapResult(createFallbackBitmap(), true)
} catch (_: IllegalArgumentException) {
mapsLog("markerId=$markerId icon: svg invalid")
return RenderBitmapResult(createFallbackBitmap(), true)
}

currentCoroutineContext().ensureActive()
bmp =
createBitmap(wPx, hPx, Bitmap.Config.ARGB_8888).apply {
density = context.resources.displayMetrics.densityDpi
Expand All @@ -372,13 +430,13 @@ class MapMarkerBuilder(
}
}

return bmp
} catch (t: Throwable) {
try {
bmp?.recycle()
} catch (_: Throwable) {
}
throw t
currentCoroutineContext().ensureActive()

return RenderBitmapResult(bmp, false)
} catch (e: Exception) {
if (e is CancellationException) throw e
bmp?.recycle()
throw e
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.google.android.gms.maps.model.Polygon
import com.google.android.gms.maps.model.PolygonOptions
import com.rngooglemapsplus.extensions.coordinatesEquals
import com.rngooglemapsplus.extensions.holesEquals
import com.rngooglemapsplus.extensions.onUi
import com.rngooglemapsplus.extensions.toColor
import com.rngooglemapsplus.extensions.toLatLng
import com.rngooglemapsplus.extensions.toMapsPolygonHoles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.facebook.react.uimanager.PixelUtil.dpToPx
import com.google.android.gms.maps.model.Polyline
import com.google.android.gms.maps.model.PolylineOptions
import com.rngooglemapsplus.extensions.coordinatesEquals
import com.rngooglemapsplus.extensions.onUi
import com.rngooglemapsplus.extensions.toColor
import com.rngooglemapsplus.extensions.toLatLng
import com.rngooglemapsplus.extensions.toMapJointType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class MapUrlTileOverlayBuilder {
return try {
URL(url)
} catch (e: Exception) {
mapsLog("tile url invalid: $url", e)
null
}
}
Expand Down
Loading
Loading