Skip to content

Commit 3cf7bd9

Browse files
Merge pull request #45 from asfadmin/test
Custom Session client-id, aria gunw stacking output normalized
2 parents 75c0f89 + 6b51b99 commit 3cf7bd9

7 files changed

Lines changed: 89 additions & 70 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2525
-
2626
2727
-->
28+
------
29+
## [1.0.5](https://github.com/asfadmin/Discovery-SearchAPI-v3/compare/v1.0.4...v1.0.5)
30+
### Added
31+
- Create wrapper class around asf-search `ASFSession`, `SearchAPISession`. Modifies client ID.
32+
33+
### Changed
34+
- Aria stack supports different output types
35+
- Aria stacking uses aria frame id instead of frame number for stacking
36+
- asf_search uses `SearchAPISession` by default for search queries
37+
- bump asf-search to v9.0.4
38+
- increase search query limit to 2000, raise error if expected output is over that number
39+
2840
------
2941
## [1.0.4](https://github.com/asfadmin/Discovery-SearchAPI-v3/compare/v1.0.3...v1.0.4)
3042
### Added

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ ujson==5.7.0
2222
uvicorn==0.21.1
2323
watchfiles==0.19.0
2424

25-
asf_search==9.0.2
25+
asf_search==9.0.4
2626
python-json-logger==2.0.7
27+
asf_enumeration
2728

2829
pyshp==2.1.3
2930
geopandas
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from typing import List
2+
from asf_search import ASFSession
3+
4+
5+
class SearchAPISession(ASFSession):
6+
def __init__(
7+
self,
8+
edl_host: str = None,
9+
edl_client_id: str = None,
10+
asf_auth_host: str = None,
11+
cmr_host: str = None,
12+
cmr_collections: str = None,
13+
auth_domains: List[str] = None,
14+
auth_cookie_names: List[str] = None,
15+
):
16+
super().__init__(
17+
edl_host,
18+
edl_client_id,
19+
asf_auth_host,
20+
cmr_host,
21+
cmr_collections,
22+
auth_domains,
23+
auth_cookie_names,
24+
)
25+
26+
self.headers.update({'Client-Id': f'SearchAPI_{self.headers.get("Client-Id")}'})

src/SearchAPI/application/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
from .logger import *
44
from .log_router import *
55
from .search import *
6+
from .SearchAPISession import *
67
from .application import *

src/SearchAPI/application/application.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
from .output import as_output, get_asf_search_script, make_filename
1919
from .files_to_wkt import FilesToWKT
2020
from . import constants
21-
from .search import stack_aria_gunw
21+
from .SearchAPISession import SearchAPISession
22+
from .search import get_aria_groups_for_frame, stack_aria_gunw
2223
import time
24+
from asf_search.ASFSearchOptions.config import config as asf_config
2325

26+
asf_config['session'] = SearchAPISession()
2427

