diff --git a/CHANGELOG.md b/CHANGELOG.md index 963cbfc..21458cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,8 +27,50 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --> ------ ## [1.0.4](https://github.com/asfadmin/Discovery-SearchAPI-v3/compare/v1.0.3...v1.0.4) +### Added +- Added experimental ARIA S1 GUNW baseline stacking support + - requires: `dataset` keyword be set to "ARIA S1 GUNW" and using the desired frame as the `reference` + - returns json object list partially formatted for submitting jobs to ASF's On Demand processing. + ``` json + [ + { + "date": "2025-06-04T00:26:25Z", + "products": [ + "S1A_IW_SLC__1SDV_20250604T002649_20250604T002716_059489_076290_7E0F", + "S1A_IW_SLC__1SDV_20250604T002625_20250604T002651_059489_076290_7FE0" + ], + "group_granule_idx": 0, + "perpendicularBaseline": 0, + "temporalBaseline": 0 + }, + { + "date": "2025-05-23T00:26:25Z", + "products": [ + "S1A_IW_SLC__1SDV_20250523T002650_20250523T002717_059314_075C80_5A5D", + "S1A_IW_SLC__1SDV_20250523T002625_20250523T002652_059314_075C80_BBE7" + ], + "group_granule_idx": 0, + "perpendicularBaseline": 47, + "temporalBaseline": -12 + }, + + ... + + { + "date": "2014-10-12T00:25:42Z", + "products": [ + "S1A_IW_SLC__1SSV_20141012T002607_20141012T002634_002789_00323B_2DB9", + "S1A_IW_SLC__1SSV_20141012T002542_20141012T002609_002789_00323B_0E9F" + ], + "group_granule_idx": 0, + "perpendicularBaseline": -160, + "temporalBaseline": -3888 + } + ] + ``` ### Changed -- bumped asf-search to 9.0.0 for nisar search types, browse images, and UAT collections +- bumped asf-search to 9.0.2 for nisar search types, browse images, and UAT collections, `productionConfiguration` list support, bbox validation, opera-disp jsonlite outputs + ------ ## [1.0.3](https://github.com/asfadmin/Discovery-SearchAPI-v3/compare/v1.0.2...v1.0.3) diff --git a/requirements.txt b/requirements.txt index 5d16795..f8ed52f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ ujson==5.7.0 uvicorn==0.21.1 watchfiles==0.19.0 -asf_search==9.0.0 +asf_search==9.0.2 python-json-logger==2.0.7 pyshp==2.1.3 diff --git a/src/SearchAPI/application/__init__.py b/src/SearchAPI/application/__init__.py index 8c06435..549425f 100644 --- a/src/SearchAPI/application/__init__.py +++ b/src/SearchAPI/application/__init__.py @@ -2,4 +2,5 @@ from .output import * from .logger import * from .log_router import * +from .search import * from .application import * \ No newline at end of file diff --git a/src/SearchAPI/application/application.py b/src/SearchAPI/application/application.py index c6ec258..907684b 100644 --- a/src/SearchAPI/application/application.py +++ b/src/SearchAPI/application/application.py @@ -15,9 +15,10 @@ from .asf_opts import process_baseline_request, process_search_request, process_wkt_request from .health import get_cmr_health from .models import BaselineSearchOptsModel, SearchOptsModel -from .output import as_output, get_asf_search_script +from .output import as_output, get_asf_search_script, make_filename from .files_to_wkt import FilesToWKT from . import constants +from .search import stack_aria_gunw import time @@ -93,8 +94,19 @@ async def query_baseline(searchOptions: BaselineSearchOptsModel = Depends(proces output = searchOptions.output reference = searchOptions.reference request_method = searchOptions.request_method - # Load the reference scene: + if searchOptions.opts.dataset is not None: + if searchOptions.opts.dataset[0] == asf.DATASET.ARIA_S1_GUNW: + return JSONResponse( + content=stack_aria_gunw(reference), + status_code=200, + headers= { + **constants.DEFAULT_HEADERS, + 'Content-Disposition': f"attachment; filename={make_filename('json')}", + } + ) + # Load the reference scene: + if output.lower() == 'python': file_name, search_script = get_asf_search_script(opts, reference=reference, search_endpoint='baseline') diff --git a/src/SearchAPI/application/search.py b/src/SearchAPI/application/search.py new file mode 100644 index 0000000..e4f2038 --- /dev/null +++ b/src/SearchAPI/application/search.py @@ -0,0 +1,61 @@ +from collections import defaultdict + +import dateparser +import asf_search as asf +from shapely.wkt import dumps as dump_to_wkt +from shapely import Polygon + +def stack_aria_gunw(frame: str): + reference = asf.search(frame=int(frame), dataset=asf.DATASET.ARIA_S1_GUNW, maxResults=1)[0] + + opts = asf.ASFSearchOptions( + relativeOrbit=reference.properties['pathNumber'], + processingLevel=asf.PRODUCT_TYPE.SLC, + dataset=asf.DATASET.SENTINEL1, + beamMode='IW', + polarization=['VV','VV+VH'], + flightDirection=reference.properties['flightDirection'], + intersectsWith=dump_to_wkt(Polygon(reference.geometry['coordinates'][0])) + ) + + slc_stack = asf.search(opts=opts) + + groups = defaultdict(list) + for product in slc_stack: + group_id = product.properties['platform'] + '_' + str(product.properties['orbit']) + groups[group_id].append(product) + # dateparser.parse(str(value)) + aria_groups = [ + { + 'date': min(dateparser.parse(product.properties['startTime']) for product in group), + 'products': [product for product in group], + } + for group in groups.values() + ] + + # track group index on each product, naively choose first granule available + for idx, group in enumerate(aria_groups): + group_granule_idx = None + for idy, product in enumerate(group['products']): + product.properties['groupIDX'] = idx + if group_granule_idx is None: + if product.has_baseline(): + group_granule_idx = idy + + group['group_granule_idx'] = group_granule_idx + + + + stack = asf.ASFSearchResults([group['products'][group['group_granule_idx']] for group in aria_groups if group['group_granule_idx'] is not None]) + target_stack, warnings = asf.baseline.get_baseline_from_stack(reference, stack) + for product in target_stack: + group_idx = product.properties.pop('groupIDX') + aria_groups[group_idx]['perpendicularBaseline'] = product.properties['perpendicularBaseline'] + aria_groups[group_idx]['temporalBaseline'] = product.properties['temporalBaseline'] + + for group in aria_groups: + for idx, product in enumerate(group['products']): + group['products'][idx] = product.properties['sceneName'] + group['date'] = group['date'].strftime('%Y-%m-%dT%H:%M:%SZ') + + return aria_groups \ No newline at end of file