iOS SDK (Swift) - Unexpected Type Error using AGSPolygon.fromJSON?

870
4
Jump to solution
05-02-2022 09:05 AM
by Anonymous User
Not applicable

iOS 15.4.1

ESRI iOS SDK 100.13

I am using the ArcGIS REST API to get data in GeoJSON format. When attempting to covert the GeoJSON to an AGS Polygon using AGSPolygon.fromJSON an Unexpected Type error occurs.

 

Any ideas on what needs to be changed?

 

Please see code snippet below:

 

 

let url = URL(string: "https://tigerweb.geo.census.gov/arcgis/rest/services/Generalized_TAB2020/State_County/MapServer/7/query?where=STATE=%2716%27&&outfields=*&f=geojson")!
        
        let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
            guard let data = data else { return }
            //print(String(data: data, encoding: .utf8)!)
            
            
                let jsonObject = try? JSONSerialization.jsonObject(with: data)
                //print(jsonObject)
                let polygon = (try? AGSPolygon.fromJSON(jsonObject!)) as? AGSPolygon

                let polygonSymbol = AGSSimpleFillSymbol(style: .solid, color: .orange, outline: AGSSimpleLineSymbol(style: .solid, color: .blue, width: 2.0))
                    
                let polygonGraphic = AGSGraphic(geometry: polygon, symbol: polygonSymbol)
                self.mapView.graphicsOverlays.add(polygonGraphic)
            
            
        }
        
        task.resume()
        
0 Kudos
1 Solution

Accepted Solutions
Nicholas-Furness
Esri Regular Contributor

Any idea why  AGSPolygon.fromJSON returns NIL instead of an AGSPolygon?

Yes. As I mentioned before, the REST request you're making doesn't just return a geometry. In fact it returns a set of features, including metadata. Each feature contains a geometry, but the full JSON you get back is much more than that, and you're trying to hydrate a geometry from the entire feature set JSON.

I strongly recommend that you follow the queryFeaturesAndAddAsGraphics() example method I provide and do not make your own REST requests. It will simplify your life significantly. You can see on line 79 of my example code that I get the polygon of the first feature that's returned. If you need to persist that, you can call toJSON() on it. The JSON you get out of that can be used later to create a new AGSPolygon with fromJSON().

Incidentally, your call to fromJSON() is also ignoring the error that is returned.

View solution in original post

4 Replies
by Anonymous User
Not applicable

Here is the debug output from the Xcode console:

 

2022-05-02 08:44:13.557910-0700 GeoJSONTest[13341:4371718] ArcGIS Runtime Error Occurred. Set a breakpoint on C++ exceptions to see the original callstack and context for this error:  Error Domain=com.esri.arcgis.runtime.error Code=2 "Invalid argument." UserInfo={NSLocalizedFailureReason=element: unexpected type, NSLocalizedDescription=Invalid argument., Additional Message=element: unexpected type}
0 Kudos
Nicholas-Furness
Esri Regular Contributor

Hi.

I think you might be overthinking this a bit 🙂

The Runtime SDK takes care of a lot of this stuff for you. Take a look at this sample, and the code I've included below.

But there are also some other issues:

Here are two options to display this state. One uses an AGSFeatureLayer and filters the contents with a where clause. The other does an explicit query to get a feature and create an AGSGraphic from it. Either way, you will need to add an API Key from your developer account to load the basemaps in this code…

 

// 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 ViewController: UIViewController {
    
    @IBOutlet weak var mapView: AGSMapView! {
        didSet {
            mapView.graphicsOverlays.add(overlay)
        }
    }
    let overlay = AGSGraphicsOverlay()

    // Create a service feature table "linked" to the States 500K Layer
    let featureTable = AGSServiceFeatureTable(url: URL(string: "https://tigerweb.geo.census.gov/arcgis/rest/services/Generalized_TAB2020/State_County/MapServer/7")!)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Visit https://developers.arcgis.com/dashboard/ to create your API key (used for basemaps)
        AGSArcGISRuntimeEnvironment.apiKey = "<YOUR API KEY>"

        let map = AGSMap(basemapStyle: .arcGISNavigation)
        mapView.map = map
        
        displayLayerDirectly()
        
//        queryFeaturesAndAddAsGraphics()
    }
    
    func displayLayerDirectly() {
        guard let map = mapView.map else { return }
        
        // Use a feature layer object pointing at the service feature table
        let layer = AGSFeatureLayer(featureTable: featureTable)
        
        // The layer by default doesn't display beyond 1:4,999,999
        // See https://tigerweb.geo.census.gov/arcgis/rest/services/Generalized_TAB2020/State_County/MapServer/7
        layer.minScale = 0
        
        // Explicitly define the renderer
        layer.renderer = AGSSimpleRenderer(symbol: AGSSimpleFillSymbol(style: .solid, color: .orange, outline: AGSSimpleLineSymbol(style: .solid, color: .blue, width: 2.0)))
        
        // And now limit the results returned from the layer to just Idaho
        layer.definitionExpression = "STATE='16'"

        map.operationalLayers.add(layer)
    }
    
    func queryFeaturesAndAddAsGraphics() {
        // We will query against the service feature table to get the state feature we want…
        let queryParams = AGSQueryParameters()
        queryParams.whereClause = "STATE='16'"
        
        // Perform the query (no need to make data tasks explicitly, and we'll help with authentication if need be)
        featureTable.queryFeatures(with: queryParams, queryFeatureFields: .loadAll) { [weak self] result, error in
            guard let self = self else { return }

            if let error = error {
                print("Error performing query: \(error.localizedDescription)")
                return
            }
            
            guard let feature = result?.featureEnumerator().nextObject() else { return }
            
            // The polygon is already created as the feature's geometry.
            let polygon = feature.geometry
            
            // Create a graphic from the feature.
            let graphic = AGSGraphic(
                geometry: polygon,
                symbol: AGSSimpleFillSymbol(
                    style: .solid,
                    color: .orange,
                    outline: AGSSimpleLineSymbol(style: .solid, color: .blue, width: 2.0)),
                attributes: feature.attributes as? [String : Any])
            
            // Add the graphic to the overlay we already added to the map view.
            self.overlay.graphics.add(graphic)
        }
    }
}

 

Hope that helps.

 

0 Kudos
by Anonymous User
Not applicable

Hi Nicholas,

 

Your response and information are greatly appreciated. I had a typo in the snippet I copied over previously it should not have include the part with the Graphics/GraphicsOverlays. Good catch on that.

 

I updated the URL to request ESRI JSON Format instead of GEOJSON.

We are indeed trying to load JSON data from the ArcGIS Rest API (will save the file to a .json locally if this works) to load graphics in offline mode (I know there are other methods of working with offline data as well).

 

Note, the filtering of a single state is to make the JSON request faster for testing. The example service is public facing and not maintained by us so thank you for for your notes on the layer visibility.

 

In the code below two polygons should get added to the map. However, only one polygon loads -  the ESRI Example https://developers.arcgis.com/ios/maps-2d/tutorials/add-a-point-line-and-polygon/ which is in Southern California.

 

 

import UIKit
import ArcGIS

class ViewController: UIViewController {
    @IBOutlet weak var mapView: AGSMapView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        AGSArcGISRuntimeEnvironment.apiKey = "Insert Your API Key Here"
        
        let map = AGSMap(basemap: .imageryWithLabels())
        
        mapView.map = map
        
        mapView.setViewpoint(
            AGSViewpoint(
                latitude: 43.644026,
                longitude: -114.789499,
                scale: 10_000_000
            )
        )
        
        loadJSON()
    }
    
    func loadJSON() {
        let url = URL(string: "https://tigerweb.geo.census.gov/arcgis/rest/services/Generalized_TAB2020/State_County/MapServer/7/query?where=STATE=%2716%27&&outfields=*&f=json")!
        
        let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
            guard let data = data else { return }
                        
            let jsonObject = try? JSONSerialization.jsonObject(with: data)
            // JSON recieved from Server
            //print(jsonObject)
            let polygon = (try? AGSPolygon.fromJSON(jsonObject!)) as? AGSPolygon
            // This is nil - expected a AGS Polygon??
            print(polygon)
            
            let ESRIExamplePolygon = AGSPolygon(
                        points: [
                            AGSPoint(x: -118.818984489994, y: 34.0137559967283, spatialReference: .wgs84()),
                            AGSPoint(x: -118.806796597377, y: 34.0215816298725, spatialReference: .wgs84()),
                            AGSPoint(x: -118.791432890735, y: 34.0163883241613, spatialReference: .wgs84()),
                            AGSPoint(x: -118.79596686535, y: 34.008564864635, spatialReference: .wgs84()),
                            AGSPoint(x: -118.808558110679, y: 34.0035027131376, spatialReference: .wgs84())
                        ]
                    )
            
            
            let polygonSymbol = AGSSimpleFillSymbol(style: .solid, color: .orange, outline: AGSSimpleLineSymbol(style: .solid, color: .blue, width: 2.0))
            
            let polygonGraphic = AGSGraphic(geometry: polygon, symbol: polygonSymbol)
            let ESRIExamplePolygonGraphic = AGSGraphic(geometry: ESRIExamplePolygon, symbol: polygonSymbol)
            
            let graphicsOverlay = AGSGraphicsOverlay()
            
            graphicsOverlay.graphics.add(polygonGraphic)
            graphicsOverlay.graphics.add(ESRIExamplePolygonGraphic)
            
            self.mapView.graphicsOverlays.add(graphicsOverlay)
            
        }
        
        task.resume()
        
    }
    
    
}

 

Any idea why  AGSPolygon.fromJSON returns NIL instead of an AGSPolygon? The State of Idaho is the other Polygon we expect to see loaded onto the map.

 

Thanks again

0 Kudos
Nicholas-Furness
Esri Regular Contributor

Any idea why  AGSPolygon.fromJSON returns NIL instead of an AGSPolygon?

Yes. As I mentioned before, the REST request you're making doesn't just return a geometry. In fact it returns a set of features, including metadata. Each feature contains a geometry, but the full JSON you get back is much more than that, and you're trying to hydrate a geometry from the entire feature set JSON.

I strongly recommend that you follow the queryFeaturesAndAddAsGraphics() example method I provide and do not make your own REST requests. It will simplify your life significantly. You can see on line 79 of my example code that I get the polygon of the first feature that's returned. If you need to persist that, you can call toJSON() on it. The JSON you get out of that can be used later to create a new AGSPolygon with fromJSON().

Incidentally, your call to fromJSON() is also ignoring the error that is returned.