Slide 1

Slide 1 text

Google Map © Luca Nicoletti 2024 Drawing on it with Jetpack Compose

Slide 2

Slide 2 text

Luca Nicoletti Luca Nicoletti @luca_nicolett

Slide 3

Slide 3 text

Google Map © Luca Nicoletti 2024 Drawing on it with Jetpack Compose

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Libraries Android Map Compose • GoogleMap • Control and con f iguration • Drawing • Markers & Marker Windows • Circles • Polygons/Polylines • Overlays Maps Compose Utility Library • Clustering

Slide 6

Slide 6 text

Additional one Maps Compose Widgets • ScaleBar • DisappearingScaleBar

Slide 7

Slide 7 text

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, )

Slide 8

Slide 8 text

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, )

Slide 9

Slide 9 text

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, )

Slide 10

Slide 10 text

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, )

Slide 11

Slide 11 text

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, )

Slide 12

Slide 12 text

Styling Wizard https://mapstyle.withgoogle.com/

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

MapStyleOptions • Geometry • Fill • Stroke • Labels • Text • Text f ill • Text outline • Icon

Slide 15

Slide 15 text

Getting starter mapStyleOptions = MapStyleOptions.loadRawResourceStyle( LocalContext.current, R.raw.map_style )

Slide 16

Slide 16 text

Getting starter GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState, googleMapOptionsFactory = { GoogleMapOptions().mapId(“MyMapId") } )

Slide 17

Slide 17 text

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, )

Slide 18

Slide 18 text

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, )

Slide 19

Slide 19 text

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, )

Slide 20

Slide 20 text

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, )

Slide 21

Slide 21 text

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, )

Slide 22

Slide 22 text

Markers © Luca Nicoletti 2024 And Clusters

Slide 23

Slide 23 text

Marker

Slide 24

Slide 24 text

Markers • Standard icon • Different hues • Custom icon • Custom info window

Slide 25

Slide 25 text

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 = {}, )

Slide 26

Slide 26 text

Marker Marker( icon = BitmapDescriptorFactory .defaultMarker(HUE) ) Default Marker - HUE variations

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Marker

Slide 29

Slide 29 text

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 = {}, )

Slide 30

Slide 30 text

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) }

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Marker

Slide 33

Slide 33 text

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 )

Slide 34

Slide 34 text

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 ) } } }

Slide 35

Slide 35 text

Marker Info Window

Slide 36

Slide 36 text

Clusters • Group markers by distance • Tap handler(s) • Full control of the graphics

Slide 37

Slide 37 text

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(); }

Slide 38

Slide 38 text

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 }

Slide 39

Slide 39 text

Clustering @Composable @GoogleMapComposable @MapsComposeExperimentalApi public fun Clustering( items: Collection, onClusterClick: (Cluster) -> Boolean = { false }, onClusterItemClick: (T) -> Boolean = { false }, onClusterItemInfoWindowClick: (T) -> Unit = { }, onClusterItemInfoWindowLongClick: (T) -> Unit = { }, clusterContent: @[UiComposable Composable] ((Cluster) -> Unit)? = null, clusterItemContent: @[UiComposable Composable] ((T) -> Unit)? = null, )

Slide 40

Slide 40 text

Clustering

Slide 41

Slide 41 text

Clustering

Slide 42

Slide 42 text

Clustering Clustering( items = markersData, clusterItemContent = { markerData -> val state = rememberMarkerState(position = markerData.position) Marker(state = state) }, )

Slide 43

Slide 43 text

Clustering java.lang.IllegalStateException: Invalid applier at androidx.compose.runtime.ComposablesKt.invalidApplier(Composables.kt:472) at com.google.maps.android.compose.MarkerKt.MarkerImpl-khPtz74(Marker.kt:728) at com.google.maps.android.compose.MarkerKt.Marker-qld6geY(Marker.kt:178)

Slide 44

Slide 44 text

Clustering @Composable @GoogleMapComposable @MapsComposeExperimentalApi public fun Clustering( items: Collection, onClusterClick: (Cluster) -> Boolean = { false }, onClusterItemClick: (T) -> Boolean = { false }, onClusterItemInfoWindowClick: (T) -> Unit = { }, onClusterItemInfoWindowLongClick: (T) -> Unit = { }, clusterContent: @[UiComposable Composable] ((Cluster) -> Unit)? = null, clusterItemContent: @[UiComposable Composable] ((T) -> Unit)? = null, )

Slide 45

Slide 45 text

Clustering @Composable @GoogleMapComposable public fun Marker( […]
 )

Slide 46

Slide 46 text

Shapes © Luca Nicoletti 2024

Slide 47

