Building & Floor Selection
Using Mappedin SDK for Android with your own map requires a Pro license. Try a demo map for free or refer to the Pricing page for more information.
Floor Selection
When mapping a building with multiple floors, each floor has its own unique map. These are represented by the Floor class, which are accessed using MapData.getByType(). The currently displayed Floor can be accessed using MapView.currentFloor.
A complete example demonstrating Building and Floor Selection can be found in the Mappedin Android Github repo: BuildingFloorSelectionDemoActivity.kt
Changing Floors
When initialized, MapView displays the Floor with the elevation that's closest to 0. This can be overridden by setting Show3DMapOptions.initialFloor to a Floor.id and passing it to MapView.show3dMap().
// Setting the initial floor to Floor.id 'm_123456789'.
mapView.show3dMap(Show3DMapOptions(initialFloor = "m_123456789")) { r ->
r.onSuccess {
// Successfully initialized the map view.
}
r.onFailure {
// Failed to initialize the map view.
}
}
The Floor displayed in a MapView can also be changed during runtime by calling MapView.setFloor() and passing in a Floor.id.
// Set the floor to Floor.id 'm_987654321'.
mapView.setFloor("m_987654321") { result ->
result.onSuccess {
Log.d("MappedinDemo", "Floor changed successfully")
}
}
Listening for Floor Changes
Mappedin SDK for Android provides the ability to listen for floor changes on a MapView. The code below listens for the floor-change event and logs the new floor and building name to the console.
mapView.on(Events.FloorChange) { payload ->
payload?.let {
Log.d(
"MappedinDemo",
"Floor changed to: ${it.floor.name} in building: ${it.floor.floorStack?.name}",
)
}
}
Building Selection
Mappedin Maps can contain one to many buildings with each building having one to many floors. Each building has its floors organized into a FloorStack object. When multiple buildings are present, there are multiple FloorStack objects, one for each building. A FloorStack contains one to many Floor objects, each representing the map of each level of the building.
The FloorStack object is accessed by using MapData.getByType().
// Get all floor stacks
mapView.mapData.getByType<FloorStack>(MapDataType.FLOOR_STACK) { result ->
result.onSuccess { stacks ->
floorStacks = stacks?.sortedBy { it.name } ?: emptyList()
// Get all floors
mapView.mapData.getByType<Floor>(MapDataType.FLOOR) { floorsResult ->
floorsResult.onSuccess { floors ->
allFloors = floors ?: emptyList()
Log.d("MappedinDemo", "Floors: $floors")
}
}
}
}
Determine If a Coordinate Is Inside a Building
The FloorStack object has a geoJSON property that can be used to determine if a coordinate is inside a building.
The code below listens for a click event on the map and determines if the clicked coordinate is inside a building. If it is, the building name is logged to the console. If it is not, a message is logged to the console.
// Filter to get only buildings (type == "Building")
buildings = floorStacks.filter { it.type == FloorStack.FloorStackType.BUILDING }
mapView.on(Events.Click) { payload ->
payload?.let { clickPayload ->
val coordinate = clickPayload.coordinate
val matchingBuildings = mutableListOf<String>()
// This demonstrates how to detect if a coordinate is within a building.
// For click events, this can be detected by checking the ClickEvent.floors.
// Checking the coordinate is for demonstration purposes and useful for non click events.
buildings.forEach { building ->
if (isCoordinateWithinFeature(coordinate, building.geoJSON)) {
matchingBuildings.add(building.name)
}
}
runOnUiThread {
val message =
if (matchingBuildings.isNotEmpty()) {
"Coordinate is within building: ${matchingBuildings.joinToString(", ")}"
} else {
"Coordinate is not within any building"
}
Toast.makeText(this@BuildingFloorSelectionDemoActivity, message, Toast.LENGTH_SHORT).show()
}
}
}
/**
* Check if a coordinate is within a GeoJSON Feature.
* This implements point-in-polygon checking similar to @turf/boolean-contains.
*/
private fun isCoordinateWithinFeature(
coordinate: Coordinate,
feature: Feature?,
): Boolean {
val geometry = feature?.geometry ?: return false
val point = listOf(coordinate.longitude, coordinate.latitude)
return when (geometry) {
is Geometry.Polygon -> isPointInPolygon(point, geometry.coordinates)
is Geometry.MultiPolygon ->
geometry.coordinates.any { polygon ->
isPointInPolygon(point, polygon)
}
else -> false
}
}
/**
* Check if a point is inside a polygon using the ray-casting algorithm.
* The polygon is represented as a list of linear rings (first is outer, rest are holes).
*/
private fun isPointInPolygon(
point: List<Double>,
polygon: List<List<List<Double>>>,
): Boolean {
if (polygon.isEmpty()) return false
// Check if point is inside the outer ring
val outerRing = polygon[0]
if (!isPointInRing(point, outerRing)) {
return false
}
// Check if point is inside any hole (if so, it's not in the polygon)
for (i in 1 until polygon.size) {
if (isPointInRing(point, polygon[i])) {
return false
}
}
return true
}
/**
* Check if a point is inside a linear ring using the ray-casting algorithm.
* This counts how many times a ray from the point crosses the polygon boundary.
*/
private fun isPointInRing(
point: List<Double>,
ring: List<List<Double>>,
): Boolean {
if (ring.size < 4) return false // A valid ring needs at least 4 points (3 + closing point)
val x = point[0]
val y = point[1]
var inside = false
var j = ring.size - 1
for (i in ring.indices) {
val xi = ring[i][0]
val yi = ring[i][1]
val xj = ring[j][0]
val yj = ring[j][1]
val intersect =
((yi > y) != (yj > y)) &&
(x < (xj - xi) * (y - yi) / (yj - yi) + xi)
if (intersect) {
inside = !inside
}
j = i
}
return inside
}
A complete example demonstrating Building and Floor Selection can be found in the Mappedin Android Github repo: BuildingFloorSelectionDemoActivity.kt