demtools 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.
@@ -0,0 +1,3 @@
1
+ include LICENSE
2
+ include README.md
3
+ include requirements.txt
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: demtools
3
+ Version: 0.1.0
4
+ Summary: A collection of tools for working with DEM (Digital Elevation Model) raster files
5
+ Home-page: https://github.com/AaronOET/demtools
6
+ Author: aaronchh
7
+ Author-email: aaronchh <aaronhsu219@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/AaronOET/demtools
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: numpy>=1.20.0
16
+ Dynamic: author
17
+ Dynamic: home-page
18
+ Dynamic: requires-python
19
+
20
+ # DEMTOOLS
21
+
22
+ A collection of Python tools for working with DEM (Digital Elevation Model) raster files.
23
+
24
+ > **GDAL Installation**: GDAL is required for this package. For conda environments, use `conda install gdal` to install GDAL. For non-conda environments, download the appropriate wheel file from [https://github.com/cgohlke/geospatial-wheels/releases](https://github.com/cgohlke/geospatial-wheels/releases) to install GDAL.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install -e .
30
+ ```
31
+
32
+ ## Features
33
+
34
+ - **chgnodata**: Convert (replace) the nodata value of GeoTIFF files, updating pixel data accordingly
35
+ - **setnodata**: Assign a nodata value to GeoTIFF files without modifying pixel data (metadata only)
36
+ - **csv2tif**: Convert a plain-numeric CSV raster grid to a GeoTIFF file
37
+
38
+ ## Usage
39
+
40
+ ### Command-line
41
+
42
+ ```bash
43
+ # List all available commands
44
+ demtools-info
45
+
46
+ # Convert nodata values for all TIF files in the current directory
47
+ chgnodata -a
48
+
49
+ # Convert nodata of a single file to -9999
50
+ chgnodata -i dem.tif -v -9999
51
+
52
+ # Set nodata metadata for all TIF files (no pixel data change)
53
+ setnodata -a -v -9999
54
+
55
+ # Convert a CSV grid to GeoTIFF
56
+ csv2tif -i grid.csv -o dem.tif --xll 250000 --yll 2500000 --cellsize 5 --epsg 32648
57
+ ```
58
+
59
+ ### Python API
60
+
61
+ ```python
62
+ from demtools import chgnodata, setnodata, csv2tif
63
+
64
+ # Convert nodata value in a raster file
65
+ chgnodata.convert_nodata_value("dem.tif", output_nodata=-9999)
66
+
67
+ # Assign nodata metadata without touching pixel values
68
+ setnodata.define_nodata_value("dem.tif", nodata_value=-9999)
69
+
70
+ # Convert CSV grid to GeoTIFF
71
+ csv2tif.csv_to_tif(
72
+ input_csv="grid.csv",
73
+ output_tif="dem.tif",
74
+ xll=250000,
75
+ yll=2500000,
76
+ cellsize=5,
77
+ nodata=-999,
78
+ epsg=32648,
79
+ )
80
+ ```
81
+
82
+ ## Notes
83
+
84
+ - `chgnodata` and `setnodata` automatically create a `RAS_BAK/` directory with backup copies before modifying files.
85
+ - `csv2tif` expects a plain numeric CSV (no headers), with rows ordered from north to south.
86
+ - GDAL must be installed separately via conda or a pre-built wheel; it is not listed in `requirements.txt` as it cannot be reliably installed via pip on all platforms.
@@ -0,0 +1,67 @@
1
+ # DEMTOOLS
2
+
3
+ A collection of Python tools for working with DEM (Digital Elevation Model) raster files.
4
+
5
+ > **GDAL Installation**: GDAL is required for this package. For conda environments, use `conda install gdal` to install GDAL. For non-conda environments, download the appropriate wheel file from [https://github.com/cgohlke/geospatial-wheels/releases](https://github.com/cgohlke/geospatial-wheels/releases) to install GDAL.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install -e .
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - **chgnodata**: Convert (replace) the nodata value of GeoTIFF files, updating pixel data accordingly
16
+ - **setnodata**: Assign a nodata value to GeoTIFF files without modifying pixel data (metadata only)
17
+ - **csv2tif**: Convert a plain-numeric CSV raster grid to a GeoTIFF file
18
+
19
+ ## Usage
20
+
21
+ ### Command-line
22
+
23
+ ```bash
24
+ # List all available commands
25
+ demtools-info
26
+
27
+ # Convert nodata values for all TIF files in the current directory
28
+ chgnodata -a
29
+
30
+ # Convert nodata of a single file to -9999
31
+ chgnodata -i dem.tif -v -9999
32
+
33
+ # Set nodata metadata for all TIF files (no pixel data change)
34
+ setnodata -a -v -9999
35
+
36
+ # Convert a CSV grid to GeoTIFF
37
+ csv2tif -i grid.csv -o dem.tif --xll 250000 --yll 2500000 --cellsize 5 --epsg 32648
38
+ ```
39
+
40
+ ### Python API
41
+
42
+ ```python
43
+ from demtools import chgnodata, setnodata, csv2tif
44
+
45
+ # Convert nodata value in a raster file
46
+ chgnodata.convert_nodata_value("dem.tif", output_nodata=-9999)
47
+
48
+ # Assign nodata metadata without touching pixel values
49
+ setnodata.define_nodata_value("dem.tif", nodata_value=-9999)
50
+
51
+ # Convert CSV grid to GeoTIFF
52
+ csv2tif.csv_to_tif(
53
+ input_csv="grid.csv",
54
+ output_tif="dem.tif",
55
+ xll=250000,
56
+ yll=2500000,
57
+ cellsize=5,
58
+ nodata=-999,
59
+ epsg=32648,
60
+ )
61
+ ```
62
+
63
+ ## Notes
64
+
65
+ - `chgnodata` and `setnodata` automatically create a `RAS_BAK/` directory with backup copies before modifying files.
66
+ - `csv2tif` expects a plain numeric CSV (no headers), with rows ordered from north to south.
67
+ - GDAL must be installed separately via conda or a pre-built wheel; it is not listed in `requirements.txt` as it cannot be reliably installed via pip on all platforms.
@@ -0,0 +1,17 @@
1
+ """
2
+ DEMTOOLS - A collection of tools for working with DEM (Digital Elevation Model) raster files.
3
+ """
4
+
5
+ __version__ = '0.1.0'
6
+
7
+ __all__ = [
8
+ 'chgnodata',
9
+ 'csv2tif',
10
+ 'setnodata',
11
+ 'describe',
12
+ ]
13
+
14
+ from . import chgnodata
15
+ from . import csv2tif
16
+ from . import setnodata
17
+ from . import describe
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ chgnodata - Convert nodata values for TIF raster files
4
+ Uses GDAL/OGR Python bindings
5
+ """
6
+
7
+ import glob
8
+ import argparse
9
+ import os
10
+ import sys
11
+ from osgeo import gdal, gdalconst
12
+
13
+ def convert_nodata_value(input_file, output_nodata=-999):
14
+ """
15
+ Convert nodata value of a raster file to specified value
16
+
17
+ Args:
18
+ input_file (str): Path to input TIF file
19
+ output_nodata (float): New nodata value (default: -999)
20
+ """
21
+ print(f"Processing: {input_file}")
22
+
23
+ # Open the input dataset
24
+ src_ds = gdal.Open(input_file, gdalconst.GA_ReadOnly)
25
+ if src_ds is None:
26
+ print(f"Error: Could not open {input_file}")
27
+ return False
28
+
29
+ # Get raster band (assuming single band, modify if multi-band)
30
+ src_band = src_ds.GetRasterBand(1)
31
+
32
+ # Get current nodata value
33
+ current_nodata = src_band.GetNoDataValue()
34
+ print(f" Current nodata value: {current_nodata}")
35
+
36
+ if current_nodata == output_nodata:
37
+ print(f" Nodata value already set to {output_nodata}, skipping...")
38
+ src_ds = None
39
+ return True
40
+
41
+ # Read the data
42
+ data = src_band.ReadAsArray()
43
+
44
+ # Get raster properties
45
+ rows, cols = data.shape
46
+ geotransform = src_ds.GetGeoTransform()
47
+ projection = src_ds.GetProjection()
48
+ data_type = src_band.DataType
49
+
50
+ # Create backup directory
51
+ backup_dir = "RAS_BAK"
52
+ if not os.path.exists(backup_dir):
53
+ os.makedirs(backup_dir)
54
+ print(f" Created backup directory: {backup_dir}")
55
+
56
+ backup_filename = os.path.basename(input_file)
57
+ backup_file = os.path.join(backup_dir, backup_filename)
58
+
59
+ # Create backup
60
+ print(f" Creating backup: {backup_file}")
61
+ gdal.GetDriverByName('GTiff').CreateCopy(backup_file, src_ds)
62
+
63
+ # Close source dataset
64
+ src_ds = None
65
+
66
+ # Create new dataset (overwrite original)
67
+ driver = gdal.GetDriverByName('GTiff')
68
+ out_ds = driver.Create(input_file, cols, rows, 1, data_type)
69
+
70
+ # Set geotransform and projection
71
+ out_ds.SetGeoTransform(geotransform)
72
+ out_ds.SetProjection(projection)
73
+
74
+ # Get output band
75
+ out_band = out_ds.GetRasterBand(1)
76
+
77
+ # Replace current nodata values with new nodata value
78
+ if current_nodata is not None:
79
+ data[data == current_nodata] = output_nodata
80
+
81
+ # Write the data
82
+ out_band.WriteArray(data)
83
+
84
+ # Set the new nodata value
85
+ out_band.SetNoDataValue(output_nodata)
86
+
87
+ # Flush data to disk
88
+ out_band.FlushCache()
89
+ out_ds = None
90
+
91
+ print(f" Successfully converted nodata value to {output_nodata}")
92
+ return True
93
+
94
+
95
+ def main():
96
+ parser = argparse.ArgumentParser(
97
+ prog='chgnodata',
98
+ description='Convert nodata values for TIF raster files',
99
+ formatter_class=argparse.RawDescriptionHelpFormatter,
100
+ epilog="""
101
+ Examples:
102
+ chgnodata -a # Process all *.tif in current directory (nodata=-999)
103
+ chgnodata -a -v -9999 # Process all *.tif, set nodata to -9999
104
+ chgnodata -i dem.tif # Process a single file (nodata=-999)
105
+ chgnodata -i dem.tif -v -9999 # Process a single file, set nodata to -9999
106
+ """
107
+ )
108
+
109
+ # Mutually exclusive group: -a or -i
110
+ group = parser.add_mutually_exclusive_group(required=True)
111
+ group.add_argument(
112
+ '-a', '--all',
113
+ action='store_true',
114
+ help='Process all *.tif files in the current directory'
115
+ )
116
+ group.add_argument(
117
+ '-i', '--input',
118
+ metavar='FILE',
119
+ help='Process a single TIF file'
120
+ )
121
+
122
+ parser.add_argument(
123
+ '-v', '--value',
124
+ dest='nodata_value',
125
+ type=float,
126
+ default=-999,
127
+ metavar='NODATA',
128
+ help='New nodata value to set (default: -999)'
129
+ )
130
+
131
+ args = parser.parse_args()
132
+ new_nodata = args.nodata_value
133
+
134
+ print(f"chgnodata: converting nodata values to {new_nodata}")
135
+ print("=" * 60)
136
+
137
+ if args.all:
138
+ tif_files = glob.glob("*.tif")
139
+ if not tif_files:
140
+ print("No TIF files found in current directory.")
141
+ sys.exit(0)
142
+
143
+ print(f"Found {len(tif_files)} TIF file(s):")
144
+ for f in tif_files:
145
+ print(f" - {f}")
146
+ print(f"\nProcessing files...")
147
+ print("-" * 40)
148
+
149
+ success_count = 0
150
+ for tif_file in tif_files:
151
+ if convert_nodata_value(tif_file, new_nodata):
152
+ success_count += 1
153
+ print()
154
+
155
+ print("Conversion complete!")
156
+ print(f"Successfully processed: {success_count}/{len(tif_files)} files")
157
+ if success_count < len(tif_files):
158
+ print("Some files failed to process. Check error messages above.")
159
+
160
+ elif args.input:
161
+ input_file = args.input
162
+ if not os.path.isfile(input_file):
163
+ print(f"Error: File not found: {input_file}")
164
+ sys.exit(1)
165
+
166
+ print("-" * 40)
167
+ success = convert_nodata_value(input_file, new_nodata)
168
+ print()
169
+ if success:
170
+ print("Conversion complete!")
171
+ else:
172
+ print("Conversion failed.")
173
+ sys.exit(1)
174
+
175
+
176
+ if __name__ == "__main__":
177
+ gdal.UseExceptions()
178
+
179
+ try:
180
+ main()
181
+ except Exception as e:
182
+ print(f"Error: {e}")
183
+ import traceback
184
+ traceback.print_exc()
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ csv2tif - Convert a CSV raster grid to a GeoTIFF file
4
+ Uses GDAL/OGR Python bindings
5
+
6
+ Usage:
7
+ csv2tif -i input.csv
8
+ csv2tif -i input.csv -o output.tif
9
+ csv2tif -i input.csv -o output.tif --xll 0.0 --yll 0.0 --cellsize 1.0
10
+ csv2tif -i input.csv --epsg 32648
11
+ """
12
+
13
+ import argparse
14
+ import os
15
+ import sys
16
+
17
+ import numpy as np
18
+ from osgeo import gdal, osr
19
+
20
+
21
+ def csv_to_tif(input_csv, output_tif, xll=0.0, yll=0.0, cellsize=1.0,
22
+ nodata=-999, epsg=None):
23
+ """
24
+ Convert a CSV raster grid to a GeoTIFF.
25
+
26
+ The CSV is expected to be a plain numeric grid (no headers) where rows
27
+ correspond to raster rows from top (north) to bottom (south).
28
+
29
+ Args:
30
+ input_csv (str): Path to input CSV file.
31
+ output_tif (str): Path to output GeoTIFF file.
32
+ xll (float): X coordinate of the lower-left corner (default 0.0).
33
+ yll (float): Y coordinate of the lower-left corner (default 0.0).
34
+ cellsize (float): Cell/pixel size in map units (default 1.0).
35
+ nodata (float): NoData value to assign (default -999).
36
+ epsg (int): EPSG code for the spatial reference (optional).
37
+ """
38
+ print(f"Input CSV : {input_csv}")
39
+ print(f"Output TIF : {output_tif}")
40
+
41
+ # --- Read CSV -------------------------------------------------------
42
+ try:
43
+ data = np.genfromtxt(input_csv, delimiter=",", dtype=np.float32)
44
+ except Exception as exc:
45
+ print(f"Error reading CSV: {exc}")
46
+ sys.exit(1)
47
+
48
+ if data.ndim == 1:
49
+ # Single-row CSV — treat as one-row raster
50
+ data = data.reshape(1, -1)
51
+
52
+ rows, cols = data.shape
53
+ print(f"Grid size : {cols} cols x {rows} rows")
54
+
55
+ # --- Build GeoTransform ---------------------------------------------
56
+ # GDAL geotransform: (x_top_left, pixel_width, 0, y_top_left, 0, -pixel_height)
57
+ x_top_left = xll
58
+ y_top_left = yll + rows * cellsize
59
+ geotransform = (x_top_left, cellsize, 0.0, y_top_left, 0.0, -cellsize)
60
+
61
+ # --- Create output GeoTIFF -----------------------------------------
62
+ driver = gdal.GetDriverByName("GTiff")
63
+ out_ds = driver.Create(output_tif, cols, rows, 1, gdal.GDT_Float32)
64
+ if out_ds is None:
65
+ print(f"Error: Could not create {output_tif}")
66
+ sys.exit(1)
67
+
68
+ out_ds.SetGeoTransform(geotransform)
69
+
70
+ # Spatial reference
71
+ srs = osr.SpatialReference()
72
+ if epsg is not None:
73
+ srs.ImportFromEPSG(epsg)
74
+ out_ds.SetProjection(srs.ExportToWkt())
75
+ print(f"Projection : EPSG:{epsg}")
76
+ else:
77
+ print("Projection : none (no EPSG provided)")
78
+
79
+ # Write band
80
+ out_band = out_ds.GetRasterBand(1)
81
+ out_band.SetNoDataValue(nodata)
82
+ out_band.WriteArray(data)
83
+ out_band.FlushCache()
84
+
85
+ out_ds.FlushCache()
86
+ out_ds = None
87
+
88
+ print(f"NoData : {nodata}")
89
+ print(f"Done -> {output_tif}")
90
+
91
+
92
+ def main():
93
+ parser = argparse.ArgumentParser(
94
+ prog='csv2tif',
95
+ description="Convert a CSV raster grid to a GeoTIFF",
96
+ formatter_class=argparse.RawDescriptionHelpFormatter,
97
+ epilog="""
98
+ Examples:
99
+ csv2tif -i input.csv
100
+ csv2tif -i input.csv -o output.tif
101
+ csv2tif -i input.csv -o output.tif --xll 250000 --yll 2500000 --cellsize 5
102
+ csv2tif -i input.csv --epsg 32648
103
+ """
104
+ )
105
+ parser.add_argument(
106
+ "-i", "--input", required=True,
107
+ help="Input CSV file path"
108
+ )
109
+ parser.add_argument(
110
+ "-o", "--output", default=None,
111
+ help="Output TIF file path (default: same name as input with .tif extension)"
112
+ )
113
+ parser.add_argument(
114
+ "--xll", type=float, default=0.0,
115
+ help="X coordinate of lower-left corner (default: 0.0)"
116
+ )
117
+ parser.add_argument(
118
+ "--yll", type=float, default=0.0,
119
+ help="Y coordinate of lower-left corner (default: 0.0)"
120
+ )
121
+ parser.add_argument(
122
+ "--cellsize", type=float, default=1.0,
123
+ help="Cell size in map units (default: 1.0)"
124
+ )
125
+ parser.add_argument(
126
+ "-n", "--nodata", type=float, default=-999,
127
+ help="NoData value to assign (default: -999)"
128
+ )
129
+ parser.add_argument(
130
+ "--epsg", type=int, default=None,
131
+ help="EPSG code for spatial reference (optional)"
132
+ )
133
+
134
+ args = parser.parse_args()
135
+
136
+ if not os.path.isfile(args.input):
137
+ print(f"Error: Input file not found: {args.input}")
138
+ sys.exit(1)
139
+
140
+ output_tif = args.output
141
+ if output_tif is None:
142
+ base = os.path.splitext(args.input)[0]
143
+ output_tif = base + ".tif"
144
+
145
+ csv_to_tif(
146
+ input_csv=args.input,
147
+ output_tif=output_tif,
148
+ xll=args.xll,
149
+ yll=args.yll,
150
+ cellsize=args.cellsize,
151
+ nodata=args.nodata,
152
+ epsg=args.epsg,
153
+ )
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Command-line utility to display descriptions of demtools functionality.
4
+ """
5
+
6
+ import argparse
7
+ import sys
8
+ from textwrap import dedent
9
+
10
+ TOOL_DESCRIPTIONS = {
11
+ 'chgnodata': """
12
+ Convert nodata values for TIF raster files.
13
+
14
+ This tool reads one or more GeoTIFF files, replaces the existing nodata
15
+ value with a new one (re-writing pixel data), and saves a backup of the
16
+ original file in the RAS_BAK/ directory.
17
+
18
+ Examples:
19
+ chgnodata -a # Process all *.tif (nodata=-999)
20
+ chgnodata -a -v -9999 # Process all *.tif, set nodata to -9999
21
+ chgnodata -i dem.tif # Process a single file (nodata=-999)
22
+ chgnodata -i dem.tif -v -9999 # Process a single file, set nodata to -9999
23
+ """,
24
+ 'setnodata': """
25
+ Define a nodata value for TIF raster files without modifying pixel data.
26
+
27
+ This tool updates only the nodata *metadata* of one or more GeoTIFF files
28
+ without touching the actual pixel values. A backup is saved in RAS_BAK/.
29
+
30
+ Examples:
31
+ setnodata -a # Process all *.tif (nodata=-999)
32
+ setnodata -a -v -9999 # Process all *.tif, define nodata as -9999
33
+ setnodata -i dem.tif # Process a single file (nodata=-999)
34
+ setnodata -i dem.tif -v -9999 # Process a single file, define nodata as -9999
35
+ """,
36
+ 'csv2tif': """
37
+ Convert a plain-numeric CSV raster grid to a GeoTIFF file.
38
+
39
+ The CSV must contain only numeric values arranged in rows (top→bottom) and
40
+ columns (left→right), with no header row. Georeferencing parameters
41
+ (lower-left corner coordinates, cell size, EPSG) can be provided via options.
42
+
43
+ Examples:
44
+ csv2tif -i grid.csv
45
+ csv2tif -i grid.csv -o dem.tif
46
+ csv2tif -i grid.csv --xll 250000 --yll 2500000 --cellsize 5 --epsg 32648
47
+ csv2tif -i grid.csv -n -9999
48
+ """,
49
+ }
50
+
51
+
52
+ def main():
53
+ parser = argparse.ArgumentParser(
54
+ prog='demtools-info',
55
+ description='Display descriptions of demtools commands',
56
+ formatter_class=argparse.RawDescriptionHelpFormatter,
57
+ )
58
+ parser.add_argument(
59
+ 'tool',
60
+ nargs='?',
61
+ choices=list(TOOL_DESCRIPTIONS.keys()),
62
+ help='Tool name to describe (omit to list all tools)',
63
+ )
64
+ args = parser.parse_args()
65
+
66
+ if args.tool:
67
+ print(f"\n--- {args.tool} ---")
68
+ print(dedent(TOOL_DESCRIPTIONS[args.tool]))
69
+ else:
70
+ print("\nDEMTOOLS — DEM raster utility commands\n")
71
+ for tool, desc in TOOL_DESCRIPTIONS.items():
72
+ first_line = dedent(desc).strip().splitlines()[0]
73
+ print(f" {tool:<14} {first_line}")
74
+ print("\nRun 'demtools-info <tool>' for details on a specific command.")
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ setnodata - Define (assign) a nodata value for TIF raster files without modifying pixel data
4
+ Uses GDAL/OGR Python bindings
5
+ """
6
+
7
+ import glob
8
+ import argparse
9
+ import os
10
+ import sys
11
+ from osgeo import gdal, gdalconst
12
+
13
+ def define_nodata_value(input_file, nodata_value=-999):
14
+ """
15
+ Define a nodata value for a raster file without changing any pixel data.
16
+ Only the nodata metadata is updated in the file.
17
+
18
+ Args:
19
+ input_file (str): Path to input TIF file
20
+ nodata_value (float): Nodata value to assign (default: -999)
21
+ """
22
+ print(f"Processing: {input_file}")
23
+
24
+ # Open the input dataset in read-only to inspect current state
25
+ src_ds = gdal.Open(input_file, gdalconst.GA_ReadOnly)
26
+ if src_ds is None:
27
+ print(f"Error: Could not open {input_file}")
28
+ return False
29
+
30
+ # Get raster band (assuming single band, modify if multi-band)
31
+ src_band = src_ds.GetRasterBand(1)
32
+
33
+ # Get current nodata value
34
+ current_nodata = src_band.GetNoDataValue()
35
+ print(f" Current nodata value: {current_nodata}")
36
+
37
+ if current_nodata == nodata_value:
38
+ print(f" Nodata value already set to {nodata_value}, skipping...")
39
+ src_ds = None
40
+ return True
41
+
42
+ # Close read-only handle before backup/update
43
+ src_ds = None
44
+
45
+ # Create backup directory
46
+ backup_dir = "RAS_BAK"
47
+ if not os.path.exists(backup_dir):
48
+ os.makedirs(backup_dir)
49
+ print(f" Created backup directory: {backup_dir}")
50
+
51
+ backup_filename = os.path.basename(input_file)
52
+ backup_file = os.path.join(backup_dir, backup_filename)
53
+
54
+ # Create backup before modifying
55
+ print(f" Creating backup: {backup_file}")
56
+ src_ds = gdal.Open(input_file, gdalconst.GA_ReadOnly)
57
+ gdal.GetDriverByName('GTiff').CreateCopy(backup_file, src_ds)
58
+ src_ds = None
59
+
60
+ # Re-open in update mode to set nodata metadata only (no pixel data changed)
61
+ ds = gdal.Open(input_file, gdalconst.GA_Update)
62
+ if ds is None:
63
+ print(f"Error: Could not open {input_file} for update")
64
+ return False
65
+
66
+ band = ds.GetRasterBand(1)
67
+ band.SetNoDataValue(nodata_value)
68
+ band.FlushCache()
69
+ ds = None
70
+
71
+ print(f" Successfully defined nodata value as {nodata_value}")
72
+ return True
73
+
74
+
75
+ def main():
76
+ parser = argparse.ArgumentParser(
77
+ prog='setnodata',
78
+ description='Define a nodata value for TIF raster files without modifying pixel data',
79
+ formatter_class=argparse.RawDescriptionHelpFormatter,
80
+ epilog="""
81
+ Examples:
82
+ setnodata -a # Process all *.tif in current directory (nodata=-999)
83
+ setnodata -a -v -9999 # Process all *.tif, define nodata as -9999
84
+ setnodata -i dem.tif # Process a single file (nodata=-999)
85
+ setnodata -i dem.tif -v -9999 # Process a single file, define nodata as -9999
86
+ """
87
+ )
88
+
89
+ # Mutually exclusive group: -a or -i
90
+ group = parser.add_mutually_exclusive_group(required=True)
91
+ group.add_argument(
92
+ '-a', '--all',
93
+ action='store_true',
94
+ help='Process all *.tif files in the current directory'
95
+ )
96
+ group.add_argument(
97
+ '-i', '--input',
98
+ metavar='FILE',
99
+ help='Process a single TIF file'
100
+ )
101
+
102
+ parser.add_argument(
103
+ '-v', '--value',
104
+ dest='nodata_value',
105
+ type=float,
106
+ default=-999,
107
+ metavar='NODATA',
108
+ help='Nodata value to assign (default: -999)'
109
+ )
110
+
111
+ args = parser.parse_args()
112
+ new_nodata = args.nodata_value
113
+
114
+ print(f"setnodata: defining nodata value as {new_nodata}")
115
+ print("=" * 60)
116
+
117
+ if args.all:
118
+ tif_files = glob.glob("*.tif")
119
+ if not tif_files:
120
+ print("No TIF files found in current directory.")
121
+ sys.exit(0)
122
+
123
+ print(f"Found {len(tif_files)} TIF file(s):")
124
+ for f in tif_files:
125
+ print(f" - {f}")
126
+ print(f"\nProcessing files...")
127
+ print("-" * 40)
128
+
129
+ success_count = 0
130
+ for tif_file in tif_files:
131
+ if define_nodata_value(tif_file, new_nodata):
132
+ success_count += 1
133
+ print()
134
+
135
+ print("Done!")
136
+ print(f"Successfully processed: {success_count}/{len(tif_files)} files")
137
+ if success_count < len(tif_files):
138
+ print("Some files failed to process. Check error messages above.")
139
+
140
+ elif args.input:
141
+ input_file = args.input
142
+ if not os.path.isfile(input_file):
143
+ print(f"Error: File not found: {input_file}")
144
+ sys.exit(1)
145
+
146
+ print("-" * 40)
147
+ success = define_nodata_value(input_file, new_nodata)
148
+ print()
149
+ if success:
150
+ print("Done!")
151
+ else:
152
+ print("Failed.")
153
+ sys.exit(1)
154
+
155
+
156
+ if __name__ == "__main__":
157
+ gdal.UseExceptions()
158
+
159
+ try:
160
+ main()
161
+ except Exception as e:
162
+ print(f"Error: {e}")
163
+ import traceback
164
+ traceback.print_exc()
File without changes
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: demtools
3
+ Version: 0.1.0
4
+ Summary: A collection of tools for working with DEM (Digital Elevation Model) raster files
5
+ Home-page: https://github.com/AaronOET/demtools
6
+ Author: aaronchh
7
+ Author-email: aaronchh <aaronhsu219@gmail.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/AaronOET/demtools
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: numpy>=1.20.0
16
+ Dynamic: author
17
+ Dynamic: home-page
18
+ Dynamic: requires-python
19
+
20
+ # DEMTOOLS
21
+
22
+ A collection of Python tools for working with DEM (Digital Elevation Model) raster files.
23
+
24
+ > **GDAL Installation**: GDAL is required for this package. For conda environments, use `conda install gdal` to install GDAL. For non-conda environments, download the appropriate wheel file from [https://github.com/cgohlke/geospatial-wheels/releases](https://github.com/cgohlke/geospatial-wheels/releases) to install GDAL.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install -e .
30
+ ```
31
+
32
+ ## Features
33
+
34
+ - **chgnodata**: Convert (replace) the nodata value of GeoTIFF files, updating pixel data accordingly
35
+ - **setnodata**: Assign a nodata value to GeoTIFF files without modifying pixel data (metadata only)
36
+ - **csv2tif**: Convert a plain-numeric CSV raster grid to a GeoTIFF file
37
+
38
+ ## Usage
39
+
40
+ ### Command-line
41
+
42
+ ```bash
43
+ # List all available commands
44
+ demtools-info
45
+
46
+ # Convert nodata values for all TIF files in the current directory
47
+ chgnodata -a
48
+
49
+ # Convert nodata of a single file to -9999
50
+ chgnodata -i dem.tif -v -9999
51
+
52
+ # Set nodata metadata for all TIF files (no pixel data change)
53
+ setnodata -a -v -9999
54
+
55
+ # Convert a CSV grid to GeoTIFF
56
+ csv2tif -i grid.csv -o dem.tif --xll 250000 --yll 2500000 --cellsize 5 --epsg 32648
57
+ ```
58
+
59
+ ### Python API
60
+
61
+ ```python
62
+ from demtools import chgnodata, setnodata, csv2tif
63
+
64
+ # Convert nodata value in a raster file
65
+ chgnodata.convert_nodata_value("dem.tif", output_nodata=-9999)
66
+
67
+ # Assign nodata metadata without touching pixel values
68
+ setnodata.define_nodata_value("dem.tif", nodata_value=-9999)
69
+
70
+ # Convert CSV grid to GeoTIFF
71
+ csv2tif.csv_to_tif(
72
+ input_csv="grid.csv",
73
+ output_tif="dem.tif",
74
+ xll=250000,
75
+ yll=2500000,
76
+ cellsize=5,
77
+ nodata=-999,
78
+ epsg=32648,
79
+ )
80
+ ```
81
+
82
+ ## Notes
83
+
84
+ - `chgnodata` and `setnodata` automatically create a `RAS_BAK/` directory with backup copies before modifying files.
85
+ - `csv2tif` expects a plain numeric CSV (no headers), with rows ordered from north to south.
86
+ - GDAL must be installed separately via conda or a pre-built wheel; it is not listed in `requirements.txt` as it cannot be reliably installed via pip on all platforms.
@@ -0,0 +1,17 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ requirements.txt
5
+ setup.py
6
+ demtools/__init__.py
7
+ demtools/chgnodata.py
8
+ demtools/csv2tif.py
9
+ demtools/describe.py
10
+ demtools/setnodata.py
11
+ demtools.egg-info/PKG-INFO
12
+ demtools.egg-info/SOURCES.txt
13
+ demtools.egg-info/dependency_links.txt
14
+ demtools.egg-info/entry_points.txt
15
+ demtools.egg-info/requires.txt
16
+ demtools.egg-info/top_level.txt
17
+ demtools/tests/__init__.py
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ chgnodata = demtools.chgnodata:main
3
+ csv2tif = demtools.csv2tif:main
4
+ demtools-info = demtools.describe:main
5
+ setnodata = demtools.setnodata:main
@@ -0,0 +1 @@
1
+ numpy>=1.20.0
@@ -0,0 +1 @@
1
+ demtools
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "demtools"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name = "aaronchh", email = "aaronhsu219@gmail.com" },
10
+ ]
11
+ description = "A collection of tools for working with DEM (Digital Elevation Model) raster files"
12
+ readme = "README.md"
13
+ license = { text = "MIT" }
14
+ requires-python = ">=3.8"
15
+ dependencies = [
16
+ "numpy>=1.20.0",
17
+ ]
18
+ classifiers = [
19
+ "Programming Language :: Python :: 3",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ ]
23
+
24
+ [project.urls]
25
+ Homepage = "https://github.com/AaronOET/demtools"
26
+
27
+ [project.scripts]
28
+ demtools-info = "demtools.describe:main"
29
+ chgnodata = "demtools.chgnodata:main"
30
+ setnodata = "demtools.setnodata:main"
31
+ csv2tif = "demtools.csv2tif:main"
@@ -0,0 +1 @@
1
+ numpy>=1.20.0
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r", encoding="utf-8") as fh:
4
+ long_description = fh.read()
5
+
6
+ with open("requirements.txt", "r", encoding="utf-8") as f:
7
+ requirements = f.read().splitlines()
8
+
9
+ setup(
10
+ name="demtools",
11
+ version="0.1.0",
12
+ author="aaronchh",
13
+ author_email="aaronhsu219@gmail.com",
14
+ description="A collection of tools for working with DEM (Digital Elevation Model) raster files",
15
+ long_description=long_description,
16
+ long_description_content_type="text/markdown",
17
+ url="https://github.com/AaronOET/demtools",
18
+ packages=find_packages(),
19
+ classifiers=[
20
+ "Programming Language :: Python :: 3",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Operating System :: OS Independent",
23
+ ],
24
+ python_requires=">=3.8",
25
+ install_requires=requirements,
26
+ entry_points={
27
+ "console_scripts": [
28
+ "demtools-info=demtools.describe:main",
29
+ "chgnodata=demtools.chgnodata:main",
30
+ "setnodata=demtools.setnodata:main",
31
+ "csv2tif=demtools.csv2tif:main",
32
+ ],
33
+ },
34
+ )