Skip to content

Commit

Permalink
NLDI delineate
Browse files Browse the repository at this point in the history
  • Loading branch information
marsmith committed Nov 9, 2020
1 parent 9d53af6 commit 8827c11
Show file tree
Hide file tree
Showing 14 changed files with 582 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
.vscode
data
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"terminal.integrated.shellArgs.windows": ["/K", "C:\\Users\\marsmith\\miniconda\\Scripts\\activate.bat C:\\Users\\marsmith\\miniconda & conda activate delineate"],
"python.pythonPath": "C:\\Users\\marsmith\\miniconda\\envs\\delineate1\\python.exe",
"python.pythonPath": "C:\\Users\\marsmith\\miniconda\\envs\\delineate\\python.exe",
"python.terminal.activateEnvironment": true,
"python.testing.unittestArgs": [
"-v",
Expand All @@ -12,4 +11,5 @@
"python.testing.pytestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.unittestEnabled": true,
"liveServer.settings.port": 5501,
}
Binary file added __pycache__/app.cpython-37.pyc
Binary file not shown.
Binary file added __pycache__/delineate.cpython-37.pyc
Binary file not shown.
2 changes: 1 addition & 1 deletion flask_app.py → app.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def main():
lng = float(request.args.get('lng'))

print(region,lat,lng)
dataPath = 'c:/temp/'
dataPath = 'C:/NYBackup/GitHub/ss-delineate/data/'

#start main program
results = delineate.Watershed(lat,lng,region,dataPath)
Expand Down
5 changes: 3 additions & 2 deletions delineate.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def split_catchment(self, flow_dir, geom, x, y):

#method to use catchment bounding box instead of exact geom
minX, maxX, minY, maxY = geom.GetEnvelope()
print('HERE', minX, minY, maxX, maxY)
gdal.Warp('/vsimem/fdr.tif', flow_dir, outputBounds=[minX, minY, maxX, maxY])

#start pysheds catchment delineation
Expand Down Expand Up @@ -501,9 +502,9 @@ def cleanup(self):
timeBefore = time.perf_counter()

#test site
point = (42.17209,-73.87555) #point produces zero area splitCatchment
point = (44.00683,-73.74586)
region = 'ny'
dataPath = 'c:/temp/'
dataPath = 'C:/NYBackup/GitHub/ss-delineate/data/'

#start main program
delineation = Watershed(point[0],point[1],region,dataPath)
Expand Down
286 changes: 286 additions & 0 deletions nldi_delineate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
## NLDI split catchment delineation script

# -----------------------------------------------------
# Martyn Smith USGS
# 10/29/2020
# NLDI Delineation script
# -----------------------------------------------------

# list of required python packages:
# gdal, pysheds, requests

###### CONDA CREATE ENVIRONMENT COMMAND
#conda create -n delineate python=3.6.8 gdal pysheds requests
###### CONDA CREATE ENVIRONMENT COMMAND

from osgeo import ogr, osr, gdal
from pysheds.grid import Grid
import requests
import time
import json

#arguments
NLDI_URL = 'https://labs.waterdata.usgs.gov/api/nldi/linked-data/comid/'
NLDI_GEOSERVER_URL = 'https://labs.waterdata.usgs.gov/geoserver/wmadata/ows'
OUT_PATH = 'C:/NYBackup/GitHub/ss-delineate/data/'
IN_FDR = 'C:/NYBackup/GitHub/ss-delineate/data/nhd_fdr.tif'
OUT_FDR = 'C:/NYBackup/GitHub/ss-delineate/data/catch_fdr.tif'

class Watershed:

ogr.UseExceptions()
gdal.UseExceptions()

def __init__(self, x=None, y=None):

self.x = x
self.y = y
self.catchment_identifier = None
self.catchmentGeom = None
self.splitCatchmentGeom = None
self.upstreamBasinGeom = None
self.mergedCatchmentGeom = None

#input point spatial reference
self.sourceprj = osr.SpatialReference()
self.sourceprj.ImportFromProj4('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')

# Getting spatial reference of input raster
tif = gdal.Open(IN_FDR, gdal.GA_ReadOnly)
self.Projection = tif.GetProjectionRef()
self.targetprj = osr.SpatialReference(wkt = tif.GetProjection())

#create transform
self.transformToRaster = osr.CoordinateTransformation(self.sourceprj, self.targetprj)
self.transformToWGS = osr.CoordinateTransformation(self.targetprj, self.sourceprj)

#kick off
self.transform_click_point()

## helper functions
def geom_to_geojson(self, in_geom, name, simplify_tolerance, in_ref, out_ref, write_output=False):
in_geom = in_geom.Simplify(simplify_tolerance)
out_ref.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER)

transform = osr.CoordinateTransformation(in_ref, out_ref)

#don't want to affect original geometry
transform_geom = in_geom.Clone()

#trasnsform geometry from whatever the local projection is to wgs84
transform_geom.Transform(transform)
json_text = transform_geom.ExportToJson()

