def getDEMForBoundingBox(config, outputDir, outFilename, bbox, srs, coverage=DEFAULT_COVERAGE, resx=None, resy=None, interpolation=DEFAULT_RASTER_RESAMPLE_METHOD, scale=1.0, overwrite=True, verbose=False, outfp=sys.stdout): """ Fetch U.S. 1/3 arcsecond DEM data hosted by U.S. Geological Survey using OGC WCS 1.1.1 query. @note Adapted from code provided by [email protected]. @param config A Python ConfigParser (not currently used) @param outputDir String representing the absolute/relative path of the directory into which output raster should be written @param outFilename String representing the name of the raster file to be written @param bbox Dict representing the lat/long coordinates and spatial reference of the bounding box area for which the raster is to be extracted. The following keys must be specified: minX, minY, maxX, maxY, srs. @param srs String representing the spatial reference of the raster to be returned. @param coverage String representing the raster source from which to get the raster coverage. Must be one of: NHDPlus_hydroDEM, NED @param resx Float representing the X resolution of the raster(s) to be returned @param resy Float representing the Y resolution of the raster(s) to be returned @param interpolation String representing interpolation method. Must be one of RASTER_RESAMPLE_METHOD. Defaults to DEFAULT_RASTER_RESAMPLE_METHOD. @param scale Float representing factor by which to scale elevation data. Defaults to 1.0. @param overwrite Boolean value indicating whether or not the file indicated by filename should be overwritten. If False and filename exists, IOError exception will be thrown with errno.EEXIST @param verbose Boolean True if detailed output information should be printed to outfp @param outfp File-like object to which verbose output should be printed @raise IOError if outputDir is not a writable directory @raise IOError if outFilename already exists and overwrite is False (see above) @raise Exception if there was an error making the WCS request @return Tuple(True if raster data were fetched and False if not, URL of raster fetched) """ dataFetched = False assert(coverage in COVERAGES.keys()) assert('minX' in bbox) assert('minY' in bbox) assert('maxX' in bbox) assert('maxY' in bbox) assert('srs' in bbox) assert(scale > 0.0) if not os.path.isdir(outputDir): raise IOError(errno.ENOTDIR, "Output directory %s is not a directory" % (outputDir,)) if not os.access(outputDir, os.W_OK): raise IOError(errno.EACCES, "Not allowed to write to output directory %s" % (outputDir,)) outputDir = os.path.abspath(outputDir) outFilepath = os.path.join(outputDir, outFilename) deleteOldfile = False if os.path.exists(outFilepath): if overwrite: deleteOldfile = True else: raise IOError(errno.EEXIST, "Raster file %s already exists" % outFilepath) cov = COVERAGES[coverage] grid_origin = cov['grid_origin'] grid_offset = cov['grid_offset'] grid_extent = cov['grid_extent'] s_srs = cov['srs'] if resx is None: resx = abs(grid_offset[0]) if resy is None: resy = abs(grid_offset[1]) t_srs = srs # For requests, grid cell centers are used. Need to add half the grid_offset to the grid_origin grid_origin_0 = grid_origin[0] + grid_offset[0] / 2.0 grid_origin_1 = grid_origin[1] + grid_offset[1] / 2.0 p = Proj(init=DEFAULT_SRS) (x1, y1) = p(bbox['minX'], bbox['minY']) (x2, y2) = p(bbox['maxX'], bbox['maxY']) # Pad the width of the bounding box as the Albers transform results in regions of interest # that are a bit narrower than I would like, which risks watershed boundaries lying beyond # the DEM boundary. len_x = x2 - x1 del_x = len_x * 0.30 x1 = x1 - del_x x2 = x2 + del_x bbox_srs = DEFAULT_SRS # Find the number of grid cells from the grid origin to each edge of the request. # Multiply by the grid_offset and add the grid origin to get to the request location. xi1 = floor((x1 - grid_origin_0) / grid_offset[0]) * grid_offset[0] + grid_origin_0 xi2 = ceil((x2 - grid_origin_0) / grid_offset[0]) * grid_offset[0] + grid_origin_0 yi1 = floor((y1 - grid_origin_1) / grid_offset[1]) * grid_offset[1] + grid_origin_1 yi2 = ceil((y2 - grid_origin_1) / grid_offset[1]) * grid_offset[1] + grid_origin_1 # coverage, crs, bbox, format. May have the following fields: response_srs, store, resx, resy, interpolation url = URL_PROTO.format(coverage=coverage, x1=xi1, y1=yi1, x2=xi2, y2=yi2, bbox_srs=bbox_srs, xoffset=grid_offset[0], yoffset=grid_offset[1]) #ORG urlFetched = "http://%s%s" % (HOST, url) urlFetched = "https://%s%s" % (HOST, url) if verbose: outfp.write("Acquiring DEM data from {0} ...\n".format(urlFetched)) # Make initial request, which will return the URL of our clipped coverage r = requests.get(urlFetched) if r.status_code != 200: raise Exception("Error fetching {url}, HTTP status code was {code} {reason}".format(urlFetched, r.status_code, r.reason)) usgs_dem_coverage_handler = USGSDEMCoverageHandler() xml.sax.parseString(r.text, usgs_dem_coverage_handler) coverage_url = usgs_dem_coverage_handler.coverage_url if coverage_url is None: raise Exception("Unable to deteremine coverage URL from WCS server response. Response text was: {0}".format(r.text)) parsed_coverage_url = urlparse.urlparse(coverage_url) if verbose: outfp.write("Downloading DEM coverage from {0} ...\n".format(coverage_url)) # Download coverage to tempfile tmp_dir = tempfile.mkdtemp() tmp_cov_name = os.path.join(tmp_dir, 'usgswcsdemtmp') tmp_out = open(tmp_cov_name, mode='w+b') #ORG conn = httplib.HTTPConnection(parsed_coverage_url.netloc) conn = httplib.HTTPSConnection(parsed_coverage_url.netloc) try: conn.request('GET', parsed_coverage_url.path) res = conn.getresponse(buffering=True) except socket.error as e: msg = "Encountered the following error when trying to read raster from %s. Error: %s. Please try again later or contact the developer." % \ (urlFetched, str(e) ) raise Exception(msg) if 200 != res.status: msg = "HTTP response %d %s encountered when querying %s. Please try again later or contact the developer." % \ (res.status, res.reason, urlFetched) raise Exception(msg) contentType = res.getheader('Content-Type') mimeType = 'image/tiff' if contentType.startswith(mimeType): # The data returned were of the type expected, read the data data = res.read(_BUFF_LEN) if data: dataFetched = True while data: tmp_out.write(data) data = res.read(_BUFF_LEN) tmp_out.close() elif contentType.startswith(CONTENT_TYPE_ERRORS): # Read the error and print to stderr msg = "The following error was encountered reading WCS coverage URL %s\n\n" %\ (coverage_url, ) data = res.read(_BUFF_LEN) while data: msg += data raise Exception(msg) else: msg = "Query for raster from URL %s returned content type %s, was expecting type %s. " + \ " Operation failed." % \ (coverage_url, contentType, mimeType) raise Exception(msg) # Rescale raster values if requested if scale != 1.0: # Rescale values in raster if verbose: outfp.write("Rescaling raster values by factor {0}".format(scale)) rescale_out = os.path.basename("{0}_rescale".format(tmp_cov_name)) rescaleRaster(config, tmp_dir, tmp_cov_name, rescale_out, scale) tmp_cov_name = os.path.join(tmp_dir, rescale_out) if deleteOldfile: deleteGeoTiff(outFilepath) if verbose: msg = "Resampling raster from {s_srs} to {t_srs} " + \ "with X resolution {resx} and Y resolution {resy}\n" outfp.write(msg.format(s_srs=s_srs, t_srs=t_srs, resx=resx, resy=resy)) # Re-sample to target spatial reference and resolution resampleRaster(config, outputDir, tmp_cov_name, outFilename, s_srs, t_srs, resx, resy, interpolation) # Delete temp directory shutil.rmtree(tmp_dir) return ( dataFetched, urlFetched )
else: sys.stdout.write("Reprojecting DEM from %s to %s, spatial resolution (%.2f, %.2f) to (%.2f, %.2f)..." % \ (s_srs, t_srs, inSpatialMetadata[0], inSpatialMetadata[1], demResolutionX, demResolutionY) ) sys.stdout.flush() if args.scale: resampleRaster(context.config, context.projectDir, inDEMPath, demFilenameTemp, s_srs, t_srs, demResolutionX, demResolutionY, args.resampleMethod) else: resampleRaster(context.config, context.projectDir, inDEMPath, demFilename, s_srs, t_srs, demResolutionX, demResolutionY, args.resampleMethod) if args.scale: rescaleRaster(context.config, context.projectDir, demFilepathTemp, demFilename, args.scale) os.unlink(demFilepathTemp) sys.stdout.write('done\n') # Get the bounding box for the DEM bbox = getBoundingBoxForRaster(demFilepath) # Write a shapefile for the bounding box shpFilename = writeBboxPolygonToShapefile(bbox, context.projectDir, "studyarea") # Write metadata GenericMetadata.writeStudyAreaEntry( context, "bbox_wgs84", "%f %f %f %f" % (bbox['minX'], bbox['minY'], bbox['maxX'], bbox['maxY'])) # Get spatial metadata for DEM
def getDEMForBoundingBox(config, outputDir, outFilename, bbox, srs, coverage=DEFAULT_COVERAGE, resx=None, resy=None, interpolation=DEFAULT_RASTER_RESAMPLE_METHOD, scale=1.0, overwrite=True, verbose=False, outfp=sys.stdout): """ Fetch U.S. 1/3 arcsecond DEM data hosted by U.S. Geological Survey using OGC WCS 1.1.1 query. @note Adapted from code provided by [email protected]. @param config A Python ConfigParser (not currently used) @param outputDir String representing the absolute/relative path of the directory into which output raster should be written @param outFilename String representing the name of the raster file to be written @param bbox Dict representing the lat/long coordinates and spatial reference of the bounding box area for which the raster is to be extracted. The following keys must be specified: minX, minY, maxX, maxY, srs. @param srs String representing the spatial reference of the raster to be returned. @param coverage String representing the raster source from which to get the raster coverage. Must be one of: NHDPlus_hydroDEM, NED @param resx Float representing the X resolution of the raster(s) to be returned @param resy Float representing the Y resolution of the raster(s) to be returned @param interpolation String representing interpolation method. Must be one of RASTER_RESAMPLE_METHOD. Defaults to DEFAULT_RASTER_RESAMPLE_METHOD. @param scale Float representing factor by which to scale elevation data. Defaults to 1.0. @param overwrite Boolean value indicating whether or not the file indicated by filename should be overwritten. If False and filename exists, IOError exception will be thrown with errno.EEXIST @param verbose Boolean True if detailed output information should be printed to outfp @param outfp File-like object to which verbose output should be printed @raise IOError if outputDir is not a writable directory @raise IOError if outFilename already exists and overwrite is False (see above) @raise Exception if there was an error making the WCS request @return Tuple(True if raster data were fetched and False if not, URL of raster fetched) """ dataFetched = False assert (coverage in list(COVERAGES.keys())) assert ('minX' in bbox) assert ('minY' in bbox) assert ('maxX' in bbox) assert ('maxY' in bbox) assert ('srs' in bbox) assert (scale > 0.0) if not os.path.isdir(outputDir): raise IOError(errno.ENOTDIR, "Output directory %s is not a directory" % (outputDir, )) if not os.access(outputDir, os.W_OK): raise IOError( errno.EACCES, "Not allowed to write to output directory %s" % (outputDir, )) outputDir = os.path.abspath(outputDir) outFilepath = os.path.join(outputDir, outFilename) deleteOldfile = False if os.path.exists(outFilepath): if overwrite: deleteOldfile = True else: raise IOError(errno.EEXIST, "Raster file %s already exists" % outFilepath) cov = COVERAGES[coverage] grid_origin = cov['grid_origin'] grid_offset = cov['grid_offset'] grid_extent = cov['grid_extent'] s_srs = cov['srs'] if resx is None: resx = abs(grid_offset[0]) if resy is None: resy = abs(grid_offset[1]) t_srs = srs # For requests, grid cell centers are used. Need to add half the grid_offset to the grid_origin grid_origin_0 = grid_origin[0] + grid_offset[0] / 2.0 grid_origin_1 = grid_origin[1] + grid_offset[1] / 2.0 p = Proj(init=DEFAULT_SRS) (x1, y1) = p(bbox['minX'], bbox['minY']) (x2, y2) = p(bbox['maxX'], bbox['maxY']) # Pad the width of the bounding box as the Albers transform results in regions of interest # that are a bit narrower than I would like, which risks watershed boundaries lying beyond # the DEM boundary. len_x = x2 - x1 del_x = len_x * 0.30 x1 = x1 - del_x x2 = x2 + del_x bbox_srs = DEFAULT_SRS # Find the number of grid cells from the grid origin to each edge of the request. # Multiply by the grid_offset and add the grid origin to get to the request location. xi1 = floor( (x1 - grid_origin_0) / grid_offset[0]) * grid_offset[0] + grid_origin_0 xi2 = ceil( (x2 - grid_origin_0) / grid_offset[0]) * grid_offset[0] + grid_origin_0 yi1 = floor( (y1 - grid_origin_1) / grid_offset[1]) * grid_offset[1] + grid_origin_1 yi2 = ceil( (y2 - grid_origin_1) / grid_offset[1]) * grid_offset[1] + grid_origin_1 # coverage, crs, bbox, format. May have the following fields: response_srs, store, resx, resy, interpolation url = URL_PROTO.format(coverage=coverage, x1=xi1, y1=yi1, x2=xi2, y2=yi2, bbox_srs=bbox_srs, xoffset=grid_offset[0], yoffset=grid_offset[1]) #ORG urlFetched = "http://%s%s" % (HOST, url) urlFetched = "https://%s%s" % (HOST, url) if verbose: outfp.write("Acquiring DEM data from {0} ...\n".format(urlFetched)) # Make initial request, which will return the URL of our clipped coverage r = requests.get(urlFetched) if r.status_code != 200: raise Exception( "Error fetching {url}, HTTP status code was {code} {reason}". format(urlFetched, r.status_code, r.reason)) usgs_dem_coverage_handler = USGSDEMCoverageHandler() xml.sax.parseString(r.text, usgs_dem_coverage_handler) coverage_url = usgs_dem_coverage_handler.coverage_url if coverage_url is None: raise Exception( "Unable to deteremine coverage URL from WCS server response. Response text was: {0}" .format(r.text)) parsed_coverage_url = urllib.parse.urlparse(coverage_url) if verbose: outfp.write( "Downloading DEM coverage from {0} ...\n".format(coverage_url)) # Download coverage to tempfile tmp_dir = tempfile.mkdtemp() tmp_cov_name = os.path.join(tmp_dir, 'usgswcsdemtmp') tmp_out = open(tmp_cov_name, mode='w+b') #ORG conn = httplib.HTTPConnection(parsed_coverage_url.netloc) conn = http.client.HTTPSConnection(parsed_coverage_url.netloc) try: conn.request('GET', parsed_coverage_url.path) res = conn.getresponse(buffering=True) except socket.error as e: msg = "Encountered the following error when trying to read raster from %s. Error: %s. Please try again later or contact the developer." % \ (urlFetched, str(e) ) raise Exception(msg) if 200 != res.status: msg = "HTTP response %d %s encountered when querying %s. Please try again later or contact the developer." % \ (res.status, res.reason, urlFetched) raise Exception(msg) contentType = res.getheader('Content-Type') mimeType = 'image/tiff' if contentType.startswith(mimeType): # The data returned were of the type expected, read the data data = res.read(_BUFF_LEN) if data: dataFetched = True while data: tmp_out.write(data) data = res.read(_BUFF_LEN) tmp_out.close() elif contentType.startswith(CONTENT_TYPE_ERRORS): # Read the error and print to stderr msg = "The following error was encountered reading WCS coverage URL %s\n\n" %\ (coverage_url, ) data = res.read(_BUFF_LEN) while data: msg += data raise Exception(msg) else: msg = "Query for raster from URL %s returned content type %s, was expecting type %s. " + \ " Operation failed." % \ (coverage_url, contentType, mimeType) raise Exception(msg) # Rescale raster values if requested if scale != 1.0: # Rescale values in raster if verbose: outfp.write("Rescaling raster values by factor {0}".format(scale)) rescale_out = os.path.basename("{0}_rescale".format(tmp_cov_name)) rescaleRaster(config, tmp_dir, tmp_cov_name, rescale_out, scale) tmp_cov_name = os.path.join(tmp_dir, rescale_out) if deleteOldfile: deleteGeoTiff(outFilepath) if verbose: msg = "Resampling raster from {s_srs} to {t_srs} " + \ "with X resolution {resx} and Y resolution {resy}\n" outfp.write(msg.format(s_srs=s_srs, t_srs=t_srs, resx=resx, resy=resy)) # Re-sample to target spatial reference and resolution resampleRaster(config, outputDir, tmp_cov_name, outFilename, s_srs, t_srs, resx, resy, interpolation) # Delete temp directory shutil.rmtree(tmp_dir) return (dataFetched, urlFetched)
else: copyRasterToGeoTIFF(context.config, context.projectDir, inDEMPath, demFilename) else: sys.stdout.write("Reprojecting DEM from %s to %s, spatial resolution (%.2f, %.2f) to (%.2f, %.2f)..." % \ (s_srs, t_srs, inSpatialMetadata[0], inSpatialMetadata[1], demResolutionX, demResolutionY) ) sys.stdout.flush() if args.scale: resampleRaster(context.config, context.projectDir, inDEMPath, demFilenameTemp, s_srs, t_srs, demResolutionX, demResolutionY, args.resampleMethod) else: resampleRaster(context.config, context.projectDir, inDEMPath, demFilename, s_srs, t_srs, demResolutionX, demResolutionY, args.resampleMethod) if args.scale: rescaleRaster(context.config, context.projectDir, demFilepathTemp, demFilename, args.scale) os.unlink(demFilepathTemp) sys.stdout.write('done\n') # Get the bounding box for the DEM bbox = getBoundingBoxForRaster(demFilepath) # Write a shapefile for the bounding box shpFilename = writeBboxPolygonToShapefile(bbox, context.projectDir, "studyarea") # Write metadata GenericMetadata.writeStudyAreaEntry(context, "bbox_wgs84", "%f %f %f %f" % (bbox['minX'], bbox['minY'], bbox['maxX'], bbox['maxY'])) # Get spatial metadata for DEM demSpatialMetadata = getSpatialReferenceForRaster(demFilepath) GenericMetadata.writeStudyAreaEntry(context, "dem_res_x", demSpatialMetadata[0]) GenericMetadata.writeStudyAreaEntry(context, "dem_res_y", demSpatialMetadata[1])