Skip to content
73 changes: 49 additions & 24 deletions dataretrieval/streamstats.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

"""

import json

import requests


Expand Down Expand Up @@ -58,7 +56,7 @@ def get_sample_watershed():
from the streamstats JSON object.

"""
return get_watershed("NY", -74.524, 43.939)
return get_watershed("NY", -74.524, 43.939, format="watershed")


def get_watershed(
Expand Down Expand Up @@ -105,12 +103,17 @@ def get_watershed(
simplify: bool, optional
Boolean flag controlling whether or not to simplify the returned
result.
format: string, optional
Selects the return shape. ``"geojson"`` (default) returns the raw
``requests.Response``; ``"object"`` returns the parsed JSON ``dict``;
``"watershed"`` returns a :obj:`Watershed` instance built from the
parsed JSON. Any other value raises ``ValueError``.
Comment on lines +107 to +111

Returns
-------
Watershed: :obj:`dataretrieval.streamstats.Watershed`
Custom object that contains the watershed information as extracted
from the streamstats JSON object.
requests.Response, dict, or :obj:`dataretrieval.streamstats.Watershed`
Watershed information from StreamStats. The exact return type
depends on ``format`` (see above).

"""
payload = {
Expand All @@ -132,30 +135,52 @@ def get_watershed(
if format == "geojson":
return r

if format == "shape":
# use Fiona to return a shape object
pass
data = r.json()

if format == "object":
# return a python object
pass
return data

if format == "watershed":
return Watershed.from_streamstats_json(data)

data = json.loads(r.text)
return Watershed.from_streamstats_json(data)
raise ValueError(
f"Invalid format {format!r}; expected 'geojson', 'object', or 'watershed'."
)
Comment on lines 136 to +149


class Watershed:
"""Class to extract information from the streamstats JSON object."""
"""Class to extract information from the streamstats JSON object.

@classmethod
def from_streamstats_json(cls, streamstats_json):
"""Method that creates a Watershed object from a streamstats JSON."""
cls.watershed_point = streamstats_json["featurecollection"][0]["feature"]
cls.watershed_polygon = streamstats_json["featurecollection"][1]["feature"]
cls.parameters = streamstats_json["parameters"]
cls._workspaceID = streamstats_json["workspaceID"]
return cls
Attributes
----------
watershed_point : dict
GeoJSON feature for the watershed pour point.
watershed_polygon : dict
GeoJSON feature for the delineated watershed polygon.
parameters : list
Watershed parameters returned by StreamStats.
workspace_id : str
StreamStats workspace identifier for the watershed.
"""

def __init__(self, rcode, xlocation, ylocation):
"""Init method that calls the :obj:`from_streamstats_json` method."""
get_watershed(rcode, xlocation, ylocation)
"""Delineate a watershed and populate the instance from the response."""
data = get_watershed(rcode, xlocation, ylocation, format="object")
self._populate_from_json(data)

@classmethod
def from_streamstats_json(cls, streamstats_json):
"""Construct a :obj:`Watershed` from a StreamStats JSON response."""
instance = cls.__new__(cls)
instance._populate_from_json(streamstats_json)
return instance

def _populate_from_json(self, streamstats_json):
self.watershed_point = streamstats_json["featurecollection"][0]["feature"]
self.watershed_polygon = streamstats_json["featurecollection"][1]["feature"]
self.parameters = streamstats_json["parameters"]
self.workspace_id = streamstats_json["workspaceID"]

@property
def _workspaceID(self):
return self.workspace_id
33 changes: 33 additions & 0 deletions tests/streamstats_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Tests for the streamstats module."""

from dataretrieval.streamstats import Watershed

SAMPLE_JSON = {
"featurecollection": [
{"name": "globalwatershedpoint", "feature": {"type": "Feature", "id": "pt-1"}},
{"name": "globalwatershed", "feature": {"type": "Feature", "id": "poly-1"}},
],
"parameters": [{"code": "DRNAREA", "value": 41.2}],
"workspaceID": "NY20240101000000000",
}


def test_from_streamstats_json_does_not_mutate_class():
"""Two watersheds must not share state via class-level attributes."""
other_json = {
"featurecollection": [
{"feature": {"id": "pt-2"}},
{"feature": {"id": "poly-2"}},
],
"parameters": [{"code": "OTHER", "value": 1.0}],
"workspaceID": "VT20240101000000000",
}
w1 = Watershed.from_streamstats_json(SAMPLE_JSON)
w2 = Watershed.from_streamstats_json(other_json)

assert w1.workspace_id == "NY20240101000000000"
assert w2.workspace_id == "VT20240101000000000"
assert w1.parameters[0]["code"] == "DRNAREA"
assert w2.parameters[0]["code"] == "OTHER"
assert w1.watershed_point["id"] == "pt-1"
assert w2.watershed_point["id"] == "pt-2"
Loading