ArcGIS REST API Elevation Services using Python & Requests

This is going to be a quick tutorial about the basics of accessing some of ArcGIS REST API’s Elevation Services using the Requests library in Python. Specifically looking at the “Summarize Elevation” service. The Esri documentation for these services lacks Python-specific code examples and figuring out how to structure your code and build the necessary calls to the API can be daunting for somebody unfamiliar with the API or RESTful API’s in general.

Getting Started

In order to get an access token using this method, you will need to create an application in ArcGIS for Developers. I’m not going to walk through the specifics of setting this up, but once you have a free ArcGIS for Developer subscription, it’s fairly straight-forward. For this example, I created a Application named “Elevation Services Demo”, you can name yours whatever you’d like, we’ll revisit this in a bit to grab our client ID and client secret values.

Architecture of our Script

There are four major things we need our script to do:

  1. Create an Access Token so our API calls work.
  2. Send a request to the API containing features we’d like elevation data for (in this write-up we’ll use a few made-up sample points).
  3. Check the status of our request so we know when our results are ready to access.
  4. Access and read the results of our request.

The following sections will break down these discrete parts of our script.

Generating an Access Token

The following code block is a slightly altered version of an example pulled from an ESRI tutorial:

def get_token(client_id, client_secret):
    url = "https://www.arcgis.com/sharing/rest/oauth2/token"

    payload = "client_id={}&client_secret={}&grant_type=client_credentials".format(client_id, client_secret)
    headers = {
        'content-type': "application/x-www-form-urlencoded",
        'accept': "application/json",
        'cache-control': "no-cache",
        'postman-token': "11df29d1-17d3-c58c-565f-2ca4092ddf5f"
        }
    
    response = requests.post(url, data=payload, headers=headers)
    
    return str(json.loads(response.text)['access_token'])

This function uses the requests and json libraries to grab a token from the ArcGIS REST API to use in your script. When we call this function later you will use your client ID and client secret from the application you created above.

Sending a Request

Sending the request requires the token you created in the previous step as well as the features you are sending. Formatting these features is another tricky piece that can be hard to find documentation for. For this example I’ve put together 3 sample points. Each point gets represented as a dictionary and added to a list, which is the workflow you’d probably like to use for constructing these features from your dataset.

SR = 4326
features = [{'geometry': {'y': 45.5, 'x': -94.5}, 'attributes': {'ID': '0001'}},
            {'geometry': {'y': 44.5, 'x': -96}, 'attributes': {'ID': '0002'}},
            {'geometry': {'y': 43, 'x': -92}, 'attributes': {'ID': '0003'}}]

“features” is a simple set of 3 points with a basic ID attribute. Below is the function we’ll pass these features to in order to send the request:

def send_request(token, features, SR = 4326):
    input_features = {"geometryType":"esriGeometryPoint","spatialReference":{"wkid":SR},"features":features}
    url = 'http://elevation.arcgis.com/arcgis/rest/services/Tools/Elevation/GPServer/SummarizeElevation/ \
          submitJob?InputFeatures={}&DEMResolution=FINEST&f=json&token={}'.format(input_features, token)
    r = requests.get(url)
    jobID = str(json.loads(r.text)['jobId'])
    
    return jobID

send_request() pieces together the necessary URL for the request, sends the request to the API, and then returns the jobID. The jobID allows us to retrieve our results once the service is done processing our request.

Checking Job Status & Accessing Results

The ArcGIS Elevation Services contains a tool for checking the job status with your JobID. What you would do when using this workflow is check the status until the job has completed, and then request the results seperately. I’ve run into issues using this in the past and put together an alternative function that works in a similar way but bypasses the discrete job status check and instead determines job status while accessing the results. Is it better? It could be, but the current form is lacking a bit in error handling.

def get_results(jobID, token, kill_process = 600):
    rerun = True
    elapsed_time = 0
    while rerun == True:
        time.sleep(5)
        r = requests.get('http://elevation.arcgis.com/arcgis/rest/services/Tools/Elevation/GPServer/SummarizeElevation/ \
            jobs/{}/results/OutputSummary?token={}&f=json' \
            .format(jobID, token))
        results = json.loads(r.text)
        if 'error' in results:
            elapsed_time += 5
            if elapsed_time > kill_process:
                print ('API Call exceed {} seconds, task was killed.'.format(kill_process))
                return
        else:
            print ('API Call Succeeded. Run Time: {} seconds.'.format(elapsed_time))
            rerun = False
        
    return results

The above function works by sending the request to get the results of our task every 5 seconds until the result no longer contains any ‘errors’. This happens when the result is finally ready. The issue with this function is that an ‘error’ doesn’t only mean that our result is not yet ready, it could also mean that the result IS ready, but that there is still an error! For the very specific purpose I use this function that isn’t an issue, but for wider use it definitely is. Fair warning.

“kill_process” is the time in seconds that function will run before giving up. I have it set to default at 10 minutes, depending on the size of your call this is typically more than enough time. For larger datasets I’ve experienced times up to 6 or 7 minutes, for the 3 points used in this example I’ve typically seen response times around 10 seconds.

Viewing Results

The results are hidden in a dictionary, I encourage you to explore the results on your own but this quick line here is an example of how you can access the results of our request.

for result in elevation_results['value']['features']:
    print (result['attributes']['MeanElevation'])

>> 364.21112  #meters
>> 393.8533
>> 308.42001

The results contains your original attributes, geometry and elevation results. Take a look and explore the structure of the results dictionary and put the data to use in your projects!

Wondering how to piece these functions together to make something that works? Take a look at the repository for this tutorial on my GitHub!

Your email address will not be published. Required fields are marked *