From fcc1bd70c0aa1d56540b3abc8e8fe8c8586e17dd Mon Sep 17 00:00:00 2001 From: Rustam Antia Date: Sat, 16 Nov 2024 01:21:23 +0100 Subject: [PATCH 1/3] Replace custom __init__ logic with validator approach in DSCreatorFromSource --- .../creator_from_source_dataset_base.py | 80 ++++++++----------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/geographer/creator_from_source_dataset_base.py b/geographer/creator_from_source_dataset_base.py index 00790e8f..c532815f 100644 --- a/geographer/creator_from_source_dataset_base.py +++ b/geographer/creator_from_source_dataset_base.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Dict, List, Optional -from pydantic import BaseModel, Field, PrivateAttr +from pydantic import BaseModel, Field, PrivateAttr, field_validator, model_validator from geographer.base_model_dict_conversion.save_load_base_model_mixin import ( SaveAndLoadBaseModelMixIn, @@ -37,17 +37,39 @@ class Config: extra = "allow" underscore_attrs_are_private = True - def __init__(self, **data): - """Initialize from data. - - Args: - data: data - """ - super().__init__(**data) - self._source_connector = None - self._target_connector = None - self._set_source_connector() - self._set_target_connector() + @field_validator("source_data_dir", mode="before") + def validate_source_data_dir(cls, value: Path) -> Path: + """Ensure source_data_dir is a valid path.""" + if not value.is_dir(): + raise ValueError(f"Invalid source_data_dir: {value}") + return value + + @model_validator(mode="after") + def validate_connectors(self) -> "DSCreatorFromSource": + """Initialize and validate connectors.""" + if self.source_data_dir: + self._source_connector = Connector.from_data_dir(self.source_data_dir) + + if self.target_data_dir: + connector_file_paths_exist = [ + (self.target_data_dir / DEFAULT_CONNECTOR_DIR_NAME / filename).is_file() + for filename in INFERRED_PATH_ATTR_FILENAMES.values() + ] + + if all(connector_file_paths_exist): + self._target_connector = Connector.from_data_dir(self.target_data_dir) + elif not any(connector_file_paths_exist): + self._target_connector = ( + self._source_connector.empty_connector_same_format( + self.target_data_dir + ) + ) + else: + raise ValueError( + "Corrupted target dataset: only some of the connector files exist." + ) + + return self @abstractmethod def _create(self, *args, **kwargs) -> Connector: @@ -79,21 +101,11 @@ def save(self): @property def source_connector(self): """Connector in source_data_dir.""" - if ( - self._source_connector is None - or self._source_connector.rasters_dir.parent != self.source_data_dir - ): - self._set_source_connector() return self._source_connector @property def target_connector(self): """Connector in target_data_dir.""" - if ( - self._target_connector is None - or self._target_connector.rasters_dir.parent != self.target_data_dir - ): - self._set_target_connector() return self._target_connector def _after_creating_or_updating(self): @@ -102,30 +114,6 @@ def _after_creating_or_updating(self): Can be used to e.g. save parameters to the target_connector. """ - def _set_source_connector(self): - """Set source connector.""" - self._source_connector = Connector.from_data_dir(self.source_data_dir) - - def _set_target_connector(self): - """Set target connector.""" - connector_file_paths_exist = [ - (self.target_data_dir / DEFAULT_CONNECTOR_DIR_NAME / filename).is_file() - for filename in INFERRED_PATH_ATTR_FILENAMES.values() - ] - - if all(connector_file_paths_exist): - target_connector = Connector.from_data_dir(self.target_data_dir) - elif not any(connector_file_paths_exist): - target_connector = self.source_connector.empty_connector_same_format( - self.target_data_dir - ) - else: - raise ValueError( - "Corrupted target dataset: only some of the connector files exist." - ) - - self._target_connector = target_connector - def _add_missing_vectors_to_target(self): """Add missing vector features from source dataset to target dataset. From e34c4d42527500f73bbad92d08a56bb253a8641c Mon Sep 17 00:00:00 2001 From: Rustam Antia Date: Sat, 16 Nov 2024 01:35:46 +0100 Subject: [PATCH 2/3] Improve docstring --- geographer/downloaders/downloader_for_vectors.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/geographer/downloaders/downloader_for_vectors.py b/geographer/downloaders/downloader_for_vectors.py index ed453039..32355689 100644 --- a/geographer/downloaders/downloader_for_vectors.py +++ b/geographer/downloaders/downloader_for_vectors.py @@ -67,12 +67,12 @@ def download( Warning: The targeted number of downloads is determined by target_raster_count - and a vector features raster_count. Since the raster_count is the number of - rasters in the dataset fully containing a vector feature for "large" - vector features (polygons) the raster_count will always remain zero and - every call of the download_rasters method that includes this vector feature - will download target_raster_count rasters (or raster series). - To avoid this, you can use the + and a vector features raster_count. The raster_count is the number of + rasters in the dataset fully containing a vector feature. For vector + features (polygons) large enough not be contained in a raster the + raster_count will always remain zero and every call of the download_rasters + method that includes this vector feature will download target_raster_count + rasters (or raster series). To avoid this, you can use the filter_out_vectors_contained_in_union_of_intersecting_rasters argument. Args: @@ -87,7 +87,7 @@ def download( are not enough rasters available or higher if after downloading num_target_rasters_per_vector rasters for P P is also contained in rasters downloaded for other vector features. - filter_out_vector vectors_contained_in_union_of_intersecting_rasters: + filter_out_vectors_contained_in_union_of_intersecting_rasters: Useful when dealing with 'large' vector features. Defaults to False. shuffle: Whether to shuffle order of vector features for which rasters will be downloaded. Might in practice prevent an uneven distribution From b6dc5ca6f91f6b95752e4fd3996830e0df742ed4 Mon Sep 17 00:00:00 2001 From: Rustam Antia Date: Sat, 16 Nov 2024 01:23:08 +0100 Subject: [PATCH 3/3] Replace sentinelsat downloader by one based on eodag. Remove support for Python 3.8, add support for 3.10-13. --- .github/workflows/check.yml | 2 +- Makefile | 15 +- README.md | 11 +- docs/source/_static/GeoGrapher.png | Bin 0 -> 388245 bytes .../_templates/custom-module-template.rst | 2 +- docs/source/advanced_cutting.rst | 2 +- docs/source/basic_cutting.rst | 2 +- docs/source/cluster_rasters.rst | 2 +- docs/source/conf.py | 43 +- docs/source/downloaders.rst | 114 +++-- docs/source/index.rst | 17 +- docs/source/installation.rst | 2 +- geographer/add_drop_rasters_mixin.py | 7 +- geographer/add_drop_vectors_mixin.py | 9 +- .../base_model_dict_conversion_functional.py | 18 +- .../save_load_base_model_mixin.py | 8 +- geographer/connector.py | 31 +- .../combine_remove_vector_classes.py | 11 +- .../label_type_soft_to_categorical.py | 1 - geographer/converters/tif_to_npy.py | 4 - .../creator_from_source_dataset_base.py | 26 +- geographer/cutters/cut_iter_over_rasters.py | 13 +- geographer/cutters/cut_iter_over_vectors.py | 14 +- .../cut_rasters_around_every_vector.py | 16 +- .../cutters/raster_filter_predicates.py | 12 +- geographer/cutters/raster_selectors.py | 4 +- .../single_raster_cutter_around_vector.py | 18 +- .../cutters/single_raster_cutter_base.py | 23 +- .../cutters/single_raster_cutter_bbox.py | 11 +- .../cutters/single_raster_cutter_grid.py | 9 +- geographer/cutters/type_aliases.py | 8 +- .../cutters/vector_filter_predicates.py | 23 +- geographer/downloaders/__init__.py | 8 +- .../downloaders/base_download_processor.py | 28 +- .../base_downloader_for_single_vector.py | 30 +- .../downloaders/downloader_for_vectors.py | 134 +++--- .../eodag_downloader_for_single_vector.py | 398 ++++++++++++++++ .../downloaders/jaxa_download_processor.py | 3 - .../jaxa_downloader_for_single_vector.py | 20 +- .../sentinel2_download_processor.py | 107 +++-- .../sentinel2_downloader_for_single_vector.py | 233 ---------- .../downloaders/sentinel2_safe_unpacking.py | 215 +++++---- geographer/errors.py | 10 +- geographer/global_constants.py | 4 +- geographer/graph/bipartite_graph.py | 22 +- geographer/graph/bipartite_graph_class.py | 8 +- geographer/graph/bipartite_graph_mixin.py | 27 +- geographer/img_polygon_associator_TODO | 27 -- geographer/label_makers/label_maker_base.py | 8 +- .../label_makers/seg_label_maker_base.py | 7 +- .../seg_label_maker_categorical.py | 8 - .../seg_label_maker_soft_categorical.py | 10 - geographer/raster_bands_getter_mixin.py | 3 +- geographer/testing/graph_df_compatibility.py | 3 - geographer/testing/mock_download.py | 34 +- geographer/utils/__init__.py | 7 +- geographer/utils/cluster_rasters.py | 31 +- geographer/utils/connector_utils.py | 7 +- geographer/utils/merge_datasets.py | 9 +- geographer/utils/rasters_from_tif_dir.py | 17 +- geographer/utils/utils.py | 34 +- notebooks/blogpost.ipynb | 434 +++++++++++------- notebooks/tutorial_nb_basics.ipynb | 74 ++- notebooks/tutorial_nb_cut_label_cluster.ipynb | 22 +- pyproject.toml | 95 +++- setup.cfg | 105 ----- tests/cluster_rasters_test.py | 8 +- tests/conftest.py | 6 +- tests/connector_test.py | 49 +- tests/cut_rasters_around_every_vector_test.py | 3 +- .../data/cut_source/connector/rasters.geojson | 1 + .../data/cut_source/connector/vectors.geojson | 1 + tests/mock_download_test.py | 7 +- tests/save_load_base_model_test.py | 97 ++-- ..._manually.py => test_eodag_s2_download.py} | 75 +-- ...test_manually.py => test_jaxa_download.py} | 26 +- tests/utils.py | 14 +- 77 files changed, 1642 insertions(+), 1305 deletions(-) create mode 100644 docs/source/_static/GeoGrapher.png create mode 100644 geographer/downloaders/eodag_downloader_for_single_vector.py delete mode 100644 geographer/downloaders/sentinel2_downloader_for_single_vector.py delete mode 100644 geographer/img_polygon_associator_TODO delete mode 100644 setup.cfg rename tests/{download_s2_test_manually.py => test_eodag_s2_download.py} (55%) rename tests/{download_jaxa_test_manually.py => test_jaxa_download.py} (88%) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 4d06c71e..62187e3c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/Makefile b/Makefile index 3cfd246f..ec339425 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ help: venv: $(VIRTUAL_ENV)/timestamp -$(VIRTUAL_ENV)/timestamp: pyproject.toml setup.cfg +$(VIRTUAL_ENV)/timestamp: pyproject.toml pip install --upgrade pip pip install -e ".[dev,docs]" ifneq ($(wildcard requirements/extra.txt),) @@ -31,15 +31,16 @@ endif touch $(VIRTUAL_ENV)/timestamp format: venv - isort $(PROJECTNAME) - docformatter -i -r $(PROJECTNAME) - black $(PROJECTNAME) + ruff check $(PROJECTNAME) tests --fix lint: venv - flake8 --show-source $(PROJECTNAME) tests + ruff check $(PROJECTNAME) tests test: venv - pytest -v + pytest -v -m "not slow" + +test-slow: venv + pytest -v -m "slow" docs: venv - cd docs && sphinx-apidoc -o source/ ../$(PROJECTNAME) && make html + cd docs && sphinx-apidoc -o source/ ../$(PROJECTNAME) && make clean html diff --git a/README.md b/README.md index c0dfe3ce..27792e7c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +

+ GeoGrapher Logo
+

+

+ + Hippocratic License + +

+ # GeoGrapher *GeoGrapher* is a Python library for building remote sensing @@ -11,7 +20,7 @@ utility functions. # Installation This package has two external dependencies: -- Python 3.8 or newer. +- Python 3.9 or newer. - The geopandas and rasterio libraries might depend on GDAL base C libraries. See [https://geopandas.org/en/stable/getting_started/install.html#dependencies](https://geopandas.org/en/stable/getting_started/install.html#dependencies) and [https://pypi.org/project/rasterio/](https://pypi.org/project/rasterio/) diff --git a/docs/source/_static/GeoGrapher.png b/docs/source/_static/GeoGrapher.png new file mode 100644 index 0000000000000000000000000000000000000000..0c80874167e52d5cc8af1fb92d91e98098169370 GIT binary patch literal 388245 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 z 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�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

yAruykqYz*{ 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

O5a)SR~4 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

vB|v5|cYzr18b6jQimYzn!9OBn4pakpKYMyAI03 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 literal 0 HcmV?d00001 diff --git a/docs/source/_templates/custom-module-template.rst b/docs/source/_templates/custom-module-template.rst index ef2c09a5..c3e01c7d 100644 --- a/docs/source/_templates/custom-module-template.rst +++ b/docs/source/_templates/custom-module-template.rst @@ -63,4 +63,4 @@ {{ item }} {%- endfor %} {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/docs/source/advanced_cutting.rst b/docs/source/advanced_cutting.rst index c3a534e3..c469dcc7 100644 --- a/docs/source/advanced_cutting.rst +++ b/docs/source/advanced_cutting.rst @@ -245,7 +245,7 @@ You can write a custom ``RasterFilterPredicate`` to do this:: target_assoc: Connector, new_raster_dict: dict, source_assoc: Connector, - cut_rasters: List[str], + cut_rasters: list[str], ) -> bool: local_timestamp: str = rasters.loc[raster_name, 'local_timestamp'] diff --git a/docs/source/basic_cutting.rst b/docs/source/basic_cutting.rst index 8f412919..8133f07a 100644 --- a/docs/source/basic_cutting.rst +++ b/docs/source/basic_cutting.rst @@ -58,7 +58,7 @@ cutouts around vector features from 10980 × 10980 Sentinel-2 tiles):: source_data_dir=, target_data_dir=, name= - new_raster_size: Optional[RasterSize] + new_raster_size: RasterSize | None new_raster_size=512, target_raster_count=2, mode: "random") diff --git a/docs/source/cluster_rasters.rst b/docs/source/cluster_rasters.rst index ac9e6ebd..c04dee0e 100644 --- a/docs/source/cluster_rasters.rst +++ b/docs/source/cluster_rasters.rst @@ -18,7 +18,7 @@ train/validation split to avoid data leakage use the .. code-block:: from geographer.utils.cluster_rasters import get_raster_clusters - clusters : List[Set[str]] = get_raster_clusters( + clusters : list[Set[str]] = get_raster_clusters( connector=connector, clusters_defined_by='rasters_that_share_vectors', preclustering_method='y then x-axis' diff --git a/docs/source/conf.py b/docs/source/conf.py index 4a7983cc..202ec0f6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -13,17 +13,17 @@ import os import sys -sys.path.insert(0, os.path.abspath('./geographer')) +sys.path.insert(0, os.path.abspath("./geographer")) # -- Project information ----------------------------------------------------- -project = 'GeoGrapher' -copyright = 'Open Source TBD' -author = 'Rustam Antia' +project = "GeoGrapher" +copyright = "Open Source TBD" +author = "Rustam Antia" # The full version, including alpha/beta/rc tags -release = '0.1.0' +release = "0.1.0" # -- General configuration --------------------------------------------------- @@ -32,19 +32,19 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'nbsphinx', - 'nbsphinx_link', - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx_autodoc_typehints', - 'sphinxcontrib.autodoc_pydantic', - 'sphinx.ext.napoleon', - 'sphinx.ext.todo', - 'sphinx.ext.viewcode', + "nbsphinx", + "nbsphinx_link", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx_autodoc_typehints", + "sphinxcontrib.autodoc_pydantic", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx.ext.viewcode", ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -56,12 +56,12 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = 'furo' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [] +html_static_path = ["_static"] # HTML settings html_theme_options = { @@ -73,9 +73,11 @@ # autodoc settings autodoc_default_options = { - 'inherited-members': 'pydantic.BaseModel,BaseModel', + "inherited-members": "pydantic.BaseModel,BaseModel", } +autodoc_typehints = "description" # or "signature" + # autodoc_pydantic settings autodoc_pydantic_config_members = False autodoc_pydantic_model_show_config_member = False @@ -83,12 +85,11 @@ autodoc_pydantic_model_show_validator_summary = False autodoc_pydantic_model_show_validator_members = False autodoc_pydantic_model_show_field_summary = False -autodoc_pydantic_model_hide_paramlist = False # change? +autodoc_pydantic_model_hide_paramlist = False # change? autodoc_pydantic_model_signature_prefix = "class" autodoc_pydantic_model_show_json = False autodoc_pydantic_settings_show_json = False - # todo settings todo_include_todos = True @@ -106,4 +107,4 @@ napoleon_use_rtype = True napoleon_preprocess_types = False napoleon_type_aliases = None -napoleon_attr_annotations = True \ No newline at end of file +napoleon_attr_annotations = True diff --git a/docs/source/downloaders.rst b/docs/source/downloaders.rst index 99c714a6..9b9fef2d 100644 --- a/docs/source/downloaders.rst +++ b/docs/source/downloaders.rst @@ -3,12 +3,35 @@ Downloading rasters To download rasters for vector features use ``RasterDownloaderForVectors``. -By plugging in different ``DownloaderForSingleVector`` and ``Processor`` -components it can interface with different sources of remote sensing rasters. -Currently, it can interface with the Copernicus Open Access Hub for Sentinel-2 -rasters, and JAXA for ALOS DEM (digital elevation model) data, and can easily -be extended to other data sources by writing custom -``DownloaderForSingleSingleVector`` and ``Processor`` classes. +A ``RasterDownloaderForVectors`` requires components that implement the +abstract base classes ``DownloaderForSingleVector`` and ``Processor``. + + - ``DownloaderForSingleVector`` defines how to search for and download + a raster for a single vector from a provider. + - ``Processor`` how to process the downloaded file into a GeoTiff raster. + +Available implementations ++++++++++++++++++++++++++ + +Currently, there are two concrete implementations of ``DownloaderForSingleVector``: + + - ``EodagDownloaderForSingleVector``: Based on + the excellent `"eodag" `_ package, this implementation supports downloading + over 50 product types from more than 10 providers. + - ``JAXADownloaderForSingleVector``: Designed for downloading DEM (digital elevation model) + data from the `"JAXA ALOS" `_ mission. + +Additionally, there are two concrete implementations of the ``Processor`` class: + + - ``Sentinel2SAFEProcessor``: Processes Level-2A Sentinel-2 SAFE files. + - ``JAXADownloadProcessor``: Processes JAXA DEM data. + +To use the ``RasterDownloaderForVectors`` for a new provider or product type +you only need to write custom implementations of ``DownloaderForSingleVector`` +or ``Processor``. + +.. _EODAG: https://eodag.readthedocs.io/en/stable/ +.. _JAXA_ALOS: https://www.eorc.jaxa.jp/ALOS/en/index_e.htm Example usage +++++++++++++ @@ -19,54 +42,46 @@ Example usage: from geographer.downloaders import ( RasterDownloaderForVectors, - SentinelDownloaderForSingleVector, - Sentinel2Processor + EodagDownloaderForSingleVector, + Sentinel2SAFEProcessor, ) - downloader_for_single_vector=SentinelDownloaderForSingleVector() - download_processor=Sentinel2Processor() + download_processor = Sentinel2SAFEProcessor() + downloader_for_single_vector = EodagDownloaderForSingleVector() downloader = RasterDownloaderForVectors( downloader_for_single_vector=downloader_for_single_vector, download_processor=download_processor, ) + + # Parameters needed by the EodagDownloaderForSingleVector.download method + downloader_params = { + "search_kwargs": { # Keyword arguments for the eodag search_all method + "provider": "cop_dataspace", # Download from copernicus dataspace + "productType": "S2_MSI_L2A", # Search for Sentinel-2 L2A products + "start": "2024-11-01", + "end": "2024-12-01", + }, + "filter_online": True, # Filter out products that are not online + "sort_by": ("cloudCover", "ASC"), # Prioritize search results with less cloud cover + "suffix_to_remove": ".SAFE", # Will strip .SAFE from the stem of the tif file names + } + # Parameters needed by the Sentinel2SAFEProcessor + processor_params = { + "resolution": 10, # Extract all 10m resolution bands + "delete_safe": True, # Delete the SAFE file after extracting a .tif file + } + downloader.download( connector=my_connector, vector_names=optional_list_of_vector_names, target_raster_count=2, - producttype='L2A', - max_percent_cloud_coverage=10, - resolution=10, - date=('NOW-10DAYS', 'NOW'), - area_relation='Contains' + downloader_params=downloader_params, # Only needed the first time downloader.download is called + processor_params=processor_params, # Only needed the first time downoader.download is called ) The raster counts for all vector features are updated after every download, so that unnecessary downloads and an imbalance in the dataset due to clustering of nearby vector features are avoided. -You can supply default values for dataset/data source specific ``download`` -arguments (e.g. ``producttype``, ``max_percent_cloud_coverage`` for the -``SentinelDownloaderForSingleVector``) in the -``RasterDownloaderForVectors``'s ``kwarg_defaults`` arguments dict, -so that one doesn't have to pass them by hand to the ``download`` method, -for example: - -.. code-block:: python - - downloader = RasterDownloaderForVectors( - download_dir=, - downloader_for_single_vector=SentinelDownloaderForSingleVector(), - download_processor=Sentinel2Processor(), - kwarg_defaults={ - 'max_percent_cloud_coverage' = 10, - 'producttype': L2A, - 'resolution': 10, - 'date': ('NOW-10DAYS', 'NOW'), - 'area_relation': 'Contains'}) - downloader.download( - connector=my_connector, - vector_names=optional_list_of_vector_names, - target_raster_count=2) - Data sources ++++++++++++ @@ -77,15 +92,22 @@ to GeoTiffs. Sentinel-2 ~~~~~~~~~~ -For *Sentinel-2* data, use the ``SentinelDownloaderForSingleVector`` -to download rasters from the Copernicus Open Access Hub and the ``Sentinel2Processor``. +For *Sentinel-2* data, use the ``EodagDownloaderForSingleVector`` with +``"productType": "S2_MSI_L2A"`` together with the ``Sentinel2SAFEProcessor`` as above. +Tested with cop_dataspace. Expected to work with other archive_depth=2 providers +(creodias, onda, sara). If archive_depth differs, you'll need to adapt the processor. +Please submit the adapted ``RasterDownloadProcessor`` as a merge request :) -Sentinel-1 -~~~~~~~~~~ +Sources/providers supported by `eodag` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``EodagDownloaderForSingleVector`` will work with any sources/providers +`supported by eodag `_. For ``"productType"``s other than +``"S2_MSI_L2A"`` with ``archive_depth`` 2, you will need to write a custom +``RasterDownloadProcessor``. Please submit your custom ``RasterDownloadProcessor`` +as a merge request :) -The ``SentinelDownloaderForSingleVector`` should work with slight modifications -for downloading Sentinel-1 data from Copernicus Open Access Hub as well. Feel free to -submit a pull request for this feature. +.. _EODAG_PROVIDERS: https://eodag.readthedocs.io/en/stable/getting_started_guide/providers.html JAXA DEM data ~~~~~~~~~~~~~ diff --git a/docs/source/index.rst b/docs/source/index.rst index 15bf138c..2c84fec2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,6 +1,11 @@ -###################################### -Welcome to GeoGrapher's documentation! -###################################### +| + +.. image:: _static/GeoGrapher.png + :width: 80px + :align: center + +GeoGrapher Documentation +======================== **GeoGrapher** is a Python library for building and handling remote sensing computer vision datasets assembled from vector features and rasters. @@ -11,7 +16,7 @@ provides highly general and customizable dataset cutting functionality as well as other utility functions. User guide -================== +----------- .. toctree:: :maxdepth: 1 @@ -28,7 +33,7 @@ User guide glossary API Reference -============= +------------- .. toctree:: :maxdepth: 1 @@ -36,7 +41,7 @@ API Reference geographer Indices and tables -================== +------------------ * :ref:`genindex` * :ref:`modindex` diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 1ebea2d3..500780b0 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -4,7 +4,7 @@ Installation This package has two external dependencies: -- Python 3.8 or newer. +- Python 3.9 or newer. - The geopandas and rasterio libraries might depend on GDAL base C libraries. See `here for the geopandas instructions `_ diff --git a/geographer/add_drop_rasters_mixin.py b/geographer/add_drop_rasters_mixin.py index d7499aa7..d8a5a6e7 100644 --- a/geographer/add_drop_rasters_mixin.py +++ b/geographer/add_drop_rasters_mixin.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Sequence import pandas as pd from geopandas import GeoDataFrame @@ -21,7 +21,7 @@ class AddDropRastersMixIn: """Mix-in that implements methods to add and drop rasters.""" def add_to_rasters( - self, new_rasters: GeoDataFrame, label_maker: Optional[LabelMaker] = None + self, new_rasters: GeoDataFrame, label_maker: LabelMaker | None = None ): """Add rasters to connector's ``rasters`` attribute. @@ -76,7 +76,6 @@ def add_to_rasters( # go through all new rasters... for raster_name in new_rasters.index: - # add new raster vertex to the graph, add all connections # to existing rasters, and modify self.vectors 'raster_count' value raster_bounding_rectangle = new_rasters.loc[raster_name, "geometry"] @@ -97,7 +96,7 @@ def drop_rasters( self, raster_names: Sequence[str], remove_rasters_from_disk: bool = True, - label_maker: Optional[LabelMaker] = None, + label_maker: LabelMaker | None = None, ): """Drop rasters from ``rasters`` and from dataset. diff --git a/geographer/add_drop_vectors_mixin.py b/geographer/add_drop_vectors_mixin.py index ed395f0d..ecdd3b4a 100644 --- a/geographer/add_drop_vectors_mixin.py +++ b/geographer/add_drop_vectors_mixin.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Optional, Sequence, Union +from typing import TYPE_CHECKING, Sequence import pandas as pd from geopandas import GeoDataFrame @@ -28,7 +28,7 @@ class AddDropVectorsMixIn(object): def add_to_vectors( self, new_vectors: GeoDataFrame, - label_maker: Optional[LabelMaker] = None, + label_maker: LabelMaker | None = None, ): """Add vector features to connector's ``vectors`` attribute. @@ -91,7 +91,6 @@ def add_to_vectors( # For each new vector feature... for vector_name in new_vectors.index: - # ... add a vertex for the new vector feature to the graph and add all # connections to existing rasters. self._add_vector_to_graph(vector_name, vectors=new_vectors) @@ -114,8 +113,8 @@ def add_to_vectors( def drop_vectors( self, - vector_names: Sequence[Union[str, int]], - label_maker: Optional[LabelMaker] = None, + vector_names: Sequence[str | int], + label_maker: LabelMaker | None = None, ): """Drop vector features from connector's ``vectors`` attribute. diff --git a/geographer/base_model_dict_conversion/base_model_dict_conversion_functional.py b/geographer/base_model_dict_conversion/base_model_dict_conversion_functional.py index ddb436b4..2812b52b 100644 --- a/geographer/base_model_dict_conversion/base_model_dict_conversion_functional.py +++ b/geographer/base_model_dict_conversion/base_model_dict_conversion_functional.py @@ -7,7 +7,7 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Optional, Union +from typing import Any from pydantic import BaseModel @@ -15,7 +15,7 @@ def get_nested_base_model_dict( - base_model_obj_or_dict: Union[BaseModel, dict, Any] + base_model_obj_or_dict: BaseModel | dict | Any, ) -> dict: """Return nested dict for BaseModel or dict. @@ -26,7 +26,7 @@ def get_nested_base_model_dict( dict_ = base_model_obj_or_dict dict_items = base_model_obj_or_dict.items() elif isinstance(base_model_obj_or_dict, BaseModel): - dict_ = base_model_obj_or_dict.dict() + dict_ = base_model_obj_or_dict.model_dump() dict_items = base_model_obj_or_dict dict_or_base_model_fields_dict = { @@ -80,14 +80,14 @@ def get_nested_base_model_dict( return result -def get_nested_dict(obj: Union[BaseModel, dict, Any]) -> Union[dict, Any]: +def get_nested_dict(obj: BaseModel | dict | Any) -> dict | Any: """Return nested dict if obj is a BaseModel or dict else return obj.""" def eval_nested_base_model_dict( - dict_or_field_value: Union[dict, Any], - constructor_symbol_table: Optional[dict[str, Any]] = None, -) -> Union[BaseModel, Any]: + dict_or_field_value: dict | Any, + constructor_symbol_table: dict[str, Any] | None = None, +) -> BaseModel | Any: """Evaluate nested BaseModel dict (or field contents). Args: @@ -172,13 +172,13 @@ def is_base_model_constructor_dict(dict_: dict) -> bool: def get_base_model_constructor( - dict_: dict, constructor_symbol_table: Optional[dict[str, Any]] = None + dict_: dict, constructor_symbol_table: dict[str, Any] | None = None ) -> BaseModel: """Return constructor corresponding to encoded BaseModel. Args: dict_ (dict): nested base model dict - constructor_symbol_table (Optional[dict[str, Any]], optional): optional symbol + constructor_symbol_table (dict[str, Any] | None, optional): optional symbol table of constructors. Defaults to None. Returns: diff --git a/geographer/base_model_dict_conversion/save_load_base_model_mixin.py b/geographer/base_model_dict_conversion/save_load_base_model_mixin.py index 829355c3..3d716c4e 100644 --- a/geographer/base_model_dict_conversion/save_load_base_model_mixin.py +++ b/geographer/base_model_dict_conversion/save_load_base_model_mixin.py @@ -8,7 +8,7 @@ from importlib import import_module from inspect import getmro, isabstract, isclass from pathlib import Path -from typing import Any, Optional, Union +from typing import Any from pydantic import BaseModel @@ -28,7 +28,7 @@ def save(self): """Save instance to file.""" pass - def _save(self, json_file_path: Union[str, Path]) -> None: + def _save(self, json_file_path: Path | str) -> None: """Save to json_file.""" # Use to implement save method with file_path determined by use case json_file_path = Path(json_file_path) @@ -42,8 +42,8 @@ def _save(self, json_file_path: Union[str, Path]) -> None: @classmethod def from_json_file( cls, - json_file_path: Union[Path, str], - constructor_symbol_table: Optional[dict[str, Any]] = None, + json_file_path: Path | str, + constructor_symbol_table: dict[str, Any] | None = None, ) -> Any: """Load and return saved BaseModel.""" if constructor_symbol_table is None: diff --git a/geographer/connector.py b/geographer/connector.py index 4c89549b..b77821d7 100644 --- a/geographer/connector.py +++ b/geographer/connector.py @@ -6,7 +6,7 @@ import logging from json.decoder import JSONDecodeError from pathlib import Path -from typing import Any, Literal, Optional, Sequence, Type, TypeVar, Union +from typing import Any, Literal, Sequence, Type, TypeVar import geopandas as gpd from geopandas import GeoDataFrame @@ -70,15 +70,15 @@ def __init__( load_from_disk: bool, # data dir - data_dir: Union[Path, str], + data_dir: Path | str, # args w/o default values - vectors: Optional[GeoDataFrame] = None, - rasters: Optional[GeoDataFrame] = None, + vectors: GeoDataFrame | None = None, + rasters: GeoDataFrame | None = None, # remaining non-path args w/ default values - task_vector_classes: Optional[Sequence[str]] = None, - background_class: Optional[str] = None, + task_vector_classes: Sequence[str] | None = None, + background_class: str | None = None, crs_epsg_code: int = STANDARD_CRS_EPSG_CODE, raster_count_col_name: str = "raster_count", @@ -196,7 +196,7 @@ def __getattr__(self, key: str) -> Any: @classmethod def from_data_dir( cls: Type[ConnectorType], - data_dir: Union[Path, str], + data_dir: Path | str, ) -> ConnectorType: """Initialize a connector from a data directory. @@ -217,19 +217,20 @@ def from_data_dir( try: attrs_path = Path(connector_dir) / \ INFERRED_PATH_ATTR_FILENAMES["attrs_path"] - with open(attrs_path, "r") as read_file: - kwargs = json.load(read_file) - except FileNotFoundError as exc: + with open(attrs_path, "r") as file: + kwargs = json.load(file) + except FileNotFoundError: log.exception( "Missing connector file %s found in %s", INFERRED_PATH_ATTR_FILENAMES['attrs_path'], connector_dir) - raise exc + raise except JSONDecodeError: log.exception( "The %s file in %s is corrupted!", INFERRED_PATH_ATTR_FILENAMES['attrs_path'], connector_dir) + raise new_connector = cls( load_from_disk=True, @@ -399,7 +400,7 @@ def save(self): def empty_connector_same_format( self, - data_dir: Union[Path, str] + data_dir: Path | str ) -> Connector: """Return an empty connector of the same format. @@ -528,7 +529,7 @@ def _load_df_from_disk(self, df_name: str) -> GeoDataFrame: def _init_set_paths( self, - data_dir: Union[Path, str], + data_dir: Path | str, ): """Set paths to raster/label data and connector component files. @@ -552,7 +553,7 @@ def _init_set_paths( @classmethod def _get_default_dirs_from_data_dir( cls, - data_dir: Union[Path, str] + data_dir: Path | str ) -> tuple[Path, Path, Path]: data_dir = Path(data_dir) @@ -587,7 +588,7 @@ def _check_no_non_task_vector_classes_are_task_classes( task_vector_classes: list[str], background_class: str, **kwargs ): - """TODO. + """Check non-task vector class and task classes are disjoint. Args: task_vector_classes: [description] diff --git a/geographer/converters/combine_remove_vector_classes.py b/geographer/converters/combine_remove_vector_classes.py index 17dd0c6b..1131f08f 100644 --- a/geographer/converters/combine_remove_vector_classes.py +++ b/geographer/converters/combine_remove_vector_classes.py @@ -8,7 +8,7 @@ import logging import shutil -from typing import List, Optional, Union +from typing import Optional, Union import pandas as pd from geopandas.geodataframe import GeoDataFrame @@ -31,10 +31,10 @@ class DSConverterCombineRemoveClasses(DSCreatorFromSource): removing vector feature classes. """ - classes: List[Union[str, List[str]]] = Field( + classes: list[Union[str, list[str]]] = Field( description="Classes to keep and combine. See docstring." ) - new_class_names: Optional[List[str]] = Field( + new_class_names: Optional[list[str]] = Field( default=None, description="Names of new classes" ) class_separator: str = Field( @@ -208,7 +208,6 @@ def _create_or_update(self) -> Connector: self.target_connector.add_to_rasters(df_of_rasters_to_add_to_target_dataset) if self.label_maker is not None: - # Determine labels to delete: # For each raster that already existed in the target dataset ... for ( @@ -260,7 +259,6 @@ def _get_new_class_names(self, classes: list[str]) -> list[str]: def _run_safety_checks( self, classes_to_keep: list[str], new_class_names: list[str] ): - if not set(classes_to_keep) <= set(self.source_connector.all_vector_classes): classes_not_in_source_dataset = set(classes_to_keep) - set( self.source_connector.all_vector_classes @@ -286,7 +284,7 @@ def _combine_or_remove_classes_from_vectors( self, label_type: str, vectors: GeoDataFrame, - classes: list[Union[str, list[str]]], + classes: list[str | list[str]], new_class_names: list[str], all_source_vector_classes: list[str], ) -> GeoDataFrame: @@ -377,6 +375,7 @@ def prob_of_class_names(classes: list[str]) -> list[str]: vectors = GeoDataFrame( pd.concat([vectors, temp_vectors], axis=1), # column axis crs=vectors.crs, + geometry="geometry", ) vectors.index.name = VECTOR_FEATURES_INDEX_NAME diff --git a/geographer/converters/label_type_soft_to_categorical.py b/geographer/converters/label_type_soft_to_categorical.py index e59fef12..9818c02f 100644 --- a/geographer/converters/label_type_soft_to_categorical.py +++ b/geographer/converters/label_type_soft_to_categorical.py @@ -41,7 +41,6 @@ def _update(self): self._create_or_update() def _create_or_update(self) -> Connector: - if self.source_assoc.label_type != "soft-categorical": raise ValueError( "Only works with label_type soft-categorical\n" diff --git a/geographer/converters/tif_to_npy.py b/geographer/converters/tif_to_npy.py index 854523c1..28d34d07 100644 --- a/geographer/converters/tif_to_npy.py +++ b/geographer/converters/tif_to_npy.py @@ -1,7 +1,5 @@ """Convert a dataset of GeoTiffs to NPYs.""" -from __future__ import annotations - import logging from typing import Literal @@ -36,7 +34,6 @@ def _update(self): self._create_or_update() def _create_or_update(self) -> None: - # need this later geoms_that_will_be_added_to_target_dataset = set( self.source_assoc.geoms_df.index @@ -84,7 +81,6 @@ def _create_or_update(self) -> None: ).is_file(): # ... convert the tif: Open the tif file ... with rio.open(tif_dir / tif_raster_name) as src: - raster_bands = self._get_bands_for_raster( self.bands, tif_dir / tif_raster_name, diff --git a/geographer/creator_from_source_dataset_base.py b/geographer/creator_from_source_dataset_base.py index c532815f..12349e32 100644 --- a/geographer/creator_from_source_dataset_base.py +++ b/geographer/creator_from_source_dataset_base.py @@ -1,12 +1,17 @@ """ABC for creating or updating a dataset from an existing source dataset.""" -from __future__ import annotations - from abc import ABC, abstractmethod from pathlib import Path -from typing import Dict, List, Optional - -from pydantic import BaseModel, Field, PrivateAttr, field_validator, model_validator +from typing import Optional + +from pydantic import ( + BaseModel, + ConfigDict, + Field, + PrivateAttr, + field_validator, + model_validator, +) from geographer.base_model_dict_conversion.save_load_base_model_mixin import ( SaveAndLoadBaseModelMixIn, @@ -21,6 +26,8 @@ class DSCreatorFromSource(ABC, SaveAndLoadBaseModelMixIn, BaseModel): """ABC for creating or updating a dataset from an existing one.""" + model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) + source_data_dir: Path target_data_dir: Path name: str = Field( @@ -30,13 +37,6 @@ class DSCreatorFromSource(ABC, SaveAndLoadBaseModelMixIn, BaseModel): _source_connector: Optional[Connector] = PrivateAttr(default=None) _target_connector: Optional[Connector] = PrivateAttr(default=None) - class Config: - """BaseModel Config.""" - - arbitrary_types_allowed = True - extra = "allow" - underscore_attrs_are_private = True - @field_validator("source_data_dir", mode="before") def validate_source_data_dir(cls, value: Path) -> Path: """Ensure source_data_dir is a valid path.""" @@ -140,7 +140,7 @@ class DSCreatorFromSourceWithBands(DSCreatorFromSource, ABC): Includes a bands field. """ - bands: Optional[Dict[str, Optional[List[int]]]] = Field( + bands: Optional[dict[str, Optional[list[int]]]] = Field( default=None, title="Dict of band indices", description="keys: raster directory names, values: list of band indices " diff --git a/geographer/cutters/cut_iter_over_rasters.py b/geographer/cutters/cut_iter_over_rasters.py index 5991b8c5..b55ab4b8 100644 --- a/geographer/cutters/cut_iter_over_rasters.py +++ b/geographer/cutters/cut_iter_over_rasters.py @@ -4,10 +4,8 @@ datasets of GeoTiffs from existing ones by iterating over rasters. """ -from __future__ import annotations - import logging -from typing import List, Optional +from typing import Optional from geopandas import GeoDataFrame from pydantic import Field @@ -41,7 +39,7 @@ class DSCutterIterOverRasters(DSCreatorFromSourceWithBands): description="Optional label maker. If given, will be used to recompute labels\ when necessary. Defaults to None", ) - cut_rasters: List[str] = Field( + cut_rasters: list[str] = Field( default_factory=list, description=( "Names of cut rasters in source_data_dir. Usually not to be set by hand!" @@ -129,7 +127,6 @@ def _create_or_update(self) -> None: for raster_name in tqdm( self.source_connector.rasters.index, desc="Cutting dataset: " ): - # If filter condition is satisfied, (if not, don't do anything) ... if self.raster_filter_predicate( raster_name, @@ -138,7 +135,6 @@ def _create_or_update(self) -> None: source_connector=self.source_connector, cut_rasters=self.cut_rasters, ): - # ... cut the rasters (and their labels) and remember information # to be appended to self.target_connector rasters in return dict rasters_from_single_cut_dict = self.raster_cutter( @@ -169,7 +165,6 @@ def _create_or_update(self) -> None: for new_raster_name, raster_bounding_rectangle in zip( new_raster_names, raster_bounding_rectangles ): - self.cut_rasters.append(raster_name) # Update graph and modify vectors in self.target_connector @@ -181,7 +176,9 @@ def _create_or_update(self) -> None: # Extract accumulated information about the rasters we've # created in the target dataset into a dataframe... new_rasters = GeoDataFrame( - new_rasters_dict, crs=self.target_connector.rasters.crs + new_rasters_dict, + crs=self.target_connector.rasters.crs, + geometry="geometry", ) new_rasters.set_index(RASTER_IMGS_INDEX_NAME, inplace=True) diff --git a/geographer/cutters/cut_iter_over_vectors.py b/geographer/cutters/cut_iter_over_vectors.py index f71c2320..4dd675af 100644 --- a/geographer/cutters/cut_iter_over_vectors.py +++ b/geographer/cutters/cut_iter_over_vectors.py @@ -9,7 +9,7 @@ import logging from collections import defaultdict -from typing import Dict, List, Optional, Union +from typing import Optional from geopandas import GeoDataFrame from pydantic import Field @@ -61,7 +61,7 @@ class DSCutterIterOverVectors(DSCreatorFromSourceWithBands): description="Optional label maker. If given, will be used to recompute labels\ when necessary. Defaults to None", ) - cut_rasters: Dict[str, List[str]] = Field( + cut_rasters: dict[str, list[str]] = Field( default_factory=lambda: defaultdict(list), title="Cut rasters dictionary", description="Normally, should not be set by hand! Dict with vector features\ @@ -153,7 +153,6 @@ def _create_or_update(self) -> None: # For each vector feature ... for vector_name in tqdm(vectors_to_iterate_over, desc="Cutting dataset: "): - # ... if we want to create new rasters for it ... if self.vector_filter_predicate( vector_name=vector_name, @@ -161,7 +160,6 @@ def _create_or_update(self) -> None: new_rasters_dict=new_rasters_dict, source_connector=self.source_connector, ): - # ... remember it ... added_vectors += [vector_name] @@ -186,7 +184,6 @@ def _create_or_update(self) -> None: source_connector=self.source_connector, cut_rasters=self.cut_rasters, ): - # Cut each raster (and label) and remember the information to be # appended to self.target_connector rasters in return dict rasters_from_single_cut_dict = self.raster_cutter( @@ -222,7 +219,6 @@ def _create_or_update(self) -> None: for new_raster_name, raster_bounding_rectangle in zip( new_raster_names, raster_bounding_rectangles ): - # Update graph and modify vectors # in self.target_connector self.target_connector._add_raster_to_graph_modify_vectors( @@ -246,7 +242,9 @@ def _create_or_update(self) -> None: # Extract accumulated information about the rasters we've created in the target # dataset into a dataframe... new_rasters = GeoDataFrame( - new_rasters_dict, crs=self.target_connector.rasters.crs + new_rasters_dict, + crs=self.target_connector.rasters.crs, + geometry="geometry", ) new_rasters.set_index(RASTER_IMGS_INDEX_NAME, inplace=True) @@ -288,7 +286,7 @@ def _create_or_update(self) -> None: self.target_connector.save() def _filter_out_previously_cut_rasters( - self, vector_name: Union[str, int], src_rasters_containing_vector: set[str] + self, vector_name: str | int, src_rasters_containing_vector: set[str] ) -> list[str]: """Filter out previously cut rasters. diff --git a/geographer/cutters/cut_rasters_around_every_vector.py b/geographer/cutters/cut_rasters_around_every_vector.py index d762e8db..140e0079 100644 --- a/geographer/cutters/cut_rasters_around_every_vector.py +++ b/geographer/cutters/cut_rasters_around_every_vector.py @@ -1,8 +1,10 @@ """Dataset cutter that cuts out rasters around vector features.""" +from __future__ import annotations + import logging from pathlib import Path -from typing import Literal, Optional, Union +from typing import Literal from geographer.cutters.cut_iter_over_vectors import DSCutterIterOverVectors from geographer.cutters.raster_selectors import RandomRasterSelector, RasterSelector @@ -19,15 +21,15 @@ def get_cutter_rasters_around_every_vector( - source_data_dir: Union[Path, str], - target_data_dir: Union[Path, str], + source_data_dir: Path | str, + target_data_dir: Path | str, name: str, mode: Literal["random", "centered", "variable"] = "random", - new_raster_size: Optional[RasterSize] = 512, - min_new_raster_size: Optional[RasterSize] = None, - scaling_factor: Optional[float] = None, + new_raster_size: RasterSize | None = 512, + min_new_raster_size: RasterSize | None = None, + scaling_factor: float | None = None, target_raster_count: int = 1, - bands: Optional[dict] = None, + bands: dict | None = None, random_seed: int = 10, ) -> DSCutterIterOverVectors: """Return dataset cutter that creates cutouts around vector features. diff --git a/geographer/cutters/raster_filter_predicates.py b/geographer/cutters/raster_filter_predicates.py index 9c78adef..e8b911d0 100644 --- a/geographer/cutters/raster_filter_predicates.py +++ b/geographer/cutters/raster_filter_predicates.py @@ -8,7 +8,6 @@ from abc import ABC, abstractmethod from collections.abc import Callable from pathlib import Path -from typing import Union from geopandas import GeoSeries from pandas import Series @@ -116,12 +115,12 @@ class RasterFilterRowCondition(RasterFilterPredicate): row_series_predicate: RowSeriesPredicate def __init__( - self, row_series_predicate: Callable[[Union[GeoSeries, Series]], bool] + self, row_series_predicate: Callable[[GeoSeries | Series], bool] ) -> None: """Initialize an instance of RasterFilterRowCondition. Args: - row_series_predicate (Callable[[Union[GeoSeries, Series]], bool]): + row_series_predicate: predicate to apply to the row corresponding to a raster (i.e. source_connector.rasters.loc[raster_name]) """ @@ -154,19 +153,20 @@ def __call__( result of aplying self.row_series_predicate to source_connector.rasters[raster_name] """ - row_series: Union[GeoSeries, Series] = source_connector.rasters.loc[raster_name] + row_series: GeoSeries | Series = source_connector.rasters.loc[raster_name] answer = self.row_series_predicate(row_series) return answer def wrap_function_as_RowSeriesPredicate( - fun: Callable[[Union[GeoSeries, Series]], bool] + fun: Callable[[GeoSeries | Series], bool], ) -> RowSeriesPredicate: """Wrap a function as a RowSeriesPredicate. Args: - fun (Callable[[Union[GeoSeries, Series]], bool]): + fun: + Function to wrap. Returns: RowSeriesPredicate. diff --git a/geographer/cutters/raster_selectors.py b/geographer/cutters/raster_selectors.py index dabccf3c..a2abe818 100644 --- a/geographer/cutters/raster_selectors.py +++ b/geographer/cutters/raster_selectors.py @@ -9,7 +9,7 @@ import random from abc import abstractmethod from pathlib import Path -from typing import Any, Union +from typing import Any from pydantic import BaseModel @@ -85,7 +85,7 @@ class RandomRasterSelector(RasterSelector): def __call__( self, - vector_name: Union[str, int], + vector_name: str | int, raster_names_list: list[str], target_connector: Connector, new_rasters_dict: dict, diff --git a/geographer/cutters/single_raster_cutter_around_vector.py b/geographer/cutters/single_raster_cutter_around_vector.py index 53563ea8..7c9b0cc2 100644 --- a/geographer/cutters/single_raster_cutter_around_vector.py +++ b/geographer/cutters/single_raster_cutter_around_vector.py @@ -6,7 +6,7 @@ import math import random from pathlib import Path -from typing import Any, Literal, Optional, Union +from typing import Any, Literal, Optional import rasterio as rio from affine import Affine @@ -45,9 +45,9 @@ class SingleRasterCutterAroundVector(SingleRasterCutter): def __init__( self, mode: str, - new_raster_size: Optional[RasterSize] = None, - scaling_factor: Optional[float] = 1.2, - min_new_raster_size: Optional[RasterSize] = None, + new_raster_size: RasterSize | None = None, + scaling_factor: float | None = 1.2, + min_new_raster_size: RasterSize | None = None, random_seed: int = 42, **kwargs, ) -> None: @@ -131,7 +131,7 @@ def _check_raster_size_type_and_value(self, raster_size: RasterSize): @staticmethod def _get_size_rows_cols( - raster_size: Union[int, tuple[int, int]] + raster_size: int | tuple[int, int], ) -> tuple[int, int]: if isinstance(raster_size, tuple): new_raster_size_rows = raster_size[0] @@ -147,7 +147,7 @@ def _get_windows_transforms_raster_names( source_raster_name: str, source_connector: Connector, target_connector: Connector, - new_rasters_dict: Optional[dict] = None, + new_rasters_dict: dict | None = None, **kwargs: Any, ) -> list[tuple[Window, Affine, str]]: """Return windwos, transforms, and names of new rasters. @@ -179,7 +179,6 @@ def _get_windows_transforms_raster_names( vector_geom = target_connector.vectors.loc[vector_name, "geometry"] with rio.open(source_raster_path) as src: - # transform vector feature from connector's crs to raster source crs transformed_vector_geom = transform_shapely_geometry( vector_geom, @@ -248,7 +247,6 @@ def _get_windows_transforms_raster_names( for raster_row in range(num_small_rasters_in_row_direction): for raster_col in range(num_small_rasters_in_col_direction): - # Define the square window with the calculated offsets. window = rio.windows.Window( col_off=col_off + new_raster_size_cols * raster_col, @@ -283,7 +281,6 @@ def _get_windows_transforms_raster_names( # append window if it intersects the vector feature if window_bounding_rectangle.intersects(transformed_vector_geom): - windows_transforms_raster_names_single_geom.append( (window, window_transform, new_raster_name) ) @@ -346,7 +343,6 @@ def _get_grid_row_col_offsets_num_windows_row_col_direction( # Choose row and col offset if self.mode == "random": - # ... choose row and col offsets randomly subject to constraint that the # grid of raster windows contains rectangular envelope of vector feature. row_off = random.randint( @@ -374,7 +370,6 @@ def _get_grid_row_col_offsets_num_windows_row_col_direction( ) elif self.mode in {"centered", "variable"}: - # ... to find the row, col offsets to center the vector feature ... # ...we first find the centroid of the vector feature in the raster crs ... @@ -392,7 +387,6 @@ def _get_grid_row_col_offsets_num_windows_row_col_direction( ) else: - raise ValueError(f"Unknown mode: {self.mode}") return ( diff --git a/geographer/cutters/single_raster_cutter_base.py b/geographer/cutters/single_raster_cutter_base.py index d7e835fd..97ed718e 100644 --- a/geographer/cutters/single_raster_cutter_base.py +++ b/geographer/cutters/single_raster_cutter_base.py @@ -5,7 +5,7 @@ import logging from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Optional, Tuple, Union +from typing import Any, Tuple import rasterio as rio from affine import Affine @@ -30,8 +30,8 @@ def _get_windows_transforms_raster_names( self, source_raster_name: str, source_connector: Connector, - target_connector: Optional[Connector] = None, - new_rasters_dict: Optional[dict] = None, + target_connector: Connector | None = None, + new_rasters_dict: dict | None = None, **kwargs: Any, ) -> list[Tuple[Window, Affine, str]]: """Return windows, window transforms, and new rasters. @@ -57,9 +57,9 @@ def __call__( self, raster_name: str, source_connector: Connector, - target_connector: Optional[Connector] = None, - new_rasters_dict: Optional[dict] = None, - bands: Optional[dict[str, Optional[list[int]]]] = None, + target_connector: Connector | None = None, + new_rasters_dict: dict | None = None, + bands: dict[str, list[int] | None] | None = None, **kwargs: Any, ) -> dict: """Cut new rasters and return return_dict. @@ -117,7 +117,6 @@ def __call__( window_transform, new_raster_name, ) in windows_transforms_raster_names: - # Make new raster and label in target dataset ... raster_bounds_in_raster_crs, raster_crs = self._make_new_raster_and_label( new_raster_name=new_raster_name, @@ -203,7 +202,7 @@ def _make_new_raster_and_label( target_connector: Connector, window: Window, window_transform: Affine, - bands: Optional[dict[str, Optional[list[int]]]], + bands: dict[str, list[int] | None] | None, ) -> Tuple[Tuple[float, float, float, float], CRS]: """Make a new raster and label. @@ -222,7 +221,6 @@ def _make_new_raster_and_label( for count, (source_rasters_dir, target_rasters_dir) in enumerate( zip(source_connector.raster_data_dirs, target_connector.raster_data_dirs) ): - source_raster_path = source_rasters_dir / source_raster_name dst_raster_path = target_rasters_dir / new_raster_name @@ -257,8 +255,8 @@ def _make_new_raster_and_label( def _write_window_to_geotif( self, - src_raster_path: Union[Path, str], - dst_raster_path: Union[Path, str], + src_raster_path: Path | str, + dst_raster_path: Path | str, raster_bands: list[int], window: Window, window_transform: Affine, @@ -277,7 +275,6 @@ def _write_window_to_geotif( """ # Open source ... with rio.open(src_raster_path) as src: - # and destination ... Path(dst_raster_path).parent.mkdir(exist_ok=True, parents=True) with rio.open( @@ -291,10 +288,8 @@ def _write_window_to_geotif( crs=src.crs, transform=window_transform, ) as dst: - # ... and go through the bands. for target_band, source_band in enumerate(raster_bands, start=1): - # Read window for that band from source ... new_raster_band_raster = src.read(source_band, window=window) diff --git a/geographer/cutters/single_raster_cutter_bbox.py b/geographer/cutters/single_raster_cutter_bbox.py index 729ea8c7..2fe60619 100644 --- a/geographer/cutters/single_raster_cutter_bbox.py +++ b/geographer/cutters/single_raster_cutter_bbox.py @@ -4,7 +4,7 @@ import logging from pathlib import Path -from typing import Any, Optional, Union +from typing import Any import geopandas as gpd import rasterio as rio @@ -21,7 +21,7 @@ def _correct_window_offset( - offset: Union[int, float], size: Union[int, float], new_size: int + offset: int | float, size: int | float, new_size: int ) -> int: center = offset + size / 2 return int(center - new_size / 2) @@ -47,7 +47,7 @@ def __init__(self, **data) -> None: bbox_geojson_path: path to geojson file containing the bboxes """ super().__init__(**data) - self._bboxes_df = gpd.read_file(self.bbox_geojson_path, driver="GeoJSON") + self._bboxes_df = gpd.read_file(self.bbox_geojson_path) @field_validator("bbox_geojson_path") def path_points_to_geojson(cls, value: Path): @@ -106,11 +106,10 @@ def _get_windows_transforms_raster_names( self, source_raster_name: str, source_connector: Connector, - target_connector: Optional[Connector] = None, - new_rasters_dict: Optional[dict] = None, + target_connector: Connector | None = None, + new_rasters_dict: dict | None = None, **kwargs: Any, ) -> list[str]: - source_raster_path = source_connector.rasters_dir / source_raster_name with rio.open(source_raster_path) as src: diff --git a/geographer/cutters/single_raster_cutter_grid.py b/geographer/cutters/single_raster_cutter_grid.py index 4ff73048..736a564a 100644 --- a/geographer/cutters/single_raster_cutter_grid.py +++ b/geographer/cutters/single_raster_cutter_grid.py @@ -4,7 +4,7 @@ import logging from pathlib import Path -from typing import Any, Optional +from typing import Any import rasterio as rio from affine import Affine @@ -71,15 +71,13 @@ def _get_windows_transforms_raster_names( self, source_raster_name: str, source_connector: Connector, - target_connector: Optional[Connector] = None, - new_rasters_dict: Optional[dict] = None, + target_connector: Connector | None = None, + new_rasters_dict: dict | None = None, **kwargs: Any, ) -> list[tuple[Window, Affine, str]]: - source_raster_path = source_connector.rasters_dir / source_raster_name with rio.open(source_raster_path) as src: - if not src.height % self.new_raster_size_rows == 0: logger.warning( "number of rows in source raster not divisible by " @@ -96,7 +94,6 @@ def _get_windows_transforms_raster_names( # Iterate through grid ... for i in range(src.width // self.new_raster_size_cols): for j in range(src.height // self.new_raster_size_rows): - # ... remember windows, ... window = Window( i * self.new_raster_size_cols, diff --git a/geographer/cutters/type_aliases.py b/geographer/cutters/type_aliases.py index 1e584b3d..370748ed 100644 --- a/geographer/cutters/type_aliases.py +++ b/geographer/cutters/type_aliases.py @@ -1,9 +1,5 @@ """Type aliases.""" -from __future__ import annotations +from typing import Tuple, Union # noqa: I001,I005 -from typing import Optional, Tuple, Union # noqa: I001,I005 - -# Tuple instead of tuple because pydantic needs old-style -# type declarations for python 3.8 -RasterSize = Optional[Union[int, Tuple[int, int]]] +RasterSize = Union[int, Tuple[int, int]] diff --git a/geographer/cutters/vector_filter_predicates.py b/geographer/cutters/vector_filter_predicates.py index 2c72731a..441f855a 100644 --- a/geographer/cutters/vector_filter_predicates.py +++ b/geographer/cutters/vector_filter_predicates.py @@ -4,7 +4,7 @@ import collections from abc import abstractmethod -from typing import Any, Literal, Union +from typing import Any, Literal from geopandas import GeoSeries from pandas import Series @@ -25,7 +25,7 @@ class VectorFilterPredicate(BaseModel, collections.abc.Callable): @abstractmethod def __call__( self, - vector_name: Union[str, int], + vector_name: str | int, target_connector: Connector, new_rasters_dict: dict, source_connector: Connector, @@ -77,7 +77,7 @@ class IsVectorMissingRasters(VectorFilterPredicate): def __call__( self, - vector_name: Union[str, int], + vector_name: str | int, target_connector: Connector, new_rasters_dict: dict, source_connector: Connector, @@ -114,7 +114,7 @@ class AlwaysTrue(VectorFilterPredicate): def __call__( self, - vector_name: Union[str, int], + vector_name: str | int, target_connector: Connector, new_rasters_dict: dict, source_connector: Connector, @@ -134,7 +134,7 @@ class OnlyThisVector(VectorFilterPredicate): is equal to this_vector_name. """ - def __init__(self, this_vector_name: Union[str, int]) -> None: + def __init__(self, this_vector_name: str | int) -> None: """Initialize OnlyThisVector. Args: @@ -145,7 +145,7 @@ def __init__(self, this_vector_name: Union[str, int]) -> None: def __call__( self, - vector_name: Union[str, int], + vector_name: str | int, target_connector: Connector, new_rasters_dict: dict, source_connector: Connector, @@ -165,17 +165,18 @@ class FilterVectorByRowCondition(VectorFilterPredicate): def __init__( self, row_series_predicate: collections.abc.Callable[ - [Union[GeoSeries, Series]], bool + [GeoSeries | Series], bool ], mode: Literal["source", "target"], ) -> None: """Initialize FilterVectorByRowCondition. Args: - row_series_predicate (Callable[Union[[GeoSeries, Series]], bool]): + row_series_predicate: predicate to apply to the row corresponding to a vector feature in vectors in source_connector or target_connector. - mode (str) : Which GeoDataFrame the predicate should be applied to. + mode: + Which GeoDataFrame the predicate should be applied to. One of 'source' or 'target' """ super().__init__() @@ -189,7 +190,7 @@ def __init__( def __call__( self, - vector_name: Union[str, int], + vector_name: str | int, target_connector: Connector, new_rasters_dict: dict, source_connector: Connector, @@ -202,7 +203,7 @@ def __call__( connector = source_connector vectors = connector.vectors - row_series: Union[GeoSeries, Series] = vectors.loc[vector_name] + row_series: GeoSeries | Series = vectors.loc[vector_name] answer = self.row_series_predicate(row_series) return answer diff --git a/geographer/downloaders/__init__.py b/geographer/downloaders/__init__.py index 30c7e486..2daf61dc 100644 --- a/geographer/downloaders/__init__.py +++ b/geographer/downloaders/__init__.py @@ -1,9 +1,9 @@ from geographer.downloaders.downloader_for_vectors import RasterDownloaderForVectors +from geographer.downloaders.eodag_downloader_for_single_vector import ( + EodagDownloaderForSingleVector, +) from geographer.downloaders.jaxa_download_processor import JAXADownloadProcessor from geographer.downloaders.jaxa_downloader_for_single_vector import ( JAXADownloaderForSingleVector, ) -from geographer.downloaders.sentinel2_download_processor import Sentinel2Processor -from geographer.downloaders.sentinel2_downloader_for_single_vector import ( - SentinelDownloaderForSingleVector, -) +from geographer.downloaders.sentinel2_download_processor import Sentinel2SAFEProcessor diff --git a/geographer/downloaders/base_download_processor.py b/geographer/downloaders/base_download_processor.py index 8aa108ce..775e8d77 100644 --- a/geographer/downloaders/base_download_processor.py +++ b/geographer/downloaders/base_download_processor.py @@ -2,12 +2,15 @@ from __future__ import annotations +import logging from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Literal, Union +from typing import Any, Literal from pydantic import BaseModel +log = logging.getLogger(__name__) + class RasterDownloadProcessor(ABC, BaseModel): """Base class for download processors.""" @@ -19,20 +22,27 @@ def process( download_dir: Path, rasters_dir: Path, return_bounds_in_crs_epsg_code: int, - **kwargs: Any, + **params: Any, ) -> dict[ - Union[Literal["raster_name", "geometry", "orig_crs_epsg_code"], str], Any + Literal["raster_name", "geometry", "orig_crs_epsg_code"] | str, Any ]: """Process a single download. Args: - raster_name: name of raster - download_dir: directory containing download - rasters_dir: directory to place processed raster in - crs_epsg_code: EPSG code of crs raster bounds should be returned in - kwargs: other keyword arguments + raster_name: + Name of raster + download_dir: + Directory containing download + rasters_dir: + Directory to place processed raster in + crs_epsg_code: + EPSG code of crs raster bounds should be returned in + params: + Additional keyword arguments. Corresponds to the processor_params + argument of the RasterDownloaderForVectors.download method. Returns: - return_dict: Contains information about the downloaded product. + return_dict: + Contains information about the downloaded product. Keys should include: 'raster_name', 'geometry', 'orig_crs_epsg_code'. """ diff --git a/geographer/downloaders/base_downloader_for_single_vector.py b/geographer/downloaders/base_downloader_for_single_vector.py index 46650457..d70c3d83 100644 --- a/geographer/downloaders/base_downloader_for_single_vector.py +++ b/geographer/downloaders/base_downloader_for_single_vector.py @@ -2,13 +2,16 @@ from __future__ import annotations +import logging from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Literal, Union +from typing import Any, Literal from pydantic import BaseModel from shapely.geometry import Polygon +log = logging.getLogger(__name__) + class RasterDownloaderForSingleVector(ABC, BaseModel): """Base class for downloaders for a single vector feature.""" @@ -16,21 +19,26 @@ class RasterDownloaderForSingleVector(ABC, BaseModel): @abstractmethod def download( self, - vector_name: Union[int, str], + vector_name: str | int, vector_geom: Polygon, download_dir: Path, - previously_downloaded_rasters_set: set[Union[str, int]], - **kwargs, - ) -> dict[Union[Literal["raster_name", "raster_processed?"], str], Any]: + previously_downloaded_rasters_set: set[str | int], + **params: Any, + ) -> dict[Literal["raster_name", "raster_processed?"] | str, Any]: """Download (a series of) raster(s) for a single vector feature. Args: - vector_name: name of vector feature - vector_geom: geometry of vector feature - download_dir: directory to download to - previously_downloaded_rasters_set: set of (names of) - previously downloaded rasters - kwargs: other keyword arguments + vector_name: + Name of vector feature + vector_geom: + Geometry of vector feature + download_dir: + Directory in which raw downloads are placed + previously_downloaded_rasters_set: + Set of (names of) previously downloaded rasters + params: + Additional keyword arguments. Corresponds to the downloader_params + argument of the RasterDownloaderForVectors.download method. Returns: Dict with a key 'list_raster_info_dicts': The corresponding value is a diff --git a/geographer/downloaders/downloader_for_vectors.py b/geographer/downloaders/downloader_for_vectors.py index 32355689..a7b91c0d 100644 --- a/geographer/downloaders/downloader_for_vectors.py +++ b/geographer/downloaders/downloader_for_vectors.py @@ -5,12 +5,12 @@ import logging import random import shutil -from collections import Counter, defaultdict +from collections import Counter from pathlib import Path -from typing import Dict, Optional, Union +from typing import Any, Union from geopandas import GeoDataFrame -from pydantic import BaseModel, Field +from pydantic import BaseModel from shapely.ops import unary_union from tqdm.auto import tqdm @@ -29,8 +29,6 @@ ) from geographer.utils.utils import concat_gdfs -DEFAULT_TEMP_DOWNLOAD_DIR_NAME = "temp_download_dir" - log = logging.getLogger(__name__) log.setLevel(logging.WARNING) @@ -40,48 +38,46 @@ class RasterDownloaderForVectors(BaseModel, SaveAndLoadBaseModelMixIn): downloader_for_single_vector: RasterDownloaderForSingleVector download_processor: RasterDownloadProcessor - kwarg_defaults: Dict = Field(default_factory=dict) + temp_dir_relative_path: Union[Path, str] = "temp_download_dir" def download( self, - connector: Union[Path, str, Connector], - vector_names: Optional[Union[str, int, list[int], list[str]]] = None, + connector: Path | str | Connector, + vector_names: str | int | list[int] | list[str] | None = None, target_raster_count: int = 1, filter_out_vectors_contained_in_union_of_intersecting_rasters: bool = False, shuffle: bool = True, - **kwargs, + downloader_params: dict[str, Any] | None = None, + processor_params: dict[str, Any] | None = None, ): """Download a targeted number of rasters per vector feature. - Sequentially consider the vector features for which the raster count (number of - rasters fully containing a given vector feature) is less than - num_target_rasters_per_vector rasters in the connector's internal vectors - or the optional vectors argument (if given), for each such vector - feature attempt to download num_target_rasters_per_vector - raster_count rasters - fully containing the vector feature (or several rasters jointly containing the - vector feature), and integrate the new raster(s) into the dataset/connector. - Integrates rasters downloaded for a vector feature into the dataset/connector - immediately after downloading them and before downloading rasters for the next - vector feature. In particular, the raster count is updated immediately after - each download. + For each vector feature with fewer than `target_raster_count` + rasters fully containing it, this function attempts to download + additional rasters to meet the target. The new rasters are integrated + into the dataset/connector immediately after downloading, updating + the raster count for the vector feature before proceeding to the + next feature. Warning: - The targeted number of downloads is determined by target_raster_count - and a vector features raster_count. The raster_count is the number of - rasters in the dataset fully containing a vector feature. For vector - features (polygons) large enough not be contained in a raster the - raster_count will always remain zero and every call of the download_rasters - method that includes this vector feature will download target_raster_count - rasters (or raster series). To avoid this, you can use the - filter_out_vectors_contained_in_union_of_intersecting_rasters argument. + The target number of downloads depends on `target_raster_count` + and the current `raster_count` (number of rasters fully containing + the vector feature). For vector features (e.g., polygons) too large to + be fully contained in any raster, the `raster_count` will remain zero, + and every call to this method will attempt to download `target_raster_count` + rasters (or raster series). To avoid this, use the + `filter_out_vectors_contained_in_union_of_intersecting_rasters` argument. Args: - vector_names: Optional vector_name or list of vector_names to download + vector_names: + Optional vector_name or list of vector_names to download rasters for. Defaults to None, i.e. consider all vector features in connector.vectors. - downloader: One of 'sentinel2' or 'jaxa'. Defaults, if possible, to + downloader: + One of 'sentinel2' or 'jaxa'. Defaults, if possible, to previously used downloader. - target_raster_count: target for number of rasters per vector feature in + target_raster_count: + Target for number of rasters per vector feature in the dataset after downloading. The actual number of rasters for each vector feature P that fully contain it could be lower if there are not enough rasters available or higher if after downloading @@ -89,16 +85,23 @@ def download( in rasters downloaded for other vector features. filter_out_vectors_contained_in_union_of_intersecting_rasters: Useful when dealing with 'large' vector features. Defaults to False. - shuffle: Whether to shuffle order of vector features for which rasters + shuffle: + Whether to shuffle order of vector features for which rasters will be downloaded. Might in practice prevent an uneven distribution of the raster count for repeated downloads. Defaults to True. - kwargs: optional additional keyword arguments passed to - downloader_for_single_vector and download_processor. - Defaults to self.kwarg_defaults. - - Note: - Any kwargs given will be saved to self.default_kwargs and become default - values. + downloader_params: + (Optional) keyword arguments to pass to the + downloader_for_single_vector.download. Corresponds to ``**params`` of + download method of the the abstract base class + RasterDownloaderForSingleVector. In particular, the keywords + vector_name, vector_geom, download_dir, and + previously_downloaded_rasters_set corresponding to the other + arguments are not allowed. + processor_params: + Optional additional keyword arguments passed to + download_processor.process as ``**params``. In particular, the keywords + raster_name, download_dir, rasters_dir, and + return_bounds_in_crs_epsg_code are not allowed. Returns: None @@ -115,12 +118,12 @@ def download( jointly cover the polygon then these 20 disjoint sets will all be downloaded. """ - self.kwarg_defaults.update(kwargs) - + downloader_params = downloader_params or {} + processor_params = processor_params or {} if not isinstance(connector, Connector): connector = Connector.from_data_dir(connector) connector.rasters_dir.mkdir(parents=True, exist_ok=True) - temp_download_dir = connector.data_dir / DEFAULT_TEMP_DOWNLOAD_DIR_NAME + temp_download_dir = connector.data_dir / self.temp_dir_relative_path temp_download_dir.mkdir(parents=True, exist_ok=True) vectors_for_which_to_download = self._get_vectors_for_which_to_download( @@ -139,7 +142,7 @@ def download( # Dict to keep track of rasters we've downloaded. We'll append this to # connector.rasters as a (geo)dataframe later - new_rasters_dict = defaultdict(list) + new_raster_dicts_list = [] pbar = tqdm( enumerate( @@ -150,7 +153,6 @@ def download( ) ) for count, (vector_name, vector_geom) in pbar: - # vector_geom = connector.vectors.loc[vector_name, 'geometry'] pbar.set_description( @@ -181,11 +183,9 @@ def download( continue while num_raster_series_to_download > 0: - # Try downloading a raster series and save returned dict (of dicts) # containing information for vectors, connector.rasters... try: - # DEBUG INFO log.debug( "attempting to download raster for vector feature. %s", @@ -200,7 +200,7 @@ def download( vector_geom=vector_geom, download_dir=temp_download_dir, previously_downloaded_rasters_set=previously_downloaded_rasters_set, # noqa: E501 - **self.kwarg_defaults, + **downloader_params, ) # WHY DOES THIS NOT WORK? @@ -211,7 +211,6 @@ def download( # ... unless either no rasters could be found ... except NoRastersForVectorFoundError as exc: - # ... in which case we save it in connector.vectors, ... connector.vectors.loc[vector_name, "download_exception"] = repr(exc) @@ -223,14 +222,12 @@ def download( # ... or a download error occured, ... except RasterDownloadError as exc: - connector.vectors.loc[vector_name, "download_exception"] = repr(exc) log.warning(exc, exc_info=True) # ... or downloader_for_single_vector tried downloading a previously # downloaded raster. except RasterAlreadyExistsError: - log.exception( "downloader_for_single_vector tried " "downloading a previously downloaded raster!" @@ -238,7 +235,6 @@ def download( # If the download_method call was successful ... else: - # ... we first extract the information to be appended to # connector.rasters. list_raster_info_dicts = return_dict["list_raster_info_dicts"] @@ -256,7 +252,6 @@ def download( break # ... else ... else: - self._run_safety_checks_on_downloaded_rasters( previously_downloaded_rasters_set, vector_name, @@ -265,7 +260,6 @@ def download( # For each download ... for raster_info_dict in list_raster_info_dicts: - # ... process it to a raster ... raster_name = raster_info_dict["raster_name"] single_raster_processed_return_dict = ( @@ -274,7 +268,7 @@ def download( temp_download_dir, connector.rasters_dir, connector.crs_epsg_code, - **self.kwarg_defaults, + **processor_params, ) ) @@ -295,16 +289,14 @@ def download( # Finally, remember we downloaded the raster. previously_downloaded_rasters_set.add(raster_name) - # update new_rasters_dict - for raster_info_dict in list_raster_info_dicts: - for key in raster_info_dict: - new_rasters_dict[key].append(raster_info_dict[key]) + # update new_raster_dicts_list + new_raster_dicts_list += list_raster_info_dicts num_raster_series_to_download -= 1 - if len(new_rasters_dict) > 0: + if len(new_raster_dicts_list) > 0: new_rasters = self._get_new_rasters( - new_rasters_dict, connector.crs_epsg_code + new_raster_dicts_list, connector.crs_epsg_code ) connector.rasters = concat_gdfs([connector.rasters, new_rasters]) connector.save() @@ -313,7 +305,7 @@ def download( if not list(temp_download_dir.iterdir()): shutil.rmtree(temp_download_dir) - def save(self, file_path: Union[Path, str]): + def save(self, file_path: Path | str): """Save downloader. By convention, the downloader should be saved to the connector @@ -323,8 +315,8 @@ def save(self, file_path: Union[Path, str]): @staticmethod def _run_safety_checks_on_downloaded_rasters( - previously_downloaded_rasters_set: set[Union[str, int]], - vector_name: Union[str, int], + previously_downloaded_rasters_set: set[str | int], + vector_name: str | int, list_raster_info_dicts: list[dict], ): """Check no rasters have been downloaded more than once. @@ -386,12 +378,11 @@ def _run_safety_checks_on_downloaded_rasters( def _get_vectors_for_which_to_download( self, - vector_names: Union[str, int, list[int], list[str]], + vector_names: str | int | list[int] | list[str], target_raster_count: int, connector: Connector, filter_out_vectors_contained_in_union_of_intersecting_rasters: bool, - ) -> list[Union[int, str]]: - + ) -> list[int | str]: if vector_names is None: vectors_for_which_to_download = list( connector.vectors.loc[ @@ -429,7 +420,7 @@ def _get_vectors_for_which_to_download( def _filter_out_vectors_with_null_geometry( self, - vector_names: Union[str, int, list[int], list[str]], + vector_names: str | int | list[int] | list[str], connector: Connector, ) -> None: vectors_w_null_geometry_mask = ( @@ -453,7 +444,7 @@ def _filter_out_vectors_with_null_geometry( def _filter_out_vectors_contained_in_union_of_intersecting_rasters( self, - vector_names: Union[str, int, list[int], list[str]], + vector_names: str | int | list[int] | list[str], connector: Connector, ) -> None: vector_names = [ @@ -469,11 +460,12 @@ def _filter_out_vectors_contained_in_union_of_intersecting_rasters( def _get_new_rasters( self, - new_rasters_dict: dict, + new_raster_dicts_list: list[dict[str, Any]], rasters_crs_epsg_code: int, ) -> GeoDataFrame: - """Build and return rasters of new rasters from new_rasters_dict.""" - new_rasters = GeoDataFrame(new_rasters_dict) + """Build and return new rasters gdf from new_raster_dicts_list.""" + new_rasters = GeoDataFrame.from_records(new_raster_dicts_list) + new_rasters.set_geometry("geometry", inplace=True) new_rasters.set_crs(epsg=rasters_crs_epsg_code, inplace=True) new_rasters.set_index("raster_name", inplace=True) new_rasters = new_rasters.convert_dtypes( diff --git a/geographer/downloaders/eodag_downloader_for_single_vector.py b/geographer/downloaders/eodag_downloader_for_single_vector.py new file mode 100644 index 00000000..b01ecc30 --- /dev/null +++ b/geographer/downloaders/eodag_downloader_for_single_vector.py @@ -0,0 +1,398 @@ +"""SingleRasterDownloader for all providers supported by eodag. + +In particular, this downloader can be used to obtain Sentinel-2 L2A +data. +""" + +from __future__ import annotations + +import logging +from datetime import date, datetime +from pathlib import Path +from typing import Any, Literal + +import eodag +import pandas as pd +import shapely +from eodag import EODataAccessGateway +from eodag.api.search_result import SearchResult +from eodag.utils import sanitize +from pydantic import Field, PrivateAttr +from shapely.geometry import Polygon + +from geographer.downloaders.base_downloader_for_single_vector import ( + RasterDownloaderForSingleVector, +) +from geographer.errors import NoRastersForVectorFoundError +from geographer.global_constants import DUMMY_VALUE + +log = logging.getLogger(__name__) + + +class SearchParams(dict): + """Parameters for the `search_all` method of an EODataAccessGateway. + + Note: + The `geom` parameter of the EODataAccessGateway.search_all method is + omitted, because its value is determined as a `geographer` argument. + + See See https://eodag.readthedocs.io/en/latest/api_reference/core.html#eodag.api.core.EODataAccessGateway.search_all. # noqa + for more details on most of the arguments below. + + This dictionary may include the following keys: + - `start` (str | None): + Start sensing time in ISO 8601 format (e.g. “1990-11-26”, + “1990-11-26T14:30:10.153Z”, “1990-11-26T14:30:10+02:00”, …). + If no time offset is given, the time is assumed to be given in UTC. + - `end` (str | None): + End sensing time in ISO 8601 format (e.g. “1990-11-26”, + “1990-11-26T14:30:10.153Z”, “1990-11-26T14:30:10+02:00”, …). + If no time offset is given, the time is assumed to be given in UTC. + - `provider` (str | None): + The provider to be used. If set, search fallback will be disabled. + If not set, the configured preferred provider will be used at first + before trying others until finding results. See + https://eodag.readthedocs.io/en/stable/_modules/eodag/api/core.html#EODataAccessGateway.search. # noqa + - `items_per_page` (int | None): + Number of items to retrieve per page. + - `locations` (dict[str, str] | None): + Location filtering by name using locations configuration + {""=""}. For example, {"country"="PA."} + will use the geometry of the features having the property ISO3 starting + with 'PA' such as Panama and Pakistan in the shapefile configured with + name=country and attr=ISO3. + - In addition, the dictionary may contain any other keys (except ``geom``) + compatible with the provider. + """ + + pass + + +class DownloadParams(dict): + """Parameters for the `download` method of an EOProduct. + + Refer to the EOProduct documentation for more details: + https://eodag.readthedocs.io/en/stable/api_reference/eoproduct.html + + Some parameters of the EOProduct.download method should not be used: + - `product`: Omitted because the value is determined by `geographer`. + - `progress_callback`: Omitted because its values cannot easily + be JSON serialized. + - `extract`: Omitted because geographer requires the value of this + kwarg to be True. + - `output_dir`: Omitted because the value is determined by `geographer`. + - `asset`: Omitted because it does not make sense for a downloader + for a single vector. + - `output_extension`: Omitted for simplicity's sake. + + This dictionary may include any of the following keys: + - `wait` (int): The wait time in minutes between two download attempts. + - `timeout` (int): The max time in minutes to retry downloading before stopping. + - `dl_url_params` (dict[str, str]): Additional URL parameters to pass to + the download URL. + - `delete_archive` (bool): Whether to delete the downloaded archives + after extraction. + """ + + pass + + +FORBIDDEN_DOWNLOAD_KWARGS_KEYS = [ + "product", + "progress_callback", + "extract", + "output_dir", + "asset", +] + + +ASC_OR_DESC = Literal["ASC", "DESC"] +ASC_OR_DESC_VALUES = ["ASC", "DESC"] + + +class EodagDownloaderForSingleVector(RasterDownloaderForSingleVector): + """Downloader for providers supported by eodag. + + Refer to the eodag documentation at + https://eodag.readthedocs.io/en/stable/ + for more details on eodag. + """ + + eodag_kwargs: dict[str, Any] = Field( + default_factory=dict, + description=( + "Optional kwargs defining an EODataAccessGateway instance. " + "Possible keys are 'user_conf_file_path' to define a Path to " + "the user configuration file and locations_conf_path to define " + "a Path to the locations configuration file. " + "See https://eodag.readthedocs.io/en/stable/api_reference/core.html#eodag.api.core.EODataAccessGateway." # noqa + ), + ) + + eodag_setup_logging_kwargs: dict[str, Any] = Field( + default_factory=lambda: dict(verbose=1), + description=( + "Kwargs to be passed to eodag.utils.logging.setup_logging " + "to set up eodag logging. See " + "https://eodag.readthedocs.io/en/stable/api_reference/utils.html#eodag.utils.logging.setup_logging" # noqa + ), + ) + + # Note that eodag as is not defined as a field. + # This is so the pydantic fields are json serializable. + _eodag: EODataAccessGateway = PrivateAttr() + + def model_post_init(self, __context): + """Perform additional initialization.""" + eodag.setup_logging(**self.eodag_setup_logging_kwargs) + + self._eodag = EODataAccessGateway(**self.eodag_kwargs) + + @property + def eodag(self) -> EODataAccessGateway: + """Get eodag.""" + return self._eodag + + def download( # type: ignore + self, + vector_name: str | int, + vector_geom: Polygon, + download_dir: Path, + previously_downloaded_rasters_set: set[str], + *, # downloader_params of RasterDownloaderForVectors.download start below + search_kwargs: SearchParams | None = None, + download_kwargs: DownloadParams | None = None, + properties_to_save: list[str] | None = None, + filter_property: dict[str, Any] | list[dict[str, Any]] | None = None, + filter_online: bool = True, + sort_by: str | tuple[str, ASC_OR_DESC] | None = None, + suffix_to_remove: str | None = None, + ) -> dict: + """Download a raster for a vector feature using eodag. + + Download a raster fully containing the vector feature, + returns a dict in the format needed by the associator. + + Note: + The start, end, provider, items_per_page, and locations arguments correspond + to kwargs of EODataAccessGateway.search_all (though the provider kwarg is only + documented for the EODataAccessGateway.search). The descriptions are adapted + from the official eodag documentation at + https://eodag.readthedocs.io/en/latest/api_reference/core.html#eodag.api.core.EODataAccessGateway. + + Args: + vector_name: + name of vector feature + vector_geom: + Geometry of vector feature + download_dir: + Directory Sentinel-2 products will be downloaded to. + previously_downloaded_rasters_set: + Set of already downloaded products. + search_kwargs: + Keyword arguments for the `search_all` method of an EODataAccessGateway, + excluding "geom". Refer to the docstring of SearchParams for more details. + download_kwargs: + Keyword arguments for the download` method of an EOProduct, excluding + certain keys. Refer to the docstring of DownloadParams for more details. + properties_to_save: + List of property keys to extract and save from an EOProduct's + properties dictionary. Values that cannot be stored in a + GeoDataFrame will be replaced with the string "__DUMMY_VALUE__". + filter_property: + Kwargs or list of kwargs defining criteria according to which products + should be filtered. These correspond exactly to kwargs for the + EODataAccessGateway.filter_property method. Refer to + https://eodag.readthedocs.io/en/stable/plugins_reference/generated/eodag.plugins.crunch.filter_property.FilterProperty.html#eodag.plugins.crunch.filter_property.FilterProperty # noqa + for more details. + filter_online: + Whether to filter the results to include only products that are online. + sort_by: + (Optional) A string or tuple like ("key", "ASC"|"DESC") by which to sort the results. + If a string is provided, it will be interpreted as ("key", "ASC"). + suffix_to_remove: + (Optional) A suffix to strip from the downloaded EOProduct's file name. + The resulting .tif raster will use the modified file name (if applicable) + with ".tif" appended. + + Returns: + A dictionary containing information about the rasters. + ({'list_raster_info_dicts': [raster_info_dict]}) + + Raises: + ValueError: Raised if an unkknown product type is given. + NoRastersForPolygonFoundError: Raised if no downloadable rasters + could be found for the vector feature. + """ + search_kwargs = search_kwargs or {} + download_kwargs = download_kwargs or {} + properties_to_save = properties_to_save or [] + filter_property = filter_property or {} + if isinstance(filter_property, dict): + filter_property = [filter_property] + sort_by = sort_by or [] + if isinstance(sort_by, (str, tuple)): + sort_by = [sort_by] + sort_by = [ + (entry, "ASC") if isinstance(entry, str) else entry for entry in sort_by + ] + + self._validate_download_args(download_kwargs=download_kwargs, sort_by=sort_by) + + search_criteria = search_kwargs | { + "geom": vector_geom, + } + + result: SearchResult = self.eodag.search_all(**search_criteria) + + # Only keep results that contain the geometry + result.filter_overlap(geometry=vector_geom, contains=True) + + for filter_kwargs in filter_property: + result.filter_property(**filter_kwargs) + + if filter_online: + result.filter_online() + + if len(result) == 0: + raise NoRastersForVectorFoundError( + f"No rasters for vector feature {vector_name} found with " + f"search criteria {search_criteria}!" + ) + + if sort_by: + # Currently only support sorting by a single key. + # In the future, we may implement hierarchical + # sorting by multiple keys. + key, asc_or_desc = sort_by[0] + if asc_or_desc == "ASC": + reverse = False + elif asc_or_desc == "DESC": + reverse = True + else: + raise ValueError( + f"sort_by is {sort_by[0]}, second tuple entry must be " + f"one of 'ASC' or 'DESC'" + ) + result = SearchResult( + products=sorted( + result, key=lambda product: product.properties[key], reverse=reverse + ), + number_matched=result.number_matched, + errors=result.errors, + ) + + # Return dicts with values to be collected in calling associator. + raster_info_dict = {} + + for eo_product in result: + + # For the next couple of lines we are essentially following + # the _prepare_download method of the + # eodag.plugins.download.base.Download class to extract + # the name of the extracted product. + sanitized_title = sanitize(eo_product.properties["title"]) + if sanitized_title == eo_product.properties["title"]: + collision_avoidance_suffix = "" + else: + collision_avoidance_suffix = "-" + sanitize(eo_product.properties["id"]) + extracted_product_file_name = sanitized_title + collision_avoidance_suffix + + if suffix_to_remove is not None: + raster_name = ( + extracted_product_file_name.removesuffix(suffix_to_remove) + ".tif" + ) + else: + raster_name = extracted_product_file_name + ".tif" + + if raster_name not in previously_downloaded_rasters_set: + + download_params = download_kwargs | dict( + product=eo_product, + output_dir=download_dir, + extract=True, + ) + + try: + location = self.eodag.download(**download_params) + + location_name = Path(location).name + if location_name != extracted_product_file_name: + msg = ( + "The name of the downloaded file (%s) does not " + "match the expected name (%s). eodag must have " + "changed the way they determine the file name. " + "Unfortunately, `geographer` relies on being able " + "to determine the name of the extracted file " + "without downloading the product. The " + "`EodagDownloaderForSingleVector` will have to be " + "updated to work with the new naming convention of" + "eodag. Sorry!" + ) + log.error(msg, location_name, extracted_product_file_name) + raise RuntimeError( + msg % (msg, location_name, extracted_product_file_name) + ) + + # And assemble the information to be updated + # in the returned raster_info_dict: + properties_to_save_dict = {} + for key in properties_to_save: + if key in eo_product.properties: + val = eo_product.properties.get(key) + definitely_accepted_types = ( + str, + int, + float, + type(None), + date, + datetime, + shapely.geometry.base.BaseGeometry, + ) + if not isinstance(val, definitely_accepted_types): + try: + pd.Series([val]) + except (TypeError, ValueError): + val = DUMMY_VALUE + properties_to_save_dict[key] = val + raster_info_dict.update(properties_to_save_dict) + raster_info_dict["raster_name"] = raster_name + raster_info_dict["raster_processed?"] = False + + return {"list_raster_info_dicts": [raster_info_dict]} + + except Exception as exc: + log.warning( + "Failed to download, extract, or process %s: %s", + eo_product, + str(exc), + ) + + raise NoRastersForVectorFoundError( + f"All rasters for {vector_name} failed to download." + ) + + def _validate_download_args(self, download_kwargs: DownloadParams, sort_by: list): + """Validate download arguments.""" + for key in download_kwargs: + if key in FORBIDDEN_DOWNLOAD_KWARGS_KEYS: + msg = "The key '%s' is forbidden and cannot be used." + log.error(msg, key) + raise ValueError(msg % key) + + for key, asc_or_desc in sort_by: + if asc_or_desc not in ASC_OR_DESC_VALUES: + msg = ( + "Found %s as second entry of a sort_by pair. " + "Must be one of 'ASC' or 'DESC'!" + ) + log.error(msg, asc_or_desc) + raise ValueError(msg % asc_or_desc) + + if len(sort_by) > 1: + msg = ( + "At the moment sorting is only supported for a single key at a time. " + "The length of the sort_by list must be at most 1." + ) + log.error(msg) + raise ValueError(msg) diff --git a/geographer/downloaders/jaxa_download_processor.py b/geographer/downloaders/jaxa_download_processor.py index c2b52290..17a5ce71 100644 --- a/geographer/downloaders/jaxa_download_processor.py +++ b/geographer/downloaders/jaxa_download_processor.py @@ -1,7 +1,5 @@ """RasterDownloadProcessor for JAXA downloads.""" -from __future__ import annotations - import logging import shutil from pathlib import Path @@ -25,7 +23,6 @@ def process( download_dir: Path, rasters_dir: Path, return_bounds_in_crs_epsg_code: int, - **kwargs, ) -> dict: """Process a downloaded JAXA file. diff --git a/geographer/downloaders/jaxa_downloader_for_single_vector.py b/geographer/downloaders/jaxa_downloader_for_single_vector.py index 434dbb41..45de48bf 100644 --- a/geographer/downloaders/jaxa_downloader_for_single_vector.py +++ b/geographer/downloaders/jaxa_downloader_for_single_vector.py @@ -25,7 +25,7 @@ from contextlib import closing from datetime import datetime from pathlib import Path -from typing import Any, Literal, Optional, Union +from typing import Any, Literal import numpy as np from shapely.geometry.base import BaseGeometry @@ -50,14 +50,14 @@ class JAXADownloaderForSingleVector(RasterDownloaderForSingleVector): def download( self, - vector_name: Union[int, str], + vector_name: str | int, vector_geom: BaseGeometry, download_dir: Path, - previously_downloaded_rasters_set: set[Union[str, int]], + previously_downloaded_rasters_set: set[str | int], + *, # downloader_params of RasterDownloaderForVectors.download start below data_version: str = None, download_mode: str = None, - **kwargs, - ) -> dict[Union[Literal["raster_name", "raster_processed?"], str], Any]: + ) -> dict[Literal["raster_name", "raster_processed?"] | str, Any]: """Download JAXA DEM data for a vector feature. Download DEM data from jaxa.jp's ftp-server for a given vector @@ -83,7 +83,6 @@ def download( Defaults if possible to whichever choice you made last time. download_mode: One of 'bboxvertices', 'bboxgrid'. Defaults if possible to whichever choice you made last time. - **kwargs: other kwargs, ignored. Returns: dict of dicts according to the connector convention @@ -101,9 +100,7 @@ def download( jaxa_file_and_folder_names = set() if download_mode == "bboxvertices": - for x, y in vector_geom.envelope.exterior.coords: - jaxa_folder_name = "{}/".format( self._obtain_jaxa_index(x // 5 * 5, y // 5 * 5) ) @@ -112,7 +109,6 @@ def download( jaxa_file_and_folder_names |= {(jaxa_file_name, jaxa_folder_name)} elif download_mode == "bboxgrid": - minx, miny, maxx, maxy = vector_geom.envelope.exterior.bounds deltax = math.ceil(maxx - minx) @@ -120,7 +116,6 @@ def download( for countx in range(deltax + 1): for county in range(deltay + 1): - x = minx + countx y = miny + county @@ -139,7 +134,6 @@ def download( ) # to collect information per downloaded file for connector for jaxa_file_name, jaxa_folder_name in jaxa_file_and_folder_names: - # Skip download if file has already been downloaded ... if jaxa_file_name[:-7] + "_DSM.tif" in previously_downloaded_rasters_set: # in this case skip download, don't store in list_raster_info_dicts @@ -215,8 +209,8 @@ def download( def _obtain_jaxa_index( self, - x: Optional[float] = None, - y: Optional[float] = None, + x: float | None = None, + y: float | None = None, nx: int = 3, ny: int = 3, ): diff --git a/geographer/downloaders/sentinel2_download_processor.py b/geographer/downloaders/sentinel2_download_processor.py index c39b4d22..e534743b 100644 --- a/geographer/downloaders/sentinel2_download_processor.py +++ b/geographer/downloaders/sentinel2_download_processor.py @@ -3,19 +3,26 @@ Should be easily extendable to Sentinel-1. """ -from __future__ import annotations - -import os +import logging +import shutil from pathlib import Path -from zipfile import ZipFile from geographer.downloaders.base_download_processor import RasterDownloadProcessor -from geographer.downloaders.sentinel2_safe_unpacking import safe_to_geotif_L2A +from geographer.downloaders.sentinel2_safe_unpacking import ( + NO_DATA_VAL, + safe_to_geotif_L2A, +) from geographer.utils.utils import transform_shapely_geometry +log = logging.getLogger(__name__) + -class Sentinel2Processor(RasterDownloadProcessor): - """Processes downloads of Sentinel-2 products from Copernicus Sci-hub.""" +# TODO Test with the 'creodias', 'onda', and 'sara' providers +# TODO (archive_depth 2). +# TODO Use provider's archive_depth to extend to archive_depth not +# TODO equal to 2 i.e. 'planetary_computer' (archive_depth 1). +class Sentinel2SAFEProcessor(RasterDownloadProcessor): + """Processes downloads of L2A Sentinel-2 SAFE files.""" def process( self, @@ -23,8 +30,11 @@ def process( download_dir: Path, rasters_dir: Path, return_bounds_in_crs_epsg_code: int, + *, # processor_params of RasterDownloaderForVectors.download start below resolution: int, - **kwargs, + delete_safe: bool, # TODO better name, uniformly usable for all processors? + file_suffix: str = ".SAFE", + nodata_val: int = NO_DATA_VAL, ) -> dict: """Process Sentinel-2 download. @@ -33,32 +43,77 @@ def process( GeoTiff raster in the right directory, and return information about the raster in a dict. + Warning: + Tested with the `cop_dataspace` eodag provider. It should also work with + 'creodias', 'onda', and 'sara', which have an `archive_depth` of 2. + For providers with a different `archive_depth`, the processor may need + adjustments to locate the SAFE file correctly based on the raster name. + Args: - raster_name: The name of the raster. - in_dir: The directory containing the zip file. - out_dir: The directory to save the - convert_to_crs_epsg: The EPSG code to use to create the raster bounds - property. # TODO: this name might not be appropriate as it - suggests that the raster geometries will be converted into that crs. - resolution: resolution. + raster_name: + The name of the raster. + download_dir: + The dir containing the SAFE file to be processed. + rasters_dir: + The dir in which the .tif output file should be placed. + return_bounds_in_crs_epsg_code: + The EPSG of the CRS in which the bounds of the raster + should be returned. + resolution: + The desired resolution of the output tif file. + delete_safe: + Whether to delete the SAFE file after extracting the tif file. + file_suffix: + Possible suffix by which the stem of the raster_name and the + downloaded SAFE file to be processed differ. If used together + with the `EodagDownloaderForSingleVector` for the 'cop_dataspace' + provider and the `RasterDownloaderForVectors` and the + `downloader_params` parameter dict of the + `RasterDownloaderForVectors.download` method contains + a `"suffix_to_remove: ".SAFE"` pair then the default value of + ".SAFE" for the file_suffix will result in nicer tif names, + e.g. S2B_MSIL2A_20231208T013039_N0509_R074_T54SUE_20231208T031743.tif + instead of S2B_MSIL2A_20231208T013039_N0509_R074_T54SUE_20231208T031743.SAFE.tif. # noqa + nodata_val: + The nodata value to fill. Defaults to 0. Returns: return_dict: Contains information about the downloaded product. """ - filename_no_extension = Path(raster_name).stem - zip_filename = filename_no_extension + ".zip" - safe_path = download_dir / f"safe_files/{filename_no_extension}.SAFE" - zip_path = download_dir / zip_filename - - # extract zip to SAFE - with ZipFile(zip_path) as zip_ref: - zip_ref.extractall(download_dir / Path("safe_files/")) - os.remove(zip_path) - # convert SAFE to GeoTiff + log.info("Processing %s to a .tif file. This might take a while..") + + safe_path = download_dir / raster_name.removesuffix(".tif") + safe_path_with_suffix = safe_path.with_suffix(file_suffix) + + if safe_path.exists() and (not safe_path_with_suffix.exists()): + pass # Use safe_path + elif safe_path_with_suffix.exists() and (not safe_path.exists()): + safe_path = safe_path_with_suffix + elif safe_path.exists() and safe_path_with_suffix.exists(): + msg = ( + "Both %s and %s exist in %s.\n" + "Unable to resolve ambiguity in which file/dir to process." + ) + log.error(msg, safe_path.name, safe_path_with_suffix.name, safe_path.parent) + raise RuntimeError( + msg % (safe_path.name, safe_path_with_suffix.name, safe_path.parent) + ) + elif (not safe_path.exists()) and (not safe_path_with_suffix.exists()): + msg = "Can't find SAFE file in expected location(s): %s" + log.error(msg, safe_path) + raise RuntimeError(msg % safe_path) + conversion_dict = safe_to_geotif_L2A( - safe_root=Path(safe_path), resolution=resolution, outdir=rasters_dir + safe_root=safe_path, + resolution=resolution, + outdir=rasters_dir, + nodata_val=nodata_val, ) + if delete_safe: + log.info("Deleting SAFE file: %s", safe_path) + shutil.rmtree(safe_path, ignore_errors=True) + orig_crs_epsg_code = int(conversion_dict["crs_epsg_code"]) raster_bounding_rectangle_orig_crs = conversion_dict[ "raster_bounding_rectangle" diff --git a/geographer/downloaders/sentinel2_downloader_for_single_vector.py b/geographer/downloaders/sentinel2_downloader_for_single_vector.py deleted file mode 100644 index 46c53966..00000000 --- a/geographer/downloaders/sentinel2_downloader_for_single_vector.py +++ /dev/null @@ -1,233 +0,0 @@ -"""SingleRasterDownloader for Sentinel-2 rasters from Copernicus Sci-hub. - -Should be easily extendable to Sentinel-1. -""" - -from __future__ import annotations - -import configparser -import logging -from pathlib import Path -from typing import Any, Union -from zipfile import ZipFile - -from sentinelsat import SentinelAPI -from sentinelsat.exceptions import ServerError, UnauthorizedError -from shapely import wkt -from shapely.geometry import Polygon - -from geographer.downloaders.base_downloader_for_single_vector import ( - RasterDownloaderForSingleVector, -) -from geographer.errors import NoRastersForVectorFoundError - -# logger -log = logging.getLogger(__name__) - - -class SentinelDownloaderForSingleVector(RasterDownloaderForSingleVector): - """Downloader for Sentinel-2 rasters. - - Requires environment variables sentinelAPIusername and - sentinelAPIpassword to set up the sentinel API. Assumes rasters has - columns 'geometry', 'timestamp', 'orig_crs_epsg_code', and - 'raster_processed?'. Subclass/modify if you need other columns. - - See - https://sentinelsat.readthedocs.io/en/latest/api_reference.html - for details on args passed to the API (e.g. date). - """ - - def download( # type: ignore - self, - vector_name: Union[str, int], - vector_geom: Polygon, - download_dir: Path, - previously_downloaded_rasters_set: set[str], - producttype: str, - resolution: int, - max_percent_cloud_coverage: int, - date: Any, - area_relation: str, - credentials: Union[tuple[str, str], Path, str], - **kwargs, - ) -> dict: - """Download a S-2 raster for a vector feature. - - Download a sentinel-2 raster fully containing the vector feature, - returns a dict in the format needed by the associator. - - Note: - If not given, the username and password for the Copernicus Sentinel-2 - OpenAPI will be read from an s2_copernicus_credentials.ini in - self.associator_dir. - - Args: - vector_name: name of vector feature - vector_geom: geometry of vector feature - download_dir: Directory Sentinel-2 products will be downloaded to. - previously_downloaded_rasters_set: Set of already downloaded products. - producttype: One of 'L1C'/'S2MSI1C' or 'L2A'/'S2MSI2A' - resolution: One of 10, 20, or 60. - max_percent_cloud_coverage: Integer between 0 and 100. - date: See https://sentinelsat.readthedocs.io/en/latest/api_reference.html - area_relation : See - https://sentinelsat.readthedocs.io/en/latest/api_reference.html - credentials: Tuple of username and password or - Path or str to ini file containing API credentials. - - Returns: - A dictionary containing information about the rasters. - ({'list_raster_info_dicts': [raster_info_dict]}) - - Raises: - ValueError: Raised if an unkknown product type is given. - NoRastersForPolygonFoundError: Raised if no downloadable rasters with cloud - coverage less than or equal to max_percent_cloud_coverage could be found - for the vector feature. - """ - self._check_args_are_valid(producttype, resolution, max_percent_cloud_coverage) - - # Determine missing args for the sentinel query. - rectangle_wkt: str = wkt.dumps(vector_geom.envelope) - producttype = self._get_longform_producttype(producttype) - - api = self._get_api(credentials) - - try: - - # Query, remember results - products = api.query( - area=rectangle_wkt, - date=date, - area_relation=area_relation, - producttype=producttype, - cloudcoverpercentage=(0, max_percent_cloud_coverage), - ) - - products = {k: v for k, v in products.items() if api.is_online(k)} - - except (UnauthorizedError, ServerError) as exc: - log.exception(str(exc)) - raise - - # If we couldn't find anything, remember that, so we can deal with it later. - if len(products) == 0: - raise NoRastersForVectorFoundError( - f"No rasters for vector feature {vector_name} found with " - f"cloud coverage less than or equal to {max_percent_cloud_coverage}!" - ) - - # Return dicts with values to be collected in calling associator. - raster_info_dict = {} - - # If the query was succesful, ... - products_list = list(products.keys()) - products_list = sorted( - products_list, key=lambda x: products[x]["cloudcoverpercentage"] - ) - # ... iterate over the products ordered by cloud coverage - for product_id in products_list: - - product_metadata = api.get_product_odata(product_id, full=True) - - try: - # (this key might have to be 'filename' - # (minus the .SAFE at the end) for L1C products?) - raster_name = product_metadata["title"] + ".tif" - except Exception as exc: - raise Exception( - "Couldn't get the filename. Are you trying to download L1C " - "products? Try changing the key for the products dict in the " - "line of code above this..." - ) from exc - - if raster_name not in previously_downloaded_rasters_set: - try: - api.download(product_id, directory_path=download_dir) - zip_path = download_dir / (product_metadata["title"] + ".zip") - with ZipFile(zip_path) as zip_ref: - assert zip_ref.testzip() is None - - # And assemble the information to be updated - # in the returned raster_info_dict: - raster_info_dict["raster_name"] = raster_name - raster_info_dict["raster_processed?"] = False - raster_info_dict["timestamp"] = product_metadata["Date"].strftime( - "%Y-%m-%d-%H:%M:%S" - ) - - return {"list_raster_info_dicts": [raster_info_dict]} - except Exception as exc: - log.warning( - "Failed to download or unzip %s: %s", - product_metadata["title"], - str(exc), - ) - - raise NoRastersForVectorFoundError( - f"All rasters for {vector_name} failed to download." - ) - - def _get_longform_producttype(self, producttype: str): - """Return producttype in longform as needed by the sentinel API.""" - if producttype in {"L2A", "S2MSI2A"}: - producttype = "S2MSI2A" - elif producttype in {"L1C", "S2MSI1C"}: - producttype = "S2MSI1C" - else: - raise ValueError(f"Unknown producttype: {producttype}") - - return producttype - - @staticmethod - def _check_args_are_valid( - producttype: str, - resolution: int, - max_percent_cloud_coverage: int, - ): - """Run some safety checks on the arg values.""" - if resolution not in {10, 20, 60}: - raise ValueError(f"Unknown resolution: {resolution}") - if max_percent_cloud_coverage < 0 or max_percent_cloud_coverage > 100: - raise ValueError( - f"Unknown max_percent_cloud_coverage: {max_percent_cloud_coverage}" - ) - if producttype not in {"L1C", "S2MSI1C", "L2A", "S2MSI2A"}: - raise ValueError(f"Unknown producttype: {producttype}") - - def _get_api(self, credentials: Union[tuple[str, str], Path, str]): - # Get username and password to set up the sentinel API ... - if ( - isinstance(credentials, tuple) - and len(credentials) == 2 - and all(isinstance(cred, str) for cred in credentials) - ): - username, password = credentials - elif isinstance(credentials, (str, Path)): - try: - config = configparser.ConfigParser() - if not credentials.is_file(): - raise FileNotFoundError( - "Can't find .ini file containing username and password in " - f"{credentials}" - ) - config.read(credentials) - username = config["login"]["username"] - password = config["login"]["password"] - except KeyError as exc: - log.error( - "Missing entry in 'sentinel_scihub.ini' file. " - "Need API credentials. %s", - exc, - ) - else: - raise TypeError( - "Need username and password or config_path to .ini file " - "containing username and password" - ) - - # ... and instantiate the API. - api = SentinelAPI(username, password) - - return api diff --git a/geographer/downloaders/sentinel2_safe_unpacking.py b/geographer/downloaders/sentinel2_safe_unpacking.py index 05618fb4..df31f562 100644 --- a/geographer/downloaders/sentinel2_safe_unpacking.py +++ b/geographer/downloaders/sentinel2_safe_unpacking.py @@ -6,7 +6,6 @@ import os from collections import OrderedDict from pathlib import Path -from typing import Union import geopandas as gpd import numpy as np @@ -15,6 +14,7 @@ from rasterio.errors import RasterioIOError from scipy.ndimage import zoom from shapely.geometry import box +from tqdm.auto import tqdm from geographer.utils.utils import create_logger @@ -25,52 +25,88 @@ def safe_to_geotif_L2A( safe_root: Path, - resolution: Union[str, int], + resolution: str | int, upsample_lower_resolution: bool = True, outdir: Path = None, TCI: bool = True, requested_jp2_masks: list[str] = ["CLDPRB", "SNWPRB"], requested_gml_mask: list[tuple[str, str]] = [("CLOUDS", "B00")], + nodata_val: int = NO_DATA_VAL, ) -> dict: - """Convert a L2A-level .SAFE file to geotif. + """Convert a L2A-level Sentinel-2 .SAFE file to a GeoTIFF. - Convert a .SAFE file with L2A sentinel-2 data to geotif and return a - dict with the crs epsg code and a shapely polygon defined by the raster - bounds. + The GeoTIFF contains raster bands derived from the .SAFE file, including: + - True color composite (TCI) bands if requested. + - JP2 masks (e.g., cloud or snow masks) at the desired resolution. + - Additional GML masks if available. Warning: - The L2A band structure changed in October 2021, new products do not contain gml - masks anymore. In this + Sentinel-2 L2A products dated later than October 2021 + no longer include GML masks. - ..note:: + Note: + + - The GeoTIFF bands are ordered as follows: + + 1. **True Color Composite (TCI)** (optional): + Red, Green, Blue (if ``TCI=True``). + 2. **Spectral Bands**: JP2 data bands at the target resolution, + optionally including upsampled lower-resolution bands + if ``upsample_lower_resolution=True``. + 3. **JP2 Masks**: Added in the order specified by ``requested_jp2_masks`` + (e.g., ``"CLDPRB"``, ``"SNWPRB"``). Masks are limited to a maximum + resolution of 20m. + 4. **GML Masks**: Rasterized from ``requested_gml_mask``, with + empty bands added for missing masks. - - band structure of final geotif: - if TCI: 1-3 TCI RGB - else sorted(jps2_masks and bands (either only desired resolution or - additionally upsampled)), gml_mask_order - jp2_masks are only available up to a resolution of 20 m, so for 10m the 20m mask ist taken - - SNWPRB for snow masks + - ``"SNWPRB"`` for snow masks + Args: - safe_root: is the safe folder root - resolution: the desired resolution - upsample_lower_resolution: Whether to include lower resolution bands and - upsample them - TCI: whether to load the true color raster - requested_jp2_masks: jp2 mask to load - requested_gml_mask: gml masks to load ([0] mask name as string, [1] band for - which to get the mask) + safe_root: + Path to the root directory of the .SAFE file. + resolution: + Desired resolution for the GeoTIFF (10, 20, or 60 meters). + upsample_lower_resolution: + If True, includes lower-resolution bands + and upsamples them to match the target resolution. Defaults to True. + outdir: + Directory where the GeoTIFF will be saved. If None, saves the + file in the parent directory of `safe_root`. Defaults to None. + TCI: + Whether to include true color raster bands (TCI). + Defaults to True. + requested_jp2_masks: + List of JP2 masks to include in the output. + Defaults to ["CLDPRB", "SNWPRB"]. + requested_gml_mask: List of GML masks to include. Each tuple contains + the mask name (e.g., "CLOUDS") and the associated band (e.g., "B00"). + Defaults to [("CLOUDS", "B00")]. + nodata_val: + Value to use for no-data areas in the GeoTIFF. Defaults to 0. Returns: - dict containing tif crs and bounding rectangle + dict: A dictionary containing: + - `crs_epsg_code` (int): + The EPSG code of the CRS. + - `raster_bounding_rectangle` (shapely.geometry.Polygon): + The bounding rectangle of the output GeoTIFF. + + Raises: + AssertionError: + If `resolution` is not one of the supported values (10, 20, 60). + RasterioIOError: + If there are issues reading or processing the JP2/GML files. """ # assert resolution is within available assert resolution in [10, 20, 60, "10", "20", "60"] # define output file + raster_name = safe_root.stem out_file_parent_dir = outdir if (outdir and outdir.is_dir()) else safe_root.parent - outfile = out_file_parent_dir / (safe_root.stem + "_TEMP.tif") + outfile = out_file_parent_dir / (raster_name + "_TEMP.tif") granule_dir = safe_root / "GRANULE" masks_dir = granule_dir / "{}/QI_DATA/".format(os.listdir(granule_dir)[0]) @@ -110,7 +146,6 @@ def safe_to_geotif_L2A( # include lower resolution bands if upsample_lower_resolution: - for higher_res in filter(lambda res: res > int(resolution), [10, 20, 60]): jp2_higher_res_path = granule_dir / "{}/IMG_DATA/R{}m/".format( os.listdir(granule_dir)[0], higher_res @@ -180,69 +215,75 @@ def safe_to_geotif_L2A( transform=out_default_reader.transform, dtype=out_default_reader.dtypes[0], ) as dst: + dst.nodata = nodata_val + + with tqdm(total=count, desc=f"Extracting tif from {raster_name}.SAFE.") as pbar: + + # write gml masks + for idx, (gml_name, gml_path) in enumerate(gml_mask_paths_dict.items()): + try: + if not gml_path.is_file(): + raise FileNotFoundError( + f"Can't find GML mask {gml_name} in expected location " + f"{gml_path.relative_to(safe_root)}" + ) + shapes = gpd.read_file(gml_path)["geometry"].values + mask, _, _ = rasterio.mask.raster_geometry_mask( + out_default_reader, shapes, crop=False, invert=True + ) + # in case mask is empty or does not exist: + except (ValueError, AssertionError, RasterioIOError, FileNotFoundError): + log.info( + "Using all zero band for gml mask %s for %s", + gml_name, + safe_root.name, + ) + mask = np.full( + shape=out_default_reader.read(1).shape, + fill_value=0.0, + dtype=np.uint16, + ) + + band_idx = len(bands_dict) + 3 * TCI + idx + 1 + tif_band_names[band_idx] = "_".join(gml_name) + + dst.write(mask.astype(np.uint16), band_idx) + pbar.update(1) + + # write jp2 bands + for idx, (band_name, (dst_reader, res)) in enumerate(bands_dict.items()): + if res != int(resolution): + assert res % int(resolution) == 0 + factor = res // int(resolution) + + raster = dst_reader.read(1) + raster = zoom(raster, factor, order=3) + + assert raster.shape == (10980, 10980) + + else: + raster = dst_reader.read(1) + + if not dst_reader.dtypes[0] == out_default_reader.dtypes[0]: + raster = (raster * (65535.0 / 255.0)).astype(np.uint16) + + band_idx = 3 * TCI + idx + 1 + tif_band_names[band_idx] = band_name + dst.write(raster, band_idx) + dst_reader.close() + pbar.update(1) - dst.nodata = NO_DATA_VAL - - # write gml masks - for idx, (gml_name, gml_path) in enumerate(gml_mask_paths_dict.items()): - - try: - assert gml_path.is_file() - shapes = gpd.read_file(gml_path)["geometry"].values - mask, _, _ = rasterio.mask.raster_geometry_mask( - out_default_reader, shapes, crop=False, invert=True - ) - # in case mask is empty or does not exist: - except (ValueError, AssertionError, RasterioIOError): - log.info( - "Using all zero band for gml mask %s for %s", - gml_name, - safe_root.name, - ) - mask = np.full( - shape=out_default_reader.read(1).shape, - fill_value=0.0, - dtype=np.uint16, - ) - - band_idx = len(bands_dict) + 3 * TCI + idx + 1 - tif_band_names[band_idx] = "_".join(gml_name) - - dst.write(mask.astype(np.uint16), band_idx) - - # write jp2 bands - for idx, (band_name, (dst_reader, res)) in enumerate(bands_dict.items()): - - if res != int(resolution): - - assert res % int(resolution) == 0 - factor = res // int(resolution) - - raster = dst_reader.read(1) - raster = zoom(raster, factor, order=3) - - assert raster.shape == (10980, 10980) - - else: - raster = dst_reader.read(1) - - if not dst_reader.dtypes[0] == out_default_reader.dtypes[0]: - raster = (raster * (65535.0 / 255.0)).astype(np.uint16) - - band_idx = 3 * TCI + idx + 1 - tif_band_names[band_idx] = band_name - dst.write(raster, band_idx) - dst_reader.close() - - # write tci - if TCI: - for i in range(3): - - band_idx = i + 1 - raster = (tci_band.read(band_idx) * (65535.0 / 255.0)).astype(np.uint16) - tif_band_names[band_idx] = f"tci_{band_idx}" + # write tci + if TCI: + for i in range(3): + band_idx = i + 1 + raster = (tci_band.read(band_idx) * (65535.0 / 255.0)).astype( + np.uint16 + ) + tif_band_names[band_idx] = f"tci_{band_idx}" - dst.write(raster, band_idx) + dst.write(raster, band_idx) + pbar.update(1) # add tags and descriptions for band_idx, name in tif_band_names.items(): @@ -252,7 +293,7 @@ def safe_to_geotif_L2A( crs_epsg_code = dst.crs.to_epsg() raster_bounding_rectangle = box(*dst.bounds) - outfile.rename(out_file_parent_dir / (safe_root.stem + ".tif")) + outfile.rename(out_file_parent_dir / (raster_name + ".tif")) return { "crs_epsg_code": crs_epsg_code, diff --git a/geographer/errors.py b/geographer/errors.py index 8916fada..b896f508 100644 --- a/geographer/errors.py +++ b/geographer/errors.py @@ -1,25 +1,25 @@ """Custom Error classes.""" -class Error(Exception): - """Base class for exceptions in the connector class.""" +class GeoGrapherError(Exception): + """Base class for exceptions.""" pass -class RasterAlreadyExistsError(Error): +class RasterAlreadyExistsError(GeoGrapherError): """Raster already exists in dataset.""" pass -class NoRastersForVectorFoundError(Error): +class NoRastersForVectorFoundError(GeoGrapherError): """No rasters found or none could be downloaded.""" pass -class RasterDownloadError(Error): +class RasterDownloadError(GeoGrapherError): """Error occurs while downloading raster.""" pass diff --git a/geographer/global_constants.py b/geographer/global_constants.py index b196c93c..c7955d6e 100644 --- a/geographer/global_constants.py +++ b/geographer/global_constants.py @@ -6,6 +6,8 @@ DATA_DIR_SUBDIRS = [ Path("rasters"), Path("labels"), -] # for sentinel-2, also Path("safe_files") +] RASTER_IMGS_INDEX_NAME = "raster_name" VECTOR_FEATURES_INDEX_NAME = "vector_name" + +DUMMY_VALUE = "__DUMMY_VALUE__" diff --git a/geographer/graph/bipartite_graph.py b/geographer/graph/bipartite_graph.py index a886ad95..09b01dd1 100644 --- a/geographer/graph/bipartite_graph.py +++ b/geographer/graph/bipartite_graph.py @@ -38,7 +38,7 @@ import logging from json import JSONDecodeError from pathlib import Path -from typing import Any, Optional +from typing import Any from geographer.graph.bipartite_graph_class import BipartiteGraphClass from geographer.graph.type_aliases import VertexColor, VertexName @@ -87,8 +87,8 @@ class BipartiteGraph(BipartiteGraphClass): def __init__( self, - graph_dict: Optional[dict] = None, - file_path: Optional[Path] = None, + graph_dict: dict | None = None, + file_path: Path | None = None, red: VertexColor = None, black: VertexColor = None, directed: bool = False, @@ -112,11 +112,11 @@ def __init__( directed: If True the graph is directed, defaults to False. """ if file_path is not None: - self.file_path: Optional[Path] = file_path + self.file_path: Path | None = file_path self.directed = directed try: - with open(file_path, "r") as read_file: - self._graph_dict = json.load(read_file) + with open(file_path, "r") as file: + self._graph_dict = json.load(file) except FileNotFoundError: log.exception("Graph dict file %s not found", file_path) except JSONDecodeError: @@ -178,7 +178,7 @@ def vertices_opposite( self, vertex_name: VertexName, vertex_color: VertexColor, - edge_data: Optional[Any] = None, + edge_data: Any | None = None, ) -> list[VertexColor]: """Return list of adjacent vertices. @@ -223,7 +223,7 @@ def exists_edge( from_vertex: VertexName, from_vertex_color: VertexColor, to_vertex: VertexName, - edge_data: Optional[Any] = None, + edge_data: Any | None = None, ) -> bool: """Return True if the edge is in the graph, False otherwise.""" if edge_data is None: @@ -332,14 +332,12 @@ def delete_vertex( force_delete_with_edges: """ if not self.exists_vertex(vertex_name, vertex_color): - log.info( "delete_vertex: nothing to do, vertex %s does not exist.", vertex_name ) # if force_delete_with_edges=False check if vertex has outgoing adjacent edges elif self.directed: - log.error( "Sorry, delete_vertex is not implemented for directed graphs. " "I was too lazy to code up the complication of checking " @@ -357,7 +355,6 @@ def delete_vertex( not force_delete_with_edges and list(self.vertices_opposite(vertex_name, vertex_color)) != [] ): - raise Exception( f"delete_vertex: vertex {vertex_name} of color {vertex_color} has " "edges. Set force_delete_with_edges=True to delete anyway " @@ -365,7 +362,6 @@ def delete_vertex( ) else: - # thinking of an undirected graph as a directed graph where for each edge # there is an opposite edge, we first take out the edges _ending_ in # vertex, i.e. the opposite edges to the outgoing ones at vertex. @@ -404,7 +400,7 @@ def delete_edge( opposite_color = self._opposite_color(from_vertex_color) self._graph_dict[opposite_color][to_vertex].pop(from_vertex) - def save_to_file(self, file_path: Optional[Path] = None): + def save_to_file(self, file_path: Path | None = None): """Save graph (i.e. graph_dict) to disk as json file. Args: diff --git a/geographer/graph/bipartite_graph_class.py b/geographer/graph/bipartite_graph_class.py index 340e3b68..0314b5ab 100644 --- a/geographer/graph/bipartite_graph_class.py +++ b/geographer/graph/bipartite_graph_class.py @@ -4,7 +4,7 @@ from abc import ABC from pathlib import Path -from typing import Any, Optional +from typing import Any from geographer.graph.type_aliases import VertexColor, VertexName @@ -36,7 +36,7 @@ def vertices_opposite( self, vertex_name: VertexName, vertex_color: VertexColor, - edge_data: Optional[Any] = None, + edge_data: Any | None = None, ) -> list[VertexColor]: """Return list of adjacent vertices.""" raise NotImplementedError @@ -54,7 +54,7 @@ def exists_edge( from_vertex: VertexName, from_vertex_color: VertexColor, to_vertex: VertexName, - edge_data: Optional[Any], + edge_data: Any | None, ) -> bool: """Return True if the edge is in the graph, False otherwise.""" raise NotImplementedError @@ -101,7 +101,7 @@ def delete_edge( """Delete edge from graph.""" raise NotImplementedError - def save_to_file(self, file_path: Optional[Path] = None): + def save_to_file(self, file_path: Path | None = None): """Save graph to file.""" raise NotImplementedError diff --git a/geographer/graph/bipartite_graph_mixin.py b/geographer/graph/bipartite_graph_mixin.py index 2f56d6c9..d59ed5bb 100644 --- a/geographer/graph/bipartite_graph_mixin.py +++ b/geographer/graph/bipartite_graph_mixin.py @@ -4,7 +4,7 @@ import logging from pathlib import Path -from typing import TYPE_CHECKING, Literal, Optional, Union +from typing import TYPE_CHECKING, Literal from geopandas import GeoDataFrame from shapely.geometry.base import BaseGeometry @@ -53,7 +53,7 @@ def rectangle_bounding_raster(self, raster_name: str) -> BaseGeometry: return self.rasters.loc[raster_name, "geometry"] def vectors_intersecting_raster( - self, raster_name: Union[str, list[str]] + self, raster_name: str | list[str] ) -> list[str]: """Return vector features intersecting one or (any of) several rasters. @@ -83,7 +83,7 @@ def vectors_intersecting_raster( def rasters_intersecting_vector( self, - vector_name: Union[str, list[str]], + vector_name: str | list[str], mode: Literal["names", "paths"] = "names", ) -> list[str]: """Return rasters intersecting several vector feature(s). @@ -125,7 +125,7 @@ def rasters_intersecting_vector( return answer def vectors_contained_in_raster( - self, raster_name: Union[str, list[str]] + self, raster_name: str | list[str] ) -> list[str]: """Return vector features fully containing a given raster. @@ -160,7 +160,7 @@ def vectors_contained_in_raster( def rasters_containing_vector( self, - vector_name: Union[str, list[str]], + vector_name: str | list[str], mode: Literal["names", "paths"] = "names", ) -> list[str]: """Return rasters in which a given vector feature is fully contained. @@ -259,10 +259,10 @@ def _connect_raster_to_vector( self, raster_name: str, vector_name: str, - contains_or_intersects: Optional[str] = None, - vectors: Optional[GeoDataFrame] = None, - raster_bounding_rectangle: Optional[BaseGeometry] = None, - graph: Optional[BipartiteGraphClass] = None, + contains_or_intersects: str | None = None, + vectors: GeoDataFrame | None = None, + raster_bounding_rectangle: BaseGeometry | None = None, + graph: BipartiteGraphClass | None = None, do_safety_check: bool = True, ): """Connect a raster to a vector feature in the graph. @@ -301,7 +301,6 @@ def _connect_raster_to_vector( # get containment relation if not given if contains_or_intersects is None: - vector_geom = vectors.loc[vector_name, "geometry"] non_empty_intersection = vector_geom.intersects(raster_bounding_rectangle) @@ -338,7 +337,7 @@ def _connect_raster_to_vector( vectors.loc[vector_name, self.raster_count_col_name] += 1 def _add_vector_to_graph( - self, vector_name: str, vectors: Optional[GeoDataFrame] = None + self, vector_name: str, vectors: GeoDataFrame | None = None ): """Connect a vector feature all intersecting rasters. @@ -397,9 +396,9 @@ def _add_vector_to_graph( def _add_raster_to_graph_modify_vectors( self, raster_name: str, - raster_bounding_rectangle: Optional[BaseGeometry] = None, - vectors: Optional[GeoDataFrame] = None, - graph: Optional[BipartiteGraphClass] = None, + raster_bounding_rectangle: BaseGeometry | None = None, + vectors: GeoDataFrame | None = None, + graph: BipartiteGraphClass | None = None, ): """Add raster to graph and modify vector features. diff --git a/geographer/img_polygon_associator_TODO b/geographer/img_polygon_associator_TODO deleted file mode 100644 index 64fea973..00000000 --- a/geographer/img_polygon_associator_TODO +++ /dev/null @@ -1,27 +0,0 @@ -[Rustam: I wrote this for myself, might not be comprehensible to anyone else.] - -TODO: README.MD etc. -TODO: SHOULD I COMBINE THE DOWNLOAD AND PROCESSING FUNCTIONS? -TODO: THE __make_geotif_label__ FUNCTION ASSUMES THERE IS A "TYPE" COLUMN GIVING THE SEGMENTATION TYPE OF A POLYGON IN POLYGONS_DF, AND THAT LABELS ARE CATEGORICAL. THIS WILL NOT COVER EVERY USE CASE. HOW BEST TO MAKE THIS MODULAR/ADAPTABLE? SHOULD I TAKE OUT THE __make_geotif_label__ FUNCTION AND PASS IT AS A PARAMETER WHEN CONSTRUCTING A CLASS INSTANCE? AGAIN< WE CAN TAKE A FUNCTIONAL OR OBJECT ORIENTED APPROACH. -TODO: add failsafe/assert statements that coordinates have been converted correctly by comparing raster_bounding_rectangle with box from metadata... why does this not work? -TODO: type checking not yet implemented -TODO: make sure column and index types are what they should be. - Not sure how best to deal with dtype being 'object' or 'O' when it should be str?? -TODO: Check we add a vertex in the graph whenever we add a polygon or an raster! - and take out 'have_raster?' and 'have_raster_downloaded?' values in self.polygons_df when we delete a polygon vertex - more generally, treat the rows of polygons as "glued" to the polygon vertices, and similarly for the rasters - there should be no public (i.e. non dunder) methods which break the abstraction barrier - by creating an associator that - - how to make precise what I mean? whenever we drop or add a polygon or raster or - change the polygon geometries appropriate connections (edges in the graph) should be added as well. - Or: the following invariants should be maintained by all methods: - - rows in self.polygons_df correspond to (bijectively) polygon vertices, all polygons are connected - (i.e. the polygon vertices are) to all rasters (i.e. raster vertices) they should be connected to, - and if there exists a raster fully containing a polygon that should be reflected in that - polygon's entry in the "have_raster?" column (what about have_raster_downloaded?) - - rows in self.rasters correspond (bijectively) to raster vertices, and all rasters are connected to all - polygons they should be connected to. - Another way of thinking aout this: One should never be able to manipulate the graph from outside the associator. One can only add or delete rasters, and the connections should be taken care of automatically. -TODO: index names of associator dataframes can be set (is this still true?). this should be reflected in the documentation -TODO: be consistent with capitalization, punctuation (full stops after descriptions?) diff --git a/geographer/label_makers/label_maker_base.py b/geographer/label_makers/label_maker_base.py index 24885600..b3ae5f70 100644 --- a/geographer/label_makers/label_maker_base.py +++ b/geographer/label_makers/label_maker_base.py @@ -8,7 +8,7 @@ import logging from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from pydantic import BaseModel @@ -34,7 +34,7 @@ class LabelMaker(ABC, BaseModel, SaveAndLoadBaseModelMixIn): def make_labels( self, connector: Connector, - raster_names: Optional[list[str]] = None, + raster_names: list[str] | None = None, ): """Create segmentation labels. @@ -47,7 +47,7 @@ def make_labels( def delete_labels( self, connector: Connector, - raster_names: Optional[list[str]] = None, + raster_names: list[str] | None = None, ): """Delete (pixel) labels from the connector's labels_dir. @@ -59,7 +59,7 @@ def delete_labels( def recompute_labels( self, connector: Connector, - raster_names: Optional[list[str]] = None, + raster_names: list[str] | None = None, ): """Recompute labels. diff --git a/geographer/label_makers/seg_label_maker_base.py b/geographer/label_makers/seg_label_maker_base.py index add9bf5e..56571d3e 100644 --- a/geographer/label_makers/seg_label_maker_base.py +++ b/geographer/label_makers/seg_label_maker_base.py @@ -4,7 +4,6 @@ import logging from abc import abstractmethod -from typing import Optional from pydantic import BaseModel, Field from tqdm.auto import tqdm @@ -56,7 +55,7 @@ def _run_safety_checks(self, connector: Connector): def make_labels( self, connector: Connector, - raster_names: Optional[list[str]] = None, + raster_names: list[str] | None = None, ): """Create segmentation labels. @@ -101,7 +100,7 @@ def make_labels( def delete_labels( self, connector: Connector, - raster_names: Optional[list[str]] = None, + raster_names: list[str] | None = None, ): """Delete (pixel) labels from the connector's labels_dir. @@ -136,7 +135,6 @@ def _compare_existing_rasters_to_rasters(connector: Connector): # ... then if the set of rasters is a strict subset # of the rasters in rasters ... if existing_rasters < set(connector.rasters.index): - # ... log a warning log.warning( "There are rasters in connector.rasters that " @@ -146,7 +144,6 @@ def _compare_existing_rasters_to_rasters(connector: Connector): # ... and if it is not a subset, ... if not existing_rasters <= set(connector.rasters.index): - # ... log an warning message = ( "Warning! There are rasters in the dataset's rasters " diff --git a/geographer/label_makers/seg_label_maker_categorical.py b/geographer/label_makers/seg_label_maker_categorical.py index c9b87b54..0e6fd277 100644 --- a/geographer/label_makers/seg_label_maker_categorical.py +++ b/geographer/label_makers/seg_label_maker_categorical.py @@ -1,7 +1,5 @@ """Label maker for categorical segmentation labels.""" -from __future__ import annotations - import logging import numpy as np @@ -48,7 +46,6 @@ def _make_label_for_raster(self, connector: Connector, raster_name: str): # If the raster does not exist ... if not raster_path.is_file(): - # ... log error to file. log.error( "SegLabelMakerCategorical: input raster %s does not exist!", raster_path @@ -56,16 +53,13 @@ def _make_label_for_raster(self, connector: Connector, raster_name: str): # Else, if the label already exists ... elif label_path.is_file(): - # ... log error to file. log.error("SegLabelMakerCategorical: label %s already exists!", label_path) # Else, ... else: - # ...open the raster, ... with rio.open(raster_path) as src: - profile = src.profile profile.update({"count": 1, "dtype": rio.uint8}) @@ -78,7 +72,6 @@ def _make_label_for_raster(self, connector: Connector, raster_name: str): # nbits=1, **profile, ) as dst: - # ... create an empty band of zeros (background class) ... label = np.zeros((src.height, src.width), dtype=np.uint8) @@ -86,7 +79,6 @@ def _make_label_for_raster(self, connector: Connector, raster_name: str): shapes = [] # pairs of geometries and values to burn in for count, seg_class in enumerate(segmentation_classes, start=1): - # To do that, first find (the df of) the geometries # intersecting the raster ... vectors_intersecting_raster: GeoDataFrame = ( diff --git a/geographer/label_makers/seg_label_maker_soft_categorical.py b/geographer/label_makers/seg_label_maker_soft_categorical.py index d39398d4..6a13bdfa 100644 --- a/geographer/label_makers/seg_label_maker_soft_categorical.py +++ b/geographer/label_makers/seg_label_maker_soft_categorical.py @@ -54,7 +54,6 @@ def _make_label_for_raster( # If the raster does not exist ... if not raster_path.is_file(): - # ... log error to file. log.error( "_make_geotif_label_soft_categorical: input raster %s does not exist!", @@ -63,7 +62,6 @@ def _make_label_for_raster( # Else, if the label already exists ... elif label_path.is_file(): - # ... log error to file. log.error( "_make_geotif_label_soft_categorical: label %s already exists!", @@ -72,19 +70,16 @@ def _make_label_for_raster( # Else, ... else: - label_bands_count = self._get_label_bands_count(connector) # ...open the raster, ... with rio.open(raster_path) as src: - # Create profile for the label. profile = src.profile profile.update({"count": label_bands_count, "dtype": rio.float32}) # Open the label ... with rio.open(label_path, "w+", **profile) as dst: - # ... and create one band in the label for each segmentation class. # (if an implicit background band is to be included, @@ -94,7 +89,6 @@ def _make_label_for_raster( for count, seg_class in enumerate( connector.task_vector_classes, start=start_band ): - # To do that, first find (the df of) # the geoms intersecting the raster ... vectors_intersecting_raster_df = connector.vectors.loc[ @@ -149,7 +143,6 @@ def _make_label_for_raster( # If the background is not included in the segmentation classes ... if self.add_background_band: - # ... add background band. non_background_band_indices = list( @@ -172,16 +165,13 @@ def _make_label_for_raster( dst.write(background_band, 1) def _get_label_bands_count(self, connector: Connector) -> bool: - # If the background is not included in the segmentation classes (default) ... if self.add_background_band: - # ... add a band for the implicit background segmentation class, ... label_bands_count = 1 + len(connector.task_vector_classes) # ... if the background *is* included, ... elif not self.add_background_band: - # ... don't. label_bands_count = len(connector.task_vector_classes) diff --git a/geographer/raster_bands_getter_mixin.py b/geographer/raster_bands_getter_mixin.py index c00a15df..9956c138 100644 --- a/geographer/raster_bands_getter_mixin.py +++ b/geographer/raster_bands_getter_mixin.py @@ -3,7 +3,6 @@ from __future__ import annotations from pathlib import Path -from typing import Optional import rasterio as rio @@ -13,7 +12,7 @@ class RasterBandsGetterMixIn: def _get_bands_for_raster( self, - bands: Optional[dict[str, Optional[list[int]]]], + bands: dict[str, list[int] | None] | None, source_raster_path: Path, ) -> list[int]: """Return bands indices to be used in the target raster. diff --git a/geographer/testing/graph_df_compatibility.py b/geographer/testing/graph_df_compatibility.py index da7aa31f..48c9b2d8 100644 --- a/geographer/testing/graph_df_compatibility.py +++ b/geographer/testing/graph_df_compatibility.py @@ -54,11 +54,9 @@ def check_graph_vertices_counts(connector: Connector): answer = True for set_description, set_difference in set_descriptions_and_differences: - num_elements_in_difference = len(set_difference) if num_elements_in_difference != 0: - answer = False are_or_is = "are" if num_elements_in_difference > 1 else "is" @@ -83,7 +81,6 @@ def check_graph_vertices_counts(connector: Connector): ) if not counts_correct.all(): - return_df = pd.concat( [connector.vectors[connector.raster_count_col_name], raster_count_edges], axis=1, diff --git a/geographer/testing/mock_download.py b/geographer/testing/mock_download.py index 2f40d83f..ad548328 100644 --- a/geographer/testing/mock_download.py +++ b/geographer/testing/mock_download.py @@ -7,9 +7,9 @@ import random from pathlib import Path -from typing import Any, Dict, Literal, Union +from typing import Any, Literal -from pydantic import Field +from pydantic import ConfigDict, Field from shapely.geometry import Polygon from geographer.connector import Connector @@ -31,12 +31,9 @@ class MockDownloadProcessor(RasterDownloadProcessor): downloaded. """ - source_connector: Connector = Field(exclude=True) - - class Config: - """BaseModel Config.""" + model_config = ConfigDict(arbitrary_types_allowed=True) - arbitrary_types_allowed = True + source_connector: Connector = Field(exclude=True) def process( self, @@ -45,8 +42,8 @@ def process( rasters_dir: Path, return_bounds_in_crs_epsg_code: int, **kwargs: Any, - ) -> Dict[ - Union[Literal["raster_name", "geometry", "orig_crs_epsg_code"], str], Any + ) -> dict[ + Literal["raster_name", "geometry", "orig_crs_epsg_code"] | str, Any ]: """Process "downloaded" file, i.e. does nothing. @@ -70,23 +67,20 @@ class MockDownloaderForSingleVector(RasterDownloaderForSingleVector): source directory. No actual raster data is copied. """ + model_config = ConfigDict(arbitrary_types_allowed=True) + source_connector: Connector = Field(exclude=True) probability_of_download_error: float = 0.1 probability_raster_already_downloaded: float = 0.1 - class Config: - """BaseModel Config.""" - - arbitrary_types_allowed = True - def download( self, - vector_name: Union[int, str], + vector_name: int | str, vector_geom: Polygon, download_dir: Path, - previously_downloaded_rasters_set: set[Union[str, int]], + previously_downloaded_rasters_set: set[str | int], **kwargs, - ) -> dict[Union[Literal["raster_name", "raster_processed?"], str], Any]: + ) -> dict[Literal["raster_name", "raster_processed?"] | str, Any]: """Mock download an raster. Mock download an raster fully containing a vector feature or @@ -131,7 +125,6 @@ def download( # If there isn't such an raster ... if rasters_containing_vector == []: - # ... inform the calling download_missing_rasters_for_vectors # by raising an error. raise NoRastersForVectorFoundError( @@ -142,14 +135,12 @@ def download( # Else, there is an raster in the source dataset # containing the vector feature. else: - # With some probability the API answers our query with # an raster that has already been downloaded... if ( rasters_containing_vector and random.random() < self.probability_raster_already_downloaded ): - # ... in which case we raise an error. raise RasterAlreadyExistsError( "random.random() was less than " @@ -165,13 +156,11 @@ def download( ] if remaining_rasters: - # ... choose one to 'download'. raster_name = random.choice(remaining_rasters) # With some probabibility ... if random.random() < self.probability_of_download_error: - # ... an error occurs when downloading, # so we raise an RasterDownloadError. raise RasterDownloadError( @@ -191,7 +180,6 @@ def download( } else: - raise NoRastersForVectorFoundError( "No new rasters containing vector feature " f"{vector_name} found in source dataset" diff --git a/geographer/utils/__init__.py b/geographer/utils/__init__.py index 44c46e67..cf3a72a3 100644 --- a/geographer/utils/__init__.py +++ b/geographer/utils/__init__.py @@ -1,8 +1,9 @@ """Utils for handling remote sensing datasets. -convert_connector_dataset_tif2npy converts a dataset of GeoTiffs to -.npys. rasters_from_tif_dir generates an rasters GeoDataFrame from a -directory of GeoTiffs. +- convert_connector_dataset_tif2npy converts a dataset of GeoTiffs to + .npys. +- rasters_from_tif_dir generates an rasters GeoDataFrame from a + directory of GeoTiffs. """ from geographer.utils.rasters_from_tif_dir import ( diff --git a/geographer/utils/cluster_rasters.py b/geographer/utils/cluster_rasters.py index 3f543d06..d7ff085d 100644 --- a/geographer/utils/cluster_rasters.py +++ b/geographer/utils/cluster_rasters.py @@ -9,7 +9,7 @@ import itertools from pathlib import Path -from typing import Any, Literal, Optional, Tuple, Union +from typing import Any, Literal, Tuple import networkx as nx import pandas as pd @@ -21,15 +21,13 @@ def get_raster_clusters( - connector: Union[Connector, Path, str], + connector: Connector | Path | str, clusters_defined_by: Literal[ "rasters_that_share_vectors", "rasters_that_share_vectors_or_overlap", ], - raster_names: Optional[list[str]] = None, - preclustering_method: Optional[ - Literal["x then y-axis", "y then x-axis", "x-axis", "y-axis"] - ] = "y then x-axis", # TODO!!!!!!!!!! + raster_names: list[str] | None = None, + preclustering_method: Literal["x then y-axis", "y then x-axis", "x-axis", "y-axis"] | None = "y then x-axis", # TODO!!!!!!!!!! ) -> list[set[str]]: """Return clusters of raster. @@ -37,7 +35,7 @@ def get_raster_clusters( connector: connector or path or str to data dir containing connector clusters_defined_by: relation between rasters defining clusters raster_names: optional list of raster names - preclustering_method (Optional[ str]): optional preclustering method to speed + preclustering_method: optional preclustering method to speed up clustering Returns: @@ -56,12 +54,10 @@ def get_raster_clusters( raster_names = connector.rasters.index.tolist() if preclustering_method is None: - preclusters = [set(raster_names)] singletons, non_singletons = [], preclusters elif preclustering_method in {"x-axis", "y-axis"}: - axis = preclustering_method[0] # 'x' or 'y' geoms = _get_preclustering_geoms(connector=connector, raster_names=raster_names) @@ -69,7 +65,6 @@ def get_raster_clusters( singletons, non_singletons = _separate_non_singletons(preclusters) elif preclustering_method in {"x then y-axis", "y then x-axis"}: - first_axis = preclustering_method[0] second_axis = "y" if first_axis == "x" else "x" @@ -115,13 +110,10 @@ def _refine_preclustering_along_second_axis( singletons, preclusters_along_2nd_axis = [], [] for precluster in preclusters: - if len(precluster) == 1: - singletons.append(precluster) else: - precluster_geoms = _get_preclustering_geoms( connector=connector, raster_names=list(precluster) ) @@ -140,7 +132,6 @@ def _refine_preclustering_along_second_axis( def _get_preclustering_geoms( connector: Connector, raster_names: list[str] ) -> GeoDataFrame: - # raster geoms rasters = deepcopy_gdf(connector.rasters[["geometry"]].loc[raster_names]) rasters["name"] = rasters.index @@ -172,7 +163,9 @@ def _get_preclustering_geoms( assert set(vectors["name"]) & set(rasters["name"]) == set() # combine geoms - geoms = GeoDataFrame(pd.concat([rasters, vectors]), crs=rasters.crs) + geoms = GeoDataFrame( + pd.concat([rasters, vectors]), crs=rasters.crs, geometry="geometry" + ) # don't need ? geoms = deepcopy_gdf(geoms) @@ -185,6 +178,7 @@ def _get_preclustering_geoms( geoms = GeoDataFrame( pd.concat([geoms, geoms.geometry.bounds], axis=1), # column axis crs=geoms.crs, + geometry="geometry", ) return geoms @@ -193,7 +187,6 @@ def _get_preclustering_geoms( def _separate_non_singletons( preclusters: list[set[Any]], ) -> tuple[list[set[Any]], list[set[Any]]]: - singletons, non_singletions = [], [] for precluster in preclusters: if len(precluster) == 1: @@ -208,7 +201,6 @@ def _separate_non_singletons( def _pre_cluster_along_axis( geoms: GeoDataFrame, axis: Literal["x", "y"] ) -> list[set[str]]: - if axis not in {"x", "y"}: raise ValueError("axis arg should be one of 'x', 'y'.") @@ -231,7 +223,6 @@ def _pre_cluster_along_axis( raster_clusters_along_axis = [] while interval_endpoints != []: - rightmost_endpoint = interval_endpoints.pop() assert rightmost_endpoint["type"] == "max" @@ -295,11 +286,9 @@ def _are_connected_by_an_edge( other_raster_bbox = connector.rasters.loc[another_raster].geometry if clusters_defined_by == "rasters_that_overlap": - connected = raster_bbox.intersects(other_raster_bbox) elif clusters_defined_by == "rasters_that_share_vectors": - vectors_in_raster = set(connector.vectors_intersecting_raster(raster)) vectors_in_other_raster = set( connector.vectors_intersecting_raster(another_raster) @@ -308,7 +297,6 @@ def _are_connected_by_an_edge( connected = vectors_in_raster & vectors_in_other_raster != set() elif clusters_defined_by == "rasters_that_share_vectors_or_overlap": - connected_bc_rasters_overlap = _are_connected_by_an_edge( raster, another_raster, "rasters_that_overlap", connector ) @@ -319,7 +307,6 @@ def _are_connected_by_an_edge( connected = connected_bc_rasters_overlap or connected_bc_of_shared_polygons else: - raise ValueError(f"Unknown clusters_defined_by arg: {clusters_defined_by}") return connected diff --git a/geographer/utils/connector_utils.py b/geographer/utils/connector_utils.py index a3616a74..d3f95589 100644 --- a/geographer/utils/connector_utils.py +++ b/geographer/utils/connector_utils.py @@ -1,7 +1,5 @@ """Utilites used in the Connector class.""" -from __future__ import annotations - import logging import pandas as pd @@ -46,7 +44,9 @@ def empty_gdf( }, } - new_empty_gdf = GeoDataFrame(new_empty_gdf_dict, crs=f"EPSG:{crs_epsg_code}") + new_empty_gdf = GeoDataFrame( + new_empty_gdf_dict, crs=f"EPSG:{crs_epsg_code}", geometry="geometry" + ) new_empty_gdf.set_index(index_name, inplace=True) return new_empty_gdf @@ -123,7 +123,6 @@ def _check_df_cols_agree( ): """Log if column names don't agree.""" if set(df.columns) != set(self_df.columns) and len(self_df) > 0: - df1_cols_not_in_df2 = set(df.columns) - set(self_df.columns) df2_cols_not_in_df1 = set(self_df.columns) - set(df.columns) diff --git a/geographer/utils/merge_datasets.py b/geographer/utils/merge_datasets.py index 2355848b..a445c807 100644 --- a/geographer/utils/merge_datasets.py +++ b/geographer/utils/merge_datasets.py @@ -1,9 +1,10 @@ """Utility functions for merging datasets.""" +from __future__ import annotations + import os import shutil from pathlib import Path -from typing import Union from tqdm.auto import tqdm @@ -11,8 +12,8 @@ def merge_datasets( - source_data_dir: Union[Path, str], - target_data_dir: Union[Path, str], + source_data_dir: Path | str, + target_data_dir: Path | str, delete_source: bool = True, ) -> None: """Merge datasets. @@ -46,7 +47,7 @@ def merge_datasets( # TODO rewrite using pathlib -def merge_dirs(root_src_dir: Union[Path, str], root_dst_dir: Union[Path, str]) -> None: +def merge_dirs(root_src_dir: Path | str, root_dst_dir: Path | str) -> None: """Recursively merge two folders including subfolders. (Shamelessly copied from stackoverflow) diff --git a/geographer/utils/rasters_from_tif_dir.py b/geographer/utils/rasters_from_tif_dir.py index aea8763a..8400ec1c 100644 --- a/geographer/utils/rasters_from_tif_dir.py +++ b/geographer/utils/rasters_from_tif_dir.py @@ -4,7 +4,7 @@ import pathlib from pathlib import Path -from typing import Callable, Optional, Union +from typing import Callable import rasterio as rio from geopandas import GeoDataFrame @@ -19,8 +19,6 @@ def default_read_in_raster_for_raster_df_function( ) -> tuple[int, Polygon]: """Read in crs and bbox defining a GeoTIFF raster. - ..note:: - Args: raster_path: location of the raster @@ -28,10 +26,8 @@ def default_read_in_raster_for_raster_df_function( tuple: crs code of the raster, bounding rectangle of the raster """ if raster_path.suffix in [".tif", ".tiff"]: - # ... open them in rasterio ... with rio.open(raster_path, "r") as src: - # ... extract information ... orig_crs_epsg_code = src.crs.to_epsg() @@ -45,9 +41,9 @@ def default_read_in_raster_for_raster_df_function( def rasters_from_rasters_dir( - rasters_dir: Union[pathlib.Path, str], - rasters_crs_epsg_code: Optional[int] = None, - raster_names: Optional[list[str]] = None, + rasters_dir: pathlib.Path | str, + rasters_crs_epsg_code: int | None = None, + raster_names: list[str] | None = None, rasters_datatype: str = "tif", read_in_raster_for_raster_df_function: Callable[ [Path], tuple[int, Polygon] @@ -91,14 +87,13 @@ def rasters_from_rasters_dir( # dict to keep track of information about the rasters that # we will make the rasters from. - new_rasters_dict: dict[str, Union[str, GEOMS_UNION, int]] = { + new_rasters_dict: dict[str, str | GEOMS_UNION | int] = { index_or_col_name: [] for index_or_col_name in {"raster_name", "geometry", "orig_crs_epsg_code"} } # for all rasters in dir ... for raster_path in tqdm(raster_paths, desc="building rasters"): - ( orig_crs_epsg_code, raster_bounding_rectangle_orig_crs, @@ -125,7 +120,7 @@ def rasters_from_rasters_dir( new_rasters_dict[key].append(raster_info_dict[key]) # ... and create a rasters GeoDatFrame from new_rasters_dict: - new_rasters = GeoDataFrame(new_rasters_dict) + new_rasters = GeoDataFrame(new_rasters_dict, geometry="geometry") new_rasters.set_crs(epsg=rasters_crs_epsg_code, inplace=True) new_rasters.set_index("raster_name", inplace=True) diff --git a/geographer/utils/utils.py b/geographer/utils/utils.py index a3c7f321..f02a1b8e 100644 --- a/geographer/utils/utils.py +++ b/geographer/utils/utils.py @@ -66,7 +66,9 @@ def create_logger(app_name: str, level: int = logging.INFO) -> logging.Logger: WARNING. One needs to additionally set the console handler level to the desired level, which is done by this function. - ..note:: Function might be adapted for more specialized usage in the future + .. note:: + + Function might be adapted for more specialized usage in the future Args: app_name: Name of the logger. Will appear in the console output @@ -133,7 +135,7 @@ def transform_shapely_geometry( return transformed_geometry -def round_shapely_geometry(geometry: GEOMS_UNION, ndigits=1) -> Union[Polygon, Point]: +def round_shapely_geometry(geometry: GEOMS_UNION, ndigits=1) -> Polygon| Point: """Round the coordinates of a shapely geometry. Round the coordinates of a shapely geometry (e.g. Polygon or Point). @@ -153,7 +155,10 @@ def round_shapely_geometry(geometry: GEOMS_UNION, ndigits=1) -> Union[Polygon, P def deepcopy_gdf(gdf: GeoDataFrame) -> GeoDataFrame: """Return deepcopy of GeoDataFrame.""" gdf_copy = GeoDataFrame( - columns=gdf.columns, data=copy.deepcopy(gdf.values), crs=gdf.crs + columns=gdf.columns, + data=copy.deepcopy(gdf.values), + crs=gdf.crs, + geometry=gdf.geometry.name, ) gdf_copy = gdf_copy.astype(gdf.dtypes) gdf_copy.set_index(gdf.index, inplace=True) @@ -169,12 +174,19 @@ def concat_gdfs(objs: list[GeoDataFrame], **kwargs: Any) -> GeoDataFrame: list. """ for obj in objs: - if isinstance(obj, GeoDataFrame) and obj.crs != objs[0].crs: - raise ValueError("all geodataframes should have the same crs") + if isinstance(obj, GeoDataFrame): + if obj.crs != objs[0].crs: + raise ValueError("All geodataframes should have the same CRS") + if obj.geometry.name != objs[0].geometry.name: + raise ValueError( + "All geodataframes should have the same geometry column!" + ) elif not isinstance(obj, GeoDataFrame): raise ValueError("all objs should be GeoDataFrames") - concatenated_gdf = GeoDataFrame(pd.concat(objs, **kwargs), crs=objs[0].crs) + concatenated_gdf = GeoDataFrame( + pd.concat(objs, **kwargs), crs=objs[0].crs, geometry=objs[0].geometry.name + ) concatenated_gdf.index.name = objs[0].index.name return concatenated_gdf @@ -186,7 +198,7 @@ def map_dict_values(fun: Callable, dict_arg: dict) -> dict: def create_kml_all_geodataframes( - data_dir: Union[Path, str], out_path: Union[Path, str] + data_dir: Path | str, out_path: Path | str ) -> None: """Create KML file from a dataset's rasters and vectors. @@ -200,12 +212,8 @@ def create_kml_all_geodataframes( rasters_path = data_dir / "connector/rasters.geojson" vectors_path = data_dir / "connector/vectors.geojson" - rasters = gpd.read_file(rasters_path, driver="GeoJSON")[ - ["geometry", RASTER_IMGS_INDEX_NAME] - ] - vectors = gpd.read_file(vectors_path, driver="GeoJSON")[ - ["geometry", VECTOR_FEATURES_INDEX_NAME] - ] + rasters = gpd.read_file(rasters_path)[["geometry", RASTER_IMGS_INDEX_NAME]] + vectors = gpd.read_file(vectors_path)[["geometry", VECTOR_FEATURES_INDEX_NAME]] rasters["Description"] = "raster" rasters["Name"] = rasters[RASTER_IMGS_INDEX_NAME] diff --git a/notebooks/blogpost.ipynb b/notebooks/blogpost.ipynb index 4d75a59f..7e1aee5e 100644 --- a/notebooks/blogpost.ipynb +++ b/notebooks/blogpost.ipynb @@ -14,21 +14,24 @@ "First, we import geographer, as well as some other imports we will need." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# !pip install geographer matplotlib" + ] + }, { "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/rustam/dida/GeoGrapher/geographer-env/lib/python3.10/site-packages/tqdm/auto.py:22: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - } - ], + "outputs": [], "source": [ + "from datetime import date, timedelta\n", + "import os\n", + "\n", "import geographer as gg\n", "import geopandas as gpd\n", "from pathlib import Path" @@ -43,7 +46,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -70,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -110,55 +113,55 @@ " Munich Olympiastadion\n", " Munich, Germany\n", " stadium\n", - " POLYGON Z ((11.54677 48.17472 0.00000, 11.5446...\n", + " POLYGON Z ((11.54677 48.17472 0, 11.54469 48.1...\n", " \n", " \n", " Munich Track and Field Stadium1\n", " Munich, Germany\n", " stadium\n", - " POLYGON Z ((11.54382 48.17279 0.00000, 11.5438...\n", + " POLYGON Z ((11.54382 48.17279 0, 11.5438 48.17...\n", " \n", " \n", " Munich Olympia Track and Field2\n", " Munich, Germany\n", " stadium\n", - " POLYGON Z ((11.54686 48.17892 0.00000, 11.5468...\n", + " POLYGON Z ((11.54686 48.17892 0, 11.54685 48.1...\n", " \n", " \n", " Munich Staedtisches Stadion Dantestr\n", " Munich, Germany\n", " stadium\n", - " POLYGON Z ((11.52913 48.16874 0.00000, 11.5291...\n", + " POLYGON Z ((11.52913 48.16874 0, 11.5291 48.16...\n", " \n", " \n", " Vasil Levski National Stadium\n", " Sofia, Bulgaria\n", " stadium\n", - " POLYGON Z ((23.33410 42.68813 0.00000, 23.3340...\n", + " POLYGON Z ((23.3341 42.68813 0, 23.33408 42.68...\n", " \n", " \n", " Bulgarian Army Stadium\n", " Sofia, Bulgaria\n", " stadium\n", - " POLYGON Z ((23.34065 42.68492 0.00000, 23.3406...\n", + " POLYGON Z ((23.34065 42.68492 0, 23.34062 42.6...\n", " \n", " \n", " Arena Sofia\n", " Sofia, Bulgaria\n", " stadium\n", - " POLYGON Z ((23.34018 42.68318 0.00000, 23.3401...\n", + " POLYGON Z ((23.34018 42.68318 0, 23.34018 42.6...\n", " \n", " \n", " Jingu Baseball Stadium\n", " Tokyo, Japan\n", " stadium\n", - " POLYGON Z ((139.71597 35.67490 0.00000, 139.71...\n", + " POLYGON Z ((139.71597 35.6749 0, 139.71599 35....\n", " \n", " \n", " Japan National Stadium\n", " Tokyo, Japan\n", " stadium\n", - " POLYGON Z ((139.71482 35.67644 0.00000, 139.71...\n", + " POLYGON Z ((139.71482 35.67644 0, 139.71484 35...\n", " \n", " \n", "\n", @@ -179,18 +182,18 @@ "\n", " geometry \n", "vector_name \n", - "Munich Olympiastadion POLYGON Z ((11.54677 48.17472 0.00000, 11.5446... \n", - "Munich Track and Field Stadium1 POLYGON Z ((11.54382 48.17279 0.00000, 11.5438... \n", - "Munich Olympia Track and Field2 POLYGON Z ((11.54686 48.17892 0.00000, 11.5468... \n", - "Munich Staedtisches Stadion Dantestr POLYGON Z ((11.52913 48.16874 0.00000, 11.5291... \n", - "Vasil Levski National Stadium POLYGON Z ((23.33410 42.68813 0.00000, 23.3340... \n", - "Bulgarian Army Stadium POLYGON Z ((23.34065 42.68492 0.00000, 23.3406... \n", - "Arena Sofia POLYGON Z ((23.34018 42.68318 0.00000, 23.3401... \n", - "Jingu Baseball Stadium POLYGON Z ((139.71597 35.67490 0.00000, 139.71... \n", - "Japan National Stadium POLYGON Z ((139.71482 35.67644 0.00000, 139.71... " + "Munich Olympiastadion POLYGON Z ((11.54677 48.17472 0, 11.54469 48.1... \n", + "Munich Track and Field Stadium1 POLYGON Z ((11.54382 48.17279 0, 11.5438 48.17... \n", + "Munich Olympia Track and Field2 POLYGON Z ((11.54686 48.17892 0, 11.54685 48.1... \n", + "Munich Staedtisches Stadion Dantestr POLYGON Z ((11.52913 48.16874 0, 11.5291 48.16... \n", + "Vasil Levski National Stadium POLYGON Z ((23.3341 42.68813 0, 23.33408 42.68... \n", + "Bulgarian Army Stadium POLYGON Z ((23.34065 42.68492 0, 23.34062 42.6... \n", + "Arena Sofia POLYGON Z ((23.34018 42.68318 0, 23.34018 42.6... \n", + "Jingu Baseball Stadium POLYGON Z ((139.71597 35.6749 0, 139.71599 35.... \n", + "Japan National Stadium POLYGON Z ((139.71482 35.67644 0, 139.71484 35... " ] }, - "execution_count": 4, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -210,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -226,7 +229,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -266,63 +269,63 @@ " \n", " \n", " Munich Olympiastadion\n", - " POLYGON Z ((11.54677 48.17472 0.00000, 11.5446...\n", + " POLYGON Z ((11.54677 48.17472 0, 11.54469 48.1...\n", " 0\n", " Munich, Germany\n", " stadium\n", " \n", " \n", " Munich Track and Field Stadium1\n", - " POLYGON Z ((11.54382 48.17279 0.00000, 11.5438...\n", + " POLYGON Z ((11.54382 48.17279 0, 11.5438 48.17...\n", " 0\n", " Munich, Germany\n", " stadium\n", " \n", " \n", " Munich Olympia Track and Field2\n", - " POLYGON Z ((11.54686 48.17892 0.00000, 11.5468...\n", + " POLYGON Z ((11.54686 48.17892 0, 11.54685 48.1...\n", " 0\n", " Munich, Germany\n", " stadium\n", " \n", " \n", " Munich Staedtisches Stadion Dantestr\n", - " POLYGON Z ((11.52913 48.16874 0.00000, 11.5291...\n", + " POLYGON Z ((11.52913 48.16874 0, 11.5291 48.16...\n", " 0\n", " Munich, Germany\n", " stadium\n", " \n", " \n", " Vasil Levski National Stadium\n", - " POLYGON Z ((23.33410 42.68813 0.00000, 23.3340...\n", + " POLYGON Z ((23.3341 42.68813 0, 23.33408 42.68...\n", " 0\n", " Sofia, Bulgaria\n", " stadium\n", " \n", " \n", " Bulgarian Army Stadium\n", - " POLYGON Z ((23.34065 42.68492 0.00000, 23.3406...\n", + " POLYGON Z ((23.34065 42.68492 0, 23.34062 42.6...\n", " 0\n", " Sofia, Bulgaria\n", " stadium\n", " \n", " \n", " Arena Sofia\n", - " POLYGON Z ((23.34018 42.68318 0.00000, 23.3401...\n", + " POLYGON Z ((23.34018 42.68318 0, 23.34018 42.6...\n", " 0\n", " Sofia, Bulgaria\n", " stadium\n", " \n", " \n", " Jingu Baseball Stadium\n", - " POLYGON Z ((139.71597 35.67490 0.00000, 139.71...\n", + " POLYGON Z ((139.71597 35.6749 0, 139.71599 35....\n", " 0\n", " Tokyo, Japan\n", " stadium\n", " \n", " \n", " Japan National Stadium\n", - " POLYGON Z ((139.71482 35.67644 0.00000, 139.71...\n", + " POLYGON Z ((139.71482 35.67644 0, 139.71484 35...\n", " 0\n", " Tokyo, Japan\n", " stadium\n", @@ -334,15 +337,15 @@ "text/plain": [ " geometry \\\n", "vector_name \n", - "Munich Olympiastadion POLYGON Z ((11.54677 48.17472 0.00000, 11.5446... \n", - "Munich Track and Field Stadium1 POLYGON Z ((11.54382 48.17279 0.00000, 11.5438... \n", - "Munich Olympia Track and Field2 POLYGON Z ((11.54686 48.17892 0.00000, 11.5468... \n", - "Munich Staedtisches Stadion Dantestr POLYGON Z ((11.52913 48.16874 0.00000, 11.5291... \n", - "Vasil Levski National Stadium POLYGON Z ((23.33410 42.68813 0.00000, 23.3340... \n", - "Bulgarian Army Stadium POLYGON Z ((23.34065 42.68492 0.00000, 23.3406... \n", - "Arena Sofia POLYGON Z ((23.34018 42.68318 0.00000, 23.3401... \n", - "Jingu Baseball Stadium POLYGON Z ((139.71597 35.67490 0.00000, 139.71... \n", - "Japan National Stadium POLYGON Z ((139.71482 35.67644 0.00000, 139.71... \n", + "Munich Olympiastadion POLYGON Z ((11.54677 48.17472 0, 11.54469 48.1... \n", + "Munich Track and Field Stadium1 POLYGON Z ((11.54382 48.17279 0, 11.5438 48.17... \n", + "Munich Olympia Track and Field2 POLYGON Z ((11.54686 48.17892 0, 11.54685 48.1... \n", + "Munich Staedtisches Stadion Dantestr POLYGON Z ((11.52913 48.16874 0, 11.5291 48.16... \n", + "Vasil Levski National Stadium POLYGON Z ((23.3341 42.68813 0, 23.33408 42.68... \n", + "Bulgarian Army Stadium POLYGON Z ((23.34065 42.68492 0, 23.34062 42.6... \n", + "Arena Sofia POLYGON Z ((23.34018 42.68318 0, 23.34018 42.6... \n", + "Jingu Baseball Stadium POLYGON Z ((139.71597 35.6749 0, 139.71599 35.... \n", + "Japan National Stadium POLYGON Z ((139.71482 35.67644 0, 139.71484 35... \n", "\n", " raster_count location type \n", "vector_name \n", @@ -357,7 +360,7 @@ "Japan National Stadium 0 Tokyo, Japan stadium " ] }, - "execution_count": 6, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -372,7 +375,17 @@ "source": [ "# 3. Downloading Rasters for the Vector Data\n", "\n", - "To download rasters for the stadiums, we define a downloader:" + "To download rasters for the stadiums, we will use a downloader based on [eodag](https://eodag.readthedocs.io/en/stable/index.html). We will download rasters from the [copernicus dataspace](https://dataspace.copernicus.eu/). If you do not yet have a copernicus dataspace account, you can create one [here](https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/auth?client_id=cdse-public&redirect_uri=https%3A%2F%2Fdataspace.copernicus.eu%2Fbrowser%2F&response_type=code&scope=openid). To use eodag, eodag will need the username and password of your copernicus dataspace account. One can set these in a config file, but here we will use environment variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# os.environ[\"EODAG__COP_DATASPACE__AUTH__CREDENTIALS__USERNAME\"] = \"PLEASE_CHANGE_ME\"\n", + "# os.environ[\"EODAG__COP_DATASPACE__AUTH__CREDENTIALS__PASSWORD\"] = \"PLEASE_CHANGE_ME\"" ] }, { @@ -383,12 +396,12 @@ "source": [ "from geographer.downloaders import (\n", " RasterDownloaderForVectors,\n", - " SentinelDownloaderForSingleVector,\n", - " Sentinel2Processor,\n", + " EodagDownloaderForSingleVector,\n", + " Sentinel2SAFEProcessor,\n", ")\n", "\n", - "downloader_for_single_vector = SentinelDownloaderForSingleVector()\n", - "download_processor = Sentinel2Processor()\n", + "downloader_for_single_vector = EodagDownloaderForSingleVector()\n", + "download_processor = Sentinel2SAFEProcessor()\n", "\n", "downloader = RasterDownloaderForVectors(\n", " downloader_for_single_vector=downloader_for_single_vector,\n", @@ -400,52 +413,157 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To use the Copernicus SciHub API we need to a username and password. You can sign up for an account [here](https://scihub.copernicus.eu/dhus/#/self-registration). The password and username will be assumed to be stored in a .ini file." + "To download rasters and add them to our dataset we then run the following command. Downloading the large SAFE files and processing the SAFEs to GeoTiffs can take quite a while." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, - "outputs": [], - "source": [ - "credentials_ini_path = Path(\"copernicus_scihub_credentials.ini\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f4f8ae6a2ac54ba1a114d900cf054cea", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "0it [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c9e9281a68b946fca16a467ae22836a9", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "0.00B [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "71391febd6f64f15a15b28d68addb279", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Extracting tif from S2A_MSIL2A_20241108T092201_N0511_R093_T34TFN_20241108T123352.SAFE.: 0%| | 0/21 …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-11-21 22:56:22,769 - geographer.downloaders.sentinel2_safe_unpacking - INFO - Using all zero band for gml mask ('CLOUDS', 'B00') for S2A_MSIL2A_20241108T092201_N0511_R093_T34TFN_20241108T123352.SAFE\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5cdb756986724631a325c59db8288b6c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "0.00B [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "1841eba3e6b34cf599fc94983371bd3a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Extracting tif from S2B_MSIL2A_20240731T100559_N0511_R022_T32UPU_20240731T125141.SAFE.: 0%| | 0/21 …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-11-21 23:01:13,644 - geographer.downloaders.sentinel2_safe_unpacking - INFO - Using all zero band for gml mask ('CLOUDS', 'B00') for S2B_MSIL2A_20240731T100559_N0511_R022_T32UPU_20240731T125141.SAFE\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d0451faca6314052ab509d29b57bcd7c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "0.00B [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c300b0ab0bd44174a0ae9d145f88b3e3", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Extracting tif from S2B_MSIL2A_20231208T013039_N0509_R074_T54SUE_20231208T031743.SAFE.: 0%| | 0/21 …" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-11-21 23:06:47,516 - geographer.downloaders.sentinel2_safe_unpacking - INFO - Using all zero band for gml mask ('CLOUDS', 'B00') for S2B_MSIL2A_20231208T013039_N0509_R074_T54SUE_20231208T031743.SAFE\n" + ] + } + ], "source": [ - "The contents of the ini file should look as follows:\n", + "# Here, we define the parameters needed by the EodagDownloaderForSingleVector.download method\n", + "downloader_params = {\n", + " \"search_kwargs\": { # Keyword arguments for the eodag search_all method\n", + " \"provider\": \"cop_dataspace\", # Download from copernicus dataspace \n", + " \"productType\": \"S2_MSI_L2A\", # Search for Sentinel-2 L2A products\n", + " \"start\": (date.today() - timedelta(days=364)).strftime(\"%Y-%m-%d\"), # one year ago\n", + " \"end\": date.today().strftime(\"%Y-%m-%d\"), # today\n", + " },\n", + " \"filter_online\": True, # Filter out products that are not online\n", + " \"sort_by\": (\"cloudCover\", \"ASC\"), # Sort products by percentage of cloud cover in ascending order\n", + " \"suffix_to_remove\": \".SAFE\" # Will strip .SAFE from the stem of the tif file names\n", + "}\n", + "# Here, we define the parameters needed by the Sentinel2SAFEProcessor\n", + "processor_params = {\n", + " \"resolution\": 10, # Extract all 10m resolution bands\n", + " \"delete_safe\": True, # Delete the SAFE file after extracting a .tif file\n", + "} \n", "\n", - "```\n", - "[login]\n", - "username = your_username\n", - "password = your_password\n", - "```" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To download rasters and add them to our dataset we then run the following command." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ "downloader.download(\n", " connector=connector,\n", - " credentials=credentials_ini_path, # could also directly supply (username, password) tuple\n", - " producttype=\"L2A\",\n", - " max_percent_cloud_coverage=10,\n", - " resolution=10, # resolution of extracted GeoTiff\n", - " date=(\"NOW-364DAYS\", \"NOW\"),\n", - " area_relation=\"Contains\",\n", + " downloader_params=downloader_params,\n", + " processor_params=processor_params,\n", ")" ] }, @@ -458,7 +576,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -482,72 +600,61 @@ " \n", " \n", " \n", + " geometry\n", " raster_processed?\n", - " timestamp\n", " orig_crs_epsg_code\n", - " geometry\n", " \n", " \n", " raster_name\n", " \n", " \n", " \n", - " \n", " \n", " \n", " \n", " \n", - " S2A_MSIL2A_20220627T100611_N0400_R022_T32UPU_20220627T162810.tif\n", + " S2A_MSIL2A_20241108T092201_N0511_R093_T34TFN_20241108T123352.tif\n", + " POLYGON ((23.54663 42.33578, 23.58754 43.32358...\n", " True\n", - " 2022-06-27-10:06:11\n", - " 32632\n", - " POLYGON ((11.79809 47.73104, 11.85244 48.71769...\n", + " 32634\n", " \n", " \n", - " S2A_MSIL2A_20220412T012701_N0400_R074_T54SUE_20220412T042315.tif\n", + " S2B_MSIL2A_20240731T100559_N0511_R022_T32UPU_20240731T125141.tif\n", + " POLYGON ((11.79809 47.73104, 11.85244 48.71769...\n", " True\n", - " 2022-04-12-01:27:01\n", - " 32654\n", - " POLYGON ((140.00972 35.15084, 139.99743 36.140...\n", + " 32632\n", " \n", " \n", - " S2A_MSIL2A_20220722T092041_N0400_R093_T34TFN_20220722T134859.tif\n", + " S2B_MSIL2A_20231208T013039_N0509_R074_T54SUE_20231208T031743.tif\n", + " POLYGON ((140.00972 35.15084, 139.99743 36.140...\n", " True\n", - " 2022-07-22-09:20:41\n", - " 32634\n", - " POLYGON ((23.54663 42.33578, 23.58754 43.32358...\n", + " 32654\n", " \n", " \n", "\n", "" ], "text/plain": [ + " geometry \\\n", + "raster_name \n", + "S2A_MSIL2A_20241108T092201_N0511_R093_T34TFN_20... POLYGON ((23.54663 42.33578, 23.58754 43.32358... \n", + "S2B_MSIL2A_20240731T100559_N0511_R022_T32UPU_20... POLYGON ((11.79809 47.73104, 11.85244 48.71769... \n", + "S2B_MSIL2A_20231208T013039_N0509_R074_T54SUE_20... POLYGON ((140.00972 35.15084, 139.99743 36.140... \n", + "\n", " raster_processed? \\\n", "raster_name \n", - "S2A_MSIL2A_20220627T100611_N0400_R022_T32UPU_20... True \n", - "S2A_MSIL2A_20220412T012701_N0400_R074_T54SUE_20... True \n", - "S2A_MSIL2A_20220722T092041_N0400_R093_T34TFN_20... True \n", + "S2A_MSIL2A_20241108T092201_N0511_R093_T34TFN_20... True \n", + "S2B_MSIL2A_20240731T100559_N0511_R022_T32UPU_20... True \n", + "S2B_MSIL2A_20231208T013039_N0509_R074_T54SUE_20... True \n", "\n", - " timestamp \\\n", - "raster_name \n", - "S2A_MSIL2A_20220627T100611_N0400_R022_T32UPU_20... 2022-06-27-10:06:11 \n", - "S2A_MSIL2A_20220412T012701_N0400_R074_T54SUE_20... 2022-04-12-01:27:01 \n", - "S2A_MSIL2A_20220722T092041_N0400_R093_T34TFN_20... 2022-07-22-09:20:41 \n", - "\n", - " orig_crs_epsg_code \\\n", - "raster_name \n", - "S2A_MSIL2A_20220627T100611_N0400_R022_T32UPU_20... 32632 \n", - "S2A_MSIL2A_20220412T012701_N0400_R074_T54SUE_20... 32654 \n", - "S2A_MSIL2A_20220722T092041_N0400_R093_T34TFN_20... 32634 \n", - "\n", - " geometry \n", - "raster_name \n", - "S2A_MSIL2A_20220627T100611_N0400_R022_T32UPU_20... POLYGON ((11.79809 47.73104, 11.85244 48.71769... \n", - "S2A_MSIL2A_20220412T012701_N0400_R074_T54SUE_20... POLYGON ((140.00972 35.15084, 139.99743 36.140... \n", - "S2A_MSIL2A_20220722T092041_N0400_R093_T34TFN_20... POLYGON ((23.54663 42.33578, 23.58754 43.32358... " + " orig_crs_epsg_code \n", + "raster_name \n", + "S2A_MSIL2A_20241108T092201_N0511_R093_T34TFN_20... 32634 \n", + "S2B_MSIL2A_20240731T100559_N0511_R022_T32UPU_20... 32632 \n", + "S2B_MSIL2A_20231208T013039_N0509_R074_T54SUE_20... 32654 " ] }, - "execution_count": 15, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -565,7 +672,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -573,10 +680,10 @@ "output_type": "stream", "text": [ "rasters containing Munich Olympiastadion:\n", - "['S2A_MSIL2A_20220627T100611_N0400_R022_T32UPU_20220627T162810.tif'] \n", + "['S2B_MSIL2A_20240731T100559_N0511_R022_T32UPU_20240731T125141.tif'] \n", "\n", - "vector features (stadiums) intersecting S2A_MSIL2A_20220627T100611_N0400_R022_T32UPU_20220627T162810.tif:\n", - "['Munich Track and Field Stadium1', 'Munich Olympiastadion', 'Munich Olympia Track and Field2', 'Munich Staedtisches Stadion Dantestr']\n" + "vector features (stadiums) intersecting S2B_MSIL2A_20240731T100559_N0511_R022_T32UPU_20240731T125141.tif:\n", + "['Munich Olympiastadion', 'Munich Staedtisches Stadion Dantestr', 'Munich Track and Field Stadium1', 'Munich Olympia Track and Field2']\n" ] } ], @@ -601,15 +708,22 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "Cutting dataset: 100%|██████████| 9/9 [00:01<00:00, 7.56it/s]\n" - ] + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "685cb0130efd43458007ba344c45453a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Cutting dataset: 0%| | 0/9 [00:00" ] @@ -692,14 +813,19 @@ "import rioxarray\n", "from matplotlib import pyplot as plt\n", "\n", - "raster_paths = target_connector.rasters_containing_vector(\"Munich Olympiastadion\", mode=\"paths\")\n", + "raster_paths = target_connector.rasters_containing_vector(\n", + " \"Munich Olympiastadion\", mode=\"paths\"\n", + ")\n", "raster_path = raster_paths[0]\n", "label_path = target_connector.labels_dir / raster_path.name\n", "\n", - "raster = rioxarray.open_rasterio(raster_path).sel(band=[1,2,3]).values.transpose(1, 2, 0) / 65535\n", + "raster = (\n", + " rioxarray.open_rasterio(raster_path).sel(band=[1, 2, 3]).values.transpose(1, 2, 0)\n", + " / 65535\n", + ")\n", "label = rioxarray.open_rasterio(label_path).values.transpose(1, 2, 0) / 255\n", "\n", - "fig, ax = plt.subplots(1,2, figsize=(13, 13))\n", + "fig, ax = plt.subplots(1, 2, figsize=(13, 13))\n", "\n", "ax[0].imshow(raster)\n", "ax[0].axis(\"off\")\n", @@ -725,7 +851,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" + "version": "3.10.12" }, "orig_nbformat": 4, "vscode": { diff --git a/notebooks/tutorial_nb_basics.ipynb b/notebooks/tutorial_nb_basics.ipynb index e52602d1..e763efdb 100644 --- a/notebooks/tutorial_nb_basics.ipynb +++ b/notebooks/tutorial_nb_basics.ipynb @@ -31,9 +31,13 @@ "metadata": {}, "outputs": [], "source": [ + "from pathlib import Path\n", + "\n", + "from datetime import date, timedelta\n", + "\n", "import geographer as gg\n", "import geopandas as gpd\n", - "from pathlib import Path" + "\n" ] }, { @@ -620,7 +624,7 @@ "source": [ "## 3. Downloading rasters for the vector data\n", "\n", - "To download rasters for the stadiums, we use the `RasterDownloaderForVectors`. This class needs to be passed a `DownloaderForSingleVector` to interface with the particular data source for our rasters, and a `RasterDownloadProcessor` to process the downloaded files. In this example, we would like to download Sentinel-2, so we choose the `SentinelDownloaderForSingleVector` to interface with [Copernicus Open Access Hub](https://scihub.copernicus.eu/) and the Sentinel2Processor to process the downloaded zipped .SAFE files to GeoTiff files (see [here](https://sentinels.copernicus.eu/web/sentinel/user-guides/sentinel-2-msi/data-formats) for an explanation of the Sentinel-2 data format). The GeoTiff format is a georeferenced version for remote sensing raster data of the Tiff format for normal rasters.\n", + "To download rasters for the stadiums, we use the `RasterDownloaderForVectors`. This class needs to be passed a `DownloaderForSingleVector` to interface with the data provider for our rasters, and a `RasterDownloadProcessor` to process the downloaded files. In this example, we will use the `EodagDownloaderForSingleVector` which uses [eodag](https://eodag.readthedocs.io/en/stable/) as a backend giving easy access to more than 10 providers and more than 50 different product types. We will use it to download Sentinel-2 from the [Copernicus Dataspace](https://dataspace.copernicus.eu/). To process the downloaded SAFE files (see [here](https://sentiwiki.copernicus.eu/web/s2-products) for an explanation of the Sentinel-2 data format) into GeoTiffs we use the `Sentinel2SAFEProcessor`. The GeoTiff format is a georeferenced version for remote sensing raster data of the Tiff format for normal rasters.\n", "\n", "Here, we define the downloader:" ] @@ -633,13 +637,12 @@ "source": [ "from geographer.downloaders import (\n", " RasterDownloaderForVectors,\n", - " SentinelDownloaderForSingleVector,\n", - " Sentinel2Processor,\n", + " EodagDownloaderForSingleVector,\n", + " Sentinel2SAFEProcessor,\n", ")\n", "\n", - "downloader_for_single_vector = SentinelDownloaderForSingleVector()\n", - "download_processor = Sentinel2Processor()\n", - "\n", + "download_processor = Sentinel2SAFEProcessor()\n", + "downloader_for_single_vector = EodagDownloaderForSingleVector()\n", "downloader = RasterDownloaderForVectors(\n", " downloader_for_single_vector=downloader_for_single_vector,\n", " download_processor=download_processor,\n", @@ -650,16 +653,39 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To use the Copernicus SciHub API we need to a username and password. You can sign up for an account [here](https://scihub.copernicus.eu/dhus/#/self-registration). The password and username will be assumed to be stored in a .ini file. The format of the file should be as follows." + "TODO Username and password for cop_dataspace\n", + "\n", + "If you do not yet have a copernicus dataspace account, you can create one [here](https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/auth?client_id=cdse-public&redirect_uri=https%3A%2F%2Fdataspace.copernicus.eu%2Fbrowser%2F&response_type=code&scope=openid). To use eodag, eodag will need the username and password of your copernicus dataspace account. One can set these in a config file, but here we will use environment variables:" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "credentials_ini_path = DATA_DIR / \"copernicus_scihub_credentials.ini\"" + "# os.environ[\"EODAG__COP_DATASPACE__AUTH__CREDENTIALS__USERNAME\"] = \"PLEASE_CHANGE_ME\"\n", + "# os.environ[\"EODAG__COP_DATASPACE__AUTH__CREDENTIALS__PASSWORD\"] = \"PLEASE_CHANGE_ME\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `downloader_for_single_vector` has an eodag attribute which is an `EODataAccessGateway`. We can use it to for example check that it is configured to access the copernicus dataspace provider:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "downloader_for_single_vector.eodag.\n", + "\n", + "assert \"cop_dataspace\" in downloader_for_single_vector.eodag.available_providers(\n", + " product_type=\"S2_MSI_L2A\"\n", + " )" ] }, { @@ -1121,15 +1147,29 @@ } ], "source": [ + "# Here, we define the parameters needed by the EodagDownloaderForSingleVector.download method\n", + "downloader_params = {\n", + " \"search_kwargs\": { # Keyword arguments for the eodag search_all method\n", + " \"provider\": \"cop_dataspace\", # Download from copernicus dataspace \n", + " \"productType\": \"S2_MSI_L2A\", # Search for Sentinel-2 L2A products\n", + " \"start\": (date.today() - timedelta(days=364)).strftime(\"%Y-%m-%d\"), # one year ago\n", + " \"end\": date.today().strftime(\"%Y-%m-%d\"), # today\n", + " },\n", + " \"filter_online\": True, # Filter out products that are not online\n", + " \"sort_by\": (\"cloudCover\", \"ASC\"), # Sort products by percentage of cloud cover in ascending order\n", + " \"suffix_to_remove\": \".SAFE\" # Will strip .SAFE from the stem of the tif file names\n", + "}\n", + "# Here, we define the parameters needed by the Sentinel2SAFEProcessor\n", + "processor_params = {\n", + " \"resolution\": 10, # Extract all 10m resolution bands\n", + " \"delete_safe\": True, # Delete the SAFE file after extracting a .tif file\n", + "}\n", + "\n", "downloader.download(\n", " connector=connector,\n", - " target_raster_count=2, # optional, defaults to 1. See explanation below.\n", - " credentials=credentials_ini_path, # could also directly supply (username, password) tuple\n", - " producttype=\"L2A\",\n", - " max_percent_cloud_coverage=10,\n", - " resolution=10, # resolution of extracted GeoTiff\n", - " date=(\"NOW-364DAYS\", \"NOW\"),\n", - " area_relation=\"Contains\",\n", + " target_raster_count=2, # optional, defaults to 1. Aim for 2 rasters covering each stadium. See below for further explanation.\n", + " downloader_params=downloader_params,\n", + " processor_params=processor_params,\n", ")" ] }, diff --git a/notebooks/tutorial_nb_cut_label_cluster.ipynb b/notebooks/tutorial_nb_cut_label_cluster.ipynb index d0e76b59..352e85d9 100644 --- a/notebooks/tutorial_nb_cut_label_cluster.ipynb +++ b/notebooks/tutorial_nb_cut_label_cluster.ipynb @@ -20,6 +20,15 @@ "First, we import geographer, as well as some other imports we will need." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# !pip install geographer matplotlib" + ] + }, { "cell_type": "code", "execution_count": 20, @@ -550,14 +559,19 @@ } ], "source": [ - "raster_paths = target_connector.rasters_containing_vector(\"Munich Olympiastadion\", mode=\"paths\")\n", + "raster_paths = target_connector.rasters_containing_vector(\n", + " \"Munich Olympiastadion\", mode=\"paths\"\n", + ")\n", "raster_path = raster_paths[0]\n", "label_path = target_connector.labels_dir / raster_path.name\n", "\n", - "raster = rioxarray.open_rasterio(raster_path).sel(band=[1,2,3]).values.transpose(1, 2, 0) / 65535\n", + "raster = (\n", + " rioxarray.open_rasterio(raster_path).sel(band=[1, 2, 3]).values.transpose(1, 2, 0)\n", + " / 65535\n", + ")\n", "label = rioxarray.open_rasterio(label_path).values.transpose(1, 2, 0) / 255\n", "\n", - "fig, ax = plt.subplots(1,2, figsize=(13, 13))\n", + "fig, ax = plt.subplots(1, 2, figsize=(13, 13))\n", "\n", "ax[0].imshow(raster)\n", "ax[0].axis(\"off\")\n", @@ -1039,7 +1053,7 @@ "from geographer.cutters import DSCutterIterOverVectors\n", "\n", "cutter_json_path = TARGET_DATA_DIR2 / \"connector\" / f\"{TRUNCATED_CUTTER_NAME}.json\"\n", - "cutter = DSCutterIterOverVectors.from_json_file(cutter_json_path)\n" + "cutter = DSCutterIterOverVectors.from_json_file(cutter_json_path)" ] }, { diff --git a/pyproject.toml b/pyproject.toml index 8b94f82f..70fb85c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,22 +1,81 @@ -[tool.black] -target-version = ['py37', 'py38', 'py39'] +[project] +name = "geographer" +version = "1.0.0" +description = "Build object-centric remote sensing computer vision datasets" +readme = "README.md" +license = {file = "LICENSE"} +authors = [ + {name = "Rustam Antia", email = "rustam.antia@gmail.com"} +] +keywords = ["remote sensing", "computer vision", "satellite imagery", "GIS", "machine learning"] +classifiers = [ + "License :: Other/Proprietary License", + "Programming Language :: Python :: 3" +] +requires-python = ">=3.9" +dependencies = [ + "eodag", + "eval_type_backport", + "fiona", + "geojson", + "geopandas", + "GitPython", + "ipywidgets", + "networkx", + "numpy", + "packaging", + "pandas", + "pydantic >= 2.0", + "pyproj", + "rasterio", + "requests", + "rioxarray", + "rtree", + "scipy", + "sentinelsat", + "Shapely", + "tqdm", + "urllib3" +] -[tool.isort] -atomic = true -profile = "black" -skip_gitignore = true - -[tool.mypy] -plugins = ["pydantic.mypy"] -show_error_codes = true -disallow_untyped_defs = false -disallow_incomplete_defs = false -ignore_missing_imports = true -warn_unused_ignores = false -warn_return_any = false -warn_unreachable = false -strict_optional=true +[project.optional-dependencies] +dev = [ + "ruff==0.7.4", + "build", + "docformatter", + "ipykernel", + "pytest" +] +docs = [ + "furo", + "autodoc_pydantic", + "docutils", + "pandoc", + "Sphinx", + "sphinx-autodoc-typehints", + "nbsphinx", + "nbsphinx-link" +] [build-system] -requires = ["setuptools"] +requires = ["setuptools", "wheel", "build"] build-backend = "setuptools.build_meta" + +[tool.setuptools] +packages = {find = {include = ["geographer*"], exclude = ["docs*", "tests*", "notebooks*"]}} + +[tool.pytest.ini_options] +markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')" +] + +[tool.ruff] +line-length = 88 + +[tool.ruff.lint] +extend-ignore = ["E266"] # allow multiple leading '#' for block comments +per-file-ignores = { "__init__.py" = ["F401", "D104"] } + +[tool.ruff.lint.isort] +combine-as-imports = true +force-sort-within-sections = true diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 5412d2c6..00000000 --- a/setup.cfg +++ /dev/null @@ -1,105 +0,0 @@ -[metadata] -name = geographer -version = 0.1 -author = Rustam Antia -author_email = rustam.antia@gmail.com -description = Build objec-centric remote sensing computer vision datasets -long_description = file: README.md -long_description_content_type = text/markdown -keywords = remote sensing, computer vision, satellite imagery, GIS, machine learning -license = Apache-2.0 -classifiers = - License :: OSI Approved :: Apache Software License - Programming Language :: Python :: 3 - -[options] -python_requires = >= 3.8 -packages = find: -zip_safe = False -include_package_data = True -install_requires = - fiona - geojson - geopandas - GitPython - ipywidgets - matplotlib - networkx - numpy - packaging - pandas - pydantic - pygeos - pyproj - rasterio - requests - rioxarray - rtree - scipy - sentinelsat - Shapely - tqdm - urllib3 - -[options.extras_require] -dev = - black - docformatter - flake8 - flake8-black - flake8-docstrings - flake8-isort - isort - mypy - pytest -docs = - alabaster - autodoc_pydantic - docutils - pandoc - Sphinx - sphinx-autodoc-typehints - nbsphinx - nbsphinx-link - -[options.package_data] -geographer = data/schema.json, *.txt -* = README.md - -[flake8] -max-line-length = 88 -# allow multiple leading '#' for block comment -extend-ignore = E266 -per-file-ignores = __init__.py:F401,D104 - -[mypy] - -[mypy-geopandas.*] -ignore_missing_imports = True - -[mypy-rasterio.*] -ignore_missing_imports = True - -[mypy-scipy.*] -ignore_missing_imports = True - -[mypy-networkx.*] -ignore_missing_imports = True - -[mypy-affine.*] -ignore_missing_imports = True - -[mypy-pandas.*] -ignore_missing_imports = True - -[mypy-sentinelsat.*] -ignore_missing_imports = True - -[mypy-tqdm.*] -ignore_missing_imports = True - -[mypy-shapely.*] -ignore_missing_imports = True - -[mypy-fiona.*] -ignore_missing_imports = True diff --git a/tests/cluster_rasters_test.py b/tests/cluster_rasters_test.py index 4c0b2125..f8c4aef7 100644 --- a/tests/cluster_rasters_test.py +++ b/tests/cluster_rasters_test.py @@ -25,11 +25,10 @@ def test_cluster_rasters(): # Create empty connector data_dir = Path("/whatever/") connector = Connector.from_scratch(data_dir=data_dir) - """ Create vectors """ - new_vectors = gpd.GeoDataFrame() + new_vectors = gpd.GeoDataFrame(geometry=gpd.GeoSeries([])) new_vectors.rename_axis(VECTOR_FEATURES_INDEX_NAME, inplace=True) # polygon names and geometries @@ -45,11 +44,10 @@ def test_cluster_rasters(): new_vectors = new_vectors.set_crs(epsg=STANDARD_CRS_EPSG_CODE) connector.add_to_vectors(new_vectors) - """ Create rasters """ - new_rasters = gpd.GeoDataFrame() + new_rasters = gpd.GeoDataFrame(geometry=gpd.GeoSeries([])) new_rasters.rename_axis(RASTER_IMGS_INDEX_NAME, inplace=True) # geometries (raster bounding rectangles) @@ -85,7 +83,6 @@ def test_cluster_rasters(): new_rasters = new_rasters.set_crs(epsg=STANDARD_CRS_EPSG_CODE) connector.add_to_rasters(new_rasters) - """ Test clusters defined by 'rasters_that_share_vectors_or_overlap' """ @@ -98,7 +95,6 @@ def test_cluster_rasters(): frozenset({"raster3", "raster1", "raster2"}), frozenset({"raster4", "raster6", "raster5"}), } - """ Test clusters defined by 'rasters_that_share_vectors' """ diff --git a/tests/conftest.py b/tests/conftest.py index 4aeab935..5d093c49 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,6 @@ """Pytest fixtures.""" import shutil -from pathlib import Path import pytest from utils import create_dummy_rasters, delete_dummy_rasters, get_test_dir @@ -12,10 +11,11 @@ @pytest.fixture(scope="session") -def dummy_cut_source_data_dir() -> Path: +def dummy_cut_source_data_dir(): """Return cut source data dir containing dummy data. - Dummy rasters are created before the pytest session starts and removed afterwards. + Dummy rasters are created before the pytest session starts and + removed afterwards. """ data_dir = get_test_dir() / CUT_SOURCE_DATA_DIR_NAME connector = Connector.from_data_dir(data_dir=data_dir) diff --git a/tests/connector_test.py b/tests/connector_test.py index 48b99eb1..f7dde623 100644 --- a/tests/connector_test.py +++ b/tests/connector_test.py @@ -37,12 +37,11 @@ def test_connector(): connector = Connector.from_scratch( data_dir=data_dir, task_vector_classes=TASK_FEATURE_CLASSES ) - """ Toy vectors """ # create empty GeoDataFrame with the right index name - new_vectors = gpd.GeoDataFrame() + new_vectors = gpd.GeoDataFrame(geometry=gpd.GeoSeries([])) new_vectors.rename_axis(VECTOR_FEATURES_INDEX_NAME, inplace=True) # polygon names and geometries @@ -62,9 +61,7 @@ def test_connector(): # set crs new_vectors = new_vectors.set_crs(epsg=STANDARD_CRS_EPSG_CODE) - """ - Test add_to_vectors - """ + """Test add_to_vectors.""" # add vectors connector.add_to_vectors(new_vectors) @@ -76,12 +73,10 @@ def test_connector(): new_vectors, ) assert check_graph_vertices_counts(connector) - """ - Toy rasters - """ + """Toy rasters.""" # empty GeoDataFrame with right index name - new_rasters = gpd.GeoDataFrame() + new_rasters = gpd.GeoDataFrame(geometry=gpd.GeoSeries([])) new_rasters.rename_axis(RASTER_IMGS_INDEX_NAME, inplace=True) # geometries (raster bounding rectangles) @@ -102,9 +97,7 @@ def test_connector(): # set crs new_rasters = new_rasters.set_crs(epsg=STANDARD_CRS_EPSG_CODE) - """ - Test add_to_rasters - """ + """Test add_to_rasters.""" connector.add_to_rasters(new_rasters) assert connector._graph._graph_dict == { @@ -119,12 +112,10 @@ def test_connector(): }, } assert check_graph_vertices_counts(connector) - """ - Test have_raster_for_vector, rectangle_bounding_raster, + """Test have_raster_for_vector, rectangle_bounding_raster, polygons_intersecting_raster, polygons_contained_in_raster, - rasters_containing_vector, values of 'have_raster?' - column in connector.vectors. - """ + rasters_containing_vector, values of 'have_raster?' column in + connector.vectors.""" assert (connector.rectangle_bounding_raster("raster1")).equals( box(-0.5, -0.5, 6, 6) ) @@ -138,11 +129,9 @@ def test_connector(): "polygon1", "polygon2", } - """ - Add more rasters - """ + """Add more rasters.""" # empty GeoDataFrame with right index name - new_rasters2 = gpd.GeoDataFrame() + new_rasters2 = gpd.GeoDataFrame(geometry=gpd.GeoSeries([])) new_rasters2.rename_axis(RASTER_IMGS_INDEX_NAME, inplace=True) # the new_rasters2 geometries will be the raster bounding rectangles here: @@ -195,9 +184,7 @@ def test_connector(): }, } assert check_graph_vertices_counts(connector) - """ - Drop vector feature - """ + """Drop vector feature.""" connector.drop_vectors("polygon3") # test containment/intersection relations, i.e. graph structure @@ -226,12 +213,10 @@ def test_connector(): }, } assert check_graph_vertices_counts(connector) - """ - Add more vector features - """ + """Add more vector features.""" # create empty GeoDataFrame with the right index name - new_vectors2 = gpd.GeoDataFrame() + new_vectors2 = gpd.GeoDataFrame(geometry=gpd.GeoSeries([])) new_vectors2.rename_axis(VECTOR_FEATURES_INDEX_NAME, inplace=True) # polygon names and geometries @@ -288,9 +273,7 @@ def test_connector(): # assert we have no duplicate entries assert len(connector.rasters) == 4 assert len(connector.vectors) == 4 - """ - Test drop_rasters - """ + """Test drop_rasters.""" connector.drop_rasters(["raster2", "raster3"]) assert len(connector.rasters) == 2 @@ -313,9 +296,7 @@ def test_connector(): } assert check_graph_vertices_counts(connector) - """ - Test drop_vectors - """ + """Test drop_vectors.""" connector.drop_vectors(["polygon1", "polygon3"]) assert len(connector.vectors) == 2 diff --git a/tests/cut_rasters_around_every_vector_test.py b/tests/cut_rasters_around_every_vector_test.py index 4d818aa9..5dba4e4b 100644 --- a/tests/cut_rasters_around_every_vector_test.py +++ b/tests/cut_rasters_around_every_vector_test.py @@ -12,7 +12,6 @@ """ import shutil -from typing import List from shapely.geometry import Polygon from shapely.ops import unary_union @@ -121,7 +120,7 @@ def test_rasters_around_every_vector(dummy_cut_source_data_dir): tempelhofer_feld: Polygon = target_connector.vectors.loc[ "berlin_tempelhofer_feld" ].geometry - rasters_intersecting_tempelhofer_feld: List[str] = ( + rasters_intersecting_tempelhofer_feld: list[str] = ( target_connector.rasters_intersecting_vector("berlin_tempelhofer_feld") ) bboxes = target_connector.rasters.geometry.loc[ diff --git a/tests/data/cut_source/connector/rasters.geojson b/tests/data/cut_source/connector/rasters.geojson index 06db1810..1a5a7c45 100644 --- a/tests/data/cut_source/connector/rasters.geojson +++ b/tests/data/cut_source/connector/rasters.geojson @@ -1,5 +1,6 @@ { "type": "FeatureCollection", +"name": "rasters", "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, "features": [ { "type": "Feature", "properties": { "raster_name": "S2A_MSIL2A_20220309T100841_N0400_R022_T32UQD_20220309T121849.tif", "raster_processed?": true, "timestamp": "2022-03-09-10:08:41", "orig_crs_epsg_code": 32632 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.531032170806659, 52.175484718678582 ], [ 13.634183017872079, 53.159421967147352 ], [ 11.994640922835734, 53.211992871403481 ], [ 11.927820031382028, 52.226225390811273 ], [ 13.531032170806659, 52.175484718678582 ] ] ] } } diff --git a/tests/data/cut_source/connector/vectors.geojson b/tests/data/cut_source/connector/vectors.geojson index 22ca2506..b5596b90 100644 --- a/tests/data/cut_source/connector/vectors.geojson +++ b/tests/data/cut_source/connector/vectors.geojson @@ -1,5 +1,6 @@ { "type": "FeatureCollection", +"name": "vectors", "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, "features": [ { "type": "Feature", "properties": { "vector_name": "berlin_brandenburg_gate", "raster_count": 1, "Description": "", "type": "object", "prob_of_class_object": 1.0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 13.377565438910141, 52.516568397149548, 0.0 ], [ 13.37767670931829, 52.5159632173402, 0.0 ], [ 13.37811966376411, 52.515999764239929, 0.0 ], [ 13.37810339593884, 52.516119741283603, 0.0 ], [ 13.37786452595477, 52.516109878538877, 0.0 ], [ 13.377804199934889, 52.5164569450953, 0.0 ], [ 13.37804096941727, 52.516481108895917, 0.0 ], [ 13.378024559886381, 52.516596731533298, 0.0 ], [ 13.377565438910141, 52.516568397149548, 0.0 ] ] ] } }, diff --git a/tests/mock_download_test.py b/tests/mock_download_test.py index c36b92aa..88853e0f 100644 --- a/tests/mock_download_test.py +++ b/tests/mock_download_test.py @@ -1,5 +1,4 @@ -""" -Test RasterDownloadProcessor using mock downloader. +"""Test RasterDownloadProcessor using mock downloader. Virtually 'downloads' (no files operations are actually done) from a dataset of rasters in a source directory. @@ -36,9 +35,7 @@ def test_mock_download(): data_dir=data_dir, task_vector_classes=["object"] ) - vectors = gpd.read_file( - test_dir / "geographer_download_test.geojson", driver="GeoJSON" - ) + vectors = gpd.read_file(test_dir / "geographer_download_test.geojson") vectors.set_index("name", inplace=True) connector.add_to_vectors(vectors) diff --git a/tests/save_load_base_model_test.py b/tests/save_load_base_model_test.py index c3d0042a..320e2aea 100644 --- a/tests/save_load_base_model_test.py +++ b/tests/save_load_base_model_test.py @@ -1,63 +1,84 @@ -"""Test saving/loading nested BaseModels. - -TODO: split up into smaller units (more simple nestings etc) -""" +"""Test saving/loading nested BaseModels.""" from pathlib import Path import git +from pydantic import BaseModel -from geographer.downloaders.downloader_for_vectors import RasterDownloaderForVectors -from geographer.downloaders.sentinel2_download_processor import Sentinel2Processor -from geographer.downloaders.sentinel2_downloader_for_single_vector import ( - SentinelDownloaderForSingleVector, +from geographer.base_model_dict_conversion.save_load_base_model_mixin import ( + SaveAndLoadBaseModelMixIn, ) +class InnermostBaseModel(BaseModel): + """Innermost BaseModel.""" + + int_value: int + str_value: str + + +class NestedBaseModel(BaseModel): + """Nested BaseModel.""" + + dict_value: dict + innermost_base_model: InnermostBaseModel + + +class OutermostBaseModel(BaseModel, SaveAndLoadBaseModelMixIn): + """Outermost BaseModel.""" + + nested_base_model: NestedBaseModel + + json_path: Path + + def save(self): + """Save the model.""" + self._save(self.json_path) + + def test_save_load_nested_base_model(): """Test saving and loading nested BaseModel.""" - # get repo working tree directory repo = git.Repo(".", search_parent_directories=True) repo_root = Path(repo.working_tree_dir) - download_test_data_dir = repo_root / "tests/data/temp/download_s2_test" - - # define nested BaseModel - s2_download_processor = Sentinel2Processor() - s2_downloader_for_single_vector = SentinelDownloaderForSingleVector() - s2_downloader = RasterDownloaderForVectors( - download_dir=download_test_data_dir / "download", - downloader_for_single_vector=s2_downloader_for_single_vector, - download_processor=s2_download_processor, - kwarg_defaults={ # further nesting: dictionary - "producttype": "L2A", - "resolution": 10, - "max_percent_cloud_coverage": 10, - "date": ("NOW-364DAYS", "NOW"), - "area_relation": "Contains", - "credentials_ini_path": download_test_data_dir / "credentials.ini", - # keys must be strings in the following dict, see - # https://stackoverflow.com/questions/1450957/pythons-json-module-converts-int-dictionary-keys-to-strings # noqa: E501 - "additional_nested_dictionary": { - "1": 2, - "3": 4, + temp_dir = repo_root / "tests/data/temp/" + outermost_base_model_json_path = temp_dir / "outermost_base_model.json" + + # Define a nested model + outermost_base_model = OutermostBaseModel( + nested_base_model=NestedBaseModel( + dict_value={ + "a": 1, + "b": { + "c": None, + }, }, - "some_list": [1, 2, 3, 4], - }, + innermost_base_model=InnermostBaseModel( + int_value=2, + str_value="str_value", + ), + ), + json_path=outermost_base_model_json_path, ) """ - Test save and load Sentinel-2 Downloader + Test saving and loading a nested BaseModel """ # save - s2_downloader_json_path = download_test_data_dir / "connector/s2_downloader.json" - s2_downloader.save(s2_downloader_json_path) + outermost_base_model.save() # load - s2_downloader_from_json = RasterDownloaderForVectors.from_json_file( - s2_downloader_json_path, + outermost_base_model_from_json = OutermostBaseModel.from_json_file( + outermost_base_model_json_path, + constructor_symbol_table={ + "InnermostBaseModel": InnermostBaseModel, + "NestedBaseModel": NestedBaseModel, + "OutermostBaseModel": OutermostBaseModel, + }, ) # make sure saving and loading again doesn't change anything - assert s2_downloader_from_json == s2_downloader + assert ( + outermost_base_model_from_json.model_dump() == outermost_base_model.model_dump() + ) if __name__ == "__main__": diff --git a/tests/download_s2_test_manually.py b/tests/test_eodag_s2_download.py similarity index 55% rename from tests/download_s2_test_manually.py rename to tests/test_eodag_s2_download.py index 2bc0f61e..a16f6d54 100644 --- a/tests/download_s2_test_manually.py +++ b/tests/test_eodag_s2_download.py @@ -1,27 +1,29 @@ -""" -Manually triggered test of Sentinel-2 downloader. +"""Manually triggered test of Sentinel-2 downloader. Run by hand to test downloading Sentinel-2 data. Intentionally not discoverable by pytest: Downloading Sentinel-2 rasters is slow and uses a lot of disk space. Needs an .ini file with API credentials. - -TODO: write test for download_mode 'bboxgrid' using large polygon """ import shutil +from datetime import date, timedelta import geopandas as gpd +import pytest from utils import get_test_dir from geographer import Connector from geographer.downloaders.downloader_for_vectors import RasterDownloaderForVectors -from geographer.downloaders.sentinel2_download_processor import Sentinel2Processor -from geographer.downloaders.sentinel2_downloader_for_single_vector import ( - SentinelDownloaderForSingleVector, +from geographer.downloaders.eodag_downloader_for_single_vector import ( + EodagDownloaderForSingleVector, ) +from geographer.downloaders.sentinel2_download_processor import Sentinel2SAFEProcessor +# To run just this test, execute +# pytest -v -s tests/test_eodag_s2_download.py::test_s2_download +@pytest.mark.slow def test_s2_download(): """Test downloading Sentinel-2 data.""" # noqa: D202 @@ -31,49 +33,61 @@ def test_s2_download(): test_dir = get_test_dir() data_dir = test_dir / "temp/download_s2_test" - credentials_ini_path = test_dir / "download_s2_test_credentials.ini" - assert ( - credentials_ini_path.is_file() - ), f"Need credentials in {credentials_ini_path} to test sentinel download" - vectors = gpd.read_file( - test_dir / "geographer_download_test.geojson", driver="GeoJSON" - ) + vectors = gpd.read_file(test_dir / "geographer_download_test.geojson") vectors.set_index("name", inplace=True) connector = Connector.from_scratch( data_dir=data_dir, task_vector_classes=["object"] ) connector.add_to_vectors(vectors) - """ Test RasterDownloaderForVectors for Sentinel-2 data """ - s2_download_processor = Sentinel2Processor() - s2_downloader_for_single_vector = SentinelDownloaderForSingleVector() - s2_downloader = RasterDownloaderForVectors( - downloader_for_single_vector=s2_downloader_for_single_vector, - download_processor=s2_download_processor, - kwarg_defaults={ - "producttype": "L2A", - "resolution": 10, - "max_percent_cloud_coverage": 10, - "date": ("NOW-364DAYS", "NOW"), - "area_relation": "Contains", - "credentials": credentials_ini_path, - }, + product_type = "S2_MSI_L2A" + download_processor = Sentinel2SAFEProcessor() + downloader_for_single_vector = EodagDownloaderForSingleVector() + downloader = RasterDownloaderForVectors( + downloader_for_single_vector=downloader_for_single_vector, + download_processor=download_processor, ) + if "cop_dataspace" not in downloader_for_single_vector.eodag.available_providers( + product_type=product_type + ): + raise RuntimeError( + "'cop_dataspace' needs to be available as a provider. " + "Probably the username and/or password are missing." + ) """ Download Sentinel-2 rasters """ - s2_downloader.download(connector=connector) + downloader_params = { + "search_kwargs": { + "provider": "cop_dataspace", + "productType": product_type, + "start": (date.today() - timedelta(days=364)).strftime("%Y-%m-%d"), + "end": date.today().strftime("%Y-%m-%d"), + }, + "filter_online": True, + "sort_by": ("cloudCover", "ASC"), + "suffix_to_remove": ".SAFE", + } + processor_params = { + "resolution": 10, + "delete_safe": True, + } + downloader.download( + connector=connector, + downloader_params=downloader_params, + processor_params=processor_params, + ) # The vectors contain # - 2 objects in Berlin (Reichstag and Brandenburg gate) # that are very close to each other # - 2 objects in Lisbon (Castelo de Sao Jorge and # Praca Do Comercio) that are very close to each other. - # Thus the s2_downloader should have downloaded two rasters, + # Thus the downloader should have downloaded two rasters, # one for Berlin and one for Lisbon, each containing two objects. # Berlin @@ -89,7 +103,6 @@ def test_s2_download(): assert connector.rasters_containing_vector( "lisbon_castelo_de_sao_jorge" ) == connector.rasters_containing_vector("lisbon_praca_do_comercio") - """ Clean up: delete downloads """ diff --git a/tests/download_jaxa_test_manually.py b/tests/test_jaxa_download.py similarity index 88% rename from tests/download_jaxa_test_manually.py rename to tests/test_jaxa_download.py index d5ee5d76..8eed66e2 100644 --- a/tests/download_jaxa_test_manually.py +++ b/tests/test_jaxa_download.py @@ -1,5 +1,4 @@ -""" -Manually triggered test of JAXA downloader. +"""Manually triggered test of JAXA downloader. Run by hand to test downloading JAXA data. Intentionally not discoverable by pytest: Downloading JAXA rasters is slightly slow. @@ -8,6 +7,7 @@ import shutil import geopandas as gpd +import pytest from utils import get_test_dir from geographer import Connector @@ -18,6 +18,9 @@ ) +# To run just this test, execute +# pytest -v -s tests/test_jaxa_download.py::test_jaxa_download +@pytest.mark.slow def test_jaxa_download(): """Test downloading JAXA data.""" # noqa: D202 @@ -27,16 +30,13 @@ def test_jaxa_download(): test_dir = get_test_dir() data_dir = test_dir / "temp/download_jaxa_test" - vectors = gpd.read_file( - test_dir / "geographer_download_test.geojson", driver="GeoJSON" - ) + vectors = gpd.read_file(test_dir / "geographer_download_test.geojson") vectors.set_index("name", inplace=True) connector = Connector.from_scratch( data_dir=data_dir, task_vector_classes=["object"] ) connector.add_to_vectors(vectors) - """ Test RasterDownloaderForVectors for Sentinel-2 data """ @@ -45,16 +45,17 @@ def test_jaxa_download(): jaxa_downloader = RasterDownloaderForVectors( downloader_for_single_vector=jaxa_downloader_for_single_vector, download_processor=jaxa_download_processor, - kwarg_defaults={ - "data_version": "1804", - "download_mode": "bboxvertices", - }, ) - """ Download Sentinel-2 rasters """ - jaxa_downloader.download(connector=connector) + jaxa_downloader.download( + connector=connector, + downloader_params={ + "data_version": "1804", + "download_mode": "bboxvertices", + }, + ) # The vectors contain # - 2 objects in Berlin (Reichstag and Brandenburg gate) # that are very close to each other @@ -76,7 +77,6 @@ def test_jaxa_download(): assert connector.rasters_containing_vector("lisbon_castelo_de_sao_jorge") == ( connector.rasters_containing_vector("lisbon_praca_do_comercio") ) - """ Clean up: delete downloads """ diff --git a/tests/utils.py b/tests/utils.py index 6e021630..78ca2ce0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,7 +4,6 @@ import shutil from pathlib import Path -from typing import Optional, Union import git import numpy as np @@ -22,14 +21,14 @@ def get_test_dir(): def create_dummy_rasters( - data_dir: Union[Path, str], + data_dir: Path | str, raster_size: int, - raster_names: Optional[list[str]] = None, + raster_names: list[str] | None = None, ) -> None: """Create dummy rasters. - Create dummy rasters for a dataset from the connector's - rasters geodataframe. + Create dummy rasters for a dataset from the connector's rasters + geodataframe. """ connector = Connector.from_data_dir(data_dir) connector.rasters_dir.mkdir(parents=True, exist_ok=True) @@ -45,7 +44,6 @@ def create_dummy_rasters( ), desc="Creating dummy rasters", ): - raster_array = np.stack( [np.ones((raster_size, raster_size), dtype=np.uint8) * n for n in range(3)] ) @@ -71,7 +69,7 @@ def create_dummy_rasters( dst.write(raster_array[idx, :, :], idx + 1) -def delete_dummy_rasters(data_dir: Union[Path, str]) -> None: - """Delete dummy raster data (rasters and segmentation labels) from dataset.""" +def delete_dummy_rasters(data_dir: Path | str) -> None: + """Delete dummy raster data from dataset.""" shutil.rmtree(data_dir / "rasters", ignore_errors=True) shutil.rmtree(data_dir / "labels", ignore_errors=True)