Model My Watershed (MMW) API Demo

Emilio Mayorga, University of Washington, Seattle. 2018-5-10. Demo put together using as a starting point instructions from Azavea from October 2017.

Introduction

The Model My Watershed API allows you to delineate watersheds and analyze geo-data for watersheds and arbitrary areas. You can read more about the work at WikiWatershed or use the web app.

MMW users can discover their API keys through the user interface, and test the MMW geoprocessing API on either the live or staging apps. An Account page with the API key is available from either app (live or staging). To see it, go to the app, log in, and click on "Account" in the dropdown that appears when you click on your username in the top right. Your key is different between staging and production. For testing with the API and key, go to https://modelmywatershed.org/api/docs/

The API can be tested from the command line using curl. This example uses the API to test the watershed endpoint:

curl -H "Content-Type: application/json" -H "Authorization: Token YOUR_API_KEY" -X POST 
  -d '{ "location": [39.67185,-75.76743] }' https://modelmywatershed.org/api/watershed/

MMW API: Extract drainage area (watershed) from a point

1. Set up


In [1]:
import json
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

In [2]:
def requests_retry_session(
    retries=3,
    backoff_factor=0.3,
    status_forcelist=(500, 502, 504),
    session=None,
):
    session = session or requests.Session()
    retry = Retry(
        total=retries,
        read=retries,
        connect=retries,
        backoff_factor=backoff_factor,
        status_forcelist=status_forcelist,
    )
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    return session

In [3]:
s = requests.Session()

In [4]:
APIToken = 'Token YOURTOKEN'  # Replace YOURTOKEN with your actual token string/key

s.headers.update({
    'Authorization': APIToken,
    'Content-Type': 'application/json'
})

MMW staging (test) API Rapid Watershed Delineation (RWD) "watershed" endpoint:


In [5]:
post_url = 'https://modelmywatershed.org/api/watershed/'

2. Construct and issue the job request

Parameters passed to the RWD ("watershed") API request. This is a point in Salt Lake City


In [6]:
payload = {
    'location': [40.746054, -111.847987], # [latitude, longitude]
    'snappingOn': True, 
    'dataSource': 'nhd'}

json_dat = json.dumps(payload)

In [7]:
post_req = requests_retry_session(session=s).post(post_url, data=json_dat)

In [8]:
json_out = json.loads(post_req.content)
json_out


Out[8]:
{u'job': u'b704b73a-3ff5-4acf-a06e-135f8bf8d028', u'status': u'started'}

3. Fetch the job result once it's confirmed as done

The job is not completed instantly and the results are not returned directly by the API request that initiated the job. The user must first issue an API request to confirm that the job is complete, then fetch the results. The demo presented here performs automated retries (checks) until the server confirms the job is completed, then requests the JSON results and converts (deserializes) them into a Python dictionary.


In [9]:
get_url = 'https://modelmywatershed.org/api/jobs/{job}/'.format

In [10]:
result = ''
while not result:
    get_req = requests_retry_session(session=s).get(get_url(job=json_out['job']))
    result = json.loads(get_req.content)['result']

4. Examine the results

Everything below is just exploration of the results.

Examine the content of the results (as JSON, GeoJSON, and Python dictionaries)


In [11]:
type(result), result.keys()


Out[11]:
(dict, [u'watershed', u'input_pt'])

The results (result) are made up of two dictionary items: input_pt and watershed. Each one of those is a GeoJSON-like object already converted to a Python dictionary.

input_pt:


In [12]:
input_pt_geojson = result['input_pt']

In [13]:
input_pt_geojson


Out[13]:
{u'geometry': {u'coordinates': [-111.847987, 40.746054], u'type': u'Point'},
 u'properties': {u'DistStr_m': -32768.0,
  u'Dist_moved': -1,
  u'ID': 1,
  u'Lat': 40.746054,
  u'Lon': -111.847987},
 u'type': u'Feature'}

watershed:


In [14]:
watershed_geojson = result['watershed']

In [15]:
watershed_geojson.keys(), watershed_geojson['geometry'].keys()


Out[15]:
([u'geometry', u'type', u'properties'], [u'type', u'coordinates'])

In [16]:
watershed_geojson['properties']


Out[16]:
{u'Area_km2': 23.7861, u'GRIDCODE': 1}

In [17]:
# watershed has just one feature -- a single polygon
print("Number of polygon features: {} \nFeature type: {} \nNumber of vertices in polygon: {}".format(
    len(watershed_geojson['geometry']['coordinates']), 
    watershed_geojson['geometry']['type'], 
    len(watershed_geojson['geometry']['coordinates'][0])
))


Number of polygon features: 1 
Feature type: Polygon 
Number of vertices in polygon: 809

Render the geospatial results on an interactive map


In [18]:
%matplotlib inline
import folium

In [19]:
# Initialize Folium map
m = folium.Map(location=[input_pt_geojson['properties']['Lat'], input_pt_geojson['properties']['Lon']], 
               tiles='CartoDB positron', zoom_start=12)

In [20]:
# Add RWD watershed and drainage point onto map
folium.GeoJson(watershed_geojson).add_to(m);
folium.GeoJson(input_pt_geojson).add_to(m);

In [21]:
m


Out[21]: