Example #1
0
 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)
Example #2
0
    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__()))
Example #3
0
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
Example #4
0
 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
Example #5
0
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
Example #6
0
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)
Example #7
0
    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)
Example #8
0
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)