58
58
import pytz
59
59
from shapely .errors import WKTReadingError
60
60
from shapely .wkt import loads as shapely_loads
61
+ import shapely .geometry
61
62
62
63
from pygeoapi import __version__ , l10n
63
64
from pygeoapi .formatter .base import FormatterSerializationError
88
89
modify_pygeofilter , CrsTransformSpec ,
89
90
transform_bbox )
90
91
92
+ import json
93
+ from itertools import repeat
94
+ from collections import Counter
95
+ import urllib
96
+ import pprint
91
97
LOGGER = logging .getLogger (__name__ )
92
98
93
99
#: Return headers for requests (e.g:X-Powered-By)
112
118
(F_JSON , 'application/json' ),
113
119
(F_PNG , 'image/png' ),
114
120
(F_MVT , 'application/vnd.mapbox-vector-tile' ),
115
- (F_NETCDF , 'application/x-netcdf' ),
121
+ (F_NETCDF , 'application/x-netcdf' )
116
122
))
117
123
118
124
#: Locale used for system responses (e.g. exceptions)
178
184
"https://api.stacspec.org/v1.0.0/collections" ,
179
185
"https://api.stacspec.org/v1.0.0/core" ,
180
186
"https://api.stacspec.org/v1.0.0/ogcapi-features" ,
181
- "https://api.stacspec.org/v1.0.0/item-search"
187
+ "https://api.stacspec.org/v1.0.0/item-search" ,
188
+ "https://api.stacspec.org/v1.0.0/item-search#sort"
182
189
]
183
190
}
184
191
@@ -3871,6 +3878,13 @@ def get_stac_root(
3871
3878
"href" : f"{ self .base_url } /openapi?f=html"
3872
3879
})
3873
3880
3881
+ content ['links' ].append ({
3882
+ "rel" : "search" ,
3883
+ "type" : "application/geo+json" ,
3884
+ "title" : "STAC search" ,
3885
+ "href" : f"{ self .base_url } /stac/search" ,
3886
+ "method" : "GET"
3887
+ })
3874
3888
stac_collections = filter_dict_by_key_value (self .config ['resources' ],
3875
3889
'type' , 'stac-collection' )
3876
3890
@@ -3885,7 +3899,7 @@ def get_stac_root(
3885
3899
'href' : f'{ stac_url } /{ key } ' ,
3886
3900
'type' : FORMAT_TYPES [F_HTML ]
3887
3901
})
3888
-
3902
+ LOGGER . debug ( content )
3889
3903
if request .format == F_HTML : # render
3890
3904
content = render_j2_template (self .tpl_config ,
3891
3905
'stac/collection.html' ,
@@ -3971,7 +3985,12 @@ def get_stac_path(self, request: Union[APIRequest, Any],
3971
3985
if (len (list (stac_collections [dataset ]['links' ][0 ].keys ())) > 0 ):
3972
3986
content ['links' ].extend (stac_collections [dataset ]['links' ])
3973
3987
# LOGGER.debug(content['links'])
3974
-
3988
+ if (request .format != F_HTML or request .format is None ):
3989
+ if content ['type' ] == 'Feature' :
3990
+ try :
3991
+ del content ['assets' ]['default' ]
3992
+ except KeyError :
3993
+ pass
3975
3994
if request .format == F_HTML : # render
3976
3995
content ['path' ] = path
3977
3996
if 'assets' in content : # item view
@@ -4011,6 +4030,92 @@ def get_stac_path(self, request: Union[APIRequest, Any],
4011
4030
headers .pop ('Content-Type' , None )
4012
4031
return headers , HTTPStatus .OK , stac_data
4013
4032
4033
+ @gzip
4034
+ @pre_process
4035
+ @jsonldify
4036
+ def get_stac_search (self , request : Union [APIRequest , Any ]) -> Tuple [dict , int , str ]:
4037
+
4038
+ """
4039
+ Provide STAC Search
4040
+
4041
+ :param request: APIRequest instance with query params
4042
+
4043
+ :returns: tuple of headers, status code, content
4044
+ """
4045
+ if not request .is_valid ():
4046
+ return self .get_format_exception (request )
4047
+ headers = request .get_response_headers (** self .api_headers )
4048
+ headers ['Content-Type' ] = 'application/geo+json'
4049
+ # - b'{"limit": 10, "collections": ["eu_l8_EVI"], "sortby": [{"field": "collection", "direction": "asc"}]}'
4050
+ LOGGER .debug (f'STAC search (POST data) : { request ._data } ' )
4051
+ queries = json .loads (request ._data .decode ("utf-8" ))
4052
+ limit = queries ['limit' ]
4053
+ sortby = queries ['sortby' ]
4054
+ del queries ['limit' ]
4055
+ del queries ['sortby' ]
4056
+ criteria = list (queries .keys ())
4057
+ # Search : Start from collections level (biggest), then with others criteria for filtering.
4058
+ # If the search doesn't comes with collections param , make one.
4059
+ if ('collections' not in criteria ):
4060
+ LOGGER .debug ('STAC search doesn\' t contain collections' )
4061
+ root_result = self .get_stac_root (request )
4062
+ root_result = root_result [2 ]
4063
+ root_result = json .loads (root_result )['links' ]
4064
+ root_result = [r ['href' ] for r in root_result if (r ['rel' ] == 'child' )]
4065
+ root_result = [r .split ('/' )[- 1 ].split ('?' )[0 ] for r in root_result if (r .endswith ('json' ))]
4066
+ LOGGER .debug (f'STAC search find Collections : { root_result } ' )
4067
+ criteria .append ('collections' )
4068
+ queries ['collections' ] = root_result
4069
+ # Get items under each collections - super set
4070
+ collections = queries ['collections' ]
4071
+ result = list (map (self .get_stac_path , repeat (request ), collections ))
4072
+ result = [r [2 ] for r in result ]
4073
+ result = list (map (json .loads , result ))
4074
+ LOGGER .debug (f'STAC search collections :{ result } ' )
4075
+ result = [r ['links' ] for r in result ]
4076
+ result = [o ['href' ] for r in result for o in r if (o ['rel' ] == 'item' )]
4077
+ result = ['/' .join (r .split ('/' )[- 2 :]) for r in result ]
4078
+ LOGGER .debug (f'STAC search items:{ result } ' )
4079
+ result = list (map (self .get_stac_path , repeat (request ), result ))
4080
+ result = [r [2 ] for r in result ]
4081
+ result = list (map (json .loads , result ))
4082
+ LOGGER .debug (f'STAC search collections item result :{ len (result )} ' )
4083
+ # Filter's implememtation - create subset
4084
+ def _bboxWrapper (l ):
4085
+ return shapely .geometry .box (* l )
4086
+ filter_idx = Counter ()
4087
+ got_filter = False
4088
+ for filter_ in criteria :
4089
+ if (filter_ == 'bbox' ):
4090
+ got_filter = True
4091
+ bbox = queries ['bbox' ] # Assume WGS84
4092
+ LOGGER .debug (f'STAC search bbox filter: { bbox } ' )
4093
+ bbox = shapely .geometry .box (* bbox )
4094
+ items_bbox = [ obj ['bbox' ] for obj in result ]
4095
+ items_bbox = list (map (_bboxWrapper ,items_bbox ))
4096
+ items_bbox = list (map (bbox .intersects ,items_bbox ))
4097
+ find_idx = [ i for i ,x in enumerate (items_bbox ) if (x ) ]
4098
+ LOGGER .debug (f'STAC search bbox filter found : { find_idx } ' )
4099
+ filter_idx .update (find_idx )
4100
+ # 'assets': {'image': {'href': 'https://storage.googleapis.com/download/storage/v1/b/isaac_shared/o/europe_EVI%2Feu_l8_EVI%2Feu_l8_EVI0000000000-0000023296.tif?generation=1697631155438880&alt=media'
4101
+
4102
+ find_idx = list (filter_idx .keys ()) if (got_filter is True ) else list (range (len (result )))
4103
+ result = [ r for i ,r in enumerate (result ) if (i in find_idx )]
4104
+ for r in result :
4105
+ try :
4106
+ s = r ['assets' ]['image' ]['href' ]
4107
+ s = urllib .parse .unquote (s )
4108
+ s = s .split ('?' )[0 ]
4109
+ r ['assets' ]['image' ]['href' ]= "/" .join (s .split ('/' )[7 :8 ]+ s .split ('/' )[9 :])
4110
+ except KeyError :
4111
+ pass
4112
+ # "context": { "returned":len(result), "limit":"0", "matched":len(find_idx) }
4113
+ result = { "type" : "FeatureCollection" , "features" :result }
4114
+ LOGGER .debug (f'STAC search return results : { pprint .pprint (result )} ' )
4115
+
4116
+
4117
+ return headers , HTTPStatus .OK , to_json (result , self .pretty_print )
4118
+
4014
4119
def get_exception (self , status , headers , format_ , code ,
4015
4120
description ) -> Tuple [dict , int , str ]:
4016
4121
"""
0 commit comments