diff --git a/README.md b/README.md index 57158a4..6281aa2 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ Options: -m, --max-depth INTEGER Maximum depth to traverse when recursing. Omit this argument to get full recursion. Ignored if `recursive == False`. + -i, --item_collection Lint and validate a stac api item collection + response. -r, --recursive Recursively validate all related stac objects. --help Show this message and exit. Show this message and exit. ``` @@ -38,6 +40,12 @@ $ make build $ make shell ``` --- +### Item Collection Example + +``` +$ stac-check https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a/items --item_collection +``` +--- ### Lint JSON ``` @@ -49,9 +57,26 @@ for k, v in linter.create_best_practices_dict().items(): print(k, ":", v) ``` --- -### CLI Examples +### Lint in-memory dictionary + +``` +from stac_check.lint import Linter + +stac_item = { + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", +} + +linter = Linter(stac_item, assets=True) + +for k, v in linter.create_best_practices_dict().items(): + print(k, ":", v) +``` +--- +### CLI Example Output -``` stac-check https://raw.githubusercontent.com/stac-utils/pystac/main/tests/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json --recursive ``` +``` $ stac-check https://raw.githubusercontent.com/stac-utils/pystac/main/tests/data-files/examples/0.9.0/collection-spec/examples/landsat-collection.json --recursive ``` ``` ____ ____ __ ___ ___ _ _ ____ ___ __ _ / ___)(_ _)/ _\ / __)___ / __)/ )( \( __)/ __)( / ) @@ -96,7 +121,7 @@ Error Message: Expecting value: line 1 column 1 (char 0) ------------------------- ``` -``` stac-check sample_files/0.9.0/landsat8-sample.json``` +``` $ stac-check sample_files/0.9.0/landsat8-sample.json```
stac-check: STAC spec validaton and linting tool
 
@@ -159,7 +184,7 @@ This object has 4 links
 
 
    
-``` stac-check sample_files/1.0.0/core-item-bad-links.json --links --assets```    
+``` $ stac-check sample_files/1.0.0/core-item-bad-links.json --links --assets```    
 
 stac-check: STAC spec validaton and linting tool
 
@@ -201,7 +226,7 @@ LINK request errors:
 This object has 4 links
 
-``` stac-check sample_files/0.9.0/bad-item.json``` +``` $ stac-check sample_files/0.9.0/bad-item.json```
 stac-check: STAC spec validaton and linting tool
 
diff --git a/examples/lint_dict.py b/examples/lint_dict.py
new file mode 100644
index 0000000..d28ca00
--- /dev/null
+++ b/examples/lint_dict.py
@@ -0,0 +1,137 @@
+from stac_check.lint import Linter
+
+file = {
+    "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": None,
+        "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"
+        }
+    }
+}
+linter = Linter(file, assets=True)
+
+print(f"valid: {linter.valid_stac}")
+print(f"error_type: {linter.error_type}")
+print(f"error_msg: {linter.error_msg}")
+print(f"schema: {linter.schema}")
+print(f"file_name: {linter.file_name}")
+print("------")
+for k,v in linter.create_best_practices_dict().items():
+    print(k,":",v)
diff --git a/sample_files/1.0.0/item_collection.json b/sample_files/1.0.0/item_collection.json
new file mode 100644
index 0000000..9d6d9dd
--- /dev/null
+++ b/sample_files/1.0.0/item_collection.json
@@ -0,0 +1,234 @@
+{
+    "type": "FeatureCollection",
+    "features": [
+        {
+            "stac_version": "1.0.0",
+            "stac_extensions": [],
+            "type": "Feature",
+            "id": "20201211_223832_CS2_A",
+            "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"
+                }
+            }
+        },
+        {
+            "stac_version": "1.0.0",
+            "stac_extensions": [],
+            "type": "Feature",
+            "id": "20201211_223832_CS2_B",
+            "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"
+                }
+            }
+        }
+    ],
+    "links": [
+        {
+            "rel": "next",
+            "href": "https://stac-api.example.com/search?page=3",
+            "type": "application/geo+json"
+
+        },
+        {
+            "rel": "prev",
+            "href": "https://stac-api.example.com/search?page=1",
+            "type": "application/geo+json"
+        }
+    ]
+}
diff --git a/stac_check/cli.py b/stac_check/cli.py
index dee6d90..66168ac 100644
--- a/stac_check/cli.py
+++ b/stac_check/cli.py
@@ -20,6 +20,27 @@ def link_asset_message(link_list:list, type: str, format: str) -> None:
     else:
         click.secho(f"No {type.upper()} {format} errors!", fg="green")
 
