gisgears 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gisgears-0.1.0/LICENSE +21 -0
- gisgears-0.1.0/MANIFEST.in +3 -0
- gisgears-0.1.0/PKG-INFO +66 -0
- gisgears-0.1.0/README.md +47 -0
- gisgears-0.1.0/gisgears/__init__.py +14 -0
- gisgears-0.1.0/gisgears/cli.py +44 -0
- gisgears-0.1.0/gisgears/describe.py +53 -0
- gisgears-0.1.0/gisgears/wsdgen.py +259 -0
- gisgears-0.1.0/gisgears.egg-info/PKG-INFO +66 -0
- gisgears-0.1.0/gisgears.egg-info/SOURCES.txt +15 -0
- gisgears-0.1.0/gisgears.egg-info/dependency_links.txt +1 -0
- gisgears-0.1.0/gisgears.egg-info/entry_points.txt +4 -0
- gisgears-0.1.0/gisgears.egg-info/requires.txt +4 -0
- gisgears-0.1.0/gisgears.egg-info/top_level.txt +1 -0
- gisgears-0.1.0/pyproject.toml +36 -0
- gisgears-0.1.0/requirements.txt +4 -0
- gisgears-0.1.0/setup.cfg +4 -0
gisgears-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aaronchh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gisgears-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gisgears
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A collection of tools for GIS/hydrology raster and vector analysis
|
|
5
|
+
Author-email: aaronchh <aaronhsu219@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AaronOET/gisgears
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: rasterio
|
|
16
|
+
Requires-Dist: pyshp
|
|
17
|
+
Requires-Dist: whitebox
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# GISGEARS
|
|
21
|
+
|
|
22
|
+
A collection of Python tools for GIS/hydrology raster and vector analysis.
|
|
23
|
+
|
|
24
|
+
> **WhiteboxTools**: `wsdgen` depends on the WhiteboxTools executable, which the `whitebox` Python package downloads automatically the first time it runs.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install gisgears
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **wsdgen**: Delineate a watershed from a DEM and an outlet layer (point or line)
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### Command-line
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# List all available commands
|
|
42
|
+
gisgears-info
|
|
43
|
+
|
|
44
|
+
# Delineate a watershed from a point outlet
|
|
45
|
+
wsdgen --dem dem.tif --outlet outlet.shp
|
|
46
|
+
|
|
47
|
+
# Delineate a watershed from a line outlet (e.g. a cross-section), keeping intermediates
|
|
48
|
+
wsdgen --dem dem.tif --outlet crosssection.shp --all-outputs
|
|
49
|
+
|
|
50
|
+
# Use a coarser/finer boundary-smoothing filter
|
|
51
|
+
wsdgen --dem dem.tif --outlet outlet.shp --smooth-filter 15
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Python API
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from gisgears import wsdgen
|
|
58
|
+
|
|
59
|
+
# Delineate a watershed (written to WSD/watershed.tif, watershed.shp, watershed_smooth.shp)
|
|
60
|
+
wsdgen.delineate_watershed(dem="dem.tif", outlet="outlet.shp")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Notes
|
|
64
|
+
|
|
65
|
+
- The outlet layer may be a point shapefile (a single manually-marked outlet) or a line shapefile (e.g. a cross-section at the watershed outlet); `wsdgen` detects the geometry type automatically.
|
|
66
|
+
- By default only the final `watershed.tif`, `watershed.shp` and `watershed_smooth.shp` are written to the `WSD/` output directory; pass `--all-outputs` to also keep intermediate files (breached DEM, D8 pointer, flow accumulation, snapped outlet, etc.) there instead of a temporary folder.
|
gisgears-0.1.0/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# GISGEARS
|
|
2
|
+
|
|
3
|
+
A collection of Python tools for GIS/hydrology raster and vector analysis.
|
|
4
|
+
|
|
5
|
+
> **WhiteboxTools**: `wsdgen` depends on the WhiteboxTools executable, which the `whitebox` Python package downloads automatically the first time it runs.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install gisgears
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **wsdgen**: Delineate a watershed from a DEM and an outlet layer (point or line)
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### Command-line
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# List all available commands
|
|
23
|
+
gisgears-info
|
|
24
|
+
|
|
25
|
+
# Delineate a watershed from a point outlet
|
|
26
|
+
wsdgen --dem dem.tif --outlet outlet.shp
|
|
27
|
+
|
|
28
|
+
# Delineate a watershed from a line outlet (e.g. a cross-section), keeping intermediates
|
|
29
|
+
wsdgen --dem dem.tif --outlet crosssection.shp --all-outputs
|
|
30
|
+
|
|
31
|
+
# Use a coarser/finer boundary-smoothing filter
|
|
32
|
+
wsdgen --dem dem.tif --outlet outlet.shp --smooth-filter 15
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Python API
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from gisgears import wsdgen
|
|
39
|
+
|
|
40
|
+
# Delineate a watershed (written to WSD/watershed.tif, watershed.shp, watershed_smooth.shp)
|
|
41
|
+
wsdgen.delineate_watershed(dem="dem.tif", outlet="outlet.shp")
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Notes
|
|
45
|
+
|
|
46
|
+
- The outlet layer may be a point shapefile (a single manually-marked outlet) or a line shapefile (e.g. a cross-section at the watershed outlet); `wsdgen` detects the geometry type automatically.
|
|
47
|
+
- By default only the final `watershed.tif`, `watershed.shp` and `watershed_smooth.shp` are written to the `WSD/` output directory; pass `--all-outputs` to also keep intermediate files (breached DEM, D8 pointer, flow accumulation, snapped outlet, etc.) there instead of a temporary folder.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Top-level `gisgears` command. Running `gisgears -h` (or with no arguments)
|
|
4
|
+
prints the same information shown by `gisgears-info`. Running `gisgears -v`
|
|
5
|
+
prints the installed gisgears version.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from . import __version__, describe
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _print_info(tool=None):
|
|
14
|
+
argv_backup = sys.argv
|
|
15
|
+
try:
|
|
16
|
+
sys.argv = ['gisgears-info'] + ([tool] if tool else [])
|
|
17
|
+
describe.main()
|
|
18
|
+
finally:
|
|
19
|
+
sys.argv = argv_backup
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
args = sys.argv[1:]
|
|
24
|
+
|
|
25
|
+
if not args or args[0] in ('-h', '--help'):
|
|
26
|
+
_print_info()
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
if args[0] in ('-v', '--version'):
|
|
30
|
+
print(f"gisgears {__version__}")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
tool = args[0]
|
|
34
|
+
if tool in describe.TOOL_DESCRIPTIONS:
|
|
35
|
+
_print_info(tool)
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
print(f"Unknown option or tool: {tool}", file=sys.stderr)
|
|
39
|
+
print("Run 'gisgears -h' to see available tools.", file=sys.stderr)
|
|
40
|
+
sys.exit(2)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
main()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Command-line utility to display descriptions of gisgears functionality.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import sys
|
|
8
|
+
from textwrap import dedent
|
|
9
|
+
|
|
10
|
+
TOOL_DESCRIPTIONS = {
|
|
11
|
+
'wsdgen': """
|
|
12
|
+
Delineate a watershed from a DEM and an outlet layer (point or line).
|
|
13
|
+
|
|
14
|
+
Breaches depressions, computes D8 flow direction/accumulation, snaps
|
|
15
|
+
the outlet onto the stream, delineates the watershed, and converts
|
|
16
|
+
the result to polygon shapefiles (raw and boundary-smoothed).
|
|
17
|
+
Requires the WhiteboxTools executable, downloaded automatically by
|
|
18
|
+
the `whitebox` Python package on first use.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
wsdgen --dem dem.tif --outlet outlet.shp # Point outlet
|
|
22
|
+
wsdgen --dem dem.tif --outlet crosssection.shp --all-outputs # Keep intermediates
|
|
23
|
+
""",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main():
|
|
28
|
+
parser = argparse.ArgumentParser(
|
|
29
|
+
prog='gisgears-info',
|
|
30
|
+
description='Display descriptions of gisgears commands',
|
|
31
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
'tool',
|
|
35
|
+
nargs='?',
|
|
36
|
+
choices=list(TOOL_DESCRIPTIONS.keys()),
|
|
37
|
+
help='Tool name to describe (omit to list all tools)',
|
|
38
|
+
)
|
|
39
|
+
args = parser.parse_args()
|
|
40
|
+
|
|
41
|
+
if args.tool:
|
|
42
|
+
print(f"\n--- {args.tool} ---")
|
|
43
|
+
print(dedent(TOOL_DESCRIPTIONS[args.tool]))
|
|
44
|
+
else:
|
|
45
|
+
print("\nGISGEARS — GIS/hydrology analysis commands\n")
|
|
46
|
+
for tool, desc in TOOL_DESCRIPTIONS.items():
|
|
47
|
+
first_line = dedent(desc).strip().splitlines()[0]
|
|
48
|
+
print(f" {tool:<14} {first_line}")
|
|
49
|
+
print("\nRun 'gisgears-info <tool>' for details on a specific command.")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
main()
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
wsdgen - Delineate a watershed from a DEM and an outlet layer (point or line).
|
|
4
|
+
|
|
5
|
+
Given a DEM raster and an outlet (pour point) shapefile - which may be a
|
|
6
|
+
point layer (a single manually-marked outlet) or a line layer (e.g. a
|
|
7
|
+
cross-section at the watershed outlet) - this tool breaches depressions,
|
|
8
|
+
computes D8 flow direction/accumulation, snaps the outlet onto the stream,
|
|
9
|
+
delineates the watershed, and converts the result to polygon shapefiles
|
|
10
|
+
(raw and boundary-smoothed).
|
|
11
|
+
|
|
12
|
+
Requires the WhiteboxTools executable, which the `whitebox` Python package
|
|
13
|
+
downloads automatically on first use.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import sys
|
|
18
|
+
import tempfile
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import rasterio
|
|
23
|
+
import shapefile
|
|
24
|
+
from whitebox import WhiteboxTools
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _snap_raster_pour_points(outlet_raster_path, flow_accum_path, output_path, snap_dist):
|
|
28
|
+
"""
|
|
29
|
+
Relocate each labeled outlet cell to the highest-flow-accumulation cell
|
|
30
|
+
within snap_dist, the same way WhiteboxTools' snap_pour_points does for
|
|
31
|
+
point outlets.
|
|
32
|
+
"""
|
|
33
|
+
with rasterio.open(outlet_raster_path) as src:
|
|
34
|
+
outlet = src.read(1)
|
|
35
|
+
profile = src.profile
|
|
36
|
+
outlet_nodata = src.nodata
|
|
37
|
+
cellsize = abs(src.transform.a)
|
|
38
|
+
|
|
39
|
+
with rasterio.open(flow_accum_path) as src:
|
|
40
|
+
flow_accum = src.read(1)
|
|
41
|
+
fa_nodata = src.nodata
|
|
42
|
+
|
|
43
|
+
radius_cells = max(1, round(snap_dist / cellsize))
|
|
44
|
+
rows, cols = outlet.shape
|
|
45
|
+
snapped = np.full_like(outlet, outlet_nodata)
|
|
46
|
+
|
|
47
|
+
ids = np.unique(outlet)
|
|
48
|
+
ids = ids[ids != outlet_nodata]
|
|
49
|
+
|
|
50
|
+
for outlet_id in ids:
|
|
51
|
+
ys, xs = np.where(outlet == outlet_id)
|
|
52
|
+
# Start the search from whichever of this outlet's cells is already
|
|
53
|
+
# closest to the channel, then look for the true peak nearby
|
|
54
|
+
seed_r, seed_c = ys[np.argmax(flow_accum[ys, xs])], xs[np.argmax(flow_accum[ys, xs])]
|
|
55
|
+
|
|
56
|
+
r0, r1 = max(0, seed_r - radius_cells), min(rows, seed_r + radius_cells + 1)
|
|
57
|
+
c0, c1 = max(0, seed_c - radius_cells), min(cols, seed_c + radius_cells + 1)
|
|
58
|
+
window = flow_accum[r0:r1, c0:c1]
|
|
59
|
+
|
|
60
|
+
valid = window != fa_nodata if fa_nodata is not None else np.ones_like(window, dtype=bool)
|
|
61
|
+
if valid.any():
|
|
62
|
+
masked = np.where(valid, window, -np.inf)
|
|
63
|
+
local_r, local_c = np.unravel_index(np.argmax(masked), masked.shape)
|
|
64
|
+
snap_r, snap_c = r0 + local_r, c0 + local_c
|
|
65
|
+
else:
|
|
66
|
+
snap_r, snap_c = seed_r, seed_c
|
|
67
|
+
|
|
68
|
+
snapped[snap_r, snap_c] = outlet_id
|
|
69
|
+
|
|
70
|
+
with rasterio.open(output_path, "w", **profile) as dst:
|
|
71
|
+
dst.write(snapped, 1)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def delineate_watershed(dem, outlet, outdir="WSD", all_outputs=False, smooth_filter=9, cwd=None) -> Path:
|
|
75
|
+
"""
|
|
76
|
+
Delineate a watershed from a DEM and an outlet layer (point or line).
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
dem (str): Path to the input DEM raster
|
|
80
|
+
outlet (str): Path to the outlet (pour point) shapefile - point or line
|
|
81
|
+
outdir (str): Output folder name (default: WSD)
|
|
82
|
+
all_outputs (bool): Keep intermediate files (breach/pointer/flow
|
|
83
|
+
accumulation/snapped outlet, etc.) in outdir instead of a temp
|
|
84
|
+
folder that is deleted after processing
|
|
85
|
+
smooth_filter (int): Boundary smoothing filter size (odd, >=3)
|
|
86
|
+
cwd (Path): Working directory to resolve relative paths against and
|
|
87
|
+
to run WhiteboxTools from (defaults to the current directory)
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Path: Output directory containing watershed.tif, watershed.shp and
|
|
91
|
+
watershed_smooth.shp
|
|
92
|
+
"""
|
|
93
|
+
cwd = Path(cwd) if cwd is not None else Path.cwd()
|
|
94
|
+
dem_path = Path(dem)
|
|
95
|
+
dem_path = dem_path if dem_path.is_absolute() else cwd / dem_path
|
|
96
|
+
outlet_path = Path(outlet)
|
|
97
|
+
outlet_path = outlet_path if outlet_path.is_absolute() else cwd / outlet_path
|
|
98
|
+
|
|
99
|
+
for p in (dem_path, outlet_path):
|
|
100
|
+
if not p.is_file():
|
|
101
|
+
sys.exit(f"Error: required input file not found: {p}")
|
|
102
|
+
|
|
103
|
+
wbt = WhiteboxTools()
|
|
104
|
+
wbt.set_working_dir(str(cwd))
|
|
105
|
+
|
|
106
|
+
# whitebox_tools.exe cannot correctly resolve relative paths to subfolders
|
|
107
|
+
# on Windows, so input and output files always use absolute paths
|
|
108
|
+
dem_str = str(dem_path.resolve())
|
|
109
|
+
outlet_str = str(outlet_path.resolve())
|
|
110
|
+
|
|
111
|
+
output_dir = cwd / outdir
|
|
112
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
113
|
+
|
|
114
|
+
# Intermediate files are written to a temp folder by default and deleted
|
|
115
|
+
# entirely after processing; pass all_outputs=True to keep them in
|
|
116
|
+
# output_dir for inspection
|
|
117
|
+
if all_outputs:
|
|
118
|
+
inter_dir = output_dir
|
|
119
|
+
tmp_dir = None
|
|
120
|
+
else:
|
|
121
|
+
tmp_dir = tempfile.TemporaryDirectory()
|
|
122
|
+
inter_dir = Path(tmp_dir.name)
|
|
123
|
+
|
|
124
|
+
dem_breached = str(inter_dir / "dem_breached.tif")
|
|
125
|
+
d8_pointer = str(inter_dir / "d8_pointer.tif")
|
|
126
|
+
flow_accum = str(inter_dir / "flow_accum.tif")
|
|
127
|
+
outlet_snapped = str(inter_dir / "outlet_snapped.shp")
|
|
128
|
+
outlet_raster = str(inter_dir / "outlet_raster.tif")
|
|
129
|
+
outlet_raster_snapped = str(inter_dir / "outlet_raster_snapped.tif")
|
|
130
|
+
watershed_out = str(output_dir / "watershed.tif")
|
|
131
|
+
watershed_vector = str(output_dir / "watershed.shp")
|
|
132
|
+
watershed_vector_smooth = str(output_dir / "watershed_smooth.shp")
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
with rasterio.open(dem_str) as src:
|
|
136
|
+
gridsize = abs(src.transform.a)
|
|
137
|
+
|
|
138
|
+
wbt.breach_depressions(dem_str, dem_breached)
|
|
139
|
+
wbt.d8_pointer(dem_breached, d8_pointer)
|
|
140
|
+
wbt.d8_flow_accumulation(
|
|
141
|
+
d8_pointer,
|
|
142
|
+
flow_accum,
|
|
143
|
+
out_type="cells",
|
|
144
|
+
pntr=True,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# The outlet layer may be a point (single outlet) or a line (e.g. a
|
|
148
|
+
# cross-section at the watershed outlet); the geometry type of the
|
|
149
|
+
# shapefile determines the workflow
|
|
150
|
+
with shapefile.Reader(outlet_str) as sf:
|
|
151
|
+
is_point_outlet = sf.shapeType in (
|
|
152
|
+
shapefile.POINT, shapefile.POINTZ, shapefile.POINTM
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if is_point_outlet:
|
|
156
|
+
# The outlet point is manually marked and not yet snapped to the
|
|
157
|
+
# stream, so snap it using the flow accumulation grid first;
|
|
158
|
+
# snap_dist is set to 3x the DEM cell resolution to capture
|
|
159
|
+
# nearby cells
|
|
160
|
+
wbt.snap_pour_points(
|
|
161
|
+
outlet_str,
|
|
162
|
+
flow_accum,
|
|
163
|
+
outlet_snapped,
|
|
164
|
+
snap_dist=3 * gridsize,
|
|
165
|
+
)
|
|
166
|
+
pour_points = outlet_snapped
|
|
167
|
+
else:
|
|
168
|
+
# A line cannot be snapped to the stream with snap_pour_points
|
|
169
|
+
# (which only accepts point layers), so rasterize the line into
|
|
170
|
+
# outlet cells first, then snap each rasterized outlet cell onto
|
|
171
|
+
# the nearest high-flow-accumulation cell within snap_dist -
|
|
172
|
+
# without this, a line that is even one pixel off the true
|
|
173
|
+
# channel yields a near-empty watershed
|
|
174
|
+
wbt.vector_lines_to_raster(
|
|
175
|
+
outlet_str,
|
|
176
|
+
outlet_raster,
|
|
177
|
+
field="FID",
|
|
178
|
+
base=flow_accum,
|
|
179
|
+
)
|
|
180
|
+
_snap_raster_pour_points(
|
|
181
|
+
outlet_raster,
|
|
182
|
+
flow_accum,
|
|
183
|
+
outlet_raster_snapped,
|
|
184
|
+
snap_dist=3 * gridsize,
|
|
185
|
+
)
|
|
186
|
+
pour_points = outlet_raster_snapped
|
|
187
|
+
|
|
188
|
+
wbt.watershed(
|
|
189
|
+
d8_pointer,
|
|
190
|
+
pour_points,
|
|
191
|
+
watershed_out,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Convert the watershed raster output to a polygon shapefile for
|
|
195
|
+
# easier viewing in GIS and further analysis
|
|
196
|
+
wbt.raster_to_vector_polygons(
|
|
197
|
+
watershed_out,
|
|
198
|
+
watershed_vector,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Raster-to-polygon boundaries are jagged (following cell edges), so
|
|
202
|
+
# also output a smoothed version for downstream use/presentation
|
|
203
|
+
wbt.smooth_vectors(
|
|
204
|
+
watershed_vector,
|
|
205
|
+
watershed_vector_smooth,
|
|
206
|
+
filter=smooth_filter,
|
|
207
|
+
)
|
|
208
|
+
finally:
|
|
209
|
+
if tmp_dir is not None:
|
|
210
|
+
tmp_dir.cleanup()
|
|
211
|
+
|
|
212
|
+
print(f"Wrote watershed.tif, watershed.shp, watershed_smooth.shp to {output_dir}")
|
|
213
|
+
return output_dir
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def main():
|
|
217
|
+
parser = argparse.ArgumentParser(
|
|
218
|
+
prog="wsdgen",
|
|
219
|
+
description="Extract a watershed from a DEM and an outlet layer (point or line)",
|
|
220
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
221
|
+
epilog="""
|
|
222
|
+
Examples:
|
|
223
|
+
wsdgen --dem dem.tif --outlet outlet.shp # Point outlet
|
|
224
|
+
wsdgen --dem dem.tif --outlet crosssection.shp --all-outputs # Keep intermediates
|
|
225
|
+
""",
|
|
226
|
+
)
|
|
227
|
+
parser.add_argument("--dem", required=True, help="Input DEM file path")
|
|
228
|
+
parser.add_argument(
|
|
229
|
+
"--outlet",
|
|
230
|
+
required=True,
|
|
231
|
+
help="Outlet (pour point) shapefile path, can be a point or line layer",
|
|
232
|
+
)
|
|
233
|
+
parser.add_argument("--outdir", default="WSD", help="Output folder name (default: WSD)")
|
|
234
|
+
parser.add_argument(
|
|
235
|
+
"--all-outputs",
|
|
236
|
+
action="store_true",
|
|
237
|
+
help="Keep all intermediate files (breach/pointer/flow accumulation/snapped outlet, etc.); "
|
|
238
|
+
"by default only the final watershed.tif and watershed.shp are output",
|
|
239
|
+
)
|
|
240
|
+
parser.add_argument(
|
|
241
|
+
"--smooth-filter",
|
|
242
|
+
type=int,
|
|
243
|
+
default=9,
|
|
244
|
+
help="Boundary smoothing filter size (odd number, >=3, default 9); "
|
|
245
|
+
"larger values produce smoother boundaries; output is watershed_smooth.shp",
|
|
246
|
+
)
|
|
247
|
+
args = parser.parse_args()
|
|
248
|
+
|
|
249
|
+
delineate_watershed(
|
|
250
|
+
dem=args.dem,
|
|
251
|
+
outlet=args.outlet,
|
|
252
|
+
outdir=args.outdir,
|
|
253
|
+
all_outputs=args.all_outputs,
|
|
254
|
+
smooth_filter=args.smooth_filter,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
if __name__ == "__main__":
|
|
259
|
+
main()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gisgears
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A collection of tools for GIS/hydrology raster and vector analysis
|
|
5
|
+
Author-email: aaronchh <aaronhsu219@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AaronOET/gisgears
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: rasterio
|
|
16
|
+
Requires-Dist: pyshp
|
|
17
|
+
Requires-Dist: whitebox
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# GISGEARS
|
|
21
|
+
|
|
22
|
+
A collection of Python tools for GIS/hydrology raster and vector analysis.
|
|
23
|
+
|
|
24
|
+
> **WhiteboxTools**: `wsdgen` depends on the WhiteboxTools executable, which the `whitebox` Python package downloads automatically the first time it runs.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install gisgears
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **wsdgen**: Delineate a watershed from a DEM and an outlet layer (point or line)
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
### Command-line
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# List all available commands
|
|
42
|
+
gisgears-info
|
|
43
|
+
|
|
44
|
+
# Delineate a watershed from a point outlet
|
|
45
|
+
wsdgen --dem dem.tif --outlet outlet.shp
|
|
46
|
+
|
|
47
|
+
# Delineate a watershed from a line outlet (e.g. a cross-section), keeping intermediates
|
|
48
|
+
wsdgen --dem dem.tif --outlet crosssection.shp --all-outputs
|
|
49
|
+
|
|
50
|
+
# Use a coarser/finer boundary-smoothing filter
|
|
51
|
+
wsdgen --dem dem.tif --outlet outlet.shp --smooth-filter 15
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Python API
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from gisgears import wsdgen
|
|
58
|
+
|
|
59
|
+
# Delineate a watershed (written to WSD/watershed.tif, watershed.shp, watershed_smooth.shp)
|
|
60
|
+
wsdgen.delineate_watershed(dem="dem.tif", outlet="outlet.shp")
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Notes
|
|
64
|
+
|
|
65
|
+
- The outlet layer may be a point shapefile (a single manually-marked outlet) or a line shapefile (e.g. a cross-section at the watershed outlet); `wsdgen` detects the geometry type automatically.
|
|
66
|
+
- By default only the final `watershed.tif`, `watershed.shp` and `watershed_smooth.shp` are written to the `WSD/` output directory; pass `--all-outputs` to also keep intermediate files (breached DEM, D8 pointer, flow accumulation, snapped outlet, etc.) there instead of a temporary folder.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
requirements.txt
|
|
6
|
+
gisgears/__init__.py
|
|
7
|
+
gisgears/cli.py
|
|
8
|
+
gisgears/describe.py
|
|
9
|
+
gisgears/wsdgen.py
|
|
10
|
+
gisgears.egg-info/PKG-INFO
|
|
11
|
+
gisgears.egg-info/SOURCES.txt
|
|
12
|
+
gisgears.egg-info/dependency_links.txt
|
|
13
|
+
gisgears.egg-info/entry_points.txt
|
|
14
|
+
gisgears.egg-info/requires.txt
|
|
15
|
+
gisgears.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gisgears
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gisgears"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "aaronchh", email = "aaronhsu219@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "A collection of tools for GIS/hydrology raster and vector analysis"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = { text = "MIT" }
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy",
|
|
17
|
+
"rasterio",
|
|
18
|
+
"pyshp",
|
|
19
|
+
"whitebox",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/AaronOET/gisgears"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools]
|
|
31
|
+
packages = ["gisgears"]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
gisgears = "gisgears.cli:main"
|
|
35
|
+
gisgears-info = "gisgears.describe:main"
|
|
36
|
+
wsdgen = "gisgears.wsdgen:main"
|
gisgears-0.1.0/setup.cfg
ADDED