Filter 3D scene features out of a given geometry with a polygon filter.

Use case
You can directly control what users see within a specific scene view to give a more focused or cleaner user experience by using a SceneLayerPolygonFilter to selectively show or hide scene features within a given area.
How to use the sample
The sample initializes showing overlapping datasets of 3D buildings from the OpenStreetMap layer and an additional detailed scene layer of buildings in San Francisco. Notice how the two scene layers overlap and clip into each other. Click the “Filter OSM buildings” button, to set a SceneLayerPolygonFilter and filter out the OpenStreetMap buildings within the extent of the detailed buildings scene. Notice how the OSM buildings within and intersecting the extent of the detailed buildings layer are hidden.
How it works
- Construct an
ArcGISSceneand add aSurfaceelevation source set to the World Elevation 3D as an elevation source. - Add the two
ArcGISSceneLayers building scene layers to theArcGISScenes operational layers. - Construct a
SceneLayerPolygonFilterwith the extent of the San Francisco Buildings Scene Layer and theSceneLayerPolygonFilterSpatialRelationship.Disjointobject to hide all features within the extent. - Set the
SceneLayerPolygonFilteron the OSM Buildings layer to hide all OSM buildings within the extent of the San Francisco Buildings layer.
Relevant API
- ArcGISSceneLayer
- SceneLayerPolygonFilter
- SceneLayerPolygonFilterSpatialRelationship
About the data
This sample uses the OpenStreetMap 3D Buildings which provides generic 3D outlines of buildings throughout the world. It is based on the OSM Daylight map distribution and is hosted by Esri. It uses the San Francisco 3D Buildings scene layer which provides detailed 3D models of buildings in San Francisco, California, USA.
Additional information
This sample uses SceneLayerPolygonFilterSpatialRelationship.Disjoint to hide all features within the extent of the given geometry. You can alternatively use SceneLayerPolygonFilterSpatialRelationship.Contains to only show features within the extent of the geometry.
You can also show or hide features in a scene layer using ArcGISSceneLayer.setFeatureVisible(...) and pass in a feature or list of features and a boolean value to set their visibility.
Tags
3D, buildings, disjoint, exclude, extent, filter, hide, OSM, polygon
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.filterfeaturesinscene
import android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport androidx.compose.runtime.Composableimport com.arcgismaps.ApiKeyimport com.arcgismaps.ArcGISEnvironmentimport com.esri.arcgismaps.sample.sampleslib.theme.SampleAppThemeimport com.esri.arcgismaps.sample.filterfeaturesinscene.screens.FilterFeaturesInSceneScreen
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // authentication with an API key or named user is // required to access basemaps and other location services ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.ACCESS_TOKEN)
enableEdgeToEdge() setContent { SampleAppTheme { FilterFeaturesInSceneApp() } } }
@Composable private fun FilterFeaturesInSceneApp() { Surface(color = MaterialTheme.colorScheme.background) { FilterFeaturesInSceneScreen( sampleName = getString(R.string.filter_features_in_scene_app_name) ) } }}/* 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.filterfeaturesinscene.components
import android.app.Applicationimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.lifecycle.AndroidViewModelimport androidx.lifecycle.viewModelScopeimport com.arcgismaps.Colorimport com.arcgismaps.geometry.Polygonimport com.arcgismaps.geometry.PolygonBuilderimport com.arcgismaps.mapping.ArcGISSceneimport com.arcgismaps.mapping.ArcGISTiledElevationSourceimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.PortalItemimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.mapping.layers.ArcGISSceneLayerimport com.arcgismaps.mapping.layers.SceneLayerPolygonFilterimport com.arcgismaps.mapping.layers.SceneLayerPolygonFilterSpatialRelationshipimport com.arcgismaps.mapping.symbology.SimpleFillSymbolimport com.arcgismaps.mapping.symbology.SimpleFillSymbolStyleimport com.arcgismaps.mapping.symbology.SimpleLineSymbolimport com.arcgismaps.mapping.symbology.SimpleLineSymbolStyleimport com.arcgismaps.mapping.view.Cameraimport com.arcgismaps.mapping.view.Graphicimport com.arcgismaps.mapping.view.GraphicsOverlayimport com.arcgismaps.portal.Portalimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModelimport kotlinx.coroutines.launch
class FilterFeaturesInSceneViewModel(application: Application) : AndroidViewModel(application) {
// create a ViewModel to handle dialog interactions val messageDialogVM: MessageDialogViewModel = MessageDialogViewModel()
// Create an OSM Buildings ArcGISSceneLayer from a portal item private val osmBuildingsSceneLayer = ArcGISSceneLayer( PortalItem( portal = Portal("https://www.arcgis.com"), itemId = "ca0470dbbddb4db28bad74ed39949e25" ) )
// Create a San Francisco Buildings ArcGISSceneLayer from a url private val sanFranciscoBuildingsSceneLayer = ArcGISSceneLayer(uri = "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/SanFrancisco_Bldgs/SceneServer")
// Create a new ArcGISScene and set a basemap from a portal item with a vector tile layer val arcGISScene: ArcGISScene by mutableStateOf(ArcGISScene(BasemapStyle.ArcGISTopographic).apply { // Add an elevation source to the scene's base surface. val tiledElevationSource = ArcGISTiledElevationSource(uri = "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer") baseSurface.elevationSources.add(tiledElevationSource) // OSM building layer operationalLayers.add(osmBuildingsSceneLayer) // Add the buildings scene layer to the operational layers operationalLayers.add(sanFranciscoBuildingsSceneLayer) // Set the initial viewpoint of the scene to San Francisco initialViewpoint = Viewpoint( latitude = 37.7041, longitude = -122.421, 1000.0, Camera( latitude = 37.7041, longitude = -122.421, altitude = 207.0, heading = 60.0, pitch = 70.0, roll = 0.0 ) ) })
// Define a red boundary graphic private val boundaryGraphic = Graphic( symbol = SimpleFillSymbol( style = SimpleFillSymbolStyle.Solid, color = Color.transparent, outline = SimpleLineSymbol( style = SimpleLineSymbolStyle.Solid, color = Color.red, width = 5.0f ) ) )
// Set up graphic overlay and graphic for the San Francisco buildings layer extent. val graphicsOverlay = GraphicsOverlay(listOf(boundaryGraphic))
init { loadBuildingsLayer() }
/** * Load the San Francisco buildings layer and create a polygon boundary using the layer extent. */ private fun loadBuildingsLayer() { viewModelScope.launch { // Load the San Francisco buildings layer sanFranciscoBuildingsSceneLayer.load().onFailure { messageDialogVM.showMessageDialog(it.message.toString(), it.cause.toString()) } // Create a polygon boundary using the San Francisco buildings layer extent sanFranciscoBuildingsSceneLayer.fullExtent?.let { boundaryGraphic.geometry = PolygonBuilder().apply { addPoint(it.xMin, it.yMin) addPoint(it.xMax, it.yMin) addPoint(it.xMax, it.yMax) addPoint(it.xMin, it.yMax) }.toGeometry() } } }
/** * Filter the OSM buildings layer to only show buildings that are disjoint from the San Francisco buildings layer * extent. */ fun filterScene() { // Check that the San Francisco buildings layer extent is available (boundaryGraphic.geometry as? Polygon)?.let { boundary -> // Create a polygon filter with the San Francisco buildings layer boundary polygon and set it to be disjoint val sceneLayerPolygonFilter = SceneLayerPolygonFilter( polygons = listOf(boundary), spatialRelationship = SceneLayerPolygonFilterSpatialRelationship.Disjoint ) // Set the polygon filter to the OSM buildings layer osmBuildingsSceneLayer.polygonFilter = sceneLayerPolygonFilter } }
/** * Reset the OSM buildings layer filter to show all buildings. */ fun resetFilter() { // Clear all polygon filters osmBuildingsSceneLayer.polygonFilter?.polygons?.clear() }}/* 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.filterfeaturesinscene.screens
import androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.FabPositionimport androidx.compose.material3.FloatingActionButtonimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifierimport androidx.compose.ui.res.stringResourceimport androidx.compose.ui.unit.dpimport androidx.lifecycle.viewmodel.compose.viewModelimport com.arcgismaps.toolkit.geoviewcompose.SceneViewimport com.esri.arcgismaps.sample.filterfeaturesinscene.Rimport com.esri.arcgismaps.sample.filterfeaturesinscene.components.FilterFeaturesInSceneViewModelimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogimport com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar
/** * Main screen layout for the sample app */@Composablefun FilterFeaturesInSceneScreen(sampleName: String) { val sceneViewModel: FilterFeaturesInSceneViewModel = viewModel()
var filteringScene by remember { mutableStateOf(false) }
Scaffold(topBar = { SampleTopAppBar(title = sampleName) }, content = { padding -> Column( modifier = Modifier .padding(padding) .fillMaxSize() ) { SceneView( modifier = Modifier .fillMaxSize() .weight(1f), arcGISScene = sceneViewModel.arcGISScene, graphicsOverlays = listOf(sceneViewModel.graphicsOverlay) ) // display a MessageDialog if the sample encounters an error sceneViewModel.messageDialogVM.apply { if (dialogStatus) { MessageDialog( title = messageTitle, description = messageDescription, onDismissRequest = ::dismissDialog ) } } } }, floatingActionButton = { FloatingActionButton( modifier = Modifier.padding(bottom = 16.dp), onClick = { if (!filteringScene) { sceneViewModel.filterScene() filteringScene = true } else { sceneViewModel.resetFilter() filteringScene = false } }) { Text( modifier = Modifier.padding(8.dp), text = if (!filteringScene) { stringResource(R.string.filter_osm_buildings) } else { stringResource(R.string.reset_filter) } ) } }, floatingActionButtonPosition = FabPosition.Center )}