InteractiveVectorMap is a lightweight interactive map library for Android. Works on both traditional View system and Jetpack Compose
- Android View API with
MapView - Jetpack Compose API with
InteractiveMapandInteractiveMapState - Built-in vector maps
- Single, multiple, or disabled selection modes
- Programmatic region select, deselect, clear, hide, and show
- Region color overrides
- Zoom and pan camera helpers
- Anchored markers on a region or on the full map viewport
- Marker types: pulse, custom drawable, and label
- Shapes: circle, rectangle, polygon, and polyline
- Paths between arbitrary points, regions, and markers
- Dashed and animated dashed paths
- Per-item transforms for translation and rotation
The library is published on Maven Central.
dependencies {
implementation("com.nezihyilmaz.interactivevectormap:view:1.0.0")
implementation("com.nezihyilmaz.interactivevectormap:data-world:1.0.0")
}dependencies {
implementation("com.nezihyilmaz.interactivevectormap:compose:1.0.0")
implementation("com.nezihyilmaz.interactivevectormap:data-world:1.0.0")
}Add only the datasets you want to ship.
| Map | Use in code | Maven artifact | Projection |
|---|---|---|---|
| World (Minus Antarctica) | Maps.World |
implementation("com.nezihyilmaz.interactivevectormap:data-world:1.0.0") |
Robinson |
| World 2 (Including Antarctica) | Maps.World2 |
implementation("com.nezihyilmaz.interactivevectormap:data-world2:1.0.0") |
Natural Earth |
| Europe | Maps.Europe |
implementation("com.nezihyilmaz.interactivevectormap:data-europe:1.0.0") |
Robinson |
| Africa | Maps.Africa |
implementation("com.nezihyilmaz.interactivevectormap:data-africa:1.0.0") |
Robinson |
| Russia | Maps.Russia |
implementation("com.nezihyilmaz.interactivevectormap:data-russia:1.0.0") |
Mercator |
| UK | Maps.Uk |
implementation("com.nezihyilmaz.interactivevectormap:data-uk:1.0.0") |
Mercator |
| Turkey | Maps.Turkey |
implementation("com.nezihyilmaz.interactivevectormap:data-turkey:1.0.0") |
Mercator |
| USA | Maps.Usa |
implementation("com.nezihyilmaz.interactivevectormap:data-usa:1.0.0") |
Lambert azimuthal equal-area |
<MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />mapView.defaultColor = Color.parseColor("#ECECEC")
mapView.selectedColor = Color.parseColor("#4CAF50")
mapView.borderColor = Color.GRAY
mapView.selectionMode = SelectionMode.Multiple
mapView.setMap(Maps.World) {
mapView.addMarker(
type = Pulse(
color = Color.RED,
text = "HQ"
),
position = Anchor.Region(WorldId.US)
)
}
mapView.setOnSelectionChangedListener { regions ->
println("Selected: ${regions.map { it.id }}")
}val state = rememberInteractiveMapState(
initialSelectionMode = SelectionMode.Multiple
)
InteractiveMap(
mapData = Maps.World,
state = state,
defaultColor = Color(0xFFECECEC),
selectedColor = Color(0xFF4CAF50),
borderColor = Color.Gray,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
onMapLoaded = {
state.addMarker(
type = Pulse(
color = Color.Red,
text = "HQ"
),
position = Anchor.Region(WorldId.US)
)
},
onSelectionChanged = { regions ->
println("Selected: ${regions.map { it.id }}")
}
)Every dataset module also exposes typed region-id helpers through RegionId.
Examples:
val unitedStates = WorldId.USval istanbul = TurkeyId.TR34SelectionMode supports:
SingleMultipleNone
Map content can be positioned relative to:
Anchor.Region(regionId)for placement inside a regionAnchor.Map()for placement inside the full map viewport
Both anchors accept an offset: PositionScope.() -> PointF lambda. Inside that scope you can use:
centercentroidwidthheight
Example:
Anchor.Region(regionId = WorldId.US) { center }
Anchor.Region(regionId = WorldId.US) { PointF(width * 0.75f, height * 0.2f) }
Anchor.Map { PointF(width / 2f, height / 2f) }Transform can be used with markers and shapes:
Transform(
translationX = 12f,
translationY = -8f,
rotation = 20f
)setMap(mapData, onLoaded = null)setOnSelectionChangedListener(listener)selectRegion(regionId, animate = true)selectRegions(regionIds, animate = true)deselectRegion(regionId, animate = true)clearSelection(animate = true)setRegionColor(regionId, color)clearRegionColor(regionId)clearRegionColors()hideRegion(regionId, animate = true): BooleanshowRegion(regionId): BooleanzoomTo(ratio, smooth = true)panTo(regionId, smooth = true)cameraCenter(): PointF?addMarker(type, position, transform = Transform.None): Int?removeMarker(markerId)clearMarkers()addShape(type, position, transform = Transform.None): Int?addCircle(position, radius, style = ShapeStyle(), transform = Transform.None): Int?addRectangle(position, width, height, cornerRadius = 0f, style = ShapeStyle(), transform = Transform.None): Int?addPolygon(position, points, style = ShapeStyle(), transform = Transform.None): Int?addPolyline(position, points, style = ShapeStyle(), transform = Transform.None): Int?removeShape(shapeId)clearShapes()addPath(from, to, style = PathStyle(), curvature = 0f): Int?removePath(pathId)clearPaths()
When interactionEnabled is true, MapView supports:
- tap selection
- pinch to zoom
- pan/drag navigation
InteractiveMap(
mapData = Maps.World,
modifier = Modifier.wrapContentHeight(),
state = rememberInteractiveMapState(),
selectedColor = Color(0xFF4CAF50),
defaultColor = Color(0xFFE7E7E7),
borderColor = Color.Gray,
borderWidth = 1.5f,
onMapLoaded = {},
onSelectionChanged = {}
)InteractiveMapState exposes:
maxZoomanimationDurationMsscaleoffsetselectionModeinteractionEnabledregionsselectedRegions
addMarker(type, position, transform = Transform.None): Int?removeMarker(markerId)clearMarkers()addShape(type, position, transform = Transform.None): Int?addCircle(position, radius, style = ShapeStyle(), transform = Transform.None): Int?addRectangle(position, width, height, cornerRadius = 0f, style = ShapeStyle(), transform = Transform.None): Int?addPolygon(position, points, style = ShapeStyle(), transform = Transform.None): Int?addPolyline(position, points, style = ShapeStyle(), transform = Transform.None): Int?removeShape(shapeId)clearShapes()addPath(from, to, style = PathStyle(), curvature = 0f): Int?removePath(pathId)clearPaths()setRegionColor(regionId, color)clearRegionColor(regionId)clearRegionColors()selectRegion(regionId, animate = true)selectRegions(regionIds, animate = true)deselectRegion(regionId, animate = true)clearSelection(animate = true)hideRegion(regionId, animate = true): BooleanshowRegion(regionId): BooleanzoomTo(ratio, smooth = true)panTo(regionId, smooth = true)cameraCenter(): PointF?
When state.interactionEnabled is true, InteractiveMap supports:
- tap selection
- pinch zoom
- pan
Supported marker types:
MarkerType.PulseMarkerType.CustomMarkerType.Label
mapView.addMarker(
type = Pulse(
color = Color.RED,
text = "12"
),
position = Anchor.Region(WorldId.US)
)mapView.addMarker(
type = Custom(
drawable = yourDrawable,
text = "NYC"
),
position = Anchor.Map {
PointF(width * 0.5f, height * 0.35f)
}
)state.addMarker(
type = Label(
text = "Europe",
color = Color.BLACK,
size = Fixed(30f)
),
position = Anchor.Region(WorldId.FR)
)Supported shapes:
ShapeType.CircleShapeType.RectangleShapeType.PolygonShapeType.Polyline
val style = ShapeStyle(
fillColor = Color.argb(102, 33, 150, 243),
strokeColor = Color.parseColor("#1976D2"),
strokeWidth = 2f
)mapView.addCircle(
position = Anchor.Region(WorldId.US),
radius = 24f,
style = style
)mapView.addRectangle(
position = Anchor.Region(WorldId.US),
width = 64f,
height = 36f,
cornerRadius = 12f,
style = style
)state.addPolygon(
position = Anchor.Map(),
points = listOf(
PointF(-20f, -20f),
PointF(30f, -10f),
PointF(10f, 25f)
),
style = style
)state.addPolyline(
position = Anchor.Map(),
points = listOf(
PointF(-30f, -10f),
PointF(0f, 20f),
PointF(30f, -10f)
),
style = style
)Paths connect two PathEndpoints:
PathEndpoint.Point(x, y)PathEndpoint.Marker(markerId)PathEndpoint.Region(regionId)
curvature = 0f draws a straight line. Non-zero values produce a curved arc.
val style = PathStyle(
color = Color.parseColor("#1976D2"),
width = 2f,
dashPattern = floatArrayOf(8f, 4f),
dashAnimation = DashAnimation(
cycleDurationMs = 1200L,
reverse = false
)
)mapView.addPath(
from = Region(WorldId.US),
to = Region(WorldId.CA),
style = style,
curvature = 0.22f
)val markerId = state.addMarker(
type = Pulse(text = "Hub"),
position = Anchor.Region(WorldId.GB)
) ?: return
state.addPath(
from = Marker(markerId),
to = Region(WorldId.FR),
style = style,
curvature = -0.18f
)state.addPath(
from = Point(100f, 120f),
to = Point(260f, 180f),
style = PathStyle(
color = Color.RED,
width = 3f
),
curvature = 0f
)mapView.setRegionColor(WorldId.US, Color.parseColor("#1E88E5"))
mapView.setRegionColor(WorldId.CA, Color.parseColor("#43A047"))mapView.clearRegionColor(WorldId.US)
mapView.clearRegionColors()mapView.hideRegion(WorldId.GL)
mapView.showRegion(WorldId.GL)Hidden regions are excluded from hit testing and selection.
mapView.selectRegion(WorldId.US)
mapView.selectRegions(listOf(WorldId.US, WorldId.CA), animate = false)
mapView.deselectRegion(WorldId.CA)
mapView.clearSelection()zoomTo(ratio) maps 0f..1f into 1x..maxZoom.
mapView.zoomTo(0f)
mapView.zoomTo(0.5f)
mapView.zoomTo(1f)mapView.panTo(WorldId.TR)This project is licensed under the terms of the LICENSE file.


