Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trev8939/filter features in scene #312

Merged
merged 9 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions samples/filter-features-in-scene/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Filter features in scene

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

![Filter features in scene screenshot](filter-features-in-scene.png)

## 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

1. Construct an `ArcGISScene` and add a `Surface` elevation source set to the World Elevation 3D as an elevation source.
2. Add the two `ArcGISSceneLayer`s building scene layers to the `ArcGISScene`s operational layers.
3. Construct a `SceneLayerPolygonFilter` with the extent of the San Francisco Buildings Scene Layer and the `SceneLayerPolygonFilterSpatialRelationship.Disjoint` object to hide all features within the extent.
4. Set the `SceneLayerPolygonFilter` on 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](https://www.arcgis.com/home/item.html?id=ca0470dbbddb4db28bad74ed39949e25) 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](https://www.arcgis.com/home/item.html?id=d3344ba99c3f4efaa909ccfbcc052ed5) 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
36 changes: 36 additions & 0 deletions samples/filter-features-in-scene/README.metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"category": "Scenes",
"description": "Filter 3D scene features out of a given geometry with a polygon filter.",
"formal_name": "FilterFeaturesInScene",
"ignore": false,
"images": [
"filter-features-in-scene.png"
],
"keywords": [
"3D",
"OSM",
"buildings",
"disjoint",
"exclude",
"extent",
"filter",
"hide",
"polygon",
"ArcGISSceneLayer",
"SceneLayerPolygonFilter",
"SceneLayerPolygonFilterSpatialRelationship"
],
"language": "kotlin",
"redirect_from": "",
"relevant_apis": [
"ArcGISSceneLayer",
"SceneLayerPolygonFilter",
"SceneLayerPolygonFilterSpatialRelationship"
],
"snippets": [
"src/main/java/com/esri/arcgismaps/sample/filterfeaturesinscene/components/FilterFeaturesInSceneViewModel.kt",
"src/main/java/com/esri/arcgismaps/sample/filterfeaturesinscene/MainActivity.kt",
"src/main/java/com/esri/arcgismaps/sample/filterfeaturesinscene/screens/FilterFeaturesInSceneScreen.kt"
],
"title": "Filter features in scene"
}
22 changes: 22 additions & 0 deletions samples/filter-features-in-scene/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
alias(libs.plugins.arcgismaps.android.library)
alias(libs.plugins.arcgismaps.android.library.compose)
alias(libs.plugins.arcgismaps.kotlin.sample)
alias(libs.plugins.gradle.secrets)
}

secrets {
// this file doesn't contain secrets, it just provides defaults which can be committed into git.
defaultPropertiesFileName = "secrets.defaults.properties"
}

android {
namespace = "com.esri.arcgismaps.sample.filterfeaturesinscene"
buildFeatures {
buildConfig = true
}
}

dependencies {
// Only module specific dependencies needed here
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions samples/filter-features-in-scene/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />

<application><activity
android:exported="true"
android:name=".MainActivity"
android:label="@string/filter_features_in_scene_app_name">

</activity>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* 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.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment
import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme
import 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)

setContent {
SampleAppTheme {
FilterFeaturesInSceneApp()
}
}
}

@Composable
private fun FilterFeaturesInSceneApp() {
Surface(color = MaterialTheme.colorScheme.background) {
FilterFeaturesInSceneScreen(
sampleName = getString(R.string.filter_features_in_scene_app_name)
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/* 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.Application
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.Color
import com.arcgismaps.geometry.Polygon
import com.arcgismaps.geometry.PolygonBuilder
import com.arcgismaps.mapping.ArcGISScene
import com.arcgismaps.mapping.ArcGISTiledElevationSource
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.PortalItem
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.layers.ArcGISSceneLayer
import com.arcgismaps.mapping.layers.SceneLayerPolygonFilter
import com.arcgismaps.mapping.layers.SceneLayerPolygonFilterSpatialRelationship
import com.arcgismaps.mapping.symbology.SimpleFillSymbol
import com.arcgismaps.mapping.symbology.SimpleFillSymbolStyle
import com.arcgismaps.mapping.symbology.SimpleLineSymbol
import com.arcgismaps.mapping.symbology.SimpleLineSymbolStyle
import com.arcgismaps.mapping.view.Camera
import com.arcgismaps.mapping.view.Graphic
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.portal.Portal
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import 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()
}
}
Loading
Loading