Attempting to extract the layer or graphic details from the selected polyline

1033
6
Jump to solution
03-01-2022 10:20 PM
Narenkrishnaar
New Contributor II

Using sketchEditor in ArcGIS SDK, we are attempting to extract the layer or graphic details from the selected polyline in the Map view. I'm getting geometry values for selected areas, but is it possible to get layer details by passing this value through any APIs?  or Could anyone mention how to get the graphic details from the selected area?

0 Kudos
1 Solution

Accepted Solutions
Nicholas-Furness
Esri Regular Contributor

I see. So what you need to do is perform a Query (or multiple queries, if there are many layers).

Identify is designed to handle click or tap events on the map to find out what the user tapped on.

Query is much more powerful, and executes against the tables behind each layer in the map.

What you might do it look at the AGSMap.operationalLayers and determine a set of feature layers that you want to get information about. For each of those feature layers, get the feature table, create an AGSQueryParameters, set the geometry and spatialRelationship on that AGSQueryParameters to the geometry you get back from the sketch editor, and call query on the feature table.

Attached is a project that does this. Here's what the code looks like:

    func queryMapLayers(for polygon: AGSPolygon) {
        
        let queryParameters = AGSQueryParameters()
        queryParameters.spatialRelationship = .intersects
        queryParameters.geometry = polygon
        
        map.load { [weak self] error in
            guard let self = self else { return }
            
            if let error = error {
                print("Error loading the map: \(error.localizedDescription)")
                return
            }
            
            // Synchronize our results
            let queryGroup = DispatchGroup()
            
            var queryResults = [AGSFeatureTable: AGSFeatureQueryResult]()
            var queryErrors = [AGSFeatureTable: Error]()
            
            let currentScale = self.mapView.mapScale
            
            self.map.operationalLayers.lazy.compactMap {
                // Let's reduce the layers to just AGSFeatureLayers
                $0 as? AGSFeatureLayer
            }.filter {
                // Clear the selection from any previous selection.
                $0.clearSelection()
                
                // Make sure we just consider the currently visible layers.
                // Just return true if you want all layers, regardless of current scale.
                return $0.isVisible(atScale: currentScale)
            }.compactMap {
                // Convert the sequence to AGSFeatureTables
                $0.featureTable
            }.forEach { featureTable in
                // Query each feature table, coordinating responses using the DispatchGroup.
                queryGroup.enter()
                
                print("Querying \(featureTable.tableName)")
                featureTable.queryFeatures(with: queryParameters) { [featureTable] queryResult, error in
                    
                    print("Queryied \(featureTable.tableName)")
                    
                    defer {
                        // Let the dispatch group know we're done waiting for this table's query.
                        queryGroup.leave()
                    }
                    
                    if let error = error {
                        // Save the error to handle as a batch once everything returns using DispatchGroup.
                        queryErrors[featureTable] = error
                        return
                    }
                    
                    // Save the results to look at later, once ALL tables have responded.
                    queryResults[featureTable] = queryResult
                    
                    // Or look at the results as we get them
                    if let queryResult = queryResult {
                        // Output the results as we get them
                        print("Got results for table \(featureTable.tableName)")
                        
                        let layer = featureTable.layer as? AGSFeatureLayer
                        layer?.clearSelection()
                        
                        for case let feature as AGSFeature in queryResult.featureEnumerator() {
                            print(feature)
                            layer?.select(feature)
                        }
                    }
                    
                }
            }
            
            // You could handle errors and results as they come in above, but with a DispatchGroup,
            // you can control whether to wait for all results/errors to come back before doing
            // something. In this case we're handling each result as it comes in above, but coordinating
            // all errors we might get back.
            queryGroup.notify(queue: .main) {
                if !queryErrors.isEmpty {
                    print("\(queryErrors.count) tables failed on query:")
                    queryErrors.forEach { table, error in
                        print("Error querying table \(table.tableName): \(error)")
                        
                        let layer = table.layer as? AGSFeatureLayer
                        layer?.clearSelection()
                    }
                }
                
                // We looked at each result as it came in above, but we could instead/also look at them
                // all together here.
                // queryResults.forEach { table, result in
                //     print("Got results for table \(table.tableName)")
                //
                //     let layer = table.layer as? AGSFeatureLayer
                //     layer?.clearSelection()
                //
                //     for case let feature as AGSFeature in result.featureEnumerator() {
                //         print(feature)
                //         layer?.select(feature)
                //     }
                // }
            }
        }
    }

 Hope this helps!

