Skip to content

Commit 4341b40

Browse files
authored
Merge pull request #233 from ghrcdaac/Joseph-GHRCCLOUD-8263-dc82dstc4
Joseph ghrccloud 8263 dc82dstc4
2 parents f6664cf + f83e76d commit 4341b40

2 files changed

Lines changed: 230 additions & 0 deletions

File tree

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
from datetime import datetime, time, timedelta, timezone
2+
import re
3+
from typing import Any
4+
import os
5+
from utils.mdx import MDX
6+
from utils.ames import open_ames_1001
7+
from pprint import pprint
8+
9+
short_name = "dc82dstc4"
10+
provider_path = "dc82dstc4/"
11+
nav_lookup_local_path = ""
12+
13+
# dc82dstc4 (TC4 2DS DC-8) contains no nav data;
14+
# nav data needs to be looked up from TC4 NAV DC-8 (dc8navtc4)
15+
# which is already out of ghrc-private.
16+
# nav_lookup is hard-coded here because it's easier, not because it's elegant.
17+
nav_lookup = {
18+
'20070628': {'max_lat': 40.138952,
19+
'max_lon': -119.374708,
20+
'min_lat': 38.44974,
21+
'min_lon': -126.368228},
22+
'20070630': {'max_lat': 44.426224,
23+
'max_lon': -121.488932,
24+
'min_lat': 38.643756,
25+
'min_lon': -127.532152},
26+
'20070702': {'max_lat': 38.794256,
27+
'max_lon': -117.093816,
28+
'min_lat': 31.694268,
29+
'min_lon': -125.133096},
30+
'20070713': {'max_lat': 38.679943,
31+
'max_lon': -84.20351,
32+
'min_lat': 9.726677,
33+
'min_lon': -121.400814},
34+
'20070717': {'max_lat': 10.00803,
35+
'max_lon': -80.030766,
36+
'min_lat': -3.376579,
37+
'min_lon': -84.826298},
38+
'20070721': {'max_lat': 12.294216,
39+
'max_lon': -72.433205,
40+
'min_lat': 2.468834,
41+
'min_lon': -85.591049},
42+
'20070722': {'max_lat': 15.941677,
43+
'max_lon': -78.158283,
44+
'min_lat': 5.919914,
45+
'min_lon': -85.819702},
46+
'20070724': {'max_lat': 10.007858,
47+
'max_lon': -84.144459,
48+
'min_lat': 5.012512,
49+
'min_lon': -86.060028},
50+
'20070728': {'max_lat': 16.579227,
51+
'max_lon': -81.294193,
52+
'min_lat': 8.705292,
53+
'min_lon': -88.796482},
54+
'20070729': {'max_lat': 10.585155,
55+
'max_lon': -78.218193,
56+
'min_lat': -6.471634,
57+
'min_lon': -85.170479},
58+
'20070731': {'max_lat': 10.619144,
59+
'max_lon': -82.360039,
60+
'min_lat': 7.928867,
61+
'min_lon': -89.509563},
62+
'20070803': {'max_lat': 13.876762,
63+
'max_lon': -80.040035,
64+
'min_lat': 5.130787,
65+
'min_lon': -86.32061},
66+
'20070805': {'max_lat': 10.00906,
67+
'max_lon': -76.675987,
68+
'min_lat': 5.282021,
69+
'min_lon': -84.434395},
70+
'20070806': {'max_lat': 10.01215,
71+
'max_lon': -84.131927,
72+
'min_lat': -3.001328,
73+
'min_lon': -92.314682},
74+
'20070808': {'max_lat': 10.008202,
75+
'max_lon': -70.252419,
76+
'min_lat': 1.433201,
77+
'min_lon': -85.174427},
78+
'20070810': {'max_lat': 39.2169,
79+
'max_lon': -84.136219,
80+
'min_lat': 9.953098,
81+
'min_lon': -121.400471}
82+
}
83+
84+
85+
class MDXProcessing(MDX):
86+
87+
def __init__(self):
88+
super().__init__()
89+
self.nav_lookup = nav_lookup
90+
91+
def main(self):
92+
# Nav lookup is already done and no longer needed here, but left here
93+
# for reference and reuse
94+
#self.nav_lookup = self.build_navigation_lookup(provider_path)
95+
print(f"Nav data available for {list(nav_lookup.keys())}")
96+
97+
self.process_collection(short_name, provider_path)
98+
#self.shutdown_ec2()
99+
100+
def process(self, filename: str, stream) -> dict[str, Any]:
101+
date_key = self.get_date_key(filename)
102+
103+
if date_key not in self.nav_lookup:
104+
raise ValueError(
105+
f"No navigation data found for {filename}"
106+
f"(date {date_key})"
107+
)
108+
spatial = self.nav_lookup[date_key]
109+
temporal = self.read_temporal_bounds(stream)
110+
111+
if date_key != temporal["date_key"]:
112+
raise ValueError(
113+
f"Filename date {date_key} does not match"
114+
f"NASA Ames DATE {temporal['date_key']} for {filename}"
115+
)
116+
117+
return {
118+
"start": temporal["start_time"],
119+
"end": temporal["end_time"],
120+
"north": spatial["max_lat"],
121+
"south": spatial["min_lat"],
122+
"east": spatial["max_lon"],
123+
"west": spatial["min_lon"],
124+
"format": "ASCII"
125+
}
126+
127+
@staticmethod
128+
def get_date_key(filename):
129+
"""Parse date key from filename."""
130+
match = re.search(r"(\d{8})(?:_\d{6})?\.txt$", filename)
131+
132+
if not match:
133+
raise ValueError(f"Could not extract date from {filename}")
134+
135+
return match.group(1)
136+
137+
def build_navigation_lookup(self, provider_path: str, bucket: str = 'ghrcw-private'):
138+
"""Pre-read spatial-aware NP files in dataset and store GPS bounds by date."""
139+
140+
nav_lookup = {}
141+
142+
for entry in os.scandir(nav_lookup_local_path):
143+
if not entry.path.endswith(".txt"):
144+
continue
145+
146+
date_key = self.get_date_key(entry.name)
147+
nav_dict = self.read_spatial_bounds(entry.path)
148+
nav_lookup[date_key] = nav_dict
149+
150+
print("Found navigation data for these dates: ")
151+
pprint(nav_lookup)
152+
153+
return nav_lookup
154+
155+
@staticmethod
156+
def read_spatial_bounds(stream) -> dict[str, float]:
157+
"""Read data file and compute spatial bounds."""
158+
159+
min_lat = float("inf")
160+
max_lat = float("-inf")
161+
min_lon = float("inf")
162+
max_lon = float("-inf")
163+
164+
with open_ames_1001(stream) as (header, records):
165+
166+
# Headers per TC4_Nav_DC8_TN20070628.txt
167+
lat_idx = header.variable_names.index(
168+
"Lat (dec degree_N)"
169+
)
170+
lon_idx = header.variable_names.index(
171+
"Lon (dec degree_E)"
172+
)
173+
174+
for record in records:
175+
latitude = record.values[lat_idx]
176+
longitude = record.values[lon_idx]
177+
178+
min_lat = min(min_lat, latitude)
179+
max_lat = max(max_lat, latitude)
180+
min_lon = min(min_lon, longitude)
181+
max_lon = max(max_lon, longitude)
182+
183+
if min_lat == float("inf"):
184+
raise ValueError("No valid latitude/longitude values found")
185+
186+
nav_dict ={
187+
"min_lat": min_lat,
188+
"max_lat": max_lat,
189+
"min_lon": min_lon,
190+
"max_lon": max_lon,
191+
}
192+
return nav_dict
193+
194+
@staticmethod
195+
def read_temporal_bounds(stream) -> dict[str, Any]:
196+
"""Read data file and compute temporal bounds (start and end date)."""
197+
198+
start_time = datetime.max.replace(tzinfo=timezone.utc)
199+
end_time = datetime.min.replace(tzinfo=timezone.utc)
200+
201+
with open_ames_1001(stream) as (header, records):
202+
203+
beginning_of_day = datetime.combine(
204+
header.data_date,
205+
time.min,
206+
tzinfo=timezone.utc,
207+
)
208+
date_key = f"{header.data_date.year:04d}{header.data_date.month:02d}{header.data_date.day:02d}"
209+
210+
for record in records:
211+
timestamp = beginning_of_day + timedelta(
212+
seconds=record.independent
213+
)
214+
215+
start_time = min(start_time, timestamp)
216+
end_time = max(end_time, timestamp)
217+
218+
temp_dict = {
219+
"start_time": start_time,
220+
"end_time": end_time,
221+
"base_time": header.data_date,
222+
"date_key": date_key,
223+
}
224+
return temp_dict
225+
226+
if __name__ == '__main__':
227+
MDXProcessing().main()
228+
# The below can be use to run a profiler and see which functions are
229+
# taking the most time to process
230+
# cProfile.run('MDXProcessing().main()', sort='tottime')
7.26 KB
Binary file not shown.

0 commit comments

Comments
 (0)