|
| 1 | +import cgi |
1 | 2 | import datetime
|
2 | 3 | import glob
|
3 | 4 | import os
|
| 5 | +import requests |
4 | 6 | import warnings
|
5 | 7 |
|
| 8 | +from xml.etree import ElementTree |
6 | 9 |
|
7 | 10 | # date format used in file names
|
8 | 11 | FMT = "%Y%m%dT%H%M%S"
|
9 | 12 |
|
| 13 | +# Scihub guest credential |
| 14 | +scihub_user = 'gnssguest' |
| 15 | +scihub_password = 'gnssguest' |
| 16 | + |
| 17 | + |
| 18 | +def download_orbit(safe_file, orbit_dir): |
| 19 | + ''' |
| 20 | + Download orbits for S1-A/B SAFE "safe_file" |
| 21 | +
|
| 22 | + Parameters |
| 23 | + ---------- |
| 24 | + safe_file: str |
| 25 | + File path to SAFE file for which download the orbits |
| 26 | + orbit_dir: str |
| 27 | + File path to directory where to store downloaded orbits |
| 28 | + ''' |
| 29 | + |
| 30 | + # Create output directory & check internet connection |
| 31 | + os.makedirs(orbit_dir, exist_ok=True) |
| 32 | + check_internet_connection() |
| 33 | + |
| 34 | + # Parse info from SAFE file name |
| 35 | + sensor_id, _, start_time, end_time, _ = parse_safe_filename(safe_file) |
| 36 | + |
| 37 | + # Find precise orbit first |
| 38 | + orbit_dict = get_orbit_dict(sensor_id, start_time, |
| 39 | + end_time, 'AUX_POEORB') |
| 40 | + # If orbit dict is empty, find restituted orbits |
| 41 | + if orbit_dict is None: |
| 42 | + orbit_dict = get_orbit_dict(sensor_id, start_time, |
| 43 | + end_time, 'AUX_RESORB') |
| 44 | + # Download orbit file |
| 45 | + filename = os.path.join(orbit_dir, orbit_dict["orbit_name"]) |
| 46 | + if not os.path.exists(filename): |
| 47 | + download_orbit_file(orbit_dir, orbit_dict['orbit_url']) |
| 48 | + |
| 49 | + |
| 50 | +def check_internet_connection(): |
| 51 | + ''' |
| 52 | + Check connection availability |
| 53 | + ''' |
| 54 | + url = "http://google.com" |
| 55 | + try: |
| 56 | + requests.get(url, timeout=10) |
| 57 | + except (requests.ConnectionError, requests.Timeout) as exception: |
| 58 | + raise ConnectionError(f'Unable to reach {url}: {exception}') |
| 59 | + |
| 60 | + |
| 61 | +def parse_safe_filename(safe_filename): |
| 62 | + ''' |
| 63 | + Extract info from S1-A/B SAFE filename |
| 64 | + SAFE filename structure: S1A_IW_SLC__1SDV_20150224T114043_20150224T114111_004764_005E86_AD02.SAFE |
| 65 | + Parameters |
| 66 | + ----------- |
| 67 | + safe_filename: string |
| 68 | + Path to S1-A/B SAFE file |
| 69 | +
|
| 70 | + Returns |
| 71 | + ------- |
| 72 | + List of [sensor_id, mode_id, start_datetime, |
| 73 | + end_datetime, abs_orbit_num] |
| 74 | + sensor_id: sensor identifier (S1A or S1B) |
| 75 | + mode_id: mode/beam (e.g. IW) |
| 76 | + start_datetime: acquisition start datetime |
| 77 | + stop_datetime: acquisition stop datetime |
| 78 | + abs_orbit_num: absolute orbit number |
| 79 | +
|
| 80 | + Examples |
| 81 | + --------- |
| 82 | + parse_safe_filename('S1A_IW_SLC__1SDV_20150224T114043_20150224T114111_004764_005E86_AD02.SAFE') |
| 83 | + returns |
| 84 | + ['A', 'IW', datetime.datetime(2015, 2, 24, 11, 40, 43),\ |
| 85 | + datetime.datetime(2015, 2, 24, 11, 41, 11), 4764] |
| 86 | + ''' |
| 87 | + |
| 88 | + safe_name = os.path.basename(safe_filename) |
| 89 | + sensor_id = safe_name[2] |
| 90 | + sensor_mode = safe_name[4:6] |
| 91 | + start_datetime = datetime.datetime.strptime(safe_name[17:32], |
| 92 | + FMT) |
| 93 | + end_datetime = datetime.datetime.strptime(safe_name[33:48], |
| 94 | + FMT) |
| 95 | + abs_orb_num = int(safe_name[49:55]) |
| 96 | + |
| 97 | + return [sensor_id, sensor_mode, start_datetime, end_datetime, abs_orb_num] |
| 98 | + |
| 99 | + |
| 100 | +def get_orbit_dict(sensor_id, start_time, end_time, orbit_type): |
| 101 | + ''' |
| 102 | + Query Copernicus GNSS API to find latest orbit file |
| 103 | + Parameters |
| 104 | + ---------- |
| 105 | + sensor_id: str |
| 106 | + Sentinel satellite identifier ('A' or 'B') |
| 107 | + start_time: datetime object |
| 108 | + Sentinel start acquisition time |
| 109 | + end_time: datetime object |
| 110 | + Sentinel end acquisition time |
| 111 | + orbit_type: str |
| 112 | + Type of orbit to download (AUX_POEORB: precise, AUX_RESORB: restituted) |
| 113 | +
|
| 114 | + Returns |
| 115 | + ------- |
| 116 | + orbit_dict: dict |
| 117 | + Python dictionary with [orbit_name, orbit_type, download_url] |
| 118 | + ''' |
| 119 | + # Required for orbit download |
| 120 | + scihub_url = 'https://scihub.copernicus.eu/gnss/odata/v1/Products' |
| 121 | + # Namespaces of the XML file returned by the S1 query. Will they change it? |
| 122 | + m_url = '{http://schemas.microsoft.com/ado/2007/08/dataservices/metadata}' |
| 123 | + d_url = '{http://schemas.microsoft.com/ado/2007/08/dataservices}' |
| 124 | + |
| 125 | + # Check if correct orbit_type |
| 126 | + if orbit_type not in ['AUX_POEORB', 'AUX_RESORB']: |
| 127 | + err_msg = f'{orbit_type} not a valid orbit type' |
| 128 | + raise ValueError(err_msg) |
| 129 | + |
| 130 | + # Add a 30 min margin to start_time and end_time |
| 131 | + pad_30_min = datetime.timedelta(hours=0.5) |
| 132 | + pad_start_time = start_time - pad_30_min |
| 133 | + pad_end_time = end_time + pad_30_min |
| 134 | + new_start_time = pad_start_time.strftime('%Y-%m-%dT%H:%M:%S') |
| 135 | + new_end_time = pad_end_time.strftime('%Y-%m-%dT%H:%M:%S') |
| 136 | + query_string = f"startswith(Name,'S1{sensor_id}') and substringof('{orbit_type}',Name) " \ |
| 137 | + f"and ContentDate/Start lt datetime'{new_start_time}' and ContentDate/End gt datetime'{new_end_time}'" |
| 138 | + query_params = {'$top': 1, '$orderby': 'ContentDate/Start asc', |
| 139 | + '$filter': query_string} |
| 140 | + query_response = requests.get(url=scihub_url, params=query_params, |
| 141 | + auth=(scihub_user, scihub_password)) |
| 142 | + # Parse XML tree from query response |
| 143 | + xml_tree = ElementTree.fromstring(query_response.content) |
| 144 | + # Extract w3.org URL |
| 145 | + w3_url = xml_tree.tag.split('feed')[0] |
| 146 | + |
| 147 | + # Extract orbit's name, id, url |
| 148 | + orbit_id = xml_tree.findtext( |
| 149 | + f'.//{w3_url}entry/{m_url}properties/{d_url}Id') |
| 150 | + orbit_url = f"{scihub_url}('{orbit_id}')/$value" |
| 151 | + orbit_name = xml_tree.findtext(f'./{w3_url}entry/{w3_url}title') |
| 152 | + |
| 153 | + if orbit_id is not None: |
| 154 | + orbit_dict = {'orbit_name': orbit_name, 'orbit_type': orbit_type, |
| 155 | + 'orbit_url': orbit_url} |
| 156 | + else: |
| 157 | + orbit_dict = None |
| 158 | + return orbit_dict |
| 159 | + |
| 160 | + |
| 161 | +def download_orbit_file(output_folder, orbit_url): |
| 162 | + ''' |
| 163 | + Download S1-A/B orbits |
| 164 | + Parameters |
| 165 | + ---------- |
| 166 | + output_folder: str |
| 167 | + Path to directory where to store orbits |
| 168 | + orbit_url: str |
| 169 | + Remote url of orbit file to download |
| 170 | + ''' |
| 171 | + |
| 172 | + response = requests.get(url=orbit_url, auth=(scihub_user, scihub_password)) |
| 173 | + # Get header and find filename |
| 174 | + header = response.headers['content-disposition'] |
| 175 | + _, header_params = cgi.parse_header(header) |
| 176 | + # construct orbit filename |
| 177 | + orbit_filename = os.path.join(output_folder, header_params['filename']) |
| 178 | + # Save orbits |
| 179 | + open(orbit_filename, 'wb').write(response.content) |
| 180 | + |
| 181 | + |
10 | 182 | def get_file_name_tokens(zip_path: str) -> [str, list[datetime.datetime]]:
|
11 | 183 | '''Extract swath platform ID and start/stop times from SAFE zip file path.
|
12 | 184 |
|
13 |
| - Parameters: |
14 |
| - ----------- |
| 185 | + Parameters |
| 186 | + ---------- |
15 | 187 | zip_path: list[str]
|
16 | 188 | List containing orbit path strings. Orbit files required to adhere to
|
17 | 189 | naming convention found here:
|
18 | 190 | https://s1qc.asf.alaska.edu/aux_poeorb/
|
19 | 191 |
|
20 |
| - Returns: |
21 |
| - -------- |
| 192 | + Returns |
| 193 | + ------- |
22 | 194 | platform_id: ('S1A', 'S1B')
|
23 | 195 | orbit_path : str
|
24 | 196 | Path the orbit file.
|
25 | 197 | t_swath_start_stop: list[datetime.datetime]
|
26 | 198 | Swath start/stop times
|
27 | 199 | '''
|
28 |
| - file_name_tokens = os.path.basename(zip_path).split('_') |
29 |
| - |
30 |
| - # extract and check platform ID |
31 |
| - platform_id = file_name_tokens[0] |
32 |
| - if platform_id not in ['S1A', 'S1B']: |
33 |
| - err_str = f'{platform_id} not S1A nor S1B' |
34 |
| - ValueError(err_str) |
35 |
| - |
36 |
| - # extract start/stop time as a list[datetime.datetime]: [t_start, t_stop] |
37 |
| - t_swath_start_stop = [datetime.datetime.strptime(t, FMT) |
38 |
| - for t in file_name_tokens[5:7]] |
39 |
| - |
40 |
| - return platform_id, t_swath_start_stop |
| 200 | + platform_id, _, start_time, end_time, _ = parse_safe_filename(zip_path) |
| 201 | + return platform_id,[start_time, end_time] |
41 | 202 |
|
42 | 203 |
|
43 |
| -# lambda to check if file exisits if desired sat_id in basename |
| 204 | +# lambda to check if file exists if desired sat_id in basename |
44 | 205 | item_valid = lambda item, sat_id: os.path.isfile(item) and sat_id in os.path.basename(item)
|
45 | 206 |
|
46 | 207 |
|
|
0 commit comments