Sketch on the Map - Identify the Symbol Marker within selected Geometry in iOS

535
2
Jump to solution
07-26-2022 11:19 PM
Tarak-Kar
New Contributor III

Hi,

   I am using "Sketch on the map" documentation to select the region on the map. The map already contains some SymbolMarkers. Now I need to identify all the Symbol Markers which are in the selected region/ Geometry. Can anyone help me to achieve this?

PS: I have attached a sample reference image for my use case.

0 Kudos
1 Solution

Accepted Solutions
Nicholas-Furness
Esri Regular Contributor

Hi.

The identify calls available on AGSMapView are focused on interaction so revolve around tapping on the screen.

To search using an arbitrary geometry, you will have to call Query on the feature tables that you're interested in. This assumes that the "symbol markers" you're referring to represent features in a feature layer.

Take a look a this conceptual doc: hosted-feature-layers

Also take a look at this sample, which references a feature table (in this case, an AGSServiceFeatureTable), sets up an AGSQueryParameters, and then calls queryFeatures using the parameters, which is returned a set of features.

You would do the same, except that instead of setting a whereClause on the parameters, you would set the geometry to the geometry from the sketch editor, and spatialRelationship to .within.

If the "symbol markers" are graphics in a graphics overlay, then you'll have to iterate over each graphic in the graphics overlay and for its geometry use the AGSGeometryEngine.geometry(graphicGeom, within: sketchGeom) to narrow it down.

This code could help. It shows both GraphicsOverlay and Feature Layer approaches. I haven't actually run it, but it compiles 🙂

extension AGSGraphicsOverlay {
    func getGraphics(within searchPolygon: AGSPolygon) -> [AGSGraphic] {
        let graphicsWithinGeometry = (graphics as? [AGSGraphic])?.filter { graphic in
            guard let graphicGeometry = graphic.geometry else { return false }
            
            return AGSGeometryEngine.geometry(graphicGeometry, within: searchPolygon)
        }
        
        return graphicsWithinGeometry ?? []
    }
}

extension Array where Element == AGSGraphicsOverlay {
    func getGraphics(within searchPolygon: AGSPolygon) -> [AGSGraphicsOverlay: [AGSGraphic]] {
        var results = [AGSGraphicsOverlay: [AGSGraphic]]()
        
        for overlay in self {
            results[overlay] = overlay.getGraphics(within: searchPolygon)
        }
        
        return results
    }
}

extension AGSMap {
    func queryFeatures(within searchPolygon: AGSPolygon, completion: @escaping ([AGSFeatureLayer: Result<AGSFeatureQueryResult, Error>]) -> Void) {
        
        // Create a store for all the results coming back from each feature layer
        var results = [AGSFeatureLayer: Result<AGSFeatureQueryResult, Error>]()
        
        // Create a Dispatch Group to coordinate the async Query calls against all the feature layers.
        let coordinator = DispatchGroup()
        
        // Query each feature layer in parallel, collecting all the query results in the `results` variable.
        for layer in operationalLayers.compactMap({ return $0 as? AGSFeatureLayer }) {
            guard let table = layer.featureTable else { continue }

            // Find all features in the feature layer that are within the search polygon
            let params = AGSQueryParameters()
            params.whereClause = "1=1"
            params.spatialRelationship = .within
            params.geometry = searchPolygon
            
            coordinator.enter()
            table.queryFeatures(with: params) { [layer] result, error in
                defer { coordinator.leave() }
                
                if let error = error {
                    results[layer] = Result.failure(error)
                } else if let result = result {
                    results[layer] = Result.success(result)
                } else {
                    assertionFailure("No result OR error - that shouldn't happen")
                }
            }
        }
        
        // Once all the queries have completed, execute this code…
        coordinator.notify(queue: .main) {
            completion(results)
        }
    }
}

You could then call that with something like this…

if let searchGeom = sketchEditor.geometry as? AGSPolygon {
    map.queryFeatures(within: searchGeom) { results in
        for (layer, queryResponse) in results {
            print("Got result for \(layer.name)")
            switch queryResponse {
            case .success(let queryResult):
                print("Found \(queryResult.featureEnumerator().allObjects.count) features")
            case .failure(let error):
                print("There was an error querying the layer: \(error.localizedDescription)")
            }
        }
    }
    
    
    if let results = (mapView.graphicsOverlays as? [AGSGraphicsOverlay])?.getGraphics(within: searchGeom) {
        for (overlay, graphics) in results {
            print("Found \(graphics.count) graphics in overlay \(overlay)")
        }
    }
}

