Skip to content
Snippets Groups Projects
Commit 1091fdca authored by Mostafa Hadizadeh's avatar Mostafa Hadizadeh
Browse files

fix pipeline issue

parent abde4846
No related branches found
No related tags found
No related merge requests found
Pipeline #316082 passed with stages
in 6 minutes and 35 seconds
File deleted
# Config file for automatic testing at travis-ci.com
language: python
python:
- 3.8
- 3.7
- 3.6
# Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install: pip install -U tox-travis
# Command to run tests, e.g. python setup.py test
script: tox
# Assuming you have installed the travis-ci CLI tool, after you
# create the Github repo and add it to Travis, run the
# following command to finish PyPI deployment setup:
# $ travis encrypt --add deploy.password
deploy:
provider: pypi
distributions: sdist bdist_wheel
user: frost2stac
password:
secure: PLEASE_REPLACE_ME
on:
tags: true
repo: CAT4KIT/frost2stac
python: 3.8
=======
Credits
=======
Development Lead
----------------
* Mostafa Hadizadeh <mostafa.hadizadeh@kit.edu>
Contributors
------------
None yet. Why not be the first?
.. highlight:: shell
============
Contributing
============
Contributions are welcome, and they are greatly appreciated! Every little bit
helps, and credit will always be given.
You can contribute in many ways:
Types of Contributions
----------------------
Report Bugs
~~~~~~~~~~~
Report bugs at https://codebase.helmholtz.cloud/CAT4KIT/sta2stac/issues.
If you are reporting a bug, please include:
* Your operating system name and version.
* Any details about your local setup that might be helpful in troubleshooting.
* Detailed steps to reproduce the bug.
Fix Bugs
~~~~~~~~
Look through the Helmholtz Gitlab issues for bugs. Anything tagged with "bug" and "help
wanted" is open to whoever wants to implement it.
Implement Features
~~~~~~~~~~~~~~~~~~
Look through the Helmholtz Gitlab issues for features. Anything tagged with "enhancement"
and "help wanted" is open to whoever wants to implement it.
Write Documentation
~~~~~~~~~~~~~~~~~~~
sta2stac could always use more documentation, whether as part of the
official sta2stac docs, in docstrings, or even on the web in blog posts,
articles, and such.
Submit Feedback
~~~~~~~~~~~~~~~
The best way to send feedback is to file an issue at https://codebase.helmholtz.cloud/CAT4KIT/sta2stac/issues.
If you are proposing a feature:
* Explain in detail how it would work.
* Keep the scope as narrow as possible, to make it easier to implement.
* Remember that this is a volunteer-driven project, and that contributions
are welcome :)
Get Started!
------------
Ready to contribute? Here's how to set up `sta2stac` for local development.
1. Fork the `sta2stac` repo on Helmholtz Gitlab.
2. Clone your fork locally::
$ git clone git@codebase.helmholtz.cloud:sta2stac/sta2stac.git
3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
$ mkvirtualenv sta2stac
$ cd sta2stac/
$ python setup.py develop
4. Create a branch for local development::
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
5. When you're done making changes, check that your changes pass flake8 and the
tests, including testing other Python versions with tox::
$ flake8 sta2stac tests
$ python setup.py test or pytest
$ tox
To get flake8 and tox, just pip install them into your virtualenv.
6. Commit your changes and push your branch to Helmholtz Gitlab::
$ git add .
$ git commit -m "Your detailed description of your changes."
$ git push origin name-of-your-bugfix-or-feature
7. Submit a pull request through the Helmholtz Gitlab website.
Pull Request Guidelines
-----------------------
Before you submit a pull request, check that it meets these guidelines:
1. The pull request should include tests.
2. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring, and add the
feature to the list in README.rst.
3. The pull request should work for Python 3.5, 3.6, 3.7 and 3.8, and for PyPy. Check
https://travis-ci.com/CAT4KIT/sta2stac/pull_requests
and make sure that the tests pass for all supported Python versions.
Tips
----
To run a subset of tests::
$ pytest tests.test_sta2stac
Deploying
---------
A reminder for the maintainers on how to deploy.
Make sure all your changes are committed (including an entry in HISTORY.rst).
Then run::
$ bump2version patch # possible: major / minor / patch
$ git push
$ git push --tags
Travis will then deploy to PyPI if tests pass.
......@@ -285,5 +285,3 @@ Code from exclusive appropriation.
All other changes or additions to this Appendix require the production of a new
EUPL version.
==========
STA2STAC
==========
.. image:: https://codebase.helmholtz.cloud/cat4kit/ds2stac/sta2stac/-/raw/main/sta2stac.png
=============================
.. image:: https://img.shields.io/pypi/v/sta2stac.svg
:target: https://pypi.python.org/pypi/sta2stac
.. image:: https://readthedocs.org/projects/sta2stac/badge/?version=latest
:target: https://sta2stac.readthedocs.io/en/latest/?version=latest
:alt: Documentation Status
STAC specification is a method of exposing spatial and temporal data collections in a standardized manner. Specifically, the `SpatioTemporal Asset Catalog (STAC) <https://stacspec.org/en>`_ specification describes and catalogs spatiotemporal assets using a common structure.
This package creates STAC metadata by harvesting time series data from the `Fraunhofer Open-source SensorThings Server (Frost Server) <https://www.iosb.fraunhofer.de/en/projects-and-products/frost-server.html>`_. After creating STAC Catalogs, Collections, and Items, it imports them into `pgSTAC <https://stac-utils.github.io/pgstac/pgstac/>`_ and `STAC-FastAPI <https://stac-utils.github.io/stac-fastapi/>`_.
* Free software: EUPL-1.2
* Documentation: https://sta2stac.readthedocs.io.
Quick start
-------------
To install STA2STAC, run this command in your terminal:
.. code-block:: console
pip install sta2stac
Or for installing manually, you can either clone the public repository:
.. code-block:: console
git clone git://codebase.helmholtz.cloud/CAT4KIT/sta2stac
cd sta2stac
python -m venv venv
source venv/bin/activate
pip install -r requirements_dev.txt
Use case:
You may use the package by following the `link <https://codebase.helmholtz.cloud/cat4kit/sta2stac/-/blob/main/docs/usage.rst>`_ instructions. .
Copyright
---------
Copyright © 2023 Karlsruher Institut für Technologie
Licensed under the EUPL-1.2-or-later
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the EUPL-1.2 license for more details.
You should have received a copy of the EUPL-1.2 license along with this
program. If not, see https://www.eupl.eu/.
.. include:: ../AUTHORS.rst
.. include:: ../HISTORY.rst
.. include:: ../README.rst
=====
Usage
=====
Overview
-------------
To use STA2STAC in a project::
import sta2stac
from sta2stac import sta2stac
sta2stac.Converter(
main_url="https://sensorthings.imk-ifu.kit.edu/",
stac=True,
stac_id="an ID for the main STAC catalog",
stac_dir="/path/to/save/stac/catalogs/",
stapi_version="1.1" or "1.0",
stac_description="Description for the main STAC catalog",
stac_catalog_dynamic=True,
)
Output::
Things ID: 65 - FeatureOfInterest ID: 27
Things ID: 66 - FeatureOfInterest ID: 28
Things ID: 67 - FeatureOfInterest ID: 30
Things ID: 68 - FeatureOfInterest ID: 29
Things ID: 69 - FeatureOfInterest ID: 33
Things ID: 70 - FeatureOfInterest ID: 34
Things ID: 71 - FeatureOfInterest ID: 35
Things ID: 111 - FeatureOfInterest ID: 36
Things ID: 112 - FeatureOfInterest ID: ❌
main_url
----------------
The `main_url` for harvesting from Frost Server's SensorThingsAPI is provided. It is important to note that in STA2STAC, Things are treated as STAC Items and FeatureOfInterests acts as a STAC Collection.
It's a string type url.
stac
----------------
The boolean feature `stac` is used to permit creation of STAC Catalog, Collections, and Items.
stac_id
----------------
This feature is used to name STAC catalog ID.
stac_dir
----------------
The Directory for saving created STAC Catalogs, Collections, and Items
stapi_version
----------------
There are two options for the string type feature `stapi_version`: "1.0" and "1.1".
Otherwise, SensorThingsAPI version 1.1 is used by STA2STAC.
stac_description
------------------
This option is used to add STAC Catalog description.
stac_catalog_dynamic
-----------------------
The `stac_catalog_dynamic` option enables us to choose between the Static and Dynamic STAC Catalog types.
pip
bump2version
wheel
watchdog
flake8
tox
coverage
Sphinx
twine
pre-commit
versioneer
click
pytest
black
pystac
tqdm
pytz
pypgstac
psycopg
psycopg_pool
urllib3==1.26.6
\ No newline at end of file
[metadata]
name = STA2STAC
version = 0.1.0
description = A package to create STAC metada catalog using The Fraunhofer Open-source SensorThings Server (Frost Server)
long_description = file: README.rst
long_description_content_type = text/x-rst
url = https://codebase.helmholtz.cloud/CAT4KIT/sta2stac
author = Mostafa Hadizadeh
author_email = mostafa.hadizadeh@kit.edu
keywords =
CAT4KIT
KIT
EXU-Vorhaben-Research-Data-Management
helmholtz
license = EUPL-1.2
classifiers =
Environment :: Web Environment
Intended Audience :: Developers
License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Topic :: Internet :: WWW/HTTP
Topic :: Internet :: WWW/HTTP :: Dynamic Content
Typing :: Typed
project_urls =
Documentation = https://sta2stac.readthedocs.io
Source = https://codebase.helmholtz.cloud/CAT4KIT/sta2stac
Tracker = https://codebase.helmholtz.cloud/CAT4KIT/sta2stac/issues/
[options]
include_package_data = true
python_requires = >=3.7
packages = find:
[options.extras_require]
testsite =
tox
requests
types-requests
isort==5.9.3
black==22.3.0
blackdoc==0.3.4
flake8==3.9.2
pre-commit
mypy
pytest-django
pytest-cov
dev =
%(testsite)s
Sphinx
mde-sphinx-ext
autodocsumm
sphinxcontrib-django
[mypy]
ignore_missing_imports = True
[versioneer]
VCS = git
style = pep440
versionfile_source = sta2stac/_version.py
versionfile_build = sta2stac/_version.py
tag_prefix =
parentdir_prefix =
[bdist_wheel]
universal = 1
[flake8]
exclude = docs
ignore =
# whitespace before ':' - doesn't work well with black
E203,
# module level import not at top of file
E402,
# line too long - let black worry about that
E501,
# line break before binary operator
W503,
# do not use bare 'except'
E722,
[tool:pytest]
collect_ignore = ['setup.py']
sta2stac.png

