diff --git a/CHANGELOG.md b/CHANGELOG.md index dab3deb..da0efd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,21 @@ All notable changes to this project will be documented in this file. The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [v0.1.1] - 2021-11-26 - 2021-12-12 -### Changed +## [v0.1.2] - 2022-01-17 - 2022-01-22 +### Added +- Check for null datetime +- Check for unlocated items, bbox should be set to null if geometry is -- Remove pipenv +## [v0.1.1] - 2021-11-26 - 2021-12-12 +### Added +- Added github actions to test and push to pypi +- Added makefile, dockerfile +### Changed +- Removed pipenv ## [v0.1.0] - 2021-11-26 - 2021-12-05 ### Added - - Best practices - searchable identifiers - lowercase, numbers, '_' or '-' for id names https://github.com/radiantearth/stac-spec/blob/master/best-practices.md#searchable-identifiers @@ -26,8 +32,4 @@ The format is (loosely) based on [Keep a Changelog](http://keepachangelog.com/) - Best practices check for too many links in object - Best practices check for summaries in collections - Validation from stac-validator 2.3.0 -- Links and assets validation checks - -### Changed - -- \ No newline at end of file +- Links and assets validation checks \ No newline at end of file diff --git a/sample_files/1.0.0/core-item-null-datetime.json b/sample_files/1.0.0/core-item-null-datetime.json new file mode 100644 index 0000000..6bb2ded --- /dev/null +++ b/sample_files/1.0.0/core-item-null-datetime.json @@ -0,0 +1,125 @@ +{ + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + 172.91173669923782, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3438851951615003 + ], + [ + 172.95469614953714, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3690476620161975 + ], + [ + 172.91173669923782, + 1.3438851951615003 + ] + ] + ] + }, + "properties": { + "title": "Core Item", + "description": "A sample STAC Item that includes examples of all common metadata", + "datetime": null, + "start_datetime": "2020-12-11T22:38:32.125Z", + "end_datetime": "2020-12-11T22:38:32.327Z", + "created": "2020-12-12T01:48:13.725Z", + "updated": "2020-12-12T01:48:13.725Z", + "platform": "cool_sat1", + "instruments": [ + "cool_sensor_v1" + ], + "constellation": "ion", + "mission": "collection 5624", + "gsd": 0.512 + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html", + "title": "HTML version of this STAC Item" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff;" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } + } \ No newline at end of file diff --git a/sample_files/1.0.0/core-item-unlocated.json b/sample_files/1.0.0/core-item-unlocated.json new file mode 100644 index 0000000..e38a8ff --- /dev/null +++ b/sample_files/1.0.0/core-item-unlocated.json @@ -0,0 +1,99 @@ +{ + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", + "id": "20201211_223832_CS2", + "bbox": [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ], + "geometry": null, + "properties": { + "title": "Core Item", + "description": "A sample STAC Item that includes examples of all common metadata", + "datetime": null, + "start_datetime": "2020-12-11T22:38:32.125Z", + "end_datetime": "2020-12-11T22:38:32.327Z", + "created": "2020-12-12T01:48:13.725Z", + "updated": "2020-12-12T01:48:13.725Z", + "platform": "cool_sat1", + "instruments": [ + "cool_sensor_v1" + ], + "constellation": "ion", + "mission": "collection 5624", + "gsd": 0.512 + }, + "collection": "simple-collection", + "links": [ + { + "rel": "collection", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "root", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "parent", + "href": "./collection.json", + "type": "application/json", + "title": "Simple Example Collection" + }, + { + "rel": "alternate", + "type": "text/html", + "href": "http://remotedata.io/catalog/20201211_223832_CS2/index.html", + "title": "HTML version of this STAC Item" + } + ], + "assets": { + "analytic": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic", + "roles": [ + "data" + ] + }, + "thumbnail": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg", + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ] + }, + "visual": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "3-Band Visual", + "roles": [ + "visual" + ] + }, + "udm": { + "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic_udm.tif", + "title": "Unusable Data Mask", + "type": "image/tiff; application=geotiff;" + }, + "json-metadata": { + "href": "http://remotedata.io/catalog/20201211_223832_CS2/extended-metadata.json", + "title": "Extended Metadata", + "type": "application/json", + "roles": [ + "metadata" + ] + }, + "ephemeris": { + "href": "http://cool-sat.com/catalog/20201211_223832_CS2/20201211_223832_CS2.EPH", + "title": "Satellite Ephemeris Metadata" + } + } + } \ No newline at end of file diff --git a/setup.py b/setup.py index 883237e..f096db6 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ """ from setuptools import setup, find_packages -__version__ = "0.1.1" +__version__ = "0.1.2" with open("README.md", "r") as fh: long_description = fh.read() diff --git a/stac_check/lint.py b/stac_check/lint.py index fcf365f..1daf55e 100644 --- a/stac_check/lint.py +++ b/stac_check/lint.py @@ -32,6 +32,8 @@ def __post_init__(self): self.summaries = self.check_summaries() self.num_links = self.get_num_links() self.recursive_error_msg = "" + self.datetime_null = self.check_datetime() + self.unlocated = self.check_geometry() self.validate_all = self.recursive_validation(self.load_data(self.item)) self.object_id = self.return_id() self.file_name = self.get_file_name() @@ -111,10 +113,7 @@ def check_error_message(self): return "" def check_summaries(self): - if "summaries" in self.data: - return True - else: - return False + return "summaries" in self.data def get_num_links(self): if "links" in self.data: @@ -128,6 +127,18 @@ def return_id(self): else: return "" + def check_datetime(self): + if "properties" in self.data: + if "datetime" in self.data["properties"]: + if self.data["properties"]["datetime"] == None: + return True + else: + return False + + def check_geometry(self): + if "geometry" in self.data: + return self.data["geometry"] is None and self.data["bbox"] is not None + def get_file_name(self): return os.path.basename(self.item).split('.')[0] @@ -141,9 +152,7 @@ def check_searchable_identifiers(self): return True def check_percent_encoded(self): - if self.asset_type == "ITEM" and "/" in self.object_id or ":" in self.object_id: - return False - return True + return self.asset_type == "ITEM" and "/" in self.object_id or ":" in self.object_id def create_best_practices_msg(self): best_practices = list() @@ -158,16 +167,15 @@ def create_best_practices_msg(self): best_practices.extend([string_1, string_2, string_3, ""]) # best practices - item ids should not contain ':' or '/' characters - if self.percent_encoded == False: + if self.percent_encoded: string_1 = f" Item name '{self.object_id}' should not contain ':' or '/'" string_2 = f" https://github.com/radiantearth/stac-spec/blob/master/best-practices.md#item-ids" best_practices.extend([string_1, string_2, ""]) # best practices - item ids should match file names if self.asset_type == "ITEM" and self.object_id != self.file_name: - string_1 = f" Item names should match their ids" - string_2 = f" '{self.file_name}' not equal to '{self.object_id}'" - best_practices.extend([string_1, string_2, ""]) + string_1 = f" Item file names should match their ids: '{self.file_name}' not equal to '{self.object_id}" + best_practices.extend([string_1, ""]) # best practices - collections should contain summaries if self.asset_type == "COLLECTION" and self.summaries == False: @@ -175,12 +183,20 @@ def create_best_practices_msg(self): string_2 = f" https://github.com/radiantearth/stac-spec/blob/master/collection-spec/collection-spec.md" best_practices.extend([string_1, string_2, ""]) + # best practices - datetime files should not be set to null + if self.datetime_null: + string_1 = f" Please avoid setting the datetime field to null, many clients search on this field" + best_practices.extend([string_1, ""]) + + # best practices - check unlocated items to make sure bbox field is not set + if self.unlocated: + string_1 = f" Unlocated item. Please avoid setting the bbox field when goemetry is set to null" + best_practices.extend([string_1, ""]) + + # check to see if there are too many links if self.num_links >= 20: string_1 = f" You have {self.num_links} links. Please consider using sub-collections or sub-catalogs" string_2 = f" https://github.com/radiantearth/stac-spec/blob/master/best-practices.md#catalog--collection-practices" best_practices.extend([string_1, string_2, ""]) - for x in best_practices: - print(x) - return best_practices \ No newline at end of file diff --git a/tests/test_lint.py b/tests/test_lint.py index fb0a2c8..5392b9e 100644 --- a/tests/test_lint.py +++ b/tests/test_lint.py @@ -118,4 +118,15 @@ def test_linter_item_id_format_best_practices(): file = "sample_files/1.0.0/core-item-invalid-id.json" linter = Linter(file) assert linter.searchable_identifiers == False - assert linter.percent_encoded == False + assert linter.percent_encoded == True + +def test_datetime_set_to_null(): + file = "sample_files/1.0.0/core-item-null-datetime.json" + linter = Linter(file) + assert linter.datetime_null == True + +def test_unlocated_item(): + file = "sample_files/1.0.0/core-item-unlocated.json" + linter = Linter(file) + assert linter.unlocated == True +