Again, I haven't had a chance to run this code, but it should give you an idea of how to do this.

View solution in original post

2 Replies
Nicholas-Furness
Esri Regular Contributor

Hi.

The identify calls available on AGSMapView are focused on interaction so revolve around tapping on the screen.

To search using an arbitrary geometry, you will have to call Query on the feature tables that you're interested in. This assumes that the "symbol markers" you're referring to represent features in a feature layer.

Take a look a this conceptual doc: hosted-feature-layers

Also take a look at this sample, which references a feature table (in this case, an AGSServiceFeatureTable), sets up an AGSQueryParameters, and then calls queryFeatures using the parameters, which is returned a set of features.

You would do the same, except that instead of setting a whereClause on the parameters, you would set the geometry to the geometry from the sketch editor, and spatialRelationship to .within.

If the "symbol markers" are graphics in a graphics overlay, then you'll have to iterate over each graphic in the graphics overlay and for its geometry use the AGSGeometryEngine.geometry(graphicGeom, within: sketchGeom) to narrow it down.

This code could help. It shows both GraphicsOverlay and Feature Layer approaches. I haven't actually run it, but it compiles 🙂

extension AGSGraphicsOverlay {
    func getGraphics(within searchPolygon: AGSPolygon) -> [AGSGraphic] {
        let graphicsWithinGeometry = (graphics as? [AGSGraphic])?.filter { graphic in
            guard let graphicGeometry = graphic.geometry else { return false }
            
            return AGSGeometryEngine.geometry(graphicGeometry, within: searchPolygon)
        }
        
        return graphicsWithinGeometry ?? []
    }
}

extension Array where Element == AGSGraphicsOverlay {
    func getGraphics(within searchPolygon: AGSPolygon) -> [AGSGraphicsOverlay: [AGSGraphic]] {
        var results = [AGSGraphicsOverlay: [AGSGraphic]]()
        
        for overlay in self {
            results[overlay] = overlay.getGraphics(within: searchPolygon)
        }
        
        return results
    }
}

extension AGSMap {
    func queryFeatures(within searchPolygon: AGSPolygon, completion: @escaping ([AGSFeatureLayer: Result<AGSFeatureQueryResult, Error>]) -> Void) {
        
        // Create a store for all the results coming back from each feature layer
        var results = [AGSFeatureLayer: Result<AGSFeatureQueryResult, Error>]()
        
        // Create a Dispatch Group to coordinate the async Query calls against all the feature layers.
        let coordinator = DispatchGroup()
        
        // Query each feature layer in parallel, collecting all the query results in the `results` variable.
        for layer in operationalLayers.compactMap({ return $0 as? AGSFeatureLayer }) {
            guard let table = layer.featureTable else { continue }

            // Find all features in the feature layer that are within the search polygon
            let params = AGSQueryParameters()
            params.whereClause = "1=1"
            params.spatialRelationship = .within
            params.geometry = searchPolygon
            
            coordinator.enter()
            table.queryFeatures(with: params) { [layer] result, error in
                defer { coordinator.leave() }
                
                if let error = error {
                    results[layer] = Result.failure(error)
                } else if let result = result {
                    results[layer] = Result.success(result)
                } else {
                    assertionFailure("No result OR error - that shouldn't happen")
                }
            }
        }
        
        // Once all the queries have completed, execute this code…
        coordinator.notify(queue: .main) {
            completion(results)
        }
    }
}

You could then call that with something like this…

if let searchGeom = sketchEditor.geometry as? AGSPolygon {
    map.queryFeatures(within: searchGeom) { results in
        for (layer, queryResponse) in results {
            print("Got result for \(layer.name)")
            switch queryResponse {
            case .success(let queryResult):
                print("Found \(queryResult.featureEnumerator().allObjects.count) features")
            case .failure(let error):
                print("There was an error querying the layer: \(error.localizedDescription)")
            }
        }
    }
    
    
    if let results = (mapView.graphicsOverlays as? [AGSGraphicsOverlay])?.getGraphics(within: searchGeom) {
        for (overlay, graphics) in results {
            print("Found \(graphics.count) graphics in overlay \(overlay)")
        }
    }
}

Again, I haven't had a chance to run this code, but it should give you an idea of how to do this.

Tarak-Kar
New Contributor III

Thanks, Nicholas. It is working as expected.

0 Kudos