2528
asf.REPORT_ERRORS = False
2629
router = APIRouter(route_class=LoggingRoute)
@@ -97,14 +100,17 @@ async def query_baseline(searchOptions: BaselineSearchOptsModel = Depends(proces
97100

98101
if searchOptions.opts.dataset is not None:
99102
if searchOptions.opts.dataset[0] == asf.DATASET.ARIA_S1_GUNW:
100-
return JSONResponse(
101-
content=stack_aria_gunw(reference),
102-
status_code=200,
103-
headers= {
104-
**constants.DEFAULT_HEADERS,
105-
'Content-Disposition': f"attachment; filename={make_filename('json')}",
106-
}
107-
)
103+
if output.lower() == 'count':
104+
return Response(
105+
content=str(len(get_aria_groups_for_frame(reference)[1])),
106+
status_code=200,
107+
media_type='text/html; charset=utf-8',
108+
headers=constants.DEFAULT_HEADERS
109+
)
110+
111+
stack = stack_aria_gunw(reference)
112+
response_info = as_output(stack, output=output)
113+
return Response(**response_info)
108114
# Load the reference scene:
109115

110116
if output.lower() == 'python':

src/SearchAPI/application/asf_opts.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from asf_search.ASFSearchOptions import validator_map
1111

1212
from .asf_env import load_config_maturity
13-
13+
from .SearchAPISession import SearchAPISession
1414
from .logger import api_logger
1515

1616
non_search_param = ['output', 'maxresults', 'pagesize', 'maturity']
@@ -147,7 +147,7 @@ async def get_body(request: Request):
147147
return {}
148148

149149

150-
async def process_search_request(request: Request) -> SearchOptsModel:
150+
async def process_search_request(request: Request, is_baseline: bool = False) -> SearchOptsModel:
151151
"""
152152
Extracts the request's query+body params, returns ASFSearchOptions, request method, output format, and a dictionary
153153
of the merged request args wrapped in a pydantic model (SearchOptsModel)
@@ -166,7 +166,7 @@ async def process_search_request(request: Request) -> SearchOptsModel:
166166
merged_args = {**query_params, **body}
167167

168168
if (token := merged_args.get('cmr_token')):
169-
session = asf.ASFSession()
169+
session = SearchAPISession()
170170
session.headers.update({'Authorization': 'Bearer {0}'.format(token)})
171171
query_opts.session = session
172172

@@ -177,13 +177,25 @@ async def process_search_request(request: Request) -> SearchOptsModel:
177177

178178
try:
179179
# we are no longer allowing unbounded searches
180-
if query_opts.granule_list is None and query_opts.product_list is None:
180+
if (
181+
query_opts.granule_list is None
182+
and query_opts.product_list is None
183+
and output not in ['python', 'count']
184+
and not is_baseline
185+
):
181186
if query_opts.maxResults is None:
182-
query_opts.maxResults = asf.search_count(opts=query_opts)
187+
maxResults = asf.search_count(opts=query_opts)
188+
if maxResults > 2000:
189+
raise ValueError(
190+
(
191+
'SearchAPI no longer supports unbounded searches with expected results over 2000, '
192+
'please use the asf-search python module for long-lived searches or set `maxResults` to 2000 or less. '
193+
'To have SearchAPI automatically generate a python script for the equivalent search to your SearchAPI query '
194+
'set `output=python`'
195+
)
196+
)
183197
elif query_opts.maxResults <= 0:
184-
raise ValueError(f'Search keyword "maxResults" must be greater than 0')
185-
186-
query_opts.maxResults = min(1500, query_opts.maxResults)
198+
raise ValueError('Search keyword "maxResults" must be greater than 0')
187199

188200
searchOpts = SearchOptsModel(opts=query_opts, output=output, merged_args=merged_args, request_method=request.method)
189201
except (ValueError, ValidationError) as exc:
@@ -194,7 +206,7 @@ async def process_search_request(request: Request) -> SearchOptsModel:
194206

195207
async def process_baseline_request(request: Request) -> BaselineSearchOptsModel:
196208
"""Processes request to baseline endpoint"""
197-
searchOpts = await process_search_request(request=request)
209+
searchOpts = await process_search_request(request=request, is_baseline=True)
198210
reference = searchOpts.merged_args.get('reference')
199211
try:
200212
baselineSearchOpts = BaselineSearchOptsModel(**searchOpts.model_dump(), reference=reference)

src/SearchAPI/application/search.py

Lines changed: 12 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,21 @@
22

33
import dateparser
44
import asf_search as asf
5+
from asf_search import ASFSearchResults, ASFProduct
56
from shapely.wkt import dumps as dump_to_wkt
67
from shapely import Polygon
78

8-
def stack_aria_gunw(frame: str):
9-
reference = asf.search(frame=int(frame), dataset=asf.DATASET.ARIA_S1_GUNW, maxResults=1)[0]
10-
11-
opts = asf.ASFSearchOptions(
12-
relativeOrbit=reference.properties['pathNumber'],
13-
processingLevel=asf.PRODUCT_TYPE.SLC,
14-
dataset=asf.DATASET.SENTINEL1,
15-
beamMode='IW',
16-
polarization=['VV','VV+VH'],
17-
flightDirection=reference.properties['flightDirection'],
18-
intersectsWith=dump_to_wkt(Polygon(reference.geometry['coordinates'][0]))
19-
)
20-
21-
slc_stack = asf.search(opts=opts)
22-
23-
groups = defaultdict(list)
24-
for product in slc_stack:
25-
group_id = product.properties['platform'] + '_' + str(product.properties['orbit'])
26-
groups[group_id].append(product)
27-
# dateparser.parse(str(value))
28-
aria_groups = [
29-
{
30-
'date': min(dateparser.parse(product.properties['startTime']) for product in group),
31-
'products': [product for product in group],
32-
}
33-
for group in groups.values()
34-
]
35-
36-
# track group index on each product, naively choose first granule available
37-
for idx, group in enumerate(aria_groups):
38-
group_granule_idx = None
39-
for idy, product in enumerate(group['products']):
40-
product.properties['groupIDX'] = idx
41-
if group_granule_idx is None:
42-
if product.has_baseline():
43-
group_granule_idx = idy
44-
45-
group['group_granule_idx'] = group_granule_idx
9+
from asf_enumeration import aria_s1_gunw
4610

11+
def stack_aria_gunw(frame_id: str):
12+
reference, aria_groups = get_aria_groups_for_frame(frame_id)
13+
14+
stack = ASFSearchResults([group.products[0] for group in aria_groups])
15+
target_stack, warnings = asf.baseline.get_baseline_from_stack(reference, stack)
4716

17+
return target_stack
4818

49-
stack = asf.ASFSearchResults([group['products'][group['group_granule_idx']] for group in aria_groups if group['group_granule_idx'] is not None])
50-
target_stack, warnings = asf.baseline.get_baseline_from_stack(reference, stack)
51-
for product in target_stack:
52-
group_idx = product.properties.pop('groupIDX')
53-
aria_groups[group_idx]['perpendicularBaseline'] = product.properties['perpendicularBaseline']
54-
aria_groups[group_idx]['temporalBaseline'] = product.properties['temporalBaseline']
55-
56-
for group in aria_groups:
57-
for idx, product in enumerate(group['products']):
58-
group['products'][idx] = product.properties['sceneName']
59-
group['date'] = group['date'].strftime('%Y-%m-%dT%H:%M:%SZ')
60-
61-
return aria_groups
19+
def get_aria_groups_for_frame(frame: str) -> tuple[ASFProduct, list[aria_s1_gunw.Sentinel1Acquisition]]:
20+
aria_frame = aria_s1_gunw.get_frame(frame_id=int(frame))
21+
groups = aria_s1_gunw.get_acquisitions(aria_frame)
22+
return groups[0].products[0], groups

0 commit comments

Comments
 (0)