├── .github ├── codecov.yml └── workflows │ ├── ci.yml │ └── deploy_mkdocs.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGES.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── docs │ ├── api │ │ └── rio_stac │ │ │ └── stac.md │ ├── contributing.md │ ├── examples │ │ ├── Multi_assets_item.ipynb │ │ └── data │ │ │ ├── B01.tif │ │ │ ├── B02.tif │ │ │ ├── B03.tif │ │ │ ├── B04.tif │ │ │ └── B05.tif │ ├── index.md │ ├── intro.md │ └── release-notes.md └── mkdocs.yml ├── pyproject.toml ├── rio_stac ├── __init__.py ├── scripts │ ├── __init__.py │ └── cli.py └── stac.py └── tests ├── __init__.py ├── conftest.py ├── fixtures ├── dataset.h5 ├── dataset.hdf ├── dataset.jp2 ├── dataset.jpg ├── dataset.png ├── dataset.tif ├── dataset.webp ├── dataset_cloud_date_metadata.tif ├── dataset_cloud_date_metadata.xml ├── dataset_cog.tif ├── dataset_colormap.tif ├── dataset_dateline.tif ├── dataset_description.tif ├── dataset_gcps.tif ├── dataset_gdalcog.tif ├── dataset_geo.tif ├── dataset_geom.tif ├── dataset_int16_nodata.tif ├── dataset_mars.tif ├── dataset_nocrs.tif ├── dataset_nodata_and_nan.tif ├── dataset_nodata_nan.tif ├── dataset_tiff_datetime.tif ├── dataset_with_offsets.tif └── issue_22.tif ├── test_cli.py ├── test_create_item.py └── test_mediatype.py /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 5 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # On every pull request, but only on push to main 4 | on: 5 | push: 6 | branches: 7 | - main 8 | tags: 9 | - '*' 10 | pull_request: 11 | env: 12 | LATEST_PY_VERSION: '3.13' 13 | 14 | jobs: 15 | tests: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | python-version: 20 | - '3.9' 21 | - '3.10' 22 | - '3.11' 23 | - '3.12' 24 | - '3.13' 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up Python ${{ matrix.python-version }} 29 | uses: actions/setup-python@v5 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | 33 | - name: Install rio-stac 34 | run: | 35 | python -m pip install --upgrade pip 36 | python -m pip install .["test"] 37 | 38 | - name: run pre-commit 39 | if: ${{ matrix.python-version == env.LATEST_PY_VERSION }} 40 | run: | 41 | python -m pip install pre-commit 42 | pre-commit run --all-files 43 | 44 | - name: Run test 45 | run: python -m pytest --cov rio_stac --cov-report xml --cov-report term-missing 46 | 47 | - name: Upload Results 48 | if: ${{ matrix.python-version == env.LATEST_PY_VERSION }} 49 | uses: codecov/codecov-action@v1 50 | with: 51 | file: ./coverage.xml 52 | flags: unittests 53 | name: ${{ matrix.python-version }} 54 | fail_ci_if_error: false 55 | 56 | publish: 57 | needs: [tests] 58 | runs-on: ubuntu-latest 59 | if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release' 60 | steps: 61 | - uses: actions/checkout@v4 62 | - name: Set up Python 63 | uses: actions/setup-python@v5 64 | with: 65 | python-version: ${{ env.LATEST_PY_VERSION }} 66 | 67 | - name: Install dependencies 68 | run: | 69 | python -m pip install --upgrade pip 70 | python -m pip install flit 71 | python -m pip install . 72 | 73 | - name: Set tag version 74 | id: tag 75 | run: | 76 | echo "version=${GITHUB_REF#refs/*/}" 77 | echo "version=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT 78 | 79 | - name: Set module version 80 | id: module 81 | run: | 82 | echo version=$(python -c'import rio_stac; print(rio_stac.__version__)') >> $GITHUB_OUTPUT 83 | 84 | - name: Build and publish 85 | if: ${{ steps.tag.outputs.version }} == ${{ steps.module.outputs.version}} 86 | env: 87 | FLIT_USERNAME: ${{ secrets.PYPI_USERNAME }} 88 | FLIT_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 89 | run: flit publish 90 | -------------------------------------------------------------------------------- /.github/workflows/deploy_mkdocs.yml: -------------------------------------------------------------------------------- 1 | name: Publish docs via GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | # Only rebuild website when docs have changed 9 | - 'README.md' 10 | - 'docs/**' 11 | - 'mkdocs.yml' 12 | - 'rio_stac/**.py' 13 | - .github/workflows/deploy_mkdocs.yml 14 | 15 | jobs: 16 | build: 17 | name: Deploy docs 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout main 21 | uses: actions/checkout@v2 22 | 23 | - name: Set up Python 3.8 24 | uses: actions/setup-python@v2 25 | with: 26 | python-version: 3.8 27 | 28 | - name: Install dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | python -m pip install -e .["doc"] 32 | 33 | - name: update API docs 34 | run: | 35 | pdocs as_markdown --output_dir docs/docs/api/ --exclude_source --overwrite rio_stac.stac 36 | 37 | - name: Deploy docs 38 | run: mkdocs gh-deploy -f docs/mkdocs.yml --force 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | .pytest_cache 104 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/abravalheri/validate-pyproject 3 | rev: v0.12.1 4 | hooks: 5 | - id: validate-pyproject 6 | 7 | - repo: https://github.com/PyCQA/isort 8 | rev: 5.12.0 9 | hooks: 10 | - id: isort 11 | language_version: python 12 | 13 | - repo: https://github.com/astral-sh/ruff-pre-commit 14 | rev: v0.3.5 15 | hooks: 16 | - id: ruff 17 | args: ["--fix"] 18 | - id: ruff-format 19 | 20 | - repo: https://github.com/pre-commit/mirrors-mypy 21 | rev: v1.11.2 22 | hooks: 23 | - id: mypy 24 | language_version: python 25 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.11.0 (2025-04-25) 3 | 4 | * Use timezone-aware objects to represent datetimes in UTC (avoid future deprecation) 5 | * remove python 3.8 support 6 | * add python 3.13 support 7 | 8 | ## 0.10.1 (2024-11-21) 9 | 10 | * exclude rasterio version `1.4.2` from requirements 11 | * catch date parsing issue and raise warning 12 | 13 | ## 0.10.0 (2024-10-29) 14 | 15 | * handle `TIFFTAG_DATETIME` metadata for STAC datetime 16 | * only set `proj:epsg` if present else use `proj:wkt2` or `proj:projjson` 17 | * add `geographic_crs` parameter to `create_stac_item` function to enable non-earth raster dataset 18 | 19 | ## 0.9.0 (2023-12-08) 20 | 21 | * add `wkt2` representation of the dataset CRS if available (author @emileten, https://github.com/developmentseed/rio-stac/pull/55) 22 | 23 | ## 0.8.1 (2023-09-27) 24 | 25 | * update `tests` requirements 26 | 27 | ## 0.8.0 (2023-05-26) 28 | 29 | * update `proj` extension to `v1.1.0` 30 | * update `eo` extension to `v1.1.0` 31 | 32 | ## 0.7.1 (2023-05-03) 33 | 34 | * fix bad precision default (author @hrodmn, https://github.com/developmentseed/rio-stac/pull/50) 35 | 36 | ## 0.7.0 (2023-04-05) 37 | 38 | * add `geom_densify_pts` option allow adding points on Polygon edges to account for non-linear transformation 39 | * add `geom_precision` option to control the decimal precision of the output geometry 40 | * rename `rio_stac.stac.get_metadata` to `rio_stac.stac.get_dataset_geom` 41 | 42 | ## 0.6.1 (2022-10-26) 43 | 44 | * add python 3.11 support 45 | 46 | ## 0.6.0 (2022-10-20) 47 | 48 | * remove python 3.7 support (https://github.com/developmentseed/rio-stac/pull/42) 49 | * add `projjson` representation of the dataset CRS if available (author @clausmichele, https://github.com/developmentseed/rio-stac/pull/41) 50 | 51 | ## 0.5.0 (2022-09-05) 52 | 53 | * add python 3.10 support (https://github.com/developmentseed/rio-stac/pull/37) 54 | * get dataset datetime from GDAL Raster Data Model **breaking** 55 | * add `eo` extension support (`eo:cloud_cover`, `eo:bands`) **breaking** 56 | * use `auto` by default for `asset_media_type` **breaking** 57 | 58 | ## 0.4.2 (2022-06-09) 59 | 60 | * fix bad `nan/inf/-inf` nodata test 61 | 62 | ## 0.4.1 (2022-04-26) 63 | 64 | * handle `nan/inf` values to avoid `numpy.histogram` issue (https://github.com/developmentseed/rio-stac/pull/32) 65 | 66 | ## 0.4.0 (2022-03-29) 67 | 68 | * Switch to `pyproject.toml` to simplify setup. 69 | 70 | **bug fixes** 71 | 72 | * Split geometry to MultiPolygon for dataset crossing the dataline separation (https://github.com/developmentseed/rio-stac/pull/30) 73 | * Use correct coordinates order for Polygon (ref https://github.com/developmentseed/geojson-pydantic/pull/49) 74 | 75 | ## 0.3.2 (2021-10-29) 76 | 77 | **bug fixes** 78 | * Use the raster_max_size and asset_roles arguments in create_stac_item (author @alexgleith, https://github.com/developmentseed/rio-stac/pull/23) 79 | * Fix json serialisation by converting numpy float32 to float (author @alexgleith, https://github.com/developmentseed/rio-stac/pull/24) 80 | 81 | ## 0.3.1 (2021-10-07) 82 | 83 | * update `pystac` requirement to allow up to `<2.0` (author @alexgleith, https://github.com/developmentseed/rio-stac/pull/20) 84 | 85 | ## 0.3.0 (2021-09-10) 86 | 87 | * Move `raster:bands` information in assets (not in properties). 88 | * update pystac version 89 | * fix typo for `stddev` raster information 90 | * drop support of python 3.6 (pystac 1.0.0 dropped support of python 3.6) 91 | 92 | ## 0.2.1 (2021-08-24) 93 | 94 | * use WarpedVRT for data with internal GCPS 95 | 96 | ## 0.2.0 (2021-07-06) 97 | 98 | * fix validation issue with Collection and extension for STAC 1.0.0 99 | * add collection_url option to customize the collection link 100 | * add `raster` extension option (https://github.com/developmentseed/rio-stac/pull/12) 101 | * set `proj:epsg` value to `None` when no `CRS` is found in the dataset. 102 | 103 | **breaking changes** 104 | 105 | * update pystac version to `>=1.0.0rc1` 106 | * use full URL for extension 107 | * add Collection Link when adding a collection 108 | * add with_proj (--with-proj/--without-proj in the CLI) in `create_stac_item` to add the extension and proj properties in the stac items (will do the same for the raster extension) 109 | 110 | ## 0.1.1 (2021-03-19) 111 | 112 | * fix CLI asset-href default 113 | 114 | ## 0.1.0 (2021-03-19) 115 | 116 | Initial release. 117 | 118 | * Design API 119 | * add CLI 120 | * add tests 121 | * write docs 122 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Issues and pull requests are more than welcome. 4 | 5 | **dev install** 6 | 7 | ```bash 8 | $ git clone https://github.com/developmentseed/rio-stac.git 9 | $ cd rio-stac 10 | $ pip install -e .["test","dev"] 11 | ``` 12 | 13 | You can then run the tests with the following command: 14 | 15 | ```sh 16 | python -m pytest --cov rio_stac --cov-report term-missing 17 | ``` 18 | 19 | **pre-commit** 20 | 21 | This repo is set to use `pre-commit` to run *isort*, *flake8*, *pydocstring*, *black* ("uncompromising Python code formatter") and mypy when committing new code. 22 | 23 | ```bash 24 | $ pre-commit install 25 | ``` 26 | 27 | **Docs** 28 | 29 | ```bash 30 | $ git clone https://github.com/developmentseed/rio-stac.git 31 | $ cd rio-stac 32 | $ pip install -e .["doc"] 33 | ``` 34 | 35 | Create API docs 36 | 37 | ```bash 38 | $ pdocs as_markdown --output_dir docs/docs/api/ --exclude_source --overwrite rio_stac.stac 39 | ``` 40 | 41 | Hot-reloading docs: 42 | 43 | ```bash 44 | $ mkdocs serve 45 | ``` 46 | 47 | To manually deploy docs (note you should never need to do this because Github 48 | Actions deploys automatically for new commits.): 49 | 50 | ```bash 51 | $ mkdocs gh-deploy -f docs/mkdocs.yml 52 | ``` 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Development Seed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rio-stac 2 | 3 |

4 | rio-stac 5 |

6 |

7 | Create STAC Items from raster datasets. 8 |

9 |

10 | 11 | Test 12 | 13 | 14 | Coverage 15 | 16 | 17 | Package version 18 | 19 | 20 | Downloads 21 | 22 | 23 | Downloads 24 | 25 |

