Skip to content

Commit

Permalink
feature: ALOS 2 dataset and product type, stacking
Browse files Browse the repository at this point in the history
  • Loading branch information
Kim committed Dec 17, 2024
1 parent bcebd43 commit 67d0ff5
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 44 deletions.
4 changes: 0 additions & 4 deletions asf_search/ASFProduct.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,10 @@ def get_classname(cls):
- `path`: the expected path in the CMR UMM json granule response as a list
- `cast`: (optional): the optional type casting method
<<<<<<< HEAD
Defining `_base_properties` in subclasses allows for
defining custom properties or overiding existing ones.
See `S1Product.get_property_paths()` on how subclasses are expected to
combine `ASFProduct._base_properties` with their own separately defined `_base_properties`
=======
Defining `_properties_paths` in subclasses allows for defining custom properties or overiding existing ones.
>>>>>>> master
"""

def __init__(self, args: Dict = {}, session: ASFSession = ASFSession()):
Expand Down
6 changes: 6 additions & 0 deletions asf_search/CMR/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@


dataset_collections = {
'ALOS-2': {
'ALOS2_L1_PSR2': [
'C1269180392-ASF',
'C1269180392-ASF',
]
},
'NISAR': {
'NISAR_NEN_RRST_BETA_V1': [
'C1261815181-ASFDEV',
Expand Down
2 changes: 1 addition & 1 deletion asf_search/CMR/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def fix_cmr_shapes(fixed_params: Dict[str, Any]) -> Dict[str, Any]:


def should_use_asf_frame(cmr_opts):
asf_frame_platforms = ['SENTINEL-1A', 'SENTINEL-1B', 'ALOS']
asf_frame_platforms = ['SENTINEL-1A', 'SENTINEL-1B', 'ALOS', 'ALOS-2']

asf_frame_collections = get_concept_id_alias(asf_frame_platforms, collections_per_platform)

Expand Down
105 changes: 105 additions & 0 deletions asf_search/Products/ALOS2Product.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from copy import copy
from typing import Dict, Union

from asf_search import ASFSearchOptions, ASFSession, ASFStackableProduct
from asf_search.CMR.translate import try_parse_float, try_parse_int
from asf_search.constants import PRODUCT_TYPE

class ALOS2Product(ASFStackableProduct):
"""
Used for ALOS Palsar and Avnir dataset products
ASF Dataset Documentation Page: https://asf.alaska.edu/datasets/daac/alos-palsar/
"""

_base_properties = {
**ASFStackableProduct._base_properties,
'frameNumber': {
'path': ['AdditionalAttributes', ('Name', 'FRAME_NUMBER'), 'Values', 0],
'cast': try_parse_int,
}, # Sentinel and ALOS product alt for frameNumber (ESA_FRAME)
'groupID': {'path': ['AdditionalAttributes', ('Name', 'GROUP_ID'), 'Values', 0]},
'md5sum': {'path': ['AdditionalAttributes', ('Name', 'MD5SUM'), 'Values', 0]},
'pgeVersion': {'path': ['PGEVersionClass', 'PGEVersion']},
'center_lat': {
'path': ['AdditionalAttributes', ('Name', 'CENTER_LAT'), 'Values', 0],
'cast': try_parse_float,
},
'center_lon': {
'path': ['AdditionalAttributes', ('Name', 'CENTER_LON'), 'Values', 0],
'cast': try_parse_float,
},
}

baseline_type = ASFStackableProduct.BaselineCalcType.CALCULATED
perpendicularBaseline = None
position = None
velocity = None
scene_name = None

def __init__(self, args: Dict = {}, session: ASFSession = ASFSession()):
super().__init__(args, session)
self.properties.pop('processingLevel')
self.baseline = self.get_baseline_calc_properties()

def get_baseline_calc_properties(self) -> Dict:
"""
:returns properties required for SLC baseline stack calculations
"""
return {'stateVectors': self.get_state_vectors()}

def get_stack_opts(self, opts: ASFSearchOptions = None) -> ASFSearchOptions:
"""
Returns the search options asf-search will use internally
to build an SLC baseline stack from
:param opts: additional criteria for limiting
:returns ASFSearchOptions used for build Sentinel-1 SLC Stack
"""
stack_opts = ASFSearchOptions() if opts is None else copy(opts)

stack_opts.processingLevel = self.get_default_baseline_product_type()
stack_opts.beamMode = [self.properties['beamModeType']]
stack_opts.flightDirection = self.properties['flightDirection']
stack_opts.relativeOrbit = [int(self.properties['pathNumber'])] # path
stack_opts.dataset = 'ALOS-2'

if self.properties['polarization'] in ['HH', 'HH+HV']:
stack_opts.polarization = ['HH', 'HH+HV']
else:
stack_opts.polarization = ['VV', 'VV+VH']

stack_opts.intersectsWith = self.centroid().wkt

return stack_opts

def get_state_vectors(self) -> Dict:
"""
Used in spatio-temporal perpendicular baseline calculations for non-pre-calculated stacks
:returns dictionary of pre/post positions, velocities, and times"""

position = [
float(val)
for val in self.umm_get(
self.umm, 'AdditionalAttributes', ('Name', 'SV_POSITION'), 'Values'
)
]
velocity = [
float(val)
for val in self.umm_get(
self.umm, 'AdditionalAttributes', ('Name', 'SV_VELOCITY'), 'Values'
)
]

return dict(position=position, velocity=velocity)

@staticmethod
def get_default_baseline_product_type() -> Union[str, None]:
"""
Returns the product type to search for when building a baseline stack.
"""
return PRODUCT_TYPE.L1_1

def is_valid_reference(self):
return self.has_baseline()
1 change: 1 addition & 0 deletions asf_search/Products/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
from .OPERAS1Product import OPERAS1Product # noqa: F401
from .ARIAS1GUNWProduct import ARIAS1GUNWProduct # noqa: F401
from .NISARProduct import NISARProduct # noqa: F401
from .ALOS2Product import ALOS2Product # noqa: F401
82 changes: 44 additions & 38 deletions asf_search/baseline/calc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from asf_search import ASFProduct
from asf_search import ASFProduct, ALOS2Product
from math import sqrt, cos, sin, radians
from typing import List

Expand All @@ -17,37 +17,40 @@


def calculate_perpendicular_baselines(reference: str, stack: List[ASFProduct]):
for product in stack:
baselineProperties = product.baseline
positionProperties = baselineProperties["stateVectors"]["positions"]

if len(positionProperties.keys()) == 0:
baselineProperties["noStateVectors"] = True
continue
if None in [
positionProperties["prePositionTime"],
positionProperties["postPositionTime"],
positionProperties["prePosition"],
positionProperties["postPosition"],
]:
baselineProperties["noStateVectors"] = True
continue

asc_node_time = parse_datetime(
baselineProperties["ascendingNodeTime"]
).timestamp()

start = parse_datetime(product.properties["startTime"]).timestamp()
end = parse_datetime(product.properties["stopTime"]).timestamp()
center = start + ((end - start) / 2)
baselineProperties["relative_start_time"] = start - asc_node_time
baselineProperties["relative_center_time"] = center - asc_node_time
baselineProperties["relative_end_time"] = end - asc_node_time

t_pre = parse_datetime(positionProperties["prePositionTime"]).timestamp()
t_post = parse_datetime(positionProperties["postPositionTime"]).timestamp()
product.baseline["relative_sv_pre_time"] = t_pre - asc_node_time
product.baseline["relative_sv_post_time"] = t_post - asc_node_time
isAlos2 = isinstance(stack[0], ALOS2Product)

if not isAlos2:
for product in stack:
baselineProperties = product.baseline
positionProperties = baselineProperties["stateVectors"]["positions"]

if len(positionProperties.keys()) == 0:
baselineProperties["noStateVectors"] = True
continue
if None in [
positionProperties["prePositionTime"],
positionProperties["postPositionTime"],
positionProperties["prePosition"],
positionProperties["postPosition"],
]:
baselineProperties["noStateVectors"] = True
continue

asc_node_time = parse_datetime(
baselineProperties["ascendingNodeTime"]
).timestamp()

start = parse_datetime(product.properties["startTime"]).timestamp()
end = parse_datetime(product.properties["stopTime"]).timestamp()
center = start + ((end - start) / 2)
baselineProperties["relative_start_time"] = start - asc_node_time
baselineProperties["relative_center_time"] = center - asc_node_time
baselineProperties["relative_end_time"] = end - asc_node_time

t_pre = parse_datetime(positionProperties["prePositionTime"]).timestamp()
t_post = parse_datetime(positionProperties["postPositionTime"]).timestamp()
product.baseline["relative_sv_pre_time"] = t_pre - asc_node_time
product.baseline["relative_sv_post_time"] = t_post - asc_node_time

for product in stack:
if product.properties["sceneName"] == reference:
Expand All @@ -64,12 +67,15 @@ def calculate_perpendicular_baselines(reference: str, stack: List[ASFProduct]):
secondary.properties["perpendicularBaseline"] = None
continue

shared_rel_time = get_shared_sv_time(reference, secondary)

reference_shared_pos = get_pos_at_rel_time(reference, shared_rel_time)
reference_shared_vel = get_vel_at_rel_time(reference, shared_rel_time)
secondary_shared_pos = get_pos_at_rel_time(secondary, shared_rel_time)
# secondary_shared_vel = get_vel_at_rel_time(secondary, shared_rel_time) # unused
if isAlos2:
reference_shared_pos = reference.baseline["granulePosition"] + reference.baseline["stateVectors"]['position']
reference_shared_vel = reference.baseline["stateVectors"]['velocity']
secondary_shared_pos = secondary.baseline["stateVectors"]['position'] + get_granule_position(secondary.properties['center_lat'], secondary.properties['center_lon'])
else:
shared_rel_time = get_shared_sv_time(reference, secondary)
reference_shared_pos = get_pos_at_rel_time(reference, shared_rel_time)
reference_shared_vel = get_vel_at_rel_time(reference, shared_rel_time)
secondary_shared_pos = get_pos_at_rel_time(secondary, shared_rel_time)

# need to get sat pos and sat vel at center time
reference.baseline["alongBeamVector"] = get_along_beam_vector(
Expand Down
2 changes: 1 addition & 1 deletion asf_search/baseline/stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def get_baseline_from_stack(
stack = [
product
for product in stack
if not product.properties["processingLevel"].lower().startswith("metadata") and
if not product.properties.get("processingLevel", '').lower().startswith("metadata") and
product.baseline is not None
]
reference, stack, reference_warnings = check_reference(reference, stack)
Expand Down
1 change: 1 addition & 0 deletions asf_search/constants/DATASET.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
SLC_BURST = 'SLC-BURST'
ALOS_PALSAR = 'ALOS PALSAR'
ALOS_AVNIR_2 = 'ALOS AVNIR-2'
ALOS_2 = 'ALOS-2'
SIRC = 'SIR-C'
ARIA_S1_GUNW = 'ARIA S1 GUNW'
SMAP = 'SMAP'
Expand Down
2 changes: 2 additions & 0 deletions asf_search/search/search_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ def set_default_dates(opts: ASFSearchOptions):
)
opts.start, opts.end = opts.end, opts.start
# Can't do this sooner, since you need to compare start vs end:
#TODO python 3.13 deprecation warning, ambiguous date when month specified but not year
if opts.start is not None:
opts.start = opts.start.strftime('%Y-%m-%dT%H:%M:%SZ')
if opts.end is not None:
Expand Down Expand Up @@ -537,4 +538,5 @@ def _get_platform(item: Dict):
'SEASAT': ASFProductType.SEASATProduct,
'SEASAT 1': ASFProductType.SEASATProduct,
'NISAR': ASFProductType.NISARProduct,
'ALOS-2': ASFProductType.ALOS2Product,
}

0 comments on commit 67d0ff5

Please sign in to comment.