Source code for geowombat.core.properties

import typing as T
from collections import namedtuple
from dataclasses import dataclass

import geopandas as gpd
import numpy as np
import pandas as pd
import shapely
from affine import Affine
from pyproj import CRS
from rasterio.coords import BoundingBox
from shapely.geometry import box

from .util import n_rows_cols

# TODO: convert to enumerated classes
WavelengthsPan = namedtuple('WavelengthsPan', 'pan')
WavelengthsBGR = namedtuple('WavelengthsBGR', 'blue green red')
WavelengthsRGB = namedtuple('WavelengthsRGB', 'red green blue')
WavelengthsBGRN = namedtuple('WavelengthsBGRN', 'blue green red nir')
WavelengthsRGBN = namedtuple('WavelengthsRGBN', 'red green blue nir')
WavelengthsL57 = namedtuple('WavelengthsL57', 'blue green red nir swir1 swir2')
WavelengthsL57Thermal = namedtuple(
    'WavelengthsL57Thermal', 'blue green red nir swir1 thermal swir2'
)
WavelengthsL57Pan = namedtuple(
    'WavelengthsL57Pan', 'blue green red nir swir1 swir2 pan'
)
WavelengthsL8 = namedtuple(
    'WavelengthsL8', 'coastal blue green red nir swir1 swir2 cirrus'
)
WavelengthsL8Thermal = namedtuple(
    'WavelengthsL8Thermal',
    'coastal blue green red nir swir1 swir2 cirrus tirs1 tirs2',
)
WavelengthsL9 = namedtuple(
    'WavelengthsL9', 'coastal blue green red nir swir1 swir2 cirrus'
)
WavelengthsL9Thermal = namedtuple(
    'WavelengthsL9Thermal',
    'coastal blue green red nir swir1 swir2 cirrus tirs1 tirs2',
)
WavelengthsS2 = namedtuple(
    'WavelengthsS2', 'blue green red nir1 nir2 nir3 nir rededge swir1 swir2'
)
WavelengthsS2Full = namedtuple(
    'WavelengthsS2',
    'coastal blue green red nir1 nir2 nir3 nir rededge water cirrus swir1 swir2',
)
WavelengthsS220 = namedtuple(
    'WavelengthsS220', 'nir1 nir2 nir3 rededge swir1 swir2'
)
WavelengthsS2Cloudless = namedtuple(
    'WavelengthsS2Cloudless',
    'coastal blue red nir1 nir rededge water cirrus swir1 swir2',
)
WavelengthsMODSR = namedtuple(
    'WavelengthsMODSR', 'red nir blue green nir2 swir1 swir2'
)


