Exemplo n.º 1
0
def as_polygon_list(polygon):
    """Convenience method to return a list with one or more
    Polygons from a given Polygon or MultiPolygon.
    
    Parameters
    ----------
    polygon : list or Polygon or MultiPolygon
        Object to be converted
    
    Returns
    -------
    list
        list of Polygons
    """
    if isinstance(polygon, Polygon):
        return [polygon]
    elif isinstance(polygon, MultiPolygon):
        return [p for p in polygon]
    elif isinstance(polygon, list):
        lst = []
        for item in polygon:
            checks.check_argument(item, 'item in list', (list, Polygon, MultiPolygon))
            lst.extend(as_polygon_list(item))
        return lst
    else:
        raise TypeError(f'Expected Polygon or MultiPolygon. Got "{type(polygon)}"')
Exemplo n.º 2
0
    def generate_within_polygon(self, polygon, cellsize, rotation=0):
        """
        Function to generate a grid within a polygon. It uses the function
        'generate_grid' but automatically detects the extent.

        Parameters
        ----------
        polygon : (list of) shapely.geometry.Polygon or a shapely.geometry.MultiPolygon
        """

        checks.check_argument(polygon, 'polygon',
                              (list, Polygon, MultiPolygon))

        polygons = geometry.as_polygon_list(polygon)

        logger.info(
            f'Generating grid with cellsize {cellsize} m and rotation {rotation} degrees.'
        )
        convex = MultiPolygon(polygons).convex_hull

        # In case of a rotation, extend the grid far enough to make sure
        if rotation != 0:
            # Find a box that contains the whole polygon
            (x0, y0), xsize, ysize = geometry.minimum_bounds_fixed_rotation(
                convex, rotation)
        else:
            xsize = convex.bounds[2] - convex.bounds[0]
            ysize = convex.bounds[3] - convex.bounds[1]
            x0, y0 = convex.bounds[0], convex.bounds[1]

        self.generate_grid(x0=x0,
                           y0=y0,
                           dx=cellsize,
                           dy=cellsize,
                           ncols=int(xsize / cellsize) + 1,
                           nrows=int(ysize / cellsize) + 1,
                           clipgeo=polygon,
                           rotation=rotation)
Exemplo n.º 3
0
    def read_laterals(self,
                      locations,
                      lateral_discharges=None,
                      rr_boundaries=None):
        """
        Process laterals

        Parameters
        ----------
        locations: gpd.GeoDataFrame
            GeoDataFrame with at least 'geometry' (Point) and the column 'code'
        lateral_discharges: pd.DataFrame
            DataFrame with lateral discharges. The index should be a time object (datetime or similar).
        """

        if rr_boundaries is None: rr_boundaries = []
        # Check argument
        checks.check_argument(locations,
                              'locations',
                              gpd.GeoDataFrame,
                              columns=['geometry'])
        if lateral_discharges.any:
            checks.check_argument(lateral_discharges, 'lateral_discharges',
                                  pd.DataFrame)

        # Check if network has been loaded
        network1d = self.external_forcings.dflowfmmodel.network.mesh1d
        if not network1d.meshgeomdim.numnode:
            raise ValueError(
                '1d network has not been generated or loaded. Do this before adding laterals.'
            )

        # Find nearest 1d node per location
        nodes1d = network1d.get_nodes()
        get_nearest = KDTree(nodes1d)
        lateral_crds = np.vstack(
            [loc.geometry.coords[0] for loc in locations.itertuples()])

        _, nearest_idx = get_nearest.query(lateral_crds[:, 0:2])

        # Get time series and add to dictionary
        for crd, lateral in zip(nodes1d[nearest_idx], locations.itertuples()):

            # Check if a time is provided for the lateral
            if lateral.code in rr_boundaries:
                # Add to dictionary
                self.external_forcings.laterals[lateral.code] = {
                    'x': crd[0],
                    'y': crd[1]
                }
            else:
                if lateral.code not in lateral_discharges.columns:
                    logger.warning(
                        f'No data found for {lateral.code}. Skipping.')
                    continue

                # Get timeseries
                series = lateral_discharges.loc[:, lateral.code]

                # Add to dictionary
                self.external_forcings.laterals[lateral.code] = {
                    'x': crd[0],
                    'y': crd[1],
                    'timeseries': series
                }
