Query related features from non-spatial table

View on GitHubSample viewer app

Find features in a spatial table related to features in a non-spatial table.

Query related features from non-spatial table

Use case

The non-spatial tables contained by a map service may contain additional information about sublayer features. Such information can be accessed by traversing table relationships defined in the service.

How to use the sample

Tap the toolbar button to prompt a list of comment data from non-spatial features. Tap on one of the comments to query related spatial features and display the first result on the map.

How it works

  1. Create an AGSArcGISMapImageLayer with the URL of a map service.
  2. Load the tables and layers using loadTablesAndLayers(completion:) and get its first table.
  3. To query the table, create AGSQueryParameters. Set its whereClause to filter the request features.
  4. Use queryFeatures(with:queryFeatureFields:completion:) to get the AGSFeatureQueryResult.
  5. Make AGSFeatureQueryResult iterable using featureEnumerator()and loop through to get each AGSFeature.
  6. To query for related features, get the table's relationship info with AGSServiceFeatureTable.layerInfo.relationshipInfos. This returns an array of AGSRelationshipInfos.
  7. Now create AGSRelatedQueryParameters passing in the AGSRelationshipInfo. To query related features, use AGSServiceFeatureTable.queryRelatedFeatures(for:parameters:completion:).
  8. This returns an array of AGSRelatedFeatureQueryResults, each containing a set of related features.

Relevant API

  • AGSArcGISFeature
  • AGSArcGISMapImageLayer
  • AGSFeature
  • AGSFeatureQueryResult
  • AGSQueryParameters
  • AGSRelatedFeatureQueryResult
  • AGSRelatedQueryParameters
  • AGSRelationshipInfo
  • AGSServiceFeatureTable

About the data

This sample uses the Naperville map service, which is used to collect non-emergency requests for service from the general public.

Tags

features, query, related features, search

Sample Code

QueryRelatedFeaturesNonSpatialTableViewController.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
// Copyright 2022 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.

import UIKit
import ArcGIS

class QueryRelatedFeaturesNonSpatialTableViewController: UIViewController {
    @IBOutlet var mapView: AGSMapView! {
        didSet {
            // Assign the map to the map view.
            mapView.map = makeMap()
            // Set the viewpoint.
            mapView.setViewpoint(AGSViewpoint(latitude: 41.734152, longitude: -88.163718, scale: 2e5))
            // Add a graphics overlay to show selected features and add it to the map view.
            mapView.graphicsOverlays.add(selectedFeaturesOverlay)
        }
    }

    @IBOutlet var queryBarButtonItem: UIBarButtonItem!

    /// The map image layer that uses the service URL.
    let serviceRequestsMapImageLayer = AGSArcGISMapImageLayer(
        url: URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/ServiceRequest/MapServer")!
    )
    /// The (non-spatial) table that contains the service request comments.
    var commentsTable: AGSServiceFeatureTable?
    /// The array to store the possible comments.
    var comments: [AGSFeature] = []
    /// The graphics overlay to add graphics to.
    let selectedFeaturesOverlay: AGSGraphicsOverlay = {
        let overlay = AGSGraphicsOverlay()
        overlay.renderer = AGSSimpleRenderer(
            symbol: AGSSimpleMarkerSymbol(style: .circle, color: .cyan, size: 14)
        )
        return overlay
    }()

    @IBAction func queryFeaturesActions(_ sender: UIBarButtonItem) {
        // Create an action sheet to display the various comments to choose from.
        let alertController = UIAlertController(title: "Related Service Requests", message: "Select a comment to view related spatial features on the map.", preferredStyle: .actionSheet)
        // Create an action for each comment.
        comments.forEach { feature in
            // Extract the "comments" attribute as a string.
            let title = feature.attributes["comments"] as! String
            // Create an action with the comments title.
            let action = UIAlertAction(title: title, style: .default) { _ in
                // Clear the former graphics.
                self.selectedFeaturesOverlay.graphics.removeAllObjects()
                // Disable the query button while the feature loads.
                self.queryBarButtonItem.isEnabled = false
                // Cast the selected feature as an AGSArcGISFeature.
                guard let selectedFeature = feature as? AGSArcGISFeature else { return }
                self.queryCommentsTable(feature: selectedFeature)
            }
            // Add the action to the controller.
            alertController.addAction(action)
        }
        // Add "cancel" item.
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
        alertController.addAction(cancelAction)
        // Present the controller.
        alertController.popoverPresentationController?.barButtonItem = queryBarButtonItem
        present(alertController, animated: true)
    }

