Skip to content
View on GitHub

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

Image of filter features in scene sample

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 the "Navigation" 3D Basemap. Tap the "Filter" button to set a SceneLayerPolygonFilter and filter out the Esri 3D buildings within the extent of a detailed buildings scene layer. Notice how the Esri 3D buildings within and intersecting the extent of the detailed buildings layer are hidden. Tap the "Show Detailed Buildings" button to load a scene layer that contains more detailed buildings. Tap the "Reset" button to hide the detailed buildings scene layer and clear the 3D buildings filter.

How it works

  1. Create a Surface for the scene and set the World Elevation 3D as an elevation source.
  2. Create a Basemap for the scene using the "Navigation" 3D Basemap, load it, then search for the "Buildings" base layer.
  3. Add the 3D San Francisco Buildings ArcGISSceneLayer to the scene's operational layers and set its visibility to false so it does not intersect the 3D basemap buildings.
  4. Create a SceneLayerPolygonFilter with the extent of the San Francisco Buildings Scene Layer and the SceneLayerPolygonFilter.SpatialRelationship.disjoint enum to hide all features within the extent.
  5. Set the SceneLayerPolygonFilter on the Buildings layer to hide all buildings within the extent of the San Francisco Buildings layer.
  6. Set the visibility of the 3D San Francisco Buildings layer to true to show the 3D buildings in the extent.

Relevant API

  • ArcGISSceneLayer
  • SceneLayerPolygonFilter
  • SceneLayerPolygonFilter.SpatialRelationship

About the data

This sample uses the Navigation 3D Basemap, which includes commercial 3D buildings data acquired from TomTom and Vantor, in addition to Esri Community Maps and Overture Maps Foundation data. It also 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 SceneLayerPolygonFilter.SpatialRelationship.disjoint to hide all features within the extent of the given geometry. You can alternatively use SceneLayerPolygonFilter.SpatialRelationship.contains to only show features within the extent of the geometry.

You can also show or hide features in a scene layer using ArcGISSceneLayer.setVisible(_:for:) and pass in a feature or a list of features and a boolean value to set their visibility.

Tags

3D, buildings, disjoint, exclude, extent, filter, hide, polygon

Sample Code

FilterFeaturesInSceneView.swift
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
// Copyright 2023 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
//
//   https://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.

import ArcGIS
import SwiftUI

struct FilterFeaturesInSceneView: View {
    /// The result of the loading the view model for this sample.
    @State private var modelResult: Result<Model, any Error>?

    var body: some View {
        switch modelResult {
        case .success(let model):
            SceneView(scene: model.scene, graphicsOverlays: [model.graphicsOverlay])
                .toolbar {
                    ToolbarItem(placement: .bottomBar) {
                        switch model.filterState {
                        case .filter:
                            Button("Filter", action: model.filterScene)
                        case .showDetailedBuildings:
                            Button("Show Detailed Buildings", action: model.showDetailedBuildings)
                        case .reset:
                            Button("Reset", action: model.resetScene)
                        }
                    }
                }
        case .failure(let error):
            ContentUnavailableView {
                Label("Error Setting Up Sample", systemImage: "exclamationmark.triangle")
            } description: {
                Text(error.localizedDescription)
            } actions: {
                Button("Retry") { modelResult = nil }
            }
        case .none:
            ProgressView("Loading model")
                .task {
                    modelResult = await Result(awaiting: Model.init)
                }
        }
    }
}

private extension FilterFeaturesInSceneView {
    /// The model used to store the geo model and other expensive objects
    /// used in this view.
    @Observable
    final class Model {
        /// The scene for this sample.
        let scene: ArcGIS.Scene = {
            let scene = Scene()

            // Adds the World Elevation 3D elevation source to the scene's base surface.
            let elevationSource = ArcGISTiledElevationSource(url: .worldElevationService)
            scene.baseSurface.addElevationSource(elevationSource)

            // Sets the initial viewpoint for the scene.
            scene.initialViewpoint = .sanFranciscoBuildings

            return scene
        }()

        /// The "Buildings" ArcGIS scene layer from the scene's basemap.
        private let buildingsLayer: ArcGISSceneLayer

        /// An ArcGIS scene layer containing detailed buildings in San Francisco, CA, USA.
        private let detailedBuildingsLayer = ArcGISSceneLayer(url: .sanFranciscoBuildings)

        /// The graphics overlay for the scene view.
        let graphicsOverlay = GraphicsOverlay()

        /// A polygon filter for filtering the features in the `buildingsLayer`.
        private let polygonFilter: SceneLayerPolygonFilter

