Skip to content

Draw a Circle on the Map

This tutorial shows how to draw circular areas on your MapMetrics Android map — useful for showing radius zones, geofences, or proximity areas.

Prerequisites

Draw a Circle Using Turf

Since there's no native circle geometry in GeoJSON, we approximate one using a polygon with many points:

kotlin
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
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.FillLayer
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.Point
import org.maplibre.geojson.Polygon
import kotlin.math.*

class DrawCircleActivity : AppCompatActivity() {

    private lateinit var mapView: MapView
    private lateinit var map: MapMetricsMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_map)

        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 ->
                drawCircle(style, LatLng(48.8584, 2.2945), 500.0) // 500m radius
            }
        }
    }

    private fun drawCircle(style: Style, center: LatLng, radiusMeters: Double) {
        val circlePolygon = createCirclePolygon(center, radiusMeters, 64)

        style.addSource(
            GeoJsonSource("circle-source", Feature.fromGeometry(circlePolygon))
        )

        // Fill
        style.addLayer(
            FillLayer("circle-fill", "circle-source")
                .withProperties(
                    fillColor(Color.parseColor("#4285F4")),
                    fillOpacity(0.2f)
                )
        )

        // Outline
        style.addLayer(
            LineLayer("circle-outline", "circle-source")
                .withProperties(
                    lineColor(Color.parseColor("#4285F4")),
                    lineWidth(2f)
                )
        )

        // Center camera
        map.cameraPosition = CameraPosition.Builder()
            .target(center)
            .zoom(14.0)
            .build()
    }

    /**
     * Generate a polygon approximating a circle.
     * @param center Center point
     * @param radiusMeters Radius in meters
     * @param steps Number of polygon vertices (more = smoother)
     */
    private fun createCirclePolygon(
        center: LatLng,
        radiusMeters: Double,
        steps: Int
    ): Polygon {
        val points = mutableListOf<Point>()
        val earthRadius = 6371000.0 // meters

        for (i in 0..steps) {
            val angle = Math.toRadians((360.0 / steps) * i)
            val lat = Math.toRadians(center.latitude)
            val lng = Math.toRadians(center.longitude)

            val newLat = asin(
                sin(lat) * cos(radiusMeters / earthRadius) +
                cos(lat) * sin(radiusMeters / earthRadius) * cos(angle)
            )
            val newLng = lng + atan2(
                sin(angle) * sin(radiusMeters / earthRadius) * cos(lat),
                cos(radiusMeters / earthRadius) - sin(lat) * sin(newLat)
            )

            points.add(
                Point.fromLngLat(
                    Math.toDegrees(newLng),
                    Math.toDegrees(newLat)
                )
            )
        }

        return Polygon.fromLngLats(listOf(points))
    }

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

Multiple Radius Rings

Show concentric distance rings around a point:

kotlin
private fun drawRadiusRings(style: Style, center: LatLng) {
    val rings = listOf(
        Pair(250.0, "#4285F4"),   // 250m — blue
        Pair(500.0, "#34A853"),   // 500m — green
        Pair(1000.0, "#FF6B35"), // 1km — orange
    )

    for ((index, ring) in rings.withIndex()) {
        val (radius, color) = ring
        val polygon = createCirclePolygon(center, radius, 64)
        val sourceId = "ring-source-$index"

        style.addSource(GeoJsonSource(sourceId, Feature.fromGeometry(polygon)))

        style.addLayer(
            FillLayer("ring-fill-$index", sourceId)
                .withProperties(
                    fillColor(Color.parseColor(color)),
                    fillOpacity(0.1f)
                )
        )

        style.addLayer(
            LineLayer("ring-outline-$index", sourceId)
                .withProperties(
                    lineColor(Color.parseColor(color)),
                    lineWidth(2f),
                    lineDasharray(arrayOf(2f, 1f))
                )
        )
    }

    // Add center marker
    map.addMarker(
        org.maplibre.android.annotations.MarkerOptions()
            .position(center)
            .title("Center Point")
            .snippet("250m / 500m / 1km radius")
    )
}

Circle on Tap

Let users tap the map to place a circle:

kotlin
private fun setupTapToCircle(style: Style) {
    // Pre-create empty source and layers
    style.addSource(GeoJsonSource("tap-circle-source"))
    style.addLayer(
        FillLayer("tap-circle-fill", "tap-circle-source")
            .withProperties(
                fillColor(Color.parseColor("#FF6B35")),
                fillOpacity(0.2f)
            )
    )
    style.addLayer(
        LineLayer("tap-circle-outline", "tap-circle-source")
            .withProperties(
                lineColor(Color.parseColor("#FF6B35")),
                lineWidth(2f)
            )
    )

    map.addOnMapClickListener { latLng ->
        val polygon = createCirclePolygon(latLng, 300.0, 64)
        val source = style.getSource("tap-circle-source") as? GeoJsonSource
        source?.setGeoJson(Feature.fromGeometry(polygon))
        true
    }
}

Using Turf Library

If you include the Turf dependency, circle creation is simpler:

kotlin
import com.mapbox.turf.TurfTransformation
import com.mapbox.turf.TurfConstants

val center = Point.fromLngLat(2.2945, 48.8584)
val circlePolygon = TurfTransformation.circle(
    center,
    500.0,
    64,
    TurfConstants.UNIT_METRES
)

Next Steps


Tip: Use 64 steps for smooth circles. Below 32 you'll see visible edges. Above 128 adds points without visible improvement. The Turf library approach is recommended when available as it handles edge cases near the poles.