View solution in original post

6 Replies
Nicholas-Furness
Esri Regular Contributor

Hi,

A graphic or feature displayed in a map combines a geometry (the shape) and a dictionary of attributes. The AGSSketchEditor purely works with geometries and doesn't relate to the feature or graphic itself. If you need to edit a feature or a graphic, you select or identify that feature or graphic, get its geometry, and pass that to the sketch editor. When the sketch editor is done, you updated the geometry on the original feature or graphic with the modified one the sketch editor provides, but it's up to you to keep hold of the feature or graphic.

If you need to get hold of a feature or graphic to obtain the attribution, call AGSMapView.identify() (there are a few different overloads of that method you can use). You can then get the AGSGraphic or AGSFeature the user tapped on and use the attributes dictionary to get or set attribute values.

Take a look at these samples for some more info:

Hope that helps.

0 Kudos
Narenkrishnaar
New Contributor II

Hello @Nicholas-Furness , thank you for your response. Please allow me to elaborate the requirement even more.
There is a feature on the map that allows you to select a certain area in the existing map using a poly line or any using other options. We need to show the layer details within the selected area, . I've attached a screenshot in which I've used sketchEditor to mark the area, and now I need to show the available layer data within that area. I'm not sure how to proceed because we need to pass some parameters, such as the screen point, in order to get the layer data. I don't think we'll be able to acquire that screen point manually. Also, once the user has marked a spot, I can get the geometry value from the sketchEditor, but I'm not sure if I can access the layer data from passing that. 

0 Kudos
Nicholas-Furness
Esri Regular Contributor

I see. So what you need to do is perform a Query (or multiple queries, if there are many layers).

Identify is designed to handle click or tap events on the map to find out what the user tapped on.

Query is much more powerful, and executes against the tables behind each layer in the map.

What you might do it look at the AGSMap.operationalLayers and determine a set of feature layers that you want to get information about. For each of those feature layers, get the feature table, create an AGSQueryParameters, set the geometry and spatialRelationship on that AGSQueryParameters to the geometry you get back from the sketch editor, and call query on the feature table.

Attached is a project that does this. Here's what the code looks like:

    func queryMapLayers(for polygon: AGSPolygon) {
        
        let queryParameters = AGSQueryParameters()
        queryParameters.spatialRelationship = .intersects
        queryParameters.geometry = polygon
        
        map.load { [weak self] error in
            guard let self = self else { return }
            
            if let error = error {
                print("Error loading the map: \(error.localizedDescription)")
                return
            }
            
            // Synchronize our results
            let queryGroup = DispatchGroup()
            
            var queryResults = [AGSFeatureTable: AGSFeatureQueryResult]()
            var queryErrors = [AGSFeatureTable: Error]()
            
            let currentScale = self.mapView.mapScale
            
            self.map.operationalLayers.lazy.compactMap {
                // Let's reduce the layers to just AGSFeatureLayers
                $0 as? AGSFeatureLayer
            }.filter {
                // Clear the selection from any previous selection.
                $0.clearSelection()
                
                // Make sure we just consider the currently visible layers.
                // Just return true if you want all layers, regardless of current scale.
                return $0.isVisible(atScale: currentScale)
            }.compactMap {
                // Convert the sequence to AGSFeatureTables
                $0.featureTable
            }.forEach { featureTable in
                // Query each feature table, coordinating responses using the DispatchGroup.
                queryGroup.enter()
                
                print("Querying \(featureTable.tableName)")
                featureTable.queryFeatures(with: queryParameters) { [featureTable] queryResult, error in
                    
                    print("Queryied \(featureTable.tableName)")
                    
                    defer {
                        // Let the dispatch group know we're done waiting for this table's query.
                        queryGroup.leave()
                    }
                    
                    if let error = error {
                        // Save the error to handle as a batch once everything returns using DispatchGroup.
                        queryErrors[featureTable] = error
                        return
                    }
                    
                    // Save the results to look at later, once ALL tables have responded.
                    queryResults[featureTable] = queryResult
                    
                    // Or look at the results as we get them
                    if let queryResult = queryResult {
                        // Output the results as we get them
                        print("Got results for table \(featureTable.tableName)")
                        
                        let layer = featureTable.layer as? AGSFeatureLayer
                        layer?.clearSelection()
                        
                        for case let feature as AGSFeature in queryResult.featureEnumerator() {
                            print(feature)
                            layer?.select(feature)
                        }
                    }
                    
                }
            }
            
            // You could handle errors and results as they come in above, but with a DispatchGroup,
            // you can control whether to wait for all results/errors to come back before doing
            // something. In this case we're handling each result as it comes in above, but coordinating
            // all errors we might get back.
            queryGroup.notify(queue: .main) {
                if !queryErrors.isEmpty {
                    print("\(queryErrors.count) tables failed on query:")
                    queryErrors.forEach { table, error in
                        print("Error querying table \(table.tableName): \(error)")
                        
                        let layer = table.layer as? AGSFeatureLayer
                        layer?.clearSelection()
                    }
                }
                
                // We looked at each result as it came in above, but we could instead/also look at them
                // all together here.
                // queryResults.forEach { table, result in
                //     print("Got results for table \(table.tableName)")
                //
                //     let layer = table.layer as? AGSFeatureLayer
                //     layer?.clearSelection()
                //
                //     for case let feature as AGSFeature in result.featureEnumerator() {
                //         print(feature)
                //         layer?.select(feature)
                //     }
                // }
            }
        }
    }

 Hope this helps!