        /// A red extent boundary graphic that represents the full extent of the detailed buildings scene layer.
        private let sanFranciscoExtentGraphic: Graphic = {
            // Creates a graphic from a red outline symbol.
            let redLineSymbol = SimpleLineSymbol(color: .red, width: 5)
            let redOutlineFillSymbol = SimpleFillSymbol(color: .clear, outline: redLineSymbol)
            let graphic = Graphic(symbol: redOutlineFillSymbol)

            // Initially hides the graphic, since the filter has not been applied yet.
            graphic.isVisible = false

            return graphic
        }()

        /// The different states for filtering features in a scene.
        enum FilterState {
            case filter, showDetailedBuildings, reset
        }

        /// The filter state for the scene view.
        private(set) var filterState: FilterState = .filter

        /// An error that can occur during the model's initialization.
        enum InitializationError: Error {
            case missingBuildingsLayer
            case missingDetailedBuildingsLayerExtent
        }

        init() async throws {
            // Creates the "Navigation" 3D basemap and sets it on the scene.
            let basemap = Basemap(url: .navigation3DBasemap)!
            scene.basemap = basemap

            // Gets the "Buildings" base layer from the basemap.
            try await basemap.load()
            guard let buildingsLayer = basemap.baseLayers
                .first(where: { $0.name == "Buildings" }) as? ArcGISSceneLayer else {
                throw InitializationError.missingBuildingsLayer
            }
            self.buildingsLayer = buildingsLayer

            // Creates a polygon from the detailedBuildingsLayer's extent.
            try await detailedBuildingsLayer.load()
            guard let extent = detailedBuildingsLayer.fullExtent else {
                throw InitializationError.missingDetailedBuildingsLayerExtent
            }
            let polygon = Polygon(
                points: [
                    Point(x: extent.xMin, y: extent.yMin),
                    Point(x: extent.xMax, y: extent.yMin),
                    Point(x: extent.xMax, y: extent.yMax),
                    Point(x: extent.xMin, y: extent.yMax)
                ]
            )

            // Adds the polygon to the graphic and adds the graphic to the overlay.
            sanFranciscoExtentGraphic.geometry = polygon
            graphicsOverlay.addGraphic(sanFranciscoExtentGraphic)

            // Creates a disjoint scene layer polygon filter using the polygon.
            polygonFilter = SceneLayerPolygonFilter(
                polygons: [polygon],
                spatialRelationship: .disjoint
            )

            // Adds the detailed buildings layer to the scene.
            // The layer is also initially hidden to that it doesn't
            // clip into the `buildingsLayer` while it is unfiltered.
            detailedBuildingsLayer.isVisible = false
            scene.addOperationalLayer(detailedBuildingsLayer)
        }

        /// Filters the `buildingsLayer` features within the `detailedBuildingsLayer` extent.
        func filterScene() {
            // Applies the polygon filter to the buildings layer.
            buildingsLayer.polygonFilter = polygonFilter
            // Shows graphic to indicate the filtered extent.
            sanFranciscoExtentGraphic.isVisible = true

            filterState = .showDetailedBuildings
        }

        /// Shows the detailed buildings scene layer.
        func showDetailedBuildings() {
            detailedBuildingsLayer.isVisible = true

            filterState = .reset
        }

        /// Resets the scene filters and hides the detailed buildings and extent graphic.
        func resetScene() {
            // Hides the detailed buildings layer.
            detailedBuildingsLayer.isVisible = false
            // Removes the polygon filter from the building layer.
            buildingsLayer.polygonFilter = nil
            // Hides the red extent boundary graphic.
            sanFranciscoExtentGraphic.isVisible = false

            filterState = .filter
        }
    }
}

private extension URL {
    /// The URL to the "Navigation" 3D basemap on ArcGIS Online.
    static var navigation3DBasemap: URL {
        URL(string: "https://www.arcgis.com/home/item.html?id=00a5f468dda941d7bf0b51c144aae3f0")!
    }

    /// The URL to the San Francisco Buildings scene server on ArcGIS REST.
    static var sanFranciscoBuildings: URL {
        URL(string: "https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/SanFrancisco_Bldgs/SceneServer")!
    }

    /// The URL to the World Elevation 3D image server on ArcGIS REST.
    static var worldElevationService: URL {
        URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!
    }
}

private extension Viewpoint {
    /// The initial viewpoint to be displayed when the sample is first opened.
    static let sanFranciscoBuildings = Viewpoint(
        latitude: .nan,
        longitude: .nan,
        scale: .nan,
        camera: Camera(
            latitude: 37.702425,
            longitude: -122.421008,
            altitude: 207,
            heading: 60,
            pitch: 70,
            roll: 0
        )
    )
}

#Preview {
    NavigationStack {
        FilterFeaturesInSceneView()
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.