diff --git a/src/stactools/sentinel2/commands.py b/src/stactools/sentinel2/commands.py index d90df72..af88ff4 100644 --- a/src/stactools/sentinel2/commands.py +++ b/src/stactools/sentinel2/commands.py @@ -66,7 +66,7 @@ def create_item_command( antimeridian_strategy=strategy, ) - item_path = os.path.join(dst, "{}.json".format(item.id)) + item_path = os.path.join(dst, f"{item.id}.json") item.set_self_href(item_path) item.save_object() diff --git a/src/stactools/sentinel2/constants.py b/src/stactools/sentinel2/constants.py index 5bdc156..ce86e28 100644 --- a/src/stactools/sentinel2/constants.py +++ b/src/stactools/sentinel2/constants.py @@ -1,4 +1,4 @@ -from typing import Dict, Final, List +from typing import Final import pystac from pystac.extensions.eo import Band @@ -16,7 +16,7 @@ target="https://sentinel.esa.int/documents/247904/690755/Sentinel_Data_Legal_Notice", ) -SENTINEL_INSTRUMENTS: Final[List[str]] = ["msi"] +SENTINEL_INSTRUMENTS: Final[list[str]] = ["msi"] SENTINEL_CONSTELLATION: Final[str] = "sentinel-2" SENTINEL_PROVIDER: Final[pystac.Provider] = pystac.Provider( @@ -35,7 +35,7 @@ DEFAULT_TOLERANCE: Final[float] = 0.01 COORD_ROUNDING: Final[int] = 6 -SENTINEL_BANDS: Final[Dict[str, Band]] = { +SENTINEL_BANDS: Final[dict[str, Band]] = { "coastal": Band.create( name="coastal", common_name="coastal", @@ -133,7 +133,7 @@ # available for each band as separate assets. # The first resolution is the sensor gsd; others # are downscaled versions. -UNSUFFIXED_BAND_RESOLUTION: Final[Dict[str, int]] = { +UNSUFFIXED_BAND_RESOLUTION: Final[dict[str, int]] = { "coastal": 60, "blue": 10, "green": 10, @@ -151,7 +151,7 @@ "snow": 20, } -BANDS_TO_ASSET_NAME: Final[Dict[str, str]] = { +BANDS_TO_ASSET_NAME: Final[dict[str, str]] = { "B01": "coastal", "B02": "blue", "B03": "green", @@ -167,7 +167,7 @@ "B12": "swir22", } -L2A_IMAGE_PATHS: Final[List[str]] = [ +L2A_IMAGE_PATHS: Final[list[str]] = [ "preview.jpg", "R10m/B04.jp2", "R10m/B03.jp2", @@ -210,7 +210,7 @@ "qi/SNW_20m.jp2", ] -L1C_IMAGE_PATHS: Final[List[str]] = [ +L1C_IMAGE_PATHS: Final[list[str]] = [ "preview.jpg", "B01.jp2", "B02.jp2", diff --git a/src/stactools/sentinel2/granule_metadata.py b/src/stactools/sentinel2/granule_metadata.py index 676467d..f11f556 100644 --- a/src/stactools/sentinel2/granule_metadata.py +++ b/src/stactools/sentinel2/granule_metadata.py @@ -2,7 +2,8 @@ import re from dataclasses import dataclass -from typing import Dict, Final, List, Optional, Pattern, Tuple +from re import Pattern +from typing import Final import pystac from pystac.utils import map_opt @@ -19,7 +20,7 @@ class GranuleMetadataError(Exception): class GranuleMetadata: - def __init__(self, href, read_href_modifier: Optional[ReadHrefModifier] = None): + def __init__(self, href, read_href_modifier: ReadHrefModifier | None = None): self.href = href self._root = XmlElement.from_file(href, read_href_modifier) @@ -51,7 +52,7 @@ def __init__(self, href, read_href_modifier: Optional[ReadHrefModifier] = None): "n1:Quality_Indicators_Info/Image_Content_QI" ) - self.resolution_to_shape: Dict[int, Tuple[int, int]] = {} + self.resolution_to_shape: dict[int, tuple[int, int]] = {} for size_node in self._geocoding_node.findall("Size"): res = size_node.get_attr("resolution") if res is None: @@ -69,7 +70,7 @@ def __init__(self, href, read_href_modifier: Optional[ReadHrefModifier] = None): self.resolution_to_shape[int(res)] = (nrows, ncols) @property - def epsg(self) -> Optional[int]: + def epsg(self) -> int | None: epsg_str = self._geocoding_node.find_text("HORIZONTAL_CS_CODE") if epsg_str is None: return None @@ -77,7 +78,7 @@ def epsg(self) -> Optional[int]: return int(epsg_str.split(":")[1]) @property - def proj_bbox(self) -> List[float]: + def proj_bbox(self) -> list[float]: """The bbox of the image in the CRS of the image data""" nrows, ncols = self.resolution_to_shape[10] geoposition = self._geocoding_node.find("Geoposition") @@ -93,21 +94,21 @@ def proj_bbox(self) -> List[float]: return [ulx, uly - (10 * nrows), ulx + (10 * ncols), uly] @property - def cloudiness_percentage(self) -> Optional[float]: + def cloudiness_percentage(self) -> float | None: return map_opt( float, self._image_content_node.find_text("CLOUDY_PIXEL_PERCENTAGE"), ) @property - def mean_solar_zenith(self) -> Optional[float]: + def mean_solar_zenith(self) -> float | None: return map_opt( float, self._tile_angles_node.find_text("Mean_Sun_Angle/ZENITH_ANGLE"), ) @property - def mean_solar_azimuth(self) -> Optional[float]: + def mean_solar_azimuth(self) -> float | None: return map_opt( float, self._tile_angles_node.find_text("Mean_Sun_Angle/AZIMUTH_ANGLE"), @@ -115,7 +116,7 @@ def mean_solar_azimuth(self) -> Optional[float]: @property def metadata_dict(self): - properties: Dict[str, Optional[float]] = { + properties: dict[str, float | None] = { f"{s2_prefix}:tile_id": self.tile_id, f"{s2_prefix}:product_type": map_opt( float, self._image_content_node.find_text("PRODUCT_TYPE") @@ -208,7 +209,7 @@ def scene_id(self) -> str: return "_".join(id_parts) @property - def platform(self) -> Optional[str]: + def platform(self) -> str | None: if self.tile_id.startswith("S2A"): return "sentinel-2a" elif self.tile_id.startswith("S2B"): @@ -217,7 +218,7 @@ def platform(self) -> Optional[str]: return None @property - def processing_baseline(self) -> Optional[str]: + def processing_baseline(self) -> str | None: """Returns the string to be used for the baseline_processing property Parsed based on the naming convention found here: https://sentinel.esa.int/web/sentinel/user-guides/sentinel-2-msi/naming-convention @@ -228,7 +229,7 @@ def processing_baseline(self) -> Optional[str]: return None @property - def pvi_filename(self) -> Optional[str]: + def pvi_filename(self) -> str | None: return self._root.find_text("n1:Quality_Indicators_Info/PVI_FILENAME") def create_asset(self): @@ -244,7 +245,7 @@ class ViewingAngle: zenith: float @classmethod - def from_nodes(cls, nodes: List[XmlElement]) -> Dict[str, ViewingAngle]: + def from_nodes(cls, nodes: list[XmlElement]) -> dict[str, ViewingAngle]: angles = dict() for node in nodes: band_id_str = node.get_attr("bandId") diff --git a/src/stactools/sentinel2/mgrs.py b/src/stactools/sentinel2/mgrs.py index caed59d..802cc91 100644 --- a/src/stactools/sentinel2/mgrs.py +++ b/src/stactools/sentinel2/mgrs.py @@ -1,7 +1,8 @@ """Implements the :stac-ext:`MGRS Extension `.""" import re -from typing import Any, Dict, FrozenSet, Optional, Pattern, Set, Union, cast +from re import Pattern +from typing import Any, Optional, Union, cast import pystac from pystac.extensions.base import ExtensionManagementMixin, PropertiesExtension @@ -15,7 +16,7 @@ GRID_SQUARE_PROP: str = PREFIX + "grid_square" # required UTM_ZONE_PROP: str = PREFIX + "utm_zone" -LATITUDE_BANDS: FrozenSet[str] = frozenset( +LATITUDE_BANDS: frozenset[str] = frozenset( { "C", "D", @@ -40,7 +41,7 @@ } ) -UTM_ZONES: FrozenSet[int] = frozenset( +UTM_ZONES: frozenset[int] = frozenset( { 1, 2, @@ -158,7 +159,7 @@ class MgrsExtension( item: pystac.Item """The :class:`~pystac.Item` being extended.""" - properties: Dict[str, Any] + properties: dict[str, Any] """The :class:`~pystac.Item` properties, including extension properties.""" def __init__(self, item: pystac.Item): @@ -166,7 +167,7 @@ def __init__(self, item: pystac.Item): self.properties = item.properties def __repr__(self) -> str: - return "".format(self.item.id) + return f"" def apply( self, @@ -242,7 +243,7 @@ def ext(cls, obj: pystac.Item, add_if_missing: bool = False) -> "MgrsExtension": class MgrsExtensionHooks(ExtensionHooks): schema_uri: str = SCHEMA_URI - prev_extension_ids: Set[str] = set() + prev_extension_ids: set[str] = set() stac_object_types = {pystac.STACObjectType.ITEM} diff --git a/src/stactools/sentinel2/product_metadata.py b/src/stactools/sentinel2/product_metadata.py index 2357f1f..c88d7f8 100644 --- a/src/stactools/sentinel2/product_metadata.py +++ b/src/stactools/sentinel2/product_metadata.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, List, Optional +from typing import Any, Optional import pystac from pystac.utils import map_opt, str_to_datetime @@ -162,7 +162,7 @@ def image_media_type(self) -> str: return pystac.MediaType.JPEG2000 @property - def image_paths(self) -> List[str]: + def image_paths(self) -> list[str]: extension = ".tif" if self.image_media_type == pystac.MediaType.COG else ".jp2" return [f"{x.text}{extension}" for x in self.granule_node.findall("IMAGE_FILE")] @@ -180,7 +180,7 @@ def platform(self) -> Optional[str]: return self.datatake_node.find_text("SPACECRAFT_NAME") @property - def metadata_dict(self) -> Dict[str, Any]: + def metadata_dict(self) -> dict[str, Any]: result = { f"{s2_prefix}:product_uri": self.product_id, f"{s2_prefix}:generation_time": self.product_info_node.find_text( @@ -208,7 +208,7 @@ def metadata_dict(self) -> Dict[str, Any]: return {k: v for k, v in result.items() if v is not None} @property - def boa_add_offsets(self) -> Dict[str, int]: + def boa_add_offsets(self) -> dict[str, int]: if self.boa_add_offset_values_list_node is not None: xs = { x.get_attr("band_id"): int(x.text) diff --git a/src/stactools/sentinel2/safe_manifest.py b/src/stactools/sentinel2/safe_manifest.py index bf0c91c..1eff96c 100644 --- a/src/stactools/sentinel2/safe_manifest.py +++ b/src/stactools/sentinel2/safe_manifest.py @@ -1,5 +1,5 @@ import os -from typing import List, Optional, Tuple +from typing import Optional import pystac from stactools.core.io import ReadHrefModifier @@ -25,7 +25,7 @@ def __init__( f"Manifest at {self.href} does not have a dataObjectSection" ) - def _find_href(self, xpaths: List[str]) -> Optional[str]: + def _find_href(self, xpaths: list[str]) -> Optional[str]: file_path = None for xpath in xpaths: file_path = self._data_object_section.find_attr("href", xpath) @@ -73,7 +73,7 @@ def granule_metadata_href(self) -> Optional[str]: ] ) - def create_asset(self) -> Tuple[str, pystac.Asset]: + def create_asset(self) -> tuple[str, pystac.Asset]: asset = pystac.Asset( href=self.href, media_type=pystac.MediaType.XML, roles=["metadata"] ) diff --git a/src/stactools/sentinel2/stac.py b/src/stactools/sentinel2/stac.py index 8234c5f..74c8a23 100644 --- a/src/stactools/sentinel2/stac.py +++ b/src/stactools/sentinel2/stac.py @@ -5,7 +5,8 @@ from dataclasses import dataclass from datetime import datetime from itertools import chain -from typing import Any, Dict, Final, List, Optional, Pattern, Tuple +from re import Pattern +from typing import Any, Final, Optional import antimeridian import pystac @@ -65,7 +66,7 @@ BAND_ID_PATTERN: Final[Pattern[str]] = re.compile(r"[_/](B\d[A\d])") RESOLUTION_PATTERN: Final[Pattern[str]] = re.compile(r"(\w{2}m)") -RGB_BANDS: Final[List[Band]] = [ +RGB_BANDS: Final[list[Band]] = [ SENTINEL_BANDS["red"], SENTINEL_BANDS["green"], SENTINEL_BANDS["blue"], @@ -78,30 +79,30 @@ class Metadata: scene_id: str cloudiness_percentage: Optional[float] - extra_assets: Dict[str, pystac.Asset] - geometry: Dict[str, Any] - bbox: List[float] + extra_assets: dict[str, pystac.Asset] + geometry: dict[str, Any] + bbox: list[float] datetime: datetime platform: str - metadata_dict: Dict[str, Any] + metadata_dict: dict[str, Any] image_media_type: str - image_paths: List[str] + image_paths: list[str] epsg: int - proj_bbox: List[float] - resolution_to_shape: Dict[int, Tuple[int, int]] + proj_bbox: list[float] + resolution_to_shape: dict[int, tuple[int, int]] processing_baseline: str - viewing_angles: Dict[str, ViewingAngle] + viewing_angles: dict[str, ViewingAngle] orbit_state: Optional[str] = None relative_orbit: Optional[int] = None sun_azimuth: Optional[float] = None sun_zenith: Optional[float] = None - boa_add_offsets: Optional[Dict[str, int]] = None + boa_add_offsets: Optional[dict[str, int]] = None def create_item( granule_href: str, tolerance: float = DEFAULT_TOLERANCE, - additional_providers: Optional[List[pystac.Provider]] = None, + additional_providers: Optional[list[pystac.Provider]] = None, read_href_modifier: Optional[ReadHrefModifier] = None, asset_href_prefix: Optional[str] = None, antimeridian_strategy: Strategy = Strategy.SPLIT, @@ -247,13 +248,13 @@ def create_item( def image_asset_from_href( item: pystac.Item, asset_href: str, - resolution_to_shape: Dict[int, Tuple[int, int]], - proj_bbox: List[float], + resolution_to_shape: dict[int, tuple[int, int]], + proj_bbox: list[float], media_type: Optional[str], processing_baseline: str, - viewing_angles: Dict[str, ViewingAngle], - boa_add_offsets: Optional[Dict[str, int]] = None, -) -> Tuple[str, pystac.Asset]: + viewing_angles: dict[str, ViewingAngle], + boa_add_offsets: Optional[dict[str, int]] = None, +) -> tuple[str, pystac.Asset]: logger.debug(f"Creating asset for image {asset_href}") _, ext = os.path.splitext(asset_href) @@ -666,11 +667,11 @@ def offset_for_pb(processing_baseline: str) -> float: def raster_bands( - boa_add_offsets: Optional[Dict[str, int]], + boa_add_offsets: Optional[dict[str, int]], processing_baseline: str, band_id: str, resolution: float, -) -> List[RasterBand]: +) -> list[RasterBand]: # prior to processing baseline 04.00, scale and offset were # defined out of band, so handle that case offset = ( diff --git a/src/stactools/sentinel2/tileinfo_metadata.py b/src/stactools/sentinel2/tileinfo_metadata.py index cc9df1e..94c586c 100644 --- a/src/stactools/sentinel2/tileinfo_metadata.py +++ b/src/stactools/sentinel2/tileinfo_metadata.py @@ -1,6 +1,6 @@ import json from datetime import datetime -from typing import Any, Dict, Optional, Tuple +from typing import Any, Optional import pystac from pystac.utils import str_to_datetime @@ -29,11 +29,11 @@ def product_path(self) -> str: return self._product_path @property - def geometry(self) -> Dict[str, Any]: + def geometry(self) -> dict[str, Any]: return self._geometry @property - def bbox(self) -> Tuple[float, float, float, float]: + def bbox(self) -> tuple[float, float, float, float]: return self._bbox @property diff --git a/src/stactools/sentinel2/utils.py b/src/stactools/sentinel2/utils.py index e254ece..48a60c9 100644 --- a/src/stactools/sentinel2/utils.py +++ b/src/stactools/sentinel2/utils.py @@ -1,5 +1,6 @@ import re -from typing import Final, List, Optional, Pattern +from re import Pattern +from typing import Final, Optional import shapely from pystac import Item @@ -18,7 +19,7 @@ def extract_gsd(image_path: str) -> Optional[int]: return None -def fix_z_values(coord_values: List[str]) -> List[float]: +def fix_z_values(coord_values: list[str]) -> list[float]: """Some geometries have a '0' value in the z position of the coordinates. This method detects and removes z position coordinates. This assumes that in cases where