Skip to content

Commit

Permalink
Support IVLIS2 fetch, download, and ITRF transformation
Browse files Browse the repository at this point in the history
This replicates the behaivor in `valkyrie`: the GLAT, GLON, and ZG fields are
used for lat/lon/elev. We may want to revisit supporting transformation of other
lat/lon/elev combinations.
  • Loading branch information
trey-stafford committed Oct 15, 2024
1 parent 9fcddee commit 87497cf
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- `transform_itrf` will calculate plate for source ITRF if not given with
`target_epoch`.
- Add support for ILATM1B v2 and BLATM1B v1.
- Add support for ILVIS2.

# v0.2.0

Expand Down
19 changes: 14 additions & 5 deletions src/nsidc/iceflow/data/ilvis2.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def _ilvis2_data(filepath: Path, file_date: dt.date, fields) -> pd.DataFrame:
conversions / augmentation on the data.
"""
field_names = [name for name, _, _ in fields]
df = pd.read_csv(filepath, delim_whitespace=True, comment="#", names=field_names)
df = pd.read_csv(filepath, sep=r"\s+", comment="#", names=field_names)

for col in ILVIS2_LONGITUDE_FIELD_NAMES:
if col in df.columns:
Expand Down Expand Up @@ -199,10 +199,19 @@ def ilvis2_data(filepath: Path) -> ILVIS2DataFrame:
data = _ilvis2_data(filepath, file_date, the_fields)
data["ITRF"] = ILVIS2_ITRF

# TODO: this data does not have latitude, longitude, and elevation
# fields. Instead, it has e.g., "CLON" and "GLON" and "HLON". Which should
# be chosen for e.g., ITRF transformations? Look at the NSIDC data tutorials
# iceflow notebooks to see how the "harmonization" was done.
# TODO: this data does not have a single set of latitude, longitude, and
# elevation fields. Instead, it has e.g., "CLON" and "GLON" and "HLON". In
# the original `valkyrie` service code, it looks like "GLON", "GLAT", and
# "ZG" cols were used as for the points stored in the valkyrie database and
# transformed by the ITRF transformation service. Ideally, we support
# consistent transformation of the ITRF across all lat/lon/elev
# fields. E.g., a user may be more interested in looking at the "CLON",
# "CLAT", and "ZC" fields instead.
# For now, we will replicate the behavior of `valkyrie`:
data["latitude"] = data["GLAT"]
data["longitude"] = data["GLON"]
data["elevation"] = data["ZG"]

data = data.set_index("utc_datetime")

return ILVIS2DataFrame(data)
62 changes: 60 additions & 2 deletions src/nsidc/iceflow/data/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,67 @@ class ATM1BSchema(CommonDataColumnsSchema):
pulse_width: Series[float] = pa.Field(nullable=True, coerce=True)


# Note/TODO: the ILVIS2 data contain multiple sets of lat/lon/elev. The common
# schema assumes one set of lat/lon/elev which is used for the ITRF
# transformation code.
class ILVIS2Schema(CommonDataColumnsSchema):
# TODO!
...
# Common columns
LFID: Series[float] = pa.Field(nullable=True, coerce=True)
SHOTNUMBER: Series[float] = pa.Field(nullable=True, coerce=True)
TIME: Series[float] = pa.Field(nullable=True, coerce=True)
ZG: Series[float] = pa.Field(nullable=True, coerce=True)
GLAT: Series[float] = pa.Field(nullable=True, coerce=True)
GLON: Series[float] = pa.Field(nullable=True, coerce=True)
HLAT: Series[float] = pa.Field(nullable=True, coerce=True)
HLON: Series[float] = pa.Field(nullable=True, coerce=True)
ZH: Series[float] = pa.Field(nullable=True, coerce=True)

# V104-specific
CLAT: Series[float] = pa.Field(nullable=True, coerce=True)
CLON: Series[float] = pa.Field(nullable=True, coerce=True)
ZC: Series[float] = pa.Field(nullable=True, coerce=True)

# V202B-specific
AZIMUTH: Series[float] = pa.Field(nullable=True, coerce=True)
CHANNEL_RH: Series[float] = pa.Field(nullable=True, coerce=True)
CHANNEL_ZG: Series[float] = pa.Field(nullable=True, coerce=True)
CHANNEL_ZT: Series[float] = pa.Field(nullable=True, coerce=True)
COMPLEXITY: Series[float] = pa.Field(nullable=True, coerce=True)
INCIDENT_ANGLE: Series[float] = pa.Field(nullable=True, coerce=True)
RANGE: Series[float] = pa.Field(nullable=True, coerce=True)
RH10: Series[float] = pa.Field(nullable=True, coerce=True)
RH15: Series[float] = pa.Field(nullable=True, coerce=True)
RH20: Series[float] = pa.Field(nullable=True, coerce=True)
RH25: Series[float] = pa.Field(nullable=True, coerce=True)
RH30: Series[float] = pa.Field(nullable=True, coerce=True)
RH35: Series[float] = pa.Field(nullable=True, coerce=True)
RH40: Series[float] = pa.Field(nullable=True, coerce=True)
RH45: Series[float] = pa.Field(nullable=True, coerce=True)
RH50: Series[float] = pa.Field(nullable=True, coerce=True)
RH55: Series[float] = pa.Field(nullable=True, coerce=True)
RH60: Series[float] = pa.Field(nullable=True, coerce=True)
RH65: Series[float] = pa.Field(nullable=True, coerce=True)
RH70: Series[float] = pa.Field(nullable=True, coerce=True)
RH75: Series[float] = pa.Field(nullable=True, coerce=True)
RH80: Series[float] = pa.Field(nullable=True, coerce=True)
RH85: Series[float] = pa.Field(nullable=True, coerce=True)
RH90: Series[float] = pa.Field(nullable=True, coerce=True)
RH95: Series[float] = pa.Field(nullable=True, coerce=True)
RH96: Series[float] = pa.Field(nullable=True, coerce=True)
RH97: Series[float] = pa.Field(nullable=True, coerce=True)
RH98: Series[float] = pa.Field(nullable=True, coerce=True)
RH99: Series[float] = pa.Field(nullable=True, coerce=True)
RH100: Series[float] = pa.Field(nullable=True, coerce=True)
TLAT: Series[float] = pa.Field(nullable=True, coerce=True)
TLON: Series[float] = pa.Field(nullable=True, coerce=True)
ZT: Series[float] = pa.Field(nullable=True, coerce=True)

class Config:
# This ensures all columns are present, regardless of the date. Granules
# before 2017 use the V104 fields and anything after uses the v202b
# fields. The data type for all values must be `float` because the null
# value is `np.nan` - a float.
add_missing_columns = True


IceflowDataFrame = DataFrame[CommonDataColumnsSchema]
Expand Down
21 changes: 21 additions & 0 deletions tests/integration/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
DatasetSearchParameters,
IceflowDataFrame,
ILATM1BDataset,
ILVIS2Dataset,
)


Expand Down Expand Up @@ -80,3 +81,23 @@ def test_atm1b_blatm1b(tmp_path):
)

assert (results_blamt1b_v2_2014.ITRF == "ITRF2000").all()


def test_ivlis2(tmp_path):
common_bounding_box = BoundingBox(
lower_left_lon=-120.0,
lower_left_lat=-80.0,
upper_right_lon=-90.0,
upper_right_lat=-65.0,
)

results = fetch_iceflow_df(
dataset_search_params=DatasetSearchParameters(
dataset=ILVIS2Dataset(),
bounding_box=common_bounding_box,
temporal=(dt.datetime(2009, 10, 25, 15), dt.datetime(2009, 10, 25, 17)),
),
output_dir=tmp_path,
)

assert (results.ITRF == "ITRF2000").all()

0 comments on commit 87497cf

Please sign in to comment.