├── .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 |
5 |
6 |
7 | Create STAC Items from raster datasets.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
--------------------------------------------------------------------------------