Skip to content

Commit 75b30e9

Browse files
author
vbrancat
authored
Add orbit download functionality (#52)
* Add orbit download functionality * Move URL internal to get_orbit_dict * Improve variable clarity * Move main function to the top * Add check to avoid orbit download if exists * Reuse parse_filename in get_filename_tokens
1 parent e642bd6 commit 75b30e9

File tree

1 file changed

+179
-18
lines changed

1 file changed

+179
-18
lines changed

src/s1reader/s1_orbit.py

Lines changed: 179 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,207 @@
1+
import cgi
12
import datetime
23
import glob
34
import os
5+
import requests
46
import warnings
57

8+
from xml.etree import ElementTree
69

710
# date format used in file names
811
FMT = "%Y%m%dT%H%M%S"
912

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+
10182
def get_file_name_tokens(zip_path: str) -> [str, list[datetime.datetime]]:
11183
'''Extract swath platform ID and start/stop times from SAFE zip file path.
12184
13-
Parameters:
14-
-----------
185+
Parameters
186+
----------
15187
zip_path: list[str]
16188
List containing orbit path strings. Orbit files required to adhere to
17189
naming convention found here:
18190
https://s1qc.asf.alaska.edu/aux_poeorb/
19191
20-
Returns:
21-
--------
192+
Returns
193+
-------
22194
platform_id: ('S1A', 'S1B')
23195
orbit_path : str
24196
Path the orbit file.
25197
t_swath_start_stop: list[datetime.datetime]
26198
Swath start/stop times
27199
'''
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]
41202

42203

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
44205
item_valid = lambda item, sat_id: os.path.isfile(item) and sat_id in os.path.basename(item)
45206

46207

0 commit comments

Comments
 (0)