Exemplo n.º 4
0
    def read_laterals(self, locations, lateral_discharges=None, rr_boundaries=None):
        """
        Process laterals

        Parameters
        ----------
        locations: gpd.GeoDataFrame
            GeoDataFrame with at least 'geometry' (Point) and the column 'code'
        lateral_discharges: pd.DataFrame
            DataFrame with lateral discharges. The index should be a time object (datetime or similar).
        rr_boundaries: pd.DataFrame
            DataFrame with RR-catchments that are coupled 
        """

        if rr_boundaries is None: rr_boundaries = []
        # Check argument
        checks.check_argument(locations, 'locations', gpd.GeoDataFrame, columns=['geometry'])
        if lateral_discharges is not None:
            checks.check_argument(lateral_discharges, 'lateral_discharges', pd.DataFrame)

        # Check if network has been loaded
        network1d = self.external_forcings.dflowfmmodel.network.mesh1d
        if not network1d.meshgeomdim.numnode:
            raise ValueError('1d network has not been generated or loaded. Do this before adding laterals.')

        # in case of 3d points, remove the 3rd dimension
        locations['geometry2'] = [Point([point.geometry.x, point.geometry.y]) for _,point in locations.iterrows()]    
        locations.drop('geometry', inplace=True, axis=1)
        locations.rename(columns={'geometry2':'geometry'}, inplace=True)
        
        # Find nearest 1d node per location and find the nodeid
        #lateral_crds = np.vstack([loc.geometry.coords[0] for loc in locations.itertuples()])             
        #nodes1d = network1d.get_nodes()
        #get_nearest = KDTree(nodes1d)
        #_, nearest_idx = get_nearest.query(lateral_crds[:,0:2])
                
        # Get time series and add to dictionary
        #for nidx, lateral in zip(nearest_idx, locations.itertuples()):
        for lateral in locations.itertuples():
            # crd = nodes1d[nearest_idx]
            #nid = f'{nodes1d[nidx][0]:g}_{nodes1d[nidx][1]:g}'
            
            # Check if a time is provided for the lateral
            if lateral.code in rr_boundaries:                
                # Add to dictionary
                self.external_forcings.laterals[lateral.code] = {
                    'branchid': lateral.branch_id,
                    'branch_offset': str(lateral.branch_offset)                    
                }
            else:
                if lateral_discharges is None:
                    logger.warning(f'No lateral_discharges provied. {lateral.code} expects them. Skipping.')
                    continue
                else:
                    if lateral.code not in lateral_discharges.columns:
                        logger.warning(f'No data found for {lateral.code}. Skipping.')
                        continue
                    
                # Get timeseries
                series = lateral_discharges.loc[:, lateral.code]
                
                # Add to dictionary
                self.external_forcings.laterals[lateral.code] = { 
                    'branchid': lateral.branch_id,
                    'branch_offset': str(lateral.branch_offset), 
                    'timeseries': series            
                }                                   
Exemplo n.º 5
0
    def refine(self, polygon, level, cellsize, keep_grid=False, dflowfm_path=None):
        """
        Parameters
        ----------
        polygon : list of tuples
            Polygon in which to refine
        level : int
            Number of times to refine
        """

        if self.rotated:
            raise NotImplementedError('Mesh refinement does not work for rotation grids.')
        
        checks.check_argument(polygon, 'polygon', (list, Polygon, MultiPolygon))

        # Save network as grid
        temppath = 'tempgrid_net.nc'
        gridio.to_netcdf_old(self.meshgeom, temppath)

        if isinstance(level, int):
            level = [level]
            polygon = [polygon]

        factor = 2 ** max(level)
        stepsize = cellsize / factor
        dxmin = stepsize
        dtmax = stepsize / (9.81) ** 0.5


        xnodes = self.meshgeom.get_values('nodex')
        ynodes = self.meshgeom.get_values('nodey')
        x0 = min(xnodes)
        ncols = int((max(xnodes) - x0) / cellsize)
        y0 = min(ynodes)
        nrows = int((max(ynodes) - y0) / cellsize)

        arr = np.ones((nrows * factor, ncols * factor)) * 4**max(level)


        for poly, lvl in zip(polygon, level):
            mask = geometry.geometry_to_mask(poly, (x0, y0), stepsize, shape=(nrows * factor, ncols * factor))
            arr[mask] = 4 ** (max(level) - lvl)

        # Create ascii grid based on polygon
        header = (
            'nCols        {0:d}\n'
            'nRows        {1:d}\n'
            'xllCorner    {2:.6f}\n'
            'yllCorner    {3:.6f}\n'
            'CellSize     {4:.6f}\n'
            'nodata_value 0\n'
        ).format(ncols * factor, nrows * factor, x0, y0, stepsize)

        values = '\n'.join(' '.join('%d' %x for x in y) for y in arr.squeeze())

        with open('temp_ascgrid.asc', 'w') as f:
            f.write(header + values)

        # Get dflow fm on system path
        if dflowfm_path is not None:
            if not os.path.exists(os.path.join(dflowfm_path, 'dflowfm.exe')):
                raise OSError('dflowfm.exe not found on given path')
            dflowfm_path = os.path.join(dflowfm_path, 'dflowfm.exe')
        else:
            dflowfm_path = 'dflowfm.exe'

        # Construct statement
        statement = f'{dflowfm_path} --refine:hmin={dxmin}:dtmax={dtmax}:connect=1:outsidecell=1 {temppath} temp_ascgrid.asc > log.txt'
        # Execute statement
        os.system(statement)

        if not keep_grid:
            os.remove('temp_ascgrid.asc')

        # Read geometry and dimensions again
        gridio.from_netcdf_old(self.meshgeom, 'out_net.nc')
        
        # Find cells
        self._find_cells(self.meshgeom)
    
        os.remove('out_net.nc')
        os.remove(temppath)
Exemplo n.º 6
0
def parametrised_to_profiles(parametrised, branches):
    """
    Generate parametrised cross sections for all branches,
    or the branches missing a cross section.

    Parameters
    ----------
    parametrised : pd.DataFrame
        GeoDataFrame with geometries and attributes of parametrised profiles.
    branches : list
        List of branches for which the parametrised profiles are derived

    Returns
    -------
    dictionary
        Dictionary with attributes of cross sections, usable for dflowfm
    """

    checks.check_argument(parametrised, 'parametrised',
                          (pd.DataFrame, gpd.GeoDataFrame))
    checks.check_argument(branches, 'branches', (list, tuple))

    # Find
    if len(branches) != 0:
        parambranches = ExtendedGeoDataFrame(
            geotype=LineString,
            columns=parametrised.columns.tolist() + ['css_type'])
        parambranches.set_data(parametrised[np.isin(parametrised.code,
                                                    branches)],
                               index_col='code',
                               check_columns=True)
    else:
        parambranches = parametrised
    #
    #else:
    #parambranches = parametrised.reindex(columns=parametrised.requiredcolumns + ['css_type'])
    #    parambranches = parametrised[np.isin(parametrised.code,branches)]# ['css_type'])

    # Drop profiles for which not enough data is available to write (as rectangle)
    #nulls = pd.isnull(parambranches[['bodembreedte', 'bodemhoogtebenedenstrooms', 'bodemhoogtebovenstrooms']]).any(axis=1).values
    #parambranches = parambranches.drop(ExtendedGeoDataFrame(geotype=LineString), parambranches.index[nulls], index_col='code',axis=0)
    #parambranches.drop(parambranches.index[nulls], inplace=True)

    # Determine characteristics
    botlev = (parambranches['bodemhoogtebenedenstrooms'] +
              parambranches['bodemhoogtebovenstrooms']) / 2.0
    dh1 = parambranches['hoogteinsteeklinkerzijde'] - botlev
    dh2 = parambranches['hoogteinsteekrechterzijde'] - botlev
    parambranches['height'] = (dh1 + dh2) / 2.0
    parambranches['bottomlevel'] = botlev

    # Determine maximum flow width and slope (both needed for output)
    parambranches[
        'maxflowwidth'] = parambranches['bodembreedte'] + parambranches[
            'taludhellinglinkerzijde'] * dh1 + parambranches[
                'taludhellingrechterzijde'] * dh2
    parambranches['slope'] = (parambranches['taludhellinglinkerzijde'] +
                              parambranches['taludhellingrechterzijde']) / 2.0

    # Determine profile type
    parambranches.loc[:, 'css_type'] = 'trapezium'
    nulls = pd.isnull(
        parambranches[parametrised.required_columns]).any(axis=1).values
    parambranches.loc[nulls, 'css_type'] = 'rectangle'

    cssdct = {}
    for branch in parambranches.itertuples():
        # Determine name for cross section
        if branch.css_type == 'trapezium':
            cssdct[branch.Index] = {
                'type':
                branch.css_type,
                'slope':
                round(branch.slope, 1),
                'maximumflowwidth':
                round(branch.maxflowwidth, 1),
                'bottomwidth':
                round(branch.bodembreedte, 3),
                'closed':
                0,
                'thalweg':
                0.0,
                'ruwheidstypecode':
                int(branch.ruwheidstypecode) if isinstance(
                    branch.ruwheidstypecode, float) else
                branch.ruwheidstypecode,
                'ruwheidswaarde':
                branch.ruwheidswaarde,
                'bottomlevel':
                branch.bottomlevel
            }
        elif branch.css_type == 'rectangle':
            cssdct[branch.Index] = {
                'type':
                branch.css_type,
                'height':
                5.0,
                'width':
                round(branch.bodembreedte, 3),
                'closed':
                0,
                'thalweg':
                0.0,
                'ruwheidstypecode':
                int(branch.ruwheidstypecode) if isinstance(
                    branch.ruwheidstypecode, float) else
                branch.ruwheidstypecode,
                'ruwheidswaarde':
                branch.ruwheidswaarde,
                'bottomlevel':
                branch.bottomlevel
            }

    return cssdct
Exemplo n.º 7
0
    def clip_mesh_by_polygon(self, polygon, outside=True):
        """
        Removes part of the existing mesh within the given polygon.

        Parameters
        ----------
        polygon : list, Polygon, MultiPolygon
            A polygon, multi-polygon or list of multiple polygons or multipolygons.
            The faces within the polygons are removed.
        """
        logger.info('Clipping 2D mesh by polygon.')

        # Check if the input arguments is indeed some kind of polygon
        checks.check_argument(polygon, 'polygon',
                              (list, Polygon, MultiPolygon))

        # Get xnodes, ynodes and edge_nodes
        xnodes = self.meshgeom.get_values('nodex', as_array=True)
        ynodes = self.meshgeom.get_values('nodey', as_array=True)
        edge_nodes = self.meshgeom.get_values('edge_nodes', as_array=True)

        # Clip
        logger.info('Clipping nodes.')
        xnodes, ynodes, edge_nodes = self.clip_nodes(xnodes,
                                                     ynodes,
                                                     edge_nodes,
                                                     polygon,
                                                     keep_outside=outside)
        # edge_nodes = np.sort(edge_nodes, axis=1)

        # Update dimensions
        self.meshgeomdim.numnode = len(xnodes)
        self.meshgeomdim.numedge = len(edge_nodes)

        # Add nodes and links
        self.meshgeom.set_values('nodex', xnodes)
        self.meshgeom.set_values('nodey', ynodes)
        self.meshgeom.set_values('edge_nodes', np.ravel(edge_nodes).tolist())

        # Determine what the cells are
        logger.info('Finding cell faces within clipped nodes.')
        self._find_cells(self.meshgeom)

        # Clean nodes, this function deletes nodes based on no longer existing cells
        face_nodes = self.meshgeom.get_values('face_nodes', as_array=True)
        logger.info('Removing incomplete faces.')
        cleaned = self.clean_nodes(xnodes, ynodes, edge_nodes, face_nodes)

        # If cleaning leaves no whole cells, return None
        if cleaned is None:
            raise ValueError('Clipping the grid does not leave any nodes.')
        # Else, get the arguments from the returned tuple
        else:
            xnodes, ynodes, edge_nodes, face_nodes = cleaned

        logger.info('Update mesh with clipped nodes, edges and faces.')
        # Update dimensions
        self.meshgeomdim.numnode = len(xnodes)
        self.meshgeomdim.numedge = len(edge_nodes)

        # Add nodes and links
        self.meshgeom.set_values('nodex', xnodes)
        self.meshgeom.set_values('nodey', ynodes)
        self.meshgeom.set_values('edge_nodes', np.ravel(edge_nodes).tolist())
        self.meshgeom.set_values('face_nodes', np.ravel(face_nodes).tolist())