Query related features

View on GitHub

List features related to the selected feature.

Image of Query related features sample

Use case

Related features are useful for managing relational information, like what you would store in a relational database management system (RDBMS). You can define relationship between records as one-to-one, one-to-many, or many-to-one. For example, you could model inspections and facilities as a many-to-one relationship. Then, for any facility feature, you could list related inspection features.

How to use the sample

Tap on a feature to select it, and the related features will be displayed in a list.

How it works

  1. With a ArcGISFeature, call ArcGISFeatureTable.queryRelatedFeatures(to:using:) on the feature's feature table.
  2. Iterate over the result's collection of RelatedFeatureQueryResult objects to get the related features and add them to a list.

Relevant API

  • ArcGISFeature
  • ArcGISFeatureTable
  • FeatureQueryResult
  • RelatedFeatureQueryResult

Tags

features, identify, query, related, relationship, search

Sample Code

QueryRelatedFeaturesView.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
// Copyright 2024 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 QueryRelatedFeaturesView: View {
    /// A map with a topographic basemap.
    @State private var map: Map = {
        let map = Map(basemapStyle: .arcGISTopographic)

        // Creates and adds feature tables to the map to allow related feature querying.
        let preservesFeatureTable = ServiceFeatureTable(
            url: .alaskaParksFeatureServer.appending(component: "0")
        )
        let speciesFeatureTable = ServiceFeatureTable(
            url: .alaskaParksFeatureServer.appending(component: "2")
        )
        map.addTables([preservesFeatureTable, speciesFeatureTable])

        return map
    }()

    /// An "Alaska National Parks" feature layer.
    @State private var parksFeatureLayer = FeatureLayer(
        featureTable: ServiceFeatureTable(
            url: .alaskaParksFeatureServer.appending(component: "1")
        )
    )

    /// The text describing the loading status of the sample.
    @State private var loadingStatusText: String?

    /// The point on the screen where the user tapped.
    @State private var tapPoint: CGPoint?

    /// The results from querying related features.
    @State private var queryResults: RelatedFeatureQueryResults?

    /// The error shown in the error alert.
    @State private var error: Error?

    var body: some View {
        MapViewReader { mapViewProxy in
            MapView(map: map)
                .onSingleTapGesture { screenPoint, _ in
                    tapPoint = screenPoint
                }
                .task(id: tapPoint) {
                    guard let tapPoint else { return }
                    parksFeatureLayer.clearSelection()

                    do {
                        // Identifies the tapped screen point.
                        loadingStatusText = "Identifying feature…"
                        defer { loadingStatusText = nil }

                        let identifyResult = try await mapViewProxy.identify(
                            on: parksFeatureLayer,
                            screenPoint: tapPoint,
                            tolerance: 12
                        )

                        // Selects the first feature in the result.
                        guard let parkFeature = identifyResult.geoElements.first
                                as? ArcGISFeature else { return }
                        parksFeatureLayer.selectFeature(parkFeature)

                        // Queries for related features of the identified feature.
                        loadingStatusText = "Querying related features…"

                        let parksFeatureTable = parksFeatureLayer.featureTable as! ServiceFeatureTable
                        let queryResults = try await parksFeatureTable.queryRelatedFeatures(
                            to: parkFeature
                        )
                        self.queryResults = RelatedFeatureQueryResults(results: queryResults)
                    } catch {
                        self.error = error
                    }
                }
                .sheet(
                    item: $queryResults,
                    onDismiss: parksFeatureLayer.clearSelection
                ) { newQueryResults in
                    RelatedFeaturesList(results: newQueryResults.results)
                        .frame(idealWidth: 320, idealHeight: 380)
                }
                .overlay(alignment: .center) {
                    // Shows a progress view when there is a loading status.
                    if let loadingStatusText {
                        VStack {
                            Text(loadingStatusText)
                            ProgressView()
                        }
                        .padding()
                        .background(.ultraThickMaterial)
                        .cornerRadius(10)
                        .shadow(radius: 50)
                    }
                }
                .task {
                    // Loads the parks feature layer and zooms the viewpoint to its extent.
                    do {
                        loadingStatusText = "Loading feature layer…"
                        defer { loadingStatusText = nil }

                        try await parksFeatureLayer.load()
                        map.addOperationalLayer(parksFeatureLayer)

                        guard let parksLayerExtent = parksFeatureLayer.fullExtent else { return }
                        await mapViewProxy.setViewpointGeometry(parksLayerExtent, padding: 20)
                    } catch {
                        self.error = error
                    }
                }
                .errorAlert(presentingError: $error)
        }
    }
}

private extension QueryRelatedFeaturesView {
    /// A list of features from given related feature query results.
    struct RelatedFeaturesList: View {
        /// The results to display in the list.
        let results: [RelatedFeatureQueryResult]

        /// The action to dismiss the view.
        @Environment(\.dismiss) private var dismiss

        var body: some View {
            NavigationStack {
                List {
                    ForEach(Array(results.enumerated()), id: \.offset) { offset, result in
                        let relatedTableName = result.relatedTable?.tableName
                        Section(relatedTableName ?? "Feature Table \(offset + 1)") {
                            ForEach(result.featureDisplayNames, id: \.self) { featureName in
                                Text(featureName)
                            }
                        }
                    }
                }
                .navigationTitle(
                    results.first?.feature?.attributes["UNIT_NAME"] as? String ?? "Origin Feature"
                )
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItem(placement: .confirmationAction) {
                        Button("Done") { dismiss() }
                    }
                }
            }
        }
    }

    /// A struct containing the results from a related features query.
    struct RelatedFeatureQueryResults: Identifiable {
        /// A universally unique id to identify the type.
        let id = UUID()
        /// The related feature query results.
        let results: [RelatedFeatureQueryResult]
    }
}

private extension RelatedFeatureQueryResult {
    /// The display names of the result's features.
    var featureDisplayNames: [String] {
        guard let displayFieldName = relatedTable?.layerInfo?.displayFieldName else { return [] }
        return features()
            .compactMap { $0.attributes[displayFieldName] as? String }
            .sorted()
    }
}

private extension URL {
    /// The URL to the "Alaska National Parks Preserves Species" feature server.
    static var alaskaParksFeatureServer: URL {
        URL(string: "https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/arcgis/rest/services/AlaskaNationalParksPreservesSpecies_List/FeatureServer")!
    }
}

#Preview {
    QueryRelatedFeaturesView()
}

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