Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.google.android.libraries.navigation.DisplayOptions
import com.google.android.libraries.navigation.ForceNightMode
import com.google.android.libraries.navigation.NavigationRoadStretchRenderingData
import com.google.android.libraries.navigation.NavigationTrafficData
import com.google.android.libraries.navigation.NavigationUpdatesOptions
import com.google.android.libraries.navigation.Navigator
import com.google.android.libraries.navigation.Navigator.AudioGuidance
import com.google.android.libraries.navigation.Navigator.TaskRemovedBehavior
Expand Down Expand Up @@ -857,10 +858,16 @@ object Convert {
}
}

fun convertNavInfo(navInfo: NavInfo): NavInfoDto {
fun convertNavInfo(
navInfo: NavInfo,
imageDescriptors: Map<String, ImageDescriptorDto?>,
): NavInfoDto {
return NavInfoDto(
currentStep = navInfo.currentStep?.let { convertNavInfoStepInfo(it) },
remainingSteps = navInfo.remainingSteps.map { convertNavInfoStepInfo(it) },
currentStep = navInfo.currentStep?.let { convertNavInfoStepInfo(it, imageDescriptors) },
remainingSteps =
navInfo.remainingSteps.mapIndexed { index, item ->
convertNavInfoStepInfo(item, imageDescriptors)
},
routeChanged = navInfo.routeChanged,
distanceToCurrentStepMeters = navInfo.distanceToCurrentStepMeters?.toLong(),
distanceToNextDestinationMeters = navInfo.distanceToNextDestinationMeters?.toLong(),
Expand All @@ -882,7 +889,15 @@ object Convert {
}
}

private fun convertNavInfoStepInfo(stepInfo: StepInfo): StepInfoDto {
fun convertManeuverToKey(maneuver: Int): String {
return "maneuver_$maneuver"
}

private fun convertNavInfoStepInfo(
stepInfo: StepInfo,
imageDescriptors: Map<String, ImageDescriptorDto?>,
): StepInfoDto {
val key = convertManeuverToKey(stepInfo.maneuver)
return StepInfoDto(
distanceFromPrevStepMeters = stepInfo.distanceFromPrevStepMeters?.toLong() ?: 0L,
timeFromPrevStepSeconds = stepInfo.timeFromPrevStepSeconds?.toLong() ?: 0L,
Expand All @@ -906,6 +921,7 @@ object Convert {
)
},
maneuver = convertManeuver(stepInfo.maneuver),
image = imageDescriptors[key],
)
}

Expand Down Expand Up @@ -1132,10 +1148,25 @@ object Convert {
registeredImage.imagePixelRatio,
registeredImage.width,
registeredImage.height,
registeredImageType(registeredImage.type),
)
} else {
// For default marker icon
ImageDescriptorDto()
ImageDescriptorDto(type = RegisteredImageTypeDto.REGULAR)
}
}

fun registeredImageType(type: RegisteredImageTypeDto): RegisteredImageType {
return when (type) {
RegisteredImageTypeDto.REGULAR -> RegisteredImageType.REGULAR
RegisteredImageTypeDto.MANEUVER_ICON -> RegisteredImageType.MANEUVER_ICON
}
}

fun registeredImageType(type: RegisteredImageType): RegisteredImageTypeDto {
return when (type) {
RegisteredImageType.REGULAR -> RegisteredImageTypeDto.REGULAR
RegisteredImageType.MANEUVER_ICON -> RegisteredImageTypeDto.MANEUVER_ICON
}
}

Expand All @@ -1148,4 +1179,14 @@ object Convert {
else -> TaskRemovedBehavior.CONTINUE_SERVICE
}
}