[docs]def get_sensor_info(key=None, sensor=None): altitude = dict( aster=705, l5=705, l7=705, l8=705, l9=705, s2=786, ps=475, qb=482, ik=681, wv3=617, ) solar_irradiance = dict( s2a=WavelengthsS2( blue=1941.63, green=1822.61, red=1512.79, nir1=1425.56, nir2=1288.32, nir3=1163.19, nir=1036.39, rededge=955.19, swir1=245.59, swir2=85.25, ), s2af=WavelengthsS2Full( coastal=1913.57, blue=1941.63, green=1822.61, red=1512.79, nir1=1425.56, nir2=1288.32, nir3=1163.19, nir=1036.39, rededge=955.19, water=813.04, cirrus=367.15, swir1=245.59, swir2=85.25, ), s2b=WavelengthsS2( blue=1959.75, green=1824.93, red=1512.79, nir1=1425.78, nir2=1291.13, nir3=1175.57, nir=1041.28, rededge=953.93, swir1=247.08, swir2=87.75, ), s2bf=WavelengthsS2Full( coastal=1874.3, blue=1959.75, green=1824.93, red=1512.79, nir1=1425.78, nir2=1291.13, nir3=1175.57, nir=1041.28, rededge=953.93, water=817.58, cirrus=365.41, swir1=247.08, swir2=87.75, ), s2c=WavelengthsS2( blue=1941.63, green=1822.61, red=1512.79, nir1=1425.56, nir2=1288.32, nir3=1163.19, nir=1036.39, rededge=955.19, swir1=245.59, swir2=85.25, ), s2cf=WavelengthsS2Full( coastal=1913.57, blue=1941.63, green=1822.61, red=1512.79, nir1=1425.56, nir2=1288.32, nir3=1163.19, nir=1036.39, rededge=955.19, water=813.04, cirrus=367.15, swir1=245.59, swir2=85.25, ), ) central_wavelength = dict( l5=WavelengthsL57( blue=0.485, green=0.56, red=0.66, nir=0.835, swir1=1.65, swir2=2.22 ), l7=WavelengthsL57( blue=0.485, green=0.56, red=0.66, nir=0.835, swir1=1.65, swir2=2.22 ), l7th=WavelengthsL57Thermal( blue=0.485, green=0.56, red=0.66, nir=0.835, swir1=1.65, thermal=11.45, swir2=2.22, ), l7mspan=WavelengthsL57Pan( blue=0.485, green=0.56, red=0.66, nir=0.835, swir1=1.65, swir2=2.22, pan=0.71, ), l7pan=WavelengthsPan(pan=0.71), l8=WavelengthsL8( coastal=0.44, blue=0.48, green=0.56, red=0.655, nir=0.865, swir1=1.61, swir2=2.2, cirrus=1.37, ), l9=WavelengthsL9( coastal=0.44, blue=0.48, green=0.56, red=0.655, nir=0.865, swir1=1.61, swir2=2.2, cirrus=1.37, ), l8l7=WavelengthsL57( blue=0.48, green=0.56, red=0.655, nir=0.865, swir1=1.61, swir2=2.2 ), l9l7=WavelengthsL57( blue=0.48, green=0.56, red=0.655, nir=0.865, swir1=1.61, swir2=2.2 ), l8l7mspan=WavelengthsL57Pan( blue=0.48, green=0.56, red=0.655, nir=0.865, swir1=1.61, swir2=2.2, pan=0.59, ), l9l7mspan=WavelengthsL57Pan( blue=0.48, green=0.56, red=0.655, nir=0.865, swir1=1.61, swir2=2.2, pan=0.59, ), l8pan=WavelengthsPan(pan=0.59), l9pan=WavelengthsPan(pan=0.59), l5bgrn=WavelengthsBGRN(blue=0.485, green=0.56, red=0.66, nir=0.835), l7bgrn=WavelengthsBGRN(blue=0.485, green=0.56, red=0.66, nir=0.835), l8bgrn=WavelengthsBGRN(blue=0.48, green=0.56, red=0.655, nir=0.865), l9bgrn=WavelengthsBGRN(blue=0.48, green=0.56, red=0.655, nir=0.865), s2=WavelengthsS2( blue=0.4924, green=0.5598, red=0.6646, nir1=0.7041, nir2=0.7405, nir3=0.7828, nir=0.8328, rededge=0.8647, swir1=1.6137, swir2=2.2024, ), s2a=WavelengthsS2( blue=0.4924, green=0.5598, red=0.6646, nir1=0.7041, nir2=0.7405, nir3=0.7828, nir=0.8328, rededge=0.8647, swir1=1.6137, swir2=2.2024, ), s2b=WavelengthsS2( blue=0.4921, green=0.559, red=0.665, nir1=0.7038, nir2=0.7391, nir3=0.7797, nir=0.833, rededge=0.864, swir1=1.6104, swir2=2.1857, ), s2f=WavelengthsS2Full( coastal=0.4427, blue=0.4924, green=0.5598, red=0.6646, nir1=0.7041, nir2=0.7405, nir3=0.7828, nir=0.8328, rededge=0.8647, water=0.9451, cirrus=1.3735, swir1=1.6137, swir2=2.2024, ), s2af=WavelengthsS2Full( coastal=0.4427, blue=0.4924, green=0.5598, red=0.6646, nir1=0.7041, nir2=0.7405, nir3=0.7828, nir=0.8328, rededge=0.8647, water=0.9451, cirrus=1.3735, swir1=1.6137, swir2=2.2024, ), s2bf=WavelengthsS2Full( coastal=0.4423, blue=0.4921, green=0.559, red=0.665, nir1=0.7038, nir2=0.7391, nir3=0.7797, nir=0.833, rededge=0.864, water=0.9432, cirrus=1.3769, swir1=1.6104, swir2=2.1857, ), s2l7=WavelengthsL57( blue=0.49, green=0.56, red=0.665, nir=0.842, swir1=1.61, swir2=2.19 ), s2al7=WavelengthsL57( blue=0.4924, green=0.5598, red=0.6646, nir=0.8328, swir1=1.6137, swir2=2.2024, ), s2bl7=WavelengthsL57( blue=0.4921, green=0.559, red=0.665, nir=0.833, swir1=1.6104, swir2=2.1857, ), s210=WavelengthsBGRN(blue=0.49, green=0.56, red=0.665, nir=0.842), s2a10=WavelengthsBGRN( blue=0.4924, green=0.5598, red=0.6646, nir=0.8328 ), s2b10=WavelengthsBGRN(blue=0.4921, green=0.559, red=0.665, nir=0.833), s220=WavelengthsS220( nir1=0.705, nir2=0.74, nir3=0.783, rededge=0.865, swir1=1.61, swir2=2.19, ), s2a20=WavelengthsS220( nir1=0.7041, nir2=0.7405, nir3=0.7828, rededge=0.8647, swir1=1.6137, swir2=2.2024, ), s2b20=WavelengthsS220( nir1=0.7038, nir2=0.7391, nir3=0.7797, rededge=0.864, swir1=1.6104, swir2=2.1857, ), s2cloudless=WavelengthsS2Cloudless( coastal=0.443, blue=0.49, red=0.665, nir1=0.705, nir=0.842, rededge=0.865, water=0.945, cirrus=1.375, swir1=1.61, swir2=2.19, ), s2acloudless=WavelengthsS2Cloudless( coastal=0.4427, blue=0.4924, red=0.6646, nir1=0.7041, nir=0.8328, rededge=0.8647, water=0.9451, cirrus=1.3735, swir1=1.6137, swir2=2.2024, ), s2bcloudless=WavelengthsS2Cloudless( coastal=0.4423, blue=0.4921, red=0.665, nir1=0.7038, nir=0.833, rededge=0.864, water=0.9432, cirrus=1.3769, swir1=1.6104, swir2=2.1857, ), ps=WavelengthsBGRN(blue=0.485, green=0.545, red=0.63, nir=0.82), ) name = dict( rgb='red, green, and blue', rgbn='red, green, blue, and NIR', bgr='blue, green, and red', bgrn='blue, green, red, and NIR', l5='Landsat 5 Thematic Mapper (TM)', l7='Landsat 7 Enhanced Thematic Mapper Plus (ETM+) without panchromatic and thermal bands', l7th='Landsat 7 Enhanced Thematic Mapper Plus (ETM+) with thermal band', l7mspan='Landsat 7 Enhanced Thematic Mapper Plus (ETM+) with panchromatic band', l7pan='Landsat 7 panchromatic band', l8='Landsat 8 Operational Land Imager (OLI) and Thermal Infrared Sensor (TIRS) without panchromatic and thermal bands', l8l7='Landsat 8 Operational Land Imager (OLI) and Thermal Infrared Sensor (TIRS) with 6 Landsat 7-like bands', l8l7mspan='Landsat 8 Operational Land Imager (OLI) and panchromatic band with 6 Landsat 7-like bands', l8th='Landsat 8 Operational Land Imager (OLI) and Thermal Infrared Sensor (TIRS) with thermal band', l8pan='Landsat 8 panchromatic band', l9='Landsat 9 Operational Land Imager (OLI) and Thermal Infrared Sensor (TIRS) without panchromatic and thermal bands', l9l7='Landsat 9 Operational Land Imager (OLI) and Thermal Infrared Sensor (TIRS) with 6 Landsat 7-like bands', l9l7mspan='Landsat 9 Operational Land Imager (OLI) and panchromatic band with 6 Landsat 7-like bands', l9th='Landsat 9 Operational Land Imager (OLI) and Thermal Infrared Sensor (TIRS) with thermal band', l9pan='Landsat 9 panchromatic band', s2='Sentinel 2 Multi-Spectral Instrument (MSI) without 3 60m bands (coastal, water vapor, cirrus)', s2a='Sentinel 2 Multi-Spectral Instrument (MSI) without 3 60m bands (coastal, water vapor, cirrus)', s2b='Sentinel 2 Multi-Spectral Instrument (MSI) without 3 60m bands (coastal, water vapor, cirrus)', s2f='Sentinel 2 Multi-Spectral Instrument (MSI) with 3 60m bands (coastal, water vapor, cirrus)', s2l7='Sentinel 2 Multi-Spectral Instrument (MSI) with 6 Landsat 7-like bands', s2al7='Sentinel 2 Multi-Spectral Instrument (MSI) with 6 Landsat 7-like bands', s2bl7='Sentinel 2 Multi-Spectral Instrument (MSI) with 6 Landsat 7-like bands', s210='Sentinel 2 Multi-Spectral Instrument (MSI) with 4 10m (visible + NIR) bands', s220='Sentinel 2 Multi-Spectral Instrument (MSI) with 6 20m bands', s2cloudless='Sentinel 2 Multi-Spectral Instrument (MSI) with 10 bands for s2cloudless', ps='PlanetScope with 4 (visible + NIR) bands', qb='Quickbird with 4 (visible + NIR) bands', ik='IKONOS with 4 (visible + NIR) bands', mcd43a4='MODIS Nadir BRDF-Adjusted Reflectance Daily 500m with 7 bands', ) wavelength = dict( rgb=WavelengthsRGB(red=1, green=2, blue=3), rgbn=WavelengthsRGBN(red=1, green=2, blue=3, nir=4), bgr=WavelengthsBGR(blue=1, green=2, red=3), bgrn=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), l5=WavelengthsL57(blue=1, green=2, red=3, nir=4, swir1=5, swir2=6), l7=WavelengthsL57(blue=1, green=2, red=3, nir=4, swir1=5, swir2=6), l7th=WavelengthsL57Thermal( blue=1, green=2, red=3, nir=4, swir1=5, thermal=6, swir2=7 ), l7mspan=WavelengthsL57Pan( blue=1, green=2, red=3, nir=4, swir1=5, swir2=6, pan=7 ), l7pan=WavelengthsPan(pan=1), l8=WavelengthsL8( coastal=1, blue=2, green=3, red=4, nir=5, swir1=6, swir2=7, cirrus=8, ), l9=WavelengthsL9( coastal=1, blue=2, green=3, red=4, nir=5, swir1=6, swir2=7, cirrus=8, ), l8th=WavelengthsL8Thermal( coastal=1, blue=2, green=3, red=4, nir=5, swir1=6, swir2=7, cirrus=8, tirs1=9, tirs2=10, ), l9th=WavelengthsL9Thermal( coastal=1, blue=2, green=3, red=4, nir=5, swir1=6, swir2=7, cirrus=8, tirs1=9, tirs2=10, ), l8l7=WavelengthsL57(blue=1, green=2, red=3, nir=4, swir1=5, swir2=6), l9l7=WavelengthsL57(blue=1, green=2, red=3, nir=4, swir1=5, swir2=6), l8l7mspan=WavelengthsL57Pan( blue=1, green=2, red=3, nir=4, swir1=5, swir2=6, pan=7 ), l9l7mspan=WavelengthsL57Pan( blue=1, green=2, red=3, nir=4, swir1=5, swir2=6, pan=7 ), l8pan=WavelengthsPan(pan=1), l9pan=WavelengthsPan(pan=1), l5bgrn=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), l7bgrn=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), l8bgrn=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), s2=WavelengthsS2( blue=1, green=2, red=3, nir1=4, nir2=5, nir3=6, nir=7, rededge=8, swir1=9, swir2=10, ), s2a=WavelengthsS2( blue=1, green=2, red=3, nir1=4, nir2=5, nir3=6, nir=7, rededge=8, swir1=9, swir2=10, ), s2b=WavelengthsS2( blue=1, green=2, red=3, nir1=4, nir2=5, nir3=6, nir=7, rededge=8, swir1=9, swir2=10, ), s2f=WavelengthsS2Full( coastal=1, blue=2, green=3, red=4, nir1=5, nir2=6, nir3=7, nir=8, rededge=9, water=10, cirrus=11, swir1=12, swir2=13, ), s2af=WavelengthsS2Full( coastal=1, blue=2, green=3, red=4, nir1=5, nir2=6, nir3=7, nir=8, rededge=9, water=10, cirrus=11, swir1=12, swir2=13, ), s2bf=WavelengthsS2Full( coastal=1, blue=2, green=3, red=4, nir1=5, nir2=6, nir3=7, nir=8, rededge=9, water=10, cirrus=11, swir1=12, swir2=13, ), s2l7=WavelengthsL57(blue=1, green=2, red=3, nir=4, swir1=5, swir2=6), s2al7=WavelengthsL57(blue=1, green=2, red=3, nir=4, swir1=5, swir2=6), s2bl7=WavelengthsL57(blue=1, green=2, red=3, nir=4, swir1=5, swir2=6), s210=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), s2a10=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), s2b10=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), s220=WavelengthsS220( nir1=1, nir2=2, nir3=3, rededge=4, swir1=5, swir2=6 ), s2a20=WavelengthsS220( nir1=1, nir2=2, nir3=3, rededge=4, swir1=5, swir2=6 ), s2b20=WavelengthsS220( nir1=1, nir2=2, nir3=3, rededge=4, swir1=5, swir2=6 ), s2cloudless=dict( coastal=1, blue=2, red=3, nir1=4, nir=5, rededge=6, water=7, cirrus=8, swir1=9, swir2=10, ), s2acloudless=dict( coastal=1, blue=2, red=3, nir1=4, nir=5, rededge=6, water=7, cirrus=8, swir1=9, swir2=10, ), s2bcloudless=dict( coastal=1, blue=2, red=3, nir1=4, nir=5, rededge=6, water=7, cirrus=8, swir1=9, swir2=10, ), ps=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), qb=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), ik=WavelengthsBGRN(blue=1, green=2, red=3, nir=4), mcd43a4=WavelengthsMODSR( red=1, nir=2, blue=3, green=4, nir2=5, swir1=6, swir2=7 ), mod09a1=WavelengthsMODSR( red=1, nir=2, blue=3, green=4, nir2=5, swir1=6, swir2=7 ), myd09a1=WavelengthsMODSR( red=1, nir=2, blue=3, green=4, nir2=5, swir1=6, swir2=7 ), ) sensor_info = { 'altitude': altitude, 'solar_irradiance': solar_irradiance, 'central_wavelength': central_wavelength, 'name': name, 'wavelength': wavelength, } if not key and sensor: raise NameError('The key sensor must be given with the key.') elif key and sensor: return sensor_info[key][sensor] elif key and not sensor: return sensor_info[key] else: return sensor_info
[docs]@dataclass class Metadata: left: float bottom: float right: float top: float bounds: T.Tuple[float, float, float, float] affine: Affine geometry: box
[docs]class DataProperties(object): @property def avail_sensors(self) -> list: """Get supported sensors.""" return sorted(list(self.wavelengths.keys())) @property def altitude(self): """Get satellite altitudes (in km)""" return get_sensor_info(key='altitude') @property def central_um(self): """Get a dictionary of central wavelengths (in micrometers)""" return get_sensor_info(key='central_wavelength') @property def sensor_names(self): """Get sensor full names.""" return get_sensor_info(key='name') @property def wavelengths(self): """Get a dictionary of sensor wavelengths.""" return get_sensor_info(key='wavelength') @property def array_is_dask(self) -> bool: """Get whether the array is a Dask array.""" return False if isinstance(self._obj.data, np.ndarray) else True @property def ndims(self) -> int: """Get the number of array dimensions.""" return len(self._obj.shape) @property def row_chunks(self) -> int: """Get the row chunk size.""" return self._obj.data.chunksize[-2] @property def col_chunks(self) -> int: """Get the column chunk size.""" return self._obj.data.chunksize[-1] @property def band_chunks(self) -> int: """Get the band chunk size.""" if self.ndims > 2: return self._obj.data.chunksize[-3] else: return 1 @property def time_chunks(self) -> int: """Get the time chunk size.""" if self.ndims > 3: return self._obj.data.chunksize[-4] else: return 1 @property def ntime(self) -> int: """Get the number of time dimensions.""" if self.ndims > 3: return self._obj.shape[-4] else: return 1 @property def nbands(self) -> int: """Get the number of array bands.""" if self.ndims > 2: return self._obj.shape[-3] else: return 1 @property def nrows(self) -> int: """Get the number of array rows.""" return self._obj.shape[-2] @property def ncols(self) -> int: """Get the number of array columns.""" return self._obj.shape[-1] @property def left(self) -> float: """Get the array bounding box left coordinate. Pixel shift reference: https://github.com/pydata/xarray/blob/master/xarray/backends/rasterio_.py http://web.archive.org/web/20160326194152/http://remotesensing.org/geotiff/spec/geotiff2.5.html#2.5.2 """ return float(self._obj.x.min().values) - self.cellxh @property def right(self) -> float: """Get the array bounding box right coordinate.""" return float(self._obj.x.max().values) + self.cellxh @property def top(self) -> float: """Get the array bounding box top coordinate.""" return float(self._obj.y.max().values) + self.cellyh @property def bottom(self) -> float: """Get the array bounding box bottom coordinate.""" return float(self._obj.y.min().values) - self.cellyh @property def bounds(self) -> T.Tuple[float, float, float, float]: """Get the array bounding box (left, bottom, right, top)""" return self.left, self.bottom, self.right, self.top @property def bounds_as_namedtuple(self) -> BoundingBox: """Get the array bounding box as a ``rasterio.coords.BoundingBox``""" return BoundingBox( left=self.left, bottom=self.bottom, right=self.right, top=self.top ) @property def celly(self) -> float: """Get the cell size in the y direction.""" return self._obj.res[1] @property def cellx(self) -> float: """Get the cell size in the x direction.""" return self._obj.res[0] @property def cellyh(self) -> float: """Get the half width of the cell size in the y direction.""" return self.celly / 2.0 @property def cellxh(self) -> float: """Get the half width of the cell size in the x direction.""" return self.cellx / 2.0 @property def crs_to_pyproj(self) -> CRS: """Get the CRS as a ``pyproj.CRS`` object.""" return CRS.from_user_input(self._obj.crs) @property def pydatetime(self): """Get Python datetime objects from the time dimension.""" return pd.to_datetime(self._obj.time.values).to_pydatetime() @property def dtype(self) -> str: """Get the data type of the DataArray.""" return ( self._obj.dtype.name if isinstance(self._obj.dtype, np.dtype) else self._obj.dtype ) @property def chunk_grid(self) -> gpd.GeoDataFrame: """Get the image chunk grid.""" geometries = [] top = self.top for i in range(0, self.nrows, self.row_chunks): i_csize = n_rows_cols(i, self.row_chunks, self.nrows) bottom = top - (i_csize * abs(self.celly)) left = self.left for j in range(0, self.ncols, self.col_chunks): j_csize = n_rows_cols(j, self.col_chunks, self.ncols) right = left + (j_csize * abs(self.cellx)) geom = box(left, bottom, right, top) geometries.append(geom) left += self.col_chunks * abs(self.cellx) top -= self.row_chunks * abs(self.celly) return gpd.GeoDataFrame( data=list(range(1, len(geometries) + 1)), columns=['chunk'], geometry=geometries, crs=self._obj.crs, ) @property def footprint_grid(self) -> gpd.GeoDataFrame: """Get the image footprint grid.""" if not hasattr(self._obj, '_filenames'): raise AttributeError( "The DataArray object does not have a 'filenames' attribute." ) import geowombat as gw_ geometries = [] if hasattr(self._obj, 'geometries'): if len(self._obj.geometries) == len(self._obj.gw.filenames): geometries = self._obj.geometries if not geometries: for fn in self._obj.gw.filenames: kwargs = ( {'engine': 'h5netcdf'} if fn.lower().endswith('.nc') else {} ) with gw_.open( fn, chunks={ 'band': self._obj.gw.band_chunks, 'y': self._obj.gw.row_chunks, 'x': self._obj.gw.col_chunks, }, **kwargs ) as src: geometries.append(src.gw.geometry) return gpd.GeoDataFrame( data=self._obj.gw.filenames, columns=['footprint'], geometry=geometries, crs=self._obj.crs, ) @property def geometry(self) -> box: """Get the polygon geometry of the array bounding box.""" return box(self.left, self.bottom, self.right, self.top) @property def has_band_coord(self) -> bool: """Check whether the DataArray has a band coordinate.""" return True if 'band' in self._obj.coords else False @property def has_band_dim(self) -> bool: """Check whether the DataArray has a band dimension.""" return True if 'band' in self._obj.dims else False @property def has_band(self) -> bool: """Check whether the DataArray has a band attribute.""" return self.has_band_coord and self.has_band_dim @property def has_time_coord(self) -> bool: """Check whether the DataArray has a time coordinate.""" return True if 'time' in self._obj.coords else False @property def has_time_dim(self) -> bool: """Check whether the DataArray has a time dimension.""" return True if 'time' in self._obj.dims else False @property def has_time(self) -> bool: """Check whether the DataArray has a time attribute.""" return self.has_time_coord and self.has_time_dim @property def geodataframe(self) -> gpd.GeoDataFrame: """Get a ``geopandas.GeoDataFrame`` of the array bounds.""" return gpd.GeoDataFrame( data=[1], columns=['grid'], geometry=[self.geometry], crs=self._obj.crs, ) @property def unary_union(self): """Get a representation of the union of the image bounds.""" return shapely.ops.unary_union(self.geometry) @property def transform(self) -> T.Tuple[float, float, float, float, float, float]: """Get the data transform (cell x, 0, left, 0, cell y, top)""" return self.cellx, 0.0, self.left, 0.0, -self.celly, self.top @property def affine(self) -> Affine: """Get the affine transform object.""" return Affine(*self.transform) @property def meta(self) -> Metadata: """Get the array metadata.""" return Metadata( left=self.left, bottom=self.bottom, right=self.right, top=self.top, bounds=self.bounds, affine=self.affine, geometry=self.geometry, ) @property def nodataval(self) -> T.Union[float, int, None]: """Get the 'no data' value from the attributes.""" nodata_value = None if hasattr(self._obj, '_FillValue'): nodata_value = self._obj.attrs['_FillValue'] elif hasattr(self._obj, 'nodatavals'): nodata_value = self._obj.attrs['nodatavals'][0] return nodata_value @property def scaleval(self) -> T.Union[float, int]: """Get the scale factor value.""" scale_value = 1.0 if hasattr(self._obj, 'scales'): scale_value = self._obj.attrs['scales'][0] return scale_value @property def offsetval(self) -> T.Union[float, int]: """Get the offset value.""" offset_value = 0 if hasattr(self._obj, 'offsets'): offset_value = self._obj.attrs['offsets'][0] return offset_value