Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Using Google Maps with Jetpack Compose

Luca Nicoletti
August 01, 2024
67

Using Google Maps with Jetpack Compose

Slides for my presentation @ AndroidWW July 24.

Luca Nicoletti

August 01, 2024
Tweet

Transcript

  1. Libraries Android Map Compose • GoogleMap • Control and con

    f iguration • Drawing • Markers & Marker Windows • Circles • Polygons/Polylines • Overlays Maps Compose Utility Library • Clustering
  2. Getting starter @Composable public fun GoogleMap( mergeDescendants: Boolean = false,

    modifier: Modifier = Modifier, cameraPositionState: CameraPositionState = rememberCameraPositionState(), contentDescription: String? = null, googleMapOptionsFactory: () -> GoogleMapOptions = { GoogleMapOptions() }, properties: MapProperties = DefaultMapProperties, locationSource: LocationSource? = null, uiSettings: MapUiSettings = DefaultMapUiSettings, indoorStateChangeListener: IndoorStateChangeListener = DefaultIndoorStateChangeListener, onMapClick: ((LatLng) -> Unit)? = null, onMapLongClick: ((LatLng) -> Unit)? = null, onMapLoaded: (() -> Unit)? = null, onMyLocationButtonClick: (() -> Boolean)? = null, onMyLocationClick: ((Location) -> Unit)? = null, onPOIClick: ((PointOfInterest) -> Unit)? = null, contentPadding: PaddingValues = NoPadding, content: (@Composable @GoogleMapComposable () -> Unit)? = null, )
  3. Getting starter @Composable public fun GoogleMap( mergeDescendants: Boolean = false,

    modifier: Modifier = Modifier, cameraPositionState: CameraPositionState = rememberCameraPositionState(), contentDescription: String? = null, googleMapOptionsFactory: () -> GoogleMapOptions = { GoogleMapOptions() }, properties: MapProperties = DefaultMapProperties, locationSource: LocationSource? = null, uiSettings: MapUiSettings = DefaultMapUiSettings, indoorStateChangeListener: IndoorStateChangeListener = DefaultIndoorStateChangeListener, onMapClick: ((LatLng) -> Unit)? = null, onMapLongClick: ((LatLng) -> Unit)? = null, onMapLoaded: (() -> Unit)? = null, onMyLocationButtonClick: (() -> Boolean)? = null, onMyLocationClick: ((Location) -> Unit)? = null, onPOIClick: ((PointOfInterest) -> Unit)? = null, contentPadding: PaddingValues = NoPadding, content: (@Composable @GoogleMapComposable () -> Unit)? = null, )
  4. Getting starter @Composable public fun GoogleMap( mergeDescendants: Boolean = false,

    modifier: Modifier = Modifier, cameraPositionState: CameraPositionState = rememberCameraPositionState(), contentDescription: String? = null, googleMapOptionsFactory: () -> GoogleMapOptions = { GoogleMapOptions() }, properties: MapProperties = DefaultMapProperties, locationSource: LocationSource? = null, uiSettings: MapUiSettings = DefaultMapUiSettings, indoorStateChangeListener: IndoorStateChangeListener = DefaultIndoorStateChangeListener, onMapClick: ((LatLng) -> Unit)? = null, onMapLongClick: ((LatLng) -> Unit)? = null, onMapLoaded: (() -> Unit)? = null, onMyLocationButtonClick: (() -> Boolean)? = null, onMyLocationClick: ((Location) -> Unit)? = null, onPOIClick: ((PointOfInterest) -> Unit)? = null, contentPadding: PaddingValues = NoPadding, content: (@Composable @GoogleMapComposable () -> Unit)? = null, )
  5. Getting starter public class MapProperties( public val isBuildingEnabled: Boolean =

    false, public val isIndoorEnabled: Boolean = false, public val isMyLocationEnabled: Boolean = false, public val isTrafficEnabled: Boolean = false, public val latLngBoundsForCameraTarget: LatLngBounds? = null, public val mapStyleOptions: MapStyleOptions? = null, public val mapType: MapType = MapType.NORMAL, public val maxZoomPreference: Float = 21.0f, public val minZoomPreference: Float = 3.0f, )
  6. Getting starter public class MapProperties( public val isBuildingEnabled: Boolean =

    false, public val isIndoorEnabled: Boolean = false, public val isMyLocationEnabled: Boolean = false, public val isTrafficEnabled: Boolean = false, public val latLngBoundsForCameraTarget: LatLngBounds? = null, public val mapStyleOptions: MapStyleOptions? = null, public val mapType: MapType = MapType.NORMAL, public val maxZoomPreference: Float = 21.0f, public val minZoomPreference: Float = 3.0f, )
  7. MapStyleOptions What can be customised • Administrative • Country •

    Province • Locality • Neighbourhood • Land parcel • Landscape • Human-made • Natural • Landcover • Terrain • Points of interest • Attraction • Business • Government • Medical • Park • Place of worship • School • Sports Complex • Road • Highway • Controlled access • Arterial • Local • Transit • Line • Station • Airport • Bus • Rail • Water
  8. MapStyleOptions • Geometry • Fill • Stroke • Labels •

    Text • Text f ill • Text outline • Icon
  9. Getting starter public class MapUiSettings( public val compassEnabled: Boolean =

    true, public val indoorLevelPickerEnabled: Boolean = true, public val mapToolbarEnabled: Boolean = true, public val myLocationButtonEnabled: Boolean = true, public val rotationGesturesEnabled: Boolean = true, public val scrollGesturesEnabled: Boolean = true, public val scrollGesturesEnabledDuringRotateOrZoom: Boolean = true, public val tiltGesturesEnabled: Boolean = true, public val zoomControlsEnabled: Boolean = true, public val zoomGesturesEnabled: Boolean = true, )
  10. Getting starter public class MapUiSettings( public val compassEnabled: Boolean =

    true, public val indoorLevelPickerEnabled: Boolean = true, public val mapToolbarEnabled: Boolean = true, public val myLocationButtonEnabled: Boolean = true, public val rotationGesturesEnabled: Boolean = true, public val scrollGesturesEnabled: Boolean = true, public val scrollGesturesEnabledDuringRotateOrZoom: Boolean = true, public val tiltGesturesEnabled: Boolean = true, public val zoomControlsEnabled: Boolean = true, public val zoomGesturesEnabled: Boolean = true, )
  11. Getting starter public class MapUiSettings( public val compassEnabled: Boolean =

    true, public val indoorLevelPickerEnabled: Boolean = true, public val mapToolbarEnabled: Boolean = true, public val myLocationButtonEnabled: Boolean = true, public val rotationGesturesEnabled: Boolean = true, public val scrollGesturesEnabled: Boolean = true, public val scrollGesturesEnabledDuringRotateOrZoom: Boolean = true, public val tiltGesturesEnabled: Boolean = true, public val zoomControlsEnabled: Boolean = true, public val zoomGesturesEnabled: Boolean = true, )
  12. Getting starter public class MapUiSettings( public val compassEnabled: Boolean =

    true, public val indoorLevelPickerEnabled: Boolean = true, public val mapToolbarEnabled: Boolean = true, public val myLocationButtonEnabled: Boolean = true, public val rotationGesturesEnabled: Boolean = true, public val scrollGesturesEnabled: Boolean = true, public val scrollGesturesEnabledDuringRotateOrZoom: Boolean = true, public val tiltGesturesEnabled: Boolean = true, public val zoomControlsEnabled: Boolean = true, public val zoomGesturesEnabled: Boolean = true, )
  13. Getting starter public class MapUiSettings( public val compassEnabled: Boolean =

    true, public val indoorLevelPickerEnabled: Boolean = true, public val mapToolbarEnabled: Boolean = true, public val myLocationButtonEnabled: Boolean = true, public val rotationGesturesEnabled: Boolean = true, public val scrollGesturesEnabled: Boolean = true, public val scrollGesturesEnabledDuringRotateOrZoom: Boolean = true, public val tiltGesturesEnabled: Boolean = true, public val zoomControlsEnabled: Boolean = true, public val zoomGesturesEnabled: Boolean = true, )
  14. Marker @Composable @GoogleMapComposable public fun Marker( contentDescription: String? = "",

    state: MarkerState = rememberMarkerState(), alpha: Float = 1.0f, anchor: Offset = Offset(0.5f, 1.0f), draggable: Boolean = false, flat: Boolean = false, icon: BitmapDescriptor? = null, infoWindowAnchor: Offset = Offset(0.5f, 0.0f), rotation: Float = 0.0f, snippet: String? = null, tag: Any? = null, title: String? = null, visible: Boolean = true, zIndex: Float = 0.0f, onClick: (Marker) -> Boolean = { false }, onInfoWindowClick: (Marker) -> Unit = {}, onInfoWindowClose: (Marker) -> Unit = {}, onInfoWindowLongClick: (Marker) -> Unit = {}, )
  15. Marker public static final float HUE_RED = 0.0F; public static

    final float HUE_ORANGE = 30.0F; public static final float HUE_YELLOW = 60.0F; public static final float HUE_GREEN = 120.0F; public static final float HUE_CYAN = 180.0F; public static final float HUE_AZURE = 210.0F; public static final float HUE_BLUE = 240.0F; public static final float HUE_VIOLET = 270.0F; public static final float HUE_MAGENTA = 300.0F; public static final float HUE_ROSE = 330.0F; Default Marker - HUE variations
  16. Marker @Composable @GoogleMapComposable public fun Marker( contentDescription: String? = "",

    state: MarkerState = rememberMarkerState(), alpha: Float = 1.0f, anchor: Offset = Offset(0.5f, 1.0f), draggable: Boolean = false, flat: Boolean = false, icon: BitmapDescriptor? = null, infoWindowAnchor: Offset = Offset(0.5f, 0.0f), rotation: Float = 0.0f, snippet: String? = null, tag: Any? = null, title: String? = null, visible: Boolean = true, zIndex: Float = 0.0f, onClick: (Marker) -> Boolean = { false }, onInfoWindowClick: (Marker) -> Unit = {}, onInfoWindowClose: (Marker) -> Unit = {}, onInfoWindowLongClick: (Marker) -> Unit = {}, )
  17. Marker private fun bitmapDescriptor( context: Context, vectorResId: Int ): BitmapDescriptor?

    { val drawable = ContextCompat.getDrawable(context, vectorResId) ?: return null drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) val bm = Bitmap.createBitmap( /* width = */ drawable.intrinsicWidth, /* height = */ drawable.intrinsicHeight, /* config = */ Bitmap.Config.ARGB_8888 ) // draw it onto the bitmap val canvas = android.graphics.Canvas(bm) drawable.draw(canvas) return BitmapDescriptorFactory.fromBitmap(bm) }
  18. Marker @Composable fun MapMarker( position: LatLng, @DrawableRes iconResourceId: Int )

    { val context = LocalContext.current val icon = remember { bitmapDescriptor(context, iconResourceId) } Marker( state = MarkerState(position = position), icon = icon, anchor = Offset(HALF_OFFSET, HALF_OFFSET) ) } private const val HALF_OFFSET = 0.5f
  19. Marker Info Window @Composable @GoogleMapComposable public fun MarkerInfoWindow( state: MarkerState

    = rememberMarkerState(), alpha: Float = 1.0f, anchor: Offset = Offset(0.5f, 1.0f), draggable: Boolean = false, flat: Boolean = false, icon: BitmapDescriptor? = null, infoWindowAnchor: Offset = Offset(0.5f, 0.0f), rotation: Float = 0.0f, snippet: String? = null, tag: Any? = null, title: String? = null, visible: Boolean = true, zIndex: Float = 0.0f, onClick: (Marker) -> Boolean = { false }, onInfoWindowClick: (Marker) -> Unit = {}, onInfoWindowClose: (Marker) -> Unit = {}, onInfoWindowLongClick: (Marker) -> Unit = {}, content: (@Composable (Marker) -> Unit)? = null )
  20. Marker Info Window MarkerInfoWindowContent( state = MarkerState(position = data.location), title

    = data.title, snippet = data.description, ) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth().padding(32.dp) ) { Text( modifier = Modifier.padding(top = 6.dp), text = data.title, fontWeight = FontWeight.Bold, color = Color.Black, ) data.description?.let { desc -> Text(desc) } data.imageResourceId?.let { res -> Image( modifier = Modifier.padding(top = 6.dp).size(240.dp), painter = painterResource(id = res), contentDescription = data.description ) } } }
  21. Cluster Item public interface ClusterItem { /** * The position

    of this marker. This must always return the same value. */ @NonNull LatLng getPosition(); /** * The title of this marker. */ @Nullable String getTitle(); /** * The description of this marker. */ @Nullable String getSnippet(); /** * The z-index of this marker. */ @Nullable Float getZIndex(); }
  22. Cluster Item data class MarkerData( val location: LatLng, val name:

    String, val description: String?, ) : ClusterItem { override fun getPosition(): LatLng = location override fun getTitle(): String? = name override fun getSnippet(): String? = description override fun getZIndex(): Float? = 1f }
  23. Clustering @Composable @GoogleMapComposable @MapsComposeExperimentalApi public fun <T : ClusterItem> Clustering(

    items: Collection<T>, onClusterClick: (Cluster<T>) -> Boolean = { false }, onClusterItemClick: (T) -> Boolean = { false }, onClusterItemInfoWindowClick: (T) -> Unit = { }, onClusterItemInfoWindowLongClick: (T) -> Unit = { }, clusterContent: @[UiComposable Composable] ((Cluster<T>) -> Unit)? = null, clusterItemContent: @[UiComposable Composable] ((T) -> Unit)? = null, )
  24. Clustering Clustering( items = markersData, clusterItemContent = { markerData ->

    val state = rememberMarkerState(position = markerData.position) Marker(state = state) }, )
  25. Clustering @Composable @GoogleMapComposable @MapsComposeExperimentalApi public fun <T : ClusterItem> Clustering(

    items: Collection<T>, onClusterClick: (Cluster<T>) -> Boolean = { false }, onClusterItemClick: (T) -> Boolean = { false }, onClusterItemInfoWindowClick: (T) -> Unit = { }, onClusterItemInfoWindowLongClick: (T) -> Unit = { }, clusterContent: @[UiComposable Composable] ((Cluster<T>) -> Unit)? = null, clusterItemContent: @[UiComposable Composable] ((T) -> Unit)? = null, )
  26. Clustering @OptIn(MapsComposeExperimentalApi::class) @Composable private fun <T : ClusterItem> rememberClusterManager( clusterContent:

    @Composable ((Cluster<T>) -> Unit)?, clusterItemContent: @Composable ((T) -> Unit)?, clusterRenderer: ClusterRenderer<T>? = null, )
  27. Clustering @Composable @GoogleMapComposable @MapsComposeExperimentalApi public fun <T : ClusterItem> rememberClusterRenderer(

    clusterContent: @Composable ((Cluster<T>) -> Unit)?, clusterItemContent: @Composable ((T) -> Unit)?, clusterManager: ClusterManager<T>?, )
  28. Clustering GoogleMap() {
 val clusterManager = rememberClusterManager<MarkerData>() clusterManager?.let { manager

    -> val renderer = rememberClusterRenderer( clusterContent = { IconAsClusterContent(it) }, clusterItemContent = { IconAsClusterContentItem(it) }, clusterManager = manager, )
 Clustering( items = positions.map { ClusterItemImpl(it) }, clusterManager = manager, ) }
 }
  29. Clustering GoogleMap() {
 val clusterManager = rememberClusterManager<MarkerData>() clusterManager?.let { manager

    -> val renderer = rememberClusterRenderer( clusterContent = { IconAsClusterContent(it) }, clusterItemContent = { IconAsClusterContentItem(it) }, clusterManager = manager, )
 SideEffect { if (manager.renderer != renderer) { manager.renderer = renderer ?: return@SideEffect } }
 Clustering( items = positions.map { ClusterItemImpl(it) }, clusterManager = manager, ) }
 }
  30. Clustering val nonHierarchicalViewBasedAlgorithm = NonHierarchicalViewBasedAlgorithm<ClusterItemImpl>( screenWidth.value.toInt(), screenHeight.value.toInt() )
 val distanceBasedAlgorithm

    = NonHierarchicalDistanceBasedAlgorithm<ClusterItemImpl>()
 .apply { setMaxDistanceBetweenClusteredItems(150) } manager.algorithm = distanceBasedAlgorithm
  31. Circles @Composable @GoogleMapComposable public fun Circle( center: LatLng, clickable: Boolean

    = false, fillColor: Color = Color.Transparent, radius: Double = 0.0, strokeColor: Color = Color.Black, strokePattern: List<PatternItem>? = null, strokeWidth: Float = 10f, tag: Any? = null, visible: Boolean = true, zIndex: Float = 0f, onClick: (Circle) -> Unit = {}, )
  32. Circles @Composable @GoogleMapComposable public fun Circle( center: LatLng, clickable: Boolean

    = false, fillColor: Color = Color.Transparent, radius: Double = 0.0, strokeColor: Color = Color.Black, strokePattern: List<PatternItem>? = null, strokeWidth: Float = 10f, tag: Any? = null, visible: Boolean = true, zIndex: Float = 0f, onClick: (Circle) -> Unit = {}, )
  33. Circles @Composable @GoogleMapComposable public fun Circle( center: LatLng, clickable: Boolean

    = false, fillColor: Color = Color.Transparent, radius: Double = 0.0, strokeColor: Color = Color.Black, strokePattern: List<PatternItem>? = null, strokeWidth: Float = 10f, tag: Any? = null, visible: Boolean = true, zIndex: Float = 0f, onClick: (Circle) -> Unit = {}, )
  34. Circles Circle( center = LatLng(51.510949, -0.086413), fillColor = Color.Red, radius

    = 250.0, strokeColor = Color.Blue, strokeWidth = 25f, strokePattern = listOf(Dash(200f), Gap(1f), Dot(), Gap(1f)), )
  35. Circles Circle( center = LatLng(51.510949, -0.086413), fillColor = Color.Blue.copy(alpha =

    0.5f), radius = 250.0, strokeColor = Color.Transparent, strokeWidth = 0f, )
  36. Polygons @Composable @GoogleMapComposable public fun Polygon( points: List<LatLng>, clickable: Boolean

    = false, fillColor: Color = Color.Black, geodesic: Boolean = false, holes: List<List<LatLng>> = emptyList(), strokeColor: Color = Color.Black, strokeJointType: Int = JointType.DEFAULT, strokePattern: List<PatternItem>? = null, strokeWidth: Float = 10f, tag: Any? = null, visible: Boolean = true, zIndex: Float = 0f, onClick: (Polygon) -> Unit = {} )
  37. Polygons private val pins = listOf( LatLng(51.51223, -0.08317), LatLng(51.517619, -0.082958),

    LatLng(51.525481, -0.087196), LatLng(51.510949, -0.086413), ) Polygon(points = pins)
  38. Polygons private val pins = listOf( LatLng(51.51223, -0.08317), LatLng(51.517619, -0.082958),

    LatLng(51.525481, -0.087196), LatLng(51.510949, -0.086413), ) Polygon( points = pins, fillColor = Color.Cyan, strokeWidth = 3f, )
  39. Polygons Polygon( points = pins, fillColor = Color.Cyan, strokeWidth =

    15f, strokeColor = Color.Gray, strokePattern = listOf(Dash(50f), Gap(15f), Dot(), Gap(15f)) )
  40. Polygons Polygon( points = pins, fillColor = Color.Cyan.copy(alpha = 0.4f),

    strokeWidth = 15f, strokeColor = Color.Gray, strokePattern = listOf(Dash(50f), Gap(15f), Dot(), Gap(15f)) )
  41. Polygons Polygon( points = outerPins, fillColor = Color.Cyan.copy(alpha = 0.4f),

    strokeWidth = 15f, strokeColor = Color.Gray, strokePattern = listOf(Dash(50f), Gap(15f), Dot(), Gap(15f)), holes = listOf(pins) )
  42. Polygons Polygon( points = outerPins, fillColor = Color.Cyan.copy(alpha = 0.4f),

    strokeWidth = 15f, strokeColor = Color.Gray, strokePattern = listOf(Dash(50f), Gap(15f), Dot(), Gap(15f)), holes = listOf(pins, pins1) )
  43. Polylines @Composable @GoogleMapComposable public fun Polyline( points: List<LatLng>, clickable: Boolean

    = false, color: Color = Color.Black, endCap: Cap = ButtCap(), geodesic: Boolean = false, jointType: Int = JointType.DEFAULT, pattern: List<PatternItem>? = null, startCap: Cap = ButtCap(), tag: Any? = null, visible: Boolean = true, width: Float = 10f, zIndex: Float = 0f, onClick: (Polyline) -> Unit = {} )
  44. Polylines Polyline( points = outerPins, color = Color.Magenta, startCap =

    RoundCap(), endCap = SquareCap(), pattern = listOf(Dash(20f), Gap(20f)), )
  45. Polylines Polyline( points = markersData.map { it.position }, spans =

    listOf( StyleSpan(Color.Red.toArgb()), StyleSpan(Color.Blue.toArgb()) ) )
  46. Polylines Polyline( points = markersData.map { it.position }, spans =

    listOf( StyleSpan( StrokeStyle.gradientBuilder( Color.Red.toArgb(), Color.Yellow.toArgb(), ).build() ) ), width = 15f, endCap = RoundCap(), startCap = RoundCap(), )
  47. GroundOverlay @Composable @GoogleMapComposable public fun GroundOverlay( position: GroundOverlayPosition, image: BitmapDescriptor,

    anchor: Offset = Offset(0.5f, 0.5f), bearing: Float = 0f, clickable: Boolean = false, tag: Any? = null, transparency: Float = 0f, visible: Boolean = true, zIndex: Float = 0f, onClick: (GroundOverlay) -> Unit = {}, )
  48. GroundOverlay public fun create(latLngBounds: LatLngBounds) : GroundOverlayPosition { return GroundOverlayPosition(latLngBounds

    = latLngBounds) } public fun create(location: LatLng, width: Float, height: Float? = null) : 
 GroundOverlayPosition { return GroundOverlayPosition( location = location, width = width, height = height )
 }
  49. GroundOverlay @Composable fun GroundOverlayUnderground() { val context = LocalContext.current GroundOverlay(

    position = GroundOverlayPosition.create( latLngBounds = LatLngBounds( /* southwest = */ LatLng(51.493684, -0.224643), /* northeast = */ LatLng(51.527323, -0.056233), ) ), anchor = Offset.Zero, image = drawableToBitmapDescriptor( context, R.drawable.overlay1 ), transparency = 0.5f, ) }
  50. GroundOverlay fun drawableToBitmapDescriptor(context: Context, drawableId: Int): BitmapDescriptor { val drawableResource:

    Drawable? = ContextCompat.getDrawable(context, drawableId) drawableResource?.let { drawable -> val width = drawable.intrinsicWidth val height = drawable.intrinsicHeight val bitmap: Bitmap = Bitmap.createBitmap( width, height, Bitmap.Config.ARGB_8888 ) val canvas = Canvas(bitmap) drawable.setBounds(0, 0, canvas.width, canvas.height) drawable.draw(canvas) canvas.scale(15f, 15f) return BitmapDescriptorFactory.fromBitmap(bitmap) } ?: run { throw IllegalArgumentException("Drawable not found") } }
  51. TileOverlay @Composable @GoogleMapComposable public fun TileOverlay( tileProvider: TileProvider, state: TileOverlayState

    = rememberTileOverlayState(), fadeIn: Boolean = true, transparency: Float = 0f, visible: Boolean = true, zIndex: Float = 0f, onClick: (TileOverlay) -> Unit = {}, )
  52. TileOverlay public interface TileProvider { @NonNull Tile NO_TILE = new

    Tile(-1, -1, (byte[])null); @Nullable Tile getTile(int var1, int var2, int var3); }
  53. TileOverlay class CoordTileProvider(context: Context) : TileProvider { private val scaleFactor:

    Float = context.resources.displayMetrics.density * 0.6f private val borderTile: Bitmap companion object { private const val TILE_SIZE_DP = 256 } init { val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG) borderPaint.style = Paint.Style.STROKE borderTile = Bitmap.createBitmap( (TILE_SIZE_DP * scaleFactor).toInt(), (TILE_SIZE_DP * scaleFactor).toInt(), Bitmap.Config.ARGB_8888 ) val canvas = Canvas(borderTile) canvas.drawRect( 0f, 0f, TILE_SIZE_DP * scaleFactor, TILE_SIZE_DP * scaleFactor, borderPaint ) } }
  54. TileOverlay override fun getTile(x: Int, y: Int, zoom: Int): Tile

    { val coordTile = drawTileCoords(x, y, zoom) val stream = ByteArrayOutputStream() coordTile!!.compress(Bitmap.CompressFormat.PNG, 0, stream) val bitmapData = stream.toByteArray() return Tile( (TILE_SIZE_DP * scaleFactor).toInt(), (TILE_SIZE_DP * scaleFactor).toInt(), bitmapData ) }
  55. TileOverlay private fun drawTileCoords(x: Int, y: Int, zoom: Int): Bitmap?

    { var copy: Bitmap? synchronized(borderTile) { 
 copy = borderTile.copy(Bitmap.Config.ARGB_8888, true) 
 } val canvas = Canvas(copy!!) val tileCoords = "($x, $y)" val zoomLevel = "zoom = $zoom" val mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG) mTextPaint.color = Color.Blue.toArgb() mTextPaint.textAlign = Paint.Align.CENTER mTextPaint.textSize = 18 * scaleFactor canvas.drawText( tileCoords, TILE_SIZE_DP * scaleFactor / 2, TILE_SIZE_DP * scaleFactor / 2, mTextPaint ) canvas.drawText( zoomLevel, TILE_SIZE_DP * scaleFactor / 2, TILE_SIZE_DP * scaleFactor * 2 / 3, mTextPaint ) return copy }
  56. TileOverlay • The tile at (0,0) is at the northwest

    corner of the map • At zoom level 0, the entire world is rendered in a single tile • At zoom level 1 the map will be rendered as a 2x2 grid of tiles • # tiles = (2 ^ zoom level) x (2 ^ zoom level)
  57. TileOverlay class MapTileProvider(tileSize: Int) : UrlTileProvider(tileSize, tileSize) { private val

    urlTemplate = "https://" + BuildConfig.TILE_WEB_URL + "/%d/%d/%d.jpg?key=" + BuildConfig.TILE_API_KEY override fun getTileUrl(x: Int, y: Int, zoom: Int): URL = URL(urlTemplate.format(zoom, x, y)) }