Commit 3d5179f7 authored by Daniel Scheffler's avatar Daniel Scheffler

Merge branch 'enhancement/improve_nodata_value_handling' into 'master'

Enhancement/improve nodata value handling

See merge request !3
parents 8d362c57 f930d2bc
Pipeline #3923 passed with stages
in 1 minute and 24 seconds
...@@ -52,14 +52,17 @@ __author__ = 'Daniel Scheffler' ...@@ -52,14 +52,17 @@ __author__ = 'Daniel Scheffler'
class GeoArray(object): class GeoArray(object):
"""
This class creates a fast Python interface for geodata - either on disk or in memory. It can be instanced
with a file path or with a numpy array and the corresponding geoinformation. Instances can always be indexed
like normal numpy arrays, no matter if GeoArray has been instanced from file or from an in-memory array.
GeoArray provides a wide range of geo-related attributes belonging to the dataset as well as some functions for
quickly visualizing the data as a map, a simple image or an interactive image.
"""
def __init__(self, path_or_array, geotransform=None, projection=None, bandnames=None, nodata=None, progress=True, def __init__(self, path_or_array, geotransform=None, projection=None, bandnames=None, nodata=None, progress=True,
q=False): q=False):
# type: (Union[str, np.ndarray], tuple, str, list, float, bool, bool) -> None # type: (Union[str, np.ndarray], tuple, str, list, float, bool, bool) -> None
"""This class creates a fast Python interface for geodata - either on disk or in memory. It can be instanced with """Get an instance of GeoArray.
a file path or with a numpy array and the corresponding geoinformation. Instances can always be indexed like
normal numpy arrays, no matter if GeoArray has been instanced from file or from an in-memory array. GeoArray
provides a wide range of geo-related attributes belonging to the dataset as well as some functions for quickly
visualizing the data as a map, a simple image or an interactive image.
:param path_or_array: a numpy.ndarray or a valid file path :param path_or_array: a numpy.ndarray or a valid file path
:param geotransform: GDAL geotransform of the given array or file on disk :param geotransform: GDAL geotransform of the given array or file on disk
...@@ -354,7 +357,7 @@ class GeoArray(object): ...@@ -354,7 +357,7 @@ class GeoArray(object):
if not self.is_inmem: if not self.is_inmem:
self.set_gdalDataset_meta() self.set_gdalDataset_meta()
if self._nodata is None: if self._nodata is None:
self._nodata = self.find_noDataVal() self.find_noDataVal()
if self._nodata == 'ambiguous': if self._nodata == 'ambiguous':
warnings.warn('Nodata value could not be clearly identified. It has been set to None.') warnings.warn('Nodata value could not be clearly identified. It has been set to None.')
self._nodata = None self._nodata = None
...@@ -369,6 +372,9 @@ class GeoArray(object): ...@@ -369,6 +372,9 @@ class GeoArray(object):
# type: (Union[int, None]) -> None # type: (Union[int, None]) -> None
self._nodata = value self._nodata = value
if self._metadata and value is not None:
self.metadata.global_meta.update({'data ignore value': str(value)})
@property @property
def mask_nodata(self): def mask_nodata(self):
""" """
...@@ -515,7 +521,7 @@ class GeoArray(object): ...@@ -515,7 +521,7 @@ class GeoArray(object):
if self._metadata is not None: if self._metadata is not None:
return self._metadata return self._metadata
else: else:
default = GDAL_Metadata(nbands=self.bands) default = GDAL_Metadata(nbands=self.bands, nodata_allbands=self._nodata)
self._metadata = default self._metadata = default
if not self.is_inmem: if not self.is_inmem:
...@@ -705,6 +711,7 @@ class GeoArray(object): ...@@ -705,6 +711,7 @@ class GeoArray(object):
else: else:
nodata = None nodata = None
self.nodata = nodata
return nodata return nodata
def set_gdalDataset_meta(self): def set_gdalDataset_meta(self):
...@@ -736,12 +743,12 @@ class GeoArray(object): ...@@ -736,12 +743,12 @@ class GeoArray(object):
if 'nodata' not in self._initParams or self._initParams['nodata'] is None: if 'nodata' not in self._initParams or self._initParams['nodata'] is None:
band = ds.GetRasterBand(1) band = ds.GetRasterBand(1)
# FIXME this does not support different nodata values within the same file # FIXME this does not support different nodata values within the same file
self._nodata = band.GetNoDataValue() self.nodata = band.GetNoDataValue()
# set metadata attribute # set metadata attribute
if self.is_inmem or not self.filePath: if self.is_inmem or not self.filePath:
# metadata cannot be read from disk -> set it to the default # metadata cannot be read from disk -> set it to the default
self._metadata = GDAL_Metadata(nbands=self.bands) self._metadata = GDAL_Metadata(nbands=self.bands, nodata_allbands=self._nodata)
else: else:
self._metadata = GDAL_Metadata(filePath=self.filePath) self._metadata = GDAL_Metadata(filePath=self.filePath)
...@@ -977,27 +984,27 @@ class GeoArray(object): ...@@ -977,27 +984,27 @@ class GeoArray(object):
gdal.Unlink(out_path + '.aux.xml') gdal.Unlink(out_path + '.aux.xml')
elif self.metadata.all_meta: elif self.metadata.all_meta:
# set global domain metadata # set global domain metadata
if self.metadata.global_meta: if self.metadata.global_meta:
ds_out.SetMetadata(dict((k, repr(v)) for k, v in self.metadata.global_meta.items())) ds_out.SetMetadata(dict((k, repr(v)) for k, v in self.metadata.global_meta.items()))
if 'description' in envi_metadict: if 'description' in envi_metadict:
ds_out.SetDescription(envi_metadict['description']) ds_out.SetDescription(envi_metadict['description'])
# set band domain metadata # set band domain metadata
bandmeta_dict = self.metadata.to_DataFrame().astype(str).to_dict() bandmeta_dict = self.metadata.to_DataFrame().astype(str).to_dict()
for bidx in range(self.bands): for bidx in range(self.bands):
band = ds_out.GetRasterBand(bidx + 1) band = ds_out.GetRasterBand(bidx + 1)
bandmeta = bandmeta_dict[bidx] bandmeta = bandmeta_dict[bidx]
# meta2write = dict((k, repr(v)) for k, v in self.metadata.band_meta.items() if v is not np.nan) # meta2write = dict((k, repr(v)) for k, v in self.metadata.band_meta.items() if v is not np.nan)
band.SetMetadata(bandmeta) band.SetMetadata(bandmeta)
if 'band_names' in envi_metadict: if 'band_names' in envi_metadict:
band.SetDescription(self.metadata.band_meta['band_names'][bidx].strip()) band.SetDescription(self.metadata.band_meta['band_names'][bidx].strip())
band.FlushCache() band.FlushCache()
del band del band
ds_out.FlushCache() ds_out.FlushCache()
del ds_out del ds_out
...@@ -1507,7 +1514,7 @@ class GeoArray(object): ...@@ -1507,7 +1514,7 @@ class GeoArray(object):
def get_subset(self, xslice=None, yslice=None, zslice=None, return_GeoArray=True, def get_subset(self, xslice=None, yslice=None, zslice=None, return_GeoArray=True,
reset_bandnames=False): reset_bandnames=False):
# type: (slice, slice, slice, bool, bool) -> GeoArray # type: (slice, slice, slice, bool, bool) -> GeoArray
"""Returns a new instance of GeoArray representing a subset of the initial one wit respect to given array position. """Return a new GeoArray instance representing a subset of the initial one wit respect to given array position.
:param xslice: a slice providing the X-position for the subset in the form slice(xstart, xend, xstep) :param xslice: a slice providing the X-position for the subset in the form slice(xstart, xend, xstep)
:param yslice: a slice providing the Y-position for the subset in the form slice(ystart, yend, ystep) :param yslice: a slice providing the Y-position for the subset in the form slice(ystart, yend, ystep)
......
...@@ -4,6 +4,7 @@ import os ...@@ -4,6 +4,7 @@ import os
from pprint import pformat from pprint import pformat
from copy import deepcopy from copy import deepcopy
from typing import Union # noqa F401 # flake8 issue from typing import Union # noqa F401 # flake8 issue
from collections import OrderedDict
from geopandas import GeoDataFrame, GeoSeries from geopandas import GeoDataFrame, GeoSeries
import numpy as np import numpy as np
...@@ -27,18 +28,22 @@ autohandled_meta = [ ...@@ -27,18 +28,22 @@ autohandled_meta = [
class GDAL_Metadata(object): class GDAL_Metadata(object):
def __init__(self, filePath='', nbands=1): def __init__(self, filePath='', nbands=1, nodata_allbands=None):
# privates # privates
self._global_meta = dict() self._global_meta = OrderedDict()
self._band_meta = dict() self._band_meta = OrderedDict()
self.bands = nbands self.bands = nbands
self.filePath = filePath self.filePath = filePath
self.fileFormat = '' self.fileFormat = ''
self.nodata_allbands = nodata_allbands
if filePath: if filePath:
self.read_from_file(filePath) self.read_from_file(filePath)
if nodata_allbands is not None:
self.global_meta.update({'data ignore value': str(nodata_allbands)})
@classmethod @classmethod
def from_file(cls, filePath): def from_file(cls, filePath):
return GDAL_Metadata(filePath=filePath) return GDAL_Metadata(filePath=filePath)
...@@ -73,8 +78,8 @@ class GDAL_Metadata(object): ...@@ -73,8 +78,8 @@ class GDAL_Metadata(object):
@band_meta.setter @band_meta.setter
def band_meta(self, meta_dict): def band_meta(self, meta_dict):
if not isinstance(meta_dict, dict): if not isinstance(meta_dict, (dict, OrderedDict)):
raise TypeError("Expected type 'dict', received '%s'." % type(meta_dict)) raise TypeError("Expected type 'dict'/'OrderedDict', received '%s'." % type(meta_dict))
for k, v in meta_dict.items(): for k, v in meta_dict.items():
if not isinstance(v, list): if not isinstance(v, list):
...@@ -83,11 +88,11 @@ class GDAL_Metadata(object): ...@@ -83,11 +88,11 @@ class GDAL_Metadata(object):
raise ValueError("The length of the given lists must be equal to the number of bands. " raise ValueError("The length of the given lists must be equal to the number of bands. "
"Received a list with %d items for '%s'." % (len(v), k)) "Received a list with %d items for '%s'." % (len(v), k))
self._band_meta = meta_dict # TODO convert strings to useful types self._band_meta = OrderedDict(meta_dict) # TODO convert strings to useful types
@property @property
def all_meta(self): def all_meta(self):
all_meta = self.global_meta.copy() all_meta = OrderedDict(self.global_meta.copy())
all_meta.update(self.band_meta) all_meta.update(self.band_meta)
return all_meta return all_meta
...@@ -190,8 +195,8 @@ class GDAL_Metadata(object): ...@@ -190,8 +195,8 @@ class GDAL_Metadata(object):
return 'Metadata: \n\n' + pformat(self.all_meta) return 'Metadata: \n\n' + pformat(self.all_meta)
def to_ENVI_metadict(self): def to_ENVI_metadict(self):
return dict(zip(self.all_meta.keys(), return OrderedDict(zip(self.all_meta.keys(),
[self._convert_param_to_ENVI_str(i) for i in self.all_meta.values()])) [self._convert_param_to_ENVI_str(i) for i in self.all_meta.values()]))
def get_subset(self, bands2extract=None, keys2extract=None): def get_subset(self, bands2extract=None, keys2extract=None):
# type: (Union[slice, list, np.ndarray], Union[str, list]) -> 'GDAL_Metadata' # type: (Union[slice, list, np.ndarray], Union[str, list]) -> 'GDAL_Metadata'
......
__version__ = '0.8.13' __version__ = '0.8.14'
__versionalias__ = '20190329.02' __versionalias__ = '20190329.03'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment