How do I get feature data from a feature layer using the ArcGIS iOS SDK?

3933
13
Jump to solution
12-31-2017 12:24 PM
TrevorPostma
New Contributor II

Hi everyone,

I'm building an AR app using ARKit and the ArcGIS iOS SDK. I'm trying to access the data of a Feature Layer which I'm hosting on ArcGIS Online. The Feature Layer consists of images which have been submitted by participants through a Crowdsource Story Map. Specially, I'm trying to get the location and URL of each image in the Feature Layer, using the ArcGIS iOS SDK, so that I can then place/display them in the AR app based on that information.

I've been working on this problem for a few days now and have been unable to come up with a solution. I'll share what I have so far, below. Any help would be greatly appreciated!

// Connect to the ArcGIS Online portal
let portal = AGSPortal(url: URL(string: "https://organizationName.maps.arcgis.com")!, loginRequired: false)

// Provide credentials
portal.credential = AGSCredential(user: "username", password: "password")

// Get the portal item
let portalItem = AGSPortalItem(portal: portal, itemID: "xxxx836axxxx4781xxxxe4d58bcxxxx")

// Next I load the portal item
portalItem.load { [weak self] (error) -> Void in

    if let error = error {
        print("Could not load portal item due to error : \(error)")
        return
    }

    // Printing the portal item to the console produces the result I'm looking for. I'm getting the correct portal item.
    print("portal item is loaded \(portalItem)")

   // The above print statements outputs the following:
   // portal item is loaded xxxx836axxxx4781xxxxe4d58bcxxxx, Feature Service, featureLayerName, organizationName, https://organizationName.maps.arcgis.com/sharing/rest/content/items/xxxx836axxxx4781xxxxe4d58bcxxxx/...
}

// Then I try to fetch the data of the portal item. This is where I get stuck. I would have thought this grabs the data of the feature layer, which would then let me get the data for each specific feature but it doesn't fetch any data (print statement outputs "0 bytes")

portalItem.fetchData { [weak self](data, error) -> Void in


    if let error = error {
        print("Could not fetch data due to error: \(error.localizedDescription)")
        return
    }

    // Print statement is reached but output is "0 bytes"
    if let data = data {
        print(data)
    }
}

I apologize if there is a better way to post code on this forum. Again, any help would be awesome!

Trevor

0 Kudos
1 Solution

Accepted Solutions
Nicholas-Furness
Esri Regular Contributor

Hi.

The short answer is that the "data" you're getting with fetchData() is the metadata describing the Portal Item (mostly the stuff you'd see if you browse to the web page for that portal item).

What you are looking to do is to create an AGSServiceFeatureTable. You can do that using serviceFeatureTableWithItem:layerID() (most likely you'll use layer ID 0, but a feature service can have many layers, depending on how it was published). Then you can call queryFeaturesWithParameters:completion() to get the features and iterate over those. You'll be working with AGSQueryParameters an AGSFeatureQueryResult.

If you find that not all your fields are coming back when you query, consider using queryFeaturesWithParameters:queryFeatureFields:completion:() instead, specifying that you want to Load All fields.

When working with the AGSQueryParameters, a couple of things to note:

  • If you just need all records, you should set the where clause to "1=1". It's a trick that always evaluates to true and so returns everything. But if you have a where clause in mind to filter down or are passing a geometry in to constrain the results geographically, no need to use that.
  • If you have a lot of features in your feature service, you should use maxFeatures and resultOffset to get pages of data at a time.

One last thing. If you just want to show the feature layer on a map, you can just create an ASGFeatureLayer from the AGSServiceFeatureTable and add that to a map. The Runtime will take care of querying as necessary to retrieve and display the data.

Hope this helps!

Nick.

P.S. By the way, to post code, you can expand the editing toolbar, then select More->Syntax Highlighter.

View solution in original post

13 Replies
Nicholas-Furness
Esri Regular Contributor

Hi.

The short answer is that the "data" you're getting with fetchData() is the metadata describing the Portal Item (mostly the stuff you'd see if you browse to the web page for that portal item).

What you are looking to do is to create an AGSServiceFeatureTable. You can do that using serviceFeatureTableWithItem:layerID() (most likely you'll use layer ID 0, but a feature service can have many layers, depending on how it was published). Then you can call queryFeaturesWithParameters:completion() to get the features and iterate over those. You'll be working with AGSQueryParameters an AGSFeatureQueryResult.

If you find that not all your fields are coming back when you query, consider using queryFeaturesWithParameters:queryFeatureFields:completion:() instead, specifying that you want to Load All fields.

When working with the AGSQueryParameters, a couple of things to note:

  • If you just need all records, you should set the where clause to "1=1". It's a trick that always evaluates to true and so returns everything. But if you have a where clause in mind to filter down or are passing a geometry in to constrain the results geographically, no need to use that.
  • If you have a lot of features in your feature service, you should use maxFeatures and resultOffset to get pages of data at a time.

One last thing. If you just want to show the feature layer on a map, you can just create an ASGFeatureLayer from the AGSServiceFeatureTable and add that to a map. The Runtime will take care of querying as necessary to retrieve and display the data.

Hope this helps!

Nick.

P.S. By the way, to post code, you can expand the editing toolbar, then select More->Syntax Highlighter.

TrevorPostma
New Contributor II

Hi Nicholas.

Thanks for getting back to me. That's very helpful.

I'm having trouble with one part. I'm trying to create the AGSServiceFeatureTable, but it's only giving me the options for serviceFeatureTableWithTable and serviceFeatureTableWithUrl. Do you know why I'm not getting the serviceFeatureTableWithItem option?

That's all I get. Maybe I'm misunderstanding something.

Thanks again for your help.

0 Kudos
Nicholas-Furness
Esri Regular Contributor

Hi Trevor. That's Xcode being a pain. I'm sure if you just type the constructor like this it'll compile:

let table = AGSServiceFeatureTable(item: portalItem, layerID: 0)let layer = AGSFeatureLayer(featureTable: table)
‍‍‍‍‍

One thought - can you scroll up in that autocomplete list? Perhaps Xcode is scrolling you to the second suggestion for some reason (I think I've seen that before).

Alternatively, if you have other errors in the code, Xcode can get confused with what it offers for autocomplete. Xcode gets better at this with each release, in case you're not on the latest.

Nick.

TrevorPostma
New Contributor II

Hi Nicholas.

I updated the SDK in my project from 100.1.1 to 100.2 and that resolved the issue. I'm able to use the serviceFeatureTableWithItem option.

It doesn't seem to be loading anything though, unfortunately. When I print out the number of features for the table it always returns 0.

Do you know any reasons why I might be blocked from getting that data? Am I missing any steps? Do I need to load the table the same way I load the portalItem previously? I know the feature service requires a token to access the REST API endpoint, would that have anything to do with it?

 

Sorry for all the questions.

0 Kudos
Nicholas-Furness
Esri Regular Contributor

Aha. Ok, so that property refers to what's cached locally in the feature table. You would need to set the table's featureRequestMode to Manual for that and call populateFromServiceWithParameters. But that's more advanced than you need…

Instead, just call one of the query functions. If you need to know the total record count in the service, use a queryParameter with whereClause of 1=1 and call queryFeatureCountWithParameters to get the result. That's an async operation by necessity.

Here are some links that could help:

If eventually you need to work with all data locally on the device, those workflows are also supported by the Runtime, but to get started the workflows involve connecting to your hosted service and working against that.

TrevorPostma
New Contributor II

Ah, that makes sense.

Okay, I looked through all those links you provided and it seems like I have it set up properly now, but I can't get any action inside the query itself. Here's what I have.

Does that look correct to you? I don't get anything in the console to indicate that I made it inside the queryFeatures completion block.

Thanks for your patience and help, Nicholas!

0 Kudos
Nicholas-Furness
Esri Regular Contributor

I'm betting that code is within a func. That func is being exited before the async call returns, and so "table" is being deallocated before the service responds and the block isn't being called.

Try making the "table" variable a class variable instead. So something like this.

class ViewController: NSViewController {

    ...

    // Declare "table" here as a class-level variable
    // table will not be deallocated just because queryTable() finished.
    var table:AGSServiceFeatureTable!
    
    func queryTable() {
        // Here we set the class level variable rather than declaring
        // a local variable that is deallocated when the function exits.
        table = AGSServiceFeatureTable(item: portalItem, layerID: 0)
        
        let params = AGSQueryParameters()
        params.whereClause = "1=1"
        
        // Fire off the query asynchronously... But...
        table.queryFeatures(with: params, completion: { (results, error) in
            guard error == nil else {
                print("Error querying... \(error!.localizedDescription)")
                return
            }
            print("Got a result")
        })

        // ...the code will exit this function before the query returns.
    }

    ...

}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Note that this isn't going to happen if you're reading from, say, a layer on a map in a mapview with something like (pseudocode alert) mapview.map.operationalLayers[0].featureTable because the map and layer will still be "alive" so you'll be querying against a table that isn't going to be deallocated (it's referenced by the layer which is referenced by the map which is referenced by the mapview which is referenced by the app's UI). In this case what you're seeing is because you're declaring a standalone table item that Swift will deallocate just like any other variable declared in that block.

TrevorPostma
New Contributor II

That did it. I'm getting the results I'm after now and should be good from here. You were a massive help, Nicholas. Thank you so much!

0 Kudos
Nicholas-Furness
Esri Regular Contributor

Great stuff. Good to hear.

Would you mind marking some answers as Helpful etc.? Nothing like that Esri Community Karma  

Thanks!

Nick.

0 Kudos