77
88import requests
99import yaml
10- from shapely .geometry import MultiPolygon , Polygon , shape
10+ from shapely .geometry import MultiPolygon , Polygon , box , shape
1111
1212from hyp3_api import CMR_URL , multi_burst_validation
1313from hyp3_api .util import get_granules
@@ -22,11 +22,7 @@ class InternalValidationError(Exception):
2222 pass
2323
2424
25- class GranuleValidationError (Exception ):
26- pass
27-
28-
29- class BoundsValidationError (Exception ):
25+ class ValidationError (Exception ):
3026 pass
3127
3228
@@ -84,13 +80,13 @@ def _make_sure_granules_exist(granules: Iterable[str], granule_metadata: list[di
8480 not_found_granules = set (granules ) - set (found_granules )
8581 not_found_granules = {granule for granule in not_found_granules if not _is_third_party_granule (granule )}
8682 if not_found_granules :
87- raise GranuleValidationError (f'Some requested scenes could not be found: { ", " .join (not_found_granules )} ' )
83+ raise ValidationError (f'Some requested scenes could not be found: { ", " .join (not_found_granules )} ' )
8884
8985
9086def check_dem_coverage (_ , granule_metadata : list [dict ]) -> None :
9187 bad_granules = [g ['name' ] for g in granule_metadata if not _has_sufficient_coverage (g ['polygon' ])]
9288 if bad_granules :
93- raise GranuleValidationError (f'Some requested scenes do not have DEM coverage: { ", " .join (bad_granules )} ' )
89+ raise ValidationError (f'Some requested scenes do not have DEM coverage: { ", " .join (bad_granules )} ' )
9490
9591
9692def check_multi_burst_pairs (job : dict , _ ) -> None :
@@ -114,17 +110,17 @@ def check_single_burst_pair(job: dict, _) -> None:
114110 granule2_id = '_' .join (granule2 .split ('_' )[1 :3 ])
115111
116112 if granule1_id != granule2_id :
117- raise GranuleValidationError (f'Burst IDs do not match for { granule1 } and { granule2 } .' )
113+ raise ValidationError (f'Burst IDs do not match for { granule1 } and { granule2 } .' )
118114
119115 granule1_pol = granule1 .split ('_' )[4 ]
120116 granule2_pol = granule2 .split ('_' )[4 ]
121117
122118 if granule1_pol != granule2_pol :
123- raise GranuleValidationError (
119+ raise ValidationError (
124120 f'The requested scenes need to have the same polarization, got: { ", " .join ([granule1_pol , granule2_pol ])} '
125121 )
126122 if granule1_pol not in ['VV' , 'HH' ]:
127- raise GranuleValidationError (f'Only VV and HH polarizations are currently supported, got: { granule1_pol } ' )
123+ raise ValidationError (f'Only VV and HH polarizations are currently supported, got: { granule1_pol } ' )
128124
129125
130126def check_not_antimeridian (_ , granule_metadata : list [dict ]) -> None :
@@ -135,7 +131,7 @@ def check_not_antimeridian(_, granule_metadata: list[dict]) -> None:
135131 f'Granule { granule ["name" ]} crosses the antimeridian.'
136132 ' Processing across the antimeridian is not currently supported.'
137133 )
138- raise GranuleValidationError (msg )
134+ raise ValidationError (msg )
139135
140136
141137def _format_points (point_string : str ) -> list :
@@ -155,10 +151,10 @@ def _get_multipolygon_from_geojson(input_file: str) -> MultiPolygon:
155151def check_bounds_formatting (job : dict , _ ) -> None :
156152 bounds = job ['job_parameters' ]['bounds' ]
157153 if bounds == [0.0 , 0.0 , 0.0 , 0.0 ]:
158- raise BoundsValidationError ('Invalid bounds. Bounds cannot be [0, 0, 0, 0].' )
154+ raise ValidationError ('Invalid bounds. Bounds cannot be [0, 0, 0, 0].' )
159155
160156 if bounds [0 ] >= bounds [2 ] or bounds [1 ] >= bounds [3 ]:
161- raise BoundsValidationError (
157+ raise ValidationError (
162158 'Invalid order for bounds. Bounds should be ordered [min lon, min lat, max lon, max lat].'
163159 )
164160
@@ -169,15 +165,15 @@ def bad_lon(lon: float) -> bool:
169165 return lon > 180 or lon < - 180
170166
171167 if any ([bad_lon (bounds [0 ]), bad_lon (bounds [2 ]), bad_lat (bounds [1 ]), bad_lat (bounds [3 ])]):
172- raise BoundsValidationError (
168+ raise ValidationError (
173169 'Invalid lon/lat value(s) in bounds. Bounds should be ordered [min lon, min lat, max lon, max lat].'
174170 )
175171
176172
177173def check_granules_intersecting_bounds (job : dict , granule_metadata : list [dict ]) -> None :
178174 bounds = job ['job_parameters' ]['bounds' ]
179175 if bounds == [0.0 , 0.0 , 0.0 , 0.0 ]:
180- raise BoundsValidationError ('Invalid bounds. Bounds cannot be [0, 0, 0, 0].' )
176+ raise ValidationError ('Invalid bounds. Bounds cannot be [0, 0, 0, 0].' )
181177
182178 bounds = Polygon .from_bounds (* bounds )
183179 bad_granules = []
@@ -186,7 +182,7 @@ def check_granules_intersecting_bounds(job: dict, granule_metadata: list[dict])
186182 if not bbox .intersection (bounds ):
187183 bad_granules .append (granule ['name' ])
188184 if bad_granules :
189- raise GranuleValidationError (f'The following granules do not intersect the provided bounds: { bad_granules } .' )
185+ raise ValidationError (f'The following granules do not intersect the provided bounds: { bad_granules } .' )
190186
191187
192188def check_same_relative_orbits (_ , granule_metadata : list [dict ]) -> None :
@@ -200,7 +196,7 @@ def check_same_relative_orbits(_, granule_metadata: list[dict]) -> None:
200196 if not previous_relative_orbit :
201197 previous_relative_orbit = relative_orbit
202198 if relative_orbit != previous_relative_orbit :
203- raise GranuleValidationError (
199+ raise ValidationError (
204200 f'Relative orbit number for { granule ["name" ]} does not match that of the previous granules: '
205201 f'{ relative_orbit } is not { previous_relative_orbit } .'
206202 )
@@ -212,32 +208,43 @@ def check_bounding_box_size(job: dict, _, max_bounds_area: float = 4.5) -> None:
212208 bounds_area = (bounds [3 ] - bounds [1 ]) * (bounds [2 ] - bounds [0 ])
213209
214210 if bounds_area > max_bounds_area :
215- raise BoundsValidationError (
211+ raise ValidationError (
216212 f'Bounds must be smaller than { max_bounds_area } degrees squared. Box provided was { bounds_area :.2f} '
217213 )
218214
219215
220- def _has_opera_rtc_s1_static_coverage (granule_name : str ) -> bool :
221- burst_number , swath = granule_name .split ('_' )[1 :3 ]
222- params = {
223- 'short_name' : 'OPERA_L2_RTC-S1-STATIC_V1' ,
224- 'granule_ur' : f'OPERA_L2_RTC-S1-STATIC_T*-{ burst_number } -{ swath } _*' ,
225- 'options[granule_ur][pattern]' : 'true' ,
226- }
227- response = requests .get (CMR_URL , params = params )
228- response .raise_for_status ()
229- return bool (response .json ()['feed' ]['entry' ])
216+ def check_opera_rtc_s1_bounds (_ , granule_metadata : list [dict ]) -> None :
217+ opera_rtc_s1_bounds = box (- 180 , - 60 , 180 , 90 )
218+ for granule in granule_metadata :
219+ if not granule ['polygon' ].intersects (opera_rtc_s1_bounds ):
220+ raise ValidationError (
221+ f'Granule { granule ["name" ]} is south of -60 degrees latitude and outside the valid processing extent '
222+ f'for OPERA RTC-S1 products.'
223+ )
230224
231225
232- def check_opera_rtc_s1_static_coverage (job : dict , _ ) -> None :
233- granules = job ['job_parameters' ]['granules' ]
234- if len (granules ) != 1 :
235- raise InternalValidationError (f'Expected 1 granule, got { granules } ' )
226+ def check_aria_s1_gunw_dates (job : dict , _ ) -> None :
227+ def format_date (key : str ) -> date :
228+ return date .fromisoformat (job ['job_parameters' ][key ])
236229
237- granule = granules [0 ]
238- if not _has_opera_rtc_s1_static_coverage (granule ):
239- raise GranuleValidationError (
240- f'Granule { granule } is outside the valid processing extent for OPERA RTC-S1 products.'
230+ reference , secondary = format_date ('reference_date' ), format_date ('secondary_date' )
231+ _validate_date_during_s1 ('reference_date' , reference )
232+ _validate_date_during_s1 ('secondary_date' , secondary )
233+
234+ if secondary >= reference :
235+ raise ValidationError ('secondary date must be earlier than reference date.' )
236+
237+
238+ def _validate_date_during_s1 (date_name : str , date_value : date ) -> None :
239+ s1_start_date = date (2014 , 6 , 15 )
240+ todays_date = date .today ()
241+
242+ if date_value > todays_date :
243+ raise ValidationError (f'"{ date_name } " is { date_value } which is a date in the future.' )
244+
245+ if date_value < s1_start_date :
246+ raise ValidationError (
247+ f'"{ date_name } " is { date_value } which is before the start of the sentinel 1 mission ({ s1_start_date } ).'
241248 )
242249
243250
@@ -252,7 +259,7 @@ def check_opera_rtc_s1_date(job: dict, _) -> None:
252259 # Disallow IPF version < 002.70 according to the dates given at https://sar-mpc.eu/processor/ipf/
253260 # Also see https://github.com/ASFHyP3/hyp3/issues/2739
254261 if granule_date < date (2016 , 4 , 14 ):
255- raise GranuleValidationError (
262+ raise ValidationError (
256263 f'Granule { granule } was acquired before 2016-04-14 '
257264 'and is not available for On-Demand OPERA RTC-S1 processing.'
258265 )
@@ -263,7 +270,7 @@ def check_opera_rtc_s1_date(job: dict, _) -> None:
263270
264271 end_date = datetime .strptime (end_date_str , '%Y-%m-%d' ).date ()
265272 if granule_date >= end_date :
266- raise GranuleValidationError (
273+ raise ValidationError (
267274 f'Granule { granule } was acquired on or after { end_date_str } '
268275 'and is not available for On-Demand OPERA RTC-S1 processing. '
269276 'You can download the product from the ASF DAAC archive.'
@@ -272,8 +279,13 @@ def check_opera_rtc_s1_date(job: dict, _) -> None:
272279
273280def validate_jobs (jobs : list [dict ]) -> None :
274281 granules = get_granules (jobs )
275- granule_metadata = _get_cmr_metadata (granules )
276- _make_sure_granules_exist (granules , granule_metadata )
282+
283+ if granules :
284+ granule_metadata = _get_cmr_metadata (granules )
285+ _make_sure_granules_exist (granules , granule_metadata )
286+ else :
287+ granule_metadata = []
288+
277289 for job in jobs :
278290 for validator_name in JOB_VALIDATION_MAP [job ['job_type' ]]:
279291 job_granule_metadata = [granule for granule in granule_metadata if granule ['name' ] in get_granules ([job ])]
0 commit comments