Docs / Basics / GroundImage

GroundImage — pinning an image to the map

Define a rectangle with a south-west and a north-east point and lay one image inside it: floor plans, historical maps, aerial photography, your own colour-coded diagrams — anything that has not been cut into tiles. Bounds and opacity can be changed later, and the image stays glued to the ground as the camera moves.

BOUNDS
SW / NE
IMAGE
image / url
OPACITY
0.0 – 1.0
TILE SIZE
512 · tiled path only
Platform

01 · Ground image state

GroundImageState represents one image: bounds, the image itself, opacity and a tap handler. The image is passed in whatever form is natural per platform — a Drawable on Android, a UIImage on iOS, a URL on the web.

GroundImageMapPageViewModel.kt · Jetpack Compose
val groundImageState =
    GroundImageState(
        id = "groundImage",
        bounds =
            GeoRectBounds(
                southWest = southWestPosition,
                northEast = northEastPosition,
            ),
        image = drawable,       // Drawable をそのまま渡す
        opacity = 0.5f,
        onClick = this::onGroundImageClick,
    )

GroundImage(groundImageState)
Property
Description
bounds
The rectangle the image covers, defined by its south-west and north-east corners.
image / imageUrl
The image: a Drawable on Android, a UIImage on iOS, an image URL on the web.
opacity
Opacity — 0.0 fully transparent, 1.0 opaque. Lower it to blend with the base map.
tileSize
Tile edge length used only when the image is drawn as raster tiles; unused on providers with a native image overlay. The default of 512 is fine.
extra
Arbitrary payload, handed back on tap.
onClick
Tap handler; receives the image state and the tapped coordinate.

02 · Bounds and opacity

The bounds are a rectangle. If the image aspect ratio differs from the bounds, the image is stretched to fit, so match the bounds to the aspect of the plan you are placing. Bounds, opacity and the image can all be reassigned and apply immediately.

SW
NE
The south-west (SW) and north-east (NE) points define the rectangle. The outline and markers shown here are sample-app code.
BOUNDS

Rebuild the bounds by dragging

Reassign bounds as two corner markers are dragged and the image follows — how smoothly it follows continuous updates varies by provider.
OPACITY

Blend with the base map

Lower the opacity and the roads and labels underneath show through. Some engines limit when or how that change applies.
Sample · a historic map fitted to its bounds
Changing bounds and opacity
// 四隅のマーカーをドラッグして範囲を作り直す
override fun onMarkerDrag(dragged: MarkerState) {
    when (dragged.id) {
        "south_west" -> southWestPosition = GeoPoint.from(dragged.position)
        "north_east" -> northEastPosition = GeoPoint.from(dragged.position)
    }
    bounds = GeoRectBounds(southWest = southWestPosition, northEast = northEastPosition)
}

// 透明度はそのまま代入するだけ
groundImageState.opacity = sliderOpacity

03 · The same result on every provider

GroundImage is the feature with the widest implementation gap between map SDK drivers. Overlay mechanisms differ greatly and some engines have no equivalent at all. MapConductor absorbs as much of that as it can, but some providers keep limits on dynamic changes.

PLACEMENT

Same bounds, same stretch

The image is fitted to the bounds you gave, regardless of how the engine models overlay coordinates — same position, same size everywhere.

NATIVE FIRST

Native primitives first

Each driver draws through the map SDK own image-overlay primitive wherever one exists — MediaLayer with ImageElement on ArcGIS, an image-material Rectangle on Cesium, and so on. Only where no equivalent exists is the image sliced into tiles behind a raster layer. Your code is the same either way.

LIMITATIONS

Per-provider limits

Rebuilding the bounds (continuous updates during a drag) and changing opacity can be limited by the engine. Static placement is consistent everywhere, but verify on your target providers if you change these dynamically.

HIT TEST

Shared tap detection

Tap detection is shared logic, so onClick fires whether or not the engine supports taps on image overlays.

04 · Tapping

onClick receives the image state and the tapped coordinate. The samples swap the image itself on tap to toggle between two versions.

Swapping the image on tap
// タップで画像を差し替える
override fun onGroundImageClick(clicked: GroundImageEvent) {
    val isBase = clicked.state.image == imageResources.image
    image = if (isBase) imageResources.clickedImage else imageResources.image
}
event.state
The tapped image state — read the current image or the extra payload.
event.clicked
The tapped geographic coordinate, so you know where inside the bounds was hit.

Transparent parts of the image still count as tappable. To make them inert, match the bounds to the image content or pair it with a polygon.

Related pages