Slide 47 text

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? = null, strokeWidth: Float = 10f, tag: Any? = null, visible: Boolean = true, zIndex: Float = 0f, onClick: (Circle) -> Unit = {}, )

Slide 48

Slide 48 text

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? = null, strokeWidth: Float = 10f, tag: Any? = null, visible: Boolean = true, zIndex: Float = 0f, onClick: (Circle) -> Unit = {}, )

Slide 49

Slide 49 text

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? = null, strokeWidth: Float = 10f, tag: Any? = null, visible: Boolean = true, zIndex: Float = 0f, onClick: (Circle) -> Unit = {}, )

Slide 50

Slide 50 text

Circles https://github.com/googlemaps/android-maps-compose/issues/531

Slide 51

Slide 51 text

Circles

Slide 52

Slide 52 text

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)), )

Slide 53

Slide 53 text

Circles

Slide 54

Slide 54 text

Circles Circle( center = LatLng(51.510949, -0.086413), fillColor = Color.Blue.copy(alpha = 0.5f), radius = 250.0, strokeColor = Color.Transparent, strokeWidth = 0f, )

Slide 55

Slide 55 text

Circles

Slide 56

Slide 56 text

Polygons @Composable @GoogleMapComposable public fun Polygon( points: List, clickable: Boolean = false, fillColor: Color = Color.Black, geodesic: Boolean = false, holes: List> = emptyList(), strokeColor: Color = Color.Black, strokeJointType: Int = JointType.DEFAULT, strokePattern: List? = null, strokeWidth: Float = 10f, tag: Any? = null, visible: Boolean = true, zIndex: Float = 0f, onClick: (Polygon) -> Unit = {} )

Slide 57

Slide 57 text

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)

Slide 58

Slide 58 text

Polygons

Slide 59

Slide 59 text

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, )

Slide 60

Slide 60 text

Polygons

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

Polygons

Slide 63

Slide 63 text

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)) )

Slide 64

Slide 64 text

Polygons

Slide 65

Slide 65 text

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) )

Slide 66

Slide 66 text

Polygons

Slide 67

Slide 67 text

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) )

Slide 68

Slide 68 text

Polygons

Slide 69

Slide 69 text

Polygons

Slide 70

Slide 70 text

Polylines @Composable @GoogleMapComposable public fun Polyline( points: List, clickable: Boolean = false, color: Color = Color.Black, endCap: Cap = ButtCap(), geodesic: Boolean = false, jointType: Int = JointType.DEFAULT, pattern: List? = null, startCap: Cap = ButtCap(), tag: Any? = null, visible: Boolean = true, width: Float = 10f, zIndex: Float = 0f, onClick: (Polyline) -> Unit = {} )

Slide 71

Slide 71 text

Polylines Polyline(points = outerPins)

Slide 72

Slide 72 text

Polylines

Slide 73

Slide 73 text

Polylines Polyline( points = outerPins, color = Color.Magenta, startCap = RoundCap(), endCap = SquareCap(), pattern = listOf(Dash(20f), Gap(20f)), )

Slide 74

Slide 74 text

Polylines

Slide 75

Slide 75 text

Polylines

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

Polylines

Slide 78

Slide 78 text

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(), )

Slide 79

Slide 79 text

Polylines

Slide 80

Slide 80 text

Overlays © Luca Nicoletti 2024

Slide 81

Slide 81 text

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 = {}, )

Slide 82

Slide 82 text

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 )
 }

Slide 83

Slide 83 text

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, ) }

Slide 84

Slide 84 text

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") } }

Slide 85

Slide 85 text

GroundOverlay

Slide 86

Slide 86 text

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 = {}, )

Slide 87

Slide 87 text

TileOverlay public interface TileProvider { @NonNull Tile NO_TILE = new Tile(-1, -1, (byte[])null); @Nullable Tile getTile(int var1, int var2, int var3); }

Slide 88

Slide 88 text

Tiles

Slide 89

Slide 89 text

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 ) } }

Slide 90

Slide 90 text

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 ) }

Slide 91

Slide 91 text

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 }

Slide 92

Slide 92 text

TileOverlay

Slide 93

Slide 93 text

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)

Slide 94

Slide 94 text

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)) }

Slide 95

Slide 95 text

URLTileOverlay Using a service • https://www.strasis.com/documentation/limelight-xe/reference/tile-map- servers • https://www.maptiler.com/

Slide 96

Slide 96 text

TileOverlay

Slide 97

Slide 97 text

Multiple things GoogleMap(){ Circle([…]) Polyline([…]) Polygon([…]) GroundOverlay([…]) TileOverlay([…]) […] }

Slide 98

Slide 98 text

Multiple things

Slide 99

Slide 99 text

Questions?

Slide 100

Slide 100 text

Thanks! © Luca Nicoletti 2024