26 | 27 | --- 28 | 29 | **Documentation**: https://developmentseed.github.io/rio-stac/ 30 | 31 | **Source Code**: https://github.com/developmentseed/rio-stac 32 | 33 | --- 34 | 35 | `rio-stac` is a simple [rasterio](https://github.com/mapbox/rasterio) plugin for creating valid STAC items from a raster dataset. The library is built on top of [pystac](https://github.com/stac-utils/pystac) to make sure we follow the STAC specification. 36 | 37 | ## Installation 38 | 39 | ```bash 40 | $ pip install pip -U 41 | 42 | # From Pypi 43 | $ pip install rio-stac 44 | 45 | # Or from source 46 | $ pip install git+http://github.com/developmentseed/rio-stac 47 | ``` 48 | 49 | ### Example 50 | 51 | ```json 52 | // rio stac tests/fixtures/dataset_cog.tif | jq 53 | { 54 | "type": "Feature", 55 | "stac_version": "1.0.0", 56 | "id": "dataset_cog.tif", 57 | "properties": { 58 | "proj:epsg": 32621, 59 | "proj:geometry": { 60 | "type": "Polygon", 61 | "coordinates": [ 62 | [ 63 | [ 64 | 373185.0, 65 | 8019284.949381611 66 | ], 67 | [ 68 | 639014.9492102272, 69 | 8019284.949381611 70 | ], 71 | [ 72 | 639014.9492102272, 73 | 8286015.0 74 | ], 75 | [ 76 | 373185.0, 77 | 8286015.0 78 | ], 79 | [ 80 | 373185.0, 81 | 8019284.949381611 82 | ] 83 | ] 84 | ] 85 | }, 86 | "proj:bbox": [ 87 | 373185.0, 88 | 8019284.949381611, 89 | 639014.9492102272, 90 | 8286015.0 91 | ], 92 | "proj:shape": [ 93 | 2667, 94 | 2658 95 | ], 96 | "proj:transform": [ 97 | 100.01126757344893, 98 | 0.0, 99 | 373185.0, 100 | 0.0, 101 | -100.01126757344893, 102 | 8286015.0, 103 | 0.0, 104 | 0.0, 105 | 1.0 106 | ], 107 | "proj:projjson": { 108 | "$schema": "https://proj.org/schemas/v0.4/projjson.schema.json", 109 | "type": "ProjectedCRS", 110 | "name": "WGS 84 / UTM zone 21N", 111 | "base_crs": { 112 | "name": "WGS 84", 113 | "datum": { 114 | "type": "GeodeticReferenceFrame", 115 | "name": "World Geodetic System 1984", 116 | "ellipsoid": { 117 | "name": "WGS 84", 118 | "semi_major_axis": 6378137, 119 | "inverse_flattening": 298.257223563 120 | } 121 | }, 122 | "coordinate_system": { 123 | "subtype": "ellipsoidal", 124 | "axis": [ 125 | { 126 | "name": "Geodetic latitude", 127 | "abbreviation": "Lat", 128 | "direction": "north", 129 | "unit": "degree" 130 | }, 131 | { 132 | "name": "Geodetic longitude", 133 | "abbreviation": "Lon", 134 | "direction": "east", 135 | "unit": "degree" 136 | } 137 | ] 138 | }, 139 | "id": { 140 | "authority": "EPSG", 141 | "code": 4326 142 | } 143 | }, 144 | "conversion": { 145 | "name": "UTM zone 21N", 146 | "method": { 147 | "name": "Transverse Mercator", 148 | "id": { 149 | "authority": "EPSG", 150 | "code": 9807 151 | } 152 | }, 153 | "parameters": [ 154 | { 155 | "name": "Latitude of natural origin", 156 | "value": 0, 157 | "unit": "degree", 158 | "id": { 159 | "authority": "EPSG", 160 | "code": 8801 161 | } 162 | }, 163 | { 164 | "name": "Longitude of natural origin", 165 | "value": -57, 166 | "unit": "degree", 167 | "id": { 168 | "authority": "EPSG", 169 | "code": 8802 170 | } 171 | }, 172 | { 173 | "name": "Scale factor at natural origin", 174 | "value": 0.9996, 175 | "unit": "unity", 176 | "id": { 177 | "authority": "EPSG", 178 | "code": 8805 179 | } 180 | }, 181 | { 182 | "name": "False easting", 183 | "value": 500000, 184 | "unit": "metre", 185 | "id": { 186 | "authority": "EPSG", 187 | "code": 8806 188 | } 189 | }, 190 | { 191 | "name": "False northing", 192 | "value": 0, 193 | "unit": "metre", 194 | "id": { 195 | "authority": "EPSG", 196 | "code": 8807 197 | } 198 | } 199 | ] 200 | }, 201 | "coordinate_system": { 202 | "subtype": "Cartesian", 203 | "axis": [ 204 | { 205 | "name": "Easting", 206 | "abbreviation": "", 207 | "direction": "east", 208 | "unit": "metre" 209 | }, 210 | { 211 | "name": "Northing", 212 | "abbreviation": "", 213 | "direction": "north", 214 | "unit": "metre" 215 | } 216 | ] 217 | }, 218 | "id": { 219 | "authority": "EPSG", 220 | "code": 32621 221 | } 222 | }, 223 | "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 21N\",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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-57],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],AUTHORITY[\"EPSG\",\"32621\"]]", 224 | "datetime": "2023-12-08T09:30:38.153261Z" 225 | }, 226 | "geometry": { 227 | "type": "Polygon", 228 | "coordinates": [ 229 | [ 230 | [ 231 | -60.72634617297825, 232 | 72.23689137791739 233 | ], 234 | [ 235 | -52.91627525610924, 236 | 72.22979795551834 237 | ], 238 | [ 239 | -52.301598718454485, 240 | 74.61378388950398 241 | ], 242 | [ 243 | -61.28762442711404, 244 | 74.62204314252978 245 | ], 246 | [ 247 | -60.72634617297825, 248 | 72.23689137791739 249 | ] 250 | ] 251 | ] 252 | }, 253 | "links": [], 254 | "assets": { 255 | "asset": { 256 | "href": "/Users/vincentsarago/Dev/DevSeed/rio-stac/tests/fixtures/dataset_cog.tif", 257 | "raster:bands": [ 258 | { 259 | "data_type": "uint16", 260 | "scale": 1.0, 261 | "offset": 0.0, 262 | "sampling": "point", 263 | "statistics": { 264 | "mean": 2107.524612053134, 265 | "minimum": 1, 266 | "maximum": 7872, 267 | "stddev": 2271.0065537857326, 268 | "valid_percent": 0.00009564764936336924 269 | }, 270 | "histogram": { 271 | "count": 11, 272 | "min": 1.0, 273 | "max": 7872.0, 274 | "buckets": [ 275 | 503460, 276 | 0, 277 | 0, 278 | 161792, 279 | 283094, 280 | 0, 281 | 0, 282 | 0, 283 | 87727, 284 | 9431 285 | ] 286 | } 287 | } 288 | ], 289 | "eo:bands": [ 290 | { 291 | "name": "b1", 292 | "description": "gray" 293 | } 294 | ], 295 | "roles": [] 296 | } 297 | }, 298 | "bbox": [ 299 | -61.28762442711404, 300 | 72.22979795551834, 301 | -52.301598718454485, 302 | 74.62204314252978 303 | ], 304 | "stac_extensions": [ 305 | "https://stac-extensions.github.io/projection/v1.1.0/schema.json", 306 | "https://stac-extensions.github.io/raster/v1.1.0/schema.json", 307 | "https://stac-extensions.github.io/eo/v1.1.0/schema.json" 308 | ] 309 | } 310 | ``` 311 | 312 | See https://developmentseed.org/rio-stac/intro/ for more. 313 | 314 | ## Contribution & Development 315 | 316 | See [CONTRIBUTING.md](https://github.com/developmentseed/rio-stac/blob/main/CONTRIBUTING.md) 317 | 318 | ## Authors 319 | 320 | See [contributors](https://github.com/developmentseed/rio-stac/graphs/contributors) 321 | 322 | ## Changes 323 | 324 | See [CHANGES.md](https://github.com/developmentseed/rio-stac/blob/main/CHANGES.md). 325 | 326 | ## License 327 | 328 | See [LICENSE](https://github.com/developmentseed/rio-stac/blob/main/LICENSE) 329 | -------------------------------------------------------------------------------- /docs/docs/api/rio_stac/stac.md: -------------------------------------------------------------------------------- 1 | # Module rio_stac.stac 2 | 3 | Create STAC Item from a rasterio dataset. 4 | 5 | None 6 | 7 | ## Functions 8 | 9 | 10 | ### bbox_to_geom 11 | 12 | ```python3 13 | def bbox_to_geom( 14 | bbox: Tuple[float, float, float, float] 15 | ) -> Dict 16 | ``` 17 | 18 | 19 | Return a geojson geometry from a bbox. 20 | 21 | 22 | ### create_stac_item 23 | 24 | ```python3 25 | def create_stac_item( 26 | source: Union[str, rasterio.io.DatasetReader, rasterio.io.DatasetWriter, rasterio.vrt.WarpedVRT, rasterio.io.MemoryFile], 27 | input_datetime: Union[datetime.datetime, NoneType] = None, 28 | extensions: Union[List[str], NoneType] = None, 29 | collection: Union[str, NoneType] = None, 30 | properties: Union[Dict, NoneType] = None, 31 | id: Union[str, NoneType] = None, 32 | assets: Union[Dict[str, pystac.item.Asset], NoneType] = None, 33 | asset_name: str = 'asset', 34 | asset_roles: Union[List[str], NoneType] = None, 35 | asset_media_type: Union[str, pystac.media_type.MediaType, NoneType] = None, 36 | asset_href: Union[str, NoneType] = None 37 | ) -> pystac.item.Item 38 | ``` 39 | 40 | 41 | Create a Stac Item. 42 | 43 | **Parameters:** 44 | 45 | | Name | Type | Description | Default | 46 | |---|---|---|---| 47 | | source | str or rasterio openned dataset | input path or rasterio dataset. | None | 48 | | input_datetime | datetime.datetime | datetime associated with the item. | None | 49 | | extensions | list of str | input list of extensions to use in the item. | None | 50 | | collection | str | collection's name the item belong to. | None | 51 | | properties | dict | additional properties to add in the item. | None | 52 | | id | str | id to assign to the item (default to the source basename). | None | 53 | | assets | dict | Assets to set in the item. If set we won't create one from the source. | None | 54 | | asset_name | str | asset name in the Assets object. | None | 55 | | asset_roles | list of str | list of asset's role. | None | 56 | | asset_media_type | str or pystac.MediaType | asset's media type. | None | 57 | | asset_href | str | asset's URI (default to input path). | None | 58 | 59 | **Returns:** 60 | 61 | | Type | Description | 62 | |---|---| 63 | | pystac.Item | valid STAC Item. | 64 | 65 | 66 | ### get_media_type 67 | 68 | ```python3 69 | def get_media_type( 70 | src_dst: Union[rasterio.io.DatasetReader, rasterio.io.DatasetWriter, rasterio.vrt.WarpedVRT, rasterio.io.MemoryFile] 71 | ) -> Union[pystac.media_type.MediaType, NoneType] 72 | ``` 73 | 74 | 75 | Define or validate MediaType. 76 | 77 | 78 | ### get_metadata 79 | 80 | ```python3 81 | def get_metadata( 82 | src_dst: Union[rasterio.io.DatasetReader, rasterio.io.DatasetWriter, rasterio.vrt.WarpedVRT, rasterio.io.MemoryFile] 83 | ) -> Dict 84 | ``` 85 | 86 | 87 | Get Raster Metadata. -------------------------------------------------------------------------------- /docs/docs/contributing.md: -------------------------------------------------------------------------------- 1 | ../../CONTRIBUTING.md -------------------------------------------------------------------------------- /docs/docs/examples/Multi_assets_item.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "id": "6b40fc6f", 7 | "metadata": {}, 8 | "source": [ 9 | "The Goal of this notebook is to show how to use the rio-stac API to create a STAC Item from multiple assets." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 2, 15 | "id": "1a61b77d", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "import datetime\n", 20 | "\n", 21 | "import pystac\n", 22 | "from pystac.utils import str_to_datetime\n", 23 | "\n", 24 | "import rasterio\n", 25 | "\n", 26 | "# Import extension version\n", 27 | "from rio_stac.stac import PROJECTION_EXT_VERSION, RASTER_EXT_VERSION, EO_EXT_VERSION\n", 28 | "\n", 29 | "# Import rio_stac methods\n", 30 | "from rio_stac.stac import (\n", 31 | " get_dataset_geom,\n", 32 | " get_projection_info,\n", 33 | " get_raster_info,\n", 34 | " get_eobands_info,\n", 35 | " bbox_to_geom,\n", 36 | ")" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 3, 42 | "id": "6650dbf2", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "assets = [\n", 47 | " {\"name\": \"B01\", \"path\": \"./data/B01.tif\", \"href\": None, \"role\": None},\n", 48 | " {\"name\": \"B02\", \"path\": \"./data/B02.tif\", \"href\": None, \"role\": None},\n", 49 | " {\"name\": \"B03\", \"path\": \"./data/B03.tif\", \"href\": None, \"role\": None},\n", 50 | " {\"name\": \"B04\", \"path\": \"./data/B04.tif\", \"href\": None, \"role\": None},\n", 51 | " {\"name\": \"B05\", \"path\": \"./data/B05.tif\", \"href\": None, \"role\": None},\n", 52 | "]\n", 53 | "\n", 54 | "media_type = pystac.MediaType.COG # we could also use rio_stac.stac.get_media_type\n", 55 | "\n", 56 | "# additional properties to add in the item\n", 57 | "properties = {}\n", 58 | "\n", 59 | "# datetime associated with the item\n", 60 | "input_datetime = None\n", 61 | "\n", 62 | "# STAC Item Id\n", 63 | "id = \"my_stac_item\"\n", 64 | "\n", 65 | "# name of collection the item belongs to\n", 66 | "collection = None\n", 67 | "collection_url = None\n", 68 | "\n", 69 | "extensions =[\n", 70 | " f\"https://stac-extensions.github.io/projection/{PROJECTION_EXT_VERSION}/schema.json\",\n", 71 | " f\"https://stac-extensions.github.io/raster/{RASTER_EXT_VERSION}/schema.json\",\n", 72 | " f\"https://stac-extensions.github.io/eo/{EO_EXT_VERSION}/schema.json\",\n", 73 | "]" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 4, 79 | "id": "f6c9c7c2", 80 | "metadata": {}, 81 | "outputs": [ 82 | { 83 | "data": { 84 | "text/plain": [ 85 | "['https://schemas.stacspec.org/v1.0.0/item-spec/json-schema/item.json',\n", 86 | " 'https://stac-extensions.github.io/projection/v1.1.0/schema.json',\n", 87 | " 'https://stac-extensions.github.io/raster/v1.1.0/schema.json',\n", 88 | " 'https://stac-extensions.github.io/eo/v1.1.0/schema.json']" 89 | ] 90 | }, 91 | "execution_count": 4, 92 | "metadata": {}, 93 | "output_type": "execute_result" 94 | } 95 | ], 96 | "source": [ 97 | "\n", 98 | "bboxes = []\n", 99 | "\n", 100 | "pystac_assets = []\n", 101 | "\n", 102 | "img_datetimes = []\n", 103 | "\n", 104 | "for asset in assets:\n", 105 | "\n", 106 | " with rasterio.open(asset[\"path\"]) as src_dst:\n", 107 | "\n", 108 | " # Get BBOX and Footprint\n", 109 | " dataset_geom = get_dataset_geom(src_dst, densify_pts=0, precision=-1)\n", 110 | " bboxes.append(dataset_geom[\"bbox\"])\n", 111 | "\n", 112 | " if \"start_datetime\" not in properties and \"end_datetime\" not in properties:\n", 113 | " # Try to get datetime from https://gdal.org/user/raster_data_model.html#imagery-domain-remote-sensing\n", 114 | " dst_date = src_dst.get_tag_item(\"ACQUISITIONDATETIME\", \"IMAGERY\")\n", 115 | " dst_datetime = str_to_datetime(dst_date) if dst_date else None\n", 116 | " if dst_datetime:\n", 117 | " img_datetimes.append(dst_datetime)\n", 118 | "\n", 119 | " proj_info = {\n", 120 | " f\"proj:{name}\": value\n", 121 | " for name, value in get_projection_info(src_dst).items()\n", 122 | " }\n", 123 | "\n", 124 | " raster_info = {\n", 125 | " \"raster:bands\": get_raster_info(src_dst, max_size=1024)\n", 126 | " }\n", 127 | "\n", 128 | " eo_info = {}\n", 129 | " eo_info = {\"eo:bands\": get_eobands_info(src_dst)}\n", 130 | " cloudcover = src_dst.get_tag_item(\"CLOUDCOVER\", \"IMAGERY\")\n", 131 | " if cloudcover is not None:\n", 132 | " properties.update({\"eo:cloud_cover\": int(cloudcover)})\n", 133 | "\n", 134 | " pystac_assets.append(\n", 135 | " (\n", 136 | " asset[\"name\"],\n", 137 | " pystac.Asset(\n", 138 | " href=asset[\"href\"] or src_dst.name,\n", 139 | " media_type=media_type,\n", 140 | " extra_fields={\n", 141 | " **proj_info,\n", 142 | " **raster_info,\n", 143 | " **eo_info\n", 144 | " },\n", 145 | " roles=asset[\"role\"],\n", 146 | " ),\n", 147 | " )\n", 148 | " )\n", 149 | "\n", 150 | "if img_datetimes and not input_datetime:\n", 151 | " input_datetime = img_datetimes[0]\n", 152 | "\n", 153 | "input_datetime = input_datetime or datetime.datetime.utcnow()\n", 154 | "\n", 155 | "minx, miny, maxx, maxy = zip(*bboxes)\n", 156 | "bbox = [min(minx), min(miny), max(maxx), max(maxy)]\n", 157 | "\n", 158 | "# item\n", 159 | "item = pystac.Item(\n", 160 | " id=id,\n", 161 | " geometry=bbox_to_geom(bbox),\n", 162 | " bbox=bbox,\n", 163 | " collection=collection,\n", 164 | " stac_extensions=extensions,\n", 165 | " datetime=input_datetime,\n", 166 | " properties=properties,\n", 167 | ")\n", 168 | "\n", 169 | "# if we add a collection we MUST add a link\n", 170 | "if collection:\n", 171 | " item.add_link(\n", 172 | " pystac.Link(\n", 173 | " pystac.RelType.COLLECTION,\n", 174 | " collection_url or collection,\n", 175 | " media_type=pystac.MediaType.JSON,\n", 176 | " )\n", 177 | " )\n", 178 | "\n", 179 | "for key, asset in pystac_assets:\n", 180 | " item.add_asset(key=key, asset=asset)\n", 181 | "\n", 182 | "item.validate()" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": 5, 188 | "id": "3ed579da", 189 | "metadata": {}, 190 | "outputs": [ 191 | { 192 | "name": "stdout", 193 | "output_type": "stream", 194 | "text": [ 195 | "{\n", 196 | " \"type\": \"Feature\",\n", 197 | " \"stac_version\": \"1.0.0\",\n", 198 | " \"id\": \"my_stac_item\",\n", 199 | " \"properties\": {\n", 200 | " \"datetime\": \"2023-12-08T09:55:37.520499Z\"\n", 201 | " },\n", 202 | " \"geometry\": {\n", 203 | " \"type\": \"Polygon\",\n", 204 | " \"coordinates\": [\n", 205 | " [\n", 206 | " [\n", 207 | " -11.979244865430262,\n", 208 | " 24.296321392464336\n", 209 | " ],\n", 210 | " [\n", 211 | " -10.874546803397616,\n", 212 | " 24.296321392464336\n", 213 | " ],\n", 214 | " [\n", 215 | " -10.874546803397616,\n", 216 | " 25.304623891542274\n", 217 | " ],\n", 218 | " [\n", 219 | " -11.979244865430262,\n", 220 | " 25.304623891542274\n", 221 | " ],\n", 222 | " [\n", 223 | " -11.979244865430262,\n", 224 | " 24.296321392464336\n", 225 | " ]\n", 226 | " ]\n", 227 | " ]\n", 228 | " },\n", 229 | " \"links\": [],\n", 230 | " \"assets\": {\n", 231 | " \"B01\": {\n", 232 | " \"href\": \"./data/B01.tif\",\n", 233 | " \"type\": \"image/tiff; application=geotiff; profile=cloud-optimized\",\n", 234 | " \"proj:epsg\": 32629,\n", 235 | " \"proj:geometry\": {\n", 236 | " \"type\": \"Polygon\",\n", 237 | " \"coordinates\": [\n", 238 | " [\n", 239 | " [\n", 240 | " 199980.0,\n", 241 | " 2690220.0\n", 242 | " ],\n", 243 | " [\n", 244 | " 309780.0,\n", 245 | " 2690220.0\n", 246 | " ],\n", 247 | " [\n", 248 | " 309780.0,\n", 249 | " 2800020.0\n", 250 | " ],\n", 251 | " [\n", 252 | " 199980.0,\n", 253 | " 2800020.0\n", 254 | " ],\n", 255 | " [\n", 256 | " 199980.0,\n", 257 | " 2690220.0\n", 258 | " ]\n", 259 | " ]\n", 260 | " ]\n", 261 | " },\n", 262 | " \"proj:bbox\": [\n", 263 | " 199980.0,\n", 264 | " 2690220.0,\n", 265 | " 309780.0,\n", 266 | " 2800020.0\n", 267 | " ],\n", 268 | " \"proj:shape\": [\n", 269 | " 549,\n", 270 | " 549\n", 271 | " ],\n", 272 | " \"proj:transform\": [\n", 273 | " 200.0,\n", 274 | " 0.0,\n", 275 | " 199980.0,\n", 276 | " 0.0,\n", 277 | " -200.0,\n", 278 | " 2800020.0,\n", 279 | " 0.0,\n", 280 | " 0.0,\n", 281 | " 1.0\n", 282 | " ],\n", 283 | " \"proj:projjson\": {\n", 284 | " \"$schema\": \"https://proj.org/schemas/v0.4/projjson.schema.json\",\n", 285 | " \"type\": \"ProjectedCRS\",\n", 286 | " \"name\": \"WGS 84 / UTM zone 29N\",\n", 287 | " \"base_crs\": {\n", 288 | " \"name\": \"WGS 84\",\n", 289 | " \"datum\": {\n", 290 | " \"type\": \"GeodeticReferenceFrame\",\n", 291 | " \"name\": \"World Geodetic System 1984\",\n", 292 | " \"ellipsoid\": {\n", 293 | " \"name\": \"WGS 84\",\n", 294 | " \"semi_major_axis\": 6378137,\n", 295 | " \"inverse_flattening\": 298.257223563\n", 296 | " }\n", 297 | " },\n", 298 | " \"coordinate_system\": {\n", 299 | " \"subtype\": \"ellipsoidal\",\n", 300 | " \"axis\": [\n", 301 | " {\n", 302 | " \"name\": \"Geodetic latitude\",\n", 303 | " \"abbreviation\": \"Lat\",\n", 304 | " \"direction\": \"north\",\n", 305 | " \"unit\": \"degree\"\n", 306 | " },\n", 307 | " {\n", 308 | " \"name\": \"Geodetic longitude\",\n", 309 | " \"abbreviation\": \"Lon\",\n", 310 | " \"direction\": \"east\",\n", 311 | " \"unit\": \"degree\"\n", 312 | " }\n", 313 | " ]\n", 314 | " },\n", 315 | " \"id\": {\n", 316 | " \"authority\": \"EPSG\",\n", 317 | " \"code\": 4326\n", 318 | " }\n", 319 | " },\n", 320 | " \"conversion\": {\n", 321 | " \"name\": \"UTM zone 29N\",\n", 322 | " \"method\": {\n", 323 | " \"name\": \"Transverse Mercator\",\n", 324 | " \"id\": {\n", 325 | " \"authority\": \"EPSG\",\n", 326 | " \"code\": 9807\n", 327 | " }\n", 328 | " },\n", 329 | " \"parameters\": [\n", 330 | " {\n", 331 | " \"name\": \"Latitude of natural origin\",\n", 332 | " \"value\": 0,\n", 333 | " \"unit\": \"degree\",\n", 334 | " \"id\": {\n", 335 | " \"authority\": \"EPSG\",\n", 336 | " \"code\": 8801\n", 337 | " }\n", 338 | " },\n", 339 | " {\n", 340 | " \"name\": \"Longitude of natural origin\",\n", 341 | " \"value\": -9,\n", 342 | " \"unit\": \"degree\",\n", 343 | " \"id\": {\n", 344 | " \"authority\": \"EPSG\",\n", 345 | " \"code\": 8802\n", 346 | " }\n", 347 | " },\n", 348 | " {\n", 349 | " \"name\": \"Scale factor at natural origin\",\n", 350 | " \"value\": 0.9996,\n", 351 | " \"unit\": \"unity\",\n", 352 | " \"id\": {\n", 353 | " \"authority\": \"EPSG\",\n", 354 | " \"code\": 8805\n", 355 | " }\n", 356 | " },\n", 357 | " {\n", 358 | " \"name\": \"False easting\",\n", 359 | " \"value\": 500000,\n", 360 | " \"unit\": \"metre\",\n", 361 | " \"id\": {\n", 362 | " \"authority\": \"EPSG\",\n", 363 | " \"code\": 8806\n", 364 | " }\n", 365 | " },\n", 366 | " {\n", 367 | " \"name\": \"False northing\",\n", 368 | " \"value\": 0,\n", 369 | " \"unit\": \"metre\",\n", 370 | " \"id\": {\n", 371 | " \"authority\": \"EPSG\",\n", 372 | " \"code\": 8807\n", 373 | " }\n", 374 | " }\n", 375 | " ]\n", 376 | " },\n", 377 | " \"coordinate_system\": {\n", 378 | " \"subtype\": \"Cartesian\",\n", 379 | " \"axis\": [\n", 380 | " {\n", 381 | " \"name\": \"Easting\",\n", 382 | " \"abbreviation\": \"\",\n", 383 | " \"direction\": \"east\",\n", 384 | " \"unit\": \"metre\"\n", 385 | " },\n", 386 | " {\n", 387 | " \"name\": \"Northing\",\n", 388 | " \"abbreviation\": \"\",\n", 389 | " \"direction\": \"north\",\n", 390 | " \"unit\": \"metre\"\n", 391 | " }\n", 392 | " ]\n", 393 | " },\n", 394 | " \"id\": {\n", 395 | " \"authority\": \"EPSG\",\n", 396 | " \"code\": 32629\n", 397 | " }\n", 398 | " },\n", 399 | " \"proj:wkt2\": \"PROJCS[\\\"WGS 84 / UTM zone 29N\\\",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.0174532925199433,AUTHORITY[\\\"EPSG\\\",\\\"9122\\\"]],AUTHORITY[\\\"EPSG\\\",\\\"4326\\\"]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"latitude_of_origin\\\",0],PARAMETER[\\\"central_meridian\\\",-9],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"false_easting\\\",500000],PARAMETER[\\\"false_northing\\\",0],UNIT[\\\"metre\\\",1,AUTHORITY[\\\"EPSG\\\",\\\"9001\\\"]],AXIS[\\\"Easting\\\",EAST],AXIS[\\\"Northing\\\",NORTH],AUTHORITY[\\\"EPSG\\\",\\\"32629\\\"]]\",\n", 400 | " \"raster:bands\": [\n", 401 | " {\n", 402 | " \"data_type\": \"uint16\",\n", 403 | " \"scale\": 1.0,\n", 404 | " \"offset\": 0.0,\n", 405 | " \"sampling\": \"area\",\n", 406 | " \"nodata\": 0.0,\n", 407 | " \"statistics\": {\n", 408 | " \"mean\": 4774.295702403111,\n", 409 | " \"minimum\": 1342,\n", 410 | " \"maximum\": 7725,\n", 411 | " \"stddev\": 509.38944965166854,\n", 412 | " \"valid_percent\": 100.0\n", 413 | " },\n", 414 | " \"histogram\": {\n", 415 | " \"count\": 11,\n", 416 | " \"min\": 1342.0,\n", 417 | " \"max\": 7725.0,\n", 418 | " \"buckets\": [\n", 419 | " 366,\n", 420 | " 1478,\n", 421 | " 2385,\n", 422 | " 9548,\n", 423 | " 57747,\n", 424 | " 182460,\n", 425 | " 41982,\n", 426 | " 4332,\n", 427 | " 1010,\n", 428 | " 93\n", 429 | " ]\n", 430 | " }\n", 431 | " }\n", 432 | " ],\n", 433 | " \"eo:bands\": [\n", 434 | " {\n", 435 | " \"name\": \"b1\",\n", 436 | " \"description\": \"gray\"\n", 437 | " }\n", 438 | " ]\n", 439 | " },\n", 440 | " \"B02\": {\n", 441 | " \"href\": \"./data/B02.tif\",\n", 442 | " \"type\": \"image/tiff; application=geotiff; profile=cloud-optimized\",\n", 443 | " \"proj:epsg\": 32629,\n", 444 | " \"proj:geometry\": {\n", 445 | " \"type\": \"Polygon\",\n", 446 | " \"coordinates\": [\n", 447 | " [\n", 448 | " [\n", 449 | " 199980.0,\n", 450 | " 2690220.0\n", 451 | " ],\n", 452 | " [\n", 453 | " 309780.0,\n", 454 | " 2690220.0\n", 455 | " ],\n", 456 | " [\n", 457 | " 309780.0,\n", 458 | " 2800020.0\n", 459 | " ],\n", 460 | " [\n", 461 | " 199980.0,\n", 462 | " 2800020.0\n", 463 | " ],\n", 464 | " [\n", 465 | " 199980.0,\n", 466 | " 2690220.0\n", 467 | " ]\n", 468 | " ]\n", 469 | " ]\n", 470 | " },\n", 471 | " \"proj:bbox\": [\n", 472 | " 199980.0,\n", 473 | " 2690220.0,\n", 474 | " 309780.0,\n", 475 | " 2800020.0\n", 476 | " ],\n", 477 | " \"proj:shape\": [\n", 478 | " 549,\n", 479 | " 549\n", 480 | " ],\n", 481 | " \"proj:transform\": [\n", 482 | " 200.0,\n", 483 | " 0.0,\n", 484 | " 199980.0,\n", 485 | " 0.0,\n", 486 | " -200.0,\n", 487 | " 2800020.0,\n", 488 | " 0.0,\n", 489 | " 0.0,\n", 490 | " 1.0\n", 491 | " ],\n", 492 | " \"proj:projjson\": {\n", 493 | " \"$schema\": \"https://proj.org/schemas/v0.4/projjson.schema.json\",\n", 494 | " \"type\": \"ProjectedCRS\",\n", 495 | " \"name\": \"WGS 84 / UTM zone 29N\",\n", 496 | " \"base_crs\": {\n", 497 | " \"name\": \"WGS 84\",\n", 498 | " \"datum\": {\n", 499 | " \"type\": \"GeodeticReferenceFrame\",\n", 500 | " \"name\": \"World Geodetic System 1984\",\n", 501 | " \"ellipsoid\": {\n", 502 | " \"name\": \"WGS 84\",\n", 503 | " \"semi_major_axis\": 6378137,\n", 504 | " \"inverse_flattening\": 298.257223563\n", 505 | " }\n", 506 | " },\n", 507 | " \"coordinate_system\": {\n", 508 | " \"subtype\": \"ellipsoidal\",\n", 509 | " \"axis\": [\n", 510 | " {\n", 511 | " \"name\": \"Geodetic latitude\",\n", 512 | " \"abbreviation\": \"Lat\",\n", 513 | " \"direction\": \"north\",\n", 514 | " \"unit\": \"degree\"\n", 515 | " },\n", 516 | " {\n", 517 | " \"name\": \"Geodetic longitude\",\n", 518 | " \"abbreviation\": \"Lon\",\n", 519 | " \"direction\": \"east\",\n", 520 | " \"unit\": \"degree\"\n", 521 | " }\n", 522 | " ]\n", 523 | " },\n", 524 | " \"id\": {\n", 525 | " \"authority\": \"EPSG\",\n", 526 | " \"code\": 4326\n", 527 | " }\n", 528 | " },\n", 529 | " \"conversion\": {\n", 530 | " \"name\": \"UTM zone 29N\",\n", 531 | " \"method\": {\n", 532 | " \"name\": \"Transverse Mercator\",\n", 533 | " \"id\": {\n", 534 | " \"authority\": \"EPSG\",\n", 535 | " \"code\": 9807\n", 536 | " }\n", 537 | " },\n", 538 | " \"parameters\": [\n", 539 | " {\n", 540 | " \"name\": \"Latitude of natural origin\",\n", 541 | " \"value\": 0,\n", 542 | " \"unit\": \"degree\",\n", 543 | " \"id\": {\n", 544 | " \"authority\": \"EPSG\",\n", 545 | " \"code\": 8801\n", 546 | " }\n", 547 | " },\n", 548 | " {\n", 549 | " \"name\": \"Longitude of natural origin\",\n", 550 | " \"value\": -9,\n", 551 | " \"unit\": \"degree\",\n", 552 | " \"id\": {\n", 553 | " \"authority\": \"EPSG\",\n", 554 | " \"code\": 8802\n", 555 | " }\n", 556 | " },\n", 557 | " {\n", 558 | " \"name\": \"Scale factor at natural origin\",\n", 559 | " \"value\": 0.9996,\n", 560 | " \"unit\": \"unity\",\n", 561 | " \"id\": {\n", 562 | " \"authority\": \"EPSG\",\n", 563 | " \"code\": 8805\n", 564 | " }\n", 565 | " },\n", 566 | " {\n", 567 | " \"name\": \"False easting\",\n", 568 | " \"value\": 500000,\n", 569 | " \"unit\": \"metre\",\n", 570 | " \"id\": {\n", 571 | " \"authority\": \"EPSG\",\n", 572 | " \"code\": 8806\n", 573 | " }\n", 574 | " },\n", 575 | " {\n", 576 | " \"name\": \"False northing\",\n", 577 | " \"value\": 0,\n", 578 | " \"unit\": \"metre\",\n", 579 | " \"id\": {\n", 580 | " \"authority\": \"EPSG\",\n", 581 | " \"code\": 8807\n", 582 | " }\n", 583 | " }\n", 584 | " ]\n", 585 | " },\n", 586 | " \"coordinate_system\": {\n", 587 | " \"subtype\": \"Cartesian\",\n", 588 | " \"axis\": [\n", 589 | " {\n", 590 | " \"name\": \"Easting\",\n", 591 | " \"abbreviation\": \"\",\n", 592 | " \"direction\": \"east\",\n", 593 | " \"unit\": \"metre\"\n", 594 | " },\n", 595 | " {\n", 596 | " \"name\": \"Northing\",\n", 597 | " \"abbreviation\": \"\",\n", 598 | " \"direction\": \"north\",\n", 599 | " \"unit\": \"metre\"\n", 600 | " }\n", 601 | " ]\n", 602 | " },\n", 603 | " \"id\": {\n", 604 | " \"authority\": \"EPSG\",\n", 605 | " \"code\": 32629\n", 606 | " }\n", 607 | " },\n", 608 | " \"proj:wkt2\": \"PROJCS[\\\"WGS 84 / UTM zone 29N\\\",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.0174532925199433,AUTHORITY[\\\"EPSG\\\",\\\"9122\\\"]],AUTHORITY[\\\"EPSG\\\",\\\"4326\\\"]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"latitude_of_origin\\\",0],PARAMETER[\\\"central_meridian\\\",-9],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"false_easting\\\",500000],PARAMETER[\\\"false_northing\\\",0],UNIT[\\\"metre\\\",1,AUTHORITY[\\\"EPSG\\\",\\\"9001\\\"]],AXIS[\\\"Easting\\\",EAST],AXIS[\\\"Northing\\\",NORTH],AUTHORITY[\\\"EPSG\\\",\\\"32629\\\"]]\",\n", 609 | " \"raster:bands\": [\n", 610 | " {\n", 611 | " \"data_type\": \"uint16\",\n", 612 | " \"scale\": 1.0,\n", 613 | " \"offset\": 0.0,\n", 614 | " \"sampling\": \"area\",\n", 615 | " \"nodata\": 0.0,\n", 616 | " \"statistics\": {\n", 617 | " \"mean\": 3704.1896078646055,\n", 618 | " \"minimum\": 1149,\n", 619 | " \"maximum\": 8069,\n", 620 | " \"stddev\": 425.3794265417571,\n", 621 | " \"valid_percent\": 100.0\n", 622 | " },\n", 623 | " \"histogram\": {\n", 624 | " \"count\": 11,\n", 625 | " \"min\": 1149.0,\n", 626 | " \"max\": 8069.0,\n", 627 | " \"buckets\": [\n", 628 | " 1193,\n", 629 | " 2792,\n", 630 | " 21120,\n", 631 | " 196053,\n", 632 | " 75547,\n", 633 | " 3758,\n", 634 | " 519,\n", 635 | " 212,\n", 636 | " 151,\n", 637 | " 56\n", 638 | " ]\n", 639 | " }\n", 640 | " }\n", 641 | " ],\n", 642 | " \"eo:bands\": [\n", 643 | " {\n", 644 | " \"name\": \"b1\",\n", 645 | " \"description\": \"gray\"\n", 646 | " }\n", 647 | " ]\n", 648 | " },\n", 649 | " \"B03\": {\n", 650 | " \"href\": \"./data/B03.tif\",\n", 651 | " \"type\": \"image/tiff; application=geotiff; profile=cloud-optimized\",\n", 652 | " \"proj:epsg\": 32629,\n", 653 | " \"proj:geometry\": {\n", 654 | " \"type\": \"Polygon\",\n", 655 | " \"coordinates\": [\n", 656 | " [\n", 657 | " [\n", 658 | " 199980.0,\n", 659 | " 2690220.0\n", 660 | " ],\n", 661 | " [\n", 662 | " 309780.0,\n", 663 | " 2690220.0\n", 664 | " ],\n", 665 | " [\n", 666 | " 309780.0,\n", 667 | " 2800020.0\n", 668 | " ],\n", 669 | " [\n", 670 | " 199980.0,\n", 671 | " 2800020.0\n", 672 | " ],\n", 673 | " [\n", 674 | " 199980.0,\n", 675 | " 2690220.0\n", 676 | " ]\n", 677 | " ]\n", 678 | " ]\n", 679 | " },\n", 680 | " \"proj:bbox\": [\n", 681 | " 199980.0,\n", 682 | " 2690220.0,\n", 683 | " 309780.0,\n", 684 | " 2800020.0\n", 685 | " ],\n", 686 | " \"proj:shape\": [\n", 687 | " 549,\n", 688 | " 549\n", 689 | " ],\n", 690 | " \"proj:transform\": [\n", 691 | " 200.0,\n", 692 | " 0.0,\n", 693 | " 199980.0,\n", 694 | " 0.0,\n", 695 | " -200.0,\n", 696 | " 2800020.0,\n", 697 | " 0.0,\n", 698 | " 0.0,\n", 699 | " 1.0\n", 700 | " ],\n", 701 | " \"proj:projjson\": {\n", 702 | " \"$schema\": \"https://proj.org/schemas/v0.4/projjson.schema.json\",\n", 703 | " \"type\": \"ProjectedCRS\",\n", 704 | " \"name\": \"WGS 84 / UTM zone 29N\",\n", 705 | " \"base_crs\": {\n", 706 | " \"name\": \"WGS 84\",\n", 707 | " \"datum\": {\n", 708 | " \"type\": \"GeodeticReferenceFrame\",\n", 709 | " \"name\": \"World Geodetic System 1984\",\n", 710 | " \"ellipsoid\": {\n", 711 | " \"name\": \"WGS 84\",\n", 712 | " \"semi_major_axis\": 6378137,\n", 713 | " \"inverse_flattening\": 298.257223563\n", 714 | " }\n", 715 | " },\n", 716 | " \"coordinate_system\": {\n", 717 | " \"subtype\": \"ellipsoidal\",\n", 718 | " \"axis\": [\n", 719 | " {\n", 720 | " \"name\": \"Geodetic latitude\",\n", 721 | " \"abbreviation\": \"Lat\",\n", 722 | " \"direction\": \"north\",\n", 723 | " \"unit\": \"degree\"\n", 724 | " },\n", 725 | " {\n", 726 | " \"name\": \"Geodetic longitude\",\n", 727 | " \"abbreviation\": \"Lon\",\n", 728 | " \"direction\": \"east\",\n", 729 | " \"unit\": \"degree\"\n", 730 | " }\n", 731 | " ]\n", 732 | " },\n", 733 | " \"id\": {\n", 734 | " \"authority\": \"EPSG\",\n", 735 | " \"code\": 4326\n", 736 | " }\n", 737 | " },\n", 738 | " \"conversion\": {\n", 739 | " \"name\": \"UTM zone 29N\",\n", 740 | " \"method\": {\n", 741 | " \"name\": \"Transverse Mercator\",\n", 742 | " \"id\": {\n", 743 | " \"authority\": \"EPSG\",\n", 744 | " \"code\": 9807\n", 745 | " }\n", 746 | " },\n", 747 | " \"parameters\": [\n", 748 | " {\n", 749 | " \"name\": \"Latitude of natural origin\",\n", 750 | " \"value\": 0,\n", 751 | " \"unit\": \"degree\",\n", 752 | " \"id\": {\n", 753 | " \"authority\": \"EPSG\",\n", 754 | " \"code\": 8801\n", 755 | " }\n", 756 | " },\n", 757 | " {\n", 758 | " \"name\": \"Longitude of natural origin\",\n", 759 | " \"value\": -9,\n", 760 | " \"unit\": \"degree\",\n", 761 | " \"id\": {\n", 762 | " \"authority\": \"EPSG\",\n", 763 | " \"code\": 8802\n", 764 | " }\n", 765 | " },\n", 766 | " {\n", 767 | " \"name\": \"Scale factor at natural origin\",\n", 768 | " \"value\": 0.9996,\n", 769 | " \"unit\": \"unity\",\n", 770 | " \"id\": {\n", 771 | " \"authority\": \"EPSG\",\n", 772 | " \"code\": 8805\n", 773 | " }\n", 774 | " },\n", 775 | " {\n", 776 | " \"name\": \"False easting\",\n", 777 | " \"value\": 500000,\n", 778 | " \"unit\": \"metre\",\n", 779 | " \"id\": {\n", 780 | " \"authority\": \"EPSG\",\n", 781 | " \"code\": 8806\n", 782 | " }\n", 783 | " },\n", 784 | " {\n", 785 | " \"name\": \"False northing\",\n", 786 | " \"value\": 0,\n", 787 | " \"unit\": \"metre\",\n", 788 | " \"id\": {\n", 789 | " \"authority\": \"EPSG\",\n", 790 | " \"code\": 8807\n", 791 | " }\n", 792 | " }\n", 793 | " ]\n", 794 | " },\n", 795 | " \"coordinate_system\": {\n", 796 | " \"subtype\": \"Cartesian\",\n", 797 | " \"axis\": [\n", 798 | " {\n", 799 | " \"name\": \"Easting\",\n", 800 | " \"abbreviation\": \"\",\n", 801 | " \"direction\": \"east\",\n", 802 | " \"unit\": \"metre\"\n", 803 | " },\n", 804 | " {\n", 805 | " \"name\": \"Northing\",\n", 806 | " \"abbreviation\": \"\",\n", 807 | " \"direction\": \"north\",\n", 808 | " \"unit\": \"metre\"\n", 809 | " }\n", 810 | " ]\n", 811 | " },\n", 812 | " \"id\": {\n", 813 | " \"authority\": \"EPSG\",\n", 814 | " \"code\": 32629\n", 815 | " }\n", 816 | " },\n", 817 | " \"proj:wkt2\": \"PROJCS[\\\"WGS 84 / UTM zone 29N\\\",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.0174532925199433,AUTHORITY[\\\"EPSG\\\",\\\"9122\\\"]],AUTHORITY[\\\"EPSG\\\",\\\"4326\\\"]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"latitude_of_origin\\\",0],PARAMETER[\\\"central_meridian\\\",-9],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"false_easting\\\",500000],PARAMETER[\\\"false_northing\\\",0],UNIT[\\\"metre\\\",1,AUTHORITY[\\\"EPSG\\\",\\\"9001\\\"]],AXIS[\\\"Easting\\\",EAST],AXIS[\\\"Northing\\\",NORTH],AUTHORITY[\\\"EPSG\\\",\\\"32629\\\"]]\",\n", 818 | " \"raster:bands\": [\n", 819 | " {\n", 820 | " \"data_type\": \"uint16\",\n", 821 | " \"scale\": 1.0,\n", 822 | " \"offset\": 0.0,\n", 823 | " \"sampling\": \"area\",\n", 824 | " \"nodata\": 0.0,\n", 825 | " \"statistics\": {\n", 826 | " \"mean\": 3756.1745382397535,\n", 827 | " \"minimum\": 1151,\n", 828 | " \"maximum\": 7859,\n", 829 | " \"stddev\": 422.8205914467619,\n", 830 | " \"valid_percent\": 100.0\n", 831 | " },\n", 832 | " \"histogram\": {\n", 833 | " \"count\": 11,\n", 834 | " \"min\": 1151.0,\n", 835 | " \"max\": 7859.0,\n", 836 | " \"buckets\": [\n", 837 | " 1033,\n", 838 | " 2515,\n", 839 | " 13318,\n", 840 | " 159276,\n", 841 | " 117130,\n", 842 | " 6978,\n", 843 | " 687,\n", 844 | " 244,\n", 845 | " 159,\n", 846 | " 61\n", 847 | " ]\n", 848 | " }\n", 849 | " }\n", 850 | " ],\n", 851 | " \"eo:bands\": [\n", 852 | " {\n", 853 | " \"name\": \"b1\",\n", 854 | " \"description\": \"gray\"\n", 855 | " }\n", 856 | " ]\n", 857 | " },\n", 858 | " \"B04\": {\n", 859 | " \"href\": \"./data/B04.tif\",\n", 860 | " \"type\": \"image/tiff; application=geotiff; profile=cloud-optimized\",\n", 861 | " \"proj:epsg\": 32629,\n", 862 | " \"proj:geometry\": {\n", 863 | " \"type\": \"Polygon\",\n", 864 | " \"coordinates\": [\n", 865 | " [\n", 866 | " [\n", 867 | " 199980.0,\n", 868 | " 2690220.0\n", 869 | " ],\n", 870 | " [\n", 871 | " 309780.0,\n", 872 | " 2690220.0\n", 873 | " ],\n", 874 | " [\n", 875 | " 309780.0,\n", 876 | " 2800020.0\n", 877 | " ],\n", 878 | " [\n", 879 | " 199980.0,\n", 880 | " 2800020.0\n", 881 | " ],\n", 882 | " [\n", 883 | " 199980.0,\n", 884 | " 2690220.0\n", 885 | " ]\n", 886 | " ]\n", 887 | " ]\n", 888 | " },\n", 889 | " \"proj:bbox\": [\n", 890 | " 199980.0,\n", 891 | " 2690220.0,\n", 892 | " 309780.0,\n", 893 | " 2800020.0\n", 894 | " ],\n", 895 | " \"proj:shape\": [\n", 896 | " 549,\n", 897 | " 549\n", 898 | " ],\n", 899 | " \"proj:transform\": [\n", 900 | " 200.0,\n", 901 | " 0.0,\n", 902 | " 199980.0,\n", 903 | " 0.0,\n", 904 | " -200.0,\n", 905 | " 2800020.0,\n", 906 | " 0.0,\n", 907 | " 0.0,\n", 908 | " 1.0\n", 909 | " ],\n", 910 | " \"proj:projjson\": {\n", 911 | " \"$schema\": \"https://proj.org/schemas/v0.4/projjson.schema.json\",\n", 912 | " \"type\": \"ProjectedCRS\",\n", 913 | " \"name\": \"WGS 84 / UTM zone 29N\",\n", 914 | " \"base_crs\": {\n", 915 | " \"name\": \"WGS 84\",\n", 916 | " \"datum\": {\n", 917 | " \"type\": \"GeodeticReferenceFrame\",\n", 918 | " \"name\": \"World Geodetic System 1984\",\n", 919 | " \"ellipsoid\": {\n", 920 | " \"name\": \"WGS 84\",\n", 921 | " \"semi_major_axis\": 6378137,\n", 922 | " \"inverse_flattening\": 298.257223563\n", 923 | " }\n", 924 | " },\n", 925 | " \"coordinate_system\": {\n", 926 | " \"subtype\": \"ellipsoidal\",\n", 927 | " \"axis\": [\n", 928 | " {\n", 929 | " \"name\": \"Geodetic latitude\",\n", 930 | " \"abbreviation\": \"Lat\",\n", 931 | " \"direction\": \"north\",\n", 932 | " \"unit\": \"degree\"\n", 933 | " },\n", 934 | " {\n", 935 | " \"name\": \"Geodetic longitude\",\n", 936 | " \"abbreviation\": \"Lon\",\n", 937 | " \"direction\": \"east\",\n", 938 | " \"unit\": \"degree\"\n", 939 | " }\n", 940 | " ]\n", 941 | " },\n", 942 | " \"id\": {\n", 943 | " \"authority\": \"EPSG\",\n", 944 | " \"code\": 4326\n", 945 | " }\n", 946 | " },\n", 947 | " \"conversion\": {\n", 948 | " \"name\": \"UTM zone 29N\",\n", 949 | " \"method\": {\n", 950 | " \"name\": \"Transverse Mercator\",\n", 951 | " \"id\": {\n", 952 | " \"authority\": \"EPSG\",\n", 953 | " \"code\": 9807\n", 954 | " }\n", 955 | " },\n", 956 | " \"parameters\": [\n", 957 | " {\n", 958 | " \"name\": \"Latitude of natural origin\",\n", 959 | " \"value\": 0,\n", 960 | " \"unit\": \"degree\",\n", 961 | " \"id\": {\n", 962 | " \"authority\": \"EPSG\",\n", 963 | " \"code\": 8801\n", 964 | " }\n", 965 | " },\n", 966 | " {\n", 967 | " \"name\": \"Longitude of natural origin\",\n", 968 | " \"value\": -9,\n", 969 | " \"unit\": \"degree\",\n", 970 | " \"id\": {\n", 971 | " \"authority\": \"EPSG\",\n", 972 | " \"code\": 8802\n", 973 | " }\n", 974 | " },\n", 975 | " {\n", 976 | " \"name\": \"Scale factor at natural origin\",\n", 977 | " \"value\": 0.9996,\n", 978 | " \"unit\": \"unity\",\n", 979 | " \"id\": {\n", 980 | " \"authority\": \"EPSG\",\n", 981 | " \"code\": 8805\n", 982 | " }\n", 983 | " },\n", 984 | " {\n", 985 | " \"name\": \"False easting\",\n", 986 | " \"value\": 500000,\n", 987 | " \"unit\": \"metre\",\n", 988 | " \"id\": {\n", 989 | " \"authority\": \"EPSG\",\n", 990 | " \"code\": 8806\n", 991 | " }\n", 992 | " },\n", 993 | " {\n", 994 | " \"name\": \"False northing\",\n", 995 | " \"value\": 0,\n", 996 | " \"unit\": \"metre\",\n", 997 | " \"id\": {\n", 998 | " \"authority\": \"EPSG\",\n", 999 | " \"code\": 8807\n", 1000 | " }\n", 1001 | " }\n", 1002 | " ]\n", 1003 | " },\n", 1004 | " \"coordinate_system\": {\n", 1005 | " \"subtype\": \"Cartesian\",\n", 1006 | " \"axis\": [\n", 1007 | " {\n", 1008 | " \"name\": \"Easting\",\n", 1009 | " \"abbreviation\": \"\",\n", 1010 | " \"direction\": \"east\",\n", 1011 | " \"unit\": \"metre\"\n", 1012 | " },\n", 1013 | " {\n", 1014 | " \"name\": \"Northing\",\n", 1015 | " \"abbreviation\": \"\",\n", 1016 | " \"direction\": \"north\",\n", 1017 | " \"unit\": \"metre\"\n", 1018 | " }\n", 1019 | " ]\n", 1020 | " },\n", 1021 | " \"id\": {\n", 1022 | " \"authority\": \"EPSG\",\n", 1023 | " \"code\": 32629\n", 1024 | " }\n", 1025 | " },\n", 1026 | " \"proj:wkt2\": \"PROJCS[\\\"WGS 84 / UTM zone 29N\\\",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.0174532925199433,AUTHORITY[\\\"EPSG\\\",\\\"9122\\\"]],AUTHORITY[\\\"EPSG\\\",\\\"4326\\\"]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"latitude_of_origin\\\",0],PARAMETER[\\\"central_meridian\\\",-9],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"false_easting\\\",500000],PARAMETER[\\\"false_northing\\\",0],UNIT[\\\"metre\\\",1,AUTHORITY[\\\"EPSG\\\",\\\"9001\\\"]],AXIS[\\\"Easting\\\",EAST],AXIS[\\\"Northing\\\",NORTH],AUTHORITY[\\\"EPSG\\\",\\\"32629\\\"]]\",\n", 1027 | " \"raster:bands\": [\n", 1028 | " {\n", 1029 | " \"data_type\": \"uint16\",\n", 1030 | " \"scale\": 1.0,\n", 1031 | " \"offset\": 0.0,\n", 1032 | " \"sampling\": \"area\",\n", 1033 | " \"nodata\": 0.0,\n", 1034 | " \"statistics\": {\n", 1035 | " \"mean\": 3815.5430970700163,\n", 1036 | " \"minimum\": 1164,\n", 1037 | " \"maximum\": 7793,\n", 1038 | " \"stddev\": 425.60416042616544,\n", 1039 | " \"valid_percent\": 100.0\n", 1040 | " },\n", 1041 | " \"histogram\": {\n", 1042 | " \"count\": 11,\n", 1043 | " \"min\": 1164.0,\n", 1044 | " \"max\": 7793.0,\n", 1045 | " \"buckets\": [\n", 1046 | " 915,\n", 1047 | " 2315,\n", 1048 | " 10676,\n", 1049 | " 132912,\n", 1050 | " 142978,\n", 1051 | " 10115,\n", 1052 | " 997,\n", 1053 | " 266,\n", 1054 | " 165,\n", 1055 | " 62\n", 1056 | " ]\n", 1057 | " }\n", 1058 | " }\n", 1059 | " ],\n", 1060 | " \"eo:bands\": [\n", 1061 | " {\n", 1062 | " \"name\": \"b1\",\n", 1063 | " \"description\": \"gray\"\n", 1064 | " }\n", 1065 | " ]\n", 1066 | " },\n", 1067 | " \"B05\": {\n", 1068 | " \"href\": \"./data/B05.tif\",\n", 1069 | " \"type\": \"image/tiff; application=geotiff; profile=cloud-optimized\",\n", 1070 | " \"proj:epsg\": 32629,\n", 1071 | " \"proj:geometry\": {\n", 1072 | " \"type\": \"Polygon\",\n", 1073 | " \"coordinates\": [\n", 1074 | " [\n", 1075 | " [\n", 1076 | " 199980.0,\n", 1077 | " 2690220.0\n", 1078 | " ],\n", 1079 | " [\n", 1080 | " 309780.0,\n", 1081 | " 2690220.0\n", 1082 | " ],\n", 1083 | " [\n", 1084 | " 309780.0,\n", 1085 | " 2800020.0\n", 1086 | " ],\n", 1087 | " [\n", 1088 | " 199980.0,\n", 1089 | " 2800020.0\n", 1090 | " ],\n", 1091 | " [\n", 1092 | " 199980.0,\n", 1093 | " 2690220.0\n", 1094 | " ]\n", 1095 | " ]\n", 1096 | " ]\n", 1097 | " },\n", 1098 | " \"proj:bbox\": [\n", 1099 | " 199980.0,\n", 1100 | " 2690220.0,\n", 1101 | " 309780.0,\n", 1102 | " 2800020.0\n", 1103 | " ],\n", 1104 | " \"proj:shape\": [\n", 1105 | " 549,\n", 1106 | " 549\n", 1107 | " ],\n", 1108 | " \"proj:transform\": [\n", 1109 | " 200.0,\n", 1110 | " 0.0,\n", 1111 | " 199980.0,\n", 1112 | " 0.0,\n", 1113 | " -200.0,\n", 1114 | " 2800020.0,\n", 1115 | " 0.0,\n", 1116 | " 0.0,\n", 1117 | " 1.0\n", 1118 | " ],\n", 1119 | " \"proj:projjson\": {\n", 1120 | " \"$schema\": \"https://proj.org/schemas/v0.4/projjson.schema.json\",\n", 1121 | " \"type\": \"ProjectedCRS\",\n", 1122 | " \"name\": \"WGS 84 / UTM zone 29N\",\n", 1123 | " \"base_crs\": {\n", 1124 | " \"name\": \"WGS 84\",\n", 1125 | " \"datum\": {\n", 1126 | " \"type\": \"GeodeticReferenceFrame\",\n", 1127 | " \"name\": \"World Geodetic System 1984\",\n", 1128 | " \"ellipsoid\": {\n", 1129 | " \"name\": \"WGS 84\",\n", 1130 | " \"semi_major_axis\": 6378137,\n", 1131 | " \"inverse_flattening\": 298.257223563\n", 1132 | " }\n", 1133 | " },\n", 1134 | " \"coordinate_system\": {\n", 1135 | " \"subtype\": \"ellipsoidal\",\n", 1136 | " \"axis\": [\n", 1137 | " {\n", 1138 | " \"name\": \"Geodetic latitude\",\n", 1139 | " \"abbreviation\": \"Lat\",\n", 1140 | " \"direction\": \"north\",\n", 1141 | " \"unit\": \"degree\"\n", 1142 | " },\n", 1143 | " {\n", 1144 | " \"name\": \"Geodetic longitude\",\n", 1145 | " \"abbreviation\": \"Lon\",\n", 1146 | " \"direction\": \"east\",\n", 1147 | " \"unit\": \"degree\"\n", 1148 | " }\n", 1149 | " ]\n", 1150 | " },\n", 1151 | " \"id\": {\n", 1152 | " \"authority\": \"EPSG\",\n", 1153 | " \"code\": 4326\n", 1154 | " }\n", 1155 | " },\n", 1156 | " \"conversion\": {\n", 1157 | " \"name\": \"UTM zone 29N\",\n", 1158 | " \"method\": {\n", 1159 | " \"name\": \"Transverse Mercator\",\n", 1160 | " \"id\": {\n", 1161 | " \"authority\": \"EPSG\",\n", 1162 | " \"code\": 9807\n", 1163 | " }\n", 1164 | " },\n", 1165 | " \"parameters\": [\n", 1166 | " {\n", 1167 | " \"name\": \"Latitude of natural origin\",\n", 1168 | " \"value\": 0,\n", 1169 | " \"unit\": \"degree\",\n", 1170 | " \"id\": {\n", 1171 | " \"authority\": \"EPSG\",\n", 1172 | " \"code\": 8801\n", 1173 | " }\n", 1174 | " },\n", 1175 | " {\n", 1176 | " \"name\": \"Longitude of natural origin\",\n", 1177 | " \"value\": -9,\n", 1178 | " \"unit\": \"degree\",\n", 1179 | " \"id\": {\n", 1180 | " \"authority\": \"EPSG\",\n", 1181 | " \"code\": 8802\n", 1182 | " }\n", 1183 | " },\n", 1184 | " {\n", 1185 | " \"name\": \"Scale factor at natural origin\",\n", 1186 | " \"value\": 0.9996,\n", 1187 | " \"unit\": \"unity\",\n", 1188 | " \"id\": {\n", 1189 | " \"authority\": \"EPSG\",\n", 1190 | " \"code\": 8805\n", 1191 | " }\n", 1192 | " },\n", 1193 | " {\n", 1194 | " \"name\": \"False easting\",\n", 1195 | " \"value\": 500000,\n", 1196 | " \"unit\": \"metre\",\n", 1197 | " \"id\": {\n", 1198 | " \"authority\": \"EPSG\",\n", 1199 | " \"code\": 8806\n", 1200 | " }\n", 1201 | " },\n", 1202 | " {\n", 1203 | " \"name\": \"False northing\",\n", 1204 | " \"value\": 0,\n", 1205 | " \"unit\": \"metre\",\n", 1206 | " \"id\": {\n", 1207 | " \"authority\": \"EPSG\",\n", 1208 | " \"code\": 8807\n", 1209 | " }\n", 1210 | " }\n", 1211 | " ]\n", 1212 | " },\n", 1213 | " \"coordinate_system\": {\n", 1214 | " \"subtype\": \"Cartesian\",\n", 1215 | " \"axis\": [\n", 1216 | " {\n", 1217 | " \"name\": \"Easting\",\n", 1218 | " \"abbreviation\": \"\",\n", 1219 | " \"direction\": \"east\",\n", 1220 | " \"unit\": \"metre\"\n", 1221 | " },\n", 1222 | " {\n", 1223 | " \"name\": \"Northing\",\n", 1224 | " \"abbreviation\": \"\",\n", 1225 | " \"direction\": \"north\",\n", 1226 | " \"unit\": \"metre\"\n", 1227 | " }\n", 1228 | " ]\n", 1229 | " },\n", 1230 | " \"id\": {\n", 1231 | " \"authority\": \"EPSG\",\n", 1232 | " \"code\": 32629\n", 1233 | " }\n", 1234 | " },\n", 1235 | " \"proj:wkt2\": \"PROJCS[\\\"WGS 84 / UTM zone 29N\\\",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.0174532925199433,AUTHORITY[\\\"EPSG\\\",\\\"9122\\\"]],AUTHORITY[\\\"EPSG\\\",\\\"4326\\\"]],PROJECTION[\\\"Transverse_Mercator\\\"],PARAMETER[\\\"latitude_of_origin\\\",0],PARAMETER[\\\"central_meridian\\\",-9],PARAMETER[\\\"scale_factor\\\",0.9996],PARAMETER[\\\"false_easting\\\",500000],PARAMETER[\\\"false_northing\\\",0],UNIT[\\\"metre\\\",1,AUTHORITY[\\\"EPSG\\\",\\\"9001\\\"]],AXIS[\\\"Easting\\\",EAST],AXIS[\\\"Northing\\\",NORTH],AUTHORITY[\\\"EPSG\\\",\\\"32629\\\"]]\",\n", 1236 | " \"raster:bands\": [\n", 1237 | " {\n", 1238 | " \"data_type\": \"uint16\",\n", 1239 | " \"scale\": 1.0,\n", 1240 | " \"offset\": 0.0,\n", 1241 | " \"sampling\": \"area\",\n", 1242 | " \"nodata\": 0.0,\n", 1243 | " \"statistics\": {\n", 1244 | " \"mean\": 3825.699888188825,\n", 1245 | " \"minimum\": 1140,\n", 1246 | " \"maximum\": 7808,\n", 1247 | " \"stddev\": 431.14276343286804,\n", 1248 | " \"valid_percent\": 100.0\n", 1249 | " },\n", 1250 | " \"histogram\": {\n", 1251 | " \"count\": 11,\n", 1252 | " \"min\": 1140.0,\n", 1253 | " \"max\": 7808.0,\n", 1254 | " \"buckets\": [\n", 1255 | " 819,\n", 1256 | " 2294,\n", 1257 | " 10344,\n", 1258 | " 127145,\n", 1259 | " 147857,\n", 1260 | " 11167,\n", 1261 | " 1259,\n", 1262 | " 287,\n", 1263 | " 169,\n", 1264 | " 60\n", 1265 | " ]\n", 1266 | " }\n", 1267 | " }\n", 1268 | " ],\n", 1269 | " \"eo:bands\": [\n", 1270 | " {\n", 1271 | " \"name\": \"b1\",\n", 1272 | " \"description\": \"gray\"\n", 1273 | " }\n", 1274 | " ]\n", 1275 | " }\n", 1276 | " },\n", 1277 | " \"bbox\": [\n", 1278 | " -11.979244865430262,\n", 1279 | " 24.296321392464336,\n", 1280 | " -10.874546803397616,\n", 1281 | " 25.304623891542274\n", 1282 | " ],\n", 1283 | " \"stac_extensions\": [\n", 1284 | " \"https://stac-extensions.github.io/projection/v1.1.0/schema.json\",\n", 1285 | " \"https://stac-extensions.github.io/raster/v1.1.0/schema.json\",\n", 1286 | " \"https://stac-extensions.github.io/eo/v1.1.0/schema.json\"\n", 1287 | " ]\n", 1288 | "}\n" 1289 | ] 1290 | } 1291 | ], 1292 | "source": [ 1293 | "import json\n", 1294 | "\n", 1295 | "print(json.dumps(item.to_dict(), indent=4))" 1296 | ] 1297 | }, 1298 | { 1299 | "cell_type": "code", 1300 | "execution_count": null, 1301 | "id": "8e9a65a0", 1302 | "metadata": {}, 1303 | "outputs": [], 1304 | "source": [] 1305 | } 1306 | ], 1307 | "metadata": { 1308 | "kernelspec": { 1309 | "display_name": "Python 3 (ipykernel)", 1310 | "language": "python", 1311 | "name": "python3" 1312 | }, 1313 | "language_info": { 1314 | "codemirror_mode": { 1315 | "name": "ipython", 1316 | "version": 3 1317 | }, 1318 | "file_extension": ".py", 1319 | "mimetype": "text/x-python", 1320 | "name": "python", 1321 | "nbconvert_exporter": "python", 1322 | "pygments_lexer": "ipython3", 1323 | "version": "3.9.18" 1324 | } 1325 | }, 1326 | "nbformat": 4, 1327 | "nbformat_minor": 5 1328 | } 1329 | -------------------------------------------------------------------------------- /docs/docs/examples/data/B01.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/docs/docs/examples/data/B01.tif -------------------------------------------------------------------------------- /docs/docs/examples/data/B02.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/docs/docs/examples/data/B02.tif -------------------------------------------------------------------------------- /docs/docs/examples/data/B03.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/docs/docs/examples/data/B03.tif -------------------------------------------------------------------------------- /docs/docs/examples/data/B04.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/docs/docs/examples/data/B04.tif -------------------------------------------------------------------------------- /docs/docs/examples/data/B05.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/docs/docs/examples/data/B05.tif -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /docs/docs/intro.md: -------------------------------------------------------------------------------- 1 | 2 | `rio-stac` can be used either from the command line as a rasterio plugin (`rio stac`) or from your own script. 3 | 4 | For more information about the `Item` specification, please see https://github.com/radiantearth/stac-spec/blob/master/item-spec/item-spec.md 5 | 6 | # CLI 7 | 8 | ``` 9 | $ rio stac --help 10 | 11 | Usage: rio stac [OPTIONS] INPUT 12 | 13 | Rasterio STAC plugin: Create a STAC Item for raster dataset. 14 | 15 | Options: 16 | -d, --datetime TEXT The date and time of the assets, in UTC (e.g 2020-01-01, 2020-01-01T01:01:01). 17 | -e, --extension TEXT STAC extensions the Item implements (default is set to ["proj"]). Multiple allowed (e.g. `-e extensionUrl1 -e extensionUrl2`). 18 | -c, --collection TEXT The Collection ID that this item belongs to. 19 | --collection-url TEXT Link to the STAC Collection. 20 | -p, --property NAME=VALUE Additional property to add (e.g `-p myprops=1`). Multiple allowed. 21 | --id TEXT Item id. 22 | -n, --asset-name TEXT Asset name. 23 | --asset-href TEXT Overwrite asset href. 24 | --asset-mediatype [COG|GEOJSON|GEOPACKAGE|GEOTIFF|HDF|HDF5|JPEG|JPEG2000|JSON|PNG|TEXT|TIFF|XML|auto] Asset media-type. 25 | --with-proj / --without-proj Add the 'projection' extension and properties (default to True). 26 | --with-raster / --without-raster Add the 'raster' extension and properties (default to True). 27 | --with-eo / --without-eo Add the 'eo' extension and properties (default to True). 28 | --max-raster-size INTEGER Limit array size from which to get the raster statistics (default to 1024). 29 | --densify-geom INTEGER Densifies the number of points on each edges of the polygon geometry to account for non-linear transformation. 30 | --geom-precision INTEGER Round geometry coordinates to this number of decimal. By default, coordinates will not be rounded 31 | -o, --output PATH Output file name 32 | --config NAME=VALUE GDAL configuration options. 33 | --help Show this message and exit. 34 | ``` 35 | 36 | ### How To 37 | 38 | The CLI can be run as is, just by passing a `source` raster data. You can also use options to customize the output STAC item: 39 | 40 | - **datetime** (-d, --datetime) 41 | 42 | By design, all STAC items must have a datetime in their properties. By default the CLI will set the time to the actual UTC Time or use `ACQUISITIONDATETIME` defined in dataset metadata (see [GDAL Raster data model](https://gdal.org/user/raster_data_model.html#imagery-domain-remote-sensing)). The CLI will accept any format supported by [`dateparser`](https://dateparser.readthedocs.io/en/latest/). 43 | 44 | You can also define `start_datetime` and `end_datetime` by using `--datetime {start}/{end}` notation. 45 | 46 | Note: `GDAL Raster data model` metadata are stored in an external file so you may want to set `GDAL_DISABLE_READDIR_ON_OPEN=FALSE` environment variable to allow GDAL to fetch the sidecar files. 47 | 48 | - **extension** (-e, --extension) 49 | 50 | STAC Item can have [extensions](https://github.com/radiantearth/stac-spec/tree/master/extensions) which indicates that the item has additional properies (e.g proj information). This option can be set multiple times. 51 | 52 | You can pass the extension option multiple times: `-e extension1 -e extension2`. 53 | 54 | - **projection extension** (--with-proj / --without-proj) 55 | 56 | By default the `projection` extension and properties will be added to the item. 57 | 58 | link: https://github.com/stac-extensions/projection/ 59 | 60 | ```json 61 | { 62 | "proj:epsg": 3857, 63 | "proj:geometry": {"type": "Polygon", "coordinates": [...]}, 64 | "proj:bbox": [...], 65 | "proj:shape": [8192, 8192], 66 | "proj:transform": [...], 67 | "datetime": "2021-03-19T02:27:33.266356Z" 68 | } 69 | ``` 70 | 71 | You can pass `--without-proj` to disable it. 72 | 73 | - **raster extension** (--with-raster / --without-raster) 74 | 75 | By default the `raster` extension and properties will be added to the item. 76 | 77 | link: https://github.com/stac-extensions/raster 78 | 79 | ```json 80 | "raster:bands": [ 81 | { 82 | "sampling": "point", 83 | "data_type": "uint16", 84 | "scale": 1, 85 | "offset": 0, 86 | "statistics": { 87 | "mean": 2107.524612053134, 88 | "minimum": 1, 89 | "maximum": 7872, 90 | "stdev": 2271.0065537857326, 91 | "valid_percent": 9.564764936336924e-05 92 | }, 93 | "histogram": { 94 | "count": 11, 95 | "min": 1, 96 | "max": 7872, 97 | "buckets": [503460, 0, 0, 161792, 283094, 0, 0, 0, 87727, 9431] 98 | } 99 | } 100 | ] 101 | ``` 102 | 103 | You can pass `--without-raster` to disable it. 104 | 105 | - **eo extension** (--with-eo / --without-eo) 106 | 107 | By default the `eo` extension and properties will be added to the item. The `eo:cloud_cover` value will be fetched from [GDAL Raster data model](https://gdal.org/user/raster_data_model) metadata. 108 | 109 | link: https://github.com/stac-extensions/eo/ 110 | 111 | Cloud Cover property 112 | ```json 113 | "eo:cloud_cover": 2 114 | ``` 115 | 116 | Asset's bands 117 | ```json 118 | "eo:bands": [ 119 | { 120 | "name": "b1", 121 | "description": "red" 122 | }, 123 | { 124 | "name": "b2" 125 | "description": "green" 126 | }, 127 | { 128 | "name": "b3" 129 | "description": "blue" 130 | } 131 | ], 132 | ``` 133 | 134 | You can pass `--without-eo` to disable it. 135 | 136 | Note: `GDAL Raster data model` metadata are stored in an external file so you may want to set `GDAL_DISABLE_READDIR_ON_OPEN=FALSE` environment variable to allow GDAL to fetch the sidecar files. 137 | 138 | - **collection** (-c, --collection) 139 | 140 | Add a `collection` attribute to the item. 141 | 142 | - **collection link** (--collection-url) 143 | 144 | When adding a collection to the Item, the specification state that a Link must also be set. By default the `href` will be set with the collection id. You can specify a custom URL using this option. 145 | 146 | - **properties** (-p, --property) 147 | 148 | You can add multiple properties to the item using `-p {KEY}={VALUE}` notation. This option can be set multiple times. 149 | 150 | - **id** (--id) 151 | 152 | STAC Item id to set. Default to the source basename. 153 | 154 | - **asset name** (-n, --asset-name) 155 | 156 | Name to use in the assets section. Default to `asset`. 157 | 158 | ```json 159 | { 160 | "asset": { 161 | "href": "raster.tif" 162 | } 163 | } 164 | ``` 165 | 166 | - **asset href** (--asset-href) 167 | 168 | Overwrite the HREF in the `asset` object. Default to the source path. 169 | 170 | - **media type** (--asset-mediatype) 171 | 172 | Set the asset `mediatype`. 173 | 174 | If set to `auto`, `rio-stac` will try to find the mediatype. 175 | 176 | - **geometry density** (--densify-geom) 177 | 178 | When creating the GeoJSON geometry from the input dataset we usually take the `bounding box` of the data and construct a simple Polygon which then get reprojected to EPSG:4326. Sadly the world is neither flat and square, so doing a transformation using bounding box can lead to non-ideal result. To get better results and account for nonlinear transformation you can add `points` on each edge of the polygon using `--densify-geom` option. 179 | 180 | ### Example 181 | 182 | ```json 183 | // rio stac tests/fixtures/dataset_cog.tif | jq 184 | { 185 | "type": "Feature", 186 | "stac_version": "1.0.0", 187 | "id": "dataset_cog.tif", 188 | "properties": { 189 | "proj:epsg": 32621, 190 | "proj:geometry": { 191 | "type": "Polygon", 192 | "coordinates": [ 193 | [ 194 | [ 195 | 373185.0, 196 | 8019284.949381611 197 | ], 198 | [ 199 | 639014.9492102272, 200 | 8019284.949381611 201 | ], 202 | [ 203 | 639014.9492102272, 204 | 8286015.0 205 | ], 206 | [ 207 | 373185.0, 208 | 8286015.0 209 | ], 210 | [ 211 | 373185.0, 212 | 8019284.949381611 213 | ] 214 | ] 215 | ] 216 | }, 217 | "proj:bbox": [ 218 | 373185.0, 219 | 8019284.949381611, 220 | 639014.9492102272, 221 | 8286015.0 222 | ], 223 | "proj:shape": [ 224 | 2667, 225 | 2658 226 | ], 227 | "proj:transform": [ 228 | 100.01126757344893, 229 | 0.0, 230 | 373185.0, 231 | 0.0, 232 | -100.01126757344893, 233 | 8286015.0, 234 | 0.0, 235 | 0.0, 236 | 1.0 237 | ], 238 | "proj:projjson": { 239 | "$schema": "https://proj.org/schemas/v0.4/projjson.schema.json", 240 | "type": "ProjectedCRS", 241 | "name": "WGS 84 / UTM zone 21N", 242 | "base_crs": { 243 | "name": "WGS 84", 244 | "datum": { 245 | "type": "GeodeticReferenceFrame", 246 | "name": "World Geodetic System 1984", 247 | "ellipsoid": { 248 | "name": "WGS 84", 249 | "semi_major_axis": 6378137, 250 | "inverse_flattening": 298.257223563 251 | } 252 | }, 253 | "coordinate_system": { 254 | "subtype": "ellipsoidal", 255 | "axis": [ 256 | { 257 | "name": "Geodetic latitude", 258 | "abbreviation": "Lat", 259 | "direction": "north", 260 | "unit": "degree" 261 | }, 262 | { 263 | "name": "Geodetic longitude", 264 | "abbreviation": "Lon", 265 | "direction": "east", 266 | "unit": "degree" 267 | } 268 | ] 269 | }, 270 | "id": { 271 | "authority": "EPSG", 272 | "code": 4326 273 | } 274 | }, 275 | "conversion": { 276 | "name": "UTM zone 21N", 277 | "method": { 278 | "name": "Transverse Mercator", 279 | "id": { 280 | "authority": "EPSG", 281 | "code": 9807 282 | } 283 | }, 284 | "parameters": [ 285 | { 286 | "name": "Latitude of natural origin", 287 | "value": 0, 288 | "unit": "degree", 289 | "id": { 290 | "authority": "EPSG", 291 | "code": 8801 292 | } 293 | }, 294 | { 295 | "name": "Longitude of natural origin", 296 | "value": -57, 297 | "unit": "degree", 298 | "id": { 299 | "authority": "EPSG", 300 | "code": 8802 301 | } 302 | }, 303 | { 304 | "name": "Scale factor at natural origin", 305 | "value": 0.9996, 306 | "unit": "unity", 307 | "id": { 308 | "authority": "EPSG", 309 | "code": 8805 310 | } 311 | }, 312 | { 313 | "name": "False easting", 314 | "value": 500000, 315 | "unit": "metre", 316 | "id": { 317 | "authority": "EPSG", 318 | "code": 8806 319 | } 320 | }, 321 | { 322 | "name": "False northing", 323 | "value": 0, 324 | "unit": "metre", 325 | "id": { 326 | "authority": "EPSG", 327 | "code": 8807 328 | } 329 | } 330 | ] 331 | }, 332 | "coordinate_system": { 333 | "subtype": "Cartesian", 334 | "axis": [ 335 | { 336 | "name": "Easting", 337 | "abbreviation": "", 338 | "direction": "east", 339 | "unit": "metre" 340 | }, 341 | { 342 | "name": "Northing", 343 | "abbreviation": "", 344 | "direction": "north", 345 | "unit": "metre" 346 | } 347 | ] 348 | }, 349 | "id": { 350 | "authority": "EPSG", 351 | "code": 32621 352 | } 353 | }, 354 | "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 21N\",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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",-57],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],AUTHORITY[\"EPSG\",\"32621\"]]", 355 | "datetime": "2023-12-08T09:30:38.153261Z" 356 | }, 357 | "geometry": { 358 | "type": "Polygon", 359 | "coordinates": [ 360 | [ 361 | [ 362 | -60.72634617297825, 363 | 72.23689137791739 364 | ], 365 | [ 366 | -52.91627525610924, 367 | 72.22979795551834 368 | ], 369 | [ 370 | -52.301598718454485, 371 | 74.61378388950398 372 | ], 373 | [ 374 | -61.28762442711404, 375 | 74.62204314252978 376 | ], 377 | [ 378 | -60.72634617297825, 379 | 72.23689137791739 380 | ] 381 | ] 382 | ] 383 | }, 384 | "links": [], 385 | "assets": { 386 | "asset": { 387 | "href": "/Users/vincentsarago/Dev/DevSeed/rio-stac/tests/fixtures/dataset_cog.tif", 388 | "raster:bands": [ 389 | { 390 | "data_type": "uint16", 391 | "scale": 1.0, 392 | "offset": 0.0, 393 | "sampling": "point", 394 | "statistics": { 395 | "mean": 2107.524612053134, 396 | "minimum": 1, 397 | "maximum": 7872, 398 | "stddev": 2271.0065537857326, 399 | "valid_percent": 0.00009564764936336924 400 | }, 401 | "histogram": { 402 | "count": 11, 403 | "min": 1.0, 404 | "max": 7872.0, 405 | "buckets": [ 406 | 503460, 407 | 0, 408 | 0, 409 | 161792, 410 | 283094, 411 | 0, 412 | 0, 413 | 0, 414 | 87727, 415 | 9431 416 | ] 417 | } 418 | } 419 | ], 420 | "eo:bands": [ 421 | { 422 | "name": "b1", 423 | "description": "gray" 424 | } 425 | ], 426 | "roles": [] 427 | } 428 | }, 429 | "bbox": [ 430 | -61.28762442711404, 431 | 72.22979795551834, 432 | -52.301598718454485, 433 | 74.62204314252978 434 | ], 435 | "stac_extensions": [ 436 | "https://stac-extensions.github.io/projection/v1.1.0/schema.json", 437 | "https://stac-extensions.github.io/raster/v1.1.0/schema.json", 438 | "https://stac-extensions.github.io/eo/v1.1.0/schema.json" 439 | ] 440 | } 441 | ``` 442 | 443 | ```json 444 | // rio stac S-2_20200422_COG.tif \ 445 | // -d 2020-04-22 \ 446 | // -c myprivatecollection \ 447 | // -p comments:name=myfile \ 448 | // --id COG \ 449 | // -n mosaic \ 450 | // --asset-href https://somewhere.overtherainbow.io/S-2_20200422_COG.tif \ 451 | // --asset-mediatype COG | jq 452 | // { 453 | "type": "Feature", 454 | "stac_version": "1.0.0", 455 | "id": "COG", 456 | "properties": { 457 | "comments:name": "myfile", 458 | "proj:epsg": 32632, 459 | "proj:geometry": { 460 | "type": "Polygon", 461 | "coordinates": [ 462 | [ 463 | [ 464 | 342765.0, 465 | 5682885.0 466 | ], 467 | [ 468 | 674215.0, 469 | 5682885.0 470 | ], 471 | [ 472 | 674215.0, 473 | 5971585.0 474 | ], 475 | [ 476 | 342765.0, 477 | 5971585.0 478 | ], 479 | [ 480 | 342765.0, 481 | 5682885.0 482 | ] 483 | ] 484 | ] 485 | }, 486 | "proj:bbox": [ 487 | 342765.0, 488 | 5682885.0, 489 | 674215.0, 490 | 5971585.0 491 | ], 492 | "proj:shape": [ 493 | 28870, 494 | 33145 495 | ], 496 | "proj:transform": [ 497 | 10.0, 498 | 0.0, 499 | 342765.0, 500 | 0.0, 501 | -10.0, 502 | 5971585.0, 503 | 0.0, 504 | 0.0, 505 | 1.0 506 | ], 507 | "proj:projjson": { 508 | "$schema": "https://proj.org/schemas/v0.4/projjson.schema.json", 509 | "type": "ProjectedCRS", 510 | "name": "WGS 84 / UTM zone 32N", 511 | "base_crs": { 512 | "name": "WGS 84", 513 | "datum": { 514 | "type": "GeodeticReferenceFrame", 515 | "name": "World Geodetic System 1984", 516 | "ellipsoid": { 517 | "name": "WGS 84", 518 | "semi_major_axis": 6378137, 519 | "inverse_flattening": 298.257223563 520 | } 521 | }, 522 | "coordinate_system": { 523 | "subtype": "ellipsoidal", 524 | "axis": [ 525 | { 526 | "name": "Geodetic latitude", 527 | "abbreviation": "Lat", 528 | "direction": "north", 529 | "unit": "degree" 530 | }, 531 | { 532 | "name": "Geodetic longitude", 533 | "abbreviation": "Lon", 534 | "direction": "east", 535 | "unit": "degree" 536 | } 537 | ] 538 | }, 539 | "id": { 540 | "authority": "EPSG", 541 | "code": 4326 542 | } 543 | }, 544 | "conversion": { 545 | "name": "UTM zone 32N", 546 | "method": { 547 | "name": "Transverse Mercator", 548 | "id": { 549 | "authority": "EPSG", 550 | "code": 9807 551 | } 552 | }, 553 | "parameters": [ 554 | { 555 | "name": "Latitude of natural origin", 556 | "value": 0, 557 | "unit": "degree", 558 | "id": { 559 | "authority": "EPSG", 560 | "code": 8801 561 | } 562 | }, 563 | { 564 | "name": "Longitude of natural origin", 565 | "value": 9, 566 | "unit": "degree", 567 | "id": { 568 | "authority": "EPSG", 569 | "code": 8802 570 | } 571 | }, 572 | { 573 | "name": "Scale factor at natural origin", 574 | "value": 0.9996, 575 | "unit": "unity", 576 | "id": { 577 | "authority": "EPSG", 578 | "code": 8805 579 | } 580 | }, 581 | { 582 | "name": "False easting", 583 | "value": 500000, 584 | "unit": "metre", 585 | "id": { 586 | "authority": "EPSG", 587 | "code": 8806 588 | } 589 | }, 590 | { 591 | "name": "False northing", 592 | "value": 0, 593 | "unit": "metre", 594 | "id": { 595 | "authority": "EPSG", 596 | "code": 8807 597 | } 598 | } 599 | ] 600 | }, 601 | "coordinate_system": { 602 | "subtype": "Cartesian", 603 | "axis": [ 604 | { 605 | "name": "Easting", 606 | "abbreviation": "", 607 | "direction": "east", 608 | "unit": "metre" 609 | }, 610 | { 611 | "name": "Northing", 612 | "abbreviation": "", 613 | "direction": "north", 614 | "unit": "metre" 615 | } 616 | ] 617 | }, 618 | "id": { 619 | "authority": "EPSG", 620 | "code": 32632 621 | } 622 | }, 623 | "proj:wkt2": "PROJCS[\"WGS 84 / UTM zone 32N\",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.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",9],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",500000],PARAMETER[\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],AUTHORITY[\"EPSG\",\"32632\"]]", 624 | "datetime": "2020-04-22T00:00:00Z" 625 | }, 626 | "geometry": { 627 | "type": "Polygon", 628 | "coordinates": [ 629 | [ 630 | [ 631 | 6.745709371926977, 632 | 51.27558086786243 633 | ], 634 | [ 635 | 11.497498156319669, 636 | 51.270642883468916 637 | ], 638 | [ 639 | 11.64938680867944, 640 | 53.86346627759 641 | ], 642 | [ 643 | 6.608576517072109, 644 | 53.868886713141336 645 | ], 646 | [ 647 | 6.745709371926977, 648 | 51.27558086786243 649 | ] 650 | ] 651 | ] 652 | }, 653 | "links": [ 654 | { 655 | "rel": "collection", 656 | "href": "myprivatecollection", 657 | "type": "application/json" 658 | } 659 | ], 660 | "assets": { 661 | "mosaic": { 662 | "href": "https://somewhere.overtherainbow.io/S-2_20200422_COG.tif", 663 | "type": "image/tiff; application=geotiff; profile=cloud-optimized", 664 | "raster:bands": [ 665 | { 666 | "data_type": "uint8", 667 | "scale": 1.0, 668 | "offset": 0.0, 669 | "sampling": "area", 670 | "statistics": { 671 | "mean": 70.14680057905686, 672 | "minimum": 0, 673 | "maximum": 255, 674 | "stddev": 36.47197403839734, 675 | "valid_percent": 49.83785997057175 676 | }, 677 | "histogram": { 678 | "count": 11, 679 | "min": 0.0, 680 | "max": 255.0, 681 | "buckets": [ 682 | 21135, 683 | 129816, 684 | 152194, 685 | 76363, 686 | 39423, 687 | 20046, 688 | 10272, 689 | 3285, 690 | 1115, 691 | 1574 692 | ] 693 | } 694 | }, 695 | { 696 | "data_type": "uint8", 697 | "scale": 1.0, 698 | "offset": 0.0, 699 | "sampling": "area", 700 | "statistics": { 701 | "mean": 70.72913714816694, 702 | "minimum": 0, 703 | "maximum": 255, 704 | "stddev": 34.031434334640124, 705 | "valid_percent": 49.83785997057175 706 | }, 707 | "histogram": { 708 | "count": 11, 709 | "min": 0.0, 710 | "max": 255.0, 711 | "buckets": [ 712 | 14829, 713 | 116732, 714 | 171933, 715 | 81023, 716 | 38736, 717 | 18977, 718 | 8362, 719 | 2259, 720 | 918, 721 | 1454 722 | ] 723 | } 724 | }, 725 | { 726 | "data_type": "uint8", 727 | "scale": 1.0, 728 | "offset": 0.0, 729 | "sampling": "area", 730 | "statistics": { 731 | "mean": 47.96346845392258, 732 | "minimum": 0, 733 | "maximum": 255, 734 | "stddev": 32.447819767110225, 735 | "valid_percent": 49.83785997057175 736 | }, 737 | "histogram": { 738 | "count": 11, 739 | "min": 0.0, 740 | "max": 255.0, 741 | "buckets": [ 742 | 110478, 743 | 177673, 744 | 93767, 745 | 41101, 746 | 20804, 747 | 7117, 748 | 1939, 749 | 856, 750 | 829, 751 | 659 752 | ] 753 | } 754 | } 755 | ], 756 | "eo:bands": [ 757 | { 758 | "name": "b1", 759 | "description": "red" 760 | }, 761 | { 762 | "name": "b2", 763 | "description": "green" 764 | }, 765 | { 766 | "name": "b3", 767 | "description": "blue" 768 | } 769 | ], 770 | "roles": [] 771 | } 772 | }, 773 | "bbox": [ 774 | 6.608576517072109, 775 | 51.270642883468916, 776 | 11.64938680867944, 777 | 53.868886713141336 778 | ], 779 | "stac_extensions": [ 780 | "https://stac-extensions.github.io/projection/v1.1.0/schema.json", 781 | "https://stac-extensions.github.io/raster/v1.1.0/schema.json", 782 | "https://stac-extensions.github.io/eo/v1.1.0/schema.json" 783 | ], 784 | "collection": "myprivatecollection" 785 | } 786 | ``` 787 | 788 | 789 | # API 790 | 791 | see: [api](api/rio_stac/stac.md) 792 | -------------------------------------------------------------------------------- /docs/docs/release-notes.md: -------------------------------------------------------------------------------- 1 | ../../CHANGES.md -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Project Information 2 | site_name: 'rio-stac' 3 | site_description: 'Create a STAC Items from raster datasets.' 4 | 5 | # Repository 6 | repo_name: 'developmentseed/rio-stac' 7 | repo_url: 'http://github.com/developmentseed/rio-stac' 8 | edit_uri: 'blob/main/docs/src/' 9 | site_url: 'https://developmentseed.org/rio-stac/' 10 | 11 | # Social links 12 | extra: 13 | social: 14 | - icon: 'fontawesome/brands/github' 15 | link: 'https://github.com/developmentseed' 16 | - icon: 'fontawesome/brands/twitter' 17 | link: 'https://twitter.com/developmentseed' 18 | 19 | # Layout 20 | nav: 21 | - Home: 'index.md' 22 | - User Guide: 'intro.md' 23 | - Examples: 24 | - Create a STAC Items from multiple Assets: examples/Multi_assets_item.ipynb 25 | - API: 26 | - rio_stac.stac: api/rio_stac/stac.md 27 | - Development - Contributing: 'contributing.md' 28 | - Release Notes: 'release-notes.md' 29 | 30 | plugins: 31 | - search 32 | - mkdocs-jupyter: 33 | include_source: True 34 | 35 | # Theme 36 | theme: 37 | icon: 38 | logo: 'material/home' 39 | repo: 'fontawesome/brands/github' 40 | name: 'material' 41 | language: 'en' 42 | palette: 43 | primary: 'red' 44 | accent: 'light red' 45 | font: 46 | text: 'Nunito Sans' 47 | code: 'Fira Code' 48 | 49 | # These extensions are chosen to be a superset of Pandoc's Markdown. 50 | # This way, I can write in Pandoc's Markdown and have it be supported here. 51 | # https://pandoc.org/MANUAL.html 52 | markdown_extensions: 53 | - admonition 54 | - attr_list 55 | - codehilite: 56 | guess_lang: false 57 | - def_list 58 | - footnotes 59 | - pymdownx.arithmatex 60 | - pymdownx.betterem 61 | - pymdownx.caret: 62 | insert: false 63 | - pymdownx.details 64 | - pymdownx.emoji 65 | - pymdownx.escapeall: 66 | hardbreak: true 67 | nbsp: true 68 | - pymdownx.magiclink: 69 | hide_protocol: true 70 | repo_url_shortener: true 71 | - pymdownx.smartsymbols 72 | - pymdownx.superfences 73 | - pymdownx.tasklist: 74 | custom_checkbox: true 75 | - pymdownx.tilde 76 | - toc: 77 | permalink: true 78 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "rio-stac" 3 | description = "Create STAC Items from raster datasets." 4 | readme = "README.md" 5 | requires-python = ">=3.9" 6 | license = {file = "LICENSE"} 7 | authors = [ 8 | {name = "Vincent Sarago", email = "vincent@developmentseed.com"}, 9 | ] 10 | classifiers = [ 11 | "Intended Audience :: Information Technology", 12 | "Intended Audience :: Science/Research", 13 | "License :: OSI Approved :: BSD License", 14 | "Programming Language :: Python :: 3.9", 15 | "Programming Language :: Python :: 3.10", 16 | "Programming Language :: Python :: 3.11", 17 | "Programming Language :: Python :: 3.12", 18 | "Programming Language :: Python :: 3.13", 19 | "Topic :: Scientific/Engineering :: GIS", 20 | ] 21 | dynamic = ["version"] 22 | dependencies = [ 23 | "rasterio>=1.0,!=1.4.2", 24 | "pystac>=1.0.0,<2.0.0", 25 | ] 26 | 27 | [project.optional-dependencies] 28 | test = [ 29 | "pytest", 30 | "pytest-cov", 31 | "requests", 32 | "pystac[validation]>=1.0.0,<2.0.0" 33 | ] 34 | dev = [ 35 | "pre-commit", 36 | "bump-my-version", 37 | ] 38 | doc = [ 39 | "mkdocs", 40 | "mkdocs-material", 41 | "mkdocs-jupyter", 42 | "pygments", 43 | "pdocs", 44 | ] 45 | 46 | [project.urls] 47 | Source = "https://github.com/developmentseed/rio-stac" 48 | Documentation = "https://developmentseed.org/rio-stac/" 49 | 50 | [project.entry-points."rasterio.rio_plugins"] 51 | stac = "rio_stac.scripts.cli:stac" 52 | 53 | [build-system] 54 | requires = ["flit_core>=3.2,<4"] 55 | build-backend = "flit_core.buildapi" 56 | 57 | [tool.flit.module] 58 | name = "rio_stac" 59 | 60 | [tool.flit.sdist] 61 | exclude = [ 62 | "tests/", 63 | "docs/", 64 | ".github/", 65 | "CHANGES.md", 66 | "CONTRIBUTING.md", 67 | ] 68 | 69 | [tool.coverage.run] 70 | branch = true 71 | parallel = true 72 | 73 | [tool.coverage.report] 74 | exclude_lines = [ 75 | "no cov", 76 | "if __name__ == .__main__.:", 77 | "if TYPE_CHECKING:", 78 | ] 79 | 80 | [tool.isort] 81 | profile = "black" 82 | known_first_party = ["rio_stac"] 83 | known_third_party = ["rasterio", "pystac"] 84 | default_section = "THIRDPARTY" 85 | 86 | [tool.mypy] 87 | no_strict_optional = true 88 | 89 | [tool.ruff] 90 | line-length = 90 91 | 92 | [tool.ruff.lint] 93 | select = [ 94 | "D1", # pydocstyle errors 95 | "E", # pycodestyle errors 96 | "W", # pycodestyle warnings 97 | "F", # flake8 98 | "C", # flake8-comprehensions 99 | "B", # flake8-bugbear 100 | ] 101 | ignore = [ 102 | "E501", # line too long, handled by black 103 | "B008", # do not perform function calls in argument defaults 104 | "B905", # ignore zip() without an explicit strict= parameter, only support with python >3.10 105 | "B028", 106 | "C416", 107 | ] 108 | 109 | [tool.ruff.lint.mccabe] 110 | max-complexity = 14 111 | 112 | [tool.bumpversion] 113 | current_version = "0.11.0" 114 | 115 | search = "{current_version}" 116 | replace = "{new_version}" 117 | regex = false 118 | tag = true 119 | commit = true 120 | tag_name = "{new_version}" 121 | 122 | [[tool.bumpversion.files]] 123 | filename = "rio_stac/__init__.py" 124 | search = '__version__ = "{current_version}"' 125 | replace = '__version__ = "{new_version}"' 126 | -------------------------------------------------------------------------------- /rio_stac/__init__.py: -------------------------------------------------------------------------------- 1 | """rio-stac: Create STAC items from raster file.""" 2 | 3 | __version__ = "0.11.0" 4 | 5 | from rio_stac.stac import create_stac_item # noqa 6 | -------------------------------------------------------------------------------- /rio_stac/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | """rio-stac.scripts.cli.""" 2 | -------------------------------------------------------------------------------- /rio_stac/scripts/cli.py: -------------------------------------------------------------------------------- 1 | """rio_stac.scripts.cli.""" 2 | 3 | import json 4 | 5 | import click 6 | import rasterio 7 | from pystac import MediaType 8 | from pystac.utils import datetime_to_str, str_to_datetime 9 | from rasterio.rio import options 10 | 11 | from rio_stac import create_stac_item 12 | 13 | 14 | def _cb_key_val(ctx, param, value): 15 | if not value: 16 | return {} 17 | else: 18 | out = {} 19 | for pair in value: 20 | if "=" not in pair: 21 | raise click.BadParameter( 22 | "Invalid syntax for KEY=VAL arg: {}".format(pair) 23 | ) 24 | else: 25 | k, v = pair.split("=", 1) 26 | out[k] = v 27 | return out 28 | 29 | 30 | @click.command() 31 | @options.file_in_arg 32 | @click.option( 33 | "--datetime", 34 | "-d", 35 | "input_datetime", 36 | type=str, 37 | help="The date and time of the assets, in UTC (e.g 2020-01-01, 2020-01-01T01:01:01).", 38 | ) 39 | @click.option( 40 | "--extension", 41 | "-e", 42 | type=str, 43 | multiple=True, 44 | help="STAC extension URL the Item implements.", 45 | ) 46 | @click.option( 47 | "--collection", "-c", type=str, help="The Collection ID that this item belongs to." 48 | ) 49 | @click.option("--collection-url", type=str, help="Link to the STAC Collection.") 50 | @click.option( 51 | "--property", 52 | "-p", 53 | metavar="NAME=VALUE", 54 | multiple=True, 55 | callback=_cb_key_val, 56 | help="Additional property to add.", 57 | ) 58 | @click.option("--id", type=str, help="Item id.") 59 | @click.option( 60 | "--asset-name", 61 | "-n", 62 | type=str, 63 | default="asset", 64 | help="Asset name.", 65 | show_default=True, 66 | ) 67 | @click.option("--asset-href", type=str, help="Overwrite asset href.") 68 | @click.option( 69 | "--asset-mediatype", 70 | type=click.Choice([it.name for it in MediaType] + ["auto"]), 71 | help="Asset media-type.", 72 | ) 73 | @click.option( 74 | "--with-proj/--without-proj", 75 | default=True, 76 | help="Add the 'projection' extension and properties.", 77 | show_default=True, 78 | ) 79 | @click.option( 80 | "--with-raster/--without-raster", 81 | default=True, 82 | help="Add the 'raster' extension and properties.", 83 | show_default=True, 84 | ) 85 | @click.option( 86 | "--with-eo/--without-eo", 87 | default=True, 88 | help="Add the 'eo' extension and properties.", 89 | show_default=True, 90 | ) 91 | @click.option( 92 | "--max-raster-size", 93 | type=int, 94 | default=1024, 95 | help="Limit array size from which to get the raster statistics.", 96 | show_default=True, 97 | ) 98 | @click.option( 99 | "--densify-geom", 100 | type=int, 101 | help="Densifies the number of points on each edges of the polygon geometry to account for non-linear transformation.", 102 | ) 103 | @click.option( 104 | "--geom-precision", 105 | type=int, 106 | default=-1, 107 | help="Round geometry coordinates to this number of decimal. By default, coordinates will not be rounded", 108 | ) 109 | @click.option("--output", "-o", type=click.Path(exists=False), help="Output file name") 110 | @click.option( 111 | "--config", 112 | "config", 113 | metavar="NAME=VALUE", 114 | multiple=True, 115 | callback=options._cb_key_val, 116 | help="GDAL configuration options.", 117 | ) 118 | def stac( 119 | input, 120 | input_datetime, 121 | extension, 122 | collection, 123 | collection_url, 124 | property, 125 | id, 126 | asset_name, 127 | asset_href, 128 | asset_mediatype, 129 | with_proj, 130 | with_raster, 131 | with_eo, 132 | max_raster_size, 133 | densify_geom, 134 | geom_precision, 135 | output, 136 | config, 137 | ): 138 | """Rasterio STAC plugin: Create a STAC Item for raster dataset.""" 139 | property = property or {} 140 | densify_geom = densify_geom or 0 141 | 142 | if input_datetime: 143 | if "/" in input_datetime: 144 | start_datetime, end_datetime = input_datetime.split("/") 145 | property["start_datetime"] = datetime_to_str(str_to_datetime(start_datetime)) 146 | property["end_datetime"] = datetime_to_str(str_to_datetime(end_datetime)) 147 | input_datetime = None 148 | else: 149 | input_datetime = str_to_datetime(input_datetime) 150 | 151 | if asset_mediatype and asset_mediatype != "auto": 152 | asset_mediatype = MediaType[asset_mediatype] 153 | 154 | extensions = [e for e in extension if e] 155 | 156 | with rasterio.Env(**config): 157 | item = create_stac_item( 158 | input, 159 | input_datetime=input_datetime, 160 | extensions=extensions, 161 | collection=collection, 162 | collection_url=collection_url, 163 | properties=property, 164 | id=id, 165 | asset_name=asset_name, 166 | asset_href=asset_href, 167 | asset_media_type=asset_mediatype, 168 | with_proj=with_proj, 169 | with_raster=with_raster, 170 | with_eo=with_eo, 171 | raster_max_size=max_raster_size, 172 | geom_densify_pts=densify_geom, 173 | geom_precision=geom_precision, 174 | ) 175 | 176 | if output: 177 | with open(output, "w") as f: 178 | f.write(json.dumps(item.to_dict(), separators=(",", ":"))) 179 | else: 180 | click.echo(json.dumps(item.to_dict(), separators=(",", ":"))) 181 | -------------------------------------------------------------------------------- /rio_stac/stac.py: -------------------------------------------------------------------------------- 1 | """Create STAC Item from a rasterio dataset.""" 2 | 3 | import datetime 4 | import math 5 | import os 6 | import warnings 7 | from contextlib import ExitStack 8 | from typing import Any, Dict, List, Optional, Tuple, Union 9 | 10 | import numpy 11 | import pystac 12 | import rasterio 13 | from pystac.utils import str_to_datetime 14 | from rasterio import transform, warp 15 | from rasterio.features import bounds as feature_bounds 16 | from rasterio.io import DatasetReader, DatasetWriter, MemoryFile 17 | from rasterio.vrt import WarpedVRT 18 | 19 | PROJECTION_EXT_VERSION = "v1.1.0" 20 | RASTER_EXT_VERSION = "v1.1.0" 21 | EO_EXT_VERSION = "v1.1.0" 22 | 23 | EPSG_4326 = rasterio.crs.CRS.from_epsg(4326) 24 | 25 | 26 | def bbox_to_geom(bbox: Tuple[float, float, float, float]) -> Dict: 27 | """Return a geojson geometry from a bbox.""" 28 | return { 29 | "type": "Polygon", 30 | "coordinates": [ 31 | [ 32 | [bbox[0], bbox[1]], 33 | [bbox[2], bbox[1]], 34 | [bbox[2], bbox[3]], 35 | [bbox[0], bbox[3]], 36 | [bbox[0], bbox[1]], 37 | ] 38 | ], 39 | } 40 | 41 | 42 | def get_dataset_geom( 43 | src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile], 44 | densify_pts: int = 0, 45 | precision: int = -1, 46 | geographic_crs: rasterio.crs.CRS = EPSG_4326, 47 | ) -> Dict: 48 | """Get Raster Footprint.""" 49 | if densify_pts < 0: 50 | raise ValueError("`densify_pts` must be positive") 51 | 52 | if src_dst.crs is not None: 53 | # 1. Create Polygon from raster bounds 54 | geom = bbox_to_geom(src_dst.bounds) 55 | 56 | # 2. Densify the Polygon geometry 57 | if src_dst.crs != geographic_crs and densify_pts: 58 | # Derived from code found at 59 | # https://stackoverflow.com/questions/64995977/generating-equidistance-points-along-the-boundary-of-a-polygon-but-cw-ccw 60 | coordinates = numpy.asarray(geom["coordinates"][0]) 61 | 62 | densified_number = len(coordinates) * densify_pts 63 | existing_indices = numpy.arange(0, densified_number, densify_pts) 64 | interp_indices = numpy.arange(existing_indices[-1] + 1) 65 | interp_x = numpy.interp(interp_indices, existing_indices, coordinates[:, 0]) 66 | interp_y = numpy.interp(interp_indices, existing_indices, coordinates[:, 1]) 67 | geom = { 68 | "type": "Polygon", 69 | "coordinates": [[(x, y) for x, y in zip(interp_x, interp_y)]], 70 | } 71 | 72 | # 3. Reproject the geometry to "epsg:4326" 73 | geom = warp.transform_geom(src_dst.crs, geographic_crs, geom, precision=precision) 74 | bbox = feature_bounds(geom) 75 | 76 | else: 77 | warnings.warn( 78 | "Input file doesn't have CRS information, setting geometry and bbox to (-180,-90,180,90)." 79 | ) 80 | bbox = (-180.0, -90.0, 180.0, 90.0) 81 | geom = bbox_to_geom(bbox) 82 | 83 | return {"bbox": list(bbox), "footprint": geom} 84 | 85 | 86 | def get_projection_info( 87 | src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile], 88 | ) -> Dict: 89 | """Get projection metadata. 90 | 91 | The STAC projection extension allows for three different ways to describe the coordinate reference system 92 | associated with a raster : 93 | - EPSG code 94 | - WKT2 95 | - PROJJSON 96 | 97 | All are optional, and they can be provided altogether as well. Therefore, as long as one can be obtained from 98 | the data, we add it to the returned dictionary. 99 | 100 | see: https://github.com/stac-extensions/projection 101 | 102 | """ 103 | 104 | epsg = None 105 | if src_dst.crs is not None: 106 | # EPSG 107 | epsg = src_dst.crs.to_epsg() if src_dst.crs.is_epsg_code else None 108 | 109 | meta = { 110 | "epsg": epsg, 111 | "geometry": bbox_to_geom(src_dst.bounds), 112 | "bbox": list(src_dst.bounds), 113 | "shape": [src_dst.height, src_dst.width], 114 | "transform": list(src_dst.transform), 115 | } 116 | 117 | if not epsg and src_dst.crs: 118 | # WKT2 119 | try: 120 | meta["wkt2"] = src_dst.crs.to_wkt() 121 | except Exception as ex: 122 | warnings.warn(f"Could not get WKT2 from dataset : {ex}") 123 | # PROJJSON 124 | try: 125 | meta["projjson"] = src_dst.crs.to_dict(projjson=True) 126 | except (AttributeError, TypeError) as ex: 127 | warnings.warn(f"Could not get PROJJSON from dataset : {ex}") 128 | 129 | return meta 130 | 131 | 132 | def get_eobands_info( 133 | src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile], 134 | ) -> List: 135 | """Get eo:bands metadata. 136 | 137 | see: https://github.com/stac-extensions/eo#item-properties-or-asset-fields 138 | 139 | """ 140 | eo_bands = [] 141 | 142 | colors = src_dst.colorinterp 143 | for ix in src_dst.indexes: 144 | band_meta = {"name": f"b{ix}"} 145 | 146 | descr = src_dst.descriptions[ix - 1] 147 | color = colors[ix - 1].name 148 | 149 | # Description metadata or Colorinterp or Nothing 150 | description = descr or color 151 | if description: 152 | band_meta["description"] = description 153 | 154 | eo_bands.append(band_meta) 155 | 156 | return eo_bands 157 | 158 | 159 | def _get_stats(arr: numpy.ma.MaskedArray, **kwargs: Any) -> Dict: 160 | """Calculate array statistics.""" 161 | # Avoid non masked nan/inf values 162 | numpy.ma.fix_invalid(arr, copy=False) 163 | sample, edges = numpy.histogram(arr[~arr.mask]) 164 | return { 165 | "statistics": { 166 | "mean": arr.mean().item(), 167 | "minimum": arr.min().item(), 168 | "maximum": arr.max().item(), 169 | "stddev": arr.std().item(), 170 | "valid_percent": numpy.count_nonzero(~arr.mask) / float(arr.data.size) * 100, 171 | }, 172 | "histogram": { 173 | "count": len(edges), 174 | "min": float(edges.min()), 175 | "max": float(edges.max()), 176 | "buckets": sample.tolist(), 177 | }, 178 | } 179 | 180 | 181 | def get_raster_info( # noqa: C901 182 | src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile], 183 | max_size: int = 1024, 184 | ) -> List[Dict]: 185 | """Get raster metadata. 186 | 187 | see: https://github.com/stac-extensions/raster#raster-band-object 188 | 189 | """ 190 | height = src_dst.height 191 | width = src_dst.width 192 | if max_size: 193 | if max(width, height) > max_size: 194 | ratio = height / width 195 | if ratio > 1: 196 | height = max_size 197 | width = math.ceil(height / ratio) 198 | else: 199 | width = max_size 200 | height = math.ceil(width * ratio) 201 | 202 | meta: List[Dict] = [] 203 | 204 | area_or_point = src_dst.tags().get("AREA_OR_POINT", "").lower() 205 | 206 | # Missing `bits_per_sample` and `spatial_resolution` 207 | for band in src_dst.indexes: 208 | value = { 209 | "data_type": src_dst.dtypes[band - 1], 210 | "scale": src_dst.scales[band - 1], 211 | "offset": src_dst.offsets[band - 1], 212 | } 213 | if area_or_point: 214 | value["sampling"] = area_or_point 215 | 216 | # If the Nodata is not set we don't forward it. 217 | if src_dst.nodata is not None: 218 | if numpy.isnan(src_dst.nodata): 219 | value["nodata"] = "nan" 220 | elif numpy.isposinf(src_dst.nodata): 221 | value["nodata"] = "inf" 222 | elif numpy.isneginf(src_dst.nodata): 223 | value["nodata"] = "-inf" 224 | else: 225 | value["nodata"] = src_dst.nodata 226 | 227 | if src_dst.units[band - 1] is not None: 228 | value["unit"] = src_dst.units[band - 1] 229 | 230 | value.update( 231 | _get_stats(src_dst.read(indexes=band, out_shape=(height, width), masked=True)) 232 | ) 233 | meta.append(value) 234 | 235 | return meta 236 | 237 | 238 | def get_media_type( 239 | src_dst: Union[DatasetReader, DatasetWriter, WarpedVRT, MemoryFile], 240 | ) -> Optional[pystac.MediaType]: 241 | """Find MediaType for a raster dataset.""" 242 | driver = src_dst.driver 243 | 244 | if driver == "GTiff": 245 | if src_dst.crs: 246 | return pystac.MediaType.GEOTIFF 247 | else: 248 | return pystac.MediaType.TIFF 249 | 250 | elif driver in [ 251 | "JP2ECW", 252 | "JP2KAK", 253 | "JP2LURA", 254 | "JP2MrSID", 255 | "JP2OpenJPEG", 256 | "JPEG2000", 257 | ]: 258 | return pystac.MediaType.JPEG2000 259 | 260 | elif driver in ["HDF4", "HDF4Image"]: 261 | return pystac.MediaType.HDF 262 | 263 | elif driver in ["HDF5", "HDF5Image"]: 264 | return pystac.MediaType.HDF5 265 | 266 | elif driver == "JPEG": 267 | return pystac.MediaType.JPEG 268 | 269 | elif driver == "PNG": 270 | return pystac.MediaType.PNG 271 | 272 | warnings.warn("Could not determine the media type from GDAL driver.") 273 | return None 274 | 275 | 276 | def create_stac_item( 277 | source: Union[str, DatasetReader, DatasetWriter, WarpedVRT, MemoryFile], 278 | input_datetime: Optional[datetime.datetime] = None, 279 | extensions: Optional[List[str]] = None, 280 | collection: Optional[str] = None, 281 | collection_url: Optional[str] = None, 282 | properties: Optional[Dict] = None, 283 | id: Optional[str] = None, 284 | assets: Optional[Dict[str, pystac.Asset]] = None, 285 | asset_name: str = "asset", 286 | asset_roles: Optional[List[str]] = None, 287 | asset_media_type: Optional[Union[str, pystac.MediaType]] = "auto", 288 | asset_href: Optional[str] = None, 289 | with_proj: bool = False, 290 | with_raster: bool = False, 291 | with_eo: bool = False, 292 | raster_max_size: int = 1024, 293 | geom_densify_pts: int = 0, 294 | geom_precision: int = -1, 295 | geographic_crs: rasterio.crs.CRS = EPSG_4326, 296 | ) -> pystac.Item: 297 | """Create a Stac Item. 298 | 299 | Args: 300 | source (str or opened rasterio dataset): input path or rasterio dataset. 301 | input_datetime (datetime.datetime, optional): datetime associated with the item. 302 | extensions (list of str): input list of extensions to use in the item. 303 | collection (str, optional): name of collection the item belongs to. 304 | collection_url (str, optional): Link to the STAC Collection. 305 | properties (dict, optional): additional properties to add in the item. 306 | id (str, optional): id to assign to the item (default to the source basename). 307 | assets (dict, optional): Assets to set in the item. If set we won't create one from the source. 308 | asset_name (str, optional): asset name in the Assets object. 309 | asset_roles (list of str, optional): list of str | list of asset's roles. 310 | asset_media_type (str or pystac.MediaType, optional): asset's media type. 311 | asset_href (str, optional): asset's URI (default to input path). 312 | with_proj (bool): Add the `projection` extension and properties (default to False). 313 | with_raster (bool): Add the `raster` extension and properties (default to False). 314 | with_eo (bool): Add the `eo` extension and properties (default to False). 315 | raster_max_size (int): Limit array size from which to get the raster statistics. Defaults to 1024. 316 | geom_densify_pts (int): Number of points to add to each edge to account for nonlinear edges transformation (Note: GDAL uses 21). 317 | geom_precision (int): If >= 0, geometry coordinates will be rounded to this number of decimal. 318 | 319 | Returns: 320 | pystac.Item: valid STAC Item. 321 | 322 | """ 323 | properties = properties or {} 324 | extensions = extensions or [] 325 | asset_roles = asset_roles or [] 326 | 327 | with ExitStack() as ctx: 328 | if isinstance(source, (DatasetReader, DatasetWriter, WarpedVRT)): 329 | dataset = source 330 | else: 331 | dataset = ctx.enter_context(rasterio.open(source)) 332 | 333 | if dataset.gcps[0]: 334 | src_dst = ctx.enter_context( 335 | WarpedVRT( 336 | dataset, 337 | src_crs=dataset.gcps[1], 338 | src_transform=transform.from_gcps(dataset.gcps[0]), 339 | ) 340 | ) 341 | else: 342 | src_dst = dataset 343 | 344 | dataset_geom = get_dataset_geom( 345 | src_dst, 346 | densify_pts=geom_densify_pts, 347 | precision=geom_precision, 348 | geographic_crs=geographic_crs, 349 | ) 350 | 351 | media_type = ( 352 | get_media_type(dataset) if asset_media_type == "auto" else asset_media_type 353 | ) 354 | 355 | if "start_datetime" not in properties and "end_datetime" not in properties: 356 | # Try to get datetime from https://gdal.org/user/raster_data_model.html#imagery-domain-remote-sensing 357 | acq_date = src_dst.get_tag_item("ACQUISITIONDATETIME", "IMAGERY") 358 | tiff_date = src_dst.get_tag_item("TIFFTAG_DATETIME") 359 | dst_date = acq_date or tiff_date 360 | try: 361 | dst_datetime = str_to_datetime(dst_date) if dst_date else None 362 | except ValueError as err: 363 | warnings.warn(f"Could not get parse date: {dst_date}: {err}") 364 | dst_datetime = None 365 | 366 | input_datetime = ( 367 | input_datetime 368 | or dst_datetime 369 | or datetime.datetime.now(datetime.timezone.utc) 370 | ) 371 | 372 | # add projection properties 373 | if with_proj: 374 | extensions.append( 375 | f"https://stac-extensions.github.io/projection/{PROJECTION_EXT_VERSION}/schema.json", 376 | ) 377 | 378 | properties.update( 379 | { 380 | f"proj:{name}": value 381 | for name, value in get_projection_info(src_dst).items() 382 | } 383 | ) 384 | 385 | # add raster properties 386 | raster_info = {} 387 | if with_raster: 388 | extensions.append( 389 | f"https://stac-extensions.github.io/raster/{RASTER_EXT_VERSION}/schema.json", 390 | ) 391 | 392 | raster_info = { 393 | "raster:bands": get_raster_info(dataset, max_size=raster_max_size) 394 | } 395 | 396 | eo_info: Dict[str, List] = {} 397 | if with_eo: 398 | extensions.append( 399 | f"https://stac-extensions.github.io/eo/{EO_EXT_VERSION}/schema.json", 400 | ) 401 | 402 | eo_info = {"eo:bands": get_eobands_info(src_dst)} 403 | 404 | cloudcover = src_dst.get_tag_item("CLOUDCOVER", "IMAGERY") 405 | if cloudcover is not None: 406 | properties.update({"eo:cloud_cover": int(cloudcover)}) 407 | 408 | # item 409 | item = pystac.Item( 410 | id=id or os.path.basename(dataset.name), 411 | geometry=dataset_geom["footprint"], 412 | bbox=dataset_geom["bbox"], 413 | collection=collection, 414 | stac_extensions=extensions, 415 | datetime=input_datetime, 416 | properties=properties, 417 | ) 418 | 419 | # if we add a collection we MUST add a link 420 | if collection: 421 | item.add_link( 422 | pystac.Link( 423 | pystac.RelType.COLLECTION, 424 | collection_url or collection, 425 | media_type=pystac.MediaType.JSON, 426 | ) 427 | ) 428 | 429 | # item.assets 430 | if assets: 431 | for key, asset in assets.items(): 432 | item.add_asset(key=key, asset=asset) 433 | 434 | else: 435 | item.add_asset( 436 | key=asset_name, 437 | asset=pystac.Asset( 438 | href=asset_href or dataset.name, 439 | media_type=media_type, 440 | extra_fields={**raster_info, **eo_info}, 441 | roles=asset_roles, 442 | ), 443 | ) 444 | 445 | return item 446 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """rio-stac tests suite.""" 2 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """``pytest`` configuration.""" 2 | 3 | import pytest 4 | import rasterio 5 | 6 | with rasterio.Env() as env: 7 | drivers = env.drivers() 8 | 9 | 10 | requires_hdf5 = pytest.mark.skipif( 11 | "HDF5" not in drivers.keys(), reason="Only relevant if HDF5 drivers is supported" 12 | ) 13 | requires_hdf4 = pytest.mark.skipif( 14 | "HDF4" not in drivers.keys(), reason="Only relevant if HDF4 drivers is supported" 15 | ) 16 | 17 | 18 | @pytest.fixture 19 | def runner(): 20 | """CLI Runner fixture.""" 21 | from click.testing import CliRunner 22 | 23 | return CliRunner() 24 | -------------------------------------------------------------------------------- /tests/fixtures/dataset.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset.h5 -------------------------------------------------------------------------------- /tests/fixtures/dataset.hdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset.hdf -------------------------------------------------------------------------------- /tests/fixtures/dataset.jp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset.jp2 -------------------------------------------------------------------------------- /tests/fixtures/dataset.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset.jpg -------------------------------------------------------------------------------- /tests/fixtures/dataset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset.png -------------------------------------------------------------------------------- /tests/fixtures/dataset.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset.webp -------------------------------------------------------------------------------- /tests/fixtures/dataset_cloud_date_metadata.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_cloud_date_metadata.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_cloud_date_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24.06 5 | 2011-05-01T13:00:00.000000Z 6 | 000000000000_00_P000 7 | 000000000000000 8 | ORStandard2A 9 | Multi 10 | None 11 | 50 12 | 50 13 | LV2A 14 | Standard 15 | 1 16 | Corrected 17 | Off 18 | 16 19 | None 20 | GeoTIFF 21 | 22 | WV03 23 | FullSwath 24 | Forward 25 | 000000000000000 26 | 2011-05-01T13:00:00.000000Z 27 | 5.000010000000000e+03 28 | 2.000000000000000e-04 29 | 1.315000000000000e+00 30 | 1.315000000000000e+00 31 | 1.315000000000000e+00 32 | 1.286000000000000e+00 33 | 1.287000000000000e+00 34 | 1.286000000000000e+00 35 | 1.301000000000000e+00 36 | 2.229000000000000e+01 37 | 1.060000000000000e+01 38 | 3.300000000000000e+00 39 | 2.700000000000000e-02 40 | MTF 41 | R 42 | R 43 | 337 44 | 45 | 46 | 2015-01-01T00:00:00.000000Z 47 | 2015-01-01T00:00:00.000000Z 48 | WE 49 | 6.378137000000000e+06 50 | 2.982572235630000e+02 51 | 52 | 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 53 | 54 | Base Elevation 55 | 9.166000000000000e+01 56 | 0 57 | 58 | 59 | 60 | WV03 61 | Multi 62 | RPC00B 63 | 64 | 1.490000000000000e+00 65 | 5.800000000000000e-01 66 | 812 67 | 850 68 | 4.187910000000000e+01 69 | 1.257980000000000e+01 70 | 95 71 | 938 72 | 1152 73 | 1.500000000000000e-02 74 | 2.250000000000000e-02 75 | 501 76 | 77 | -6.181087000000000e-03 3.510113000000000e-02 -1.109763000000000e+00 -8.245545999999999e-02 -1.574358000000000e-04 -1.151270000000000e-05 3.785618000000000e-04 -1.617343000000000e-04 3.392421000000000e-03 1.557476000000000e-05 2.094558000000000e-07 -6.314111999999999e-08 7.033553000000000e-07 5.121700000000000e-08 2.389848000000000e-06 -1.042301000000000e-05 -1.402098000000000e-06 6.953425000000000e-08 -5.038526000000000e-07 -9.876126999999999e-08 78 | 79 | 80 | 1.000000000000000e+00 4.696998000000000e-05 -3.057960000000000e-03 -8.778793000000000e-05 -3.454986000000000e-07 1.971646000000000e-08 1.083809000000000e-06 -1.723920000000000e-06 9.397055000000000e-06 1.371016000000000e-06 1.647007000000000e-08 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 -3.685579000000000e-08 0.000000000000000e+00 -2.476523000000000e-07 0.000000000000000e+00 81 | 82 | 83 | -1.941040000000000e-03 1.012973000000000e+00 2.549412000000000e-02 3.146281000000000e-02 -7.701827000000001e-05 4.973536000000000e-04 -2.130061000000000e-04 9.757313000000000e-04 7.164101000000000e-06 -5.137094000000000e-06 -3.021765000000000e-07 9.135275000000000e-07 -5.816734000000000e-08 1.078116000000000e-07 -1.534883000000000e-07 0.000000000000000e+00 -1.318700000000000e-07 9.196606000000000e-07 -3.705506000000000e-07 0.000000000000000e+00 84 | 85 | 86 | 1.000000000000000e+00 9.641438000000000e-04 1.340215000000000e-04 -4.371442000000000e-04 4.849114000000000e-08 0.000000000000000e+00 -6.403717000000000e-08 9.014668000000000e-07 0.000000000000000e+00 -2.387648000000000e-07 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 0.000000000000000e+00 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /tests/fixtures/dataset_cog.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_cog.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_colormap.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_colormap.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_dateline.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_dateline.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_description.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_description.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_gcps.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_gcps.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_gdalcog.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_gdalcog.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_geo.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_geo.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_geom.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_geom.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_int16_nodata.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_int16_nodata.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_mars.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_mars.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_nocrs.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_nocrs.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_nodata_and_nan.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_nodata_and_nan.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_nodata_nan.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_nodata_nan.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_tiff_datetime.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_tiff_datetime.tif -------------------------------------------------------------------------------- /tests/fixtures/dataset_with_offsets.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/dataset_with_offsets.tif -------------------------------------------------------------------------------- /tests/fixtures/issue_22.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/rio-stac/219c84f0342b5ac58e3a1a68b43eb50f3f30dda3/tests/fixtures/issue_22.tif -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | """tests rio_stac.cli.""" 2 | 3 | import json 4 | import os 5 | 6 | import pystac 7 | 8 | from rio_stac.scripts.cli import stac 9 | 10 | PREFIX = os.path.join(os.path.dirname(__file__), "fixtures") 11 | 12 | 13 | def test_rio_stac_cli(runner): 14 | """Should work as expected.""" 15 | with runner.isolated_filesystem(): 16 | src_path = os.path.join(PREFIX, "dataset_cog.tif") 17 | result = runner.invoke(stac, [src_path]) 18 | assert not result.exception 19 | assert result.exit_code == 0 20 | stac_item = json.loads(result.output) 21 | assert stac_item["type"] == "Feature" 22 | assert stac_item["assets"]["asset"] 23 | assert stac_item["assets"]["asset"]["href"] == src_path 24 | assert stac_item["links"] == [] 25 | assert stac_item["stac_extensions"] == [ 26 | "https://stac-extensions.github.io/projection/v1.1.0/schema.json", 27 | "https://stac-extensions.github.io/raster/v1.1.0/schema.json", 28 | "https://stac-extensions.github.io/eo/v1.1.0/schema.json", 29 | ] 30 | assert "datetime" in stac_item["properties"] 31 | assert "proj:epsg" in stac_item["properties"] 32 | assert "raster:bands" in stac_item["assets"]["asset"] 33 | assert "eo:bands" in stac_item["assets"]["asset"] 34 | 35 | result = runner.invoke( 36 | stac, 37 | [ 38 | src_path, 39 | "--without-proj", 40 | "--without-raster", 41 | "--without-eo", 42 | "--datetime", 43 | "2010-01-01", 44 | "--id", 45 | "000001", 46 | ], 47 | ) 48 | assert not result.exception 49 | assert result.exit_code == 0 50 | stac_item = json.loads(result.output) 51 | assert stac_item["stac_extensions"] == [] 52 | assert stac_item["id"] == "000001" 53 | assert "datetime" in stac_item["properties"] 54 | assert stac_item["properties"]["datetime"] == "2010-01-01T00:00:00Z" 55 | 56 | result = runner.invoke( 57 | stac, 58 | [ 59 | src_path, 60 | "--without-proj", 61 | "--without-raster", 62 | "--without-eo", 63 | "--datetime", 64 | "2010-01-01/2010-01-02", 65 | ], 66 | ) 67 | assert not result.exception 68 | assert result.exit_code == 0 69 | stac_item = json.loads(result.output) 70 | assert stac_item["stac_extensions"] == [] 71 | assert "datetime" in stac_item["properties"] 72 | assert not stac_item["properties"]["datetime"] 73 | assert stac_item["properties"]["start_datetime"] == "2010-01-01T00:00:00Z" 74 | assert stac_item["properties"]["end_datetime"] == "2010-01-02T00:00:00Z" 75 | 76 | result = runner.invoke(stac, [src_path, "--asset-mediatype", "COG"]) 77 | assert not result.exception 78 | assert result.exit_code == 0 79 | stac_item = json.loads(result.output) 80 | assert stac_item["assets"]["asset"]["type"] == pystac.MediaType.COG 81 | 82 | result = runner.invoke(stac, [src_path, "--asset-mediatype", "auto"]) 83 | assert not result.exception 84 | assert result.exit_code == 0 85 | stac_item = json.loads(result.output) 86 | assert stac_item["assets"]["asset"]["type"] == pystac.MediaType.GEOTIFF 87 | 88 | result = runner.invoke(stac, [src_path, "--property", "comment:name=something"]) 89 | assert not result.exception 90 | assert result.exit_code == 0 91 | stac_item = json.loads(result.output) 92 | assert stac_item["properties"]["comment:name"] == "something" 93 | 94 | result = runner.invoke(stac, [src_path, "--property", "comment:something"]) 95 | assert result.exception 96 | assert result.exit_code == 2 97 | 98 | result = runner.invoke(stac, [src_path, "-o", "item.json"]) 99 | assert not result.exception 100 | assert result.exit_code == 0 101 | with open("item.json", "r") as f: 102 | stac_item = json.loads(f.read()) 103 | assert stac_item["type"] == "Feature" 104 | assert stac_item["assets"]["asset"] 105 | assert stac_item["links"] == [] 106 | assert stac_item["stac_extensions"] == [ 107 | "https://stac-extensions.github.io/projection/v1.1.0/schema.json", 108 | "https://stac-extensions.github.io/raster/v1.1.0/schema.json", 109 | "https://stac-extensions.github.io/eo/v1.1.0/schema.json", 110 | ] 111 | assert "datetime" in stac_item["properties"] 112 | assert "proj:epsg" in stac_item["properties"] 113 | assert "raster:bands" in stac_item["assets"]["asset"] 114 | 115 | with runner.isolated_filesystem(): 116 | src_path = os.path.join(PREFIX, "dataset_nocrs.tif") 117 | result = runner.invoke(stac, [src_path]) 118 | assert not result.exception 119 | assert result.exit_code == 0 120 | stac_item = json.loads(result.output) 121 | assert stac_item["type"] == "Feature" 122 | assert stac_item["assets"]["asset"] 123 | assert stac_item["assets"]["asset"]["href"] == src_path 124 | assert stac_item["links"] == [] 125 | assert stac_item["stac_extensions"] == [ 126 | "https://stac-extensions.github.io/projection/v1.1.0/schema.json", 127 | "https://stac-extensions.github.io/raster/v1.1.0/schema.json", 128 | "https://stac-extensions.github.io/eo/v1.1.0/schema.json", 129 | ] 130 | assert "datetime" in stac_item["properties"] 131 | assert "proj:epsg" in stac_item["properties"] 132 | assert "proj:projjson" not in stac_item["properties"] 133 | assert "raster:bands" in stac_item["assets"]["asset"] 134 | assert "eo:bands" in stac_item["assets"]["asset"] 135 | -------------------------------------------------------------------------------- /tests/test_create_item.py: -------------------------------------------------------------------------------- 1 | """test media type functions.""" 2 | 3 | import datetime 4 | import os 5 | 6 | import pystac 7 | import pytest 8 | import rasterio 9 | 10 | from rio_stac.stac import create_stac_item 11 | 12 | from .conftest import requires_hdf4, requires_hdf5 13 | 14 | PREFIX = os.path.join(os.path.dirname(__file__), "fixtures") 15 | input_date = datetime.datetime.now(datetime.timezone.utc) 16 | 17 | 18 | @pytest.mark.parametrize( 19 | "file", 20 | [ 21 | "dataset_nodata_nan.tif", 22 | "dataset_nodata_and_nan.tif", 23 | "dataset_cog.tif", 24 | "dataset_gdalcog.tif", 25 | "dataset_geo.tif", 26 | "dataset.tif", 27 | "dataset.jp2", 28 | "dataset.jpg", 29 | "dataset.png", 30 | "dataset.webp", 31 | "dataset_gcps.tif", 32 | "issue_22.tif", 33 | "dataset_dateline.tif", 34 | "dataset_int16_nodata.tif", 35 | ], 36 | ) 37 | def test_create_item(file): 38 | """Should run without exceptions.""" 39 | src_path = os.path.join(PREFIX, file) 40 | with rasterio.open(src_path) as src_dst: 41 | assert create_stac_item( 42 | src_dst, input_datetime=input_date, with_raster=True 43 | ).validate() 44 | 45 | 46 | @requires_hdf4 47 | def test_hdf4(): 48 | """Test hdf4.""" 49 | src_path = os.path.join(PREFIX, "dataset.hdf") 50 | with rasterio.open(src_path) as src_dst: 51 | assert create_stac_item(src_dst, input_datetime=input_date).validate() 52 | 53 | 54 | @requires_hdf5 55 | def test_hdf5(): 56 | """Test hdf5.""" 57 | src_path = os.path.join(PREFIX, "dataset.h5") 58 | with rasterio.open(src_path) as src_dst: 59 | assert create_stac_item(src_dst, input_datetime=input_date).validate() 60 | 61 | 62 | def test_create_item_options(): 63 | """Should return the correct mediatype.""" 64 | src_path = os.path.join(PREFIX, "dataset_cog.tif") 65 | 66 | # pass string 67 | assert create_stac_item(src_path, input_datetime=input_date).validate() 68 | 69 | # default COG 70 | item = create_stac_item( 71 | src_path, 72 | input_datetime=input_date, 73 | asset_media_type=pystac.MediaType.COG, 74 | with_proj=False, 75 | ) 76 | assert item.validate() 77 | item_dict = item.to_dict() 78 | assert item_dict["assets"]["asset"]["type"] == pystac.MediaType.COG 79 | assert item_dict["links"] == [] 80 | assert item_dict["stac_extensions"] == [] 81 | assert list(item_dict["properties"]) == ["datetime"] 82 | 83 | # additional extensions and properties 84 | item = create_stac_item( 85 | src_path, 86 | input_datetime=input_date, 87 | extensions=["https://stac-extensions.github.io/scientific/v1.0.0/schema.json"], 88 | properties={"sci:citation": "A nice image"}, 89 | with_proj=False, 90 | ) 91 | assert item.validate() 92 | item_dict = item.to_dict() 93 | assert item_dict["links"] == [] 94 | assert item_dict["stac_extensions"] == [ 95 | "https://stac-extensions.github.io/scientific/v1.0.0/schema.json", 96 | ] 97 | assert "datetime" in item_dict["properties"] 98 | assert "proj:epsg" not in item_dict["properties"] 99 | assert "sci:citation" in item_dict["properties"] 100 | 101 | # additional extensions and properties 102 | item = create_stac_item( 103 | src_path, 104 | input_datetime=input_date, 105 | extensions=["https://stac-extensions.github.io/scientific/v1.0.0/schema.json"], 106 | properties={"sci:citation": "A nice image"}, 107 | with_proj=True, 108 | ) 109 | assert item.validate() 110 | item_dict = item.to_dict() 111 | assert item_dict["links"] == [] 112 | assert item_dict["stac_extensions"] == [ 113 | "https://stac-extensions.github.io/scientific/v1.0.0/schema.json", 114 | "https://stac-extensions.github.io/projection/v1.1.0/schema.json", 115 | ] 116 | assert "datetime" in item_dict["properties"] 117 | assert "proj:epsg" in item_dict["properties"] 118 | assert "proj:wkt2" not in item_dict["properties"] 119 | assert "proj:projjson" not in item_dict["properties"] 120 | assert "sci:citation" in item_dict["properties"] 121 | 122 | # external assets 123 | assets = {"cog": pystac.Asset(href=src_path)} 124 | item = create_stac_item( 125 | src_path, input_datetime=input_date, assets=assets, with_proj=False 126 | ) 127 | assert item.validate() 128 | item_dict = item.to_dict() 129 | assert item_dict["assets"]["cog"] 130 | assert item_dict["links"] == [] 131 | assert item_dict["stac_extensions"] == [] 132 | assert "datetime" in item_dict["properties"] 133 | 134 | # collection 135 | item = create_stac_item( 136 | src_path, input_datetime=input_date, collection="mycollection", with_proj=False 137 | ) 138 | assert item.validate() 139 | item_dict = item.to_dict() 140 | assert item_dict["links"][0]["href"] == "mycollection" 141 | assert item_dict["stac_extensions"] == [] 142 | assert "datetime" in item_dict["properties"] 143 | assert item_dict["collection"] == "mycollection" 144 | 145 | item = create_stac_item( 146 | src_path, 147 | input_datetime=input_date, 148 | collection="mycollection", 149 | collection_url="https://stac.somewhere.io/mycollection.json", 150 | with_proj=False, 151 | ) 152 | assert item.validate() 153 | item_dict = item.to_dict() 154 | assert item_dict["links"][0]["href"] == "https://stac.somewhere.io/mycollection.json" 155 | assert item_dict["stac_extensions"] == [] 156 | assert "datetime" in item_dict["properties"] 157 | assert item_dict["collection"] == "mycollection" 158 | 159 | 160 | def test_proj_without_proj(): 161 | """Use the Proj extension without proj info.""" 162 | src_path = os.path.join(PREFIX, "dataset.tif") 163 | 164 | # additional extensions and properties 165 | item = create_stac_item( 166 | src_path, 167 | input_datetime=input_date, 168 | with_proj=True, 169 | ) 170 | assert item.validate() 171 | item_dict = item.to_dict() 172 | assert item_dict["links"] == [] 173 | assert item_dict["stac_extensions"] == [ 174 | "https://stac-extensions.github.io/projection/v1.1.0/schema.json", 175 | ] 176 | assert "datetime" in item_dict["properties"] 177 | # EPSG should be set to None 178 | assert not item_dict["properties"]["proj:epsg"] 179 | assert item_dict["properties"]["proj:bbox"] 180 | 181 | 182 | def test_create_item_raster(): 183 | """Should return a valid item with raster properties.""" 184 | src_path = os.path.join(PREFIX, "dataset_cog.tif") 185 | item = create_stac_item( 186 | src_path, 187 | input_datetime=input_date, 188 | with_raster=True, 189 | raster_max_size=128, 190 | ) 191 | assert item.validate() 192 | item_dict = item.to_dict() 193 | assert item_dict["links"] == [] 194 | assert item_dict["stac_extensions"] == [ 195 | "https://stac-extensions.github.io/raster/v1.1.0/schema.json", 196 | ] 197 | assert "raster:bands" in item_dict["assets"]["asset"] 198 | assert len(item_dict["assets"]["asset"]["raster:bands"]) == 1 199 | 200 | # Nodata=None not in the properties 201 | assert "nodata" not in item_dict["assets"]["asset"]["raster:bands"][0] 202 | 203 | # Unit=None not in the properties 204 | assert "unit" not in item_dict["assets"]["asset"]["raster:bands"][0] 205 | 206 | assert item_dict["assets"]["asset"]["raster:bands"][0]["sampling"] in [ 207 | "point", 208 | "area", 209 | ] 210 | 211 | src_path = os.path.join(PREFIX, "dataset_nodata_nan.tif") 212 | item = create_stac_item(src_path, input_datetime=input_date, with_raster=True) 213 | assert item.validate() 214 | item_dict = item.to_dict() 215 | assert item_dict["stac_extensions"] == [ 216 | "https://stac-extensions.github.io/raster/v1.1.0/schema.json", 217 | ] 218 | assert "raster:bands" in item_dict["assets"]["asset"] 219 | 220 | assert item_dict["assets"]["asset"]["raster:bands"][0]["nodata"] == "nan" 221 | 222 | src_path = os.path.join(PREFIX, "dataset_with_offsets.tif") 223 | item = create_stac_item( 224 | src_path, 225 | input_datetime=input_date, 226 | with_raster=True, 227 | ) 228 | assert item.validate() 229 | item_dict = item.to_dict() 230 | assert item_dict["stac_extensions"] == [ 231 | "https://stac-extensions.github.io/raster/v1.1.0/schema.json", 232 | ] 233 | assert "raster:bands" in item_dict["assets"]["asset"] 234 | assert item_dict["assets"]["asset"]["raster:bands"][0]["scale"] == 0.0001 235 | assert item_dict["assets"]["asset"]["raster:bands"][0]["offset"] == 1000.0 236 | 237 | 238 | def test_create_item_raster_with_gcps(): 239 | """Should return a valid item with raster properties.""" 240 | src_path = os.path.join(PREFIX, "dataset_gcps.tif") 241 | item = create_stac_item( 242 | src_path, input_datetime=input_date, with_raster=True, with_proj=True 243 | ) 244 | assert item.validate() 245 | 246 | 247 | @pytest.mark.xfail 248 | def test_dateline_polygon_split(): 249 | """make sure we return a multipolygon.""" 250 | src_path = os.path.join(PREFIX, "dataset_dateline.tif") 251 | item = create_stac_item( 252 | src_path, input_datetime=input_date, with_raster=True, with_proj=True 253 | ) 254 | item_dict = item.to_dict() 255 | assert item_dict["geometry"]["type"] == "MultiPolygon" 256 | 257 | 258 | def test_negative_nodata(): 259 | """Make sure we catch valid nodata (issue 33).""" 260 | src_path = os.path.join(PREFIX, "dataset_int16_nodata.tif") 261 | item = create_stac_item( 262 | src_path, input_datetime=input_date, with_raster=True, with_proj=True 263 | ) 264 | item_dict = item.to_dict() 265 | assert item_dict["assets"]["asset"]["raster:bands"][0]["nodata"] == -9999 266 | 267 | 268 | def test_create_item_eo(): 269 | """Should return a valid item with eo properties.""" 270 | src_path = os.path.join(PREFIX, "dataset_cog.tif") 271 | item = create_stac_item(src_path, with_eo=True) 272 | assert item.validate() 273 | item_dict = item.to_dict() 274 | assert item_dict["links"] == [] 275 | assert item_dict["stac_extensions"] == [ 276 | "https://stac-extensions.github.io/eo/v1.1.0/schema.json", 277 | ] 278 | assert "eo:bands" in item_dict["assets"]["asset"] 279 | assert len(item_dict["assets"]["asset"]["eo:bands"]) == 1 280 | assert item_dict["assets"]["asset"]["eo:bands"][0] == { 281 | "name": "b1", 282 | "description": "gray", 283 | } 284 | 285 | src_path = os.path.join(PREFIX, "dataset_description.tif") 286 | item = create_stac_item(src_path, with_eo=True) 287 | assert item.validate() 288 | item_dict = item.to_dict() 289 | assert len(item_dict["assets"]["asset"]["eo:bands"]) == 1 290 | 291 | assert item_dict["assets"]["asset"]["eo:bands"][0] == { 292 | "name": "b1", 293 | "description": "b1", 294 | } 295 | 296 | with rasterio.Env(GDAL_DISABLE_READDIR_ON_OPEN="FALSE"): 297 | src_path = os.path.join(PREFIX, "dataset_cloud_date_metadata.tif") 298 | item = create_stac_item(src_path, with_eo=True) 299 | assert item.validate() 300 | item_dict = item.to_dict() 301 | assert "eo:cloud_cover" in item_dict["properties"] 302 | 303 | 304 | def test_create_item_datetime(): 305 | """Should return a valid item with datetime from IMD.""" 306 | with rasterio.Env(GDAL_DISABLE_READDIR_ON_OPEN="FALSE"): 307 | src_path = os.path.join(PREFIX, "dataset_cloud_date_metadata.tif") 308 | item = create_stac_item(src_path, with_eo=True) 309 | assert item.validate() 310 | item_dict = item.to_dict() 311 | assert item_dict["properties"]["datetime"] == "2011-05-01T13:00:00Z" 312 | 313 | src_path = os.path.join(PREFIX, "dataset_tiff_datetime.tif") 314 | item = create_stac_item(src_path, with_eo=True) 315 | assert item.validate() 316 | item_dict = item.to_dict() 317 | assert item_dict["properties"]["datetime"] == "2023-10-30T11:37:13Z" 318 | 319 | 320 | def test_densify_geom(): 321 | """Should run without exceptions.""" 322 | src_path = os.path.join(PREFIX, "dataset_geom.tif") 323 | 324 | item = create_stac_item(src_path, with_eo=True) 325 | assert item.validate() 326 | item_dict = item.to_dict() 327 | 328 | item_dens = create_stac_item(src_path, geom_densify_pts=21) 329 | assert item_dens.validate() 330 | item_dens_dict = item_dens.to_dict() 331 | 332 | assert item_dict["bbox"] != item_dens_dict["bbox"] 333 | 334 | 335 | def test_mars_dataset(): 336 | """Test with Mars Dataset.""" 337 | MARS2000_SPHERE = rasterio.crs.CRS.from_proj4("+proj=longlat +R=3396190 +no_defs") 338 | src_path = os.path.join(PREFIX, "dataset_mars.tif") 339 | 340 | item = create_stac_item(src_path, geographic_crs=MARS2000_SPHERE, with_proj=True) 341 | assert item.validate() 342 | item_dict = item.to_dict() 343 | 344 | assert not item_dict["properties"].get("proj:epsg") 345 | assert ( 346 | "proj:projjson" in item_dict["properties"] 347 | or "proj:wkt2" in item_dict["properties"] 348 | ) 349 | -------------------------------------------------------------------------------- /tests/test_mediatype.py: -------------------------------------------------------------------------------- 1 | """test media type functions.""" 2 | 3 | import os 4 | 5 | import pystac 6 | import pytest 7 | import rasterio 8 | 9 | from rio_stac.stac import get_media_type 10 | 11 | from .conftest import requires_hdf4, requires_hdf5 12 | 13 | PREFIX = os.path.join(os.path.dirname(__file__), "fixtures") 14 | 15 | 16 | @pytest.mark.parametrize( 17 | "file,mediatype", 18 | [ 19 | ["dataset_cog.tif", pystac.MediaType.GEOTIFF], 20 | ["dataset_gdalcog.tif", pystac.MediaType.GEOTIFF], 21 | ["dataset_geo.tif", pystac.MediaType.GEOTIFF], 22 | ["dataset.tif", pystac.MediaType.TIFF], 23 | ["dataset.jp2", pystac.MediaType.JPEG2000], 24 | ["dataset.jpg", pystac.MediaType.JPEG], 25 | ["dataset.png", pystac.MediaType.PNG], 26 | ], 27 | ) 28 | def test_get_mediatype(file, mediatype): 29 | """Should return the correct mediatype.""" 30 | src_path = os.path.join(PREFIX, file) 31 | with rasterio.open(src_path) as src_dst: 32 | assert get_media_type(src_dst) == mediatype 33 | 34 | 35 | @requires_hdf4 36 | def test_hdf4(): 37 | """Test hdf4 mediatype.""" 38 | src_path = os.path.join(PREFIX, "dataset.hdf") 39 | with rasterio.open(src_path) as src_dst: 40 | assert get_media_type(src_dst) == pystac.MediaType.HDF 41 | 42 | 43 | @requires_hdf5 44 | def test_hdf5(): 45 | """Test hdf5 mediatype.""" 46 | src_path = os.path.join(PREFIX, "dataset.h5") 47 | with rasterio.open(src_path) as src_dst: 48 | assert get_media_type(src_dst) == pystac.MediaType.HDF5 49 | 50 | 51 | def test_unknow(): 52 | """Should warn when media type is not found.""" 53 | src_path = os.path.join(PREFIX, "dataset.webp") 54 | with rasterio.open(src_path) as src_dst: 55 | with pytest.warns(UserWarning): 56 | assert not get_media_type(src_dst) 57 | --------------------------------------------------------------------------------