Measure Distances on the Map
This tutorial shows how to build a tap-to-measure tool that calculates distances between points on your MapMetrics Android map using the Haversine formula.
Prerequisites
- Completed the Getting Started Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Tap-to-Measure Tool
Tap points on the map to measure the total distance:
kotlin
import android.graphics.Color
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import org.maplibre.android.annotations.MarkerOptions
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapMetricsMap
import org.maplibre.android.maps.Style
import org.maplibre.android.style.layers.LineLayer
import org.maplibre.android.style.layers.PropertyFactory.*
import org.maplibre.android.style.sources.GeoJsonSource
import org.maplibre.geojson.Feature
import org.maplibre.geojson.LineString
import org.maplibre.geojson.Point
import kotlin.math.*
class MeasureActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var map: MapMetricsMap
private lateinit var distanceText: TextView
private val measurePoints = mutableListOf<LatLng>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_measure)
distanceText = findViewById(R.id.tvDistance)
mapView = findViewById(R.id.mapView)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync { mapMetricsMap ->
map = mapMetricsMap
map.setStyle(
Style.Builder().fromUri(
"https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY"
)
) { style ->
setupMeasureLine(style)
setupMapClick()
map.cameraPosition = CameraPosition.Builder()
.target(LatLng(48.8566, 2.3522))
.zoom(12.0)
.build()
}
// Clear button
findViewById<Button>(R.id.btnClear).setOnClickListener {
clearMeasurement()
}
}
}
private fun setupMeasureLine(style: Style) {
style.addSource(GeoJsonSource("measure-source"))
style.addLayer(
LineLayer("measure-line", "measure-source")
.withProperties(
lineColor(Color.parseColor("#FF6B35")),
lineWidth(3f),
lineDasharray(arrayOf(2f, 1f))
)
)
}
private fun setupMapClick() {
map.addOnMapClickListener { latLng ->
// Add point
measurePoints.add(latLng)
// Add marker at tap point
map.addMarker(
MarkerOptions()
.position(latLng)
.title("Point ${measurePoints.size}")
.snippet(
"Lat: ${String.format("%.6f", latLng.latitude)}, " +
"Lng: ${String.format("%.6f", latLng.longitude)}"
)
)
// Update line
if (measurePoints.size >= 2) {
updateMeasureLine()
}
// Update total distance
updateDistance()
true
}
}
private fun updateMeasureLine() {
val points = measurePoints.map {
Point.fromLngLat(it.longitude, it.latitude)
}
val lineString = LineString.fromLngLats(points)
val source = map.style?.getSource("measure-source") as? GeoJsonSource
source?.setGeoJson(Feature.fromGeometry(lineString))
}
private fun updateDistance() {
var totalDistance = 0.0
for (i in 0 until measurePoints.size - 1) {
totalDistance += haversineDistance(
measurePoints[i], measurePoints[i + 1]
)
}
distanceText.text = when {
totalDistance < 1.0 -> "${(totalDistance * 1000).toInt()} m"
else -> String.format("%.2f km", totalDistance)
}
}
private fun clearMeasurement() {
measurePoints.clear()
map.clear()
distanceText.text = "0 m"
val source = map.style?.getSource("measure-source") as? GeoJsonSource
source?.setGeoJson(Feature.fromGeometry(LineString.fromLngLats(emptyList())))
}
/**
* Calculate distance between two points using the Haversine formula.
* Returns distance in kilometers.
*/
private fun haversineDistance(point1: LatLng, point2: LatLng): Double {
val R = 6371.0 // Earth's radius in km
val lat1 = Math.toRadians(point1.latitude)
val lat2 = Math.toRadians(point2.latitude)
val dLat = Math.toRadians(point2.latitude - point1.latitude)
val dLng = Math.toRadians(point2.longitude - point1.longitude)
val a = sin(dLat / 2).pow(2) +
cos(lat1) * cos(lat2) * sin(dLng / 2).pow(2)
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
return R * c
}
override fun onStart() { super.onStart(); mapView.onStart() }
override fun onResume() { super.onResume(); mapView.onResume() }
override fun onPause() { super.onPause(); mapView.onPause() }
override fun onStop() { super.onStop(); mapView.onStop() }
override fun onDestroy() { super.onDestroy(); mapView.onDestroy() }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
}Layout (res/layout/activity_measure.xml):
xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.maplibre.android.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:layout_marginTop="16dp"
android:background="#CC000000"
android:padding="12dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Distance: "
android:textColor="#FFFFFF"
android:textSize="16sp" />
<TextView
android:id="@+id/tvDistance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 m"
android:textColor="#FF6B35"
android:textStyle="bold"
android:textSize="18sp" />
<Button
android:id="@+id/btnClear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Clear"
android:layout_marginStart="12dp" />
</LinearLayout>
</FrameLayout>Show Segment Distances
Display distance on each segment:
kotlin
private fun updateDistance() {
var totalDistance = 0.0
val segmentInfo = StringBuilder()
for (i in 0 until measurePoints.size - 1) {
val segmentDist = haversineDistance(measurePoints[i], measurePoints[i + 1])
totalDistance += segmentDist
segmentInfo.append("Segment ${i + 1}: ")
segmentInfo.append(
if (segmentDist < 1.0) "${(segmentDist * 1000).toInt()} m"
else String.format("%.2f km", segmentDist)
)
segmentInfo.append("\n")
}
distanceText.text = when {
totalDistance < 1.0 -> "${(totalDistance * 1000).toInt()} m total"
else -> String.format("%.2f km total", totalDistance)
}
}Next Steps
- Polyline Route — Draw lines on the map
- Map Click Events — Handling taps and gestures
- Polygon Area — Draw filled zones
Tip: The Haversine formula gives great-circle distances — straight line over the Earth's surface. For road distances, you would need a routing API. The measurements shown here are accurate for aerial/straight-line distance.