@@ -185,107 +185,12 @@ def _identify_stac_extensions(
185
185
186
186
# checksum
187
187
if "links" in d :
188
- found_checksum = False
189
188
for link in d ["links" ]:
190
- # Account for old links as dicts
191
- if isinstance (link , str ):
192
- link_props = cast (Dict [str , Any ], d ["links" ][link ]).keys ()
193
- else :
194
- link_props = cast (Dict [str , Any ], link ).keys ()
189
+ link_props = cast (Dict [str , Any ], link ).keys ()
195
190
196
191
if any (prop .startswith ("checksum:" ) for prop in link_props ):
197
- found_checksum = True
198
192
stac_extensions .add (OldExtensionShortIDs .CHECKSUM .value )
199
- if not found_checksum :
200
- if "assets" in d :
201
- for asset in d ["assets" ].values ():
202
- asset_props = cast (Dict [str , Any ], asset ).keys ()
203
- if any (prop .startswith ("checksum:" ) for prop in asset_props ):
204
- found_checksum = True
205
- stac_extensions .add (OldExtensionShortIDs .CHECKSUM .value )
206
- if found_checksum :
207
- version_range .set_min (STACVersionID ("0.6.2" ))
208
-
209
- # datacube
210
- if object_type == pystac .STACObjectType .ITEM :
211
- if any (k .startswith ("cube:" ) for k in cast (Dict [str , Any ], d ["properties" ])):
212
- stac_extensions .add (OldExtensionShortIDs .DATACUBE .value )
213
- version_range .set_min (STACVersionID ("0.6.1" ))
214
-
215
- # datetime-range (old extension)
216
- if object_type == pystac .STACObjectType .ITEM :
217
- if "dtr:start_datetime" in d ["properties" ]:
218
- stac_extensions .add ("datetime-range" )
219
- version_range .set_min (STACVersionID ("0.6.0" ))
220
-
221
- # eo
222
- if object_type == pystac .STACObjectType .ITEM :
223
- if any (k .startswith ("eo:" ) for k in cast (Dict [str , Any ], d ["properties" ])):
224
- stac_extensions .add (OldExtensionShortIDs .EO .value )
225
- if "eo:epsg" in d ["properties" ]:
226
- if d ["properties" ]["eo:epsg" ] is None :
227
- version_range .set_min (STACVersionID ("0.6.1" ))
228
- if "eo:crs" in d ["properties" ]:
229
- version_range .set_max (STACVersionID ("0.4.1" ))
230
- if "eo:constellation" in d ["properties" ]:
231
- version_range .set_min (STACVersionID ("0.6.0" ))
232
- if "eo:bands" in d :
233
- stac_extensions .add (OldExtensionShortIDs .EO .value )
234
- version_range .set_max (STACVersionID ("0.5.2" ))
235
-
236
- # pointcloud
237
- if object_type == pystac .STACObjectType .ITEM :
238
- if any (k .startswith ("pc:" ) for k in cast (Dict [str , Any ], d ["properties" ])):
239
- stac_extensions .add (OldExtensionShortIDs .POINTCLOUD .value )
240
- version_range .set_min (STACVersionID ("0.6.2" ))
241
-
242
- # sar
243
- if object_type == pystac .STACObjectType .ITEM :
244
- if any (k .startswith ("sar:" ) for k in cast (Dict [str , Any ], d ["properties" ])):
245
- stac_extensions .add (OldExtensionShortIDs .SAR .value )
246
- version_range .set_min (STACVersionID ("0.6.2" ))
247
- if version_range .contains ("0.6.2" ):
248
- for prop in [
249
- "sar:absolute_orbit" ,
250
- "sar:resolution" ,
251
- "sar:pixel_spacing" ,
252
- "sar:looks" ,
253
- ]:
254
- if prop in d ["properties" ]:
255
- if isinstance (d ["properties" ][prop ], list ):
256
- version_range .set_max (STACVersionID ("0.6.2" ))
257
- if version_range .contains ("0.7.0" ):
258
- for prop in [
259
- "sar:incidence_angle" ,
260
- "sar:relative_orbit" ,
261
- "sar:observation_direction" ,
262
- "sar:resolution_range" ,
263
- "sar:resolution_azimuth" ,
264
- "sar:pixel_spacing_range" ,
265
- "sar:pixel_spacing_azimuth" ,
266
- "sar:looks_range" ,
267
- "sar:looks_azimuth" ,
268
- "sar:looks_equivalent_number" ,
269
- ]:
270
- if prop in d ["properties" ]:
271
- version_range .set_min (STACVersionID ("0.7.0" ))
272
- if "sar:absolute_orbit" in d ["properties" ] and not isinstance (
273
- d ["properties" ]["sar:absolute_orbit" ], list
274
- ):
275
- version_range .set_min (STACVersionID ("0.7.0" ))
276
- if "sar:off_nadir" in d ["properties" ]:
277
- version_range .set_max (STACVersionID ("0.6.2" ))
278
-
279
- # scientific
280
- if (
281
- object_type == pystac .STACObjectType .ITEM
282
- or object_type == pystac .STACObjectType .COLLECTION
283
- ):
284
- if "properties" in d :
285
- prop_keys = cast (Dict [str , Any ], d ["properties" ]).keys ()
286
- if any (k .startswith ("sci:" ) for k in prop_keys ):
287
- stac_extensions .add (OldExtensionShortIDs .SCIENTIFIC .value )
288
- version_range .set_min (STACVersionID ("0.6.0" ))
193
+ version_range .set_min (STACVersionID ("0.6.2" ))
289
194
290
195
# Single File STAC
291
196
if object_type == pystac .STACObjectType .ITEMCOLLECTION :
@@ -298,40 +203,59 @@ def _identify_stac_extensions(
298
203
return list (stac_extensions )
299
204
300
205
301
- def identify_stac_object_type (json_dict : Dict [str , Any ]) -> "STACObjectType_Type" :
302
- """Determines the STACObjectType of the provided JSON dict.
206
+ def identify_stac_object_type (
207
+ json_dict : Dict [str , Any ]
208
+ ) -> Optional ["STACObjectType_Type" ]:
209
+ """Determines the STACObjectType of the provided JSON dict. If the JSON dict does
210
+ not represent a STAC object, returns ``None``.
303
211
304
- Args:
305
- json_dict : The dict of STAC JSON to identify.
212
+ Will first try to identify the object using ``"type"`` field as described in the
213
+ guidelines in :stac-spec:`How to Differentiate STAC Files
214
+ <best-practices.md#how-to-differentiate-stac-files>`. If this fails, will fall back
215
+ to using the pre-1.0 heuristic described in `this issue
216
+ <https://github.com/radiantearth/stac-spec/issues/889#issuecomment-684529444>`__
306
217
307
- Returns :
308
- STACObjectType : The object type represented by the JSON .
218
+ Args :
219
+ json_dict : The dict of JSON to identify .
309
220
"""
310
- object_type = None
311
-
312
- if "type" in json_dict : # Try to identify using 'type' property
221
+ # Try to identify using 'type' property, if present
222
+ if "type" in json_dict :
223
+ # Try to find 'type' property in known STACObjectType values
313
224
for t in pystac .STACObjectType :
314
225
if json_dict ["type" ].lower () == t .value .lower ():
315
- object_type = t
316
- break
317
-
318
- if object_type is None : # Use old-approach based on other properties
319
- # Identify pre-1.0 ITEMCOLLECTION (since removed)
320
- if "type" in json_dict and "assets" not in json_dict :
321
- if "stac_version" in json_dict and json_dict ["stac_version" ].startswith (
322
- "0"
323
- ):
324
- if json_dict ["type" ] == "FeatureCollection" :
325
- object_type = pystac .STACObjectType .ITEMCOLLECTION
326
-
327
- if "extent" in json_dict :
328
- object_type = pystac .STACObjectType .COLLECTION
329
- elif "assets" in json_dict :
330
- object_type = pystac .STACObjectType .ITEM
226
+ return t
227
+
228
+ obj_type = json_dict .get ("type" )
229
+
230
+ # For pre-1.0 objects for version 0.8.* or later 'stac_version' must be present,
231
+ # except for in ItemCollections (which are handled in the else clause)
232
+ if "stac_version" in json_dict :
233
+ # Pre-1.0 STAC objects with 'type' == "Feature" are Items
234
+ if obj_type == "Feature" :
235
+ return pystac .STACObjectType .ITEM
236
+ # Pre-1.0 STAC objects with 'type' == "FeatureCollection" are ItemCollections
237
+ if obj_type == "FeatureCollection" :
238
+ return pystac .STACObjectType .ITEMCOLLECTION
239
+ # Anything else with a 'type' field is not a STAC object
240
+ if obj_type is not None :
241
+ return None
242
+
243
+ # Collections will contain either an 'extent' or a 'license' (or both)
244
+ if "extent" in json_dict or "license" in json_dict :
245
+ return pystac .STACObjectType .COLLECTION
246
+ # Everything else that has a stac_version is a Catalog
331
247
else :
332
- object_type = pystac .STACObjectType .CATALOG
333
-
334
- return object_type
248
+ return pystac .STACObjectType .CATALOG
249
+ else :
250
+ # Prior to STAC 0.9 ItemCollections did not have a stac_version field and could
251
+ # only be identified by the fact that all of their 'features' are STAC Items
252
+ if obj_type == "FeatureCollection" :
253
+ if all (
254
+ identify_stac_object_type (feat ) == pystac .STACObjectType .ITEM
255
+ for feat in json_dict .get ("features" , [])
256
+ ):
257
+ return pystac .STACObjectType .ITEMCOLLECTION
258
+ return None
335
259
336
260
337
261
def identify_stac_object (json_dict : Dict [str , Any ]) -> STACJSONDescription :
@@ -346,21 +270,16 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription:
346
270
"""
347
271
object_type = identify_stac_object_type (json_dict )
348
272
273
+ if object_type is None :
274
+ raise pystac .STACTypeError ("JSON does not represent a STAC object." )
275
+
349
276
version_range = STACVersionRange ()
350
277
351
278
stac_version = json_dict .get ("stac_version" )
352
279
stac_extensions = json_dict .get ("stac_extensions" , None )
353
280
354
281
if stac_version is None :
355
- if (
356
- object_type == pystac .STACObjectType .CATALOG
357
- or object_type == pystac .STACObjectType .COLLECTION
358
- ):
359
- version_range .set_max (STACVersionID ("0.5.2" ))
360
- elif object_type == pystac .STACObjectType .ITEM :
361
- version_range .set_max (STACVersionID ("0.7.0" ))
362
- else : # ItemCollection
363
- version_range .set_min (STACVersionID ("0.8.0" ))
282
+ version_range .set_min (STACVersionID ("0.8.0" ))
364
283
else :
365
284
version_range .set_to_single (stac_version )
366
285
@@ -372,7 +291,7 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription:
372
291
# if the stac_extensions property doesn't exist for everything
373
292
# but ItemCollection (except after 0.9.0, when ItemCollection also got
374
293
# the stac_extensions property).
375
- if version_range . is_earlier_than ( "0.8.0" ) or (
294
+ if (
376
295
object_type == pystac .STACObjectType .ITEMCOLLECTION
377
296
and not version_range .is_later_than ("0.8.1" )
378
297
):
@@ -390,21 +309,4 @@ def identify_stac_object(json_dict: Dict[str, Any]) -> STACJSONDescription:
390
309
# code translates the short name IDs used pre-1.0.0-RC1 to the
391
310
# relevant extension schema uri identifier.
392
311
393
- if not version_range .is_single_version ():
394
- # Final Checks
395
-
396
- if "links" in json_dict :
397
- # links were a dictionary only in 0.5
398
- if "links" in json_dict and isinstance (json_dict ["links" ], dict ):
399
- version_range .set_to_single (STACVersionID ("0.5.2" ))
400
-
401
- # self links became non-required in 0.7.0
402
- if not version_range .is_earlier_than ("0.7.0" ) and not any (
403
- filter (
404
- lambda l : cast (Dict [str , Any ], l )["rel" ] == pystac .RelType .SELF ,
405
- json_dict ["links" ],
406
- )
407
- ):
408
- version_range .set_min (STACVersionID ("0.7.0" ))
409
-
410
312
return STACJSONDescription (object_type , version_range , set (stac_extensions ))
0 commit comments