#add some attributes
geom_json = json.loads(json_text)

#get area in local units
area = in_geom.GetArea()

print('processing: ' + name + ' area: ' + str(area*0.00000038610))

geojson_dict = {
"type": "Feature",
"geometry": geom_json,
"properties": {
"area": area
}
}

if write_output:
f = open('C:/NYBackup/GitHub/ss-delineate/data/' + name + '.geojson','w')
f.write(json.dumps(geojson_dict))
f.close()
print('Exported geojson:', name)

return geojson_dict


def geom_to_shapefile(self, geom, name):

#write out shapefile
# set up the shapefile driver
driver = ogr.GetDriverByName("ESRI Shapefile")

# create the data source
data_source = driver.CreateDataSource(OUT_PATH + name + '.shp')
layer = data_source.CreateLayer(name, self.targetprj, ogr.wkbPolygon)
feature = ogr.Feature(layer.GetLayerDefn())

# Set the feature geometry using the point
feature.SetGeometry(geom)
# Create the feature in the layer (shapefile)
layer.CreateFeature(feature)
# Dereference the feature
feature = None


## main functions
def transform_click_point(self):

print('Input X,Y:', self.x, self.y)
self.projectedLng, self.projectedLat, z = self.transformToRaster.TransformPoint(self.x,self.y)
print('Projected X,Y:',self.projectedLng, ',', self.projectedLat)

self.get_local_catchment_geom()

def get_local_catchment_geom(self):

wkt_point = "POINT(%f %f)" % (self.x , self.y)
cql_filter = "INTERSECTS(the_geom, %s)" % (wkt_point)

payload = {
'service': 'wfs',
'version': '1.0.0',
'request': 'GetFeature',
'typeName': 'wmadata:catchmentsp',
'outputFormat': 'application/json',
'srsName': 'EPSG:4326',
'CQL_FILTER': cql_filter
}

#request catchment geometry from point in polygon query from NLDI geoserver
# https://labs.waterdata.usgs.gov/geoserver/wmadata/ows?service=wfs&version=1.0.0&request=GetFeature&typeName=wmadata%3Acatchmentsp&outputFormat=application%2Fjson&srsName=EPSG%3A4326&CQL_FILTER=INTERSECTS%28the_geom%2C+POINT%28-73.745860+44.006830%29%29
r = requests.get(NLDI_GEOSERVER_URL, params=payload)
resp = r.json()

#print(r.text)

#get catchment id
self.catchment_identifier = json.dumps(resp['features'][0]['properties']['featureid'])

#get main catchment geometry polygon
gj_geom = json.dumps(resp['features'][0]['geometry'])
self.catchmentGeom = ogr.CreateGeometryFromJson(gj_geom)

#transform catchment geometry
self.catchmentGeom.Transform(self.transformToRaster)

self.geom_to_shapefile(self.catchmentGeom, 'catchment')

#get extent of transformed polygon
minX, maxX, minY, maxY = self.catchmentGeom.GetEnvelope() # Get bounding box of the shapefile feature
bounds = [minX, minY, maxX, maxY]

print('projected bounds', bounds)

self.splitCatchmentGeom = self.split_catchment(bounds, self.projectedLng,self.projectedLat)

#get upstream basin
self.upstreamBasinGeom = self.get_upstream_basin()


def get_local_catchment_id(self):

#request local catchment identifier from NLDI
# https://labs.waterdata.usgs.gov/api/nldi/linked-data/comid/position?f=json&coords=POINT(-89.35%2043.0864)
wkt_point = wkt = "POINT(%f %f)" % (self.x , self.y)
payload = {'f': 'json', 'coords': wkt_point}

#get comid of catchment point is in
r = requests.get(NLDI_URL + 'position', params=payload)

resp = r.json()
self.catchment_identifier = resp['features'][0]['properties']['identifier']

#print('identifier: ', self.catchment_identifier)

self.get_upstream_basin()

def get_upstream_basin(self):

#request upstream basin

payload = {'f': 'json', 'simplified': 'false'}

#request upstream basin from NLDI using comid of catchment point is in
r = requests.get(NLDI_URL + self.catchment_identifier + '/basin', params=payload)

#print('upstream basin', r.text)
resp = r.json()

#convert geojson to ogr geom
gj_geom = json.dumps(resp['features'][0]['geometry'])
self.upstreamBasinGeom = ogr.CreateGeometryFromJson(gj_geom)
self.upstreamBasinGeom.Transform(self.transformToRaster)

self.geom_to_shapefile(self.upstreamBasinGeom, 'upstreamBasin')

self.mergeGeoms()

def mergeGeoms(self):

#create new cloned geom
self.mergedCatchmentGeom = self.upstreamBasinGeom.Clone()

#remove downstream catchment
diff = self.catchmentGeom.Difference(self.splitCatchmentGeom.Buffer(10).Buffer(-10))

self.mergedCatchmentGeom = self.mergedCatchmentGeom.Difference(diff).Simplify(30)

# #add split catchment
# self.mergedCatchmentGeom.AddGeometry(self.splitCatchmentGeom)

# self.mergedCatchmentGeom = self.mergedCatchmentGeom.UnionCascaded()

# self.mergedCatchmentGeom = self.mergedCatchmentGeom.Simplify(30)

#write out
self.geom_to_shapefile(self.mergedCatchmentGeom, 'xxFinalBasinxx')

def split_catchment(self, bounds, x, y):

RasterFormat = 'GTiff'
PixelRes = 30

#method to use catchment bounding box instead of exact geom
gdal.Warp(OUT_FDR, IN_FDR, format=RasterFormat, outputBounds=bounds, xRes=PixelRes, yRes=PixelRes, dstSRS=self.Projection, resampleAlg=gdal.GRA_NearestNeighbour, options=['COMPRESS=DEFLATE'])

#start pysheds catchment delineation
grid = Grid.from_raster(OUT_FDR, data_name='dir')

#compute flow accumulation to snap to
dirmap = (64, 128, 1, 2, 4, 8, 16, 32)
grid.accumulation(data='dir', dirmap=dirmap, out_name='acc', apply_mask=False)

grid.to_raster('acc', 'C:/NYBackup/GitHub/ss-delineate/data/acc.tif', view=False, blockxsize=16, blockysize=16)

#snap the pourpoint to
xy = (x, y)
new_xy = grid.snap_to_mask(grid.acc > 50, xy, return_dist=False)

#get catchment with pysheds
grid.catchment(data='dir', x=new_xy[0], y=new_xy[1], out_name='catch', recursionlimit=15000, xytype='label')

# Clip the bounding box to the catchment
grid.clip_to('catch')

#some sort of strange raster to polygon conversion using rasterio method
shapes = grid.polygonize()

#get split Catchment geometry
print('Split catchment complete')
split_geom = ogr.Geometry(ogr.wkbPolygon)

for shape in shapes:
split_geom = split_geom.Union(ogr.CreateGeometryFromJson(json.dumps(shape[0])))

#write out shapefile
self.geom_to_shapefile(split_geom, 'splitCatchment')

return split_geom


if __name__=='__main__':

timeBefore = time.perf_counter()

#test site
point = (-73.74586, 44.00683)

#start main program
delineation = Watershed(point[0],point[1])

timeAfter = time.perf_counter()
totalTime = timeAfter - timeBefore
print("Total Time:",totalTime)
1 change: 1 addition & 0 deletions splitCatchment.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[-73.74600553494398, 44.00677064364619], [-73.74564034772175, 44.006708220257686], [-73.7458145778786, 44.00618486187204], [-73.74617976245392, 44.0062472847174], [-73.74626687520464, 44.00598560536642], [-73.74663205912603, 44.00604802694228], [-73.74671916988382, 44.00578634739527], [-73.74708435315124, 44.005848767701586], [-73.74717146191612, 44.005587087958546], [-73.74753664452956, 44.00564950699539], [-73.74762375130155, 44.005387827056396], [-73.747988933261, 44.0054502448237], [-73.74807603804011, 44.00518856468874], [-73.74844121934557, 44.00525098118656], [-73.74852832213182, 44.00498930085563], [-73.74889350278325, 44.005051716083926], [-73.74898060357667, 44.004790035556994], [-73.74934578357406, 44.004852449515774], [-73.74943288237465, 44.00459076879292], [-73.74979806171798, 44.004653181482276], [-73.74988515852577, 44.004391500563415], [-73.75025033721504, 44.00445391198326], [-73.75033743203005, 44.00419223086852], [-73.75070261006525, 44.004254641018825], [-73.75078970288747, 44.00399295970815], [-73.75152005831899, 44.0041177764719], [-73.75143296814363, 44.004379458325516], [-73.75106778876977, 44.004317050171174], [-73.75098069660166, 44.00457873182886], [-73.75061551657367, 44.00451632240503], [-73.75052842241277, 44.00477800386682], [-73.75016324173069, 44.004715593173515], [-73.75007614557698, 44.00497727443939], [-73.74971096424083, 44.00491486247661], [-73.74962386609431, 44.005176543546455], [-73.74925868410406, 44.005114130314205], [-73.74917158396471, 44.00537581118811], [-73.74880640132044, 44.0053133966863], [-73.74871929918821, 44.0055750773643], [-73.7483541158899, 44.005512661593016], [-73.74826701176481, 44.00577434207502], [-73.74790182781246, 44.00571192503426], [-73.74781472169447, 44.00597360532026], [-73.74744953708813, 44.00591118700994], [-73.74736242897723, 44.00617286709999], [-73.74699724371689, 44.006110447520136], [-73.74691013361307, 44.00637212741429], [-73.74654494769875, 44.00630970656482], [-73.74645783560199, 44.006571386263005], [-73.7460926490337, 44.00650896414398], [-73.74600553494398, 44.00677064364619]]]}, "properties": {"area": 22499.99999999255}}
Loading

0 comments on commit 8827c11

Please sign in to comment.