+def api_collection(linter):
+    counter = 0
+    data = linter.data
+    if data["type"] == "FeatureCollection":
+        for item in data["features"]:
+            lint = Linter(item=item)
+            cli_message(lint)
+            if lint.version == "1.0.0":
+                click.secho(lint.set_update_message(), fg='green')
+            else:
+                click.secho(lint.set_update_message(), fg='red')
+            click.secho()
+            counter = counter + 1
+        click.secho("----------------------------------")
+        click.secho(f"item collection: {counter} items analyzed!", fg="blue", bold=True)
+        click.secho("----------------------------------")
+    else:
+        click.secho("-------------------------")
+        click.secho("The response is not a proper item collection.", fg="red")
+        click.secho("-------------------------")
+
 def recursive_message(linter: Linter) -> None:
     """Displays messages related to the recursive validation of assets in a collection or catalog.
 
@@ -48,7 +69,7 @@ def recursive_message(linter: Linter) -> None:
             click.secho(f"Error Message: {msg['error_message']}", fg='red')
         click.secho("-------------------------")
 
-def intro_message(linter: Linter) -> None:
+def intro_message(linter: Linter, skip_version):
     """Prints an introduction message for the stac-check tool.
 
     The message includes the stac-check logo, the name of the tool, the version
@@ -74,10 +95,11 @@ def intro_message(linter: Linter) -> None:
 
     click.secho()
 
-    if linter.version == "1.0.0":
-        click.secho(linter.set_update_message(), fg='green')
-    else:
-        click.secho(linter.set_update_message(), fg='red')
+    if not skip_version:
+        if linter.version == "1.0.0":
+            click.secho(linter.set_update_message(), fg='green')
+        else:
+            click.secho(linter.set_update_message(), fg='red')
 
     click.secho()
 
@@ -157,7 +179,13 @@ def cli_message(linter: Linter) -> None:
     "--recursive",
     "-r",
     is_flag=True,
-    help="Recursively validate all related stac objects.",
+    help="Recursively lint and validate all related stac objects.",
+)
+@click.option(
+    "--item_collection",
+    "-i",
+    is_flag=True,
+    help="Lint and validate a stac api item collection response.",
 )
 @click.option(
     "--max-depth",
@@ -174,10 +202,19 @@ def cli_message(linter: Linter) -> None:
 @click.command()
 @click.argument('file')
 @click.version_option(version=pkg_resources.require("stac-check")[0].version)
-def main(file, recursive, max_depth, assets, links):
-    linter = Linter(file, assets=assets, links=links, recursive=recursive, max_depth=max_depth)
-    intro_message(linter)
+def main(file, recursive, item_collection, max_depth, assets, links):
+    linter = Linter(
+        item=file,
+        assets=assets,
+        links=links,
+        recursive=recursive,
+        item_collection=item_collection,
+        max_depth=max_depth
+    )
+    intro_message(linter, skip_version=item_collection)
     if recursive > 0:
         recursive_message(linter)
+    elif item_collection > 0:
+        api_collection(linter)
     else:
         cli_message(linter)
\ No newline at end of file
diff --git a/stac_check/lint.py b/stac_check/lint.py
index 30f7c4d..26bcd66 100644
--- a/stac_check/lint.py
+++ b/stac_check/lint.py
@@ -122,6 +122,7 @@ def check_summaries(self) -> bool:
     assets: bool = False
     links: bool = False
     recursive: bool = False
+    item_collection: bool = False
     max_depth: Optional[int] = None
 
     def __post_init__(self):