Blog Infos
Author
Published
Topics
, , ,
Published

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.

  1. cameraPositionState — used to move camera on the map
  2. 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)
)
}
)
}
view raw MapDrawer.kt hosted with ❤ by GitHub

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

This article was previously published on proandroiddev.com

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Jetpack Compose: Drawing without pain and recomposition

This is a talk on recomposition in Jetpack Compose and the myths of too many calls it is followed by. I’ll briefly explain the reasons behind recompositions and why they are not as problematic as…
Watch Video

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jetpack Compose: Drawing without pain and recomposition

Vitalii Markus
Android Engineer
Flo Health Inc.

Jobs

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog

How to animate BottomSheet content using Jetpack Compose

Early this year I started a new pet project for listening to random radio…
READ MORE
Menu