    /// Make a map for the map view.
    func makeMap() -> AGSMap {
        // Create a map with the ArcGIS streets basemap style.
        let map = AGSMap(basemapStyle: .arcGISStreets)
        // Add the layer to the map.
        map.operationalLayers.add(serviceRequestsMapImageLayer)
        // Load the map image layer's tables and layers.
        serviceRequestsMapImageLayer.loadTablesAndLayers { [weak self] _ in
            self?.queryFeatures()
        }
        return map
    }

    /// Query features on the first table in the map image layer.
    func queryFeatures() {
        // Create query parameters and set its where clause.
        let nullCommentsParameters = AGSQueryParameters()
        nullCommentsParameters.whereClause = "requestid <> '' AND comments <> ''"
        // Set the first table from the map image layer.
        commentsTable = serviceRequestsMapImageLayer.tables.first
        // Query features on the feature table with the query parameters and all feature fields.
        commentsTable?.queryFeatures(with: nullCommentsParameters, queryFeatureFields: .loadAll) { [weak self] result, error in
            guard let self = self else { return }
            if let comments = result?.featureEnumerator().allObjects {
                // Show the records from the service request comments table in the list view control.
                self.comments = comments
                // Enable the button after the map and features have been loaded.
                self.queryBarButtonItem.isEnabled = true
            } else if let error = error {
                self.presentAlert(error: error)
            }
        }
    }

    /// Query related features for the selected feature.
    func queryCommentsTable(feature: AGSArcGISFeature) {
        // Get the relationship that defines related service request features for features in the comments table (this is the first and only relationship).
        guard let relationshipInfo = commentsTable?.layerInfo?.relationshipInfos.first else { return }
        // Create query parameters to get the related service request for features in the comments table.
        let relatedQueryParameters = AGSRelatedQueryParameters(relationshipInfo: relationshipInfo)
        relatedQueryParameters.returnGeometry = true
        // Query related features for the selected comment and its related query parameters.
        commentsTable?.queryRelatedFeatures(for: feature, parameters: relatedQueryParameters) { [weak self] results, error in
            // Get the first related feature.
            if let relatedFeature = results?.first?.featureEnumerator().nextObject() as? AGSArcGISFeature {
                // Load the feature and get its geometry to show as a graphic on the map.
                relatedFeature.load { error in
                    guard let self = self else { return }
                    if let error = error {
                        self.presentAlert(error: error)
                    } else if let serviceRequestPoint = relatedFeature.geometry as? AGSPoint {
                        // Create a graphic to add to the graphics overlay.
                        let graphic = AGSGraphic(geometry: serviceRequestPoint, symbol: nil)
                        self.selectedFeaturesOverlay.graphics.add(graphic)
                        // Set the viewpoint to the the related feature.
                        self.mapView.setViewpointCenter(serviceRequestPoint, scale: 150_000)
                        // Enable the button after the feature has finished loading.
                        self.queryBarButtonItem.isEnabled = true
                    }
                }
            } else {
                // Present an error message is the related feature is not found.
                self?.presentAlert(title: "Related feature not found. No Feature", message: nil)
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Add the source code button item to the right of navigation bar.
        (self.navigationItem.rightBarButtonItem as? SourceCodeBarButtonItem)?.filenames = ["QueryRelatedFeaturesNonSpatialTableViewController"]
    }
}

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