44import os
55import math
66import pathlib
7+ import numpy as np
78
89
910def parse_int (s : str ) -> int :
@@ -14,6 +15,35 @@ def parse_int(s: str) -> int:
1415 return int (s )
1516
1617
18+
19+ def calc_distance (latitude , longitude , altitude = 0.0 ):
20+ """Lat/Lon to distance from earth center"""
21+ DTOR = math .pi / 180.0
22+ RTOD = 180 / math .pi
23+ A = 6378137.0
24+ B = 6356752.3142451794975639665996337
25+ FLAT_EARTH_COEF = 1.0 / ((A - B ) / A )
26+ E2 = 2.0 / FLAT_EARTH_COEF - 1.0 / (FLAT_EARTH_COEF * FLAT_EARTH_COEF )
27+ EP2 = E2 / (1 - E2 )
28+
29+ lat = latitude * DTOR
30+ lon = longitude * DTOR
31+
32+ sin_lat = math .sin (lat )
33+ cos_lat = math .cos (lat )
34+
35+ N = (A / math .sqrt (1.0 - E2 * sin_lat * sin_lat ))
36+ NcosLat = (N + altitude ) * cos_lat
37+
38+ sin_lon = math .sin (lon )
39+ cos_lon = math .cos (lon )
40+
41+ x_pos = NcosLat * cos_lon
42+ y_pos = NcosLat * sin_lon
43+ z_pos = (N + altitude - E2 * N ) * sin_lat
44+ return math .sqrt (x_pos ** 2 + y_pos ** 2 + z_pos ** 2 )
45+
46+
1747class EnvisatADS :
1848 """Class representing an Envisat Annotation Data Set (ADS) descriptor."""
1949
@@ -32,7 +62,7 @@ def __str__(self) -> str:
3262 return "Envisat ADS: \" {}\" {} {} {}" .format (self .name , self .offset , self .size , self .num )
3363
3464
35- def parse_direct (path : str , gdal_metadata : dict [ str , Any ] ) -> dict [str , Any ]:
65+ def parse_direct (path : str , gdal_metadata ) -> dict [str , Any ]:
3666 """
3767 Parse an Envisat product file and extract relevant metadata fields.
3868
@@ -42,7 +72,8 @@ def parse_direct(path: str, gdal_metadata: dict[str, Any]) -> dict[str, Any]:
4272
4373 returns: Dictionary containing extracted metadata fields.
4474 """
45- metadata : dict [str , Any ] = {}
75+ metadata = {}
76+ file_buffer = None
4677 with open (path , "rb" ) as fp :
4778 file_buffer = fp .read ()
4879
@@ -59,145 +90,161 @@ def parse_direct(path: str, gdal_metadata: dict[str, Any]) -> dict[str, Any]:
5990
6091 for i in range (dsd_num ):
6192 ads = EnvisatADS (dsd_buf [i * dsd_size :(i + 1 ) * dsd_size ])
93+ #print(ads.name)
6294 if ads .name == "GEOLOCATION GRID ADS" :
63- extract_geolocation_metadata (ads , file_buffer , metadata )
95+ rec_size = 521
96+ assert ((ads .size // ads .num ) == rec_size )
97+ geoloc_buf = file_buffer [ads .offset :ads .offset + ads .size ]
98+ middle_idx = ads .num // 2
99+ geoloc_record = geoloc_buf [middle_idx * rec_size : (middle_idx + 1 ) * rec_size ]
100+ # Geolocation Grid ADSRs header
101+ header_size = 12 + 1 + 4 + 4 + 4
102+
103+ lats_buffer = geoloc_buf [header_size + 11 * 4 * 3 : header_size + 11 * 4 * 4 ]
104+ lons_buffer = geoloc_buf [header_size + 11 * 4 * 4 : header_size + 11 * 4 * 5 ]
105+
106+
107+ lats = list (struct .unpack (">11i" , lats_buffer ))
108+ lons = list (struct .unpack (">11i" , lons_buffer ))
109+ lats = [e * 1e-6 for e in lats ]
110+ lons = [e * 1e-6 for e in lons ]
111+
112+
113+ # tiepoints, 11 of big endian floats for each of the following:
114+ # samp numbers, slant range times, angles, lats, longs
115+ block_size = 11 * 4
116+ slant_time_offset = header_size + 1 * block_size
117+ incidence_angle_offset = header_size + 2 * block_size
118+ # adjust to middle of 11
119+ incidence_angle_offset += 5 * 4
120+
121+ slant_time_first = struct .unpack (">f" , geoloc_record [slant_time_offset :slant_time_offset + 4 ])[0 ]
122+ incidence_angle_middle = \
123+ struct .unpack (">f" , geoloc_record [incidence_angle_offset :incidence_angle_offset + 4 ])[0 ]
124+
125+
64126
127+ metadata ["slant_time_first" ] = slant_time_first
128+ metadata ["incidence_angle_center" ] = incidence_angle_middle
129+
130+
131+
132+ ext_cal_buf = None
65133 if ads .name == "EXTERNAL CALIBRATION" :
66- extract_external_calibration_metadata (ads , gdal_metadata , metadata )
67134
68- if ads .name == "SR GR ADS" and ads .size > 0 :
69- srgr_coeffs = extract_srgr_coeffs (ads , file_buffer )
135+ auxfolder = pathlib .Path (os .path .abspath (__file__ )).parent
136+ prod_name = gdal_metadata ["product" ]
137+ auxfolder /= "aux"
138+ if ".N1" in prod_name :
139+ auxfolder /= "ASAR_Auxiliary_Files"
140+ auxfolder /= "ASA_XCA_AX"
141+ elif ".E1" in prod_name :
142+ auxfolder /= "ERS1"
143+ elif ".E2" in prod_name :
144+ auxfolder /= "ERS2"
145+
146+ for p in os .scandir (auxfolder ):
147+ if ads .filename == p .name :
148+ with open (p .path , "rb" ) as fp :
149+ ext_cal_buf = fp .read ()
150+ pol = gdal_metadata ["mds1_tx_rx_polar" ]
151+ swath = gdal_metadata ["swath" ]
152+ swath_offset = ord (swath [2 ]) - ord ("1" )
153+ pol_offset = {"H/H" : 0 , "V/V" :1 , "H/V" : 2 , "V/H" :3 }[pol ]
154+
155+ # Envisat_Product_Spec_Vol8.pdf
156+ # 8.6.2 External Calibration Data
157+ offset = 1247 + 378
158+
159+ offset += 12
160+ offset += 4
161+ offset += 26 * 28 + 4 * 4
162+
163+ mid_angles = struct .unpack (">7f" , ext_cal_buf [offset :offset + 4 * 7 ])
164+
165+ offset += 8 * 4
166+
167+ offset += 804 * 4 * swath_offset
168+ offset += 201 * 4 * pol_offset
169+
170+ antenna_gains = struct .unpack (">201f" , ext_cal_buf [offset :offset + 4 * 201 ])
171+ metadata ["antenna_ref_elev_angle" ] = mid_angles [swath_offset ]
172+
173+ if ads .name == "SR GR ADS" and ads .size > 0 :
174+ srgr_buf = file_buffer [ads .offset :ads .offset + ads .size ]
175+
176+ r = struct .unpack (">ff5f" , srgr_buf [13 :41 ])
177+
178+
179+ srgr_coeffs = list (r [2 :])
70180 metadata ["srgr_coeffs" ] = srgr_coeffs
71181
72- # calculate spreading loss compensation
73- c = 299792458
74182
183+
184+
185+
186+
187+ # antenna gain
188+ c = 299792458
75189 n_samp = gdal_metadata ["line_length" ]
190+ R_first = c * slant_time_first * 1e-9 / 2
76191 range_spacing = c / (2 * gdal_metadata ["records" ]["main_processing_params" ]["range_samp_rate" ])
77192 range_ref = gdal_metadata ["records" ]["main_processing_params" ]["range_ref" ]
78- r_first = c * metadata [ " slant_time_first" ] * 1e-9 / 2
193+ R_first = c * slant_time_first * 1e-9 / 2
79194
80- spreading_loss = []
81- for n in range (n_samp ):
82- r = r_first + n * range_spacing
83- factor = math .sqrt ((range_ref / r ) ** 3 )
84- spreading_loss .append (1 / factor )
195+ osv = gdal_metadata ["records" ]["main_processing_params" ]["orbit_state_vectors" ]
85196
86- cal_factor = gdal_metadata ["records" ]["main_processing_params" ]["calibration_factors" ][0 ]["ext_cal_fact" ]
197+ sat_x = osv [0 ]["x_pos_1" ] * 1e-2
198+ sat_y = osv [0 ]["y_pos_1" ] * 1e-2
199+ sat_z = osv [0 ]["z_pos_1" ] * 1e-2
87200
88- metadata ["cal_factor" ] = cal_factor
89- metadata ["cal_vector" ] = spreading_loss
201+ gain_arr = []
202+ for n in range (n_samp ):
203+ # https://github.com/senbox-org/microwave-toolbox/blob/master/sar-op-calibration/src/main/java/eu/esa/sar/calibration/gpf/calibrators/ASARCalibrator.java
204+ R = R_first + n * range_spacing
205+ n_geogrid = len (lats )
206+ geo_interp_idx = (n / n_samp ) * (len (lats ) - 1 )
207+ idx = int (geo_interp_idx )
208+ fract = geo_interp_idx % 1
209+ #interpolate geogrid to match first line geogrid to first OSV
210+ lat = lats [idx ] + (lats [idx + 1 ] - lats [idx ]) * fract
211+ lon = lons [idx ] + (lons [idx + 1 ] - lons [idx ]) * fract
90212
91- return metadata
213+ sar_dis = math . sqrt ( sat_x ** 2 + sat_y ** 2 + sat_z ** 2 )
92214
215+ distance = calc_distance (lat , lon )
93216
94- def extract_geolocation_metadata ( ads : EnvisatADS , file_buffer : bytes , metadata : dict [ str , Any ]) -> None :
95- """
96- Extract geolocation metadata from the GEOLOCATION GRID ADS record.
217+ # elevation angle, cosine law from three sides, slant range, earth center to satellite, earth center to target
218+ angle_cos = ( R * R + sar_dis * sar_dis - distance * distance ) / ( 2 * R * sar_dis )
219+ elev_angle = np . rad2deg ( math . acos ( angle_cos ))
97220
98- Args:
99- ----
100- ads: EnvisatADS object containing ADS descriptor information.
101- file_buffer: The full file buffer as bytes.
102- metadata: Dictionary to store extracted metadata.
221+ # find the gain from LUT, with 201 gains against reference elevation angle with 0.05 degree steps
222+ elev_idx = int ((elev_angle - metadata ["antenna_ref_elev_angle" ]) / 0.05 )
223+ elev_idx += 100
224+ gain = 1.0
225+ if elev_idx >= 0 and elev_idx <= len (antenna_gains ):
226+ # dB -> linear
227+ gain = math .pow (10 , antenna_gains [elev_idx ] / 10 )
103228
104- Populates the metadata dictionary with:
105- - slant_time_first: First slant range time (float).
106- - incidence_angle_center: Incidence angle at the center (float).
107- """
108- rec_size = 521
109- assert ((ads .size // ads .num ) == rec_size )
110- geoloc_buf = file_buffer [ads .offset :ads .offset + ads .size ]
111- middle_idx = ads .num // 2
112- geoloc_record = geoloc_buf [middle_idx * rec_size : (middle_idx + 1 ) * rec_size ]
113- # Geolocation Grid ADSRs header
114- header_size = 12 + 1 + 4 + 4 + 4
115-
116- lats_buffer = geoloc_buf [header_size + 11 * 4 * 3 : header_size + 11 * 4 * 4 ]
117- lons_buffer = geoloc_buf [header_size + 11 * 4 * 4 : header_size + 11 * 4 * 5 ]
118-
119- lats = list (struct .unpack (">11i" , lats_buffer ))
120- lons = list (struct .unpack (">11i" , lons_buffer ))
121- lats = [e * 1e-6 for e in lats ]
122- lons = [e * 1e-6 for e in lons ]
123-
124- # tiepoints, 11 of big endian floats for each of the following:
125- # samp numbers, slant range times, angles, lats, longs
126- block_size = 11 * 4
127- slant_time_offset = header_size + 1 * block_size
128- incidence_angle_offset = header_size + 2 * block_size
129- # adjust to middle of 11
130- incidence_angle_offset += 5 * 4
131-
132- slant_time_first = struct .unpack (">f" , geoloc_record [slant_time_offset :slant_time_offset + 4 ])[0 ]
133- incidence_angle_middle = \
134- struct .unpack (">f" , geoloc_record [incidence_angle_offset :incidence_angle_offset + 4 ])[0 ]
135-
136- metadata ["slant_time_first" ] = slant_time_first
137- metadata ["incidence_angle_center" ] = incidence_angle_middle
138-
139-
140- def extract_external_calibration_metadata (ads : EnvisatADS , gdal_metadata : dict [str , Any ],
141- metadata : dict [str , Any ]) -> None :
142- """
143- Extract external calibration metadata from the EXTERNAL CALIBRATION ADS record.
229+ gain_arr .append (gain )
144230
145- Args:
146- ----
147- ads: EnvisatADS object containing ADS descriptor information.
148- gdal_metadata: Dictionary with GDAL metadata required for extraction.
149- metadata: Dictionary to store extracted metadata.
150-
151- Populates the metadata dictionary with:
152- - antenna_ref_elev_angle: Reference elevation angle for the antenna (float).
153- - antenna_elev_gains: List of antenna elevation gain values (tuple of 201 floats).
154- """
155- aux_folder = pathlib .Path (os .path .abspath (__file__ )).parent
156- aux_folder /= "aux"
157- aux_folder /= "ASAR_Auxiliary_Files"
158- aux_folder /= "ASA_XCA_AX"
159231
160- for p in os .scandir (aux_folder ):
161- if ads .filename == p .name :
162- with open (p .path , "rb" ) as fp :
163- ext_cal_buf = fp .read ()
164- pol = gdal_metadata ["mds1_tx_rx_polar" ]
165- swath = gdal_metadata ["swath" ]
166- swath_offset = ord (swath [2 ]) - ord ("1" )
167- pol_offset = {"H/H" : 0 , "V/V" : 1 , "H/V" : 2 , "V/H" : 3 }[pol ]
168232
169- # Envisat_Product_Spec_Vol8.pdf
170- # 8.6.2 External Calibration Data
171- offset = 1247 + 378
172233
173- offset += 12
174- offset += 4
175- offset += 26 * 28 + 4 * 4
176234
177- mid_angles = struct .unpack (">7f" , ext_cal_buf [offset :offset + 4 * 7 ])
178235
179- offset += 8 * 4
180-
181- offset += 804 * 4 * swath_offset
182- offset += 201 * 4 * pol_offset
236+ # calculate spreading loss compensation
183237
184- antenna_gains = struct .unpack (">201f" , ext_cal_buf [offset :offset + 4 * 201 ])
185- metadata ["antenna_ref_elev_angle" ] = mid_angles [swath_offset ]
186- metadata ["antenna_elev_gains" ] = antenna_gains
238+ spreading_loss = []
239+ for n in range (n_samp ):
240+ R = R_first + n * range_spacing
241+ factor = math .sqrt ((range_ref / R ) ** 3 )
242+ spreading_loss .append (1 / factor )
187243
244+ cal_factor = gdal_metadata ["records" ]["main_processing_params" ]["calibration_factors" ][0 ]["ext_cal_fact" ]
188245
189- def extract_srgr_coeffs (ads : EnvisatADS , file_buffer : bytes ) -> list [float ]:
190- """
191- Extract SRGR (Slant Range to Ground Range) coefficients from the SR GR ADS record.
246+ metadata ["cal_factor" ] = cal_factor
247+ metadata ["cal_vector" ] = np .array (spreading_loss ) * np .array (gain_arr )
192248
193- Args:
194- ----
195- ads: EnvisatADS object containing ADS descriptor information.
196- file_buffer: The full file buffer as bytes.
197249
198- Returns: List of SRGR coefficients (list of 5 floats).
199- """
200- srgr_buf = file_buffer [ads .offset :ads .offset + ads .size ]
201- r = struct .unpack (">ff5f" , srgr_buf [13 :41 ])
202- srgr_coeffs = list (r [2 :])
203- return srgr_coeffs
250+ return metadata
0 commit comments