In the previous part we learned how to draw in Jetpack Compose using Canvas API. If you didn’t read it yet, here is the link
Free hand draw polygon in Google Maps Compose
We will learn how to create freehand draw on Google Maps in Jetpack Compose
agarasul.medium.com
Today we will learn how select area in google maps using free hand drawn polygon.
First of all we need add Google Maps dependencies.
implementation("com.google.maps.android:maps-compose:2.11.4") implementation("com.google.android.gms:play-services-maps:18.2.0")
I will not deep dive into this, you can read more about Maps Compose here:
Maps Compose Library | Maps SDK for Android | Google for Developers
Jetpack Compose is a declarative, native UI toolkit that simplifies and accelerates UI development. With Jetpack…
developers.google.com
Now let’s create our map composable
@Composable fun MyMap(points : List<Point>) { val losAngeles = LatLng(34.052235, -118.243683) val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(losAngeles, 10f) } GoogleMap( modifier = Modifier .fillMaxSize(), cameraPositionState = cameraPositionState ){ } }
There isn’t too much things to describe.
- cameraPositionState — used to move camera on the map
- points — we will use to draw polygon on the map and later we transform them into real world coordinates.
To transform points into real world coordinates we have class Projection, which has method
fromScreenLocation n in Google Maps SDK
We will use this function to transform our screen locations to geographic locations.
We can access to projection from cameraPositionState
@Composable fun MyMap(points : List<Point>) { val losAngeles = LatLng(34.052235, -118.243683) val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(losAngeles, 10f) } GoogleMap( modifier = Modifier .fillMaxSize(), cameraPositionState = cameraPositionState ){ cameraPositionState.projection?.let { projection -> } } }
We also need a state variable to store our transformed points
// We will store our geo points there. We will use them to draw polygon on map var geoPoints by remember { mutableStateOf(listOf<LatLng>()) }
Now let’s start to transform our coordinates using projection
cameraPositionState.projection?.let { projection -> geoPoints = points.map { projection.fromScreenLocation(it) } }
You can see, it’s super easy to use.
In order to show polygon on the map, we can use Polygon composable which provided by Maps Compose SDK. So now our code will look like this
@OptIn(MapsComposeExperimentalApi::class) @Composable fun MyMap(points: List<Point>) { val losAngeles = LatLng(34.052235, -118.243683) val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(losAngeles, 10f) } var geoPoints by remember { mutableStateOf(listOf<LatLng>()) } GoogleMap( modifier = Modifier .fillMaxSize(), cameraPositionState = cameraPositionState ) { cameraPositionState.projection?.let { projection -> geoPoints = points.map { projection.fromScreenLocation(it) } } if (geoPoints.isNotEmpty()) { Polygon( points = geoPoints, fillColor = Color.Black.copy(alpha = 0.4f) ) } } }
That’s all on map part. Now we should somehow pass points to our map composable. Let’s jump quickly to our MapDrawer composable.
First, we need add some variable to store our coordinates. Let’s modify our awaitEachGesture
function body and also, we need add callback which will be trigger when drawing finished.
@Composable | |
fun MapDrawer(onDrawingEnd : (List<Point>) -> Unit) { | |
var state by remember { mutableStateOf(MapPolygonState()) } | |
val brush = remember { SolidColor(Color.Black) } | |
val path = remember { Path() } | |
Canvas( | |
modifier = Modifier | |
.fillMaxSize() | |
.pointerInput(Unit) { | |
awaitEachGesture { | |
val screenPoints = mutableListOf<Point>() | |
awaitPointerEvent().changes | |
.first() | |
.also { changes -> | |
val position = changes.position | |
screenPoints.add(position.toPoint()) | |
state = state.copy( | |
currentPosition = position, | |
event = DrawerMotionEvent.down | |
) | |
} | |
do { | |
val event: PointerEvent = awaitPointerEvent() | |
event.changes.forEach { changes -> | |
val position = changes.position | |
screenPoints.add(position.toPoint()) | |
state = state.copy( | |
currentPosition = position, | |
event = DrawerMotionEvent.move | |
) | |
} | |
} while (event.changes.any { it.pressed }) | |
currentEvent.changes | |
.first() | |
.also { change -> | |
state = state.copy( | |
currentPosition = change.position, | |
event = DrawerMotionEvent.up | |
) | |
screenPoints.add(change.position.toPoint()) | |
} | |
onDrawingEnd.invoke(screenPoints) | |
} | |
}, | |
onDraw = { | |
when (state.event) { | |
DrawerMotionEvent.idle -> Unit | |
DrawerMotionEvent.up, DrawerMotionEvent.move -> path.lineTo( | |
state.currentPosition.x, | |
state.currentPosition.y | |
) | |
DrawerMotionEvent.down -> path.moveTo( | |
state.currentPosition.x, | |
state.currentPosition.y | |
) | |
} | |
drawPath( | |
path = path, | |
brush = brush, | |
style = Stroke(width = 5f) | |
) | |
} | |
) | |
} |
Now we should put these 2 composable together:
@Composable fun MapsDrawerPage() { var points by remember { mutableStateOf(listOf<Point>()) } Box(modifier = Modifier.fillMaxSize()) { MyMap(points = points) MapDrawer( onDrawingEnd = { points = it } ) } }
Let’s see how this looks:
Now the final part get bounds of this polygon. For this should use LatLngBounds.Builder class. Let’s do some modification
cameraPositionState.projection?.let { projection -> geoPoints = if (points.isEmpty()) { emptyList() } else { val boundsBuilder = LatLngBounds.Builder() points.map { val latLng = projection.fromScreenLocation( Point(it.x, it.y) ) boundsBuilder.include(latLng) latLng } } }
We added empty check because, boundsBuilder.build() will raise an exception if we it don’t have any locations.
Conclusion
Today learned how to draw polygon on Canvas and transform them into geographic coordinates using Maps Compose SDK.
Feel free to follow me on Twitter and don’t hesitate to ask questions related to Jetpack Compose.
Twitter: https://twitter.com/a_rasul98
Also check out my other post related to Jetpack Compose:
Rasul Aghakishiyev – Medium
Read writing from Rasul Aghakishiyev on Medium. Android Software Engineer. Interested in mobile development. In love…
agarasul.medium.com