Narenkrishnaar
New Contributor II

@Nicholas-Furness  Thank you so much, It's really worked. 

0 Kudos
Narenkrishnaar
New Contributor II

@Nicholas-Furness One more query,  I'm using the Mapserver URL, and as you stated, I tried using map.operationalLayers to filter the AGSFeatureLayer. Because the layer type is AGSArcGISMapImageLayer and the sublayers are AGSArcGISMapImageSublayer, I couldn't filter. Is it feasible to get AGSFeatureLayer from operational layers? I have attached the Sample server URL which I'm using. 

http://sampleserver6.arcgisonline.com/arcgis/rest/services/Water_Network/MapServer

 

0 Kudos
Nicholas-Furness
Esri Regular Contributor

Hi,

You can, but bear in mind a couple of things: The Map Service must allow its layers to be queried (you can see the Query action is enabled on this sublayer, for example so that will work in this case), and Map Image Services are rendered on the server. These days we recommend using Feature Services if you can as clients can do so much in terms of rendering now (and in fact querying may be quicker as Runtime might be able to use its in-memory feature cache).

You would have to create a new AGSServiceFeatureTable for each sublayer (not for display) and query that, or else simply add those layers to the map instead of the single MapImageService.

If you must use MapImage services (but again, in general I'd recommend publishing as Feature Services and using Feature Layers in the app), you can try this modification to the above code…

 

let featureTablesForMapLayers = self.map.operationalLayers.compactMap {
        $0 as? AGSArcGISMapImageLayer
    }.flatMap { [self] (mapImageLayer: AGSArcGISMapImageLayer) -> [URL] in
        let allSublayers = mapImageLayer.mapImageSublayers.flatMap({
            self.getAllSublayers(for:$0 as! AGSArcGISMapImageSublayer)
        })
        
        let visibleSubLayers = allSublayers.filter { $0.isVisible(atScale: currentScale) }
        
        return visibleSubLayers.compactMap {
            if let layer = $0 as? AGSArcGISSublayer {
                return mapImageLayer.url!.appendingPathComponent("\(layer.sublayerID)")
            }
            return nil
        }
    }.map {
        AGSServiceFeatureTable(url: $0)
    }

let featureTablesForFeatureLayers = self.map.operationalLayers.compactMap {
    // Let's reduce the layers to just AGSFeatureLayers
    $0 as? AGSFeatureLayer
}.filter {
    // Clear the selection from any previous selection.
    $0.clearSelection()
    
    // Make sure we just consider the currently visible layers.
    // Just return true if you want all layers, regardless of current scale.
    return $0.isVisible(atScale: currentScale)
}.compactMap {
    // Convert the sequence to AGSFeatureTables
    $0.featureTable
}

let allFeatureTablesInMap = featureTablesForMapLayers + featureTablesForFeatureLayers

allFeatureTablesInMap.forEach { featureTable in
    // Query each feature table, coordinating responses using the DispatchGroup.
    queryGroup.enter()
...

 

And add this method…

 

func getAllSublayers(for layer: AGSLayerContent) -> [AGSLayerContent] {
    if layer.subLayerContents.isEmpty {
        return [layer]
    } else {
        let allSublayers = layer.subLayerContents
            .compactMap({ $0 as? AGSArcGISSublayer })
            .flatMap {
                getAllSublayers(for: $0)
            }
        return allSublayers
    }
}

 

Note that selection is not possible when using MapImageServices.

0 Kudos