88import rioxarray as rxr
99import script_utils
1010import xarray as xr
11+ import yaml
1112from rasterio .enums import Resampling
1213from rasterio .features import rasterize
1314
1617# From Troendle et al. (2019) https://github.com/timtroendle/possibility-for-electricity-autarky
1718
1819
19- GlobCover = {
20+ GLOBCOVER_TYPES = {
2021 11 : "POST_FLOODING" ,
2122 14 : "RAINFED_CROPLANDS" ,
2223 20 : "MOSAIC_CROPLAND" ,
3233 130 : "CLOSED_TO_OPEN_SHRUBLAND" ,
3334 140 : "CLOSED_TO_OPEN_HERBS" ,
3435 150 : "SPARSE_VEGETATION" ,
35- 160 : "CLOSED_TO_OPEN_REGULARLY_FLOODED_FOREST" , # doesn't exist in Europe
36- 170 : "CLOSED_REGULARLY_FLOODED_FOREST" , # doesn't exist in Europe
37- 180 : "CLOSED_TO_OPEN_REGULARLY_FLOODED_GRASSLAND" , # roughly 2.3% of land in Europe
38- 190 : "ARTIFICAL_SURFACES_AND_URBAN_AREAS " ,
36+ 160 : "CLOSED_TO_OPEN_REGULARLY_FLOODED_FOREST" ,
37+ 170 : "CLOSED_REGULARLY_FLOODED_FOREST" ,
38+ 180 : "CLOSED_TO_OPEN_REGULARLY_FLOODED_GRASSLAND" ,
39+ 190 : "ARTIFICIAL_SURFACES_AND_URBAN_AREAS " ,
3940 200 : "BARE_AREAS" ,
4041 210 : "WATER_BODIES" ,
4142 220 : "PERMANENT_SNOW" ,
4243 230 : "NO_DATA" ,
4344}
4445
45- CoverType = {
46- "POST_FLOODING" : "FARM" ,
47- "RAINFED_CROPLANDS" : "FARM" ,
48- "MOSAIC_CROPLAND" : "FARM" ,
49- "MOSAIC_VEGETATION" : "FARM" ,
50- "CLOSED_TO_OPEN_BROADLEAVED_FOREST" : "FOREST" ,
51- "CLOSED_BROADLEAVED_FOREST" : "FOREST" ,
52- "OPEN_BROADLEAVED_FOREST" : "FOREST" ,
53- "CLOSED_NEEDLELEAVED_FOREST" : "FOREST" ,
54- "OPEN_NEEDLELEAVED_FOREST" : "FOREST" ,
55- "CLOSED_TO_OPEN_MIXED_FOREST" : "FOREST" ,
56- "MOSAIC_FOREST" : "FOREST" ,
57- "CLOSED_TO_OPEN_REGULARLY_FLOODED_FOREST" : "FOREST" ,
58- "CLOSED_REGULARLY_FLOODED_FOREST" : "FOREST" ,
59- "MOSAIC_GRASSLAND" : "OTHER" , # vegetation
60- "CLOSED_TO_OPEN_SHRUBLAND" : "OTHER" , # vegetation
61- "CLOSED_TO_OPEN_HERBS" : "OTHER" , # vegetation
62- "SPARSE_VEGETATION" : "OTHER" , # vegetation
63- "CLOSED_TO_OPEN_REGULARLY_FLOODED_GRASSLAND" : "OTHER" , # vegetation
64- "BARE_AREAS" : "OTHER" ,
65- "ARTIFICAL_SURFACES_AND_URBAN_AREAS" : "URBAN" ,
66- "WATER_BODIES" : "WATER" ,
67- "PERMANENT_SNOW" : "NOT_SUITABLE" ,
68- "NO_DATA" : "NOT_SUITABLE" ,
69- }
70-
7146
72- def get_suitable_land_cover_type (ds_land_cover , suitable_land_cover_types ):
47+ def aggregate_land_cover_types (ds_land_cover , land_cover_types ):
7348 """Convert raw GlobCover data to a dataset with suitable land cover types."""
7449 suitable_land_cover = xr .Dataset (coords = ds_land_cover .coords )
7550
7651 # convert the input value to land cover type of interest
7752 for value in np .unique (ds_land_cover .data ):
78- if value in GlobCover :
53+ if value in GLOBCOVER_TYPES :
7954 ds_land_cover = ds_land_cover .where (
80- ds_land_cover != value , other = CoverType [GlobCover [value ]], drop = False
55+ ds_land_cover != value ,
56+ other = land_cover_types [GLOBCOVER_TYPES [value ]],
57+ drop = False ,
8158 )
8259
8360 # check if each pixel is in the list of suitable land cover types
84- for type in suitable_land_cover_types :
85- suitable_land_cover [type ] = (ds_land_cover == type ).astype (float )
61+ for type_ in sorted ( list ( set ( land_cover_types . values ()))) :
62+ suitable_land_cover [type_ ] = (ds_land_cover == type_ ).astype (np . byte )
8663
8764 return suitable_land_cover
8865
@@ -159,6 +136,7 @@ def _rasterize_regions(shapes, reference_raster):
159136@click .argument ("settlement_path" , type = str )
160137@click .argument ("bathymetry_path" , type = str )
161138@click .argument ("protected_area_path" , type = str )
139+ @click .argument ("land_cover_configuration_yaml_string" , type = str )
162140@click .argument ("output_path" , type = str )
163141@click .argument ("plot_path" , type = str )
164142def resample_inputs (
@@ -168,6 +146,7 @@ def resample_inputs(
168146 settlement_path ,
169147 bathymetry_path ,
170148 protected_area_path ,
149+ land_cover_configuration_yaml_string ,
171150 output_path ,
172151 plot_path ,
173152):
@@ -178,23 +157,21 @@ def resample_inputs(
178157
179158 """
180159 shapes = gpd .read_parquet (shapes_path )
160+ resampled = xr .Dataset ()
181161
182162 ##
183163 # Land cover
184164 ##
185- suitable_land_cover_types = sorted (list (set (CoverType .values ())))
186165 ds_land_cover = rxr .open_rasterio (land_cover_path )
187- reference_raster = xr .ones_like (ds_land_cover )
166+ land_cover_types = yaml .safe_load (land_cover_configuration_yaml_string )
167+ reference_raster = xr .ones_like (ds_land_cover , dtype = np .byte )
188168 reference_resolution = ds_land_cover .rio .resolution ()
189- print (f"Land cover resolution: { reference_resolution } " )
190- land_cover = get_suitable_land_cover_type (ds_land_cover , suitable_land_cover_types )
191-
192- resampled = xr .Dataset ()
169+ print (f"Land cover resolution used as reference resolution: { reference_resolution } " )
170+ land_cover = aggregate_land_cover_types (ds_land_cover , land_cover_types )
193171
194- for land_type in suitable_land_cover_types :
195- resampled [f"landcover_{ land_type } " ] = land_cover [land_type ].rio .reproject_match (
196- reference_raster , resampling = Resampling .average
197- )
172+ for land_type in sorted (list (set (land_cover_types .values ()))):
173+ resampled [f"landcover_{ land_type } " ] = land_cover [land_type ]
174+ del ds_land_cover , land_cover
198175
199176 ##
200177 # Pixel area
@@ -204,6 +181,7 @@ def resample_inputs(
204181 resampled ["pixel_area" ] = pixel_area .expand_dims ({"x" : resampled .x }).transpose (
205182 "y" , "x"
206183 )
184+ del pixel_area
207185
208186 ##
209187 # Regions
@@ -227,17 +205,21 @@ def resample_inputs(
227205 dims = resampled ["regions" ].dims ,
228206 coords = resampled ["regions" ].coords ,
229207 )
230- resampled ["regions_land" ] = xr .where (mask_land , 1.0 , np .nan )
231- resampled ["regions_maritime" ] = xr .where (mask_maritime , 1.0 , np .nan )
208+ resampled ["regions_land" ] = xr .where (mask_land , np .half (1.0 ), np .half (np .nan ))
209+ resampled ["regions_maritime" ] = xr .where (
210+ mask_maritime , np .half (1.0 ), np .half (np .nan )
211+ )
212+ del mask_land , mask_maritime
232213
233214 ##
234215 # Slope
235216 ##
236217 da_slope = rxr .open_rasterio (slope_path , masked = True ) / 100
237218 print (f"Slope resolution: { da_slope .rio .resolution ()} " )
238- resampled ["slope" ] = da_slope .astype ( float ). rio .reproject_match (
219+ resampled ["slope" ] = da_slope .rio .reproject_match (
239220 reference_raster , resampling = Resampling .average
240221 )
222+ del da_slope
241223
242224 ##
243225 # Settlement in sum of area of built-up surface (m2)
@@ -260,6 +242,7 @@ def resample_inputs(
260242 resampled ["settlement_area" ] = (
261243 resampled ["settlement_share" ] * resampled ["pixel_area" ]
262244 )
245+ del ds_settlement , ds_settlement_pixel_area
263246
264247 ##
265248 # Bathymetry
@@ -272,6 +255,7 @@ def resample_inputs(
272255 resampled ["bathymetry" ] = ds_bathymetry .rio .reproject_match (
273256 reference_raster , resampling = Resampling .average
274257 )
258+ del ds_bathymetry
275259
276260 ##
277261 # Protected areas
@@ -287,14 +271,42 @@ def resample_inputs(
287271 protected_areas .geometry , protected_areas .crs
288272 )
289273 resampled ["protected" ] = resampled ["protected" ].fillna (0 )
274+ del protected_areas
290275
291- compression = {
276+ netcdf4_encoding = {
292277 var : {"zlib" : True , "complevel" : 1 }
293278 for var in resampled .data_vars
294279 if var not in ["spatial_ref" , "band" ]
295280 }
281+ for v in ["regions_land" , "regions_maritime" ]:
282+ netcdf4_encoding [v ]["dtype" ] = "int8"
283+ netcdf4_encoding [v ]["scale_factor" ] = 1
284+ netcdf4_encoding [v ]["add_offset" ] = 0
285+ netcdf4_encoding [v ]["_FillValue" ] = - 128
286+
287+ print ("Saving result to output path:" , output_path )
288+ resampled .to_netcdf (output_path , encoding = netcdf4_encoding )
289+
290+ print ("Saving image to plot path:" , plot_path )
291+ # If needed, resample `resampled` to fit within a maximum of `max_pixels` pixels
292+ max_pixels = 1000000
293+ total_pixels = resampled .sizes ["y" ] * resampled .sizes ["x" ]
294+ if total_pixels > max_pixels :
295+ # Calculate the new resolution to fit within the max_pixels limit
296+ resolution_multiplier = 1 / math .sqrt (total_pixels / max_pixels )
297+ new_y_size = int (resampled .sizes ["y" ] * resolution_multiplier )
298+ new_x_size = int (resampled .sizes ["x" ] * resolution_multiplier )
299+ print (
300+ f"Resampling old size { resampled .sizes ['y' ]} x { resampled .sizes ['x' ]} "
301+ f"to new size: { new_y_size } x { new_x_size } "
302+ f"to fit within { max_pixels } pixels."
303+ )
296304
297- resampled .to_netcdf (output_path , encoding = compression )
305+ resampled = resampled .coarsen (
306+ x = round (resampled .sizes ["x" ] / new_x_size ),
307+ y = round (resampled .sizes ["y" ] / new_y_size ),
308+ boundary = "trim" ,
309+ ).mean ()
298310
299311 script_utils .plot_all_dataset_variables (resampled , ncols = 3 , savefig = plot_path )
300312
0 commit comments