pax_global_header00006660000000000000000000000064150361555500014517gustar00rootroot0000000000000052 comment=270a7da99cd0ae0864e2c038249f9bf69f8b44c6 stac-pydantic-3.4.0/000077500000000000000000000000001503615555000142665ustar00rootroot00000000000000stac-pydantic-3.4.0/.github/000077500000000000000000000000001503615555000156265ustar00rootroot00000000000000stac-pydantic-3.4.0/.github/workflows/000077500000000000000000000000001503615555000176635ustar00rootroot00000000000000stac-pydantic-3.4.0/.github/workflows/cicd.yml000066400000000000000000000020671503615555000213150ustar00rootroot00000000000000name: CI # On every pull request, but only on push to main on: push: branches: - main tags: - '*' pull_request: env: LATEST_PY_VERSION: '3.13' jobs: tests: runs-on: ubuntu-latest strategy: matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install tox pre-commit pre-commit install # Run tox using the version of Python in `PATH` - name: Run Tox run: tox -e py - name: Upload Results if: ${{ matrix.python-version == env.LATEST_PY_VERSION }} uses: codecov/codecov-action@v4 with: file: ./coverage.xml flags: unittests name: ${{ matrix.python-version }} fail_ci_if_error: false stac-pydantic-3.4.0/.github/workflows/release.yml000066400000000000000000000014021503615555000220230ustar00rootroot00000000000000name: Release on: workflow_dispatch: release: types: - created jobs: release: name: release runs-on: ubuntu-latest if: ${{ github.repository }} == 'stac-utils/stac-pydantic' steps: - uses: actions/checkout@v4 - name: Set up Python 3.x uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install release dependencies run: | python -m pip install --upgrade pip python -m pip install build twine - name: Build and publish package env: TWINE_USERNAME: ${{ secrets.PYPI_STACUTILS_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_STACUTILS_PASSWORD }} run: | python -m build twine upload dist/* stac-pydantic-3.4.0/.gitignore000066400000000000000000000026171503615555000162640ustar00rootroot00000000000000PIP_EXTRA_INDEX_URL !tests/resources/*.jpg **.pyc **.log *.mat target/* src/local/* src/local-test/* *.iml .idea/ model/ .DS_Store #config.yaml **.save *.jpg **.save.* **.bak .DS_Store .mvn/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # user specific overrides tests/tests.ini tests/logging.ini # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # dotenv .env # venv .venv # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # skaffold temporary build/deploy files build.out stac-pydantic-3.4.0/.pre-commit-config.yaml000066400000000000000000000020171503615555000205470ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/abravalheri/validate-pyproject rev: v0.16 hooks: - id: validate-pyproject - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace - id: check-yaml - id: check-added-large-files args: ["--maxkb=40960"] - repo: https://github.com/PyCQA/isort rev: 5.13.2 hooks: - id: isort language_version: python - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.3.5 hooks: - id: ruff args: ["--fix"] - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.15.0 hooks: - id: mypy language_version: python # No reason to run if only tests have changed. They intentionally break typing. exclude: tests/.* additional_dependencies: - types-requests stac-pydantic-3.4.0/CHANGELOG.md000066400000000000000000000143741503615555000161100ustar00rootroot00000000000000 ## Unreleased ## 3.4.0 (2025-07-17) - Remove 'label:assets' (extension) from Link attribute (#184, @fmigneault) ## 3.3.2 (2025-06-18) - Remove restriction on valid media types for links (#182, @mishaschwartz) ## 3.3.1 (2025-06-01) - Add back `SearchDatetime` in `stac_pydantic.api.search` to avoid breaking change ## 3.3.0 (2025-05-29) - Add validation for collection's spatial intervals - Add validation for collection's time intervals - Move `validate_bbox`, `validate_datetime` and `str_to_datetimes` functions from `api.search` to `shared` sub-module - Improve `bbox` validation for Antimeridian crossing bbox ## 3.2.0 (2025-03-20) - Move `validate_bbox` and `validate_datetime` field validation functions outside the Search class (to enable re-utilization) - Remove `Search()._start_date` and ``Search()._end_date` private attributes - Add `api.search.str_to_datetime(value: str) -> List[Optional[datetime.datetime]]` function ## 3.1.5 (2025-02-28) - Fix `Search` model to make sure `_start_date` and `_end_date` privateAttr are cleared on model initialization (#72, @sbrunato and @vincentsarago) - Fix bbox validation to allow anti-meridian crossing (#167, @ujjwal360) - Add `octet_stream=application/octet-stream` to MimeTypes (#169, @tjellicoe-tpzuk) ## 3.1.4 (2025-01-08) - Fix URL comparison for Landing page conformance (#163, @gadomski) - Fix `Search` validation when `datetime` is `None` (#165, @gadomski) ## 3.1.3 (2024-10-14) - Add optional `numberMatched` and `numberReturned` to `api.collections.Collections` model to match the OGC Common part2 specification ## 3.1.2 (2024-08-20) - Add more **MimeTypes** (geojsonseq, pbf, mvt, ndjson, openapi_yaml, pdf, csv, parquet) ## 3.1.1 (2024-07-09) - Cache remote JSON schemas for extensions (#155, @avbentem) - add `requests` and `jsonschema` in a **validation** optional dependencies (#156, @vincentsarago) ## 3.1.0 (2024-05-21) - Allow extra fields in Links (#144, @jonhealy1) - Remove the deprecated `Context` extension (#138, @vincentsarago) - Rename `stac_pydantic.api.conformance.ConformanceClasses` to `stac_pydantic.api.conformance.Conformance` - Update pre-commit configuration and switch to astral-sh/ruff for linter and formater - Add official support for python 3.12 - Enforce required `type` key for `Collection` and `Catalog` models - Add queryables link relation type (#123, @constantinius) - Fix STAC API Query Extension operator names from ne->neq, le->lte, and ge->gte (#120, @philvarner) - Better **datetime** parsing/validation by using Pydantic native types and remove `ciso8601` requirement (#131, @eseglem) - move datetime validation in `StacCommonMetadata` model definition (#131, @eseglem) - use `StacBaseModel` as base model for `Asset` model (#148, @vincentsarago) - add `license` in `StacCommonMetadata` model (#147, @vincentsarago) - make `limit` optional in `api.Search` model (#150, @vincentsarago) - set `start/end datetime` to the datetime value when passing only one value in `api.Search` (#152, @vincentsarago) ## 3.0.0 (2024-01-25) - Support pydantic>2.0 (@huard) ## 2.0.3 (2022-5-3) - Allow item bbox to be null if item geometry is null (#108, @yellowcap) - Include 'children' link relation (#112, @moradology) ## 2.0.2 (2021-11-22) - Remove fields added by STAC API search extensions (#100, @rsmith013 & @moradology) - Add ExtendedSearch class with fields from STAC API search extensions (#100, @rsmith013 & @moradology) - Allow for non-ellipsis open temporal windows (#103, @moradology) - Add the canonical and service-doc relation types (#104, @moradology) ## 2.0.1 (2021-07-08) - Add bbox validator to STAC search (#95, @geospatialjeff) - Fix LandPage to make valid STAC 1.0 catalog (#96, @lossyrob) ## 2.0.0 (2020-06-29) - Add Collections model (#81, @moradology) - Update to stac version 1.0.0 (#86, @moradology) - Remove models for STAC spec extensions (#86, @moradology) - Add conformsTo to LandingPage (#90, @moradology) ## 1.3.9 (2021-03-02) - Add id to landing page, making it a valid catalog (#43, @lossyrob) - Make `item_assets` (item assets extension) a dictionary of assets (#47, @kylebarron) - Add pre-commit to CI (#48, @kylebarron) - Add a `Links` model with custom root type to represent a list of links (#52) - Move link related models to their own file (#53) - Add link factories for generating inferred links (#55) - Switch from relative to absolute imports (#61) - Serialize date type fields to `datetime.datetime` upon model creation (#62) ## 1.3.9 (2020-03-02) - Add id to landing page, making it a valid catalog (#43, @lossyrob) - Make `item_assets` (item assets extension) a dictionary of assets (#47, @kylebarron) - Add pre-commit to CI (#48, @kylebarron) - Add a `Links` model with custom root type to represent a list of links (#52) - Move link related models to their own file (#53) - Add link factories for generating inferred links (#55) - Switch from relative to absolute imports (#61) - Serialize date type fields to `datetime.datetime` upon model creation (#62) ## 1.3.8 (2020-11-21) - Remove enum restriction for asset roles (#39). - Remove enum restriction for band common name (#40). ## 1.3.7 (2020-11-15) - Rename `proj` extension to `projection` (#34). - Remove `stac_extensions` enum requirement (#35). ## 1.3.6 (2020-09-11) - Publish mypy type hints (#30) - Correct Cloud Optimized GeoTiff mime type (#31) ## 1.3.5 (2020-09-09) - Add `created` and `updated` to stac common metadata, fix aliases. (#28) ## 1.3.4 (2020-09-09) - Update to stac version 1.0.0-beta.2 (#26) ## 1.2.4 (2020-09-08) - Update to stac version 1.0.0-beta.1 (#24) ## 1.1.4 (2020-08-18) - Fix multiple inheritance of stac extensions (#20) - Properly instantiate lru_cache (found by @francbartoli, #21) ## 1.1.3 (2020-08-10) - Add item model factory (#13) - Add pre-commit hooks (#14) - Add CLI for validating items (#15) - Add option to skip validation of remote extensions (#16) - Add helper function for item validation (#17) ## 1.0.3 (2020-06-03) - Bugfixes (#10) - Add rel types enum (#11) ## 1.0.2 (2020-06-02) - Add models for the STAC API spec (#7) ## 1.0.1 (2020-05-21) - Allow extra asset-level fields (#1) - Fix population by field name model config, allowing model creation without extension namespaces (#2) - Add enum of commonly used asset media types (#3) - Move geojson models to `geojson-pydantic` library (#4) stac-pydantic-3.4.0/CONTRIBUTING.md000066400000000000000000000007441503615555000165240ustar00rootroot00000000000000# Contributing Issues and pull requests are more than welcome. **dev install** ```bash git clone https://github.com/stac-utils/stac-pydantic.git cd stac-pydantic python -m pip install -e ".[dev]" ``` You can then run the tests with the following command: ```sh python -m pytest --cov stac_pydantic --cov-report term-missing ``` **pre-commit** This repo is set to use `pre-commit` to run *ruff*, *pydocstring* and mypy when committing new code. ```bash pre-commit install ``` stac-pydantic-3.4.0/LICENSE000066400000000000000000000020521503615555000152720ustar00rootroot00000000000000MIT License Copyright (c) 2020 Arturo AI Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. stac-pydantic-3.4.0/README.md000066400000000000000000000143161503615555000155520ustar00rootroot00000000000000# stac-pydantic [![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/stac-utils/stac-pydantic/cicd.yml?style=for-the-badge)](https://github.com/stac-utils/stac-pydantic/actions/workflows/cicd.yml) [Pydantic](https://pydantic-docs.helpmanual.io/) models for [STAC](https://github.com/radiantearth/stac-spec) Catalogs, Collections, Items, and the [STAC API](https://github.com/radiantearth/stac-api-spec) spec. Initially developed by [arturo-ai](https://github.com/arturo-ai). The main purpose of this library is to provide reusable request/response models for tools such as [fastapi](https://fastapi.tiangolo.com/). For more comprehensive schema validation and robust extension support, use [pystac](https://github.com/stac-utils/pystac). ## Installation ```shell python -m pip install stac-pydantic # or python -m pip install stac-pydantic["validation"] ``` For local development: ```shell python -m pip install -e '.[dev,lint]' ``` | stac-pydantic | STAC Version | STAC API Version | Pydantic Version | |--------------|---------------|------------------|-----------------| | 1.2.x | 1.0.0-beta.1 | <1* | ^1.6 | | 1.3.x | 1.0.0-beta.2 | <1* | ^1.6 | | 2.0.x | 1.0.0 | <1* | ^1.6 | | 3.0.x | 1.0.0 | 1.0.0 | ^2.4 | | 3.1.x | 1.0.0 | 1.0.0 | ^2.4 | \* various beta releases, specs not fully implemented ## Development Install the [pre-commit](https://pre-commit.com/) hooks: ```shell pre-commit install ``` ## Testing Ensure you have all Python versions installed that the tests will be run against. If using pyenv, run: ```shell pyenv install 3.8.18 pyenv install 3.9.18 pyenv install 3.10.13 pyenv install 3.11.5 pyenv local 3.8.18 3.9.18 3.10.13 3.11.5 ``` Run the entire test suite: ```shell tox ``` Run a single test case using the standard pytest convention: ```shell python -m pytest -v tests/test_models.py::test_item_extensions ``` ## Usage ### Loading Models Load data into models with standard pydantic: ```python from stac_pydantic import Catalog stac_catalog = { "type": "Catalog", "stac_version": "1.0.0", "id": "sample", "description": "This is a very basic sample catalog.", "links": [ { "href": "item.json", "rel": "item" } ] } catalog = Catalog(**stac_catalog) assert catalog.id == "sample" assert catalog.links[0].href == "item.json" ``` ### Extensions STAC defines many extensions which let the user customize the data in their catalog. `stac-pydantic.extensions.validate_extensions` gets the JSON schemas from the URLs provided in the `stac_extensions` property (caching the last fetched ones), and will validate a `dict`, `Item`, `Collection` or `Catalog` against those fetched schemas: ```python from stac_pydantic import Item from stac_pydantic.extensions import validate_extensions stac_item = { "id": "12345", "type": "Feature", "stac_extensions": [ "https://stac-extensions.github.io/eo/v1.0.0/schema.json" ], "geometry": { "type": "Point", "coordinates": [0, 0] }, "bbox": [0.0, 0.0, 0.0, 0.0], "properties": { "datetime": "2020-03-09T14:53:23.262208+00:00", "eo:cloud_cover": 25, }, "links": [], "assets": {}, } model = Item(**stac_item) validate_extensions(model, reraise_exception=True) assert getattr(model.properties, "eo:cloud_cover") == 25 ``` The complete list of current STAC Extensions can be found [here](https://stac-extensions.github.io/). #### Vendor Extensions The same procedure described above works for any STAC Extension schema as long as it can be loaded from a public url. ### STAC API The [STAC API Specs](https://github.com/radiantearth/stac-api-spec) extent the core STAC specification for implementing dynamic catalogs. STAC Objects used in an API context should always import models from the `api` subpackage. This package extends Catalog, Collection, and Item models with additional fields and validation rules and introduces Collections and ItemCollections models and Pagination/ Search Links. It also implements models for defining ItemSeach queries. ```python from stac_pydantic.api import Item, ItemCollection stac_item = Item(**{ "id": "12345", "type": "Feature", "stac_extensions": [], "geometry": { "type": "Point", "coordinates": [0, 0] }, "bbox": [0.0, 0.0, 0.0, 0.0], "properties": { "datetime": "2020-03-09T14:53:23.262208+00:00", }, "collection": "CS3", "links": [ { "rel": "self", "href": "http://stac.example.com/catalog/collections/CS3-20160503_132130_04/items/CS3-20160503_132130_04.json" }, { "rel": "collection", "href": "http://stac.example.com/catalog/CS3-20160503_132130_04/catalog.json" }, { "rel": "root", "href": "http://stac.example.com/catalog" }], "assets": {}, }) stac_item_collection = ItemCollection(**{ "type": "FeatureCollection", "features": [stac_item], "links": [ { "rel": "self", "href": "http://stac.example.com/catalog/search?collection=CS3", "type": "application/geo+json" }, { "rel": "root", "href": "http://stac.example.com/catalog", "type": "application/json" }], }) ``` ### Exporting Models Most STAC extensions are namespaced with a colon (ex `eo:gsd`) to keep them distinct from other extensions. Because Python doesn't support the use of colons in variable names, we use [Pydantic aliasing](https://pydantic-docs.helpmanual.io/usage/model_config/#alias-generator) to add the namespace upon model export. This requires [exporting](https://pydantic-docs.helpmanual.io/usage/exporting_models/) the model with the `by_alias = True` parameter. Export methods (`model_dump()` and `model_dump_json()`) for models in this library have `by_alias` and `exclude_unset` st to `True` by default: ```python item_dict = item.model_dump() assert item_dict['properties']['landsat:row'] == item.properties.row == 250 ``` ### CLI ```text Usage: stac-pydantic [OPTIONS] COMMAND [ARGS]... stac-pydantic cli group Options: --help Show this message and exit. Commands: validate-item Validate STAC Item ``` stac-pydantic-3.4.0/pyproject.toml000066400000000000000000000053351503615555000172100ustar00rootroot00000000000000[build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name="stac-pydantic" description="Pydantic data models for the STAC spec" classifiers=[ "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", ] keywords=["stac", "pydantic", "validation"] authors=[{ name = "Arturo Engineering", email = "engineering@arturo.ai"}] license= { text = "MIT" } requires-python=">=3.8" dependencies = [ "click>=8.1.7", "pydantic>=2.4.1", "geojson-pydantic>=1.0.0", ] dynamic = ["version", "readme"] [project.scripts] stac-pydantic = "stac_pydantic.scripts.cli:app" [project.urls] homepage = "https://github.com/stac-utils/stac-pydantic" repository ="https://github.com/stac-utils/stac-pydantic.git" [project.optional-dependencies] validation = ["jsonschema>=4.19.1", "requests>=2.31.0"] dev = [ "pytest>=7.4.2", "pytest-cov>=4.1.0", "pytest-icdiff>=0.8", "requests>=2.31.0", "shapely>=2.0.1", "dictdiffer>=0.9.0", "jsonschema>=4.19.1", "pyyaml>=6.0.1" ] lint = [ "types-requests>=2.31.0.5", "types-jsonschema>=4.19.0.3", "types-PyYAML>=6.0.12.12", "black>=23.9.1", "isort>=5.12.0", "flake8>=6.1.0", "Flake8-pyproject>=1.2.3", "mypy>=1.5.1", "pre-commit>=3.4.0", "tox>=4.11.3" ] [tool.setuptools.dynamic] version = { attr = "stac_pydantic.version.__version__" } readme = {file = ["README.md"], content-type = "text/markdown"} [tool.setuptools.package-data] stac_pydantic= ["*.typed"] [tool.setuptools.packages.find] include = ["stac_pydantic*"] exclude = ["tests*"] [tool.pytest.ini_options] addopts = "-sv --cov stac_pydantic --cov-report xml --cov-report term-missing --cov-fail-under 95" [tool.isort] profile = "black" known_first_party = "stac_pydantic" known_third_party = ["pydantic", "geojson-pydantic", "click"] default_section = "THIRDPARTY" [tool.mypy] ignore_missing_imports = true exclude = ["tests", ".venv"] [tool.ruff] line-length = 88 [tool.ruff.lint] select = [ "C", "E", "F", "W", "B", ] ignore = [ "E203", # line too long, handled by black "E501", # do not perform function calls in argument defaults "B028", # No explicit `stacklevel` keyword argument found ] stac-pydantic-3.4.0/stac_pydantic/000077500000000000000000000000001503615555000171135ustar00rootroot00000000000000stac-pydantic-3.4.0/stac_pydantic/__init__.py000066400000000000000000000002501503615555000212210ustar00rootroot00000000000000# flake8: noqa: F401 from .catalog import Catalog from .collection import Collection from .item import Item, ItemProperties from .item_collection import ItemCollection stac-pydantic-3.4.0/stac_pydantic/api/000077500000000000000000000000001503615555000176645ustar00rootroot00000000000000stac-pydantic-3.4.0/stac_pydantic/api/__init__.py000066400000000000000000000015161503615555000220000ustar00rootroot00000000000000"""STAC API models.""" import warnings from .collection import Collection from .collections import Collections from .conformance import Conformance from .item import Item from .item_collection import ItemCollection from .landing import LandingPage from .search import Search __all__ = [ "Collection", "Collections", "Conformance", "Item", "ItemCollection", "LandingPage", "Search", ] # TODO: remove in 4.0 def __getattr__(name): if name == "ConformanceClasses": warnings.warn( "Class `ConformanceClasses` has been renamed to `Conformance`. Please use the new name. The old alias will be removed in a future version.", DeprecationWarning, stacklevel=2, ) return Conformance raise AttributeError(f"module {__name__!r} has no attribute {name!r}") stac-pydantic-3.4.0/stac_pydantic/api/collection.py000066400000000000000000000013611503615555000223720ustar00rootroot00000000000000from pydantic import model_validator from stac_pydantic.collection import Collection as BaseCollection from stac_pydantic.links import Relations class Collection(BaseCollection): """ https://github.com/radiantearth/stac-api-spec/tree/v1.0.0/ogcapi-features#collection-collectionscollectionid """ @model_validator(mode="after") def required_links(self) -> "Collection": links_rel = [] for link in self.links.root: links_rel.append(link.rel) required_rels = [Relations.root, Relations.self] for rel in required_rels: assert ( rel in links_rel ), f"STAC API COLLECTIONS conform Collection pages must include a `{rel}` link." return self stac-pydantic-3.4.0/stac_pydantic/api/collections.py000066400000000000000000000020631503615555000225550ustar00rootroot00000000000000from typing import List, Optional from pydantic import model_validator from stac_pydantic.api.collection import Collection from stac_pydantic.api.links import Links from stac_pydantic.links import Relations from stac_pydantic.shared import StacBaseModel class Collections(StacBaseModel): """ https://github.com/radiantearth/stac-api-spec/tree/v1.0.0/ogcapi-features#endpoints https://github.com/radiantearth/stac-api-spec/tree/v1.0.0/ogcapi-features#collections-collections """ links: Links collections: List[Collection] numberMatched: Optional[int] = None numberReturned: Optional[int] = None @model_validator(mode="after") def required_links(self) -> "Collections": links_rel = [] for link in self.links.root: links_rel.append(link.rel) required_rels = [Relations.root, Relations.self] for rel in required_rels: assert ( rel in links_rel ), f"STAC API COLLECTIONS conform Collections pages must include a `{rel}` link." return self stac-pydantic-3.4.0/stac_pydantic/api/conformance.py000066400000000000000000000003751503615555000225350ustar00rootroot00000000000000from typing import List from pydantic import AnyHttpUrl, BaseModel class Conformance(BaseModel): """ https://github.com/radiantearth/stac-api-spec/blob/master/api-spec.md#ogc-api---features-endpoints """ conformsTo: List[AnyHttpUrl] stac-pydantic-3.4.0/stac_pydantic/api/extensions/000077500000000000000000000000001503615555000220635ustar00rootroot00000000000000stac-pydantic-3.4.0/stac_pydantic/api/extensions/__init__.py000066400000000000000000000000001503615555000241620ustar00rootroot00000000000000stac-pydantic-3.4.0/stac_pydantic/api/extensions/fields.py000066400000000000000000000037171503615555000237130ustar00rootroot00000000000000from typing import Dict, Optional, Set from pydantic import BaseModel class FieldsExtension(BaseModel): """ https://github.com/radiantearth/stac-api-spec/tree/master/extensions/fields#fields-api-extension """ includes: Optional[Set[str]] = None excludes: Optional[Set[str]] = None def _get_field_dict(self, fields: Set[str]) -> Dict[str, Set[str]]: """Internal method to create a dictionary for advanced include or exclude of pydantic fields on model export Ref: https://pydantic-docs.helpmanual.io/usage/exporting_models/#advanced-include-and-exclude """ field_dict = {} for field in fields: if "." in field: parent, key = field.split(".") if parent not in field_dict: field_dict[parent] = {key} else: field_dict[parent].add(key) else: field_dict[field] = ... # type:ignore return field_dict @property def filter(self) -> Dict[str, Dict[str, Set[str]]]: """ Create dictionary of fields to include/exclude on model export based on the included and excluded fields passed to the API. The output of this property may be passed to pydantic's serialization methods to include or exclude certain keys. Ref: https://pydantic-docs.helpmanual.io/usage/exporting_models/#advanced-include-and-exclude """ include: Set[str] = set() # If only include is specified, add fields to the set if self.includes and not self.excludes: include = include.union(self.includes) # If both include + exclude specified, find the difference between sets elif self.includes and self.excludes: include = include.union(self.includes) - self.excludes return { "include": self._get_field_dict(include), "exclude": self._get_field_dict(self.excludes or Set()), } stac-pydantic-3.4.0/stac_pydantic/api/extensions/query.py000066400000000000000000000032111503615555000235770ustar00rootroot00000000000000"""Query Extension.""" import warnings from enum import auto from types import DynamicClassAttribute from typing import Any, Callable from stac_pydantic.utils import AutoValueEnum # TODO: These are defined in the spec but aren't currently implemented by the operator syntax UNSUPPORTED_OPERATORS = {"startsWith", "endsWith", "contains", "in"} _OPERATIONS = { "eq": lambda x, y: x == y, "ne": lambda x, y: x != y, # deprecated "neq": lambda x, y: x != y, "lt": lambda x, y: x < y, "le": lambda x, y: x <= y, # deprecated "lte": lambda x, y: x <= y, "gt": lambda x, y: x > y, "ge": lambda x, y: x >= y, # deprecated "gte": lambda x, y: x >= y, "startsWith": lambda x, y: x.startsWith(y), "endsWith": lambda x, y: x.endsWith(y), "contains": lambda x, y: y in x, } class Operator(str, AutoValueEnum): """ https://github.com/radiantearth/stac-api-spec/tree/master/extensions/query#query-api-extension """ eq = auto() ne = auto() # deprecated neq = auto() lt = auto() le = auto() # deprecated lte = auto() gt = auto() ge = auto() # deprecated gte = auto() startsWith = auto() endsWith = auto() contains = auto() @DynamicClassAttribute def operator(self) -> Callable[[Any, Any], bool]: """Return python operator""" if self._value_ in ["ne", "ge", "le"]: newvalue = self._value_.replace("e", "te") warnings.warn( f"`{self._value_}` is deprecated, please use `{newvalue}`", DeprecationWarning, stacklevel=3, ) return _OPERATIONS[self._value_] stac-pydantic-3.4.0/stac_pydantic/api/extensions/sort.py000066400000000000000000000006461503615555000234320ustar00rootroot00000000000000from enum import auto from pydantic import BaseModel, Field from stac_pydantic.utils import AutoValueEnum class SortDirections(str, AutoValueEnum): asc = auto() desc = auto() class SortExtension(BaseModel): """ https://github.com/radiantearth/stac-api-spec/tree/master/extensions/sort#sort-api-extension """ field: str = Field(..., alias="field", min_length=1) direction: SortDirections stac-pydantic-3.4.0/stac_pydantic/api/item.py000066400000000000000000000011271503615555000211750ustar00rootroot00000000000000from pydantic import model_validator from stac_pydantic.item import Item as BaseItem from stac_pydantic.links import Relations class Item(BaseItem): @model_validator(mode="after") def required_links(self) -> "Item": links_rel = [] for link in self.links.root: links_rel.append(link.rel) required_rels = [Relations.root, Relations.self, Relations.collection] for rel in required_rels: assert ( rel in links_rel ), f"STAC API FEATURE conform Item pages must include a `{rel}` link." return self stac-pydantic-3.4.0/stac_pydantic/api/item_collection.py000066400000000000000000000036021503615555000234100ustar00rootroot00000000000000from typing import Optional, Sequence from urllib.parse import urlparse from pydantic import model_validator from stac_pydantic.api.item import Item from stac_pydantic.api.links import Links from stac_pydantic.item_collection import ItemCollection as BaseItemCollection from stac_pydantic.links import Relations class ItemCollection(BaseItemCollection): """ https://github.com/radiantearth/stac-api-spec/blob/v1.0.0/fragments/itemcollection/README.md https://github.com/radiantearth/stac-api-spec/blob/v1.0.0/item-search/README.md#link-relations """ features: Sequence[Item] links: Optional[Links] = None numberMatched: Optional[int] = None numberReturned: Optional[int] = None @model_validator(mode="after") def required_links(self) -> "ItemCollection": if self.links: links_rel = [link.rel for link in self.links.root] if Relations.self in links_rel: self_href = [link for link in self.links.root if link.rel == "self"][0] item_collection_type = urlparse(self_href.href).path.split("/")[-1] if item_collection_type == "items": required_links = [ Relations.root, Relations.self, Relations.collection, ] for link in required_links: assert ( link in links_rel ), f"STAC API FEATURES conform Items pages must include a `{link}` link." if item_collection_type == "search": required_links = [Relations.root] for link in required_links: assert ( link in links_rel ), f"STAC API FEATURES conform Items pages must include a `{link}` link." return self stac-pydantic-3.4.0/stac_pydantic/api/landing.py000066400000000000000000000037221503615555000216560ustar00rootroot00000000000000from typing import List from pydantic import AnyUrl, HttpUrl, model_validator from stac_pydantic.api.links import Links from stac_pydantic.api.version import STAC_API_VERSION from stac_pydantic.catalog import Catalog from stac_pydantic.links import Relations class LandingPage(Catalog): """ https://github.com/radiantearth/stac-api-spec/tree/v1.0.0/core https://github.com/radiantearth/stac-api-spec/tree/v1.0.0/ogcapi-features#landing-page- https://github.com/radiantearth/stac-api-spec/tree/v1.0.0/item-search#link-relations """ conformsTo: List[AnyUrl] = [ HttpUrl("https://api.stacspec.org/v1.0.0/core"), HttpUrl("http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core"), ] links: Links @model_validator(mode="after") def required_links(self) -> "LandingPage": links_rel = [] for link in self.links.root: links_rel.append(link.rel) required_core_rels = [Relations.root, Relations.self, Relations.service_desc] for rel in required_core_rels: assert ( rel in links_rel ), f"STAC API conform Landing pages must include a `{rel}` link." if ( AnyUrl(f"https://api.stacspec.org/v{STAC_API_VERSION}/collections") in self.conformsTo ): required_collections_rels = [Relations.data] for rel in required_collections_rels: assert ( rel in links_rel ), f"STAC API COLLECTION conform Landing pages must include a `{rel}` link." if ( AnyUrl(f"https://api.stacspec.org/v{STAC_API_VERSION}/item-search") in self.conformsTo ): required_feature_rels = [Relations.search] for rel in required_feature_rels: assert ( rel in links_rel ), f"STAC API ITEM SEARCH conform Landing pages must include a `{rel}` link." return self stac-pydantic-3.4.0/stac_pydantic/api/links.py000066400000000000000000000025461503615555000213650ustar00rootroot00000000000000from typing import Any, Dict, List, Literal, Optional, Union from pydantic import Field from stac_pydantic.links import Link from stac_pydantic.links import Links as BaseLinks from stac_pydantic.links import Relations from stac_pydantic.shared import MimeTypes class _MethodLink(Link): """ https://github.com/radiantearth/stac-api-spec/blob/v1.0.0/item-search/README.md#pagination """ method: Literal["GET", "POST"] = "GET" headers: Optional[Dict[str, Any]] = None body: Optional[Dict[Any, Any]] = None merge: bool = False class PaginationLink(_MethodLink): """ https://github.com/radiantearth/stac-api-spec/blob/v1.0.0/item-search/README.md#pagination """ rel: Literal[Relations.next, Relations.prev] class SearchLink(_MethodLink): """ https://github.com/radiantearth/stac-api-spec/tree/v1.0.0/item-search#link-relations """ href: str = Field(..., pattern=r".*\/search(\?.*)?\b") type: Literal[MimeTypes.geojson] = MimeTypes.geojson rel: Literal[Relations.self, Relations.search, Relations.next, Relations.prev] class ItemsLink(_MethodLink): href: str = Field(..., pattern=r".*\/items(\?.*)?\b") type: Literal[MimeTypes.geojson] = MimeTypes.geojson rel: Literal[Relations.self, Relations.items] class Links(BaseLinks): root: List[Union[SearchLink, PaginationLink, ItemsLink, Link]] stac-pydantic-3.4.0/stac_pydantic/api/search.py000066400000000000000000000056261503615555000215140ustar00rootroot00000000000000from datetime import datetime as dt from typing import Any, Dict, List, Optional, Union from geojson_pydantic.geometries import ( GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, ) from pydantic import AfterValidator, BaseModel, Field, model_validator from typing_extensions import Annotated from stac_pydantic.api.extensions.fields import FieldsExtension from stac_pydantic.api.extensions.query import Operator from stac_pydantic.api.extensions.sort import SortExtension # TODO: remove in 4.0 from stac_pydantic.shared import SearchDatetime # noqa from stac_pydantic.shared import ( BBox, str_to_datetimes, validate_bbox, validate_datetime, ) Intersection = Union[ Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, GeometryCollection, ] class Search(BaseModel): """ The base class for STAC API searches. https://github.com/radiantearth/stac-api-spec/blob/v1.0.0/item-search/README.md#query-parameter-table """ collections: Optional[List[str]] = None ids: Optional[List[str]] = None bbox: Annotated[Optional[BBox], AfterValidator(validate_bbox)] = None intersects: Optional[Intersection] = None datetime: Annotated[Optional[str], AfterValidator(validate_datetime)] = None limit: Optional[int] = 10 @property def start_date(self) -> Optional[dt]: start_date: Optional[dt] = None if self.datetime: start_date = str_to_datetimes(self.datetime)[0] return start_date @property def end_date(self) -> Optional[dt]: end_date: Optional[dt] = None if self.datetime: dates = str_to_datetimes(self.datetime) end_date = dates[0] if len(dates) == 1 else dates[1] return end_date # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information. @model_validator(mode="before") def validate_spatial(cls, values: Dict[str, Any]) -> Dict[str, Any]: if values.get("intersects") and values.get("bbox") is not None: raise ValueError("intersects and bbox parameters are mutually exclusive") return values @property def spatial_filter(self) -> Optional[Intersection]: """Return a geojson-pydantic object representing the spatial filter for the search request. Check for both because the ``bbox`` and ``intersects`` parameters are mutually exclusive. """ if self.bbox: return Polygon.from_bounds(*self.bbox) if self.intersects: return self.intersects else: return None class ExtendedSearch(Search): """ STAC API search with extensions enabled. """ field: Optional[FieldsExtension] = Field(None, alias="fields") query: Optional[Dict[str, Dict[Operator, Any]]] = None sortby: Optional[List[SortExtension]] = None stac-pydantic-3.4.0/stac_pydantic/api/utils/000077500000000000000000000000001503615555000210245ustar00rootroot00000000000000stac-pydantic-3.4.0/stac_pydantic/api/utils/__init__.py000066400000000000000000000000001503615555000231230ustar00rootroot00000000000000stac-pydantic-3.4.0/stac_pydantic/api/utils/link_factory.py000066400000000000000000000051561503615555000240710ustar00rootroot00000000000000from dataclasses import dataclass from typing import ClassVar, Tuple from urllib.parse import urljoin from stac_pydantic.links import Link, Links, Relations from stac_pydantic.shared import MimeTypes @dataclass class BaseLinks: """Create inferred links common to collections and items.""" base_url: str _link_members: ClassVar[Tuple[str, ...]] = ("root",) def root(self) -> Link: """Return the catalog root.""" return Link( rel=Relations.root, type=MimeTypes.json, href=urljoin(self.base_url, "/") ) def create_links(self) -> Links: """Create inferred links""" return Links.model_validate( [getattr(self, member)() for member in self._link_members] ) @dataclass class CollectionLinks(BaseLinks): """Create inferred links specific to collections.""" collection_id: str _link_members: ClassVar[Tuple[str, ...]] = ("root", "self", "parent", "items") def self(self) -> Link: """Create the `self` link.""" return Link( rel=Relations.self, type=MimeTypes.json, href=urljoin(self.base_url, f"/collections/{self.collection_id}"), ) def parent(self) -> Link: """Create the `parent` link.""" return Link( rel=Relations.parent, type=MimeTypes.json, href=urljoin(self.base_url, "/") ) def items(self) -> Link: """Create the `item` link.""" return Link( rel=Relations.items, type=MimeTypes.geojson, href=urljoin(self.base_url, f"/collections/{self.collection_id}/items"), ) @dataclass class ItemLinks(BaseLinks): """Create inferred links specific to items.""" collection_id: str item_id: str _link_members: ClassVar[Tuple[str, ...]] = ("root", "self", "parent", "collection") def self(self) -> Link: """Create the `self` link.""" return Link( rel=Relations.self, type=MimeTypes.geojson, href=urljoin( self.base_url, f"/collections/{self.collection_id}/items/{self.item_id}" ), ) def parent(self) -> Link: """Create the `parent` link.""" return Link( rel=Relations.parent, type=MimeTypes.json, href=urljoin(self.base_url, f"/collections/{self.collection_id}"), ) def collection(self) -> Link: """Create the `collection` link.""" return Link( rel=Relations.collection, type=MimeTypes.json, href=urljoin(self.base_url, f"/collections/{self.collection_id}"), ) stac-pydantic-3.4.0/stac_pydantic/api/version.py000066400000000000000000000000331503615555000217170ustar00rootroot00000000000000STAC_API_VERSION = "1.0.0" stac-pydantic-3.4.0/stac_pydantic/catalog.py000066400000000000000000000014341503615555000211010ustar00rootroot00000000000000from typing import List, Literal, Optional from pydantic import AnyUrl, ConfigDict, Field from stac_pydantic.links import Links from stac_pydantic.shared import SEMVER_REGEX, StacBaseModel from stac_pydantic.version import STAC_VERSION class _Catalog(StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/catalog-spec/catalog-spec.md """ id: str = Field(..., alias="id", min_length=1) description: str = Field(..., alias="description", min_length=1) stac_version: str = Field(STAC_VERSION, pattern=SEMVER_REGEX) links: Links stac_extensions: Optional[List[AnyUrl]] = [] title: Optional[str] = None type: str model_config = ConfigDict(use_enum_values=True, extra="allow") class Catalog(_Catalog): type: Literal["Catalog"] stac-pydantic-3.4.0/stac_pydantic/collection.py000066400000000000000000000203001503615555000216130ustar00rootroot00000000000000from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union from pydantic import AfterValidator, Field, conlist from typing_extensions import Annotated from stac_pydantic.catalog import _Catalog from stac_pydantic.shared import ( Asset, BBox, NumType, Provider, StacBaseModel, UtcDatetime, validate_bbox, ) if TYPE_CHECKING: StartEndTime = List[Union[UtcDatetime, None]] TInterval = List[StartEndTime] else: StartEndTime = conlist(Union[UtcDatetime, None], min_length=2, max_length=2) TInterval = conlist(StartEndTime, min_length=1) def validate_bbox_interval(v: List[BBox]) -> List[BBox]: # noqa: C901 ivalues = iter(v) # The first time interval always describes the overall spatial extent of the data. overall_bbox = next(ivalues, None) if not overall_bbox: return v assert validate_bbox(overall_bbox) if len(overall_bbox) == 4: xmin, ymin, xmax, ymax = overall_bbox else: xmin, ymin, _, xmax, ymax, _ = overall_bbox crossing_antimeridian = xmin > xmax for bbox in ivalues: error_msg = ValueError( f"`BBOX` {bbox} not fully contained in `Overall BBOX` {overall_bbox}" ) _ = validate_bbox(bbox) if len(bbox) == 4: xmin_sub, ymin_sub, xmax_sub, ymax_sub = bbox else: xmin_sub, ymin_sub, _, xmax_sub, ymax_sub, _ = bbox if not ((ymin_sub >= ymin) and (ymax_sub <= ymax)): raise error_msg sub_crossing_antimeridian = xmin_sub > xmax_sub if not crossing_antimeridian and sub_crossing_antimeridian: raise error_msg elif crossing_antimeridian: # Antimeridian # 0 + 180 │ - 180 0 # │ [176,1,179,3] │ │ # │ │ │ │ # │ │ │ │ # │ │ │ │ [-178,1,-176,3] # │ │ ┌─────────────────────────────────────────┐ │ │ # │ │ │ xmax_sub │ xmax_sub │ │ │ # │ │ │ ┌──────| │ ┌─────────| │ │ │ # │ └──│──► 2 │ │ │ 3 │ │ │ │ # | │ │ │ │ │ │◄────│─────────┼───────────┘ # │ │ |──────┘ │ |─────────┘ │ │ # │ │xmin_sub │ xmin_sub │ │ 0 # ──┼──────────│────────────────┼────────────────────────│─────────┼────────── # │ │ │ xmax_sub(-179) │ │ # │ │ ┌──────────────| │ │ # │ │ │ │ │ │ │ # │ │ │ │ 1 │ │ │ # | │ │ │ │◄────────┐ │◄────────┼─────── [175,-3,-174,5] # │ │ │ │ │ │ │ │ # │ │ |──────────────┘ │ │ │ # │ │ xmin_sub(179)│ │ │ │ # │ |──────────────────────────────────┼──────| │ # │ xmin(174) │ │ xmax(-174) │ # │ │ │ │ # │ │ │ │ # │ │ │ │ # │ │ [179,-2,-179,-1] │ # Case 1 if sub_crossing_antimeridian: if not (xmin_sub > xmin and xmax_sub < xmax): raise error_msg # Case 2: if sub-sequent has lon > 0 (0 -> 180 side), then we must check if # its min lon is < to the western lon (xmin for bbox crossing antimeridian limit) # of the overall bbox (on 0 -> +180 side) elif xmin_sub >= 0 and xmin_sub < xmin: raise error_msg # Case 3: if sub-sequent has lon < 0 (-180 -> 0 side), then we must check if # its max lon is > to the eastern lon (xmax for bbox crossing antimeridian limit) # of the overall bbox (on -180 -> 0 side) elif xmin_sub <= 0 and xmax_sub > xmax: raise error_msg else: if not ((xmin_sub >= xmin) and (xmax_sub <= xmax)): raise error_msg return v def validate_time_interval(v: TInterval) -> TInterval: # noqa: C901 ivalues = iter(v) # The first time interval always describes the overall temporal extent of the data. overall_interval = next(ivalues, None) if not overall_interval: return v start, end = overall_interval if start and end: if start > end: raise ValueError(f"`Start` time {start} older than `End` time {end}") # All subsequent time intervals can be used to provide a more precise # description of the extent and identify clusters of data. for s, e in ivalues: if s and e: if s > e: raise ValueError(f"`Start` time {s} older than `End` time {e}") if start and s: if start > s: raise ValueError( f"`Overall Start` time {start} older than `Start` time {s}" ) if end and e: if e > end: raise ValueError( f"`End` time {e} older than `Overall Start` time {end}" ) return v class SpatialExtent(StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#spatial-extent-object """ bbox: Annotated[List[BBox], AfterValidator(validate_bbox_interval)] class TimeInterval(StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#temporal-extent-object """ interval: Annotated[TInterval, AfterValidator(validate_time_interval)] class Extent(StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#extent-object """ spatial: SpatialExtent temporal: TimeInterval class Range(StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#stats-object """ minimum: Union[NumType, str] maximum: Union[NumType, str] class Collection(_Catalog): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md """ assets: Optional[Dict[str, Asset]] = None license: str = Field(..., alias="license", min_length=1) extent: Extent title: Optional[str] = None keywords: Optional[List[str]] = None providers: Optional[List[Provider]] = None summaries: Optional[Dict[str, Union[Range, List[Any], Dict[str, Any]]]] = None type: Literal["Collection"] stac-pydantic-3.4.0/stac_pydantic/extensions.py000066400000000000000000000027231503615555000216700ustar00rootroot00000000000000from functools import lru_cache from typing import Any, Dict, Union from stac_pydantic.catalog import Catalog from stac_pydantic.collection import Collection from stac_pydantic.item import Item try: import requests except ImportError: # pragma: nocover requests = None # type: ignore try: import jsonschema except ImportError: # pragma: nocover jsonschema = None # type: ignore @lru_cache(maxsize=128) def _fetch_and_cache_schema(url: str) -> dict: """Fetch the remote JSON schema, if not already cached.""" req = requests.get(url) return req.json() def validate_extensions( stac_obj: Union[Item, Collection, Catalog, Dict[str, Any]], reraise_exception: bool = False, ) -> bool: """ Fetch the remote JSON schema, if not already cached, and validate the STAC object against that schema. """ assert requests is not None, "requests must be installed to validate extensions" assert jsonschema is not None, "jsonschema must be installed to validate extensions" if isinstance(stac_obj, dict): stac_dict = stac_obj else: stac_dict = stac_obj.model_dump(mode="json") try: if stac_dict["stac_extensions"]: for ext in stac_dict["stac_extensions"]: schema = _fetch_and_cache_schema(ext) jsonschema.validate(instance=stac_dict, schema=schema) except Exception: if reraise_exception: raise return False return True stac-pydantic-3.4.0/stac_pydantic/item.py000066400000000000000000000031241503615555000204230ustar00rootroot00000000000000from typing import Any, Dict, List, Optional from geojson_pydantic import Feature from pydantic import AnyUrl, ConfigDict, Field, model_serializer, model_validator from stac_pydantic.links import Links from stac_pydantic.shared import SEMVER_REGEX, Asset, StacBaseModel, StacCommonMetadata from stac_pydantic.version import STAC_VERSION class ItemProperties(StacCommonMetadata): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/item-spec.md#properties-object """ model_config = ConfigDict(extra="allow") class Item(Feature, StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/item-spec.md """ id: str = Field(..., alias="id", min_length=1) stac_version: str = Field(STAC_VERSION, pattern=SEMVER_REGEX) properties: ItemProperties assets: Dict[str, Asset] links: Links stac_extensions: Optional[List[AnyUrl]] = [] collection: Optional[str] = None @model_validator(mode="before") @classmethod def validate_bbox(cls, values: Dict[str, Any]) -> Dict[str, Any]: if isinstance(values, dict): if values.get("geometry") and values.get("bbox") is None: raise ValueError("bbox is required if geometry is not null") return values # https://github.com/developmentseed/geojson-pydantic/issues/147 @model_serializer(mode="wrap") def _serialize(self, handler): data = handler(self) for field in self.__geojson_exclude_if_none__: if field in data and data[field] is None: del data[field] return data stac-pydantic-3.4.0/stac_pydantic/item_collection.py000066400000000000000000000011761503615555000226430ustar00rootroot00000000000000from typing import Optional, Sequence from geojson_pydantic import FeatureCollection from stac_pydantic.item import Item from stac_pydantic.links import Links from stac_pydantic.shared import StacBaseModel class ItemCollection(FeatureCollection, StacBaseModel): """ This is a less strict implementation of ItemCollection. It only implements the FeatureCollection Specs not enforcing required links as specified in STAC-API-FEATURES specs. Use `stac_pydantic.api.ItemCollection` to enforce link relationships and extra fields. """ features: Sequence[Item] # type: ignore links: Optional[Links] = None stac-pydantic-3.4.0/stac_pydantic/links.py000066400000000000000000000042611503615555000206100ustar00rootroot00000000000000from enum import auto from typing import Iterator, List, Optional, Union from urllib.parse import urljoin from pydantic import ConfigDict, Field, RootModel from stac_pydantic.shared import MimeTypes, StacBaseModel from stac_pydantic.utils import AutoValueEnum class Link(StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#link-object """ href: str = Field(..., alias="href", min_length=1) rel: str = Field(..., alias="rel", min_length=1) type: Optional[Union[MimeTypes, str]] = None title: Optional[str] = None model_config = ConfigDict(use_enum_values=True, extra="allow") def resolve(self, base_url: str) -> None: """resolve a link to the given base URL""" self.href = urljoin(base_url, self.href) class Links(RootModel[List[Link]]): root: List[Link] def link_iterator(self) -> Iterator[Link]: """Produce iterator to iterate through links""" return iter(self.root) def resolve(self, base_url: str) -> None: """resolve all links to the given base URL""" for link in self.link_iterator(): link.resolve(base_url) def append(self, link: Link) -> None: self.root.append(link) def __len__(self) -> int: return len(self.root) def __getitem__(self, idx: int) -> Link: return self.root[idx] class Relations(str, AutoValueEnum): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#relation-types https://github.com/radiantearth/stac-spec/blob/v1.0.0/best-practices.md#using-relation-types """ self = auto() root = auto() parent = auto() child = auto() children = auto() items = auto() license = auto() derived_from = auto() collection = auto() alternate = auto() previous = auto() prev = auto() next = auto() conformance = auto() docs = auto() tiles = auto() search = auto() preview = auto() canonical = auto() via = auto() data = auto() service_desc = "service-desc" service_doc = "service-doc" queryables = "http://www.opengis.net/def/rel/ogc/1.0/queryables" stac-pydantic-3.4.0/stac_pydantic/py.typed000066400000000000000000000000001503615555000206000ustar00rootroot00000000000000stac-pydantic-3.4.0/stac_pydantic/scripts/000077500000000000000000000000001503615555000206025ustar00rootroot00000000000000stac-pydantic-3.4.0/stac_pydantic/scripts/__init__.py000066400000000000000000000000001503615555000227010ustar00rootroot00000000000000stac-pydantic-3.4.0/stac_pydantic/scripts/cli.py000066400000000000000000000021101503615555000217150ustar00rootroot00000000000000import click import requests from pydantic import ValidationError from stac_pydantic.extensions import validate_extensions from stac_pydantic.item import Item try: import requests except ImportError: # pragma: nocover requests = None # type: ignore try: import jsonschema except ImportError: # pragma: nocover jsonschema = None # type: ignore @click.group(short_help="Validate STAC") def app() -> None: """stac-pydantic cli group""" pass @app.command(short_help="Validate STAC Item") @click.argument("infile") def validate_item(infile: str) -> None: """Validate stac item""" assert requests is not None, "requests must be installed to validate items" assert jsonschema is not None, "jsonschema must be installed to validate items" r = requests.get(infile) r.raise_for_status() stac_item = r.json() try: item = Item.model_validate(stac_item) validate_extensions(item, reraise_exception=True) except ValidationError as e: click.echo(str(e)) return click.echo(f"{infile} is valid") return stac-pydantic-3.4.0/stac_pydantic/shared.py000066400000000000000000000216141503615555000207370ustar00rootroot00000000000000from datetime import datetime as dt from datetime import timezone from enum import Enum, auto from typing import Any, Dict, List, Optional, Tuple, Union, cast from warnings import warn from pydantic import ( AfterValidator, AwareDatetime, BaseModel, ConfigDict, Field, TypeAdapter, model_validator, ) from typing_extensions import Annotated, Self from stac_pydantic.utils import AutoValueEnum NumType = Union[float, int] BBox = Union[ Tuple[NumType, NumType, NumType, NumType], # 2D bbox Tuple[NumType, NumType, NumType, NumType, NumType, NumType], # 3D bbox ] SEMVER_REGEX = r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" # Allows for some additional flexibility in the input datetime format. As long as # the input value has timezone information, it will be converted to UTC timezone. UtcDatetime = Annotated[ # Input value must be in a format which has timezone information AwareDatetime, # Convert the input value to UTC timezone AfterValidator(lambda d: d.astimezone(timezone.utc)), ] SearchDatetime = TypeAdapter(Optional[UtcDatetime]) class MimeTypes(str, Enum): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/item-spec.md#media-types """ # Raster geotiff = "image/tiff; application=geotiff" cog = "image/tiff; application=geotiff; profile=cloud-optimized" jp2 = "image/jp2" png = "image/png" jpeg = "image/jpeg" # Vector geojson = "application/geo+json" geojsonseq = "application/geo+json-seq" geopackage = "application/geopackage+sqlite3" kml = "application/vnd.google-earth.kml+xml" kmz = "application/vnd.google-earth.kmz" pbf = "application/x-protobuf" mvt = "application/vnd.mapbox-vector-tile" # Others hdf = "application/x-hdf" hdf5 = "application/x-hdf5" xml = "application/xml" json = "application/json" ndjson = "application/ndjson" html = "text/html" text = "text/plain" openapi = "application/vnd.oai.openapi+json;version=3.0" openapi_yaml = "application/vnd.oai.openapi;version=3.0" jsonschema = "application/schema+json" pdf = "application/pdf" csv = "text/csv" parquet = "application/vnd.apache.parquet" octet_stream = "application/octet-stream" class AssetRoles(str, AutoValueEnum): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/extensions/asset/README.md """ thumbnail = auto() overview = auto() data = auto() metadata = auto() class ProviderRoles(str, AutoValueEnum): licensor = auto() producer = auto() processor = auto() host = auto() class StacBaseModel(BaseModel): def to_dict( self, by_alias: bool = True, exclude_unset: bool = True, **kwargs: Any ) -> Dict[str, Any]: warn( "`to_dict` method is deprecated. Use `model_dump` instead", DeprecationWarning, ) return self.model_dump(by_alias=by_alias, exclude_unset=exclude_unset, **kwargs) def to_json( self, by_alias: bool = True, exclude_unset: bool = True, **kwargs: Any ) -> str: warn( "`to_json` method is deprecated. Use `model_dump_json` instead", DeprecationWarning, ) return self.model_dump_json( by_alias=by_alias, exclude_unset=exclude_unset, **kwargs ) def model_dump( self, *, by_alias: bool = True, exclude_unset: bool = True, **kwargs: Any ) -> Dict[str, Any]: return super().model_dump( by_alias=by_alias, exclude_unset=exclude_unset, **kwargs ) def model_dump_json( self, *, by_alias: bool = True, exclude_unset: bool = True, **kwargs: Any ) -> str: return super().model_dump_json( by_alias=by_alias, exclude_unset=exclude_unset, **kwargs ) class Provider(StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/collection-spec/collection-spec.md#provider-object """ name: str = Field(..., min_length=1) description: Optional[str] = None roles: Optional[List[str]] = None url: Optional[str] = None class StacCommonMetadata(StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/common-metadata.md """ # Basic title: Optional[str] = None description: Optional[str] = None # Date and Time datetime: Optional[UtcDatetime] = Field(...) created: Optional[UtcDatetime] = None updated: Optional[UtcDatetime] = None # Date and Time Range start_datetime: Optional[UtcDatetime] = None end_datetime: Optional[UtcDatetime] = None # Licensing license: Optional[str] = None # Provider providers: Optional[List[Provider]] = None # Instrument platform: Optional[str] = None instruments: Optional[List[str]] = None constellation: Optional[str] = None mission: Optional[str] = None gsd: Optional[float] = Field(None, gt=0) @model_validator(mode="after") def validate_datetime_or_start_end(self) -> Self: # When datetime is null, start_datetime and end_datetime must be specified if not self.datetime and (not self.start_datetime or not self.end_datetime): raise ValueError( "start_datetime and end_datetime must be specified when datetime is null" ) return self @model_validator(mode="after") def validate_start_end(self) -> Self: # Using one of start_datetime or end_datetime requires the use of the other if (self.start_datetime and not self.end_datetime) or ( not self.start_datetime and self.end_datetime ): raise ValueError( "use of start_datetime or end_datetime requires the use of the other" ) return self class Asset(StacBaseModel): """ https://github.com/radiantearth/stac-spec/blob/v1.0.0/item-spec/item-spec.md#asset-object """ href: str = Field(..., min_length=1) type: Optional[str] = None title: Optional[str] = None description: Optional[str] = None roles: Optional[List[str]] = None model_config = ConfigDict( populate_by_name=True, use_enum_values=True, extra="allow" ) def str_to_datetimes(value: str) -> List[Optional[dt]]: # Split on "/" and replace no value or ".." with None values = [v if v and v != ".." else None for v in value.split("/")] # Cast because pylance gets confused by the type adapter and annotated type dates = cast( List[Optional[dt]], [ # Use the type adapter to validate the datetime strings, strict is necessary # due to pydantic issues #8736 and #8762 SearchDatetime.validate_strings(v, strict=True) if v else None for v in values ], ) return dates def validate_datetime(v: Optional[str]) -> Optional[str]: """Validate Datetime value.""" if v is not None: dates = str_to_datetimes(v) # If there are more than 2 dates, it's invalid if len(dates) > 2: raise ValueError( "Invalid datetime range. Too many values. Must match format: {begin_date}/{end_date}" ) # If there is only one date, duplicate to use for both start and end dates if len(dates) == 1: dates = [dates[0], dates[0]] # If there is a start and end date, check that the start date is before the end date if dates[0] and dates[1] and dates[0] > dates[1]: raise ValueError( "Invalid datetime range. Begin date after end date. " "Must match format: {begin_date}/{end_date}" ) return v def validate_bbox(v: Optional[BBox]) -> Optional[BBox]: """Validate BBOX value.""" if v: # Validate order if len(v) == 4: xmin, ymin, xmax, ymax = cast(Tuple[int, int, int, int], v) elif len(v) == 6: xmin, ymin, min_elev, xmax, ymax, max_elev = cast( Tuple[int, int, int, int, int, int], v ) if max_elev < min_elev: raise ValueError( "Maximum elevation must greater than minimum elevation" ) else: raise ValueError("Bounding box must have 4 or 6 coordinates") # Validate against WGS84 if xmin < -180 or ymin < -90 or xmax > 180 or ymax > 90: raise ValueError("Bounding box must be within (-180, -90, 180, 90)") if xmax < xmin and (xmax > 0 or xmin < 0): raise ValueError( f"Maximum longitude ({xmax}) must be greater than minimum ({xmin}) longitude when not crossing the Antimeridian" ) if ymax < ymin: raise ValueError( f"Maximum latitude ({ymax}) must be greater than minimum latitude ({ymin})" ) return v stac-pydantic-3.4.0/stac_pydantic/utils.py000066400000000000000000000003431503615555000206250ustar00rootroot00000000000000from enum import Enum from typing import Any, List class AutoValueEnum(Enum): def _generate_next_value_( # type: ignore name: str, start: int, count: int, last_values: List[Any] ) -> Any: return name stac-pydantic-3.4.0/stac_pydantic/version.py000066400000000000000000000001331503615555000211470ustar00rootroot00000000000000"""stac-pydantic and STAC spec versions.""" __version__ = "3.4.0" STAC_VERSION = "1.0.0" stac-pydantic-3.4.0/tests/000077500000000000000000000000001503615555000154305ustar00rootroot00000000000000stac-pydantic-3.4.0/tests/__init__.py000066400000000000000000000000001503615555000175270ustar00rootroot00000000000000stac-pydantic-3.4.0/tests/api/000077500000000000000000000000001503615555000162015ustar00rootroot00000000000000stac-pydantic-3.4.0/tests/api/__init__.py000066400000000000000000000000001503615555000203000ustar00rootroot00000000000000stac-pydantic-3.4.0/tests/api/examples/000077500000000000000000000000001503615555000200175ustar00rootroot00000000000000stac-pydantic-3.4.0/tests/api/examples/v1.0.0/000077500000000000000000000000001503615555000206415ustar00rootroot00000000000000stac-pydantic-3.4.0/tests/api/examples/v1.0.0/example-collection-list.json000066400000000000000000001705401503615555000263000ustar00rootroot00000000000000{ "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections", "rel":"self", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/", "rel":"root", "type":"application/json" } ], "collections":[ { "id":"aster-l1t", "description":"The [ASTER](https://terra.nasa.gov/about/terra-instruments/aster) instrument, launched on-board NASA's [Terra](https://terra.nasa.gov/) satellite in 1999, provides multispectral images of the Earth at 15m-90m resolution. ASTER images provide information about land surface temperature, color, elevation, and mineral composition.\n\nThis dataset represents ASTER [L1T](https://lpdaac.usgs.gov/products/ast_l1tv003/) data from 2000-2006. L1T images have been terrain-corrected and rotated to a north-up UTM projection. Images are in [cloud-optimized GeoTIFF](https://www.cogeo.org/) format.\n", "stac_version":"1.0.0", "type": "Collection", "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/aster-l1t", "rel":"self", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/", "rel":"parent", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/aster-l1t/items", "rel":"item", "type":"application/geo+json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/", "rel":"root", "type":"application/json" }, { "href":"https://www.usgs.gov/core-science-systems/hdds/data-policy", "rel":"license", "title":"public domain" } ], "stac_extensions":[ "https://raw.githubusercontent.com/stac-extensions/item-assets/v1.0.0/json-schema/schema.json" ], "title":"ASTER L1T", "license":"proprietary", "extent":{ "spatial":{ "bbox":[ [ -180.0, -90.0, 180.0, 90.0 ] ] }, "temporal":{ "interval":[ [ "2000-03-04T12:00:00Z", "2006-12-31T12:00:00Z" ] ] } }, "keywords":[ "aster", "usgs", "nasa", "satellite", "global" ], "providers":[ { "name":"NASA", "roles":[ "producer", "licensor" ], "url":"https://terra.nasa.gov/about/terra-instruments/aster" }, { "name":"USGS", "roles":[ "processor", "producer", "licensor" ], "url":"https://lpdaac.usgs.gov/data/get-started-data/collection-overview/missions/aster-overview/" }, { "name":"Microsoft", "roles":[ "host", "processor" ], "url":"https://planetarycomputer.microsoft.com" } ], "summaries":{ "gsd":[ 15, 30, 90 ], "eo:bands":[ { "gsd":15, "name":"VNIR_Band1", "common_name":"yellow/green", "description":"visible yellow/green", "center_wavelength":0.56, "full_width_half_max":0.08 }, { "gsd":15, "name":"VNIR_Band2", "common_name":"red", "description":"visible red", "center_wavelength":0.66, "full_width_half_max":0.06 }, { "gsd":15, "name":"VNIR_Band3N", "common_name":"near infrared", "description":"near infrared", "center_wavelength":0.82, "full_width_half_max":0.08 }, { "gsd":30, "name":"SWIR_Band4", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":1.65, "full_width_half_max":0.1 }, { "gsd":30, "name":"SWIR_Band5", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.165, "full_width_half_max":0.04 }, { "gsd":30, "name":"SWIR_Band6", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.205, "full_width_half_max":0.04 }, { "gsd":30, "name":"SWIR_Band7", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.26, "full_width_half_max":0.05 }, { "gsd":30, "name":"SWIR_Band8", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.339, "full_width_half_max":0.07 }, { "gsd":30, "name":"SWIR_Band9", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.395, "full_width_half_max":0.07 }, { "gsd":90, "name":"TIR_Band10", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":8.3, "full_width_half_max":0.35 }, { "gsd":90, "name":"TIR_Band11", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":8.65, "full_width_half_max":0.35 }, { "gsd":90, "name":"TIR_Band12", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":9.11, "full_width_half_max":0.35 }, { "gsd":90, "name":"TIR_Band13", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":10.6, "full_width_half_max":0.7 }, { "gsd":90, "name":"TIR_Band14", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":11.3, "full_width_half_max":0.7 } ], "platform":[ "terra" ], "instruments":[ "aster" ] }, "msft:storage_account":"astersa", "msft:container":"aster", "msft:short_description":"The ASTER instrument, launched on-board NASA's Terra satellite in 1999, provides multispectral images of the Earth at 15m-90m resolution. This dataset contains ASTER data from 2000-2006.", "item_assets":{ "TIR":{ "title":"TIR Swath data", "roles":[ "data" ], "eo:bands":[ { "name":"TIR_Band10", "common_name":"lwir", "center_wavelength":8.3, "full_width_half_max":0.35, "description":"thermal infrared" }, { "name":"TIR_Band11", "common_name":"lwir", "center_wavelength":8.65, "full_width_half_max":0.35, "description":"thermal infrared" }, { "name":"TIR_Band12", "common_name":"lwir", "center_wavelength":9.11, "full_width_half_max":0.35, "description":"thermal infrared" }, { "name":"TIR_Band13", "common_name":"lwir", "center_wavelength":10.6, "full_width_half_max":0.7, "description":"thermal infrared" }, { "name":"TIR_Band14", "common_name":"lwir", "center_wavelength":11.3, "full_width_half_max":0.7, "description":"thermal infrared" } ] }, "xml":{ "title":"XML metadata", "type":"application/xml", "roles":[ "metadata" ] }, "SWIR":{ "title":"SWIR Swath data", "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"SWIR_Band4", "common_name":"swir", "center_wavelength":1.65, "full_width_half_max":0.1, "description":"short-wave infrared" }, { "name":"SWIR_Band5", "common_name":"swir", "center_wavelength":2.165, "full_width_half_max":0.04, "description":"short-wave infrared" }, { "name":"SWIR_Band6", "common_name":"swir", "center_wavelength":2.205, "full_width_half_max":0.04, "description":"short-wave infrared" }, { "name":"SWIR_Band7", "common_name":"swir", "center_wavelength":2.26, "full_width_half_max":0.05, "description":"short-wave infrared" }, { "name":"SWIR_Band8", "common_name":"swir", "center_wavelength":2.339, "full_width_half_max":0.07, "description":"short-wave infrared" }, { "name":"SWIR_Band9", "common_name":"swir", "center_wavelength":2.395, "full_width_half_max":0.07, "description":"short-wave infrared" } ] }, "VNIR":{ "title":"VNIR Swath data", "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"VNIR_Band1", "common_name":"yellow/green", "center_wavelength":0.56, "full_width_half_max":0.08, "description":"visible yellow/green" }, { "name":"VNIR_Band2", "common_name":"red", "center_wavelength":0.66, "full_width_half_max":0.06, "description":"visible red" }, { "name":"VNIR_Band3N", "common_name":"near infrared", "center_wavelength":0.82, "full_width_half_max":0.08, "description":"near infrared" } ] }, "qa-txt":{ "title":"QA browse file", "description":"Geometric quality assessment report.", "type":"text/plain", "roles":[ "metadata" ] }, "qa-browse":{ "title":"QA browse file", "description":"Single-band black and white reduced resolution browse overlaid with red, green, and blue (RGB) markers for GCPs used during the geometric verification quality check.", "type":"image/jpeg", "roles":[ "thumbnail" ] }, "tir-browse":{ "title":"Standalone reduced resolution TIR", "type":"image/jpeg", "roles":[ "thumbnail" ] }, "vnir-browse":{ "title":"VNIR browse file", "description":"Standalone reduced resolution VNIR", "type":"image/jpeg", "roles":[ "thumbnail" ] } }, "assets":{ "thumbnail":{ "title":"ASTER L1T", "href":"https://ai4edatasetspublicassets.blob.core.windows.net/assets/pc_thumbnails/aster.png", "media_type":"image/png" } } }, { "id":"landsat-8-c2-l2", "description":"The [Landsat](https://landsat.gsfc.nasa.gov/) program has been imaging the Earth since 1972; it provides a comprehensive, continuous archive of the Earth's surface. [Landsat 8](https://www.usgs.gov/core-science-systems/nli/landsat/landsat-8) is the most recent satellite in the Landsat series. Launched in 2013, Landsat 8 captures data in eleven spectral bands: ten optical/IR bands from the [Operational Land Imager](https://landsat.gsfc.nasa.gov/landsat-8/operational-land-imager) (OLI) instrument, and two thermal bands from the [Thermal Infrared Sensor](https://landsat.gsfc.nasa.gov/landsat-8/thermal-infrared-sensor-tirs) (TIRS) instrument.\n\nThis dataset represents the global archive of Level-2 Landsat 8 data from [Landsat Collection 2](https://www.usgs.gov/core-science-systems/nli/landsat/landsat-collection-2). Because there is some latency before Level-2 data is available, a rolling window of recent Level-1 data is available as well. Images are stored in [cloud-optimized GeoTIFF](https://www.cogeo.org/) format.\n", "stac_version":"1.0.0", "type": "Collection", "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/landsat-8-c2-l2", "rel":"self", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/", "rel":"parent", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/landsat-8-c2-l2/items", "rel":"item", "type":"application/geo+json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/", "rel":"root", "type":"application/json" }, { "href":"https://www.usgs.gov/core-science-systems/hdds/data-policy", "rel":"license", "title":"public domain" } ], "stac_extensions":[ "https://raw.githubusercontent.com/stac-extensions/item-assets/v1.0.0/json-schema/schema.json" ], "title":"Landsat 8 Collection 2 Level-2", "license":"proprietary", "extent":{ "spatial":{ "bbox":[ [ -180.0, -90.0, 180.0, 90.0 ] ] }, "temporal":{ "interval":[ [ "2013-04-11T00:00:00Z", null ] ] } }, "keywords":[ "landsat", "usgs", "nasa", "satellite", "global", "reflectance" ], "providers":[ { "name":"NASA", "roles":[ "producer", "licensor" ], "url":"https://landsat.gsfc.nasa.gov/" }, { "name":"USGS", "roles":[ "producer", "processor", "licensor" ], "url":"https://www.usgs.gov/core-science-systems/nli/landsat/landsat-collection-2-level-2-science-products" }, { "name":"Microsoft", "roles":[ "host" ], "url":"https://planetarycomputer.microsoft.com" } ], "summaries":{ "gsd":[ 15, 30, 100 ], "eo:bands":[ { "gsd":30, "name":"SR_B1", "common_name":"coastal", "description":"coastal", "center_wavelength":0.44, "full_width_half_max":0.02 }, { "gsd":30, "name":"SR_B2", "common_name":"blue", "description":"visible blue", "center_wavelength":0.48, "full_width_half_max":0.06 }, { "gsd":30, "name":"SR_B3", "common_name":"green", "description":"visible green", "center_wavelength":0.56, "full_width_half_max":0.06 }, { "gsd":30, "name":"SR_B4", "common_name":"red", "description":"visible red", "center_wavelength":0.65, "full_width_half_max":0.04 }, { "gsd":30, "name":"SR_B5", "common_name":"nir", "description":"near-infrared", "center_wavelength":0.86, "full_width_half_max":0.03 }, { "gsd":30, "name":"SR_B6", "common_name":"swir16", "description":"short-wave infrared", "center_wavelength":1.6, "full_width_half_max":0.08 }, { "gsd":30, "name":"SR_B7", "common_name":"swir22", "description":"short-wave infrared", "center_wavelength":2.2, "full_width_half_max":0.2 }, { "gsd":100, "name":"ST_B10", "common_name":"lwir11", "description":"long-wave infrared", "center_wavelength":10.9, "full_width_half_max":0.8 }, { "gsd":30, "name":"ST_TRAD", "description":"thermal radiance" }, { "gsd":30, "name":"ST_URAD", "description":"upwelled radiance" }, { "gsd":30, "name":"ST_ATRAN", "description":"atmospheric transmission" }, { "gsd":30, "name":"ST_CDIST", "description":"distance to nearest cloud" }, { "gsd":30, "name":"ST_DRAD", "description":"downwelled radiance" }, { "gsd":30, "name":"ST_EMIS", "description":"emissivity" }, { "gsd":30, "name":"ST_EMSD", "description":"emissivity standard deviation" } ], "platform":[ "landsat-8" ], "instruments":[ "oli", "tirs" ] }, "msft:storage_account":"landsateuwest", "msft:container":"landsat-c2", "msft:short_description":"Landsat 8 has captured 30m-resolution imagery of the Earth since 2013. This dataset contains global, atmospherically-corrected imagery from Landsat Collection 2", "item_assets":{ "ANG":{ "title":"Angle Coefficients File", "description":"Collection 2 Level-1 Angle Coefficients File (ANG)", "type":"text/plain" }, "SR_B1":{ "title":"Coastal/Aerosol Band (B1)", "description":"Collection 2 Level-2 Coastal/Aerosol Band (B1) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B1", "common_name":"coastal", "center_wavelength":0.44, "full_width_half_max":0.02 } ] }, "SR_B2":{ "title":"Blue Band (B2)", "description":"Collection 2 Level-2 Blue Band (B2) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B2", "common_name":"blue", "center_wavelength":0.48, "full_width_half_max":0.06 } ] }, "SR_B3":{ "title":"Green Band (B3)", "description":"Collection 2 Level-2 Green Band (B3) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B3", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.06 } ] }, "SR_B4":{ "title":"Red Band (B4)", "description":"Collection 2 Level-2 Red Band (B4) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B4", "common_name":"red", "center_wavelength":0.65, "full_width_half_max":0.04 } ] }, "SR_B5":{ "title":"Near Infrared Band 0.8 (B5)", "description":"Collection 2 Level-2 Near Infrared Band 0.8 (B5) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B5", "common_name":"nir08", "center_wavelength":0.86, "full_width_half_max":0.03 } ] }, "SR_B6":{ "title":"Short-wave Infrared Band 1.6 (B6)", "description":"Collection 2 Level-2 Short-wave Infrared Band 1.6 (B6) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B6", "common_name":"swir16", "center_wavelength":1.6, "full_width_half_max":0.08 } ] }, "SR_B7":{ "title":"Short-wave Infrared Band 2.2 (B7)", "description":"Collection 2 Level-2 Short-wave Infrared Band 2.2 (B7) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B7", "common_name":"swir22", "center_wavelength":2.2, "full_width_half_max":0.2 } ] }, "SR_B8":{ "title":"Aerosol Quality Analysis Band", "description":"Collection 2 Level-2 Aerosol Quality Analysis Band (ANG) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized" }, "ST_QA":{ "title":"Surface Temperature Quality Assessment Band", "description":"Landsat Collection 2 Level-2 Surface Temperature Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized" }, "ST_B10":{ "title":"Surface Temperature Band (B10)", "description":"Landsat Collection 2 Level-2 Surface Temperature Band (B10) Surface Temperature Product", "gsd":100.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_B10", "common_name":"lwir11", "center_wavelength":10.9, "full_width_half_max":0.8 } ] }, "MTL.txt":{ "title":"Product Metadata File", "description":"Collection 2 Level-1 Product Metadata File (MTL)", "type":"text/plain" }, "MTL.xml":{ "title":"Product Metadata File (xml)", "description":"Collection 2 Level-1 Product Metadata File (xml)", "type":"application/xml" }, "ST_DRAD":{ "title":"Downwelled Radiance Band", "description":"Landsat Collection 2 Level-2 Downwelled Radiance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_DRAD", "description":"downwelled radiance" } ] }, "ST_EMIS":{ "title":"Emissivity Band", "description":"Landsat Collection 2 Level-2 Emissivity Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_EMIS", "description":"emissivity" } ] }, "ST_EMSD":{ "title":"Emissivity Standard Deviation Band", "description":"Landsat Collection 2 Level-2 Emissivity Standard Deviation Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_EMSD", "description":"emissivity standard deviation" } ] }, "ST_TRAD":{ "title":"Thermal Radiance Band", "description":"Landsat Collection 2 Level-2 Thermal Radiance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_TRAD", "description":"thermal radiance" } ] }, "ST_URAD":{ "title":"Upwelled Radiance Band", "description":"Landsat Collection 2 Level-2 Upwelled Radiance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_URAD", "description":"upwelled radiance" } ] }, "MTL.json":{ "title":"Product Metadata File (json)", "description":"Collection 2 Level-1 Product Metadata File (json)", "type":"application/json" }, "QA_PIXEL":{ "title":"Pixel Quality Assessment Band", "description":"Collection 2 Level-1 Pixel Quality Assessment Band", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized" }, "ST_ATRAN":{ "title":"Atmospheric Transmittance Band", "description":"Landsat Collection 2 Level-2 Atmospheric Transmittance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_ATRAN", "description":"atmospheric transmission" } ] }, "ST_CDIST":{ "title":"Cloud Distance Band", "description":"Landsat Collection 2 Level-2 Cloud Distance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_CDIST", "description":"distance to nearest cloud" } ] }, "QA_RADSAT":{ "title":"Radiometric Saturation Quality Assessment Band", "description":"Collection 2 Level-1 Radiometric Saturation Quality Assessment Band", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized" }, "thumbnail":{ "title":"Thumbnail image", "type":"image/jpeg" }, "reduced_resolution_browse":{ "title":"Reduced resolution browse image", "type":"image/jpeg" } }, "assets":{ "thumbnail":{ "title":"Landsat 8 C2", "href":"https://ai4edatasetspublicassets.blob.core.windows.net/assets/pc_thumbnails/landsat.png", "media_type":"image/png" } } }, { "id":"sentinel-2-l2a", "description":"The [Sentinel-2](https://sentinel.esa.int/web/sentinel/missions/sentinel-2) program provides global imagery in thirteen spectral bands at 10m-60m resolution and a revisit time of approximately five days. This dataset represents the global Sentinel-2 archive, from 2016 to the present, processed to L2A (bottom-of-atmosphere) using [Sen2Cor](https://step.esa.int/main/snap-supported-plugins/sen2cor/) and converted to [cloud-optimized GeoTIFF](https://www.cogeo.org/) format.", "stac_version":"1.0.0", "type": "Collection", "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a", "rel":"self", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/", "rel":"parent", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a/items", "rel":"item", "type":"application/geo+json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/", "rel":"root", "type":"application/json" }, { "href":"https://scihub.copernicus.eu/twiki/pub/SciHubWebPortal/TermsConditions/Sentinel_Data_Terms_and_Conditions.pdf", "rel":"license", "title":"Copernicus Sentinel data terms" } ], "stac_extensions": [ "https://raw.githubusercontent.com/stac-extensions/item-assets/v1.0.0/json-schema/schema.json" ], "title":"Sentinel-2 Level-2A", "license":"proprietary", "extent":{ "spatial":{ "bbox":[ [ -180.0, -90.0, 180.0, 90.0 ] ] }, "temporal":{ "interval":[ [ "2015-06-27T10:25:31.456000Z", null ] ] } }, "keywords":[ "sentinel", "copernicus", "esa", "satellite", "global", "reflectance" ], "providers":[ { "name":"ESA", "roles":[ "producer", "licensor" ], "url":"https://sentinel.esa.int/web/sentinel/missions/sentinel-2" }, { "name":"Esri", "roles":[ "processor" ], "url":"https://www.esri.com/" }, { "name":"Microsoft", "roles":[ "host", "processor" ], "url":"https://planetarycomputer.microsoft.com" } ], "summaries":{ "gsd":[ 10, 20, 60 ], "eo:bands":[ { "name":"AOT", "description":"aerosol optical thickness" }, { "gsd":60, "name":"B01", "common_name":"coastal", "description":"coastal aerosol", "center_wavelength":0.443, "full_width_half_max":0.027 }, { "gsd":10, "name":"B02", "common_name":"blue", "description":"visible blue", "center_wavelength":0.49, "full_width_half_max":0.098 }, { "gsd":10, "name":"B03", "common_name":"green", "description":"visible green", "center_wavelength":0.56, "full_width_half_max":0.045 }, { "gsd":10, "name":"B04", "common_name":"red", "description":"visible red", "center_wavelength":0.665, "full_width_half_max":0.038 }, { "gsd":20, "name":"B05", "common_name":"rededge", "description":"vegetation classification red edge", "center_wavelength":0.704, "full_width_half_max":0.019 }, { "gsd":20, "name":"B06", "common_name":"rededge", "description":"vegetation classification red edge", "center_wavelength":0.74, "full_width_half_max":0.018 }, { "gsd":20, "name":"B07", "common_name":"rededge", "description":"vegetation classification red edge", "center_wavelength":0.783, "full_width_half_max":0.028 }, { "gsd":10, "name":"B08", "common_name":"nir", "description":"near infrared", "center_wavelength":0.842, "full_width_half_max":0.145 }, { "gsd":20, "name":"B8A", "common_name":"rededge", "description":"vegetation classification red edge", "center_wavelength":0.865, "full_width_half_max":0.033 }, { "gsd":60, "name":"B09", "description":"water vapor", "center_wavelength":0.945, "full_width_half_max":0.026 }, { "gsd":20, "name":"B11", "common_name":"swir16", "description":"short-wave infrared, snow/ice/cloud classification", "center_wavelength":1.61, "full_width_half_max":0.143 }, { "gsd":20, "name":"B12", "common_name":"swir22", "description":"short-wave infrared, snow/ice/cloud classification", "center_wavelength":2.19, "full_width_half_max":0.242 } ], "platform":[ "Sentinel-2A", "Sentinel-2B" ], "instruments":[ "msi" ], "constellation":[ "sentinel-2" ], "view:off_nadir":[ 0 ] }, "msft:storage_account":"sentinel2l2a01", "msft:container":"sentinel2-l2", "msft:short_description":"The Sentinel-2 program provides global imagery in thirteen spectral bands at 10m-60m resolution and a revisit time of approximately five days. This dataset contains the global Sentinel-2 archive, from 2016 to the present, processed to L2A (bottom-of-atmosphere).", "item_assets":{ "B01":{ "title":"Band 1 - Coastal aerosol", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B01", "common_name":"coastal", "center_wavelength":0.443, "full_width_half_max":0.027, "description":"Band 1 - Coastal aerosol" } ] }, "B02":{ "title":"Band 2 - Blue", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B02", "common_name":"blue", "center_wavelength":0.49, "full_width_half_max":0.098, "description":"Band 2 - Blue" } ] }, "B03":{ "title":"Band 3 - Green", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B03", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.045, "description":"Band 3 - Green" } ] }, "B04":{ "title":"Band 4 - Red", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B04", "common_name":"red", "center_wavelength":0.665, "full_width_half_max":0.038, "description":"Band 4 - Red" } ] }, "B05":{ "title":"Band 5 - Vegetation red edge 1", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B05", "common_name":"rededge", "center_wavelength":0.704, "full_width_half_max":0.019, "description":"Band 5 - Vegetation red edge 1" } ] }, "B06":{ "title":"Band 6 - Vegetation red edge 2", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B06", "common_name":"rededge", "center_wavelength":0.74, "full_width_half_max":0.018, "description":"Band 6 - Vegetation red edge 2" } ] }, "B07":{ "title":"Band 7 - Vegetation red edge 3", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B07", "common_name":"rededge", "center_wavelength":0.783, "full_width_half_max":0.028, "description":"Band 7 - Vegetation red edge 3" } ] }, "B08":{ "title":"Band 8 - NIR", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B08", "common_name":"nir", "center_wavelength":0.842, "full_width_half_max":0.145, "description":"Band 8 - NIR" } ] }, "B09":{ "title":"Band 9 - Water vapor", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B09", "center_wavelength":0.945, "full_width_half_max":0.026, "description":"Band 9 - Water vapor" } ] }, "B11":{ "title":"Band 11 - SWIR (1.6)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B11", "common_name":"swir16", "center_wavelength":1.61, "full_width_half_max":0.143, "description":"Band 11 - SWIR (1.6)" } ] }, "B12":{ "title":"Band 12 - SWIR (2.2)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B12", "common_name":"swir22", "center_wavelength":2.19, "full_width_half_max":0.242, "description":"Band 12 - SWIR (2.2)" } ] }, "B8A":{ "title":"Band 8A - Vegetation red edge 4", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B8A", "common_name":"rededge", "center_wavelength":0.865, "full_width_half_max":0.033, "description":"Band 8A - Vegetation red edge 4" } ] }, "AOT-10m":{ "title":"Aerosol optical thickness (AOT)", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "AOT-20m":{ "title":"Aerosol optical thickness (AOT)", "gsd":20.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "AOT-60m":{ "title":"Aerosol optical thickness (AOT)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "SCL-20m":{ "title":"Scene classfication map (SCL)", "gsd":20.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "SCL-60m":{ "title":"Scene classfication map (SCL)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "WVP-10m":{ "title":"Water vapour (WVP)", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "WVP-20m":{ "title":"Water vapour (WVP)", "gsd":20.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "WVP-60m":{ "title":"Water vapour (WVP)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "preview":{ "title":"Thumbnail", "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "thumbnail" ] }, "visual-10m":{ "title":"True color image", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B04", "common_name":"red", "center_wavelength":0.665, "full_width_half_max":0.038, "description":"Band 4 - Red" }, { "name":"B03", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.045, "description":"Band 3 - Green" }, { "name":"B02", "common_name":"blue", "center_wavelength":0.49, "full_width_half_max":0.098, "description":"Band 2 - Blue" } ] }, "visual-20m":{ "title":"True color image", "gsd":20.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B04", "common_name":"red", "center_wavelength":0.665, "full_width_half_max":0.038, "description":"Band 4 - Red" }, { "name":"B03", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.045, "description":"Band 3 - Green" }, { "name":"B02", "common_name":"blue", "center_wavelength":0.49, "full_width_half_max":0.098, "description":"Band 2 - Blue" } ] }, "visual-60m":{ "title":"True color image", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B04", "common_name":"red", "center_wavelength":0.665, "full_width_half_max":0.038, "description":"Band 4 - Red" }, { "name":"B03", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.045, "description":"Band 3 - Green" }, { "name":"B02", "common_name":"blue", "center_wavelength":0.49, "full_width_half_max":0.098, "description":"Band 2 - Blue" } ] }, "safe-manifest":{ "title":"SAFE manifest", "type":"application/xml", "roles":[ "metadata" ] }, "granule-metadata":{ "title":"Granule metadata", "type":"application/xml", "roles":[ "metadata" ] }, "inspire-metadata":{ "title":"INSPIRE metadata", "type":"application/xml", "roles":[ "metadata" ] }, "product-metadata":{ "title":"Product metadata", "type":"application/xml", "roles":[ "metadata" ] }, "datastrip-metadata":{ "title":"Datastrip metadata", "type":"application/xml", "roles":[ "metadata" ] } }, "assets":{ "thumbnail":{ "title":"Sentinel 2 L2A", "href":"https://ai4edatasetspublicassets.blob.core.windows.net/assets/pc_thumbnails/sentinel-2.png", "media_type":"image/png" } } }, { "id":"naip", "description":"The [National Agriculture Imagery Program](https://www.fsa.usda.gov/programs-and-services/aerial-photography/imagery-programs/naip-imagery/) (NAIP) provides US-wide, high-resolution aerial imagery, with four spectral bands (R, G, B, IR). NAIP is administered by the [Aerial Field Photography Office](https://www.fsa.usda.gov/programs-and-services/aerial-photography/) (AFPO) within the [US Department of Agriculture](https://www.usda.gov/) (USDA). Data are captured at least once every three years for each state. This dataset represents NAIP data from 2010-present, in [cloud-optimized GeoTIFF](https://www.cogeo.org/) format.\n", "stac_version":"1.0.0", "type": "Collection", "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/naip", "rel":"self", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/", "rel":"parent", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/naip/items", "rel":"item", "type":"application/geo+json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/", "rel":"root", "type":"application/json" }, { "href":"https://www.fsa.usda.gov/help/policies-and-links/", "rel":"license", "title":"public domain" } ], "stac_extensions":[ "https://raw.githubusercontent.com/stac-extensions/item-assets/v1.0.0/json-schema/schema.json" ], "title":"NAIP: National Agriculture Imagery Program", "license":"proprietary", "extent":{ "spatial":{ "bbox":[ [ -124.784, 24.744, -66.951, 49.346 ] ] }, "temporal":{ "interval":[ [ "2010-01-01T00:00:00Z", "2019-12-31T00:00:00Z" ] ] } }, "keywords":[ "naip", "aerial", "imagery", "usda", "afpo", "agriculture", "united states" ], "providers":[ { "name":"USDA Farm Service Agency", "roles":[ "producer", "licensor" ], "url":"https://www.fsa.usda.gov/programs-and-services/aerial-photography/imagery-programs/naip-imagery/" }, { "name":"Esri", "roles":[ "processor" ], "url":"https://www.esri.com/" }, { "name":"Microsoft", "roles":[ "host", "processor" ], "url":"https://planetarycomputer.microsoft.com" } ], "summaries":{ "gsd":[ 0.6, 1.0 ], "eo:bands":[ { "name":"Red", "common_name":"red", "description":"visible red" }, { "name":"Green", "common_name":"green", "description":"visible green" }, { "name":"Blue", "common_name":"blue", "description":"visible blue" }, { "name":"NIR", "common_name":"nir", "description":"near-infrared" } ] }, "msft:storage_account":"naipeuwest", "msft:container":"naip", "msft:short_description":"NAIP provides US-wide, high-resolution aerial imagery. This dataset includes NAIP images from 2010 to the present.", "item_assets":{ "image":{ "title":"RGBIR COG tile", "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"Red", "common_name":"red" }, { "name":"Green", "common_name":"green" }, { "name":"Blue", "common_name":"blue" }, { "name":"NIR", "common_name":"nir", "description":"near-infrared" } ] }, "metadata":{ "title":"FGDC Metdata", "type":"text/plain", "roles":[ "metadata" ] }, "thumbnail":{ "title":"Thumbnail", "type":"image/jpeg", "roles":[ "thumbnail" ] } }, "assets":{ "thumbnail":{ "title":"Landsat 8 C2", "href":"https://ai4edatasetspublicassets.blob.core.windows.net/assets/pc_thumbnails/naip.png", "media_type":"image/png" } } } ] } stac-pydantic-3.4.0/tests/api/examples/v1.0.0/itemcollection-sample-full.json000066400000000000000000000047321503615555000267730ustar00rootroot00000000000000{ "type": "FeatureCollection", "numberMatched": 10, "numberReturned": 1, "features": [ { "stac_version": "1.0.0", "stac_extensions": [], "type": "Feature", "id": "CS3-20160503_132131_05", "bbox": [ -122.59750209, 37.48803556, -122.2880486, 37.613537207 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ -122.308150179, 37.488035566 ], [ -122.597502109, 37.538869539 ], [ -122.576687533, 37.613537207 ], [ -122.288048600, 37.562818007 ], [ -122.308150179, 37.488035566 ] ] ] }, "properties": { "datetime": "2016-05-03T13:22:30.040Z", "title": "A CS3 item", "license": "PDDL-1.0", "providers": [ { "name": "CoolSat", "roles": [ "producer", "licensor" ], "url": "https://cool-sat.com/" } ] }, "collection": "CS3", "links": [ { "rel": "self", "href": "http://cool-sat.com/catalog/collections/CS3-20160503_132130_04/items/CS3-20160503_132130_04.json" }, { "rel": "collection", "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/catalog.json" }, { "rel": "root", "href": "http://cool-sat.com/catalog/" } ], "assets": { "analytic": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/analytic.tif", "title": "4-Band Analytic" }, "thumbnail": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/thumbnail.png", "title": "Thumbnail", "roles": [ "thumbnail" ] } } } ], "links": [ { "rel": "self", "href": "http://stac.example.com/search?collection=CS3", "type": "application/geo+json" }, { "rel": "root", "href": "http://stac.example.com/", "type": "application/json" } ] } stac-pydantic-3.4.0/tests/api/examples/v1.0.0/landing_page_core.json000066400000000000000000000040351503615555000251560ustar00rootroot00000000000000{ "stac_version": "1.0.0", "id": "example-stac", "title": "A simple STAC API Example", "description": "This Catalog aims to demonstrate the a simple landing page", "type": "Catalog", "conformsTo" : [ "https://api.stacspec.org/v1.0.0/core", "https://api.stacspec.org/v1.0.0/collections", "https://api.stacspec.org/v1.0.0/ogcapi-features", "https://api.stacspec.org/v1.0.0/item-search", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" ], "links": [ { "rel": "self", "type": "application/json", "href": "https://stac-api.example.com" }, { "rel": "root", "type": "application/json", "href": "https://stac-api.example.com" }, { "rel": "conformance", "type": "application/json", "href": "https://stac-api.example.com/conformance" }, { "rel": "service-desc", "type": "application/vnd.oai.openapi+json;version=3.0", "href": "https://stac-api.example.com/api" }, { "rel": "service-doc", "type": "text/html", "href": "https://stac-api.example.com/api.html" }, { "rel": "data", "type": "application/json", "href": "https://stac-api.example.com/collections" }, { "rel": "child", "type": "application/json", "href": "https://stac-api.example.com/collections/sentinel-2" }, { "rel": "child", "type": "application/json", "href": "https://stac-api.example.com/collections/landsat-8" }, { "rel": "search", "type": "application/geo+json", "href": "https://stac-api.example.com/search" } ] } stac-pydantic-3.4.0/tests/api/examples/v1.0.0/landing_page_ogcapi-features.json000066400000000000000000000030111503615555000272750ustar00rootroot00000000000000{ "stac_version": "1.0.0", "id": "example-stac", "title": "A simple STAC API Example", "description": "This Catalog aims to demonstrate the a simple landing page", "type": "Catalog", "conformsTo" : [ "https://api.stacspec.org/v1.0.0/core", "https://api.stacspec.org/v1.0.0/ogcapi-features", "https://api.stacspec.org/v1.0.0/collections", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/oas30", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson" ], "links": [ { "rel": "self", "type": "application/json", "href": "https://stac-api.example.com" }, { "rel": "root", "type": "application/json", "href": "https://stac-api.example.com" }, { "rel": "conformance", "type": "application/json", "href": "https://stac-api.example.com/conformance" }, { "rel": "service-desc", "type": "application/vnd.oai.openapi+json;version=3.0", "href": "https://stac-api.example.com/api" }, { "rel": "service-doc", "type": "text/html", "href": "https://stac-api.example.com/api.html" }, { "rel": "data", "type": "application/json", "href": "https://stac-api.example.com/collections" } ] } stac-pydantic-3.4.0/tests/api/examples/v1.0.0/links.json000066400000000000000000000031521503615555000226550ustar00rootroot00000000000000{ "links": [ { "rel": "search", "type": "application/geo+json", "href": "https://stac-api.example.com/search" }, { "rel": "next", "href": "https://stac-api.example.com/search", "type": "application/geo+json", "method": "POST", "headers": { "Search-After": "LC81530752019135LGN00" }, "body": { "page": 2, "limit": 10 }, "merge": true }, { "rel": "next", "href": "https://stac-api.example.com/search?page=2", "type": "application/geo+json" }, { "rel": "self", "type": "application/json", "href": "https://stac-api.example.com" }, { "rel": "root", "type": "application/json", "href": "https://stac-api.example.com" }, { "rel": "conformance", "type": "application/json", "href": "https://stac-api.example.com/conformance" }, { "rel": "service-desc", "type": "application/vnd.oai.openapi+json;version=3.0", "href": "https://stac-api.example.com/api" }, { "rel": "service-doc", "type": "text/html", "href": "https://stac-api.example.com/api.html" }, { "rel": "data", "type": "application/json", "href": "https://stac-api.example.com/collections" } ] } stac-pydantic-3.4.0/tests/api/extensions/000077500000000000000000000000001503615555000204005ustar00rootroot00000000000000stac-pydantic-3.4.0/tests/api/extensions/__init__.py000066400000000000000000000000001503615555000224770ustar00rootroot00000000000000stac-pydantic-3.4.0/tests/api/extensions/test_fields.py000066400000000000000000000025341503615555000232630ustar00rootroot00000000000000from datetime import datetime, timezone from shapely.geometry import Polygon from stac_pydantic.api import Item from stac_pydantic.api.extensions.fields import FieldsExtension from stac_pydantic.api.search import Search def test_fields_filter_item(): fields = FieldsExtension( includes={"id", "geometry", "properties.foo"}, excludes={"properties.bar"} ) item = Item( id="test-fields-filter", geometry=Polygon.from_bounds(0, 0, 0, 0), properties={"datetime": datetime.now(timezone.utc), "foo": "foo", "bar": "bar"}, assets={}, links=[ {"href": "http://link", "rel": "self"}, { "href": "http://root", "rel": "root", }, { "href": "http://collection", "rel": "collection", }, ], bbox=[0, 0, 0, 0], type="Feature", ) d = item.model_dump(**fields.filter) assert d.pop("id") == item.id assert d.pop("geometry") == item.geometry.model_dump(exclude_unset=True) props = d.pop("properties") assert props["foo"] == "foo" assert not props.get("bar") assert not d def test_api_fields_extension(): Search( collections=["collection1"], fields={"includes": {"field1", "field2"}, "excludes": {"field3", "field4"}}, ) stac-pydantic-3.4.0/tests/api/extensions/test_query.py000066400000000000000000000012051503615555000231540ustar00rootroot00000000000000import pytest from pydantic import ValidationError from stac_pydantic.api.search import ExtendedSearch, Search def test_api_query_extension(): # One field Search(collections=["collection1", "collection2"], query={"field": {"lt": 100}}) # Many fields Search( collections=["collection1", "collection2"], query={"field": {"lt": 100}, "field1": {"gt": 200}}, ) def test_api_query_extension_invalid(): # Invalid operator with pytest.raises(ValidationError): ExtendedSearch( collections=["collection1", "collection2"], query={"field": {"greater_than": 100}}, ) stac-pydantic-3.4.0/tests/api/extensions/test_sort.py000066400000000000000000000011531503615555000230000ustar00rootroot00000000000000import pytest from pydantic import ValidationError from stac_pydantic.api.search import ExtendedSearch def test_api_sort_extension(): ExtendedSearch( collections=["collection1", "collection2"], sortby=[ {"field": "field1", "direction": "asc"}, {"field": "field2", "direction": "desc"}, ], ) def test_api_sort_extension_invalid(): # Invalid sort direction with pytest.raises(ValidationError): ExtendedSearch( collections=["collection1", "collection2"], sortby=[{"field": "field1", "direction": "ascending"}], ) stac-pydantic-3.4.0/tests/api/test_collections.py000066400000000000000000000007671503615555000221420ustar00rootroot00000000000000from stac_pydantic.api import Collections from stac_pydantic.api.version import STAC_API_VERSION from ..conftest import dict_match, request EXAMPLE_COLLECTION_LIST = "example-collection-list.json" PATH = ["tests", "api", "examples", f"v{STAC_API_VERSION}"] def test_collection_list(): test_collection_list = request(EXAMPLE_COLLECTION_LIST, PATH) valid_collection_list = Collections(**test_collection_list).model_dump(mode="json") dict_match(test_collection_list, valid_collection_list) stac-pydantic-3.4.0/tests/api/test_conformance.py000066400000000000000000000010041503615555000220770ustar00rootroot00000000000000import pytest from pydantic import ValidationError from stac_pydantic.api.conformance import Conformance def test_deprecation(): with pytest.warns(DeprecationWarning): from stac_pydantic.api import ConformanceClasses # noqa def test_api_conformance(): Conformance( conformsTo=["https://conformance-class-1", "http://conformance-class-2"] ) def test_api_conformance_invalid_url(): with pytest.raises(ValidationError): Conformance(conformsTo=["s3://conformance-class"]) stac-pydantic-3.4.0/tests/api/test_item_collection.py000066400000000000000000000030171503615555000227640ustar00rootroot00000000000000import json import pytest from stac_pydantic.api import ItemCollection from stac_pydantic.api.version import STAC_API_VERSION from stac_pydantic.version import STAC_VERSION from ..conftest import compare_example, dict_match, request ITEM_COLLECTION = "itemcollection-sample-full.json" PATH = ["tests", "api", "examples", f"v{STAC_API_VERSION}"] @pytest.mark.parametrize( "example_url", [ f"https://raw.githubusercontent.com/radiantearth/stac-api-spec/v{STAC_API_VERSION}/fragments/itemcollection/examples/itemcollection-sample-full.json", f"https://raw.githubusercontent.com/radiantearth/stac-api-spec/v{STAC_API_VERSION}/fragments/itemcollection/examples/itemcollection-sample-minimal.json", ], ) def test_item_collection_examples(example_url): """ Testing the less strict version here, not enforcing required Links """ compare_example(example_url, ItemCollection) def test_item_collection(): test_item_coll = request(ITEM_COLLECTION, PATH) for feat in test_item_coll["features"]: feat["stac_version"] = STAC_VERSION valid_item_coll = ItemCollection(**test_item_coll).model_dump() for idx, feat in enumerate(test_item_coll["features"]): dict_match(feat, valid_item_coll["features"][idx]) def test_to_json(): test_item = request(ITEM_COLLECTION, PATH) for feat in test_item["features"]: feat["stac_version"] = STAC_VERSION validated = ItemCollection(**test_item) dict_match(json.loads(validated.model_dump_json()), validated.model_dump()) stac-pydantic-3.4.0/tests/api/test_landing_page.py000066400000000000000000000100611503615555000222200ustar00rootroot00000000000000import jsonschema import pytest import requests import yaml from pydantic import ValidationError from stac_pydantic import Catalog from stac_pydantic.api.landing import LandingPage from stac_pydantic.api.links import Link from stac_pydantic.api.version import STAC_API_VERSION from ..conftest import compare_example, request valid_examples = ["landing_page_core.json", "landing_page_ogcapi-features.json"] schema_url = [ f"https://raw.githubusercontent.com/radiantearth/stac-api-spec/v{STAC_API_VERSION}/core/commons.yaml", f"https://raw.githubusercontent.com/radiantearth/stac-api-spec/v{STAC_API_VERSION}/ogcapi-features/openapi-features.yaml", ] unique_combinations = [] for i in range(len(valid_examples)): for j in range(len(schema_url)): unique_combinations.append((valid_examples[i], schema_url[j])) @pytest.mark.parametrize("example_url", valid_examples) def test_landing_page(example_url): compare_example( example_url, LandingPage, path=["tests", "api", "examples", f"v{STAC_API_VERSION}"], ) @pytest.mark.parametrize("example_url", valid_examples) def test_landing_page_invalid_core(example_url): example = request( example_url, path=["tests", "api", "examples", f"v{STAC_API_VERSION}"] ) example["links"] = [] with pytest.raises(ValidationError): LandingPage(**example) @pytest.mark.parametrize("example_url", valid_examples) def test_landing_page_invalid_features(example_url): example = request( example_url, path=["tests", "api", "examples", f"v{STAC_API_VERSION}"] ) links = [ link for link in example["links"] if link["rel"] not in ["data", "conformance"] ] example["links"] = links with pytest.raises(ValidationError): LandingPage(**example) @pytest.mark.parametrize("example_url,schema_url", unique_combinations) def test_schema(example_url, schema_url): rsp_yaml = requests.get(schema_url).text schema = yaml.safe_load(rsp_yaml) example = request( example_url, path=["tests", "api", "examples", f"v{STAC_API_VERSION}"] ) landing_page = LandingPage(**example).model_dump_json() jsonschema.validate(instance=landing_page, schema=schema) def test_api_landing_page(): LandingPage( type="Catalog", id="test-landing-page", description="stac-api landing page", stac_extensions=[ "https://raw.githubusercontent.com/stac-extensions/eo/v1.0.0/json-schema/schema.json", "https://raw.githubusercontent.com/stac-extensions/projection/v1.0.0/json-schema/schema.json", ], conformsTo=[ "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson", ], links=[ Link( href="http://link", rel="self", ), Link( href="http://root", rel="root", ), Link( href="http://service_desc", rel="service-desc", ), ], ) def test_api_landing_page_is_catalog(): landing_page = LandingPage( type="Catalog", id="test-landing-page", description="stac-api landing page", stac_extensions=[ "https://raw.githubusercontent.com/stac-extensions/eo/v1.0.0/json-schema/schema.json", "https://raw.githubusercontent.com/stac-extensions/projection/v1.0.0/json-schema/schema.json", ], conformsTo=[ "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/core", "http://www.opengis.net/spec/ogcapi-features-1/1.0/conf/geojson", ], links=[ Link( href="http://link", rel="self", ), Link( href="http://root", rel="root", ), Link( href="http://service_desc", rel="service-desc", ), ], ) d = landing_page.model_dump() Catalog(**d) stac-pydantic-3.4.0/tests/api/test_link_factory.py000066400000000000000000000025651503615555000223060ustar00rootroot00000000000000from typing import ClassVar, Tuple from urllib.parse import urljoin from stac_pydantic.api.utils.link_factory import BaseLinks, CollectionLinks, ItemLinks from stac_pydantic.links import Link from stac_pydantic.shared import MimeTypes def test_collection_links() -> None: links = CollectionLinks( collection_id="collection", base_url="http://stac.com" ).create_links() for link in links.link_iterator(): assert isinstance(link, Link) assert link.rel in CollectionLinks._link_members def test_item_links() -> None: links = ItemLinks( collection_id="collection", item_id="item", base_url="http://stac.com" ).create_links() for link in links.link_iterator(): assert isinstance(link, Link) assert link.rel in ItemLinks._link_members def test_custom_links() -> None: class CustomLinks(BaseLinks): _link_members: ClassVar[Tuple[str]] = ("another_link",) def another_link(self) -> Link: return Link( rel="another-link", type=MimeTypes.json, href=urljoin(self.base_url, "/another-link"), ) links = CustomLinks(base_url="http://stac.com").create_links() assert len(links) == 1 assert links[0].rel == "another-link" assert links[0].type == "application/json" assert links[0].href == "http://stac.com/another-link" stac-pydantic-3.4.0/tests/api/test_links.py000066400000000000000000000055661503615555000207460ustar00rootroot00000000000000import pytest from pydantic import ValidationError from stac_pydantic.api import ItemCollection from stac_pydantic.api.links import Link, Links, PaginationLink, Relations, SearchLink from stac_pydantic.api.version import STAC_API_VERSION from stac_pydantic.links import MimeTypes from stac_pydantic.version import STAC_VERSION from ..conftest import request PATH = ["tests", "api", "examples", f"v{STAC_API_VERSION}"] ITEM_COLLECTION = "itemcollection-sample-full.json" def test_links(): example = request( "links.json", ["tests", "api", "examples", f"v{STAC_API_VERSION}"] )["links"] links = Links(example) for link in links.root: assert isinstance(link, Link) if link.rel in [Relations.search, Relations.next, Relations.prev]: assert isinstance(link, SearchLink) def test_api_invalid_paging_link(): # Invalid rel type with pytest.raises(ValidationError): PaginationLink(rel="self", method="GET", href="http://next") # Invalid method with pytest.raises(ValidationError): PaginationLink(rel="next", method="DELETE", href="http://next") def test_api_paging_extension(): item_collection = request(ITEM_COLLECTION, PATH) item_collection["stac_version"] = STAC_VERSION for feat in item_collection["features"]: feat["stac_version"] = STAC_VERSION item_collection["links"] += [ { "title": "next page", "rel": Relations.next, "method": "GET", "href": "http://my.stac.com/search?next", "type": MimeTypes.geojson, }, { "title": "previous page", "rel": Relations.prev, "method": "POST", "href": "http://my.stac.com/search?prev", "body": {"key": "value"}, "type": MimeTypes.geojson, }, ] model = ItemCollection(**item_collection) links = model.model_dump()["links"] # assert False # Make sure we can mix links and pagination links normal_link = Link(**links[0]) assert normal_link.rel == "self" next_link = PaginationLink(**links[2]) assert next_link.rel == "next" previous_link = PaginationLink(**links[3]) assert previous_link.rel == "prev" assert previous_link.body == {"key": "value"} def test_resolve_pagination_link(): normal_link = Link(href="/hello/world", type="image/jpeg", rel="test") page_link = PaginationLink( href="/next/page", type="image/jpeg", method="POST", rel="next" ) links = Links.model_validate([normal_link, page_link]) links.resolve(base_url="http://base_url.com") for link in links.link_iterator(): if isinstance(link, PaginationLink): assert link.href == "http://base_url.com/next/page" def test_link_types(): for type_ in (MimeTypes.xml, "some random string", None): Link(href="/hello/world", type=type_, rel="test") stac-pydantic-3.4.0/tests/api/test_search.py000066400000000000000000000154011503615555000210600ustar00rootroot00000000000000from datetime import datetime, timedelta, timezone import pytest from pydantic import ValidationError from shapely.geometry import Polygon, shape from stac_pydantic.api.search import Search def test_search(): Search(collections=["collection1", "collection2"]) def test_search_by_id(): Search(collections=["collection1", "collection2"], ids=["id1", "id2"]) def test_spatial_search(): # Search with bbox Search(collections=["collection1", "collection2"], bbox=[-180, -90, 180, 90]) # Search with geojson search = Search( collections=["collection1", "collection2"], intersects={"type": "Point", "coordinates": [0, 0]}, ) shape(search.intersects) # Search GeometryCollection search = Search( collections=["collection1", "collection2"], intersects={ "type": "GeometryCollection", "geometries": [{"type": "Point", "coordinates": [0, 0]}], }, ) def test_invalid_spatial_search(): # bbox and intersects are mutually exclusive with pytest.raises(ValidationError): Search( collections=["collection1", "collection2"], intersects={"type": "Point", "coordinates": [0, 0]}, bbox=[-180, -90, 180, 90], ) # Invalid geojson with pytest.raises(ValidationError): Search( collections=["collection1", "collection2"], intersects={"type": "Polygon", "coordinates": [0]}, ) def test_temporal_search_single_tailed(): # Test single tailed utcnow = datetime.now(timezone.utc) utcnow_str = utcnow.isoformat() search = Search(collections=["collection1"], datetime=utcnow_str) assert search.start_date == utcnow assert search.end_date == utcnow def test_datetime_clean(): # ref: https://github.com/stac-utils/stac-pydantic/issues/170 utcnow = datetime.now(timezone.utc) utcnow_str = utcnow.isoformat() search = Search(datetime=utcnow_str) assert search.start_date == utcnow assert search.end_date == utcnow search = Search() assert not search.start_date assert not search.end_date def test_temporal_search_two_tailed(): # Test two tailed utcnow = datetime.now(timezone.utc) utcnow_str = utcnow.isoformat() search = Search(collections=["collection1"], datetime=f"{utcnow_str}/{utcnow_str}") assert search.start_date == search.end_date == utcnow search = Search(collections=["collection1"], datetime=f"{utcnow_str}/..") assert search.start_date == utcnow assert search.end_date is None search = Search(collections=["collection1"], datetime=f"{utcnow_str}/") assert search.start_date == utcnow assert search.end_date is None search = Search(collections=["collection1"], datetime=f"../{utcnow_str}") assert search.start_date is None assert search.end_date == utcnow search = Search(collections=["collection1"], datetime=f"/{utcnow_str}") assert search.start_date is None assert search.end_date == utcnow def test_temporal_search_open(): # Test open date range search = Search(collections=["collection1"], datetime="../..") assert search.start_date is None assert search.end_date is None def test_invalid_temporal_search_date(): # Just a date, no time utcnow = datetime.now(timezone.utc).strftime("%Y-%m-%d") with pytest.raises(ValidationError): Search(collections=["collection1"], datetime=utcnow) def test_invalid_temporal_search_too_many(): # Too many dates t1 = datetime.now(timezone.utc) t2 = t1 + timedelta(seconds=100) t3 = t2 + timedelta(seconds=100) with pytest.raises(ValidationError): Search( collections=["collection1"], datetime=f"{t1.isoformat()}/{t2.isoformat()}/{t3.isoformat()}", ) def test_invalid_temporal_search_date_wrong_order(): # End date is before start date start = datetime.now(timezone.utc) end = start - timedelta(seconds=100) with pytest.raises(ValidationError): Search( collections=["collection1"], datetime=f"{start.isoformat()}/{end.isoformat()}", ) def test_search_geometry_bbox(): search = Search(collections=["foo", "bar"], bbox=[0, 0, 1, 1]) geom1 = shape(search.spatial_filter) geom2 = Polygon.from_bounds(*search.bbox) assert (geom1.intersection(geom2).area / geom1.union(geom2).area) == 1.0 @pytest.mark.parametrize( "bbox", [ (100.0, 1.0), # 1D Coordinates (100.0, 1.0, 105.0, 0.0), # ymin greater than ymax (100.0, 0.0, 5.0, 105.0, 1.0, 4.0), # min elev greater than max elev (-200.0, 0.0, 105.0, 1.0), # xmin is invalid WGS84 ( 105.0, 0.0, 100.0, 1.0, ), # xmin greater than xmax but not crossing Antimeridian (100.0, -100, 105.0, 1.0), # ymin is invalid WGS84 (100.0, 0.0, 190.0, 1.0), # xmax is invalid WGS84 (100.0, 0.0, 190.0, 100.0), # ymax is invalid WGS84 (-200.0, 0.0, 0.0, 105.0, 1.0, 4.0), # xmin is invalid WGS84 (3d) (100.0, -100, 0.0, 105.0, 1.0, 4.0), # ymin is invalid WGS84 (3d) (100.0, 0.0, 0.0, 190.0, 1.0, 4.0), # xmax is invalid WGS84 (3d) (100.0, 0.0, 0.0, 190.0, 100.0, 4.0), # ymax is invalid WGS84 (3d) ], ) def test_search_invalid_bbox(bbox): with pytest.raises(ValidationError): Search(collections=["foo"], bbox=bbox) def test_search_none_datetime() -> None: Search(datetime=None) @pytest.mark.parametrize( "dt,start,end", [ # unique datetime, start/end == datetime ["1985-04-12T23:20:50.52Z", False, False], # start datetime is None ["../1985-04-12T23:20:50.52Z", True, False], ["/1985-04-12T23:20:50.52Z", True, False], # end datetime is None ["1985-04-12T23:20:50.52Z/..", False, True], ["1985-04-12T23:20:50.52Z/", False, True], # Both start/end datetime are available ["1985-04-12T23:20:50.52Z/1986-04-12T23:20:50.52Z", False, False], ["1985-04-12T23:20:50.52+01:00/1986-04-12T23:20:50.52+01:00", False, False], ["1985-04-12T23:20:50.52-01:00/1986-04-12T23:20:50.52-01:00", False, False], ], ) def test_search_datetime(dt, start, end): s = Search(datetime=dt) assert s.datetime is not None assert (s.start_date is None) == start assert (s.end_date is None) == end @pytest.mark.parametrize( "dt", [ "/" "../" "/.." "../.." "/1984-04-12T23:20:50.52Z/1985-04-12T23:20:50.52Z", # extra start / "1984-04-12T23:20:50.52Z/1985-04-12T23:20:50.52Z/", # extra end / "1986-04-12T23:20:50.52Z/1985-04-12T23:20:50.52Z", # start > end ], ) def test_search_invalid_datetime(dt): with pytest.raises(ValidationError): Search(datetime=dt) stac-pydantic-3.4.0/tests/conftest.py000066400000000000000000000051051503615555000176300ustar00rootroot00000000000000import json import os from copy import deepcopy from typing import List, Optional, Type import dictdiffer import pytest import requests from click.testing import CliRunner from pydantic import BaseModel, TypeAdapter from stac_pydantic.shared import UtcDatetime def request(url: str, path: Optional[List[str]] = None): if path is None: path = ["tests", "example_stac"] if url.startswith("http"): r = requests.get(url) r.raise_for_status() return r.json() else: _full_path = deepcopy(path) _full_path.append(url) full_path = os.path.join(*_full_path) with open(full_path, "r") as local_file: lines = local_file.readlines() full_file = "".join(lines) return json.loads(full_file) # Use a TypeAdapter to parse any datetime strings in a consistent manner UtcDatetimeAdapter = TypeAdapter(UtcDatetime) def dict_match(d1: dict, d2: dict): test = dictdiffer.diff(d1, d2) for diff in test: # geojson-pydantic uses tuples for coordinates, but sometimes the example data are lists if "coordinates" in diff[1]: assert list(diff[2][0]) == list(diff[2][1]) # same for bbox elif "bbox" in diff[1]: assert list(diff[2][0]) == list(diff[2][1]) # RFC3339 is quite flexible and the test data uses various options to represent datetimes. # The datetime string stac-pydantic outputs may not be identical to the input. So we need # to compare the values as datetime objects. elif "datetime" in diff[1]: dates = [ UtcDatetimeAdapter.validate_strings(date, strict=True) if isinstance(date, str) else date for date in diff[2] ] assert dates[0] == dates[1] # any other differences are errors elif "stac_extensions" in diff[1]: url1, url2 = map(str, diff[2]) assert url1 == url2 else: raise AssertionError("Unexpected difference: ", diff) def compare_example( example_url: str, model: Type[BaseModel], fields: Optional[List[str]] = None, path: Optional[List[str]] = None, ) -> None: if path is None: path = ["tests", "example_stac"] example = request(example_url, path) model_dict = json.loads(model(**example).model_dump_json()) if fields: for field in fields: assert model_dict.get(field) == example.get(field) else: dict_match(model_dict, example) @pytest.fixture def cli_runner(): return CliRunner() stac-pydantic-3.4.0/tests/example_stac/000077500000000000000000000000001503615555000200755ustar00rootroot00000000000000stac-pydantic-3.4.0/tests/example_stac/datetimerange.json000066400000000000000000000027411503615555000236050ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [], "type":"Feature", "id":"half-moon-bay/video-2018-01-01-0001", "bbox":[ -122.44674682617188, 37.45551035774642, -122.41241455078125, 37.47077095413427 ], "geometry":{ "type":"Polygon", "coordinates":[ [ [ -122.44674682617188, 37.45551035774642 ], [ -122.41241455078125, 37.45551035774642 ], [ -122.41241455078125, 37.47077095413427 ], [ -122.44674682617188, 37.47077095413427 ], [ -122.44674682617188, 37.45551035774642 ] ] ] }, "properties":{ "datetime":null, "start_datetime":"2018-01-01T13:21:30Z", "end_datetime":"2018-01-01T13:31:30Z" }, "links":[ { "rel":"self", "href":"http://cool-drones.com/catalog/half-moon-bay/video-2018-01-01-0001.json" } ], "assets":{ "video":{ "href":"http://cool-drones.com/catalog/half-moon-bay/video-2018-01-01-0001.mpeg", "title":"10 minute drone video captured over Half Moon Bay, California, USA.", "type":"video/mpeg" }, "thumbnail":{ "href":"http://cool-drones.com/catalog/half-moon-bay/video-2018-01-01-0001.jpg", "type":"image/jpeg", "title":"Thumbnail" } } } stac-pydantic-3.4.0/tests/example_stac/example-autzen.json000066400000000000000000000155321503615555000237350ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json" ], "assets": {}, "bbox": [ -123.0755422, 44.04971882, 123.791472, -123.0619599, 44.06278031, 187.531248 ], "geometry": { "coordinates": [ [ [ -123.07498674, 44.04971882 ], [ -123.07554223, 44.06248623 ], [ -123.0625126, 44.06278031 ], [ -123.06195992, 44.05001283 ], [ -123.07498674, 44.04971882 ] ] ], "type": "Polygon" }, "id": "autzen-full.laz", "links": [ { "href": "/Users/hobu/dev/git/pdal/test/data/autzen/autzen-full.laz", "rel": "self" } ], "properties": { "datetime": "2013-07-17T00:00:00Z", "pc:count": 10653336, "pc:density": 0, "pc:encoding": "LASzip", "pc:schemas": [ { "name": "X", "size": 8, "type": "floating" }, { "name": "Y", "size": 8, "type": "floating" }, { "name": "Z", "size": 8, "type": "floating" }, { "name": "Intensity", "size": 2, "type": "unsigned" }, { "name": "ReturnNumber", "size": 1, "type": "unsigned" }, { "name": "NumberOfReturns", "size": 1, "type": "unsigned" }, { "name": "ScanDirectionFlag", "size": 1, "type": "unsigned" }, { "name": "EdgeOfFlightLine", "size": 1, "type": "unsigned" }, { "name": "Classification", "size": 1, "type": "unsigned" }, { "name": "ScanAngleRank", "size": 4, "type": "floating" }, { "name": "UserData", "size": 1, "type": "unsigned" }, { "name": "PointSourceId", "size": 2, "type": "unsigned" }, { "name": "GpsTime", "size": 8, "type": "floating" }, { "name": "Red", "size": 2, "type": "unsigned" }, { "name": "Green", "size": 2, "type": "unsigned" }, { "name": "Blue", "size": 2, "type": "unsigned" } ], "pc:statistics": [ { "average": 637294.1783, "count": 10653336, "maximum": 639003.73, "minimum": 635577.79, "name": "X", "position": 0, "stddev": 967.9329805, "variance": 936894.2548 }, { "average": 851247.6953, "count": 10653336, "maximum": 853537.66, "minimum": 848882.15, "name": "Y", "position": 1, "stddev": 1322.356387, "variance": 1748626.415 }, { "average": 434.1025002, "count": 10653336, "maximum": 615.26, "minimum": 406.14, "name": "Z", "position": 2, "stddev": 24.67893148, "variance": 609.0496589 }, { "average": 77.14742312, "count": 10653336, "maximum": 254, "minimum": 0, "name": "Intensity", "position": 3, "stddev": 62.62422344, "variance": 3921.793362 }, { "average": 1.17801438, "count": 10653336, "maximum": 4, "minimum": 1, "name": "ReturnNumber", "position": 4, "stddev": 0.4653418642, "variance": 0.2165430505 }, { "average": 1.358579791, "count": 10653336, "maximum": 4, "minimum": 1, "name": "NumberOfReturns", "position": 5, "stddev": 0.6656066447, "variance": 0.4430322055 }, { "average": 0.4989654884, "count": 10653336, "maximum": 1, "minimum": 0, "name": "ScanDirectionFlag", "position": 6, "stddev": 0.4999993213, "variance": 0.2499993213 }, { "average": 0, "count": 10653336, "maximum": 0, "minimum": 0, "name": "EdgeOfFlightLine", "position": 7, "stddev": 0, "variance": 0 }, { "average": 1.256686262, "count": 10653336, "maximum": 2, "minimum": 1, "name": "Classification", "position": 8, "stddev": 0.436805292, "variance": 0.1907988632 }, { "average": -0.812061405, "count": 10653336, "maximum": 20, "minimum": -21, "name": "ScanAngleRank", "position": 9, "stddev": 8.484319324, "variance": 71.98367439 }, { "average": 126.4052859, "count": 10653336, "maximum": 156, "minimum": 115, "name": "UserData", "position": 10, "stddev": 3.833000243, "variance": 14.69189086 }, { "average": 7329.903705, "count": 10653336, "maximum": 7334, "minimum": 7326, "name": "PointSourceId", "position": 11, "stddev": 3.107430355, "variance": 9.656123408 }, { "average": 121.3214254, "count": 10653336, "maximum": 255, "minimum": 35, "name": "Red", "position": 12, "stddev": 45.56263834, "variance": 2075.954013 }, { "average": 126.2526972, "count": 10653336, "maximum": 255, "minimum": 49, "name": "Green", "position": 13, "stddev": 36.85451838, "variance": 1358.255525 }, { "average": 111.2207554, "count": 10653336, "maximum": 255, "minimum": 49, "name": "Blue", "position": 14, "stddev": 31.95561927, "variance": 1021.161603 }, { "average": 247608.4011, "count": 10653336, "maximum": 249783.703, "minimum": 245369.8966, "name": "GpsTime", "position": 15, "stddev": 1178.538827, "variance": 1388953.768 } ], "pc:type": "lidar", "title": "USGS 3DEP LiDAR" }, "type": "Feature" } stac-pydantic-3.4.0/tests/example_stac/example-collection-list.json000066400000000000000000001702721503615555000255360ustar00rootroot00000000000000{ "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections", "rel":"self", "type":"application/json" } ], "collections":[ { "id":"aster-l1t", "description":"The [ASTER](https://terra.nasa.gov/about/terra-instruments/aster) instrument, launched on-board NASA's [Terra](https://terra.nasa.gov/) satellite in 1999, provides multispectral images of the Earth at 15m-90m resolution. ASTER images provide information about land surface temperature, color, elevation, and mineral composition.\n\nThis dataset represents ASTER [L1T](https://lpdaac.usgs.gov/products/ast_l1tv003/) data from 2000-2006. L1T images have been terrain-corrected and rotated to a north-up UTM projection. Images are in [cloud-optimized GeoTIFF](https://www.cogeo.org/) format.\n", "stac_version":"1.0.0", "type": "Collection", "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/aster-l1t", "rel":"self", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/", "rel":"parent", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/aster-l1t/items", "rel":"item", "type":"application/geo+json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/", "rel":"root", "type":"application/json" }, { "href":"https://www.usgs.gov/core-science-systems/hdds/data-policy", "rel":"license", "title":"public domain" } ], "stac_extensions":[ "https://raw.githubusercontent.com/stac-extensions/item-assets/v1.0.0/json-schema/schema.json" ], "title":"ASTER L1T", "license":"proprietary", "extent":{ "spatial":{ "bbox":[ [ -180.0, -90.0, 180.0, 90.0 ] ] }, "temporal":{ "interval":[ [ "2000-03-04T12:00:00.000000Z", "2006-12-31T12:00:00.000000Z" ] ] } }, "keywords":[ "aster", "usgs", "nasa", "satellite", "global" ], "providers":[ { "name":"NASA", "roles":[ "producer", "licensor" ], "url":"https://terra.nasa.gov/about/terra-instruments/aster" }, { "name":"USGS", "roles":[ "processor", "producer", "licensor" ], "url":"https://lpdaac.usgs.gov/data/get-started-data/collection-overview/missions/aster-overview/" }, { "name":"Microsoft", "roles":[ "host", "processor" ], "url":"https://planetarycomputer.microsoft.com" } ], "summaries":{ "gsd":[ 15, 30, 90 ], "eo:bands":[ { "gsd":15, "name":"VNIR_Band1", "common_name":"yellow/green", "description":"visible yellow/green", "center_wavelength":0.56, "full_width_half_max":0.08 }, { "gsd":15, "name":"VNIR_Band2", "common_name":"red", "description":"visible red", "center_wavelength":0.66, "full_width_half_max":0.06 }, { "gsd":15, "name":"VNIR_Band3N", "common_name":"near infrared", "description":"near infrared", "center_wavelength":0.82, "full_width_half_max":0.08 }, { "gsd":30, "name":"SWIR_Band4", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":1.65, "full_width_half_max":0.1 }, { "gsd":30, "name":"SWIR_Band5", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.165, "full_width_half_max":0.04 }, { "gsd":30, "name":"SWIR_Band6", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.205, "full_width_half_max":0.04 }, { "gsd":30, "name":"SWIR_Band7", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.26, "full_width_half_max":0.05 }, { "gsd":30, "name":"SWIR_Band8", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.339, "full_width_half_max":0.07 }, { "gsd":30, "name":"SWIR_Band9", "common_name":"swir", "description":"short-wave infrared", "center_wavelength":2.395, "full_width_half_max":0.07 }, { "gsd":90, "name":"TIR_Band10", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":8.3, "full_width_half_max":0.35 }, { "gsd":90, "name":"TIR_Band11", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":8.65, "full_width_half_max":0.35 }, { "gsd":90, "name":"TIR_Band12", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":9.11, "full_width_half_max":0.35 }, { "gsd":90, "name":"TIR_Band13", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":10.6, "full_width_half_max":0.7 }, { "gsd":90, "name":"TIR_Band14", "common_name":"lwir", "description":"thermal infrared", "center_wavelength":11.3, "full_width_half_max":0.7 } ], "platform":[ "terra" ], "instruments":[ "aster" ] }, "msft:storage_account":"astersa", "msft:container":"aster", "msft:short_description":"The ASTER instrument, launched on-board NASA's Terra satellite in 1999, provides multispectral images of the Earth at 15m-90m resolution. This dataset contains ASTER data from 2000-2006.", "item_assets":{ "TIR":{ "title":"TIR Swath data", "roles":[ "data" ], "eo:bands":[ { "name":"TIR_Band10", "common_name":"lwir", "center_wavelength":8.3, "full_width_half_max":0.35, "description":"thermal infrared" }, { "name":"TIR_Band11", "common_name":"lwir", "center_wavelength":8.65, "full_width_half_max":0.35, "description":"thermal infrared" }, { "name":"TIR_Band12", "common_name":"lwir", "center_wavelength":9.11, "full_width_half_max":0.35, "description":"thermal infrared" }, { "name":"TIR_Band13", "common_name":"lwir", "center_wavelength":10.6, "full_width_half_max":0.7, "description":"thermal infrared" }, { "name":"TIR_Band14", "common_name":"lwir", "center_wavelength":11.3, "full_width_half_max":0.7, "description":"thermal infrared" } ] }, "xml":{ "title":"XML metadata", "type":"application/xml", "roles":[ "metadata" ] }, "SWIR":{ "title":"SWIR Swath data", "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"SWIR_Band4", "common_name":"swir", "center_wavelength":1.65, "full_width_half_max":0.1, "description":"short-wave infrared" }, { "name":"SWIR_Band5", "common_name":"swir", "center_wavelength":2.165, "full_width_half_max":0.04, "description":"short-wave infrared" }, { "name":"SWIR_Band6", "common_name":"swir", "center_wavelength":2.205, "full_width_half_max":0.04, "description":"short-wave infrared" }, { "name":"SWIR_Band7", "common_name":"swir", "center_wavelength":2.26, "full_width_half_max":0.05, "description":"short-wave infrared" }, { "name":"SWIR_Band8", "common_name":"swir", "center_wavelength":2.339, "full_width_half_max":0.07, "description":"short-wave infrared" }, { "name":"SWIR_Band9", "common_name":"swir", "center_wavelength":2.395, "full_width_half_max":0.07, "description":"short-wave infrared" } ] }, "VNIR":{ "title":"VNIR Swath data", "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"VNIR_Band1", "common_name":"yellow/green", "center_wavelength":0.56, "full_width_half_max":0.08, "description":"visible yellow/green" }, { "name":"VNIR_Band2", "common_name":"red", "center_wavelength":0.66, "full_width_half_max":0.06, "description":"visible red" }, { "name":"VNIR_Band3N", "common_name":"near infrared", "center_wavelength":0.82, "full_width_half_max":0.08, "description":"near infrared" } ] }, "qa-txt":{ "title":"QA browse file", "description":"Geometric quality assessment report.", "type":"text/plain", "roles":[ "metadata" ] }, "qa-browse":{ "title":"QA browse file", "description":"Single-band black and white reduced resolution browse overlaid with red, green, and blue (RGB) markers for GCPs used during the geometric verification quality check.", "type":"image/jpeg", "roles":[ "thumbnail" ] }, "tir-browse":{ "title":"Standalone reduced resolution TIR", "type":"image/jpeg", "roles":[ "thumbnail" ] }, "vnir-browse":{ "title":"VNIR browse file", "description":"Standalone reduced resolution VNIR", "type":"image/jpeg", "roles":[ "thumbnail" ] } }, "assets":{ "thumbnail":{ "title":"ASTER L1T", "href":"https://ai4edatasetspublicassets.blob.core.windows.net/assets/pc_thumbnails/aster.png", "media_type":"image/png" } } }, { "id":"landsat-8-c2-l2", "description":"The [Landsat](https://landsat.gsfc.nasa.gov/) program has been imaging the Earth since 1972; it provides a comprehensive, continuous archive of the Earth's surface. [Landsat 8](https://www.usgs.gov/core-science-systems/nli/landsat/landsat-8) is the most recent satellite in the Landsat series. Launched in 2013, Landsat 8 captures data in eleven spectral bands: ten optical/IR bands from the [Operational Land Imager](https://landsat.gsfc.nasa.gov/landsat-8/operational-land-imager) (OLI) instrument, and two thermal bands from the [Thermal Infrared Sensor](https://landsat.gsfc.nasa.gov/landsat-8/thermal-infrared-sensor-tirs) (TIRS) instrument.\n\nThis dataset represents the global archive of Level-2 Landsat 8 data from [Landsat Collection 2](https://www.usgs.gov/core-science-systems/nli/landsat/landsat-collection-2). Because there is some latency before Level-2 data is available, a rolling window of recent Level-1 data is available as well. Images are stored in [cloud-optimized GeoTIFF](https://www.cogeo.org/) format.\n", "stac_version":"1.0.0", "type": "Collection", "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/landsat-8-c2-l2", "rel":"self", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/", "rel":"parent", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/landsat-8-c2-l2/items", "rel":"item", "type":"application/geo+json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/", "rel":"root", "type":"application/json" }, { "href":"https://www.usgs.gov/core-science-systems/hdds/data-policy", "rel":"license", "title":"public domain" } ], "stac_extensions":[ "https://raw.githubusercontent.com/stac-extensions/item-assets/v1.0.0/json-schema/schema.json" ], "title":"Landsat 8 Collection 2 Level-2", "license":"proprietary", "extent":{ "spatial":{ "bbox":[ [ -180.0, -90.0, 180.0, 90.0 ] ] }, "temporal":{ "interval":[ [ "2013-04-11T00:00:00Z", null ] ] } }, "keywords":[ "landsat", "usgs", "nasa", "satellite", "global", "reflectance" ], "providers":[ { "name":"NASA", "roles":[ "producer", "licensor" ], "url":"https://landsat.gsfc.nasa.gov/" }, { "name":"USGS", "roles":[ "producer", "processor", "licensor" ], "url":"https://www.usgs.gov/core-science-systems/nli/landsat/landsat-collection-2-level-2-science-products" }, { "name":"Microsoft", "roles":[ "host" ], "url":"https://planetarycomputer.microsoft.com" } ], "summaries":{ "gsd":[ 15, 30, 100 ], "eo:bands":[ { "gsd":30, "name":"SR_B1", "common_name":"coastal", "description":"coastal", "center_wavelength":0.44, "full_width_half_max":0.02 }, { "gsd":30, "name":"SR_B2", "common_name":"blue", "description":"visible blue", "center_wavelength":0.48, "full_width_half_max":0.06 }, { "gsd":30, "name":"SR_B3", "common_name":"green", "description":"visible green", "center_wavelength":0.56, "full_width_half_max":0.06 }, { "gsd":30, "name":"SR_B4", "common_name":"red", "description":"visible red", "center_wavelength":0.65, "full_width_half_max":0.04 }, { "gsd":30, "name":"SR_B5", "common_name":"nir", "description":"near-infrared", "center_wavelength":0.86, "full_width_half_max":0.03 }, { "gsd":30, "name":"SR_B6", "common_name":"swir16", "description":"short-wave infrared", "center_wavelength":1.6, "full_width_half_max":0.08 }, { "gsd":30, "name":"SR_B7", "common_name":"swir22", "description":"short-wave infrared", "center_wavelength":2.2, "full_width_half_max":0.2 }, { "gsd":100, "name":"ST_B10", "common_name":"lwir11", "description":"long-wave infrared", "center_wavelength":10.9, "full_width_half_max":0.8 }, { "gsd":30, "name":"ST_TRAD", "description":"thermal radiance" }, { "gsd":30, "name":"ST_URAD", "description":"upwelled radiance" }, { "gsd":30, "name":"ST_ATRAN", "description":"atmospheric transmission" }, { "gsd":30, "name":"ST_CDIST", "description":"distance to nearest cloud" }, { "gsd":30, "name":"ST_DRAD", "description":"downwelled radiance" }, { "gsd":30, "name":"ST_EMIS", "description":"emissivity" }, { "gsd":30, "name":"ST_EMSD", "description":"emissivity standard deviation" } ], "platform":[ "landsat-8" ], "instruments":[ "oli", "tirs" ] }, "msft:storage_account":"landsateuwest", "msft:container":"landsat-c2", "msft:short_description":"Landsat 8 has captured 30m-resolution imagery of the Earth since 2013. This dataset contains global, atmospherically-corrected imagery from Landsat Collection 2", "item_assets":{ "ANG":{ "title":"Angle Coefficients File", "description":"Collection 2 Level-1 Angle Coefficients File (ANG)", "type":"text/plain" }, "SR_B1":{ "title":"Coastal/Aerosol Band (B1)", "description":"Collection 2 Level-2 Coastal/Aerosol Band (B1) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B1", "common_name":"coastal", "center_wavelength":0.44, "full_width_half_max":0.02 } ] }, "SR_B2":{ "title":"Blue Band (B2)", "description":"Collection 2 Level-2 Blue Band (B2) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B2", "common_name":"blue", "center_wavelength":0.48, "full_width_half_max":0.06 } ] }, "SR_B3":{ "title":"Green Band (B3)", "description":"Collection 2 Level-2 Green Band (B3) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B3", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.06 } ] }, "SR_B4":{ "title":"Red Band (B4)", "description":"Collection 2 Level-2 Red Band (B4) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B4", "common_name":"red", "center_wavelength":0.65, "full_width_half_max":0.04 } ] }, "SR_B5":{ "title":"Near Infrared Band 0.8 (B5)", "description":"Collection 2 Level-2 Near Infrared Band 0.8 (B5) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B5", "common_name":"nir08", "center_wavelength":0.86, "full_width_half_max":0.03 } ] }, "SR_B6":{ "title":"Short-wave Infrared Band 1.6 (B6)", "description":"Collection 2 Level-2 Short-wave Infrared Band 1.6 (B6) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B6", "common_name":"swir16", "center_wavelength":1.6, "full_width_half_max":0.08 } ] }, "SR_B7":{ "title":"Short-wave Infrared Band 2.2 (B7)", "description":"Collection 2 Level-2 Short-wave Infrared Band 2.2 (B7) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"SR_B7", "common_name":"swir22", "center_wavelength":2.2, "full_width_half_max":0.2 } ] }, "SR_B8":{ "title":"Aerosol Quality Analysis Band", "description":"Collection 2 Level-2 Aerosol Quality Analysis Band (ANG) Surface Reflectance", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized" }, "ST_QA":{ "title":"Surface Temperature Quality Assessment Band", "description":"Landsat Collection 2 Level-2 Surface Temperature Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized" }, "ST_B10":{ "title":"Surface Temperature Band (B10)", "description":"Landsat Collection 2 Level-2 Surface Temperature Band (B10) Surface Temperature Product", "gsd":100.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_B10", "common_name":"lwir11", "center_wavelength":10.9, "full_width_half_max":0.8 } ] }, "MTL.txt":{ "title":"Product Metadata File", "description":"Collection 2 Level-1 Product Metadata File (MTL)", "type":"text/plain" }, "MTL.xml":{ "title":"Product Metadata File (xml)", "description":"Collection 2 Level-1 Product Metadata File (xml)", "type":"application/xml" }, "ST_DRAD":{ "title":"Downwelled Radiance Band", "description":"Landsat Collection 2 Level-2 Downwelled Radiance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_DRAD", "description":"downwelled radiance" } ] }, "ST_EMIS":{ "title":"Emissivity Band", "description":"Landsat Collection 2 Level-2 Emissivity Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_EMIS", "description":"emissivity" } ] }, "ST_EMSD":{ "title":"Emissivity Standard Deviation Band", "description":"Landsat Collection 2 Level-2 Emissivity Standard Deviation Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_EMSD", "description":"emissivity standard deviation" } ] }, "ST_TRAD":{ "title":"Thermal Radiance Band", "description":"Landsat Collection 2 Level-2 Thermal Radiance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_TRAD", "description":"thermal radiance" } ] }, "ST_URAD":{ "title":"Upwelled Radiance Band", "description":"Landsat Collection 2 Level-2 Upwelled Radiance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_URAD", "description":"upwelled radiance" } ] }, "MTL.json":{ "title":"Product Metadata File (json)", "description":"Collection 2 Level-1 Product Metadata File (json)", "type":"application/json" }, "QA_PIXEL":{ "title":"Pixel Quality Assessment Band", "description":"Collection 2 Level-1 Pixel Quality Assessment Band", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized" }, "ST_ATRAN":{ "title":"Atmospheric Transmittance Band", "description":"Landsat Collection 2 Level-2 Atmospheric Transmittance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_ATRAN", "description":"atmospheric transmission" } ] }, "ST_CDIST":{ "title":"Cloud Distance Band", "description":"Landsat Collection 2 Level-2 Cloud Distance Band Surface Temperature Product", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "eo:bands":[ { "name":"ST_CDIST", "description":"distance to nearest cloud" } ] }, "QA_RADSAT":{ "title":"Radiometric Saturation Quality Assessment Band", "description":"Collection 2 Level-1 Radiometric Saturation Quality Assessment Band", "gsd":30.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized" }, "thumbnail":{ "title":"Thumbnail image", "type":"image/jpeg" }, "reduced_resolution_browse":{ "title":"Reduced resolution browse image", "type":"image/jpeg" } }, "assets":{ "thumbnail":{ "title":"Landsat 8 C2", "href":"https://ai4edatasetspublicassets.blob.core.windows.net/assets/pc_thumbnails/landsat.png", "media_type":"image/png" } } }, { "id":"sentinel-2-l2a", "description":"The [Sentinel-2](https://sentinel.esa.int/web/sentinel/missions/sentinel-2) program provides global imagery in thirteen spectral bands at 10m-60m resolution and a revisit time of approximately five days. This dataset represents the global Sentinel-2 archive, from 2016 to the present, processed to L2A (bottom-of-atmosphere) using [Sen2Cor](https://step.esa.int/main/snap-supported-plugins/sen2cor/) and converted to [cloud-optimized GeoTIFF](https://www.cogeo.org/) format.", "stac_version":"1.0.0", "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a", "rel":"self", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/", "rel":"parent", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a/items", "rel":"item", "type":"application/geo+json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/", "rel":"root", "type":"application/json" }, { "href":"https://scihub.copernicus.eu/twiki/pub/SciHubWebPortal/TermsConditions/Sentinel_Data_Terms_and_Conditions.pdf", "rel":"license", "title":"Copernicus Sentinel data terms" } ], "stac_extensions": [ "https://raw.githubusercontent.com/stac-extensions/item-assets/v1.0.0/json-schema/schema.json" ], "title":"Sentinel-2 Level-2A", "license":"proprietary", "extent":{ "spatial":{ "bbox":[ [ -180.0, -90.0, 180.0, 90.0 ] ] }, "temporal":{ "interval":[ [ "2015-06-27T10:25:31.456000Z", null ] ] } }, "keywords":[ "sentinel", "copernicus", "esa", "satellite", "global", "reflectance" ], "providers":[ { "name":"ESA", "roles":[ "producer", "licensor" ], "url":"https://sentinel.esa.int/web/sentinel/missions/sentinel-2" }, { "name":"Esri", "roles":[ "processor" ], "url":"https://www.esri.com/" }, { "name":"Microsoft", "roles":[ "host", "processor" ], "url":"https://planetarycomputer.microsoft.com" } ], "summaries":{ "gsd":[ 10, 20, 60 ], "eo:bands":[ { "name":"AOT", "description":"aerosol optical thickness" }, { "gsd":60, "name":"B01", "common_name":"coastal", "description":"coastal aerosol", "center_wavelength":0.443, "full_width_half_max":0.027 }, { "gsd":10, "name":"B02", "common_name":"blue", "description":"visible blue", "center_wavelength":0.49, "full_width_half_max":0.098 }, { "gsd":10, "name":"B03", "common_name":"green", "description":"visible green", "center_wavelength":0.56, "full_width_half_max":0.045 }, { "gsd":10, "name":"B04", "common_name":"red", "description":"visible red", "center_wavelength":0.665, "full_width_half_max":0.038 }, { "gsd":20, "name":"B05", "common_name":"rededge", "description":"vegetation classification red edge", "center_wavelength":0.704, "full_width_half_max":0.019 }, { "gsd":20, "name":"B06", "common_name":"rededge", "description":"vegetation classification red edge", "center_wavelength":0.74, "full_width_half_max":0.018 }, { "gsd":20, "name":"B07", "common_name":"rededge", "description":"vegetation classification red edge", "center_wavelength":0.783, "full_width_half_max":0.028 }, { "gsd":10, "name":"B08", "common_name":"nir", "description":"near infrared", "center_wavelength":0.842, "full_width_half_max":0.145 }, { "gsd":20, "name":"B8A", "common_name":"rededge", "description":"vegetation classification red edge", "center_wavelength":0.865, "full_width_half_max":0.033 }, { "gsd":60, "name":"B09", "description":"water vapor", "center_wavelength":0.945, "full_width_half_max":0.026 }, { "gsd":20, "name":"B11", "common_name":"swir16", "description":"short-wave infrared, snow/ice/cloud classification", "center_wavelength":1.61, "full_width_half_max":0.143 }, { "gsd":20, "name":"B12", "common_name":"swir22", "description":"short-wave infrared, snow/ice/cloud classification", "center_wavelength":2.19, "full_width_half_max":0.242 } ], "platform":[ "Sentinel-2A", "Sentinel-2B" ], "instruments":[ "msi" ], "constellation":[ "sentinel-2" ], "view:off_nadir":[ 0 ] }, "msft:storage_account":"sentinel2l2a01", "msft:container":"sentinel2-l2", "msft:short_description":"The Sentinel-2 program provides global imagery in thirteen spectral bands at 10m-60m resolution and a revisit time of approximately five days. This dataset contains the global Sentinel-2 archive, from 2016 to the present, processed to L2A (bottom-of-atmosphere).", "item_assets":{ "B01":{ "title":"Band 1 - Coastal aerosol", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B01", "common_name":"coastal", "center_wavelength":0.443, "full_width_half_max":0.027, "description":"Band 1 - Coastal aerosol" } ] }, "B02":{ "title":"Band 2 - Blue", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B02", "common_name":"blue", "center_wavelength":0.49, "full_width_half_max":0.098, "description":"Band 2 - Blue" } ] }, "B03":{ "title":"Band 3 - Green", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B03", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.045, "description":"Band 3 - Green" } ] }, "B04":{ "title":"Band 4 - Red", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B04", "common_name":"red", "center_wavelength":0.665, "full_width_half_max":0.038, "description":"Band 4 - Red" } ] }, "B05":{ "title":"Band 5 - Vegetation red edge 1", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B05", "common_name":"rededge", "center_wavelength":0.704, "full_width_half_max":0.019, "description":"Band 5 - Vegetation red edge 1" } ] }, "B06":{ "title":"Band 6 - Vegetation red edge 2", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B06", "common_name":"rededge", "center_wavelength":0.74, "full_width_half_max":0.018, "description":"Band 6 - Vegetation red edge 2" } ] }, "B07":{ "title":"Band 7 - Vegetation red edge 3", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B07", "common_name":"rededge", "center_wavelength":0.783, "full_width_half_max":0.028, "description":"Band 7 - Vegetation red edge 3" } ] }, "B08":{ "title":"Band 8 - NIR", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B08", "common_name":"nir", "center_wavelength":0.842, "full_width_half_max":0.145, "description":"Band 8 - NIR" } ] }, "B09":{ "title":"Band 9 - Water vapor", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B09", "center_wavelength":0.945, "full_width_half_max":0.026, "description":"Band 9 - Water vapor" } ] }, "B11":{ "title":"Band 11 - SWIR (1.6)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B11", "common_name":"swir16", "center_wavelength":1.61, "full_width_half_max":0.143, "description":"Band 11 - SWIR (1.6)" } ] }, "B12":{ "title":"Band 12 - SWIR (2.2)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B12", "common_name":"swir22", "center_wavelength":2.19, "full_width_half_max":0.242, "description":"Band 12 - SWIR (2.2)" } ] }, "B8A":{ "title":"Band 8A - Vegetation red edge 4", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B8A", "common_name":"rededge", "center_wavelength":0.865, "full_width_half_max":0.033, "description":"Band 8A - Vegetation red edge 4" } ] }, "AOT-10m":{ "title":"Aerosol optical thickness (AOT)", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "AOT-20m":{ "title":"Aerosol optical thickness (AOT)", "gsd":20.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "AOT-60m":{ "title":"Aerosol optical thickness (AOT)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "SCL-20m":{ "title":"Scene classfication map (SCL)", "gsd":20.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "SCL-60m":{ "title":"Scene classfication map (SCL)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "WVP-10m":{ "title":"Water vapour (WVP)", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "WVP-20m":{ "title":"Water vapour (WVP)", "gsd":20.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "WVP-60m":{ "title":"Water vapour (WVP)", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ] }, "preview":{ "title":"Thumbnail", "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "thumbnail" ] }, "visual-10m":{ "title":"True color image", "gsd":10.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B04", "common_name":"red", "center_wavelength":0.665, "full_width_half_max":0.038, "description":"Band 4 - Red" }, { "name":"B03", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.045, "description":"Band 3 - Green" }, { "name":"B02", "common_name":"blue", "center_wavelength":0.49, "full_width_half_max":0.098, "description":"Band 2 - Blue" } ] }, "visual-20m":{ "title":"True color image", "gsd":20.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B04", "common_name":"red", "center_wavelength":0.665, "full_width_half_max":0.038, "description":"Band 4 - Red" }, { "name":"B03", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.045, "description":"Band 3 - Green" }, { "name":"B02", "common_name":"blue", "center_wavelength":0.49, "full_width_half_max":0.098, "description":"Band 2 - Blue" } ] }, "visual-60m":{ "title":"True color image", "gsd":60.0, "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"B04", "common_name":"red", "center_wavelength":0.665, "full_width_half_max":0.038, "description":"Band 4 - Red" }, { "name":"B03", "common_name":"green", "center_wavelength":0.56, "full_width_half_max":0.045, "description":"Band 3 - Green" }, { "name":"B02", "common_name":"blue", "center_wavelength":0.49, "full_width_half_max":0.098, "description":"Band 2 - Blue" } ] }, "safe-manifest":{ "title":"SAFE manifest", "type":"application/xml", "roles":[ "metadata" ] }, "granule-metadata":{ "title":"Granule metadata", "type":"application/xml", "roles":[ "metadata" ] }, "inspire-metadata":{ "title":"INSPIRE metadata", "type":"application/xml", "roles":[ "metadata" ] }, "product-metadata":{ "title":"Product metadata", "type":"application/xml", "roles":[ "metadata" ] }, "datastrip-metadata":{ "title":"Datastrip metadata", "type":"application/xml", "roles":[ "metadata" ] } }, "assets":{ "thumbnail":{ "title":"Sentinel 2 L2A", "href":"https://ai4edatasetspublicassets.blob.core.windows.net/assets/pc_thumbnails/sentinel-2.png", "media_type":"image/png" } } }, { "id":"naip", "description":"The [National Agriculture Imagery Program](https://www.fsa.usda.gov/programs-and-services/aerial-photography/imagery-programs/naip-imagery/) (NAIP) provides US-wide, high-resolution aerial imagery, with four spectral bands (R, G, B, IR). NAIP is administered by the [Aerial Field Photography Office](https://www.fsa.usda.gov/programs-and-services/aerial-photography/) (AFPO) within the [US Department of Agriculture](https://www.usda.gov/) (USDA). Data are captured at least once every three years for each state. This dataset represents NAIP data from 2010-present, in [cloud-optimized GeoTIFF](https://www.cogeo.org/) format.\n", "stac_version":"1.0.0", "type": "Collection", "links":[ { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/naip", "rel":"self", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/", "rel":"parent", "type":"application/json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/collections/naip/items", "rel":"item", "type":"application/geo+json" }, { "href":"https://planetarycomputer.microsoft.com/api/stac/v1/", "rel":"root", "type":"application/json" }, { "href":"https://www.fsa.usda.gov/help/policies-and-links/", "rel":"license", "title":"public domain" } ], "stac_extensions":[ "https://raw.githubusercontent.com/stac-extensions/item-assets/v1.0.0/json-schema/schema.json" ], "title":"NAIP: National Agriculture Imagery Program", "license":"proprietary", "extent":{ "spatial":{ "bbox":[ [ -124.784, 24.744, -66.951, 49.346 ] ] }, "temporal":{ "interval":[ [ "2010-01-01T00:00:00Z", "2019-12-31T00:00:00Z" ] ] } }, "keywords":[ "naip", "aerial", "imagery", "usda", "afpo", "agriculture", "united states" ], "providers":[ { "name":"USDA Farm Service Agency", "roles":[ "producer", "licensor" ], "url":"https://www.fsa.usda.gov/programs-and-services/aerial-photography/imagery-programs/naip-imagery/" }, { "name":"Esri", "roles":[ "processor" ], "url":"https://www.esri.com/" }, { "name":"Microsoft", "roles":[ "host", "processor" ], "url":"https://planetarycomputer.microsoft.com" } ], "summaries":{ "gsd":[ 0.6, 1.0 ], "eo:bands":[ { "name":"Red", "common_name":"red", "description":"visible red" }, { "name":"Green", "common_name":"green", "description":"visible green" }, { "name":"Blue", "common_name":"blue", "description":"visible blue" }, { "name":"NIR", "common_name":"nir", "description":"near-infrared" } ] }, "msft:storage_account":"naipeuwest", "msft:container":"naip", "msft:short_description":"NAIP provides US-wide, high-resolution aerial imagery. This dataset includes NAIP images from 2010 to the present.", "item_assets":{ "image":{ "title":"RGBIR COG tile", "type":"image/tiff; application=geotiff; profile=cloud-optimized", "roles":[ "data" ], "eo:bands":[ { "name":"Red", "common_name":"red" }, { "name":"Green", "common_name":"green" }, { "name":"Blue", "common_name":"blue" }, { "name":"NIR", "common_name":"nir", "description":"near-infrared" } ] }, "metadata":{ "title":"FGDC Metdata", "type":"text/plain", "roles":[ "metadata" ] }, "thumbnail":{ "title":"Thumbnail", "type":"image/jpeg", "roles":[ "thumbnail" ] } }, "assets":{ "thumbnail":{ "title":"Landsat 8 C2", "href":"https://ai4edatasetspublicassets.blob.core.windows.net/assets/pc_thumbnails/naip.png", "media_type":"image/png" } } } ] } stac-pydantic-3.4.0/tests/example_stac/example-collection_version-extension.json000066400000000000000000000021421503615555000303320ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": ["https://stac-extensions.github.io/version/v1.0.0/schema.json"], "type": "Collection", "id": "merraclim", "title": "MERRAclim", "description": "A high-resolution global dataset of remotely sensed bioclimatic variables for ecological modelling.", "license": "CC0-1.0", "version": "1", "deprecated": true, "extent": { "spatial": { "bbox": [[-180,-90,180,90]] }, "temporal": { "interval": [["1980-01-01T00:00:00Z","2009-12-31T23:59:59Z"]] } }, "links": [ { "rel": "self", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/v1/collection.json" }, { "rel": "item", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/v1/item.json" }, { "rel": "root", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/catalog.json" }, { "rel": "latest-version", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/v2/collection.json" } ] } stac-pydantic-3.4.0/tests/example_stac/example-item_datacube-extension.json000066400000000000000000000047601503615555000272300ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/datacube/v1.0.0/schema.json" ], "id": "datacube-123", "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [ -122.308150179, 37.488035566 ], [ -122.597502109, 37.538869539 ], [ -122.576687533, 37.613537207 ], [ -122.2880486, 37.562818007 ], [ -122.308150179, 37.488035566 ] ] ] }, "bbox": [ -122.59750209, 37.48803556, -122.2880486, 37.613537207 ], "properties": { "title": "Multi-dimensional data cube 123 in a STAC Item.", "datetime": "2016-05-03T13:21:30.040Z", "cube:dimensions": { "x": { "type": "spatial", "axis": "x", "extent": [ -122.59750209, -122.2880486 ], "reference_system": 4326 }, "y": { "type": "spatial", "axis": "y", "extent": [ 37.48803556, 37.613537207 ], "reference_system": 4326 }, "pressure_levels": { "type": "spatial", "axis": "z", "extent": [ 0, 1000 ], "step": 100, "unit": "Pa" }, "metered_levels": { "type": "spatial", "axis": "z", "values": [ 0, 10, 25, 50, 100, 1000 ], "unit": "m" }, "time": { "type": "temporal", "values": ["2016-05-03T13:21:30.040Z"] }, "spectral": { "type": "bands", "values": [ "red", "green", "blue" ] } } }, "assets": { "data": { "href": "http://cool-sat.com/catalog/datacube-123/data.nc", "type": "application/netcdf", "title": "netCDF Data cube" }, "thumbnail": { "href": "http://cool-sat.com/catalog/datacube-123/thumbnail.png", "type": "image/png", "title": "Thumbnail" } }, "links": [ { "rel": "self", "href": "http://cool-sat.com/catalog/datacube-123/example-item.json" } ] } stac-pydantic-3.4.0/tests/example_stac/example-item_geometry-null.json000066400000000000000000000146001503615555000262430ustar00rootroot00000000000000{ "type": "Feature", "stac_version": "1.0.0", "stac_extensions": [ "https://landsat.usgs.gov/stac/landsat-ard-extension/v1.0.0/schema.json", "https://stac-extensions.github.io/projection/v1.0.0/schema.json", "https://stac-extensions.github.io/eo/v1.0.0/schema.json", "https://stac-extensions.github.io/alternate-assets/v1.1.0/schema.json", "https://stac-extensions.github.io/storage/v1.0.0/schema.json" ], "id": "LE07_CU_002012_20150101_20210502_02_BA", "geometry": null, "properties": { "description": "Landsat Collection 2 Level-3 Burned Area Product", "datetime": "2015-01-01T18:39:12.4885358Z", "platform": "LANDSAT_7", "instruments": [ "ETM" ], "landsat:grid_horizontal": "02", "landsat:grid_vertical": "12", "landsat:grid_region": "CU", "landsat:scene_count": 1, "eo:cloud_cover": 0.0759, "landsat:cloud_shadow_cover": 0.1394, "landsat:snow_ice_cover": 0, "landsat:fill": 95.4286, "proj:epsg": null, "proj:shape": [ 5000, 5000 ], "proj:transform": [ 30, 0, -2265585, 0, -30, 1514805 ] }, "assets": { "index": { "title": "HTML index page", "type": "text/html", "roles": [ "metadata" ], "href": "https://landsatlook.usgs.gov/stac-browser/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02" }, "bp": { "title": "Burn Probability", "description": "Collection 2 Level-3 Albers Burn Probability Burned Area", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "roles": [ "data" ], "href": "https://landsatlook.usgs.gov/level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02_BP.TIF", "alternate": { "s3": { "storage:platform": "AWS", "storage:requester_pays": true, "href": "s3://usgs-landsat-level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02_BP.TIF" } } }, "bc": { "title": "Burn Classification", "description": "Collection 2 Level-3 Albers Burn Classification Burned Area", "type": "image/vnd.stac.geotiff; cloud-optimized=true", "roles": [ "data" ], "href": "https://landsatlook.usgs.gov/level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02_BC.TIF", "alternate": { "s3": { "storage:platform": "AWS", "storage:requester_pays": true, "href": "s3://usgs-landsat-level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02_BC.TIF" } } }, "quick_look": { "title": "Quick Look File", "description": "Collection 2 Level-3 Albers Quick Look File Burned Area", "type": "image/png", "roles": [ "data" ], "href": "https://landsatlook.usgs.gov/level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02_QuickLook.png", "alternate": { "s3": { "storage:platform": "AWS", "storage:requester_pays": true, "href": "s3://usgs-landsat-level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02_QuickLook.png" } } }, "xml": { "title": "Extensible Metadata File", "description": "Collection 2 Level-3 Albers Extensible Metadata File Burned Area", "type": "application/xml", "roles": [ "metadata" ], "href": "https://landsatlook.usgs.gov/level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02.xml", "alternate": { "s3": { "storage:platform": "AWS", "storage:requester_pays": true, "href": "s3://usgs-landsat-level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02.xml" } } }, "json": { "title": "Extensible Metadata File (json)", "description": "Collection 2 Level-3 Albers Extensible Metadata File (json) Burned Area", "type": "application/json", "roles": [ "metadata" ], "href": "https://landsatlook.usgs.gov/level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02.json", "alternate": { "s3": { "storage:platform": "AWS", "storage:requester_pays": true, "href": "s3://usgs-landsat-level-3/collection02/BA/2015/CU/002/012/LE07_CU_002012_20150101_20210502_02_BA/LE07_CU_002012_20150101_20210502_02.json" } } } }, "links": [ { "rel": "self", "href": "https://landsatlook.usgs.gov/stac-server/collections/landsat-c2l3-ba/items/LE07_CU_002012_20150101_20210502_02_BA" }, { "rel": "derived_from", "href": "https://landsatlook.usgs.gov/stac-server/collections/landsat-c2ard-sr/items/LE07_CU_002012_20150101_20210502_02_SR" }, { "rel": "derived_from", "href": "https://landsatlook.usgs.gov/stac-server/collections/landsat-c2ard-st/items/LE07_CU_002012_20150101_20210502_02_ST" }, { "rel": "derived_from", "href": "https://landsatlook.usgs.gov/stac-server/collections/landsat-c2ard-ta/items/LE07_CU_002012_20150101_20210502_02_TOA" }, { "rel": "derived_from", "href": "https://landsatlook.usgs.gov/stac-server/collections/landsat-c2ard-bt/items/LE07_CU_002012_20150101_20210502_02_BT" }, { "rel": "parent", "href": "https://landsatlook.usgs.gov/stac-server/collections/landsat-c2l3-ba" }, { "rel": "collection", "href": "https://landsatlook.usgs.gov/stac-server/collections/landsat-c2l3-ba" }, { "rel": "root", "href": "https://landsatlook.usgs.gov/stac-server/" } ], "collection": "landsat-c2l3-ba" } stac-pydantic-3.4.0/tests/example_stac/example-item_sci-extension.json000066400000000000000000000050771503615555000262400ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/scientific/v1.0.0/schema.json", "https://stac-extensions.github.io/file/v1.0.0/schema.json" ], "id": "MERRAclim.2_5m_min_80s", "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [-180,-90], [180,-90], [180,90], [-180,90], [-180,-90] ] ] }, "bbox": [-180,-90,180,90], "properties": { "title": "MERRAclim. 2_5m_min_80s", "description": "MERRAclim Dataset. 19 global bioclimatic variables from the 1980s decade at 2.5 arcminutes resolution in GEOtiff format. The humidity version used is the min. The variables have been built using the same protocol as WorldClim with data from MERRA. Temperature layers (BIO1-BIO11) are in degree Celsius multiplied by 10, humidity layers (BIO12-BIO19) are in kg of water/kg of air multiplied by 100000.", "datetime": "1985-07-01T00:00:00Z", "start_datetime": "1980-01-01T00:00:00Z", "end_datetime": "1989-12-31T23:59:59Z", "sci:doi": "10.5061/dryad.s2v81.2/27.2", "sci:publications": [ { "doi": "10.5061/dryad.s2v81.2", "citation": "Vega GC, Pertierra LR, Olalla-Tárraga MÁ (2017) Data from: MERRAclim, a high-resolution global dataset of remotely sensed bioclimatic variables for ecological modelling. Dryad Digital Repository." }, { "doi": "10.1038/sdata.2017.78", "citation": "Vega GC, Pertierra LR, Olalla-Tárraga MÁ (2017) MERRAclim, a high-resolution global dataset of remotely sensed bioclimatic variables for ecological modelling. Scientific Data 4: 170078." } ] }, "links": [ { "rel": "self", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/item.json" }, { "rel": "root", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/catalog.json" }, { "rel": "cite-as", "href": "https://doi.org/10.5061/dryad.s2v81.2/27.2" }, { "rel": "cite-as", "href": "https://doi.org/10.5061/dryad.s2v81.2" }, { "rel": "cite-as", "href": "https://doi.org/10.1038/sdata.2017.78" } ], "assets": { "primary": { "href": "https://datadryad.org/stash/downloads/file_stream/100467", "type": "application/zip", "title": "MERRAclim. 2_5m_min_80s", "checksum:multihash": "90e4021044a8995dd50b6657a037a7839304535b" } } } stac-pydantic-3.4.0/tests/example_stac/example-item_version-extension.json000066400000000000000000000035601503615555000271420ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/version/v1.0.0/schema.json" ], "id": "MERRAclim.2_5m_min_80s", "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [-180,-90], [180,-90], [180,90], [-180,90], [-180,-90] ] ] }, "bbox": [-180,-90,180,90], "properties": { "version": "1", "title": "MERRAclim. 2_5m_min_80s", "description": "MERRAclim Dataset. 19 global bioclimatic variables from the 1980s decade at 2.5 arcminutes resolution in GEOtiff format. The humidity version used is the min. The variables have been built using the same protocol as WorldClim with data from MERRA. Temperature layers (BIO1-BIO11) are in degree Celsius multiplied by 10, humidity layers (BIO12-BIO19) are in kg of water/kg of air multiplied by 100000.", "datetime": "1985-07-01T00:00:00Z" }, "collection": "merraclim", "links": [ { "rel": "self", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/v1/item.json" }, { "rel": "collection", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/v1/collection.json" }, { "rel": "parent", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/v1/collection.json" }, { "rel": "root", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/catalog.json" }, { "rel": "latest-version", "href": "https://datadryad.org/resource/doi:10.5061/dryad.s2v81/v2/item.json" } ], "assets": { "primary": { "href": "https://datadryad.org/stash/downloads/file_stream/100467", "type": "application/zip", "title": "MERRAclim. 2_5m_min_80s" } } } stac-pydantic-3.4.0/tests/example_stac/example-landsat8_eo-extension.json000066400000000000000000000217001503615555000266340ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/eo/v1.0.0/schema.json", "https://stac-extensions.github.io/view/v1.0.0/schema.json", "https://example.com/stac/landsat-extension/1.0/schema.json" ], "id": "LC08_L1TP_107018_20181001_20181001_01_RT", "collection": "landsat-8-l1", "type": "Feature", "bbox": [ 148.13933, 59.51584, 152.52758, 60.63437 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 152.52758, 60.63437 ], [ 149.1755, 61.19016 ], [ 148.13933, 59.51584 ], [ 151.33786, 58.97792 ], [ 152.52758, 60.63437 ] ] ] }, "properties": { "platform": "landsat-8", "instrument": ["oli", "tirs"], "datetime": "2018-10-01T01:08:32.033Z", "gsd": 30, "view:sun_azimuth": 168.8989761, "view:sun_elevation": 26.32596431, "view:off_nadir": 0, "landsat:path": 107, "landsat:row": 18, "eo:cloud_cover": 78 }, "assets": { "ANG": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_ANG.txt", "title": "Angle coefficients file", "type": "text/plain" }, "B1": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B1", "common_name": "coastal", "center_wavelength": 0.44, "full_width_half_max": 0.02 } ], "title": "Band 1 (coastal)" }, "B2": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B2", "common_name": "blue", "center_wavelength": 0.48, "full_width_half_max": 0.06 } ], "title": "Band 2 (blue)" }, "B3": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B3.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B3", "common_name": "green", "center_wavelength": 0.56, "full_width_half_max": 0.06 } ], "title": "Band 3 (green)" }, "B4": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B4.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B4", "common_name": "red", "center_wavelength": 0.65, "full_width_half_max": 0.04 } ], "title": "Band 4 (red)" }, "B5": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B5.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B5", "common_name": "nir", "center_wavelength": 0.86, "full_width_half_max": 0.03 } ], "title": "Band 5 (nir)" }, "B6": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B6.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B6", "common_name": "swir16", "center_wavelength": 1.6, "full_width_half_max": 0.08 } ], "title": "Band 6 (swir16)" }, "B7": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B7.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B7", "common_name": "swir22", "center_wavelength": 2.2, "full_width_half_max": 0.2 } ], "title": "Band 7 (swir22)" }, "B8": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B8", "common_name": "pan", "center_wavelength": 0.59, "full_width_half_max": 0.18 } ], "title": "Band 8 (pan)" }, "B9": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B9.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B9", "common_name": "cirrus", "center_wavelength": 1.37, "full_width_half_max": 0.02 } ], "title": "Band 9 (cirrus)" }, "B10": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B10.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B10", "common_name": "lwir11", "center_wavelength": 10.9, "full_width_half_max": 0.8 } ], "title": "Band 10 (lwir)" }, "B11": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B11.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B11", "common_name": "lwir12", "center_wavelength": 12, "full_width_half_max": 1 } ], "title": "Band 11 (lwir)" }, "BQA": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_BQA.TIF", "title": "Band quality data", "type": "image/tiff; application=geotiff" }, "MTL": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_MTL.txt", "title": "original metadata file", "type": "text/plain" }, "thumbnail": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", "title": "Thumbnail image", "type": "image/jpeg" }, "index": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", "type": "text/html", "title": "HTML index page" } }, "links": [ { "rel": "self", "href": "https://odu9mlf7d6.execute-api.us-east-1.amazonaws.com/stage/search?id=LC08_L1TP_107018_20181001_20181001_01_RT" }, { "rel": "parent", "href": "https://odu9mlf7d6.execute-api.us-east-1.amazonaws.com/stage/stac/collections/landsat-8-l1" }, { "rel": "root", "href": "https://odu9mlf7d6.execute-api.us-east-1.amazonaws.com/stage/stac" } ] } stac-pydantic-3.4.0/tests/example_stac/example-landsat8_item-assets-extension.json000066400000000000000000000173201503615555000304720ustar00rootroot00000000000000{ "id": "landsat-8-l1", "title": "Landsat 8 L1", "type": "Collection", "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", "keywords": [ "landsat", "earth observation", "usgs" ], "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/item-assets/v1.0.0/schema.json" ], "extent": { "spatial": { "bbox": [ [ -180, -90, 180, 90 ] ] }, "temporal": { "interval": [ [ "2013-06-01T00:00:00Z", null ] ] } }, "providers": [ { "name": "USGS", "roles": [ "producer" ], "url": "https://landsat.usgs.gov/" }, { "name": "Planet Labs", "roles": [ "processor" ], "url": "https://github.com/landsat-pds/landsat_ingestor" }, { "name": "AWS", "roles": [ "host" ], "url": "https://landsatonaws.com/" }, { "name": "Development Seed", "roles": [ "processor" ], "url": "https://github.com/sat-utils/sat-api" } ], "license": "PDDL-1.0", "summaries": { "gsd": [15], "platform": ["landsat-8"], "instruments": [ "oli", "tirs" ], "view:off_nadir": [0], "eo:bands": [ { "name": "B1", "common_name": "coastal", "center_wavelength": 0.44, "full_width_half_max": 0.02 }, { "name": "B2", "common_name": "blue", "center_wavelength": 0.48, "full_width_half_max": 0.06 }, { "name": "B3", "common_name": "green", "center_wavelength": 0.56, "full_width_half_max": 0.06 }, { "name": "B4", "common_name": "red", "center_wavelength": 0.65, "full_width_half_max": 0.04 }, { "name": "B5", "common_name": "nir", "center_wavelength": 0.86, "full_width_half_max": 0.03 }, { "name": "B6", "common_name": "swir16", "center_wavelength": 1.6, "full_width_half_max": 0.08 }, { "name": "B7", "common_name": "swir22", "center_wavelength": 2.2, "full_width_half_max": 0.2 }, { "name": "B8", "common_name": "pan", "center_wavelength": 0.59, "full_width_half_max": 0.18 }, { "name": "B9", "common_name": "cirrus", "center_wavelength": 1.37, "full_width_half_max": 0.02 }, { "name": "B10", "common_name": "lwir11", "center_wavelength": 10.9, "full_width_half_max": 0.8 }, { "name": "B11", "common_name": "lwir12", "center_wavelength": 12, "full_width_half_max": 1 } ] }, "item_assets": { "thumbnail": { "type": "image/jpeg", "title": "Thumbnail", "description": "A medium sized thumbnail", "roles": [ "thumbnail" ] }, "metadata": { "type": "mtl", "roles": [ "metadata" ], "title": "Original Metadata", "description": "The original MTL metadata file provided for each Landsat scene" }, "B1": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B1", "common_name": "coastal", "center_wavelength": 0.44, "full_width_half_max": 0.02 } ], "title": "Coastal Band (B1)", "description": "Coastal Band Top Of the Atmosphere" }, "B2": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B2", "common_name": "blue", "center_wavelength": 0.48, "full_width_half_max": 0.06 } ], "title": "Blue Band (B2)", "description": "Blue Band Top Of the Atmosphere" }, "B3": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B3", "common_name": "green", "center_wavelength": 0.56, "full_width_half_max": 0.06 } ], "title": "Green Band (B3)", "description": "Green Band (B3) Top Of the Atmosphere" }, "B4": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B4", "common_name": "red", "center_wavelength": 0.65, "full_width_half_max": 0.04 } ], "title": "Red Band (B4)", "description": "Red Band (B4) Top Of the Atmosphere" }, "B5": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B5", "common_name": "nir", "center_wavelength": 0.86, "full_width_half_max": 0.03 } ], "title": "NIR Band (B5)", "description": "NIR Band (B5) Top Of the Atmosphere" }, "B6": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B6", "common_name": "swir16", "center_wavelength": 1.6, "full_width_half_max": 0.08 } ], "title": "SWIR Band (B6)", "description": "SWIR Band at 1.6um (B6) Top Of the Atmosphere" }, "B7": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B7", "common_name": "swir22", "center_wavelength": 2.2, "full_width_half_max": 0.2 } ], "title": "SWIR Band (B7)", "description": "SWIR Band at 2.2um (B7) Top Of the Atmosphere" }, "B8": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B8", "common_name": "pan", "center_wavelength": 0.59, "full_width_half_max": 0.18 } ], "title": "Panchromatic Band (B8)", "description": "Panchromatic Band (B8) Top Of the Atmosphere" }, "B9": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B9", "common_name": "cirrus", "center_wavelength": 1.37, "full_width_half_max": 0.02 } ], "title": "Cirrus Band (B9)", "description": "Cirrus Band (B9) Top Of the Atmosphere - for cirrus cloud detection" }, "B10": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B10", "common_name": "lwir11", "center_wavelength": 10.9, "full_width_half_max": 0.8 } ], "title": "LWIR Band (B10)", "description": "Long-wave IR Band at 11um (B10) Top Of the Atmosphere" }, "B11": { "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B11", "common_name": "lwir12", "center_wavelength": 12, "full_width_half_max": 1 } ], "title": "LWIR Band (B11)", "description": "Long-wave IR Band at 12um (B11) Top Of the Atmosphere" } }, "links": [] } stac-pydantic-3.4.0/tests/example_stac/example-landsat8_projection-extension.json000066400000000000000000000155631503615555000304170ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/eo/v1.0.0/schema.json", "https://stac-extensions.github.io/projection/v1.0.0/schema.json" ], "id": "LC81530252014153LGN00", "type": "Feature", "collection": "landsat-8-l1", "bbox": [ 148.13933, 59.51584, 152.52758, 60.63437 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 152.52758, 60.63437 ], [ 149.1755, 61.19016 ], [ 148.13933, 59.51584 ], [ 151.33786, 58.97792 ], [ 152.52758, 60.63437 ] ] ] }, "assets": { "B1": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B1.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B1", "common_name": "coastal", "center_wavelength": 0.44, "full_width_half_max": 0.02 } ], "title": "Band 1 (coastal)" }, "B8": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B8.TIF", "type": "image/tiff; application=geotiff", "eo:bands": [ { "name": "B8", "common_name": "panchromatic", "center_wavelength": 0.59, "full_width_half_max": 0.18 } ], "title": "Band 8 (panchromatic)", "proj:shape": [16781, 16621], "proj:transform": [15.0, 0.0, 224992.5, 0.0, -15.0, 6790207.5, 0.0, 0.0, 1.0] } }, "properties": { "datetime": "2018-10-01T01:08:32.033Z", "proj:epsg": 32614, "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 14N\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-99],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"32614\"],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", "proj:projjson": { "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", "type": "ProjectedCRS", "name": "WGS 84 / UTM zone 14N", "base_crs": { "name": "WGS 84", "datum": { "type": "GeodeticReferenceFrame", "name": "World Geodetic System 1984", "ellipsoid": { "name": "WGS 84", "semi_major_axis": 6378137, "inverse_flattening": 298.257223563 } }, "coordinate_system": { "subtype": "ellipsoidal", "axis": [ { "name": "Geodetic latitude", "abbreviation": "Lat", "direction": "north", "unit": "degree" }, { "name": "Geodetic longitude", "abbreviation": "Lon", "direction": "east", "unit": "degree" } ] }, "id": { "authority": "EPSG", "code": 4326 } }, "conversion": { "name": "UTM zone 14N", "method": { "name": "Transverse Mercator", "id": { "authority": "EPSG", "code": 9807 } }, "parameters": [ { "name": "Latitude of natural origin", "value": 0, "unit": "degree", "id": { "authority": "EPSG", "code": 8801 } }, { "name": "Longitude of natural origin", "value": -99, "unit": "degree", "id": { "authority": "EPSG", "code": 8802 } }, { "name": "Scale factor at natural origin", "value": 0.9996, "unit": "unity", "id": { "authority": "EPSG", "code": 8805 } }, { "name": "False easting", "value": 500000, "unit": "metre", "id": { "authority": "EPSG", "code": 8806 } }, { "name": "False northing", "value": 0, "unit": "metre", "id": { "authority": "EPSG", "code": 8807 } } ] }, "coordinate_system": { "subtype": "Cartesian", "axis": [ { "name": "Easting", "abbreviation": "E", "direction": "east", "unit": "metre" }, { "name": "Northing", "abbreviation": "N", "direction": "north", "unit": "metre" } ] }, "area": "World - N hemisphere - 102°W to 96°W - by country", "bbox": { "south_latitude": 0, "west_longitude": -102, "north_latitude": 84, "east_longitude": -96 }, "id": { "authority": "EPSG", "code": 32614 } }, "proj:geometry": { "coordinates": [ [ [ 169200.0, 3712800.0 ], [ 403200.0, 3712800.0 ], [ 403200.0, 3951000.0 ], [ 169200.0, 3951000.0 ], [ 169200.0, 3712800.0 ] ] ], "type": "Polygon" }, "proj:bbox": [ 169200.0, 3712800.0, 403200.0, 3951000.0 ], "proj:centroid": { "lat": 34.595302781575604, "lon": -101.34448382627504 }, "proj:shape": [8391, 8311], "proj:transform": [30.0, 0.0, 224985.0, 0.0, -30.0, 6790215.0, 0.0, 0.0, 1.0] }, "links": [ { "rel": "self", "href": "https://odu9mlf7d6.execute-api.us-east-1.amazonaws.com/stage/search?id=LC08_L1TP_107018_20181001_20181001_01_RT" } ] } stac-pydantic-3.4.0/tests/example_stac/example-landsat8_sat-extension.json000066400000000000000000000054551503615555000270310ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/sat/v1.0.0/schema.json", "https://stac-extensions.github.io/view/v1.0.0/schema.json" ], "id": "LC08_L1TP_107018_20181001", "collection": "landsat-8-l1", "type": "Feature", "bbox": [ 148.13933, 59.51584, 152.52758, 60.63437 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 152.52758, 60.63437 ], [ 149.1755, 61.19016 ], [ 148.13933, 59.51584 ], [ 151.33786, 58.97792 ], [ 152.52758, 60.63437 ] ] ] }, "properties": { "datetime": "2018-10-01T01:08:32.033Z", "view:sun_azimuth": 168.8989761, "view:sun_elevation": 26.32596431, "platform": "landsat-8", "instruments": ["oli", "tirs"], "constellation": "landsat", "sat:orbit_state": "ascending", "view:off_nadir": 0 }, "assets": { "blue": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", "type": "image/tiff; application=geotiff", "title": "Band 2 (blue)" }, "green": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B3.TIF", "type": "image/tiff; application=geotiff", "title": "Band 3 (green)" }, "red": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B4.TIF", "type": "image/tiff; application=geotiff", "title": "Band 4 (red)" }, "thumbnail": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", "title": "Thumbnail image", "type": "image/jpeg" }, "index": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", "type": "text/html", "title": "HTML index page" } }, "links": [ { "rel": "collection", "href": "http://landsat-stac.s3.amazonaws.com/landsat-8-l1/catalog.json" } ] } stac-pydantic-3.4.0/tests/example_stac/example-landsat8_view-extension.json000066400000000000000000000055561503615555000272160ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/sat/v1.0.0/schema.json", "https://stac-extensions.github.io/view/v1.0.0/schema.json" ], "id": "LC08_L1TP_107018_20181001", "collection": "landsat-8-l1", "type": "Feature", "bbox": [ 148.13933, 59.51584, 152.52758, 60.63437 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ 152.52758, 60.63437 ], [ 149.1755, 61.19016 ], [ 148.13933, 59.51584 ], [ 151.33786, 58.97792 ], [ 152.52758, 60.63437 ] ] ] }, "properties": { "datetime": "2018-10-01T01:08:32.033Z", "view:sun_azimuth": 168.8989761, "view:sun_elevation": 26.32596431, "view:off_nadir": 0, "view:incidence_angle": 0, "view:azimuth": 23.4, "platform": "landsat-8", "instruments": ["oli", "tirs"], "constellation": "landsat", "sat:orbit_state": "ascending" }, "assets": { "blue": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B2.TIF", "type": "image/tiff; application=geotiff", "title": "Band 2 (blue)" }, "green": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B3.TIF", "type": "image/tiff; application=geotiff", "title": "Band 3 (green)" }, "red": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_B4.TIF", "type": "image/tiff; application=geotiff", "title": "Band 4 (red)" }, "thumbnail": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/LC08_L1TP_107018_20181001_20181001_01_RT_thumb_large.jpg", "title": "Thumbnail image", "type": "image/jpeg" }, "index": { "href": "https://landsat-pds.s3.amazonaws.com/c1/L8/107/018/LC08_L1TP_107018_20181001_20181001_01_RT/index.html", "type": "text/html", "title": "HTML index page" } }, "links": [ { "rel": "collection", "href": "http://landsat-stac.s3.amazonaws.com/landsat-8-l1/catalog.json" } ] } stac-pydantic-3.4.0/tests/example_stac/example-search.json000066400000000000000000000472331503615555000236770ustar00rootroot00000000000000{ "id": "mysearchresults", "stac_version": "1.0.0", "stac_extensions": [], "description": "A bunch of results from a search", "type": "FeatureCollection", "features": [ { "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/projection/v1.0.0/schema.json", "https://stac-extensions.github.io/view/v1.0.0/schema.json" ], "type": "Feature", "id": "LC80370332018039LGN00", "collection": "landsat-8-l1", "bbox": [ -112.21054, 37.83042, -109.4992, 39.95532 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ -111.6768167850251, 39.952817693022276 ], [ -109.5010938553632, 39.55607811527241 ], [ -110.03573868784865, 37.83172334507642 ], [ -112.20846353249907, 38.236456540046845 ], [ -111.6768167850251, 39.952817693022276 ] ] ] }, "properties": { "datetime": "2018-02-08T18:02:15.719478+00:00", "view:sun_azimuth": 152.63804142, "view:sun_elevation": 31.82216637, "landsat:product_id": "LC08_L1TP_037033_20180208_20180221_01_T1", "landsat:scene_id": "LC80370332018039LGN00", "landsat:processing_level": "L1TP", "landsat:tier": "T1", "proj:epsg": 32612 }, "assets": { "index": { "type": "text/html", "title": "HTML index page", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/index.html" }, "thumbnail": { "title": "Thumbnail image", "type": "image/jpeg", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_thumb_large.jpg" }, "B1": { "type": "image/tiff; application=geotiff", "title": "Band 1 (coastal)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B1.TIF" }, "B2": { "type": "image/tiff; application=geotiff", "title": "Band 2 (blue)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B2.TIF" }, "B3": { "type": "image/tiff; application=geotiff", "title": "Band 3 (green)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B3.TIF" }, "B4": { "type": "image/tiff; application=geotiff", "title": "Band 4 (red)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B4.TIF" }, "B5": { "type": "image/tiff; application=geotiff", "title": "Band 5 (nir)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B5.TIF" }, "B6": { "type": "image/tiff; application=geotiff", "title": "Band 6 (swir16)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B6.TIF" }, "B7": { "type": "image/tiff; application=geotiff", "title": "Band 7 (swir22)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B7.TIF" }, "B8": { "type": "image/tiff; application=geotiff", "title": "Band 8 (pan)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B8.TIF" }, "B9": { "type": "image/tiff; application=geotiff", "title": "Band 9 (cirrus)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B9.TIF" }, "B10": { "type": "image/tiff; application=geotiff", "title": "Band 10 (lwir)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B10.TIF" }, "B11": { "type": "image/tiff; application=geotiff", "title": "Band 11 (lwir)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_B11.TIF" }, "ANG": { "title": "Angle coefficients file", "type": "text/plain", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_ANG.txt" }, "MTL": { "title": "original metadata file", "type": "text/plain", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_MTL.txt" }, "BQA": { "title": "Band quality data", "type": "image/tiff; application=geotiff", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/037/033/LC08_L1TP_037033_20180208_20180221_01_T1/LC08_L1TP_037033_20180208_20180221_01_T1_BQA.TIF" } }, "links": [] }, { "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/projection/v1.0.0/schema.json", "https://stac-extensions.github.io/view/v1.0.0/schema.json" ], "type": "Feature", "id": "LC80340332018034LGN00", "collection": "landsat-8-l1", "bbox": [ -107.6044, 37.8096, -104.86884, 39.97508 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ -107.03912158283073, 39.975078807631036 ], [ -104.87161559271382, 39.548160703908025 ], [ -105.43927721248009, 37.81075859503169 ], [ -107.60423259994965, 38.24485405534073 ], [ -107.03912158283073, 39.975078807631036 ] ] ] }, "properties": { "datetime": "2018-02-03T17:43:44Z", "view:sun_azimuth": 153.39513457, "view:sun_elevation": 30.41894816, "landsat:product_id": "LC08_L1TP_034033_20180203_20180220_01_T1", "landsat:scene_id": "LC80340332018034LGN00", "landsat:processing_level": "L1TP", "landsat:tier": "T1", "proj:epsg": 32613 }, "assets": { "index": { "type": "text/html", "title": "HTML index page", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/index.html" }, "thumbnail": { "title": "Thumbnail image", "type": "image/jpeg", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_thumb_large.jpg" }, "B1": { "type": "image/tiff; application=geotiff", "title": "Band 1 (coastal)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B1.TIF" }, "B2": { "type": "image/tiff; application=geotiff", "title": "Band 2 (blue)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B2.TIF" }, "B3": { "type": "image/tiff; application=geotiff", "title": "Band 3 (green)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B3.TIF" }, "B4": { "type": "image/tiff; application=geotiff", "title": "Band 4 (red)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B4.TIF" }, "B5": { "type": "image/tiff; application=geotiff", "title": "Band 5 (nir)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B5.TIF" }, "B6": { "type": "image/tiff; application=geotiff", "title": "Band 6 (swir16)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B6.TIF" }, "B7": { "type": "image/tiff; application=geotiff", "title": "Band 7 (swir22)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B7.TIF" }, "B8": { "type": "image/tiff; application=geotiff", "title": "Band 8 (pan)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B8.TIF" }, "B9": { "type": "image/tiff; application=geotiff", "title": "Band 9 (cirrus)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B9.TIF" }, "B10": { "type": "image/tiff; application=geotiff", "title": "Band 10 (lwir)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B10.TIF" }, "B11": { "type": "image/tiff; application=geotiff", "title": "Band 11 (lwir)", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_B11.TIF" }, "ANG": { "title": "Angle coefficients file", "type": "text/plain", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_ANG.txt" }, "MTL": { "title": "original metadata file", "type": "text/plain", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_MTL.txt" }, "BQA": { "title": "Band quality data", "type": "image/tiff; application=geotiff", "href": "https://s3-us-west-2.amazonaws.com/landsat-pds/c1/L8/034/033/LC08_L1TP_034033_20180203_20180220_01_T1/LC08_L1TP_034033_20180203_20180220_01_T1_BQA.TIF" } }, "links": [] } ], "collections": [ { "id": "landsat-8-l1", "title": "Landsat 8 L1", "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", "keywords": [ "landsat", "earth observation", "usgs" ], "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/item-assets/v1.0.0/schema.json" ], "extent": { "spatial": { "bbox": [ [ -180, -90, 180, 90 ] ] }, "temporal": { "interval": [ [ "2013-06-01T00:00:00Z", null ] ] } }, "providers": [ { "name": "USGS", "roles": [ "producer" ], "url": "https://landsat.usgs.gov/" }, { "name": "Planet Labs", "roles": [ "processor" ], "url": "https://github.com/landsat-pds/landsat_ingestor" }, { "name": "AWS", "roles": [ "host" ], "url": "https://landsatonaws.com/" }, { "name": "Development Seed", "roles": [ "processor" ], "url": "https://github.com/sat-utils/sat-api" } ], "license": "PDDL-1.0", "summaries": { "gsd": [15], "platform": ["landsat-8"], "instruments": ["oli", "tirs"], "view:off_nadir": [0] }, "item_assets": { "index": { "type": "text/html", "title": "HTML index page" }, "thumbnail": { "title": "Thumbnail image", "type": "image/jpeg" }, "B1": { "type": "image/tiff; application=geotiff", "title": "Band 1 (coastal)" }, "B2": { "type": "image/tiff; application=geotiff", "title": "Band 2 (blue)" }, "B3": { "type": "image/tiff; application=geotiff", "title": "Band 3 (green)" }, "B4": { "type": "image/tiff; application=geotiff", "title": "Band 4 (red)" }, "B5": { "type": "image/tiff; application=geotiff", "title": "Band 5 (nir)" }, "B6": { "type": "image/tiff; application=geotiff", "title": "Band 6 (swir16)" }, "B7": { "type": "image/tiff; application=geotiff", "title": "Band 7 (swir22)" }, "B8": { "type": "image/tiff; application=geotiff", "title": "Band 8 (pan)" }, "B9": { "type": "image/tiff; application=geotiff", "title": "Band 9 (cirrus)" }, "B10": { "type": "image/tiff; application=geotiff", "title": "Band 10 (lwir)" }, "B11": { "type": "image/tiff; application=geotiff", "title": "Band 11 (lwir)" }, "ANG": { "title": "Angle coefficients file", "type": "text/plain" }, "MTL": { "title": "original metadata file", "type": "text/plain" }, "BQA": { "title": "Band quality data", "type": "image/tiff; application=geotiff" } }, "links": [ { "rel": "self", "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/landsat-8-l1" }, { "rel": "parent", "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod" }, { "rel": "root", "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod" }, { "rel": "items", "href": "https://n34f767n91.execute-api.us-east-1.amazonaws.com/prod/collections/landsat-8-l1/items" } ] } ], "links": [] } stac-pydantic-3.4.0/tests/example_stac/itemcollection-sample-full.json000066400000000000000000000046501503615555000262260ustar00rootroot00000000000000{ "type": "FeatureCollection", "features": [ { "stac_version": "1.0.0", "stac_extensions": [], "type": "Feature", "id": "CS3-20160503_132131_05", "bbox": [ -122.59750209, 37.48803556, -122.2880486, 37.613537207 ], "geometry": { "type": "Polygon", "coordinates": [ [ [ -122.308150179, 37.488035566 ], [ -122.597502109, 37.538869539 ], [ -122.576687533, 37.613537207 ], [ -122.288048600, 37.562818007 ], [ -122.308150179, 37.488035566 ] ] ] }, "properties": { "datetime": "2016-05-03T13:22:30.040Z", "title": "A CS3 item", "license": "PDDL-1.0", "providers": [ { "name": "CoolSat", "roles": [ "producer", "licensor" ], "url": "https://cool-sat.com/" } ] }, "collection": "CS3", "links": [ { "rel": "self", "href": "http://cool-sat.com/catalog/collections/CS3-20160503_132130_04/items/CS3-20160503_132130_04.json" }, { "rel": "collection", "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/catalog.json" }, { "rel": "root", "href": "http://cool-sat.com/catalog/" } ], "assets": { "analytic": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/analytic.tif", "title": "4-Band Analytic" }, "thumbnail": { "href": "http://cool-sat.com/catalog/CS3-20160503_132130_04/thumbnail.png", "title": "Thumbnail", "roles": [ "thumbnail" ] } } } ], "links": [ { "rel": "self", "href": "http://stac.example.com/search?collection=CS3", "type": "application/geo+json" }, { "rel": "root", "href": "http://stac.example.com/", "type": "application/json" } ] } stac-pydantic-3.4.0/tests/example_stac/landsat-collection.json000066400000000000000000000073471503615555000245620ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [], "id": "landsat-8-l1", "title": "Landsat 8 L1", "type": "Collection", "description": "Landat 8 imagery radiometrically calibrated and orthorectified using gound points and Digital Elevation Model (DEM) data to correct relief displacement.", "keywords": [ "landsat" ], "extent": { "spatial": { "bbox": [ [ -180, -90, 180, 90 ] ] }, "temporal": { "interval": [ [ "2013-06-01T00:00:00Z", null ] ] } }, "providers": [ { "name": "USGS", "url": "https://landsat.usgs.gov/", "roles": [ "producer", "licensor" ] }, { "name": "Planet Labs", "url": "https://github.com/landsat-pds/landsat_ingestor", "roles": [ "processor" ] }, { "name": "AWS", "url": "https://landsatonaws.com/", "roles": [ "host" ] }, { "name": "Development Seed", "url": "https://developmentseed.org/", "roles": ["processor"] } ], "license": "PDDL-1.0", "assets": { "thumbnail": { "href": "thumbnail.png", "title": "Preview", "type": "image/png", "roles": [ "thumbnail" ] } }, "summaries": { "platform": ["landsat-8"], "instruments": ["oli", "tirs"], "gsd": [30], "eo:bands": [ { "name": "B1", "common_name": "coastal", "center_wavelength": 0.44, "full_width_half_max": 0.02 }, { "name": "B2", "common_name": "blue", "center_wavelength": 0.48, "full_width_half_max": 0.06 }, { "name": "B3", "common_name": "green", "center_wavelength": 0.56, "full_width_half_max": 0.06 }, { "name": "B4", "common_name": "red", "center_wavelength": 0.65, "full_width_half_max": 0.04 }, { "name": "B5", "common_name": "nir", "center_wavelength": 0.86, "full_width_half_max": 0.03 }, { "name": "B6", "common_name": "swir16", "center_wavelength": 1.6, "full_width_half_max": 0.08 }, { "name": "B7", "common_name": "swir22", "center_wavelength": 2.2, "full_width_half_max": 0.2 }, { "name": "B8", "common_name": "pan", "center_wavelength": 0.59, "full_width_half_max": 0.18 }, { "name": "B9", "common_name": "cirrus", "center_wavelength": 1.37, "full_width_half_max": 0.02 }, { "name": "B10", "common_name": "lwir11", "center_wavelength": 10.9, "full_width_half_max": 0.8 }, { "name": "B11", "common_name": "lwir12", "center_wavelength": 12, "full_width_half_max": 1 } ] }, "links": [ { "rel": "self", "href": "https://landsat-stac.s3.amazonaws.com/landsat-8-l1/catalog.json" }, { "rel": "parent", "href": "https://landsat-stac.s3.amazonaws.com/catalog.json" }, { "rel": "root", "href": "https://landsat-stac.s3.amazonaws.com/catalog.json" }, { "rel": "child", "href": "https://landsat-stac.s3.amazonaws.com/landsat-8-l1/paths/catalog.json" } ] } stac-pydantic-3.4.0/tests/example_stac/roads_item.json000066400000000000000000000070211503615555000231160ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/label/v1.0.0/schema.json", "https://stac-extensions.github.io/version/v1.0.0/schema.json" ], "id": "AOI_3_Paris_img101", "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [ 2.23379639995, 49.0178709 ], [ 2.23730639995, 49.0178709 ], [ 2.23730639995, 49.0213809 ], [ 2.23379639995, 49.0213809 ] ] ] }, "bbox": [ 2.23379639995, 49.0178709, 2.23730639995, 49.0213809 ], "properties": { "datetime": "2016-08-26T22:41:55.000000Z", "title": "AOI 3 Paris img101 road network labels", "label:properties": [ "road_type", "lane_number", "paved" ], "label:type": "vector", "label:description": "sample SpaceNet roads label", "label:classes": [ { "name": "road_type", "classes": [ "1", "2", "3", "4", "5", "6" ] }, { "name": "lane_number", "classes": [ "1", "2", "3", "4", "5" ] }, { "name": "paved", "classes": [ "0", "1" ] } ], "label:tasks": [ "segmentation" ], "label:methods": [ "manual" ], "version": "1", "label:overviews": [ { "property_key": "road_type", "counts": [ { "name": "1", "count": 0 }, { "name": "2", "count": 0 }, { "name": "3", "count": 0 }, { "name": "4", "count": 0 }, { "name": "5", "count": 15 }, { "name": "6", "count": 4 } ] }, { "property_key": "lane_number", "statistics": [ { "name": "mean", "value": 1.8 }, { "name": "max", "value": 2 } ] }, { "property_key": "paved", "counts": [ { "name": "1", "count": 19 } ] } ] }, "links": [ { "href": "roads_collection.json", "rel": "root", "title": "sample SpaceNet roads label collection" }, { "href": "roads_collection.json", "rel": "parent", "title": "sample SpaceNet roads label collection" }, { "href": "roads_collection.json", "rel": "collection", "title": "sample SpaceNet roads label collection" }, { "href": "roads_source.json", "rel": "source", "title": "The source imagery these road labels were derived from", "label:assets": "road_labels" } ], "assets": { "road_labels": { "href": "spacenetroads_AOI_3_Paris_img101.geojson", "title": "AOI_3_Paris_img101_roads", "type": "application/geo+json" } } } stac-pydantic-3.4.0/tests/example_stac/sentinel1_sar-extension.json000066400000000000000000000062461503615555000255610ustar00rootroot00000000000000{ "stac_version": "1.0.0", "stac_extensions": [ "https://stac-extensions.github.io/file/v1.0.0/schema.json", "https://stac-extensions.github.io/sar/v1.0.0/schema.json", "https://stac-extensions.github.io/sat/v1.0.0/schema.json" ], "id": "S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616", "type": "Feature", "bbox": [-70.275032,-64.72924,-65.087479,-51.105831], "geometry": { "type": "Polygon", "coordinates": [ [ [-67.071648,-64.72924], [-65.087479,-56.674374], [-68.033211,-51.105831], [-70.275032,-59.805672], [-67.071648,-64.72924] ] ] }, "properties": { "datetime": "2018-11-03T23:58:55.617217Z", "start_datetime": "2018-11-03T23:58:55.121559Z", "end_datetime": "2018-11-03T23:59:55.112875Z", "platform": "sentinel-1a", "constellation": "sentinel-1", "instruments": ["c-sar"], "sat:orbit_state": "ascending", "sat:relative_orbit": 33, "sar:instrument_mode": "EW", "sar:polarizations": ["HH"], "sar:resolution_range": 50, "sar:resolution_azimuth": 50, "sar:pixel_spacing_range": 25, "sar:pixel_spacing_azimuth": 25, "sar:looks_range": 3, "sar:looks_azimuth": 1, "sar:looks_equivalent_number": 2.7, "sar:frequency_band": "C", "sar:center_frequency": 5.405, "sar:product_type": "GRD" }, "assets": { "noises": { "href": "./annotation/calibration/noise-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml", "title": "Calibration Schema", "type": "text/xml", "checksum:multihash": "90e40210a30d1711e81a4b11ef67b28744321659" }, "calibrations": { "href": "./annotation/calibration/calibration-s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml", "title": "Noise Schema", "type": "text/xml", "checksum:multihash": "90e402104fc5351af67db0b8f1746efe421a05e4" }, "products": { "href": "./annotation/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.xml", "title": "Product Schema", "type": "text/xml", "checksum:multihash": "90e402107a7f2588a85362b9beea2a12d4514d45" }, "measurement": { "href": "./measurement/s1a-ew-grd-hh-20181103t235855-20181103t235955-024430-02ad5d-001.tiff", "title": "Measurements", "type": "image/tiff", "sar:polarizations": ["HH"], "checksum:multihash": "90e40210163700a8a6501eccd00b6d3b44ddaed0" }, "thumbnail": { "href": "./preview/quick-look.png", "title": "Thumbnail", "type": "image/png", "checksum:multihash": "90e40210f52acd32b09769d3b1871b420789456c" } }, "links": [ { "rel": "self", "href": "https://example.com/collections/sentinel-1/items/S1A_EW_GRDM_1SSH_20181103T235855_20181103T235955_024430_02AD5D_5616" }, { "rel": "parent", "href": "https://example.com/collections/sentinel-1" }, { "rel": "root", "href": "https://example.com/collections" } ] } stac-pydantic-3.4.0/tests/test_cli.py000066400000000000000000000005751503615555000176170ustar00rootroot00000000000000from stac_pydantic.scripts.cli import app def test_valid_stac_item(cli_runner): result = cli_runner.invoke( app, [ "validate-item", "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/extended-item.json", ], ) if result.exception: raise result.exception assert result.exit_code == 0 stac-pydantic-3.4.0/tests/test_item_collection.py000066400000000000000000000015421503615555000222140ustar00rootroot00000000000000import json from stac_pydantic import ItemCollection from stac_pydantic.version import STAC_VERSION from .conftest import dict_match, request ITEM_COLLECTION = "itemcollection-sample-full.json" PATH = ["tests", "example_stac"] def test_item_collection(): test_item_coll = request(ITEM_COLLECTION, PATH) for feat in test_item_coll["features"]: feat["stac_version"] = STAC_VERSION valid_item_coll = ItemCollection(**test_item_coll).model_dump() for idx, feat in enumerate(test_item_coll["features"]): dict_match(feat, valid_item_coll["features"][idx]) def test_to_json(): test_item = request(ITEM_COLLECTION, PATH) for feat in test_item["features"]: feat["stac_version"] = STAC_VERSION validated = ItemCollection(**test_item) dict_match(json.loads(validated.model_dump_json()), validated.model_dump()) stac-pydantic-3.4.0/tests/test_models.py000066400000000000000000000346601503615555000203350ustar00rootroot00000000000000import json from typing import Literal import pytest from pydantic import ConfigDict, ValidationError from shapely.geometry import shape from stac_pydantic import Collection, Item, ItemProperties from stac_pydantic.collection import SpatialExtent, TimeInterval from stac_pydantic.extensions import _fetch_and_cache_schema, validate_extensions from stac_pydantic.links import Link, Links from stac_pydantic.shared import MimeTypes, StacCommonMetadata from .conftest import dict_match, request COLLECTION = "landsat-collection.json" ITEM_COLLECTION = "itemcollection-sample-full.json" SINGLE_FILE_STAC = "example-search.json" # ASSET_EXTENSION = f"https://raw.githubusercontent.com/radiantearth/stac-spec/v{STAC_VERSION}/extensions # /asset/examples/example-landsat8.json" # COLLECTION_ASSET_EXTENSION = f"https://raw.githubusercontent.com/radiantearth/stac-spec/v{STAC_VERSION} # /extensions/collection-assets/examples/example-esm.json" DATACUBE_EXTENSION = "example-item_datacube-extension.json" EO_EXTENSION = "example-landsat8_eo-extension.json" ITEM_ASSET_EXTENSION = "example-landsat8_item-assets-extension.json" LABEL_EXTENSION = "roads_item.json" POINTCLOUD_EXTENSION = "example-autzen.json" PROJ_EXTENSION = "example-landsat8_projection-extension.json" SAR_EXTENSION = "sentinel1_sar-extension.json" SAT_EXTENSION = "example-landsat8_sat-extension.json" SCIENTIFIC_EXTENSION = "example-item_sci-extension.json" VERSION_EXTENSION_ITEM = "example-item_version-extension.json" VERSION_EXTENSION_COLLECTION = "example-collection_version-extension.json" VIEW_EXTENSION = "example-landsat8_view-extension.json" DATETIME_RANGE = "datetimerange.json" ITEM_GEOMETRY_NULL = "example-item_geometry-null.json" @pytest.mark.parametrize( "infile", [ EO_EXTENSION, POINTCLOUD_EXTENSION, SAT_EXTENSION, VIEW_EXTENSION, SCIENTIFIC_EXTENSION, DATACUBE_EXTENSION, DATETIME_RANGE, ], ) def test_item_extensions(infile): test_item = request(infile) valid_item = Item(**test_item).model_dump() dict_match(test_item, valid_item) def test_sar_extensions() -> None: test_item = request(SAR_EXTENSION) valid_item = Item(**test_item).model_dump() dict_match(test_item, valid_item) def test_proj_extension() -> None: # The example item uses an invalid band name test_item = request(PROJ_EXTENSION) test_item["stac_extensions"][1] = ( "https://raw.githubusercontent.com/stac-extensions/projection/v1.0.0/json-schema/schema.json" ) test_item["assets"]["B8"]["eo:bands"][0]["common_name"] = "pan" valid_item = Item.model_validate(test_item).model_dump() dict_match(test_item, valid_item) def test_version_extension_item() -> None: test_item = request(VERSION_EXTENSION_ITEM) valid_item = Item(**test_item).model_dump() dict_match(test_item, valid_item) def test_version_extension_collection() -> None: test_coll = request(VERSION_EXTENSION_COLLECTION) valid_coll = Collection(**test_coll).model_dump(mode="json") dict_match(test_coll, valid_coll) def test_item_assets_extension() -> None: test_coll = request(ITEM_ASSET_EXTENSION) valid_coll = Collection(**test_coll).model_dump(mode="json") dict_match(test_coll, valid_coll) def test_label_extension() -> None: test_item = request(LABEL_EXTENSION) # This example contains an invalid geometry (linear ring does not close) coords = test_item["geometry"]["coordinates"] assert len(coords[0]) == 4 coords[0].append(coords[0][0]) test_item["geometry"]["coordinates"] = coords assert validate_extensions(test_item) valid_item = Item(**test_item).model_dump() dict_match(test_item, valid_item) def test_explicit_extension_validation() -> None: test_item = request(EO_EXTENSION) # This item implements the eo and view extensions assert test_item["stac_extensions"][:-1] == [ "https://stac-extensions.github.io/eo/v1.0.0/schema.json", "https://stac-extensions.github.io/view/v1.0.0/schema.json", ] validate_extensions(test_item) def test_extension_validation_schema_cache() -> None: # Defines 3 extensions, but one is a non-existing URL test_item = request(EO_EXTENSION) _fetch_and_cache_schema.cache_clear() assert not validate_extensions(test_item) assert _fetch_and_cache_schema.cache_info().hits == 0 assert _fetch_and_cache_schema.cache_info().misses == 3 assert not validate_extensions(test_item) assert _fetch_and_cache_schema.cache_info().hits == 2 # The non-existing URL will have failed, hence retried assert _fetch_and_cache_schema.cache_info().misses == 4 @pytest.mark.parametrize( "infile,model", [(EO_EXTENSION, Item), (COLLECTION, Collection)], ) def test_to_json(infile, model): test_item = request(infile) validated = model(**test_item) dict_match( json.loads(validated.model_dump_json()), validated.model_dump(mode="json") ) def test_item_to_json() -> None: test_item = request(EO_EXTENSION) item = Item(**test_item) dict_match(json.loads(item.model_dump_json()), item.model_dump()) def test_invalid_geometry() -> None: test_item = request(EO_EXTENSION) # Remove the last coordinate test_item["geometry"]["coordinates"][0].pop(-1) with pytest.raises(ValidationError): Item(**test_item) def test_asset_extras() -> None: test_item = request(EO_EXTENSION) for asset in test_item["assets"]: test_item["assets"][asset]["foo"] = "bar" item = Item(**test_item) for _, asset in item.assets.items(): assert asset.foo == "bar" def test_geo_interface() -> None: test_item = request(EO_EXTENSION) item = Item(**test_item) geom = shape(item.geometry) test_item["geometry"] = geom Item(**test_item) @pytest.mark.parametrize( "args", [ {"datetime": "2024-01-01T00:00:00Z"}, { "datetime": None, "start_datetime": "2024-01-01T00:00:00Z", "end_datetime": "2024-01-02T00:00:00Z", }, { "datetime": "2024-01-01T00:00:00Z", "start_datetime": "2024-01-01T00:00:00Z", "end_datetime": "2024-01-02T00:00:00Z", }, ], ) def test_stac_common_dates(args) -> None: StacCommonMetadata(**args) def test_stac_null_datetime_required() -> None: with pytest.raises(ValidationError): StacCommonMetadata( **{ "start_datetime": "2024-01-01T00:00:00Z", "end_datetime": "2024-01-02T00:00:00Z", } ) @pytest.mark.parametrize( "args", [ {"datetime": None}, {"datetime": None, "start_datetime": "2024-01-01T00:00:00Z"}, {"datetime": None, "end_datetime": "2024-01-01T00:00:00Z"}, ], ) def test_stac_common_no_dates(args) -> None: with pytest.raises( ValueError, match="start_datetime and end_datetime must be specified when datetime is null", ): StacCommonMetadata(**args) @pytest.mark.parametrize( "args", [ {"datetime": "2024-01-01T00:00:00Z", "start_datetime": "2024-01-01T00:00:00Z"}, {"datetime": "2024-01-01T00:00:00Z", "end_datetime": "2024-01-01T00:00:00Z"}, ], ) def test_stac_common_start_and_end(args) -> None: with pytest.raises( ValueError, match="use of start_datetime or end_datetime requires the use of the other", ): StacCommonMetadata(**args) def test_declared_model() -> None: class TestProperties(ItemProperties): foo: str bar: int model_config = ConfigDict( populate_by_name=True, alias_generator=lambda field_name: f"test:{field_name}", ) class TestItem(Item): properties: TestProperties test_item = request(EO_EXTENSION) del test_item["stac_extensions"] test_item["properties"] = { "datetime": test_item["properties"]["datetime"], "test:foo": "mocked", "test:bar": 1, } valid_item = TestItem(**test_item).model_dump() assert "test:foo" in valid_item["properties"] assert "test:bar" in valid_item["properties"] def test_item_factory_custom_base() -> None: class TestProperties(ItemProperties): foo: Literal["bar"] = "bar" class TestItem(Item): properties: TestProperties test_item = request(EO_EXTENSION) model = TestItem(**test_item) assert model.properties.foo == "bar" def test_serialize_namespace() -> None: test_item = request(SAR_EXTENSION) valid_item = Item(**test_item) assert "sar:instrument_mode" in valid_item.model_dump()["properties"] def test_excludes() -> None: test_item = request(EO_EXTENSION) valid_item = Item(**test_item).model_dump( by_alias=True, exclude_unset=True, exclude={"properties": {"bands"}} ) assert "eo:bands" not in valid_item["properties"] def test_validate_extensions() -> None: test_item = request(SAR_EXTENSION) assert validate_extensions(test_item) def test_validate_extensions_reraise_exception() -> None: test_item = request(EO_EXTENSION) del test_item["properties"]["datetime"] with pytest.raises(ValidationError): Item.model_validate(test_item) validate_extensions(test_item, reraise_exception=True) def test_validate_extensions_rfc3339_with_partial_seconds() -> None: test_item = request(SAR_EXTENSION) test_item["properties"]["updated"] = "2018-10-01T01:08:32.033Z" assert validate_extensions(test_item) @pytest.mark.parametrize("url,cls", [[EO_EXTENSION, Item], [COLLECTION, Collection]]) def test_extension(url, cls): test_data = request(url) test_data["stac_extensions"].append("https://foo") model = cls.model_validate(test_data) assert "https://foo/" in list(map(str, model.stac_extensions)) def test_resolve_link() -> None: link = Link(href="/hello/world", type=MimeTypes.jpeg, rel="test") link.resolve(base_url="http://base_url.com") assert link.href == "http://base_url.com/hello/world" def test_resolve_links() -> None: links = Links.model_validate( [Link(href="/hello/world", type=MimeTypes.jpeg, rel="test")] ) links.resolve(base_url="http://base_url.com") for link in links.link_iterator(): assert link.href == "http://base_url.com/hello/world" def test_geometry_null_item() -> None: test_item = request(ITEM_GEOMETRY_NULL) valid_item = Item(**test_item).model_dump() dict_match(test_item, valid_item) def test_item_bbox_validation() -> None: test_item = request(LABEL_EXTENSION) test_item["bbox"] = None with pytest.raises(ValueError, match="bbox is required if geometry is not null"): Item(**test_item) @pytest.mark.parametrize( "interval", [ # interval should at least have 1 value [], # interval item should be an array of 2 datetime ["2023-01-01T00:00:00Z", "2024-01-01T00:00:00Z"], # interval item should not be null [[]], # interval item should have 2 values [["2023-01-01T00:00:00Z", "2023-01-02T00:00:00Z", "2024-01-01T00:00:00Z"]], # invalid datetime values [[None, "yo"]], [["yo", None]], [["yo", "yo"]], # wrong datetime order [["2024-01-01T00:00:00Z", "2023-01-01T00:00:00Z"]], # sub-sequent starttime before overall starttime [ ["2023-01-01T00:00:00Z", "2024-01-01T00:00:00Z"], ["2022-01-31T00:00:00Z", "2024-01-01T00:00:00Z"], ], # sub-sequent endtime after overall endtime [ ["2023-01-01T00:00:00Z", "2024-01-01T00:00:00Z"], ["2023-01-31T00:00:00Z", "2024-01-02T00:00:00Z"], ], # sub-sequent starttime after sub-sequent endtime [ ["2023-01-01T00:00:00Z", "2024-01-01T00:00:00Z"], ["2023-01-31T00:00:00Z", "2023-01-01T00:00:00Z"], ], ], ) def test_time_intervals_invalid(interval) -> None: """Check Time Interval model.""" with pytest.raises(ValidationError): TimeInterval(interval=interval) @pytest.mark.parametrize( "interval", [ # Open date range to both sides is supported but not recommended. [[None, None]], [["2024-01-01T00:00:00Z", None]], [[None, "2024-01-01T00:00:00Z"]], [["2023-01-01T00:00:00Z", "2024-01-01T00:00:00Z"]], [ ["2023-01-01T00:00:00Z", "2024-01-01T00:00:00Z"], ["2023-01-31T00:00:00Z", "2024-01-01T00:00:00Z"], ], ], ) def test_time_intervals_valid(interval) -> None: """Check Time Interval model.""" assert TimeInterval(interval=interval) @pytest.mark.parametrize( "bboxes", [ # invalid Y order [[0, 1, 1, 0]], # invalid X order (if crossing Antimeridian limit, xmin > 0) [[-169, 0, -170, 1]], # invalid X order (if crossing Antimeridian limit, xmax < 0) [[170, 0, 169, 1]], # sub-sequent crossing Y [[0, 0, 2, 2], [0.5, 0.5, 2.0, 2.5]], # sub-sequent crossing X [[0, 0, 2, 2], [0.5, 0.5, 2.5, 2.0]], # sub-sequent crossing Antimeridian limit [[0, 0, 2, 2], [1, 0, -179, 1]], # both crossing Antimeridian limit but sub-sequent cross has min lat -176 > -178 [[2, 0, -178, 2], [1, 0, -176, 1]], # sub-sequent cross Antimeridian but not the overall [[0, 0, 2, 2], [1, 0, -176, 1]], # overall crossing and sub-sequent not within bounds [[2, 0, -178, 2], [-179, 0, -176, 1]], # overall crossing and sub-sequent not within bounds [[2, 0, -178, 2], [1, 0, 3, 1]], ], ) def test_spatial_intervals_invalid(bboxes) -> None: """Check invalid Spatial Interval model.""" with pytest.raises(ValidationError): SpatialExtent(bbox=bboxes) @pytest.mark.parametrize( "bboxes", [ [[0, 0, 1, 1]], # Same on both side [[0, 0, 2, 2], [0, 0, 2, 2]], [[0, 0, 2, 2], [0.5, 0.5, 1.5, 1.5]], # crossing Antimeridian limit [[2, 0, -178, 2]], # Case 1: overall crossing Antimeridian, sub-sequent bbox not crossing (but within overall right part) [[2, 0, -178, 2], [-179, 0, -178, 1]], # Case 2: overall crossing Antimeridian, sub-sequent bbox not crossing (but within overall left part) [[2, 0, -178, 2], [179, 0, 180, 1]], # Case 3: overall and sub-sequent crossing Antimeridian [[2, 0, -178, 2], [3, 0, -179, 1]], ], ) def test_spatial_intervals_valid(bboxes) -> None: """Check Spatial Interval model.""" assert SpatialExtent(bbox=bboxes) stac-pydantic-3.4.0/tox.ini000066400000000000000000000012571503615555000156060ustar00rootroot00000000000000[tox] envlist = py38,py39,py310,py311,py312 [testenv] extras = dev commands = python -m pytest [testenv:lint] extras = lint description = run linters commands = SKIP=mypy pre-commit run --all-files [testenv:type] extras = lint description = run type checks commands = pre-commit run mypy --all-files [testenv:build] basepython = python3 skip_install = true deps = build commands = python -m build [testenv:release] setenv = TWINE_USERNAME = {env:TWINE_USERNAME} TWINE_PASSWORD = {env:TWINE_PASSWORD} basepython = python3 skip_install = true deps = {[testenv:build]deps} twine >= 1.5.0 commands = {[testenv:build]commands} twine upload --skip-existing dist/*