Reverse Geocoding: Designing Map Gesture Events in Kotlin and Android
Learn more about how to design map gesture events within an Android app.
Join the DZone community and get the full member experience.
Join For FreeOver the past few weeks, I’ve been writing tutorials around Kotlin and Android that make use of the various HERE APIs. Remember, Java isn’t the only way to develop for Android, and it may not be the most popular in the near future.
In the last tutorial, we saw how to geocode addresses and display them on a map using Kotlin. This is great for showing markers or other points of interest, but what if we want to know an address based on where the user taps on the map? How do you even determine coordinate information from a tap event on a screen?
We’re going to see how to calculate addresses from where a user taps within an Android application using the HERE Android SDK and the Kotlin programming language.
To get a more realistic idea of what we’re going to accomplish, take the following animated image.
Doing a long-press event will drop a marker on the map after converting the pixel location to latitude and longitude coordinates. Doing a standard tap event on the marker will reverse geocode the coordinates and display an associated nearest address.
Going into this tutorial, the assumption is that you already have the HERE Android SDK configured in your Kotlin application. If you don’t and need help, check out my previous tutorial titled, Getting Started with HERE using Kotlin and the Android SDK.
Calculating Latitude and Longitude Coordinates From Pixel Locations
Before we try to drop any markers or reverse geocode our coordinates, we first need to calculate those coordinates. We can actually make use of the OnGestureListenerAdapter
to help us when it comes to gesture events and gathering the positions of those interactions.
Take the following code for example:
class MainActivity : AppCompatActivity() {
private lateinit var map : Map
private lateinit var mapFragment : SupportMapFragment
private lateinit var marker : MapMarker
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mapFragment = getSupportFragmentManager().findFragmentById(R.id.mapfragment) as SupportMapFragment
mapFragment.init { error ->
if (error == OnEngineInitListener.Error.NONE) {
map = mapFragment.map
map.setCenter(GeoCoordinate(37.7397, -121.4252, 0.0), Map.Animation.NONE)
map.zoomLevel = (map.maxZoomLevel + map.minZoomLevel) / 2
mapFragment.mapGesture.addOnGestureListener(object : MapGesture.OnGestureListener.OnGestureListenerAdapter() {
override fun onTapEvent(p: PointF?): Boolean {
return false
}
override fun onLongPressEvent(p: PointF?): Boolean {
return false
}
})
}
}
}
}
You’ll notice a few things about the above code. First, you’ll notice that we are not configuring our gesture listeners until after the map has initialized. If you try to configure your listeners before you have a functional map, you’re going to run into trouble. You’ll also notice that we’re not using a lambda expression for the various functions within the listener. There are numerous ways to handle interfaces with multiple functions, the approach above just being one of many. Had we only been using a single override, we could have easily used a lambda.
We have the foundation of our gesture events in place, so let's jump forward for a moment and figure out what happens when we need to place a marker on the map. We might have a function like the following:
fun dropMarker(position: GeoCoordinate) {
if (::marker.isInitialized) {
map.removeMapObject(marker)
}
marker = MapMarker()
marker.coordinate = position
map.addMapObject(marker)
}
Given a latitude and longitude coordinate, we see if a marker already exists on the map. If the marker exists, we remove it and then place a new marker based on the position. Nothing too fancy is happening here.
Going back into our gesture listener, we can update it to the following:
mapFragment.mapGesture.addOnGestureListener(object : MapGesture.OnGestureListener.OnGestureListenerAdapter() {
override fun onTapEvent(p: PointF?): Boolean {
return false
}
override fun onLongPressEvent(p: PointF?): Boolean {
val position = map.pixelToGeo(p)
dropMarker(position)
return false
}
})
Using the pixelToGeo
function, we can get the latitude and longitude coordinates of our tap event. These coordinates are then passed to our dropMarker
function.
Reverse Geocoding Latitude and Longitude Coordinates to Addresses With HERE for Android
Now that we have latitude and longitude information calculated from the previous step, we can work towards reverse geocoding it to human-readable addresses. Before we investigate the onTapEvent
in our gesture listener, let’s figure out what is involved in reverse geocoding some coordinates.
fun reverseGeocode(position: GeoCoordinate) {
val request = ReverseGeocodeRequest(position)
request.execute { data, error ->
if (error != ErrorCode.NONE) {
Log.e("HERE", error.toString())
} else {
Toast.makeText(applicationContext, data.text, Toast.LENGTH_LONG).show()
}
}
}
Given a position, we can make use of the ReverseGeocodeRequest
class that is part of the HERE Android SDK. After executing a request, we’ll get information regarding the closest match to the coordinates provided.
With this in mind, we can update our onTapEvent
to contain the following:
mapFragment.mapGesture.addOnGestureListener(object : MapGesture.OnGestureListener.OnGestureListenerAdapter() {
override fun onTapEvent(p: PointF?): Boolean {
val viewObjectList = map.getSelectedObjects(p) as ArrayList<ViewObject>
for (viewObject in viewObjectList) {
if (viewObject.baseType == ViewObject.Type.USER_OBJECT) {
val mapObject = viewObject as MapObject
if (mapObject.type == MapObject.Type.MARKER) {
val selectedMarker = mapObject as MapMarker
reverseGeocode(selectedMarker.coordinate)
}
}
}
return false
}
override fun onLongPressEvent(p: PointF?): Boolean {
val position = map.pixelToGeo(p)
dropMarker(position)
return false
}
})
When we create a marker, the coordinate information is stored with the marker. Because we are choosing to get that information from the marker and not a pixel on the map, we need to loop through our map objects and figure out which one was tapped. Once we have that information, we can call our reverseGeocode
method.
Conclusion
You just saw how to reverse geocode latitude and longitude coordinates using Kotlin and the HERE Android SDK. The star of this tutorial was in the gesture events that are listened for. While we only made use of the OnTapEvent
and the OnLongPressEvent
, there are quite a few other options.
If you are stuck with the configuration of the HERE Android SDK, I encourage you to check out my previous tutorial on the topic. I’m not a Kotlin expert, so if you think my Kotlin can be optimized, let me know in the comments.
Published at DZone with permission of Nic Raboy, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments