android-geojson-layer adds a tile-rendered GeoJSON overlay to MapConductor map views.
It parses GeoJSON data into feature models, renders the features through MapConductor's
raster tile layer pipeline, and provides hit-testing for feature selection.
The layer is useful for large GeoJSON datasets because rendering is tile based and parsed features can be supplied as lightweight data objects instead of one Compose state object per feature.
- Renders
Point,MultiPoint,LineString,MultiLineString,Polygon,MultiPolygon, andGeometryCollection. - Supports static bulk features with
GeoJSONFeature. - Supports reactive Compose features with
GeoJSONFeatureState,GeoJSONFeature, andGeoJSONFeatures. - Streams
FeatureCollectioninput withGeoJSONParser.parseStream. - Streams GeoJSON Text Sequences with
GeoJSONSeqParser. - Supports layer-level and feature-level styling.
- Provides touch hit-testing through
GeoJSONLayerState.processClick.
When developing inside the MapConductor SDK repository, include the local Gradle module:
dependencies {
implementation(project(":android-geojson-layer"))
}For published artifacts, use the configured MapConductor coordinates:
dependencies {
implementation("com.mapconductor:geojson:<version>")
}The module depends on MapConductor core and Jetpack Compose runtime.
Load a GeoJSON FeatureCollection and render it inside any MapConductor map view content
scope:
@Composable
fun BasicGeoJsonExample(modifier: Modifier) {
val mapViewState = rememberMapLibreMapViewState(
cameraPosition = MapCameraPosition(
position = GeoPoint.fromLongLat(55.3089185, 25.255377),
zoom = 13.0,
),
)
val layerState =
remember {
GeoJSONLayerState(
fillColor = android.graphics.Color.argb(127, 0x3b, 0xb2, 0xd0),
strokeColor = android.graphics.Color.argb(255, 0x1d, 0x70, 0x82),
strokeWidth = 2f,
)
}
LaunchedEffect(Unit) {
features =
withContext(Dispatchers.IO) {
BASIC_GEOJSON.byteInputStream(Charsets.UTF_8).use(GeoJSONParser::parseStream)
}
}
MapLibreMapView(state = mapViewState) {
GeoJSONLayer(state = layerState, features = features)
}
}
private val BASIC_GEOJSON =
"""
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[55.30122473231012, 25.26476622289597],
...,
[55.30122473231012, 25.26476622289597]
],
[
[55.30084858315658, 25.256531695820797],
...,
[55.30084858315658, 25.256531695820797]
],
[
[55.30173763969924, 25.262517391695198],
...,
[55.30173763969924, 25.262517391695198]
]
]
},
"properties": {}
}
]
}
""".trimIndent()Set default layer style through GeoJSONLayerState:
val layerState = remember {
GeoJSONLayerState(
strokeColor = android.graphics.Color.argb(220, 30, 136, 229),
fillColor = android.graphics.Color.argb(60, 30, 136, 229),
strokeWidth = 1.5f,
pointRadius = 8f,
opacity = 1f,
visible = true,
)
}
GeoJSONLayer(
state = layerState,
features = features,
)Individual GeoJSONFeature and GeoJSONFeatureState objects can override
strokeColor, fillColor, strokeWidth, pointRadius, and visible.
The layer keeps hit-testing in android-geojson-layer and avoids changing MapConductor
core. Because MapConductor currently exposes a single map click listener slot, apps
should forward map clicks to the GeoJSONLayerState manually.
var selectedFeature by remember { mutableStateOf<GeoJSONFeature?>(null) }
val layerState = remember {
GeoJSONLayerState(
onClick = { feature, position ->
selectedFeature = feature
// Use position for marker, info bubble, bottom sheet, etc.
},
)
}
MapLibreMapView(
state = mapViewState,
onMapClick = { point ->
val consumed = layerState.processClick(point)
if (!consumed) {
selectedFeature = null
}
},
) {
GeoJSONLayer(
state = layerState,
features = features,
)
}processClick returns true when a feature is hit and the layer onClick callback is
invoked. It returns false when no rendered feature is found at that map position.
Hit-testing supports points, lines, polygons with holes, multiparts, and geometry collections. When features overlap, the latest rendered matching feature is returned.
For small or frequently changing feature sets, place stateful features inside the layer:
val pointState = remember {
GeoJSONFeatureState(
featureId = "office",
geometry = GeoJSONGeometry.Point(
longitude = 139.77,
latitude = 35.68,
),
properties = mapOf("name" to "Office"),
)
}
GeoJSONLayer(state = layerState) {
GeoJSONFeature(pointState)
}For many static features, prefer GeoJSONFeature plus the features parameter. That
path avoids creating Compose snapshot state for every feature.
GeoJSON Text Sequences are supported for stream-friendly datasets where records are separated by the RFC 8142 record separator character.
val features = withContext(Dispatchers.IO) {
GeoJSONSeqParser.parse(file)
}Use GeoJSONSeqParser.streamParse to consume records one at a time.
- Prefer
GeoJSONParser.parseStream(input)for largeFeatureCollectionfiles. - Load and parse data on
Dispatchers.IOor another background dispatcher. - Use static
GeoJSONFeaturelists for large datasets. - Use
GeoJSONFeatureStateonly when individual feature updates are needed. - The layer internally invalidates its tile URL when feature data or style changes so map SDK raster caches request fresh rendered tiles.
- Automatic layer-level click listener registration is intentionally not implemented.
Forward map clicks to
GeoJSONLayerState.processClick. GeoJSONParser.parseStreamsupports top-levelFeatureCollectioninput. UseGeoJSONParser.parsefor top-levelFeatureor bare geometry JSON strings.- Hit-test line and point tolerances are renderer constants in world coordinates, not configurable public API yet.
- The layer is rendered as raster tiles, so native SDK vector feature querying is not used.
Compile the layer and sample app:
./gradlew :android-geojson-layer:compileDebugKotlin :simple-map-app:compileDebugKotlinRun ktlint for the module:
./gradlew :android-geojson-layer:ktlintCheck