66.8 KiB

"""Console script for sta2stac."""
import sys
import click
@click.command()
def main(args=None):
"""Console script for sta2stac."""
click.echo(
"Replace this message by putting your code into " "sta2stac.cli.main"
)
click.echo("See click documentation at https://click.palletsprojects.com/")
return 0
if __name__ == "__main__":
sys.exit(main()) # pragma: no cover
import os
variabl_list = [
["APP_HOST", "0.0.0.0"],
["APP_PORT", "8082"],
["RELOAD", "true"],
["ENVIRONMENT", "local"],
["POSTGRES_USER", "hadizadeh-m"],
["POSTGRES_PASS", ""],
["POSTGRES_DBNAME", "postgis"],
["POSTGRES_HOST_READER", "localhost"],
["POSTGRES_HOST_WRITER", "localhost"],
["POSTGRES_PORT", "5432"],
["WEB_CONCURRENCY", "10"],
["VSI_CACHE", "TRUE"],
["GDAL_HTTP_MERGE_CONSECUTIVE_RANGES", "YES"],
["GDAL_DISABLE_READDIR_ON_OPEN", "EMPTY_DIR"],
["DB_MIN_CONN_SIZE", "1"],
["DB_MAX_CONN_SIZE", "10"],
["USE_API_HYDRATE", "${USE_API_HYDRATE:-false}"],
["POSTGRES_USER", "hadizadeh-m"],
["POSTGRES_PASSWORD", ""],
["POSTGRES_DB", "postgis"],
["PGUSER", "hadizadeh-m"],
["PGPASSWORD", ""],
["PGHOST", "localhost"],
["PGDATABASE", "postgis"],
]
# Define all variabels as an array and for-loop over the array
def run_all():
for i in variabl_list:
if os.getenv(i[0]) is None:
os.environ[i[0]] = i[1]
featureofinterest_id = "?$select=@iot.id&$expand=Datastreams($select=@iot.id;$top=1;$expand=Observations($top=1;$select=@iot.id))"
featureofinterest_id_empty = (
"?$select=@iot.id&$expand=Observations($select=@iot.id;$top=1)"
)
phenomenonTime_string = (
"?$select=@iot.id&$expand=Datastreams($select=@iot.id,phenomenonTime)"
)
epilon = 0.000001
import json
import os
from urllib.request import urlopen
def Things_url(url, version=None):
"""This function serves as creating Things url"""
if "/v1.0" not in url and "/v1.1" not in url:
if version is not None:
final_url = url + "/v" + version + "/Things"
else:
final_url = url + "/v1.1/Things"
else:
final_url = url + "/Things"
return final_url
def FeaturesOfInterest_url(url, version=None):
"""This function serves as creating FeaturesOfInterests url"""
if "/v1.0" not in url and "/v1.1" not in url:
if version is not None:
final_url = url + "/v" + version + "/FeaturesOfInterest"
else:
final_url = url + "/v1.1/FeaturesOfInterest"
else:
final_url = url + "/FeaturesOfInterest"
return final_url
def Observations_url(url, version=None):
"""This function serves as creating Observations url"""
if "/v1.0" not in url and "/v1.1" not in url:
if version is not None:
final_url = url + "/v" + version + "/Observations"
else:
final_url = url + "/v1.1/Observations"
else:
final_url = url + "/Observations"
return final_url
def Datastreams_url(url, version=None):
"""This function serves as creating Datastreams url"""
if "/v1.0" not in url and "/v1.1" not in url:
if version is not None:
final_url = url + "/v" + version + "/Datastreams"
else:
final_url = url + "/v1.1/Datastreams"
else:
final_url = url + "/Datastreams"
return final_url
def json_reader(url, additional):
"""It reads json file and give back the content of json"""
content = urlopen("".join((url + additional).split()))
json_ = json.loads(content.read())
return json_
def json_loader(dir, stac_cat_col_name):
"""It reads json file and give back the path and content of json"""
f = open(os.path.join(dir, stac_cat_col_name))
path = os.path.join(dir, stac_cat_col_name)
data = json.load(f)
return path, data
"""Main module."""
import os
from datetime import datetime
import pystac
import pytz
from pypgstac.db import PgstacDB
from pypgstac.load import Loader, Methods
from . import config_pgstac, constants, funcs
class Converter(object):
def __init__(
self,
main_url, # SensorThingsAPI address
stac=False, # Permitting creation of STAC catalogs
stac_dir=None, # Directory saving of created STAC catalogs
stac_id=None, # STAC catalog ID
stac_description=None, # STAC catalog description
stapi_version=None, # Choosing SensorThingsAPI version between 1.0 and 1.1
stac_catalog_dynamic=False, # Choosing STAC catalog type between Static and Dynamic
):
self.Things_FeatureOfInterest_id = []
self.phenomenonTime_data = []
self.catalog = dict() # Main STAC catalog
if stapi_version is not None:
self.stapi_version = stapi_version
else:
self.stapi_version = None
# A function for harvesting the data
self.id_detector(main_url)
# Just for showing the summary in Terminal
self.datetime_catch(main_url)
self.stac = stac
self.stac_id = stac_id
if stac is True:
"""In this part STAC catalogs will be created"""
# Main STAC catalog for linking other items and collections
self.catalog[stac_id] = pystac.Catalog(
id=stac_id,
description=stac_description
+ "[Link to SensorThingsAPI]("
+ funcs.Things_url(main_url, version=self.stapi_version)
+ ")",
)
# Running the function for creating STAC Catalog, Collections , and Items
self.stac_creator(main_url)
# Saving Catalog in the local store
self.catalog[stac_id].normalize_hrefs(
os.path.join(stac_dir, "stac")
)
self.catalog[stac_id].save(
catalog_type=pystac.CatalogType.SELF_CONTAINED
)
if stac_catalog_dynamic is True:
"""In this part STAC catalogs ingests into pgSTAC and STACFastAPI"""
config_pgstac.run_all() # This enables the confiduration of pgSTAC
# First of all catalog should be opened
catalog_json_path, catalog_json_data = funcs.json_loader(
stac_dir, "stac/catalog.json"
)
# pgSTAC database will be loaded here
loader = Loader(db=PgstacDB(dsn=""))
# Each collection and item that are linked to the catalog through 'links' is extracted.
for dc in catalog_json_data["links"]:
if dc["rel"] == "item":
try:
loader.load_items(
str(
os.path.join(
stac_dir,
"stac/" + dc["href"].replace("./", ""),
)
),
Methods.insert,
)
except:
continue
print("|____", dc["href"])
# 'child' means Collection in Catalog json file
if dc["rel"] == "child":
self.dynamic_ingester(
loader,
dc["href"],
stac_dir,
"stac/" + dc["href"].replace("./", ""),
)
def dynamic_ingester(self, loaderx, param, stac_dirx, address_coll):
"""This is a function for ingesting collections
into pgSTAC specifically for nested datasets"""
collection_josn_path, collection_josn_data = funcs.json_loader(
stac_dirx, address_coll
)
item_collection_list = [
ci["rel"] for ci in collection_josn_data["links"]
]
if (
"child" in item_collection_list
): # To ensure collection exists in 'links'
item_collection_list = [] # Considered empty to prevent recursion
for ci in collection_josn_data["links"]:
if ci["rel"] == "child":
try:
self.dynamic_ingester(
loaderx,
ci["href"],
stac_dirx,
collection_josn_path.replace(
"collection.json", "/"
)
+ ci["href"].replace("./", ""),
)
except:
continue
else:
item_collection_list = [] # Considered empty to prevent recursion
loaderx.load_collections(
str(os.path.join(stac_dirx, collection_josn_path)),
Methods.insert,
)
print(param)
for ci in collection_josn_data["links"]:
if ci["rel"] == "item":
try:
loaderx.load_items(
str(
os.path.join(
stac_dirx,
collection_josn_path.replace(
"collection.json", "/"
)
+ ci["href"].replace("./", ""),
)
),
Methods.insert,
)
print("|____", ci["href"])
except:
continue
def id_detector(self, url):
"""A function for harvesting Frost server time series data.
Things are considered STAC Items and FeatureOfInterests acts
as STAC Collections"""
observation_json = funcs.json_reader(
funcs.Things_url(url, version=self.stapi_version),
constants.featureofinterest_id,
)
for thing in range(observation_json["@iot.count"]):
if observation_json["value"][thing]["Datastreams"]:
if observation_json["value"][thing]["Datastreams"][0][
"Observations"
]:
featureofinterest_json = funcs.json_reader(
funcs.Observations_url(url, self.stapi_version),
"("
+ str(
observation_json["value"][thing]["Datastreams"][0][
"Observations"
][0]["@iot.id"]
)
+ ")/FeatureOfInterest",
)
else:
datastream_json = funcs.json_reader(
funcs.Things_url(url, self.stapi_version),
"("
+ str(observation_json["value"][thing]["@iot.id"])
+ ")/Datastreams"
+ constants.featureofinterest_id_empty,
)
for datastream in range(datastream_json["@iot.count"]):
if datastream_json["value"][datastream][
"Observations"
]:
featureofinterest_json = funcs.json_reader(
funcs.Observations_url(
url, self.stapi_version
),
"("
+ str(
datastream_json["value"][datastream][
"Observations"
][0]["@iot.id"]
)
+ ")/FeatureOfInterest",
)
print(
"Things ID: ",
observation_json["value"][thing]["@iot.id"],
" < - > FeatureOfInterest ID:",
featureofinterest_json["@iot.id"],
)
self.Things_FeatureOfInterest_id.append(
[
observation_json["value"][thing]["@iot.id"],
featureofinterest_json["@iot.id"],
]
)
else:
featureofinterest_json["@iot.id"] = ""
print(
"Things ID: ",
observation_json["value"][thing]["@iot.id"],
" < - > FeatureOfInterest ID:",
featureofinterest_json["@iot.id"],
)
def datetime_catch(self, url):
"""This function catchs `phenomenonTime` from
Datastreams"""
phenomenonTime_json = funcs.json_reader(
funcs.Things_url(url, self.stapi_version),
constants.phenomenonTime_string,
)
for i in range(phenomenonTime_json["@iot.count"]):
self.phenomenonTime_data = []
for j in range(
phenomenonTime_json["value"][i]["Datastreams@iot.count"]
):
if (
"phenomenonTime"
in phenomenonTime_json["value"][i]["Datastreams"][j]
):
if (
"/"
in phenomenonTime_json["value"][i]["Datastreams"][j][
"phenomenonTime"
]
):
replaced1, replaced2 = phenomenonTime_json["value"][i][
"Datastreams"
][j]["phenomenonTime"].split("/")
self.phenomenonTime_data.append(
datetime.strptime(
replaced1, "%Y-%m-%dT%H:%M:%SZ"
).replace(tzinfo=pytz.utc)
)
self.phenomenonTime_data.append(
datetime.strptime(
replaced2, "%Y-%m-%dT%H:%M:%SZ"
).replace(tzinfo=pytz.utc)
)
for k in self.Things_FeatureOfInterest_id:
if k[0] == phenomenonTime_json["value"][i]["@iot.id"]:
k.append(self.phenomenonTime_data)
def stac_creator(self, url):
"""A function for creating STAC Collections and Items"""
# creation of collection
for i in self.Things_FeatureOfInterest_id:
thing_json = funcs.json_reader(
funcs.Things_url(url, self.stapi_version),
"(" + str(i[0]) + ")",
)
featureofinterest_json = funcs.json_reader(
funcs.FeaturesOfInterest_url(url, self.stapi_version),
"(" + str(i[1]) + ")",
)
collection_bbox = [
featureofinterest_json["feature"]["coordinates"][0]
- constants.epilon,
featureofinterest_json["feature"]["coordinates"][1]
- constants.epilon,
featureofinterest_json["feature"]["coordinates"][0]
+ constants.epilon,
featureofinterest_json["feature"]["coordinates"][1]
+ constants.epilon,
]
collection_interval_time = sorted(i[2])
collection_interval_final_time = [
collection_interval_time[0],
collection_interval_time[-1],
]
spatial_extent = pystac.SpatialExtent(bboxes=[collection_bbox])
temporal_extent = pystac.TemporalExtent(
intervals=[collection_interval_final_time]
)
self.catalog[featureofinterest_json["name"]] = pystac.Collection(
id=featureofinterest_json["name"],
extent=pystac.Extent(
spatial=spatial_extent, temporal=temporal_extent
),
description=thing_json["name"],
)
item = pystac.Item(
id=thing_json["name"],
geometry=featureofinterest_json["feature"],
bbox=featureofinterest_json["feature"]["coordinates"],
datetime=collection_interval_time[0],
properties={},
)
# self.catalog[featureofinterest_json["name"]].extent = pystac.Extent(
# spatial=spatial_extent, temporal=temporal_extent
# )
item.add_asset(
key=thing_json["name"],
asset=pystac.Asset(
href=featureofinterest_json[
"Observations@iot.navigationLink"
],
# title=without_slash,
media_type=pystac.MediaType.GEOJSON,
),
)
self.catalog[featureofinterest_json["name"]].add_item(item)
self.catalog[self.stac_id].add_child(
self.catalog[featureofinterest_json["name"]]
)
"""Unit test package for sta2stac."""
#Because SSL certification of sensorthingsapi is expired, we add this
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
import sys
sys.path.append('../')
from sta2stac import sta2stac
sta2stac.Converter(
main_url="https://sensorthings.imk-ifu.kit.edu/",
stac=True,
stac_id="an ID for the main STAC catalog",
stac_dir="/app/stac/",
stapi_version="1.1",
stac_description="Description for the main STAC catalog",
stac_catalog_dynamic=True,
)
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment