def from_sr(cls, sr=None, active_area=None, isfr=None, epsg=None, proj_str=None, prjfile=None): """Create StructureGrid class instance from a flopy SpatialReference instance.""" vertices = sr.vertices polygons = [Polygon(v) for v in sr.vertices] df = pd.DataFrame( { 'node': range(0, len(vertices)), 'i': sorted(list(range(sr.nrow)) * sr.ncol), 'j': list(range(sr.ncol)) * sr.nrow, 'geometry': polygons }, columns=['node', 'i', 'j', 'geometry']) if epsg is None: epsg = sr.epsg if proj_str is None: proj_str = sr.proj4_str if prjfile is not None: proj_str = get_proj_str(prjfile) if isfr is not None: # if a 3D array is supplied for isfr, convert to 2D # (retain all i, j locations with at least one active layer; # assuming that top of each highest-active cell represents the land surface) if len(isfr.shape) == 3: isfr = np.any(isfr == 1, axis=0).astype(int) df['isfr'] = isfr.ravel() elif isfr.shape == (sr.nrow, sr.ncol): df['isfr'] = isfr.ravel() else: assert isfr.size == len(df), \ "isfr must be of shape (nlay, nrow, ncol), (nrow, ncol) or (nrow * ncol,)" df['isfr'] = isfr uniform = False dx, dy = None, None if len(set(sr.delc)) == 1 and len(set(sr.delr)) == 1: dx = sr.delr[0] * sr.length_multiplier dy = sr.delc[0] * sr.length_multiplier uniform = True return cls.from_dataframe(df, uniform=uniform, xul=sr.xul, yul=sr.yul, dx=dx, dy=dy, rotation=sr.rotation, model_units=sr.model_length_units, bounds=sr.bounds, active_area=active_area, epsg=epsg, proj_str=proj_str, prjfile=prjfile)
def __init__(self, crs=None, epsg=None, proj_str=None, prjfile=None): self._crs = crs self._epsg = epsg self._proj_str = proj_str self._length_units = None self.prjfile = prjfile if prjfile is not None: assert os.path.exists(prjfile), "{} not found.".format(prjfile) self._proj_str = get_proj_str(prjfile) if not self.is_valid: print("Warning, coordinate reference system {}\nis invalid. " "\nPlease supply a valid epsg code, proj4 string, " "or ESRI projection file".format(self.__repr__()))
def setup_lake_info(model): # lake package must have a source_data block # (e.g. for supplying shapefile delineating lake extents) source_data = model.cfg.get('lak', {}).get('source_data') if source_data is None or 'lak' not in model.package_list: return lakesdata = model.load_features(**source_data['lakes_shapefile']) lakesdata_proj_str = get_proj_str(source_data['lakes_shapefile']['filename']) id_column = source_data['lakes_shapefile']['id_column'].lower() name_column = source_data['lakes_shapefile'].get('name_column', 'name').lower() nlakes = len(lakesdata) # make dataframe with lake IDs, names and locations centroids = project([g.centroid for g in lakesdata.geometry], lakesdata_proj_str, 'epsg:4269') df = pd.DataFrame({'lak_id': np.arange(1, nlakes + 1), 'feat_id': lakesdata[id_column].values, 'name': lakesdata[name_column].values, 'latitude': [c.y for c in centroids], 'geometry': lakesdata['geometry'] }) # get starting stages from model top, for specifying ranges stages = [] for lakid in df['lak_id']: loc = model._lakarr2d == lakid est_stage = model.dis.top.array[loc].min() stages.append(est_stage) df['strt'] = np.array(stages) # save a lookup file mapping lake ids to hydroids lookup_file = model.cfg['lak']['output_files']['lookup_file'].format(model.name) df.drop('geometry', axis=1).to_csv(lookup_file, index=False) # clean up names df['name'].replace('nan', '', inplace=True) df['name'].replace(' ', '', inplace=True) return df
def proj_str(self): if self._proj_str is None and self.crs is not None: self._proj_str = to_string(self._crs) elif self._proj_str is None and self.prjfile is not None: self._proj_str = get_proj_str(self.prjfile) return self._proj_str
def setup_structured_grid(xoff=None, yoff=None, xul=None, yul=None, nrow=None, ncol=None, nlay=None, dxy=None, delr=None, delc=None, top=None, botm=None, rotation=0., parent_model=None, snap_to_NHG=False, features=None, features_shapefile=None, id_column=None, include_ids=None, buffer=1000, crs=None, epsg=None, model_length_units=None, grid_file='grid.json', bbox_shapefile=None, **kwargs): """""" print('setting up model grid...') t0 = time.time() # conversions for model/parent model units to meters # set regular flag for handling delc/delr to_meters_inset = convert_length_units(model_length_units, 'meters') regular = True if dxy is not None: delr_m = np.round(dxy * to_meters_inset, 4) # dxy is specified in model units delc_m = delr_m if delr is not None: delr_m = np.round(delr * to_meters_inset, 4) # delr is specified in model units if not np.isscalar(delr_m): if (set(delr_m)) == 1: delr_m = delr_m[0] else: regular = False if delc is not None: delc_m = np.round(delc * to_meters_inset, 4) # delc is specified in model units if not np.isscalar(delc_m): if (set(delc_m)) == 1: delc_m = delc_m[0] else: regular = False if parent_model is not None: to_meters_parent = convert_length_units( get_model_length_units(parent_model), 'meters') # parent model grid spacing in meters parent_delr_m = np.round( parent_model.dis.delr.array[0] * to_meters_parent, 4) if not parent_delr_m % delr_m == 0: raise ValueError( 'inset delr spacing of {} must be factor of parent spacing of {}' .format(delr_m, parent_delr_m)) parent_delc_m = np.round( parent_model.dis.delc.array[0] * to_meters_parent, 4) if not parent_delc_m % delc_m == 0: raise ValueError( 'inset delc spacing of {} must be factor of parent spacing of {}' .format(delc_m, parent_delc_m)) if epsg is not None: crs = pyproj.crs.CRS.from_epsg(epsg) elif crs is not None: from gisutils import get_authority_crs crs = get_authority_crs(crs) elif parent_model is not None: crs = parent_model.modelgrid.crs # option 1: make grid from xoff, yoff and specified dimensions if xoff is not None and yoff is not None: assert nrow is not None and ncol is not None, \ "Need to specify nrow and ncol if specifying xoffset and yoffset." if regular: height_m = np.round(delc_m * nrow, 4) width_m = np.round(delr_m * ncol, 4) else: height_m = np.sum(delc_m) width_m = np.sum(delr_m) # optionally align grid with national hydrologic grid # grids snapping to NHD must have spacings that are a factor of 1 km if snap_to_NHG: assert regular and np.allclose(1000 % delc_m, 0, atol=1e-4) x, y = get_point_on_national_hydrogeologic_grid(xoff, yoff, offset='edge', op=np.floor) xoff = x yoff = y rotation = 0. # need to specify xul, yul in case snapping to parent # todo: allow snapping to parent grid on xoff, yoff if rotation != 0: raise NotImplementedError('Rotated grids not supported.') xul = xoff yul = yoff + height_m # option 2: make grid using buffered feature bounding box else: if features is None and features_shapefile is not None: # Make sure shapefile and bbox filter are in dest (model) CRS # TODO: CRS wrangling could be added to shp2df as a feature reproject_filter = False try: from gisutils import get_shapefile_crs features_crs = get_shapefile_crs(features_shapefile) if features_crs != crs: reproject_filter = True except: features_crs = get_proj_str(features_shapefile) reproject_filter = True filter = None if parent_model is not None: if reproject_filter: filter = project(parent_model.modelgrid.bbox, parent_model.modelgrid.crs, features_crs).bounds else: filter = parent_model.modelgrid.bbox.bounds shp2df_kwargs = {'dest_crs': crs} shp2df_kwargs = get_input_arguments(shp2df_kwargs, shp2df) df = shp2df(features_shapefile, filter=filter, **shp2df_kwargs) # optionally subset shapefile data to specified features if id_column is not None and include_ids is not None: df = df.loc[df[id_column].isin(include_ids)] # use all features by default features = df.geometry.tolist() # convert multiple features to a MultiPolygon if isinstance(features, list): if len(features) > 1: features = MultiPolygon(features) else: features = features[0] # size the grid based on the bbox for features x1, y1, x2, y2 = features.bounds L = buffer # distance from area of interest to boundary xul = x1 - L yul = y2 + L height_m = np.round(yul - (y1 - L), 4) # initial model height from buffer distance width_m = np.round((x2 + L) - xul, 4) rotation = 0. # rotation not supported with this option # align model with parent grid if there is a parent model # (and not snapping to national hydrologic grid) if parent_model is not None and not snap_to_NHG: # get location of coinciding cell in parent model for upper left pi, pj = parent_model.modelgrid.intersect(xul, yul) verts = np.array(parent_model.modelgrid.get_cell_vertices(pi, pj)) xul, yul = verts[:, 0].min(), verts[:, 1].max() # adjust the dimensions to align remaining corners def roundup(number, increment): return int(np.ceil(number / increment) * increment) height = roundup(height_m, parent_delr_m) width = roundup(width_m, parent_delc_m) # update nrow, ncol after snapping to parent grid if regular: nrow = int(height / delc_m) # h is in meters ncol = int(width / delr_m) # set the grid configuration dictionary # spacing is in meters (consistent with projected CRS) # (modelgrid object will be updated automatically from this dictionary) #if rotation == 0.: # xll = xul # yll = yul - model.height grid_cfg = { 'nrow': int(nrow), 'ncol': int(ncol), 'nlay': nlay, 'delr': delr_m, 'delc': delc_m, 'xoff': xoff, 'yoff': yoff, 'xul': xul, 'yul': yul, 'rotation': rotation, 'lenuni': 2 } if regular: grid_cfg['delr'] = np.ones(grid_cfg['ncol'], dtype=float) * grid_cfg['delr'] grid_cfg['delc'] = np.ones(grid_cfg['nrow'], dtype=float) * grid_cfg['delc'] grid_cfg['delr'] = grid_cfg['delr'].tolist() # for serializing to json grid_cfg['delc'] = grid_cfg['delc'].tolist() # renames for flopy modelgrid renames = {'rotation': 'angrot'} for k, v in renames.items(): if k in grid_cfg: grid_cfg[v] = grid_cfg.pop(k) # add epsg or wkt if there isn't an epsg if epsg is not None: grid_cfg['epsg'] = epsg elif crs is not None: if 'epsg' in crs.srs.lower(): grid_cfg['epsg'] = int(crs.srs.split(':')[1]) else: grid_cfg['wkt'] = crs.srs else: warnings.warn('No coordinate system reference provided for model grid!' 'Model input data may not be mapped correctly.') # set up the model grid instance grid_cfg['top'] = top grid_cfg['botm'] = botm grid_cfg.update(kwargs) # update with any kwargs from function call kwargs = get_input_arguments(grid_cfg, MFsetupGrid) modelgrid = MFsetupGrid(**kwargs) modelgrid.cfg = grid_cfg # write grid info to json, and shapefile of bbox # omit top and botm arrays from json represenation of grid # (just for horizontal disc.) del grid_cfg['top'] del grid_cfg['botm'] fileio.dump(grid_file, grid_cfg) if bbox_shapefile is not None: write_bbox_shapefile(modelgrid, bbox_shapefile) print("finished in {:.2f}s\n".format(time.time() - t0)) return modelgrid
def rasterize(feature, grid, id_column=None, include_ids=None, crs=None, epsg=None, proj4=None, dtype=np.float32, **kwargs): """Rasterize a feature onto the model grid, using the rasterio.features.rasterize method. Features are intersected if they contain the cell center. Parameters ---------- feature : str (shapefile path), list of shapely objects, or dataframe with geometry column id_column : str Column with unique integer identifying each feature; values from this column will be assigned to the output raster. grid : grid.StructuredGrid instance crs : obj A Python int, dict, str, or pyproj.crs.CRS instance passed to :meth:`pyproj.crs.CRS.from_user_input` Can be any of: - PROJ string - Dictionary of PROJ parameters - PROJ keyword arguments for parameters - JSON string with PROJ parameters - CRS WKT string - An authority string [i.e. 'epsg:4326'] - An EPSG integer code [i.e. 4326] - A tuple of ("auth_name": "auth_code") [i.e ('epsg', '4326')] - An object with a `to_wkt` method. - A :class:`pyproj.crs.CRS` class dtype : dtype Datatype for the output array **kwargs : keyword arguments to rasterio.features.rasterize() https://rasterio.readthedocs.io/en/stable/api/rasterio.features.html Returns ------- 2D numpy array with intersected values """ try: from rasterio import Affine, features except: print('This method requires rasterio.') return if epsg is not None: warnings.warn( "The epsg argument is deprecated. Use crs instead, " "which requires gisutils >= 0.2", DeprecationWarning) if proj4 is not None: warnings.warn( "The epsg argument is deprecated. Use crs instead, " "which requires gisutils >= 0.2", DeprecationWarning) if crs is not None: if version.parse(gisutils.__version__) < version.parse('0.2.0'): raise ValueError("The crs argument requires gisutils >= 0.2") from gisutils import get_authority_crs crs = get_authority_crs(crs) trans = grid.transform kwargs = {} if isinstance(feature, str): proj4 = get_proj_str(feature) kwargs = {'dest_crs': grid.crs} kwargs = get_input_arguments(kwargs, shp2df) df = shp2df(feature, **kwargs) elif isinstance(feature, pd.DataFrame): df = feature.copy() elif isinstance(feature, collections.Iterable): # list of shapefiles if isinstance(feature[0], str): proj4 = get_proj_str(feature[0]) kwargs = {'dest_crs': grid.crs} kwargs = get_input_arguments(kwargs, shp2df) df = shp2df(feature, **kwargs) else: df = pd.DataFrame({'geometry': feature}) elif not isinstance(feature, collections.Iterable): df = pd.DataFrame({'geometry': [feature]}) else: print('unrecognized feature input') return # handle shapefiles in different CRS than model grid if 'dest_crs' not in kwargs: reproject = False # todo: consolidate rasterize reprojection to just use crs if crs is not None: if crs != grid.crs: df['geometry'] = project(df.geometry.values, crs, grid.crs) if proj4 is not None: if proj4 != grid.proj_str: reproject = True elif epsg is not None and grid.epsg is not None: if epsg != grid.epsg: reproject = True from fiona.crs import from_epsg, to_string proj4 = to_string(from_epsg(epsg)) if reproject: df['geometry'] = project(df.geometry.values, proj4, grid.proj_str) # subset to include_ids if id_column is not None and include_ids is not None: df = df.loc[df[id_column].isin(include_ids)].copy() # create list of GeoJSON features, with unique value for each feature if id_column is None: numbers = range(1, len(df) + 1) # if IDs are strings, get a number for each one # pd.DataFrame.unique() generally preserves order elif isinstance(df[id_column].dtype, np.object): unique_values = df[id_column].unique() values = dict(zip(unique_values, range(1, len(unique_values) + 1))) numbers = [values[n] for n in df[id_column]] else: numbers = df[id_column].tolist() geoms = list(zip(df.geometry, numbers)) result = features.rasterize(geoms, out_shape=(grid.nrow, grid.ncol), transform=trans) assert result.sum(axis=(0, 1)) != 0, "Nothing was intersected!" return result.astype(dtype)
def from_modelgrid(cls, mg=None, active_area=None, isfr=None, epsg=None, proj_str=None, prjfile=None): """Create StructureGrid class instance from a flopy.discretization.StructuredGrid instance.""" i, j = np.indices((mg.nrow, mg.ncol)) vertices = mg._cell_vert_list(i.ravel(), j.ravel()) polygons = [Polygon(v) for v in vertices] df = pd.DataFrame( { 'node': range(0, len(vertices)), 'i': sorted(list(range(mg.nrow)) * mg.ncol), 'j': list(range(mg.ncol)) * mg.nrow, 'geometry': polygons }, columns=['node', 'i', 'j', 'geometry']) if epsg is None: epsg = mg.epsg if proj_str is None: proj_str = mg.proj4 if prjfile is not None: proj_str = get_proj_str(prjfile) if isfr is not None: # if a 3D array is supplied for isfr, convert to 2D # (retain all i, j locations with at least one active layer; # assuming that top of each highest-active cell represents the land surface) if len(isfr.shape) == 3: isfr = np.any(isfr == 1, axis=0).astype(int) df['isfr'] = isfr.ravel() elif isfr.shape == (mg.nrow, mg.ncol): df['isfr'] = isfr.ravel() else: assert isfr.size == len(df), \ "isfr must be of shape (nlay, nrow, ncol), (nrow, ncol) or (nrow * ncol,)" df['isfr'] = isfr uniform = False dx, dy = None, None if len(set(mg.delc)) == 1 and len(set(mg.delr)) == 1: dx = mg.delr[0] dy = mg.delc[0] uniform = True bounds = mg.extent[0], mg.extent[2], mg.extent[1], mg.extent[3] # upper left corner x0 = mg.xyedges[0][0] y0 = mg.xyedges[1][0] xul, yul = mg.get_coords(x0, y0) return cls.from_dataframe(df, uniform=uniform, xul=xul, yul=yul, dx=dx, dy=dy, rotation=mg.angrot, bounds=bounds, active_area=active_area, epsg=epsg, proj_str=proj_str, prjfile=prjfile)
def rasterize(feature, grid, id_column=None, include_ids=None, epsg=None, proj4=None, dtype=np.float32): """Rasterize a feature onto the model grid, using the rasterio.features.rasterize method. Features are intersected if they contain the cell center. Parameters ---------- feature : str (shapefile path), list of shapely objects, or dataframe with geometry column id_column : str Column with unique integer identifying each feature; values from this column will be assigned to the output raster. grid : grid.StructuredGrid instance epsg : int EPSG code for feature coordinate reference system. Optional, but an epgs code or proj4 string must be supplied if feature isn't a shapefile, and isn't in the same CRS as the model. proj4 : str Proj4 string for feature CRS (optional) dtype : dtype Datatype for the output array Returns ------- 2D numpy array with intersected values """ try: from rasterio import features from rasterio import Affine except: print('This method requires rasterio.') return #trans = Affine(sr.delr[0], 0., sr.xul, # 0., -sr.delc[0], sr.yul) * Affine.rotation(sr.rotation) trans = grid.transform if isinstance(feature, str): proj4 = get_proj_str(feature) df = shp2df(feature) elif isinstance(feature, pd.DataFrame): df = feature.copy() elif isinstance(feature, collections.Iterable): # list of shapefiles if isinstance(feature[0], str): proj4 = get_proj_str(feature[0]) df = shp2df(feature) else: df = pd.DataFrame({'geometry': feature}) elif not isinstance(feature, collections.Iterable): df = pd.DataFrame({'geometry': [feature]}) else: print('unrecognized feature input') return # handle shapefiles in different CRS than model grid reproject = False if proj4 is not None: if proj4 != grid.proj_str: reproject = True elif epsg is not None and grid.epsg is not None: if epsg != grid.epsg: reproject = True from fiona.crs import to_string, from_epsg proj4 = to_string(from_epsg(epsg)) if reproject: df['geometry'] = project(df.geometry.values, proj4, grid.proj_str) # subset to include_ids if id_column is not None and include_ids is not None: df = df.loc[df[id_column].isin(include_ids)].copy() # create list of GeoJSON features, with unique value for each feature if id_column is None: numbers = range(1, len(df)+1) # if IDs are strings, get a number for each one # pd.DataFrame.unique() generally preserves order elif isinstance(df[id_column].dtype, np.object): unique_values = df[id_column].unique() values = dict(zip(unique_values, range(1, len(unique_values) + 1))) numbers = [values[n] for n in df[id_column]] else: numbers = df[id_column].tolist() geoms = list(zip(df.geometry, numbers)) result = features.rasterize(geoms, out_shape=(grid.nrow, grid.ncol), transform=trans) assert result.sum(axis=(0, 1)) != 0, "Nothing was intersected!" return result.astype(dtype)