1
1
import rasterio
2
+ import rasterio .features
3
+ import rasterio .mask
4
+ import geopandas
2
5
import pandas
3
6
import numpy
7
+ import typing
4
8
5
9
6
10
class Raster :
@@ -9,7 +13,7 @@ class Raster:
9
13
Provides functionality for raster file operations.
10
14
'''
11
15
12
- def count_non_nodata_cells (
16
+ def count_data_cells (
13
17
self ,
14
18
input_file : str
15
19
) -> int :
@@ -61,50 +65,110 @@ def count_nodata_cells(
61
65
62
66
return output
63
67
64
- def percentage_unique_integers (
68
+ def counting_unique_values (
65
69
self ,
66
- input_file : str
70
+ input_file : str ,
71
+ csv_file : str ,
72
+ multiplier : float = 1
67
73
) -> pandas .DataFrame :
68
74
69
75
'''
70
- Computes the percentage of unique integer values in the raster array.
76
+ Computes the count of unique data values in a raster array. If the data contains decimal values,
77
+ the input multiplier is used to scale the decimal values to integers for counting purposes.
78
+ The data are then scaled back to the decimal values by dividing by the multiplier.
71
79
72
80
Parameters
73
81
----------
74
82
input_file : str
75
83
Path to the input raster file.
76
84
85
+ csv_file : str
86
+ Path to save the output csv file.
87
+
88
+ multiplier : float, optional
89
+ A factor to multiply raster values to handle decimal values by rounding.
90
+ Default is 1, which implies no scaling.
91
+
77
92
Returns
78
93
-------
79
94
DataFrame
80
- A DataFrame containing the unique integer values in the raster ,
81
- their counts, and their counts as a percentage of the total.
95
+ A DataFrame containing the raster values, their counts ,
96
+ and their counts as a percentage of the total.
82
97
'''
83
98
84
99
with rasterio .open (input_file ) as input_raster :
85
100
raster_profile = input_raster .profile
86
- if 'int' not in raster_profile ['dtype' ]:
87
- raise Exception ('Input raster data must be integer type.' )
88
- else :
89
- # DataFrame
90
- raster_array = input_raster .read (1 )
91
- valid_array = raster_array [raster_array != raster_profile ['nodata' ]]
92
- value , count = numpy .unique (
93
- valid_array ,
94
- return_counts = True
95
- )
96
- df = pandas .DataFrame ({'Value' : value , 'Count' : count })
97
- df ['Percent(%)' ] = 100 * df ['Count' ] / df ['Count' ].sum ()
101
+ raster_array = input_raster .read (1 )
102
+ value_array = (multiplier * raster_array [raster_array != raster_profile ['nodata' ]]).round ()
103
+ value , count = numpy .unique (
104
+ value_array ,
105
+ return_counts = True
106
+ )
107
+ df = pandas .DataFrame ({'Value' : value , 'Count' : count })
108
+ df ['Value' ] = df ['Value' ] / multiplier
109
+ df ['Count(%)' ] = 100 * df ['Count' ] / df ['Count' ].sum ()
110
+ df ['Cumulative_Count(%)' ] = df ['Count(%)' ].cumsum ()
111
+ df .to_csv (
112
+ path_or_buf = csv_file ,
113
+ index_label = 'Index' ,
114
+ float_format = '%.2f'
115
+ )
98
116
99
117
return df
100
118
119
+ def boundary_polygon (
120
+ self ,
121
+ input_file : str ,
122
+ output_file : str
123
+ ) -> geopandas .GeoDataFrame :
124
+
125
+ '''
126
+ Extracts boundary polygons from a raster array.
127
+
128
+ Parameters
129
+ ----------
130
+ input_file : str
131
+ Path to the input raster file.
132
+
133
+ output_file : str
134
+ Path to save the output shapefile.
135
+
136
+ Returns
137
+ -------
138
+ GeoDataFrame
139
+ A GeoDataFrame containing the boundary polygons extracted from the raster.
140
+ '''
141
+
142
+ with rasterio .open (input_file ) as input_raster :
143
+ raster_array = input_raster .read (1 )
144
+ raster_array [raster_array != input_raster .nodata ] = 1
145
+ mask = raster_array == 1
146
+ boundary_shapes = rasterio .features .shapes (
147
+ source = raster_array ,
148
+ mask = mask ,
149
+ transform = input_raster .transform
150
+ )
151
+ boundary_features = [
152
+ {'geometry' : geom , 'properties' : {'value' : val }} for geom , val in boundary_shapes
153
+ ]
154
+ gdf = geopandas .GeoDataFrame .from_features (
155
+ features = boundary_features ,
156
+ crs = input_raster .crs
157
+ )
158
+ gdf = gdf [gdf .is_valid ].reset_index (drop = True )
159
+ gdf ['bid' ] = range (1 , gdf .shape [0 ] + 1 )
160
+ gdf = gdf [['bid' , 'geometry' ]]
161
+ gdf .to_file (output_file )
162
+
163
+ return gdf
164
+
101
165
def rescaling_resolution (
102
166
self ,
103
167
input_file : str ,
104
168
target_resolution : int ,
105
169
resampling_method : str ,
106
170
output_file : str
107
- ) -> float :
171
+ ) -> list [ list [ float ]] :
108
172
109
173
'''
110
174
Rescales the raster array from the existing resolution to a new target resolution.
@@ -128,8 +192,9 @@ def rescaling_resolution(
128
192
129
193
Returns
130
194
-------
131
- float
132
- The resolution of the rescaled raster array.
195
+ list
196
+ A nested list representation of the 3x3 affine transformation matrix
197
+ of the reprojected raster array. Each sublist represents a row of the matrix.
133
198
'''
134
199
135
200
# mapping of resampling methods
@@ -179,17 +244,19 @@ def rescaling_resolution(
179
244
dst_crs = input_raster .crs ,
180
245
resampling = resampling_function [resampling_method ]
181
246
)
182
- output_resolution = float (rescaled_raster [1 ][0 ])
247
+ affine_matrix = [
248
+ list (rescaled_raster [1 ])[i :i + 3 ] for i in [0 , 3 , 6 ]
249
+ ]
183
250
184
- return output_resolution
251
+ return affine_matrix
185
252
186
253
def rescaling_resolution_with_mask (
187
254
self ,
188
255
input_file : str ,
189
256
mask_file : str ,
190
257
resampling_method : str ,
191
258
output_file : str
192
- ) -> float :
259
+ ) -> list [ list [ float ]] :
193
260
194
261
'''
195
262
Rescales the raster array from its existing resolution
@@ -214,8 +281,9 @@ def rescaling_resolution_with_mask(
214
281
215
282
Returns
216
283
-------
217
- float
218
- The resolution of the rescaled raster array.
284
+ list
285
+ A nested list representation of the 3x3 affine transformation matrix
286
+ of the reprojected raster array. Each sublist represents a row of the matrix.
219
287
'''
220
288
221
289
# mapping of resampling methods
@@ -270,6 +338,196 @@ def rescaling_resolution_with_mask(
270
338
dst_crs = mask_raster .crs ,
271
339
resampling = resampling_function [resampling_method ]
272
340
)
273
- output_resolution = float (rescaled_raster [1 ][0 ])
341
+ affine_matrix = [
342
+ list (rescaled_raster [1 ])[i :i + 3 ] for i in [0 , 3 , 6 ]
343
+ ]
344
+
345
+ return affine_matrix
346
+
347
+ def reproject_crs (
348
+ self ,
349
+ input_file : str ,
350
+ resampling_method : str ,
351
+ target_crs : str ,
352
+ output_file : str
353
+ ) -> list [list [float ]]:
354
+
355
+ '''
356
+ Reprojects a raster array to a new Coordinate Reference System.
357
+
358
+ Parameters
359
+ ----------
360
+ input_file : str
361
+ Path to the input raster file.
362
+
363
+ resampling_method : str
364
+ Method used for raster resampling. Supported options are:
365
+ - 'nearest': Nearest-neighbor interpolation.
366
+ - 'bilinear': Bilinear interpolation.
367
+ - 'cubic': Cubic convolution interpolation.
368
+
369
+ target_crs : str
370
+ Target Coordinate Reference System for the output raster (e.g., 'EPSG:4326').
371
+
372
+ output_file : str
373
+ Path to save the reprojected raster file.
374
+
375
+ Returns
376
+ -------
377
+ list
378
+ A nested list representation of the 3x3 affine transformation matrix
379
+ of the reprojected raster array. Each sublist represents a row of the matrix.
380
+ '''
381
+
382
+ # mapping of resampling methods
383
+ resampling_function = {
384
+ 'nearest' : rasterio .enums .Resampling .nearest ,
385
+ 'bilinear' : rasterio .enums .Resampling .bilinear ,
386
+ 'cubic' : rasterio .enums .Resampling .cubic
387
+ }
388
+
389
+ # check resampling method
390
+ if resampling_method in resampling_function .keys ():
391
+ pass
392
+ else :
393
+ raise Exception ('Input resampling method is not supported.' )
394
+
395
+ # reproject Coordinate Reference System
396
+ with rasterio .open (input_file ) as input_raster :
397
+ raster_profile = input_raster .profile
398
+ # output raster parameters
399
+ output_transform , output_width , output_height = rasterio .warp .calculate_default_transform (
400
+ src_crs = input_raster .crs ,
401
+ dst_crs = target_crs ,
402
+ width = input_raster .width ,
403
+ height = input_raster .height ,
404
+ left = input_raster .bounds .left ,
405
+ bottom = input_raster .bounds .bottom ,
406
+ right = input_raster .bounds .right ,
407
+ top = input_raster .bounds .top
408
+ )
409
+ # output raster profile
410
+ raster_profile .update (
411
+ {
412
+ 'transform' : output_transform ,
413
+ 'width' : output_width ,
414
+ 'height' : output_height ,
415
+ 'crs' : target_crs
416
+ }
417
+ )
418
+ # saving output raster
419
+ with rasterio .open (output_file , 'w' , ** raster_profile ) as output_raster :
420
+ reproject_raster = rasterio .warp .reproject (
421
+ source = rasterio .band (input_raster , 1 ),
422
+ destination = rasterio .band (output_raster , 1 ),
423
+ src_transform = input_raster .transform ,
424
+ src_crs = input_raster .crs ,
425
+ dst_transform = output_transform ,
426
+ dst_crs = target_crs ,
427
+ resampling = resampling_function [resampling_method ]
428
+ )
429
+ affine_matrix = [
430
+ list (reproject_raster [1 ])[i :i + 3 ] for i in [0 , 3 , 6 ]
431
+ ]
432
+
433
+ return affine_matrix
434
+
435
+ def set_value_to_nodata (
436
+ self ,
437
+ input_file : str ,
438
+ target_value : list [float ],
439
+ nodata : float ,
440
+ output_file : str
441
+ ) -> dict [str , typing .Any ]:
442
+
443
+ '''
444
+ Converts specified values in a raster array to nodata.
445
+
446
+ Parameters
447
+ ----------
448
+ input_file : str
449
+ Path to the input raster file.
450
+
451
+ target_value : list
452
+ List of values in the input raster array to convert to nodata.
453
+
454
+ nodata : float
455
+ The nodata value to assign in the output raster.
456
+
457
+ output_file : str
458
+ Path to save the output raster file.
459
+
460
+ Returns
461
+ -------
462
+ dict
463
+ A profile dictionary containing metadata about the output raster.
464
+ '''
465
+
466
+ with rasterio .open (input_file ) as input_raster :
467
+ raster_profile = input_raster .profile
468
+ input_array = input_raster .read (1 )
469
+ output_array = numpy .where (
470
+ numpy .isin (input_array , target_value ),
471
+ nodata ,
472
+ input_array
473
+ )
474
+ raster_profile .update (
475
+ {
476
+ 'nodata' : nodata
477
+ }
478
+ )
479
+ with rasterio .open (output_file , 'w' , ** raster_profile ) as output_raster :
480
+ output_raster .write (output_array , 1 )
481
+ output_profile = dict (output_raster .profile )
482
+
483
+ return output_profile
484
+
485
+ def clipping_by_shapes (
486
+ self ,
487
+ input_file : str ,
488
+ shape_file : str ,
489
+ output_file : str
490
+ ) -> dict [str , typing .Any ]:
491
+
492
+ '''
493
+ Clips a raster file using a given shape file.
494
+
495
+ Parameters
496
+ ----------
497
+ input_file : str
498
+ Path to the input raster file.
499
+
500
+ shape_file : str
501
+ Path to the input shape file used for clipping.
502
+
503
+ output_file : str
504
+ Path to save the output raster file.
505
+
506
+ Returns
507
+ -------
508
+ dict
509
+ A profile dictionary containing metadata about the output raster.
510
+ '''
511
+
512
+ with rasterio .open (input_file ) as input_raster :
513
+ raster_profile = input_raster .profile .copy ()
514
+ gdf = geopandas .read_file (shape_file )
515
+ gdf = gdf .to_crs (str (raster_profile ['crs' ]))
516
+ output_array , output_transform = rasterio .mask .mask (
517
+ dataset = input_raster ,
518
+ shapes = gdf .geometry .tolist (),
519
+ all_touched = True ,
520
+ crop = True
521
+ )
522
+ raster_profile .update (
523
+ {
524
+ 'height' : output_array .shape [1 ],
525
+ 'width' : output_array .shape [2 ],
526
+ 'transform' : output_transform
527
+ }
528
+ )
529
+ with rasterio .open (output_file , 'w' , ** raster_profile ) as output_raster :
530
+ output_raster .write (output_array )
531
+ output_profile = dict (output_raster .profile )
274
532
275
- return output_resolution
533
+ return output_profile
0 commit comments