Skip to content

Commit e384faf

Browse files
committed
enhanced query elevation util, fixed bug in command line parser for lists, working support for ancillary data
1 parent 93eef7d commit e384faf

File tree

3 files changed

+154
-64
lines changed

3 files changed

+154
-64
lines changed

sliderule/icesat2.py

Lines changed: 114 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
# create logger
5454
logger = logging.getLogger(__name__)
5555

56+
# profiling times for each major function
57+
profiles = {}
58+
5659
# default asset
5760
DEFAULT_ASSET="nsidc-s3"
5861

@@ -94,10 +97,22 @@
9497
SC_BACKWARD = 0
9598
SC_FORWARD = 1
9699

97-
# gps-based epoch for delta times #
100+
# gps-based epoch for delta times
98101
ATLAS_SDP_EPOCH = datetime.datetime(2018, 1, 1)
99102

103+
# ancillary list types
104+
ATL03_GEOLOCATION = 0
105+
ATL03_GEOCORRECTION = 1
106+
ATL03_HEIGHT = 2
107+
ATL08_SIGNAL_PHOTON = 3
100108

109+
# ancillary list type matching
110+
ancillary_lists = {
111+
ATL03_GEOLOCATION: "atl03_geolocation_fields",
112+
ATL03_GEOCORRECTION: "atl03_geocorrection_fields",
113+
ATL03_HEIGHT: "atl03_height_fields",
114+
ATL08_SIGNAL_PHOTON: "atl08_signal_photon_fields"
115+
}
101116

102117
###############################################################################
103118
# NSIDC UTILITIES
@@ -115,7 +130,6 @@
115130
# The above copyright notice and this permission notice shall be included
116131
# in all copies or substantial portions of the Software.
117132

118-
119133
# WGS84 / Mercator, Earth as Geoid, Coordinate system on the surface of a sphere or ellipsoid of reference.
120134
EPSG_MERCATOR = "EPSG:4326"
121135

@@ -316,29 +330,32 @@ def __get_values(data, dtype, size):
316330
#
317331
def __query_resources(parm, version, return_polygons=False):
318332

333+
# Latch Start Time
334+
tstart = time.perf_counter()
335+
319336
# Check Parameters are Valid
320337
if ("poly" not in parm) and ("t0" not in parm) and ("t1" not in parm):
321338
logger.error("Must supply some bounding parameters with request (poly, t0, t1)")
322339
return []
323340

324-
# Submission Arguments for CRM #
341+
# Submission Arguments for CMR
325342
kwargs = {}
326343
kwargs['version'] = version
327344
kwargs['return_polygons'] = return_polygons
328345

329-
# Pull Out Polygon #
346+
# Pull Out Polygon
330347
if "clusters" in parm and parm["clusters"] and len(parm["clusters"]) > 0:
331348
kwargs['polygon'] = parm["clusters"]
332349
elif "poly" in parm and parm["poly"] and len(parm["poly"]) > 0:
333350
kwargs['polygon'] = parm["poly"]
334351

335-
# Pull Out Time Period #
352+
# Pull Out Time Period
336353
if "t0" in parm:
337354
kwargs['time_start'] = parm["t0"]
338355
if "t1" in parm:
339356
kwargs['time_end'] = parm["t1"]
340357

341-
# Build Filters #
358+
# Build Filters
342359
name_filter_enabled = False
343360
rgt_filter = '????'
344361
if "rgt" in parm:
@@ -355,19 +372,22 @@ def __query_resources(parm, version, return_polygons=False):
355372
if name_filter_enabled:
356373
kwargs['name_filter'] = '*_' + rgt_filter + cycle_filter + region_filter + '_*'
357374

358-
# Make CMR Request #
375+
# Make CMR Request
359376
if return_polygons:
360377
resources,polygons = cmr(**kwargs)
361378
else:
362379
resources = cmr(**kwargs)
363380

