"""
Module defining constants and enumerate types used in the package
"""
from __future__ import annotations
import functools
import mimetypes
import re
import warnings
from enum import Enum, EnumMeta
from typing import Callable, ClassVar
import numpy as np
import pyproj
import utm
from aenum import extend_enum
from ._version import __version__
from .exceptions import SHUserWarning
[docs]class ServiceUrl:
"""Most commonly used Sentinel Hub service URLs"""
MAIN = "https://services.sentinel-hub.com"
USWEST = "https://services-uswest2.sentinel-hub.com"
CREODIAS = "https://creodias.sentinel-hub.com"
MUNDI = "https://shservices.mundiwebservices.com"
CODE_DE = "https://code-de.sentinel-hub.com"
[docs]class ServiceType(Enum):
"""Enum constant class for type of service
Supported types are WMS, WCS, WFS, AWS, IMAGE
"""
WMS = "wms"
WCS = "wcs"
WFS = "wfs"
AWS = "aws"
IMAGE = "image"
FIS = "fis"
PROCESSING_API = "processing"
[docs]class ResamplingType(Enum):
"""Enum constant class for type of resampling."""
NEAREST = "NEAREST"
BILINEAR = "BILINEAR"
BICUBIC = "BICUBIC"
@classmethod
def _missing_(cls, value: object) -> ResamplingType:
# This triggers if value is not found, before raising an error (see Enum docs). Makes class case-insensitive.
if isinstance(value, str) and value.upper() in cls._value2member_map_:
return cls(value.upper())
return super()._missing_(value)
[docs]class MosaickingOrder(Enum):
"""Enum constant class for type of mosaicking order."""
MOST_RECENT = "mostRecent"
LEAST_RECENT = "leastRecent"
LEAST_CC = "leastCC"
[docs]class CRS(Enum, metaclass=CRSMeta):
"""Coordinate Reference System enumerate class
Available CRS constants are WGS84, POP_WEB (i.e. Popular Web Mercator) and constants in form UTM_<zone><direction>,
where zone is an integer from [1, 60] and direction is either N or S (i.e. northern or southern hemisphere)
"""
WGS84 = "4326"
POP_WEB = "3857"
#: UTM enum members are defined in CRSMeta.__new__
def __str__(self) -> str:
"""Method for casting CRS enum into string"""
return self.ogc_string()
def __repr__(self) -> str:
"""Method for retrieving CRS enum representation"""
return f"CRS('{self.value}')"
[docs] @classmethod
def has_value(cls, value: str) -> bool:
"""Tests whether CRS contains a constant defined with string `value`.
:param value: The string representation of the enum constant.
:return: `True` if there exists a constant with string value `value`, `False` otherwise
"""
return value in cls._value2member_map_
@property
def epsg(self) -> int:
"""EPSG code property
:return: EPSG code of given CRS
"""
return int(self.value)
[docs] def ogc_string(self) -> str:
"""Returns a string of the form authority:id representing the CRS.
:param self: An enum constant representing a coordinate reference system.
:return: A string representation of the CRS.
"""
return f"EPSG:{CRS(self).value}"
@property
def opengis_string(self) -> str:
"""Returns a URL to OGC webpage where the CRS is defined
:return: A URL with CRS definition
"""
return f"http://www.opengis.net/def/crs/EPSG/0/{self.epsg}"
[docs] def is_utm(self) -> bool:
"""Checks if crs is one of the 64 possible UTM coordinate reference systems.
:param self: An enum constant representing a coordinate reference system.
:return: `True` if crs is UTM and `False` otherwise
"""
return self.name.startswith("UTM")
[docs] @functools.lru_cache(maxsize=128)
def projection(self) -> pyproj.Proj:
"""Returns a projection in form of pyproj class.
For better time performance this method will cache `128` most recent results. Cache can be released with
`CRS.projection.cache_clear()`.
:return: pyproj projection class
"""
return pyproj.Proj(self._get_pyproj_projection_def(), preserve_units=True)
[docs] @functools.lru_cache(maxsize=128)
def pyproj_crs(self) -> pyproj.CRS:
"""Returns a pyproj CRS class.
For better time performance this method will cache `128` most recent results. Cache can be released with
`CRS.pyproj_crs.cache_clear()`.
:return: pyproj CRS class
"""
return pyproj.CRS(self._get_pyproj_projection_def())
[docs] @staticmethod
def get_utm_from_wgs84(lng: float, lat: float) -> CRS:
"""Convert from WGS84 to UTM coordinate system
:param lng: Longitude
:param lat: Latitude
:return: UTM coordinates
"""
_, _, zone, _ = utm.from_latlon(lat, lng)
direction = "N" if lat >= 0 else "S"
return CRS[f"UTM_{zone}{direction}"]
def _get_pyproj_projection_def(self) -> str:
"""Returns a pyproj crs definition
For WGS 84 it ensures lng-lat order
"""
return "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs" if self is CRS.WGS84 else self.ogc_string()
[docs]class MimeType(Enum):
"""Enum class to represent supported file formats
Supported file formats are TIFF 8-bit, TIFF 16-bit, TIFF 32-bit float, PNG, JPEG, JPEG2000, JSON, CSV, ZIP, HDF5,
XML, GML, RAW
"""
TIFF = "tiff"
PNG = "png"
JPG = "jpg"
JP2 = "jp2"
JSON = "json"
CSV = "csv"
ZIP = "zip"
HDF = "hdf"
XML = "xml"
GML = "gml"
TXT = "txt"
TAR = "tar"
RAW = "raw"
SAFE = "safe"
PICKLE = "pkl"
NPY = "npy"
GPKG = "gpkg"
GEOJSON = "geojson"
GZIP = "gz"
@property
def extension(self) -> str:
"""Returns file extension of the MimeType object
:returns: A file extension string
"""
return self.value
[docs] @staticmethod
def from_string(mime_type_str: str) -> MimeType:
"""Parses mime type from a file extension string
:param mime_type_str: A file extension string
:return: A mime type enum
"""
guessed_extension = mimetypes.guess_extension(mime_type_str)
if guessed_extension:
mime_type_str = guessed_extension.strip(".")
else:
mime_type_str = mime_type_str.split("/")[-1]
if MimeType.has_value(mime_type_str):
return MimeType(mime_type_str)
try:
return {"tif": MimeType.TIFF, "jpeg": MimeType.JPG, "hdf5": MimeType.HDF, "h5": MimeType.HDF}[mime_type_str]
except KeyError as exception:
raise ValueError(f"Data format {mime_type_str} is not supported") from exception
[docs] @classmethod
def has_value(cls, value: str) -> bool:
"""Tests whether MimeType contains a constant defined with string ``value``
:param value: The string representation of the enum constant
:return: `True` if there exists a constant with string value ``value``, `False` otherwise
"""
return value in cls._value2member_map_
[docs] def get_string(self) -> str:
"""Get file format as string
:return: String describing the file format
"""
if self is MimeType.JP2:
return "image/jpeg2000"
if self is MimeType.XML:
return "text/xml"
if self is MimeType.RAW:
return self.value
return mimetypes.types_map["." + self.value]
[docs] def matches_extension(self, path: str) -> bool:
"""Checks if mime type enum is used as the last file extension in given file path.
:param path: Path that might have an extension at the end.
:return: A boolean value indicating if the file path ends with the expected extension.
"""
return path.endswith(f".{self.extension}")
[docs] def get_expected_max_value(self) -> float | int:
"""Returns max value of image `MimeType` format and raises an error if it is not an image format
:return: A maximum value of specified image format
:raises: ValueError
"""
try:
return {MimeType.TIFF: 65535, MimeType.PNG: 255, MimeType.JPG: 255, MimeType.JP2: 10000}[self]
except KeyError as exception:
raise ValueError(f"Type {self} is not supported by this method") from exception
[docs]class RequestType(Enum):
"""Enum constant class for GET/POST request type."""
GET = "GET"
POST = "POST"
DELETE = "DELETE"
PUT = "PUT"
PATCH = "PATCH"
[docs]class SHConstants:
"""Common constants used in various requests."""
LATEST = "latest"
HEADERS: ClassVar[dict[str, str]] = {"User-Agent": f"sentinelhub-py/v{__version__}"}