fun generatedStepImagesTypeDtoToGeneratesStepImages(
type: GeneratedStepImagesTypeDto?
): @NavigationUpdatesOptions.GeneratedStepImagesType Int {
return when (type) {
GeneratedStepImagesTypeDto.NONE -> NavigationUpdatesOptions.GeneratedStepImagesType.NONE
GeneratedStepImagesTypeDto.BITMAP -> NavigationUpdatesOptions.GeneratedStepImagesType.BITMAP
else -> NavigationUpdatesOptions.GeneratedStepImagesType.NONE
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@ class GoogleMapsImageRegistryMessageHandler(private val imageRegistry: ImageRegi
imageDescriptor.registeredImageId?.let { imageRegistry.unregisterImage(it) }
}

override fun clearRegisteredImages() {
imageRegistry.clearRegisteredImages()
override fun clearRegisteredImages(filter: RegisteredImageTypeDto?) {
imageRegistry.clearRegisteredImages(filter)
}

override fun getRegisteredImageData(imageDescriptor: ImageDescriptorDto): ByteArray? {
return imageRegistry.getRegisteredImageData(imageDescriptor)
}

override fun getRegisteredImages(): List<ImageDescriptorDto> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class GoogleMapsNavigationPlugin : FlutterPlugin, ActivityAware {
// Setup navigation session manager
val app = binding.applicationContext as Application
val navigationSessionEventApi = NavigationSessionEventApi(binding.binaryMessenger)
sessionManager = GoogleMapsNavigationSessionManager(navigationSessionEventApi, app)
sessionManager =
GoogleMapsNavigationSessionManager(navigationSessionEventApi, app, imageRegistry!!)

// Setup platform view factory and its method channel handlers
viewEventApi = ViewEventApi(binding.binaryMessenger)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import com.google.android.gms.maps.model.LatLng
import com.google.android.libraries.mapsplatform.turnbyturn.model.NavInfo
import com.google.android.libraries.mapsplatform.turnbyturn.model.StepInfo
import com.google.android.libraries.navigation.CustomRoutesOptions
import com.google.android.libraries.navigation.DisplayOptions
import com.google.android.libraries.navigation.GpsAvailabilityChangeEvent
import com.google.android.libraries.navigation.NavigationApi
import com.google.android.libraries.navigation.NavigationApi.NavigatorListener
import com.google.android.libraries.navigation.NavigationUpdatesOptions
import com.google.android.libraries.navigation.Navigator
import com.google.android.libraries.navigation.Navigator.TaskRemovedBehavior
import com.google.android.libraries.navigation.RoadSnappedLocationProvider
Expand All @@ -54,6 +56,7 @@ class GoogleMapsNavigationSessionManager
constructor(
private val navigationSessionEventApi: NavigationSessionEventApi,
private val application: Application,
private val imageRegistry: ImageRegistry,
) : DefaultLifecycleObserver {
companion object {
var navigationReadyListener: NavigationReadyListener? = null
Expand Down Expand Up @@ -704,11 +707,18 @@ constructor(
}

@Throws(FlutterError::class)
fun enableTurnByTurnNavigationEvents(numNextStepsToPreview: Int) {
fun enableTurnByTurnNavigationEvents(
numNextStepsToPreview: Int,
type: @NavigationUpdatesOptions.GeneratedStepImagesType Int,
) {
if (navInfoObserver == null) {
// Register the service centrally (if not already registered)
val success =
GoogleMapsNavigatorHolder.registerTurnByTurnService(application, numNextStepsToPreview)
GoogleMapsNavigatorHolder.registerTurnByTurnService(
application,
numNextStepsToPreview,
type,
)

if (!success) {
throw FlutterError(
Expand All @@ -717,9 +727,43 @@ constructor(
)
}

fun getManeuverIconImageDescriptor(maneuver: Int): ImageDescriptorDto? {
val registeredImage =
imageRegistry.findRegisteredImage(Convert.convertManeuverToKey(maneuver))
if (registeredImage == null) return null
return Convert.registeredImageToImageDescriptorDto(registeredImage)
}

fun getImageDescriptorForStepInfo(stepInfo: StepInfo): ImageDescriptorDto? {
val bitmap = stepInfo.maneuverBitmap ?: return null
val imageDescriptor = getManeuverIconImageDescriptor(stepInfo.maneuver)
if (imageDescriptor != null) {
return imageDescriptor
}
return imageRegistry.registerManeuverIcon(
Convert.convertManeuverToKey(stepInfo.maneuver),
bitmap,
bitmap.width.toDouble() / bitmap.height.toDouble(),
bitmap.width.toDouble(),
bitmap.height.toDouble(),
)
}

// Create observer for this session manager
navInfoObserver = Observer { navInfo ->
navigationSessionEventApi.onNavInfo(Convert.convertNavInfo(navInfo)) {}
// Map to store all the unique images for each maneuver
val imageDescriptors: MutableMap<String, ImageDescriptorDto?> = mutableMapOf()

(navInfo.remainingSteps + navInfo.currentStep).forEach {
val key = Convert.convertManeuverToKey(it.maneuver)
val existingImageDescriptor = imageDescriptors[key]
if (existingImageDescriptor == null) {
val imageDescriptor = getImageDescriptorForStepInfo(it)
imageDescriptors[key] = imageDescriptor
}
}

navigationSessionEventApi.onNavInfo(Convert.convertNavInfo(navInfo, imageDescriptors)) {}
}

// Add observer using observeForever (works without lifecycle owner)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,14 @@ class GoogleMapsNavigationSessionMessageHandler(
sessionManager.disableRoadSnappedLocationUpdates()
}

override fun enableTurnByTurnNavigationEvents(numNextStepsToPreview: Long?) {
sessionManager.enableTurnByTurnNavigationEvents(numNextStepsToPreview?.toInt() ?: Int.MAX_VALUE)
override fun enableTurnByTurnNavigationEvents(
numNextStepsToPreview: Long?,
type: GeneratedStepImagesTypeDto?,
) {
sessionManager.enableTurnByTurnNavigationEvents(
numNextStepsToPreview?.toInt() ?: Int.MAX_VALUE,
Convert.generatedStepImagesTypeDtoToGeneratesStepImages(type),
)
}

override fun disableTurnByTurnNavigationEvents() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ object GoogleMapsNavigatorHolder {
}

@Synchronized
fun registerTurnByTurnService(application: Application, numNextStepsToPreview: Int): Boolean {
fun registerTurnByTurnService(
application: Application,
numNextStepsToPreview: Int,
type: @NavigationUpdatesOptions.GeneratedStepImagesType Int,
): Boolean {
val nav = navigator ?: return false

if (!turnByTurnServiceRegistered) {
Expand All @@ -90,7 +94,7 @@ object GoogleMapsNavigatorHolder {
val options =
NavigationUpdatesOptions.builder()
.setNumNextStepsToPreview(numNextStepsToPreview)
.setGeneratedStepImagesType(GeneratedStepImagesType.NONE)
.setGeneratedStepImagesType(type)
.setDisplayMetrics(displayMetrics)
.build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import java.io.ByteArrayOutputStream

class ImageRegistry {
val registeredImages = mutableListOf<RegisteredImage>()
Expand All @@ -32,7 +33,14 @@ class ImageRegistry {
fun mapViewInitializationComplete() {
isMapViewInitialized = true
bitmapQueue.forEach {
addRegisteredImage(it.imageId, it.bitmap, it.imagePixelRatio, it.width, it.height)
addRegisteredImage(
it.imageId,
it.bitmap,
it.imagePixelRatio,
it.width,
it.height,
RegisteredImageTypeDto.REGULAR,
)
}
bitmapQueue.clear()
}
Expand All @@ -53,9 +61,46 @@ class ImageRegistry {
// Add to queue to make it later.
bitmapQueue.add(QueuedBitmap(imageId, bitmap, imagePixelRatio, width, height))
} else {
addRegisteredImage(imageId, bitmap, imagePixelRatio, width, height)
addRegisteredImage(
imageId,
bitmap,
imagePixelRatio,
width,
height,
RegisteredImageTypeDto.REGULAR,
)
}
return ImageDescriptorDto(imageId, imagePixelRatio, width, height)
return ImageDescriptorDto(
imageId,
imagePixelRatio,
width,
height,
RegisteredImageTypeDto.REGULAR,
)
}

fun registerManeuverIcon(
imageId: String,
bitmap: Bitmap,
imagePixelRatio: Double,
width: Double?,
height: Double?,
): ImageDescriptorDto {
addRegisteredImage(
imageId,
bitmap,
imagePixelRatio,
width,
height,
RegisteredImageTypeDto.MANEUVER_ICON,
)
return ImageDescriptorDto(
imageId,
imagePixelRatio,
width,
height,
RegisteredImageTypeDto.MANEUVER_ICON,
)
}

private fun addRegisteredImage(
Expand All @@ -64,9 +109,20 @@ class ImageRegistry {
imagePixelRatio: Double,
width: Double?,
height: Double?,
type: RegisteredImageTypeDto,
) {
val imageType = Convert.registeredImageType(type)
val bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(bitmap)
val registeredImage = RegisteredImage(imageId, bitmapDescriptor, imagePixelRatio, width, height)
val registeredImage =
RegisteredImage(
imageId,
bitmapDescriptor,
imagePixelRatio,
width,
height,
imageType,
if (imageType == RegisteredImageType.MANEUVER_ICON) bitmap else null,
)
registeredImages.add(registeredImage)
}

Expand Down Expand Up @@ -117,7 +173,26 @@ class ImageRegistry {
registeredImages.removeAll { it.imageId == imageId }
}

fun clearRegisteredImages() {
registeredImages.clear()
fun clearRegisteredImages(filter: RegisteredImageTypeDto?) {
if (filter == null) {
registeredImages.clear()
return
}
registeredImages.removeAll { Convert.registeredImageType(filter) == it.type }
}

private fun convertBitmapToPNGByteArray(bitmap: Bitmap): ByteArray {
return ByteArrayOutputStream().use { stream ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
stream.toByteArray()
}
}

fun getRegisteredImageData(imageDescriptor: ImageDescriptorDto): ByteArray? {
return imageDescriptor.registeredImageId?.let {
findRegisteredImage(imageDescriptor.registeredImageId)?.maneuverIconBitmap?.let {
convertBitmapToPNGByteArray(it)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.maps.flutter.navigation

import android.graphics.Bitmap
import com.google.android.gms.maps.model.BitmapDescriptor

data class RegisteredImage(
Expand All @@ -24,4 +25,13 @@ data class RegisteredImage(
val imagePixelRatio: Double,
val width: Double?,
val height: Double?,
val type: RegisteredImageType,
val maneuverIconBitmap: Bitmap?,
)

enum class RegisteredImageType(val raw: Int) {
/** Default type used when custom bitmaps are uploaded to registry */
REGULAR(0),
/** Maneuver icon generated from NavInfo data */
MANEUVER_ICON(1),
}
Loading
Loading