364-
# Check Resources are Under Limit #
381+
# Check Resources are Under Limit
365382
if(len(resources) > max_requested_resources):
366383
raise RuntimeError('Exceeded maximum requested granules: {} (current max is {})\nConsider using icesat2.set_max_resources to set a higher limit.'.format(len(resources), max_requested_resources))
367384
else:
368385
logger.info("Identified %d resources to process", len(resources))
369386

370-
# Return Resources #
387+
# Update Profile
388+
profiles[__query_resources.__name__] = time.perf_counter() - tstart
389+
390+
# Return Resources
371391
if return_polygons:
372392
return (resources,polygons)
373393
else:
@@ -385,7 +405,11 @@ def __emptyframe(**kwargs):
385405
# Dictionary to GeoDataFrame
386406
#
387407
def __todataframe(columns, delta_time_key="delta_time", lon_key="lon", lat_key="lat", **kwargs):
388-
# set default keyword arguments
408+
409+
# Latch Start Time
410+
tstart = time.perf_counter()
411+
412+
# Set Default Keyword Arguments
389413
kwargs['index_key'] = "time"
390414
kwargs['crs'] = EPSG_MERCATOR
391415

@@ -416,13 +440,20 @@ def __todataframe(columns, delta_time_key="delta_time", lon_key="lon", lat_key="
416440
# Sort values for reproducible output despite async processing
417441
gdf.sort_index(inplace=True)
418442

443+
# Update Profile
444+
profiles[__todataframe.__name__] = time.perf_counter() - tstart
445+
419446
# Return GeoDataFrame
420447
return gdf
421448

422449
#
423450
# GeoDataFrame to Polygon
424451
#
425452
def __gdf2poly(gdf):
453+
454+
# latch start time
455+
tstart = time.perf_counter()
456+
426457
# pull out coordinates
427458
hull = gdf.unary_union.convex_hull
428459
polygon = [{"lon": coord[0], "lat": coord[1]} for coord in list(hull.exterior.coords)]
@@ -438,6 +469,9 @@ def __gdf2poly(gdf):
438469
# replace region with counter-clockwise version #
439470
polygon = ccw_poly
440471

472+
# Update Profile
473+
profiles[__gdf2poly.__name__] = time.perf_counter() - tstart
474+
441475
# return polygon
442476
return polygon
443477

@@ -705,6 +739,8 @@ def atl06p(parm, asset=DEFAULT_ASSET, version=DEFAULT_ICESAT2_SDP_VERSION, callb
705739
[622412 rows x 16 columns]
706740
'''
707741
try:
742+
tstart = time.perf_counter()
743+
708744
# Get List of Resources from CMR (if not supplied)
709745
if resources == None:
710746
resources = __query_resources(parm, version)
@@ -719,46 +755,62 @@ def atl06p(parm, asset=DEFAULT_ASSET, version=DEFAULT_ICESAT2_SDP_VERSION, callb
719755
# Make API Processing Request
720756
rsps = sliderule.source("atl06p", rqst, stream=True, callbacks=callbacks)
721757

722-
start_time = time.perf_counter()
723758
# Flatten Responses
759+
tstart_flatten = time.perf_counter()
724760
columns = {}
725-
rectype = None
726-
num_rows = 0
727-
sample_rec = None
728-
if len(rsps) <= 0:
729-
logger.debug("No response returned")
730-
else:
731-
for rsp in rsps:
732-
# Determine Record Type
733-
if rectype == None:
734-
if rsp['__rectype'] == 'atl06rec':
735-
rectype = 'atl06rec.elevation'
736-
sample_rec = rsp
737-
elif rsp['__rectype'] == 'atl06rec-compact':
738-
rectype = 'atl06rec-compact.elevation'
739-
sample_rec = rsp
740-
# Count Rows
741-
num_rows += len(rsp["elevation"])
742-
# Check Valid ATL06 Record Returned
743-
if rectype == None:
744-
raise RuntimeError("Invalid record types returned for this api")
745-
# Build Columns
746-
for field in sample_rec["elevation"][0].keys():
747-
fielddef = sliderule.get_definition(rectype, field)
748-
if len(fielddef) > 0:
749-
columns[field] = numpy.empty(num_rows, fielddef["nptype"])
750-
# Populate Columns
751-
elev_cnt = 0
761+
elevation_records = []
762+
num_elevations = 0
763+
field_dictionary = {}
764+
if len(rsps) > 0:
765+
# Sort Records
752766
for rsp in rsps:
753-
for elevation in rsp["elevation"]:
754-
for field in columns:
755-
columns[field][elev_cnt] = elevation[field]
756-
elev_cnt += 1
767+
if 'atl06rec' in rsp['__rectype']:
768+
elevation_records += rsp,
769+
num_elevations += len(rsp['elevation'])
770+
elif 'atlxxrec' in rsp['__rectype']:
771+
if rsp['list_type'] == ATL03_GEOLOCATION or rsp['list_type'] == ATL03_GEOCORRECTION:
772+
field_name = parm[ancillary_lists[rsp['list_type']]][rsp['field_index']]
773+
if field_name in field_dictionary:
774+
data = __get_values(rsp['data'], rsp['data_type'], len(rsp['data']))
775+
# Add Left Pair Track Entry
776+
field_dictionary[field_name]['extent_id'] += rsp['extent_id'] | 0x2,
777+
field_dictionary[field_name][field_name] += data[0],
778+
# Add Right Pair Track Entry
779+
field_dictionary[field_name]['extent_id'] += rsp['extent_id'] | 0x3,
780+
field_dictionary[field_name][field_name] += data[1],
781+
else:
782+
field_dictionary[field_name] = {"extent_id": [], field_name: []}
783+
# Build Elevation Columns
784+
if num_elevations > 0:
785+
# Initialize Columns
786+
sample_elevation_record = elevation_records[0]["elevation"][0]
787+
for field in sample_elevation_record.keys():
788+
fielddef = sliderule.get_definition(sample_elevation_record['__rectype'], field)
789+
if len(fielddef) > 0:
790+
columns[field] = numpy.empty(num_elevations, fielddef["nptype"])
791+
# Populate Columns
792+
elev_cnt = 0
793+
for record in elevation_records:
794+
for elevation in record["elevation"]:
795+
for field in columns:
796+
columns[field][elev_cnt] = elevation[field]
797+
elev_cnt += 1
798+
else:
799+
logger.debug("No response returned")
800+
profiles["flatten"] = time.perf_counter() - tstart_flatten
757801

758-
# Return Response
802+
# Build GeoDataFrame
759803
gdf = __todataframe(columns, "delta_time", "lon", "lat")
760-
end_time = time.perf_counter()
761-
print("Execution Time: {} seconds".format(end_time - start_time))
804+
805+
# Merge Ancillary Fields
806+
tstart_merge = time.perf_counter()
807+
for field in field_dictionary:
808+
df = geopandas.pd.DataFrame(field_dictionary[field])
809+
gdf = gdf.merge(df, on='extent_id', how='inner')
810+
profiles["merge"] = time.perf_counter() - tstart_merge
811+
812+
# Return Response
813+
profiles[atl06p.__name__] = time.perf_counter() - tstart
762814
return gdf
763815

764816
# Handle Runtime Errors
@@ -823,6 +875,8 @@ def atl03sp(parm, asset=DEFAULT_ASSET, version=DEFAULT_ICESAT2_SDP_VERSION, call
823875
ATL03 segments (see `Photon Segments <../user_guide/ICESat-2.html#photon-segments>`_)
824876
'''
825877
try:
878+
tstart = time.perf_counter()
879+
826880
# Get List of Resources from CMR (if not specified)
827881
if resources == None:
828882
resources = __query_resources(parm, version)
@@ -838,6 +892,7 @@ def atl03sp(parm, asset=DEFAULT_ASSET, version=DEFAULT_ICESAT2_SDP_VERSION, call
838892
rsps = sliderule.source("atl03sp", rqst, stream=True, callbacks=callbacks)
839893

840894
# Flatten Responses
895+
tstart_flatten = time.perf_counter()
841896
columns = {}
842897
if len(rsps) <= 0:
843898
logger.debug("no response returned")
@@ -885,6 +940,7 @@ def atl03sp(parm, asset=DEFAULT_ASSET, version=DEFAULT_ICESAT2_SDP_VERSION, call
885940
ph_index += 1
886941
# Rename Count Column to Pair Column
887942
columns["pair"] = columns.pop("count")
943+
profiles["flatten"] = time.perf_counter() - tstart_flatten
888944

889945
# Create DataFrame
890946
df = __todataframe(columns, "delta_time", "longitude", "latitude")
@@ -893,6 +949,7 @@ def atl03sp(parm, asset=DEFAULT_ASSET, version=DEFAULT_ICESAT2_SDP_VERSION, call
893949
df['spot'] = df.apply(lambda row: __calcspot(row["sc_orient"], row["track"], row["pair"]), axis=1)
894950

895951
# Return Response
952+
profiles[atl03sp.__name__] = time.perf_counter() - tstart
896953
return df
897954

898955
# Error Case
@@ -955,9 +1012,11 @@ def h5 (dataset, resource, asset=DEFAULT_ASSET, datatype=sliderule.datatypes["DY
9551012
>>> longitudes = icesat2.h5("/gt1r/land_ice_segments/longitude", resource, asset)
9561013
>>> df = pd.DataFrame(data=list(zip(heights, latitudes, longitudes)), index=segments, columns=["h_mean", "latitude", "longitude"])
9571014
'''
1015+
tstart = time.perf_counter()
9581016
datasets = [ { "dataset": dataset, "datatype": datatype, "col": col, "startrow": startrow, "numrows": numrows } ]
9591017
values = h5p(datasets, resource, asset=asset)
9601018
if len(values) > 0:
1019+
profiles[h5.__name__] = time.perf_counter() - tstart
9611020
return values[dataset]
9621021
else:
9631022
return numpy.empty(0)
@@ -1017,6 +1076,9 @@ def h5p (datasets, resource, asset=DEFAULT_ASSET):
10171076
'/gt1r/land_ice_segments/h_li': array([45.72632446, 45.76512574, 45.76337375, 45.77102473, 45.81307948]),
10181077
'/gt3r/land_ice_segments/h_li': array([45.14954134, 45.18970635, 45.16637644, 45.15235916, 45.17135806])}
10191078
'''
1079+
# Latch Start Time
1080+
tstart = time.perf_counter()
1081+
10201082
# Baseline Request
10211083
rqst = {
10221084
"asset" : asset,
@@ -1036,6 +1098,9 @@ def h5p (datasets, resource, asset=DEFAULT_ASSET):
10361098
for result in rsps:
10371099
results[result["dataset"]] = __get_values(result["data"], result["datatype"], result["size"])
10381100

1101+
# Update Profiles
1102+
profiles[h5p.__name__] = time.perf_counter() - tstart
1103+
10391104
# Return Results
10401105
return results
10411106

@@ -1088,6 +1153,7 @@ def toregion(source, tolerance=0.0, cellsize=0.01, n_clusters=1):
10881153
>>> atl06 = icesat2.atl06p(parms)
10891154
'''
10901155

1156+
tstart = time.perf_counter()
10911157
tempfile = "temp.geojson"
10921158

10931159
if isinstance(source, geopandas.GeoDataFrame):
@@ -1170,8 +1236,10 @@ def toregion(source, tolerance=0.0, cellsize=0.01, n_clusters=1):
11701236
c_poly = __gdf2poly(c_gdf)
11711237
clusters.append(c_poly)
11721238

1239+
# update timing profiles
1240+
profiles[toregion.__name__] = time.perf_counter() - tstart
11731241

1174-
# return region #
1242+
# return region
11751243
return {
11761244
"gdf": gdf,
11771245
"poly": polygon, # convex hull of polygons

0 commit comments

Comments
 (0)