11from collections import namedtuple
22from pathlib import Path
3- from typing import Iterable , NamedTuple , Self , TypeVar
3+ from typing import Iterable , Self
44import warnings
5- from numpy .lib .stride_tricks import as_strided
65from numpy .typing import NDArray
76import xarray as xr
87import re
98import numpy as np
109from scipy import stats
1110import shapely
12- from scipy import ndimage
1311import cv2
12+ import rasterio
13+ from rasterio .transform import Affine
1414
15+ from efast .constants import S3L2SYNClassificationAerosolFlags , S3L2SYNCloudFlags
16+
17+
18+ FILL_VALUE = - 10000 # TODO move
19+
20+ CLOUD_FLAGS_COMBINED = (
21+ S3L2SYNCloudFlags .CLOUD
22+ | S3L2SYNCloudFlags .CLOUD_AMBIGUOUS
23+ | S3L2SYNCloudFlags .CLOUD_MARGIN
24+ )
1525
1626def binning_s3_py (
1727 download_dir ,
1828 binning_dir ,
1929 footprint ,
2030 s3_bands = ["SDR_Oa04" , "SDR_Oa06" , "SDR_Oa08" , "SDR_Oa17" ],
21- max_zenith_angle = 30 ,
22- crs = "EPSG:4326" ,
31+ ** kwargs
2332):
2433 """
2534 TODO: "binning" might be a misnomer, as the function does more than just binning
2635 """
36+ band_names = [* s3_bands , "CLOUD_flags" , "SYN_flags" ]
37+ sen3_paths = list (download_dir .glob ("*.SEN3" ))
38+ bbox = BBox .from_wkt (footprint )
39+ grid = create_geogrid (bbox , num_rows = 66792 )
40+ pixel_size = grid .lat [1 ] - grid .lat [0 ]
41+ transform = Affine .translation (grid .lon [0 ], grid .lat [- 1 ]) * Affine .scale (pixel_size , pixel_size )
42+
43+ for i , sen3_path in enumerate (sen3_paths ):
44+ output_path = binning_dir / (sen3_path .stem + ".tif" )
45+
46+ prod = SynProduct (sen3_path , band_names = band_names )
47+ ds = prod .read_bands ()
48+
49+ cloud_no_land_mask = ((ds ["CLOUD_flags" ] & CLOUD_FLAGS_COMBINED .value ) != 0 ) & (ds ["SYN_flags" ] & S3L2SYNClassificationAerosolFlags .SYN_land .value > 0 )
50+
51+ for reflectance_band in s3_bands :
52+ band_values = ds [reflectance_band ].data
53+ band_values [cloud_no_land_mask ] = - 10000
54+ ds [reflectance_band ] = (["lat" , "lon" ], band_values )
55+
56+
57+ binned = bin_to_grid_numpy (ds , s3_bands , grid , super_sampling = 2 )
58+
59+ binned [binned < 0 ] = np .nan
2760
28- # read required bands of sentinel-3 product
29- # reproject to EPSG 4326 (~300m grid), this step is likely unnecessary
30- # bin to SEA_grid
31- # reproject to EPSG 4326 (66xxx slices)
3261
33- pass
62+ with rasterio .open (output_path , "w" , driver = "GTiff" , height = binned .shape [1 ], width = binned .shape [2 ], count = len (s3_bands ), dtype = binned .dtype , crs = "+proj=latlong" , transform = transform , nodata = np .nan ) as ds :
63+ ds .write (binned [:, ::- 1 , :])
3464
3565
3666def get_reflectance_filename (index : int ):
@@ -220,15 +250,14 @@ def bin_to_grid_numpy(ds: xr.Dataset, bands: Iterable[str], grid: Grid,*, super_
220250 bin_idx [(bin_idx_row < 0 ) | (bin_idx_row > height ) | (bin_idx_col < 0 ) | (bin_idx_col > width )] = - 1
221251
222252 counts , _ = np .histogram (bin_idx , width * height , range = (0 , width * height ))
223-
253+
224254 binned = []
225255 means = None
226256 sampled_data = None if super_sampling == 1 else np .zeros ((lat2d .shape [0 ] * super_sampling , lat2d .shape [1 ] * super_sampling ), dtype = np .int16 )
227257
228- FILL_VALUE = - 10000 # TODO move
229258 for band in bands :
230259 data = ds [band ].data
231- data [data == FILL_VALUE ] = 0
260+ # data[data == FILL_VALUE] = 0
232261 if super_sampling != 1 :
233262 super_sample (data , super_sampling , out = sampled_data )
234263 else :
@@ -253,7 +282,6 @@ def create_geogrid(bbox: BBox, num_rows: int = 66792):
253282 # the last bin is between lon[-1] and lon[0] (antimeridian)
254283 lon = np .linspace (0 , 360 , num = num_rows * 2 + 1 , endpoint = True ) - 180
255284 lon = lon [1 :]
256- # TODO return type with names to not confuse lat/lon
257285
258286 # one lat bound before first bound that is larger than the bounding box min
259287 # TODO I can calculate lat_idx_min (and the others) directly
0 commit comments