Skip to content

nezih94/InteractiveVectorMap

Repository files navigation

InteractiveVectorMap

InteractiveVectorMap is an Android SDK for both the classic Android View system and Jetpack Compose, so you can use the same map capabilities in either UI stack. It ships with ready-to-use map datasets, region selection, region coloring, hide/show support, zoom and pan controls, anchored markers, labels, custom shapes, curved connection paths, and animated dashed routes. And it is very lightweight!

Highlights

  • Android View API with MapView
  • Jetpack Compose API with InteractiveMap and InteractiveMapState
  • 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

Screenshots

General showcase

Coloring showcase

Circle showcase

Installation

The library is published on Maven Central.

View quick start

dependencies {
    implementation("com.nezihyilmaz.interactivevectormap:view:1.0.0")
    implementation("com.nezihyilmaz.interactivevectormap:data-world:1.0.0")
}

Compose quick start

dependencies {
    implementation("com.nezihyilmaz.interactivevectormap:compose:1.0.0")
    implementation("com.nezihyilmaz.interactivevectormap:data-world:1.0.0")
}

Available Maps And Datasets

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

Quick Start

View

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

Compose

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

Region IDs

Every dataset module also exposes typed region-id helpers through RegionId.

Examples:

val unitedStates = WorldId.US
val istanbul = TurkeyId.TR34

Core Concepts

Selection modes

SelectionMode supports:

  • Single
  • Multiple
  • None

Anchors

Map content can be positioned relative to:

  • Anchor.Region(regionId) for placement inside a region
  • Anchor.Map() for placement inside the full map viewport

Both anchors accept an offset: PositionScope.() -> PointF lambda. Inside that scope you can use:

  • center
  • centroid
  • width
  • height

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

Transform can be used with markers and shapes:

Transform(
    translationX = 12f,
    translationY = -8f,
    rotation = 20f
)

Android View API

Important MapView methods

  • 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): Boolean
  • showRegion(regionId): Boolean
  • zoomTo(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()

View gestures

When interactionEnabled is true, MapView supports:

  • tap selection
  • pinch to zoom
  • pan/drag navigation

Jetpack Compose API

InteractiveMap

InteractiveMap(
    mapData = Maps.World,
    modifier = Modifier.wrapContentHeight(),
    state = rememberInteractiveMapState(),
    selectedColor = Color(0xFF4CAF50),
    defaultColor = Color(0xFFE7E7E7),
    borderColor = Color.Gray,
    borderWidth = 1.5f,
    onMapLoaded = {},
    onSelectionChanged = {}
)

InteractiveMapState

InteractiveMapState exposes:

  • maxZoom
  • animationDurationMs
  • scale
  • offset
  • selectionMode
  • interactionEnabled
  • regions
  • selectedRegions

Important InteractiveMapState methods

  • 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): Boolean
  • showRegion(regionId): Boolean
  • zoomTo(ratio, smooth = true)
  • panTo(regionId, smooth = true)
  • cameraCenter(): PointF?

Compose gestures

When state.interactionEnabled is true, InteractiveMap supports:

  • tap selection
  • pinch zoom
  • pan

Markers

Supported marker types:

  • MarkerType.Pulse
  • MarkerType.Custom
  • MarkerType.Label

Pulse marker

mapView.addMarker(
    type = Pulse(
        color = Color.RED,
        text = "12"
    ),
    position = Anchor.Region(WorldId.US)
)

Custom drawable marker

mapView.addMarker(
    type = Custom(
        drawable = yourDrawable,
        text = "NYC"
    ),
    position = Anchor.Map {
        PointF(width * 0.5f, height * 0.35f)
    }
)

Label marker

state.addMarker(
    type = Label(
        text = "Europe",
        color = Color.BLACK,
        size = Fixed(30f)
    ),
    position = Anchor.Region(WorldId.FR)
)

Shapes

Supported shapes:

  • ShapeType.Circle
  • ShapeType.Rectangle
  • ShapeType.Polygon
  • ShapeType.Polyline

Shape styling

val style = ShapeStyle(
    fillColor = Color.argb(102, 33, 150, 243),
    strokeColor = Color.parseColor("#1976D2"),
    strokeWidth = 2f
)

Circle

mapView.addCircle(
    position = Anchor.Region(WorldId.US),
    radius = 24f,
    style = style
)

Rectangle

mapView.addRectangle(
    position = Anchor.Region(WorldId.US),
    width = 64f,
    height = 36f,
    cornerRadius = 12f,
    style = style
)

Polygon

state.addPolygon(
    position = Anchor.Map(),
    points = listOf(
        PointF(-20f, -20f),
        PointF(30f, -10f),
        PointF(10f, 25f)
    ),
    style = style
)

Polyline

state.addPolyline(
    position = Anchor.Map(),
    points = listOf(
        PointF(-30f, -10f),
        PointF(0f, 20f),
        PointF(30f, -10f)
    ),
    style = style
)

Paths

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.

Path styling

val style = PathStyle(
    color = Color.parseColor("#1976D2"),
    width = 2f,
    dashPattern = floatArrayOf(8f, 4f),
    dashAnimation = DashAnimation(
        cycleDurationMs = 1200L,
        reverse = false
    )
)

Region-to-region path

mapView.addPath(
    from = Region(WorldId.US),
    to = Region(WorldId.CA),
    style = style,
    curvature = 0.22f
)

Marker-to-region path

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
)

Point-to-point path

state.addPath(
    from = Point(100f, 120f),
    to = Point(260f, 180f),
    style = PathStyle(
        color = Color.RED,
        width = 3f
    ),
    curvature = 0f
)

Region Styling And Visibility

Set region colors

mapView.setRegionColor(WorldId.US, Color.parseColor("#1E88E5"))
mapView.setRegionColor(WorldId.CA, Color.parseColor("#43A047"))

Clear overrides

mapView.clearRegionColor(WorldId.US)
mapView.clearRegionColors()

Hide and show regions

mapView.hideRegion(WorldId.GL)
mapView.showRegion(WorldId.GL)

Hidden regions are excluded from hit testing and selection.

Programmatic Selection

mapView.selectRegion(WorldId.US)
mapView.selectRegions(listOf(WorldId.US, WorldId.CA), animate = false)
mapView.deselectRegion(WorldId.CA)
mapView.clearSelection()

Camera Control

Zoom

zoomTo(ratio) maps 0f..1f into 1x..maxZoom.

mapView.zoomTo(0f)
mapView.zoomTo(0.5f)
mapView.zoomTo(1f)

Pan to region

mapView.panTo(WorldId.TR)

License

This project is licensed under the terms of the LICENSE file.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages