本文参照官网例子实现流域的提取,官方GitHub地址如下pyflwdir:
该工具包目前仅支持D8和LDD两种算法,在效率上具有较好的应用性,我用省级的DEM(30米)数据作为测试,输出效率可以满足一般作业需要。
环境environment.yml如下:
- name: pyflwdir
-
- channels:
- - conda-forge
-
- # note that these are the developer dependencies,
- # if you only want the user dependencies, see
- # install_requires in setup.py
- dependencies:
- # required
- - python>=3.9
- - pyflwdir
- # optional for notebooks
- - cartopy>=0.20
- - descartes
- - geopandas>=0.8
- - jupyter
- - matplotlib
- - rasterio
用到的utils.py的代码如下:
- import matplotlib
- import matplotlib.pyplot as plt
- from matplotlib import cm, colors
- import cartopy.crs as ccrs
- import descartes
- import numpy as np
- import os
- import rasterio
- from rasterio import features
- import geopandas as gpd
-
- np.random.seed(seed=101)
- matplotlib.rcParams["savefig.bbox"] = "tight"
- matplotlib.rcParams["savefig.dpi"] = 256
- plt.style.use("seaborn-v0_8-whitegrid")
-
- # read example elevation data and derive background hillslope
- fn = os.path.join(os.path.dirname(__file__), r"D:\CLIPGY.tif")
- with rasterio.open(fn, "r") as src:
- elevtn = src.read(1)
- extent = np.array(src.bounds)[[0, 2, 1, 3]]
- crs = src.crs
- ls = matplotlib.colors.LightSource(azdeg=115, altdeg=45)
- hs = ls.hillshade(np.ma.masked_equal(elevtn, -9999), vert_exag=1e3)
-
-
- # convenience method for plotting
- def quickplot(
- gdfs=[], raster=None, hillshade=True, extent=extent, hs=hs, title="", filename=""
- ):
- fig = plt.figure(figsize=(8, 15))
- ax = fig.add_subplot(projection=ccrs.PlateCarree())
- # plot hillshade background
- if hillshade:
- ax.imshow(
- hs,
- origin="upper",
- extent=extent,
- cmap="Greys",
- alpha=0.3,
- zorder=0,
- )
- # plot geopandas GeoDataFrame
- for gdf, kwargs in gdfs:
- gdf.plot(ax=ax, **kwargs)
- if raster is not None:
- data, nodata, kwargs = raster
- ax.imshow(
- np.ma.masked_equal(data, nodata),
- origin="upper",
- extent=extent,
- **kwargs,
- )
- ax.set_aspect("equal")
- ax.set_title(title, fontsize="large")
- ax.text(
- 0.01, 0.01, "created with pyflwdir", transform=ax.transAxes, fontsize="large"
- )
- if filename:
- plt.savefig(f"{filename}.png")
- return ax
-
-
- # convenience method for vectorizing a raster
- def vectorize(data, nodata, transform, crs=crs, name="value"):
- feats_gen = features.shapes(
- data,
- mask=data != nodata,
- transform=transform,
- connectivity=8,
- )
- feats = [
- {"geometry": geom, "properties": {name: val}} for geom, val in list(feats_gen)
- ]
-
- # parse to geopandas for plotting / writing to file
- gdf = gpd.GeoDataFrame.from_features(feats, crs=crs)
- gdf[name] = gdf[name].astype(data.dtype)
- return gdf
执行代码如下:
- import rasterio
- import numpy as np
- import pyflwdir
-
- from utils import (
- quickplot,
- colors,
- cm,
- plt,
- ) # data specific quick plot convenience method
-
- # read elevation data of the rhine basin using rasterio
- with rasterio.open(r"D:\Raster.tif", "r") as src:
- elevtn = src.read(1)
- nodata = src.nodata
- transform = src.transform
- crs = src.crs
- extent = np.array(src.bounds)[[0, 2, 1, 3]]
- latlon = src.crs.is_geographic
- prof = src.profile
-
- ax = quickplot(title="Elevation")
- im = ax.imshow(
- np.ma.masked_equal(elevtn, -9999),
- extent=extent,
- cmap="gist_earth_r",
- alpha=0.5,
- vmin=0,
- vmax=1000,
- )
- fig = plt.gcf()
- cax = fig.add_axes([0.8, 0.37, 0.02, 0.12])
- fig.colorbar(im, cax=cax, orientation="vertical", extend="max")
- cax.set_ylabel("elevation [m+EGM96]")
- # plt.savefig('elevation.png', dpi=225, bbox_axis='tight')
- #####划分子流域
- import geopandas as gpd
- import numpy as np
- import rasterio
- import pyflwdir
-
- # local convenience methods (see utils.py script in notebooks folder)
- from utils import vectorize # convenience method to vectorize rasters
- from utils import quickplot, colors, cm # data specific quick plot method
-
- # returns FlwDirRaster object
- flw = pyflwdir.from_dem(
- data=elevtn,
- nodata=src.nodata,
- transform=transform,
- latlon=latlon,
- outlets="min",
- )
- import geopandas as gpd
-
- feats = flw.streams(min_sto=4)
- gdf = gpd.GeoDataFrame.from_features(feats, crs=crs)
-
- # create nice colormap of Blues with less white
- cmap_streams = colors.ListedColormap(cm.Blues(np.linspace(0.4, 1, 7)))
- gdf_plot_kwds = dict(column="strord", cmap=cmap_streams)
- # plot streams with hillshade from elevation data (see utils.py)
- ax = quickplot(
- gdfs=[(gdf, gdf_plot_kwds)],
- title="Streams based steepest gradient algorithm",
- filename="flw_streams_steepest_gradient",
- )
- # update data type and nodata value properties which are different compared to the input elevation grid and write to geotif
- prof.update(dtype=d8_data.dtype, nodata=247)
- with rasterio.open(r"D:\flwdir.tif", "w", **prof) as src:
- src.write(d8_data, 1)
-
- ######################
- #Delineation of (sub)basins#
- ###########
- with rasterio.open(r"D:\flwdir.tif", "r") as src:
- flwdir = src.read(1)
- crs = src.crs
- flw = pyflwdir.from_array(
- flwdir,
- ftype="d8",
- transform=src.transform,
- latlon=crs.is_geographic,
- cache=True,
- )
- # define output locations
- #x, y = np.array([106.6348,26.8554]), np.array([107.0135,26.8849])
- #x, y = np.array([26.8554,106.6348]), np.array([26.8849,107.0135])
- x, y = np.array([106.4244,107.0443]), np.array([26.8554,27.0384])
- gdf_out = gpd.GeoSeries(gpd.points_from_xy(x, y, crs=4326))
- # delineate subbasins
- subbasins = flw.basins(xy=(x, y), streams=flw.stream_order() >= 5)
- # vectorize subbasins using the vectorize convenience method from utils.py
- gdf_bas = vectorize(subbasins.astype(np.int32), 0, flw.transform, name="basin")
- gdf_bas.head()
-
- # plot
- # key-word arguments passed to GeoDataFrame.plot()
- gpd_plot_kwds = dict(
- column="basin",
- cmap=cm.Set3,
- legend=True,
- categorical=True,
- legend_kwds=dict(title="Basin ID [-]"),
- alpha=0.5,
- edgecolor="black",
- linewidth=0.8,
- )
- points = (gdf_out, dict(color="red", markersize=20))
- bas = (gdf_bas, gpd_plot_kwds)
- # plot using quickplot convenience method from utils.py
- ax = quickplot([bas, points], title="Basins from point outlets", filename="flw_basins")
- # calculate subbasins with a minimum stream order 7 and its outlets
- subbas, idxs_out = flw.subbasins_streamorder(min_sto=7, mask=None)
- # transfrom map and point locations to GeoDataFrames
- gdf_subbas = vectorize(subbas.astype(np.int32), 0, flw.transform, name="basin")
- gdf_out = gpd.GeoSeries(gpd.points_from_xy(*flw.xy(idxs_out), crs=4326))
- # plot
- gpd_plot_kwds = dict(
- column="basin", cmap=cm.Set3, edgecolor="black", alpha=0.6, linewidth=0.5
- )
- bas = (gdf_subbas, gpd_plot_kwds)
- points = (gdf_out, dict(color="k", markersize=20))
- title = "Subbasins based on a minimum stream order"
- ax = quickplot([bas, points], title=title, filename="flw_subbasins")
- # get the first level nine pfafstetter basins
- pfafbas1, idxs_out = flw.subbasins_pfafstetter(depth=1)
- # vectorize raster to obtain polygons
- gdf_pfaf1 = vectorize(pfafbas1.astype(np.int32), 0, flw.transform, name="pfaf")
- gdf_out = gpd.GeoSeries(gpd.points_from_xy(*flw.xy(idxs_out), crs=4326))
- gdf_pfaf1.head()
-
- # plot
- gpd_plot_kwds = dict(
- column="pfaf",
- cmap=cm.Set3_r,
- legend=True,
- categorical=True,
- legend_kwds=dict(title="Pfafstetter \nlevel 1 index [-]", ncol=3),
- alpha=0.6,
- edgecolor="black",
- linewidth=0.4,
- )
-
- points = (gdf_out, dict(color="k", markersize=20))
- bas = (gdf_pfaf1, gpd_plot_kwds)
- title = "Subbasins based on pfafstetter coding (level=1)"
- ax = quickplot([bas, points], title=title, filename="flw_pfafbas1")
- # lets create a second pfafstetter layer with a minimum subbasin area of 5000 km2
- pfafbas2, idxs_out = flw.subbasins_pfafstetter(depth=2, upa_min=5000)
- gdf_pfaf2 = vectorize(pfafbas2.astype(np.int32), 0, flw.transform, name="pfaf2")
- gdf_out = gpd.GeoSeries(gpd.points_from_xy(*flw.xy(idxs_out), crs=4326))
- gdf_pfaf2["pfaf"] = gdf_pfaf2["pfaf2"] // 10
- gdf_pfaf2.head()
-
-
- # plot
- bas = (gdf_pfaf2, gpd_plot_kwds)
- points = (gdf_out, dict(color="k", markersize=20))
- title = "Subbasins based on pfafstetter coding (level=2)"
- ax = quickplot([bas, points], title=title, filename="flw_pfafbas2")
- # calculate subbasins with a minimum stream order 7 and its outlets
- min_area = 2000
- subbas, idxs_out = flw.subbasins_area(min_area)
- # transfrom map and point locations to GeoDataFrames
- gdf_subbas = vectorize(subbas.astype(np.int32), 0, flw.transform, name="basin")
- # randomize index for visualization
- basids = gdf_subbas["basin"].values
- gdf_subbas["color"] = np.random.choice(basids, size=basids.size, replace=False)
- # plot
- gpd_plot_kwds = dict(
- column="color", cmap=cm.Set3, edgecolor="black", alpha=0.6, linewidth=0.5
- )
- bas = (gdf_subbas, gpd_plot_kwds)
- title = f"Subbasins based on a minimum area of {min_area} km2"
- ax = quickplot([bas], title=title, filename="flw_subbasins_area")