Explore details of a building scene by using filters and sublayer visibility.
Use case
Buildings and their component parts (in this example, structural, electrical, or architectural) can be difficult to explain and visualize. An architectural firm might share a 3D building model visualization with clients and contractors to let them explore these components by floor and component type.
How to use the sample
In the filter controls, select floor and category options to filter what parts of the Building Scene Layer are displayed in the scene. Click on any of the building features to identify them.
How it works
- Create an
ArcGISScenewith the URL to a Building Scene Layer service. - Create a
LocalSceneViewand add the scene. - Retrieve the
BuildingSceneLayerfrom the scene's operational layers. - Click the floating action button to view the filtering options.
- Select a floor from the "Floor" dropdown to view the internal details of each floor or "All" to view the entire model.
- Expand the categories to show or hide individual items in the building model. The entire category may be shown or hidden as well.
- Click on any of the building features to view the attributes of the feature.
Relevant API
- ArcGISScene
- BuildingComponentSublayer
- BuildingFilter
- BuildingFilterBlock
- BuildingSceneLayer
- LocalSceneView
- Popup
About the data
This sample uses the Esri Building E Local Scene web scene, which contains a Building Scene Layer representing Building E on the Esri Campus in Redlands, CA. The Revit BIM model was brought into ArcGIS using the BIM capabilities in ArcGIS Pro and published to the web as a Building Scene Layer.
Additional information
Buildings in a Building Scene Layer can be very complex models composed of sublayers containing internal and external features of the structure. Sublayers may include structural components like columns, architectural components like floors and windows, and electrical components.
Applying filters to the Building Scene Layer can highlight features of interest in the model. Filters are made up of filter blocks, which contain several properties that allow control over the filter's function. Setting the filter mode to X-Ray, for instance, will render features with a semi-transparent white color so other interior features can be seen. In addition, toggling the visibility of sublayers can show or hide all the features of a sublayer.
This sample uses the Popup toolkit component. For information about setting up the toolkit, as well as code for the underlying component, visit the toolkit docs.
Tags
3D, building scene layer, layers, popup, toolkit
Sample Code
/* Copyright 2025 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.esri.arcgismaps.sample.filterbuildingscenelayer.components
import android.app.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.mapping.ArcGISScene
import com.arcgismaps.mapping.layers.BuildingSceneLayer
import com.arcgismaps.mapping.layers.buildingscene.BuildingComponentSublayer
import com.arcgismaps.mapping.layers.buildingscene.BuildingFilter
import com.arcgismaps.mapping.layers.buildingscene.BuildingFilterBlock
import com.arcgismaps.mapping.layers.buildingscene.BuildingGroupSublayer
import com.arcgismaps.mapping.layers.buildingscene.BuildingSolidFilterMode
import com.arcgismaps.mapping.layers.buildingscene.BuildingSublayer
import com.arcgismaps.mapping.layers.buildingscene.BuildingXrayFilterMode
import com.arcgismaps.mapping.popup.Popup
import com.arcgismaps.toolkit.popup.PopupState
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class FilterBuildingSceneLayerViewModel(app: Application) : AndroidViewModel(app) {
val scene = ArcGISScene("https://www.arcgis.com/home/item.html?id=b7c387d599a84a50aafaece5ca139d44")
// State to control if a loading progress indicator is shown
private val _showLoadingDialog = MutableStateFlow(true)
val showLoadingDialog = _showLoadingDialog.asStateFlow()
// Building scene layer that will be filtered. Set after the WebScene is loaded.
var buildingSceneLayer: BuildingSceneLayer? = null
// The selected floor
var selectedFloor by mutableStateOf("All")
// The list of available floors
val floors: MutableList<String> = mutableListOf(selectedFloor)
// The list of building sublayer categories
val categories: MutableList<BuildingSublayer> = mutableListOf()
// Create a message dialog view model for handling error messages
val messageDialogVM = MessageDialogViewModel()
// Building scene layer sublayer that contains the currently selected feature
var sublayerWithSelection : BuildingComponentSublayer? = null
// State that will contain a popup state for an identify result
private val _popupState = MutableStateFlow<PopupState?>(null)
val popupState = _popupState.asStateFlow()
init {
viewModelScope.launch {
scene.load().onFailure {
messageDialogVM.showMessageDialog(it)
_showLoadingDialog.value = false
}.onSuccess {
_showLoadingDialog.value = false
buildingSceneLayer =
scene.operationalLayers.first { layer ->
layer is BuildingSceneLayer
} as BuildingSceneLayer
buildingSceneLayer?.let { buildingSceneLayer ->
// Get the floor listing from the statistics
buildingSceneLayer.fetchStatistics().onSuccess { statistics ->
statistics["BldgLevel"]?.mostFrequentValues?.let {
floors.addAll(0, it.sorted())
}
// The top-level sublayer groups will be the categories
buildingSceneLayer.sublayers.find { sublayer ->
sublayer.modelName == "FullModel"
}?.let { buildingSublayer ->
buildingSublayer as BuildingGroupSublayer
categories.addAll(buildingSublayer.sublayers.sortedBy { it.name })
}
}
}
}
}
}
/**
* Updates the building filters based on the selected floor
*/
fun selectFloor(index: Int) {
selectedFloor = floors[index]
buildingSceneLayer?.let { buildingSceneLayer ->
if (selectedFloor == "All") {
// No filtering applied if 'All' floors are selected
buildingSceneLayer.activeFilter = null
return
}
// Build a building filter to show the selected floor and an xray view of the floors below.
// Floors above the selected floor are not shown at all.
val buildingFilter = BuildingFilter(
name = "Floor filter",
description = "Show selected floor and xray filter for lower floors.",
listOf(
BuildingFilterBlock(
title = "solid block",
whereClause = "BldgLevel = $selectedFloor",
mode = BuildingSolidFilterMode()
),
BuildingFilterBlock(
title = "x ray block",
whereClause = "BldgLevel < $selectedFloor",
mode = BuildingXrayFilterMode()
)
)
)
buildingSceneLayer.activeFilter = buildingFilter
}
}
/**
* Creates a popup state to display identify result
*/
fun createPopupState(popup: Popup) {
_popupState.value = PopupState(popup = popup, scope = viewModelScope)
}
/**
* Dismisses an identify result
*/
fun dismissPopup() {
_popupState.value = null
}
}