Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Daniel Scheffler
geoarray
Commits
1aff6e33
Commit
1aff6e33
authored
Jun 01, 2021
by
Daniel Scheffler
Browse files
Revised some docstrings and fixed a lot of docstyle issues.
Signed-off-by:
Daniel Scheffler
<
danschef@gfz-potsdam.de
>
parent
594b712d
Pipeline
#23798
passed with stages
in 15 minutes
Changes
2
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
geoarray/baseclasses.py
View file @
1aff6e33
...
...
@@ -56,13 +56,14 @@ __author__ = 'Daniel Scheffler'
class
GeoArray
(
object
):
"""A class providing a fast Python interface for geodata - either on disk or in memory.
GeoArray can be instanced with a file path or with a numpy array and the corresponding geoinformation. Instances
can always be indexed and sliced like normal numpy arrays, no matter if it 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.
"""
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
,
q
=
False
):
# type: (Union[str, np.ndarray, GeoArray], tuple, str, list, float, bool, bool) -> None
...
...
@@ -78,7 +79,6 @@ class GeoArray(object):
:param progress: show progress bars (default: True)
:param q: quiet mode (default: False)
"""
# TODO implement compatibility to GDAL VRTs
if
not
(
isinstance
(
path_or_array
,
(
str
,
np
.
ndarray
,
GeoArray
))
or
issubclass
(
getattr
(
path_or_array
,
'__class__'
),
GeoArray
)):
raise
ValueError
(
"%s parameter 'arg' takes only string, np.ndarray or GeoArray(and subclass) instances. "
...
...
@@ -241,7 +241,6 @@ class GeoArray(object):
@
property
def
columns
(
self
):
"""Get the number of columns of the associated image array."""
return
self
.
shape
[
1
]
cols
=
alias_property
(
'columns'
)
...
...
@@ -264,7 +263,7 @@ class GeoArray(object):
@
property
def
geotransform
(
self
):
"""Get the GDAL GeoTransform of the associated image, e.g., (283500.0, 5.0, 0.0, 4464500.0, 0.0, -5.0)"""
"""Get the GDAL GeoTransform of the associated image, e.g., (283500.0, 5.0, 0.0, 4464500.0, 0.0, -5.0)
.
"""
if
self
.
_geotransform
:
return
self
.
_geotransform
elif
not
self
.
is_inmem
:
...
...
@@ -298,21 +297,21 @@ class GeoArray(object):
@
property
def
xygrid_specs
(
self
):
"""
Get the specifications for the X/Y coordinate grid, e.g. [[15,30], [0,30]] for a coordinate with its origin
at X/Y[15,0] and a GSD of X/Y[15,30].
"""
"""Get the specifications for the X/Y coordinate grid.
This returns for example [[15,30], [0,30]] for a coordinate
with its origin at X/Y[15,0] and a GSD of X/Y[15,30].
"""
def
get_grid
(
gt
,
xgsd
,
ygsd
):
return
[[
gt
[
0
],
gt
[
0
]
+
xgsd
],
[
gt
[
3
],
gt
[
3
]
-
ygsd
]]
return
get_grid
(
self
.
geotransform
,
self
.
xgsd
,
self
.
ygsd
)
@
property
def
projection
(
self
):
"""
Get the projection of the associated image. Setting the projection is only allowed if GeoArray has been
instanced from memory or the associated file on disk has no projection.
"""
"""Get the projection of the associated image.
Setting the projection is only allowed if GeoArray has been instanced from memory or the associated file on
disk has no projection.
"""
if
self
.
_projection
:
return
self
.
_projection
elif
not
self
.
is_inmem
:
...
...
@@ -347,19 +346,17 @@ class GeoArray(object):
@
property
def
is_map_geo
(
self
):
# type: () -> bool
"""
Return 'True' if the GeoArray instance has a valid geoinformation with map instead of image coordinates.
"""
"""Return 'True' if the image has a valid geoinformation with map instead of image coordinates."""
return
all
([
self
.
gt
,
list
(
self
.
gt
)
!=
[
0
,
1
,
0
,
0
,
0
,
-
1
],
self
.
prj
])
@
property
def
nodata
(
self
):
"""
Get the nodata value of the GeoArray. If GeoArray has been instanced with a file path the file is checked
for an existing nodata value. Otherwise (if no value is exlicitly given during object instanciation) the nodata
value is tried to be automatically detected.
"""
"""Get the nodata value of the GeoArray instance.
If GeoArray has been instanced with a file path the metadata of the file on disk is checked for an existing
nodata value. Otherwise (if no value is exlicitly given during object instanciation) an automatic detection
based on 3x3 windows at each image corner is run that analyzes the mean and standard deviation of these windows.
"""
if
self
.
_nodata
is
not
None
:
return
self
.
_nodata
else
:
...
...
@@ -387,10 +384,7 @@ class GeoArray(object):
@
property
def
mask_nodata
(
self
):
"""
Get the nodata mask of the associated image array. It is calculated using all image bands.
"""
"""Get the nodata mask of the associated image array. It is generated based on all image bands."""
if
self
.
_mask_nodata
is
not
None
:
return
self
.
_mask_nodata
else
:
...
...
@@ -399,11 +393,10 @@ class GeoArray(object):
@
mask_nodata
.
setter
def
mask_nodata
(
self
,
mask
):
"""Set bad data mask.
"""Set
the
bad data mask.
:param mask: Can be a file path, a numpy array or an instance o GeoArray.
"""
if
mask
is
not
None
:
from
.masks
import
NoDataMask
geoArr_mask
=
NoDataMask
(
mask
,
progress
=
self
.
progress
,
q
=
self
.
q
)
...
...
@@ -432,11 +425,10 @@ class GeoArray(object):
@
property
def
mask_baddata
(
self
):
"""
Returns the bad data mask for the associated image array if it has been explicitly previously. It can be set
by passing a file path, a numpy array or an instance of GeoArray to the setter of this property.
"""
"""Return the bad data mask.
Note: The mask must be explicitly set to a file path or a numpy array before.
"""
return
self
.
_mask_baddata
@
mask_baddata
.
setter
...
...
@@ -445,7 +437,6 @@ class GeoArray(object):
:param mask: Can be a file path, a numpy array or an instance o GeoArray.
"""
if
mask
is
not
None
:
from
.masks
import
BadDataMask
geoArr_mask
=
BadDataMask
(
mask
,
progress
=
self
.
progress
,
q
=
self
.
q
)
...
...
@@ -474,11 +465,8 @@ class GeoArray(object):
@
property
def
footprint_poly
(
self
):
"""Get the footprint polygon of the associated image array (shapely.geometry.Polygon)."""
# FIXME should return polygon in image coordinates if no projection is available
"""
Get the footprint polygon of the associated image array (returns an instance of shapely.geometry.Polygon.
"""
if
self
.
_footprint_poly
is
None
:
assert
self
.
mask_nodata
is
not
None
,
'A nodata mask is needed for calculating the footprint polygon. '
if
False
not
in
self
.
mask_nodata
[:]:
...
...
@@ -519,15 +507,14 @@ class GeoArray(object):
@
property
def
metadata
(
self
):
"""
Returns a DataFrame containing all available metadata (read from file if available).
"""
Return a DataFrame containing all available metadata (read from file if available).
Use 'metadata[band_index].to_dict()' to get a metadata dictionary for a specific band.
Use 'metadata.loc[row_name].to_dict()' to get all metadata values of the same key for all bands as dictionary.
Use 'metadata.loc[row_name, band_index] = value' to set a new value.
:return: pandas.DataFrame
"""
if
self
.
_metadata
is
not
None
:
return
self
.
_metadata
else
:
...
...
@@ -624,13 +611,11 @@ class GeoArray(object):
return
self
.
from_path
(
self
.
arg
,
getitem_params
)
def
__setitem__
(
self
,
idx
,
array2set
):
"""Overwrite
s
the pixel values of GeoArray.arr with the given array.
"""Overwrite the pixel values of GeoArray.arr with the given array.
:param idx: <int, list, slice> the index position to overwrite
:param array2set: <np.ndarray> array to be set. Must be compatible to the given index position.
:return:
"""
if
self
.
is_inmem
:
self
.
arr
[
idx
]
=
array2set
else
:
...
...
@@ -653,24 +638,23 @@ class GeoArray(object):
raise
AttributeError
(
"%s object has no attribute '%s'."
%
(
self
.
__class__
.
__name__
,
attr
))
def
__getstate__
(
self
):
"""Defines how the attributes of GMS object are pickled."""
"""Define how the attributes of the GeoArray instance are pickled (e.g., by multiprocessing.Pool)."""
# clean array cache in order to avoid cache pickling
self
.
flush_cache
()
return
self
.
__dict__
def
__setstate__
(
self
,
state
):
"""Defines how the attributes of GMS object are unpickled.
"""Define how the attributes of the GeoArray instance are unpickled (e.g., by multiprocessing.Pool).
NOTE: This method has been implemented because otherwise pickled and unpickled instances show recursion errors
within __getattr__ when requesting any attribute.
"""
self
.
__dict__
=
state
def
calc_mask_nodata
(
self
,
fromBand
=
None
,
overwrite
=
False
,
flag
=
'all'
):
# type: (int, bool, str) -> np.ndarray
"""Calculate
s
a no data mask with values False (=nodata) and True (=data).
"""Calculate a no data mask with values False (=nodata) and True (=data).
:param fromBand: index of the band to be used (if None, all bands are used)
:param overwrite: whether to overwrite existing nodata mask that has already been calculated
...
...
@@ -740,7 +724,8 @@ class GeoArray(object):
return
mask
def
find_noDataVal
(
self
,
bandIdx
=
0
,
sz
=
3
):
"""Tries to derive no data value from homogenious corner pixels within 3x3 windows (by default).
"""Try to derive no data value from homogenious corner pixels within 3x3 windows (by default).
:param bandIdx:
:param sz: window size in which corner pixels are analysed
"""
...
...
@@ -775,12 +760,11 @@ class GeoArray(object):
return
nodata
def
set_gdalDataset_meta
(
self
):
"""Retrieves GDAL metadata from file. This function is only executed once to avoid overwriting of user defined
attributes, that are defined after object instanciation.
"""Retrieve GDAL metadata from file.
:return:
This is only executed once to avoid overwriting of user defined attributes,
that are defined after object instanciation.
"""
if
not
self
.
_gdalDataset_meta_already_set
:
assert
self
.
filePath
ds
=
gdal
.
Open
(
self
.
filePath
)
...
...
@@ -824,6 +808,7 @@ class GeoArray(object):
def
from_path
(
self
,
path
,
getitem_params
=
None
):
# type: (str, list) -> np.ndarray
"""Read a GDAL compatible raster image from disk, with respect to the given image position.
NOTE: If the requested array position is already in cache, it is returned from there.
:param path: <str> the file path of the image to read
...
...
@@ -996,7 +981,6 @@ class GeoArray(object):
:param creationOptions: <list> GDAL creation options,
e.g., ["QUALITY=80", "REVERSIBLE=YES", "WRITE_METADATA=YES"]
"""
if
not
self
.
q
:
print
(
'Writing GeoArray of size %s to %s.'
%
(
self
.
shape
,
out_path
))
assert
self
.
ndim
in
[
2
,
3
],
'Only 2D- or 3D arrays are supported.'
...
...
@@ -1137,7 +1121,6 @@ class GeoArray(object):
def
dump
(
self
,
out_path
):
# type: (str) -> None
"""Serialize the whole object instance to disk using dill."""
import
dill
with
open
(
out_path
,
'wb'
)
as
outF
:
dill
.
dump
(
self
,
outF
)
...
...
@@ -1227,7 +1210,7 @@ class GeoArray(object):
def
show
(
self
,
xlim
=
None
,
ylim
=
None
,
band
=
None
,
boundsMap
=
None
,
boundsMapPrj
=
None
,
figsize
=
None
,
interpolation
=
'none'
,
vmin
=
None
,
vmax
=
None
,
pmin
=
2
,
pmax
=
98
,
cmap
=
None
,
nodataVal
=
None
,
res_factor
=
None
,
interactive
=
False
,
ax
=
None
,
ignore_rotation
=
False
):
"""Plot
s
the desired array position into a figure.
"""Plot the desired array position into a figure.
:param xlim: [start_column, end_column]
:param ylim: [start_row, end_row]
...
...
@@ -1327,7 +1310,7 @@ class GeoArray(object):
def
show_map
(
self
,
xlim
=
None
,
ylim
=
None
,
band
=
0
,
boundsMap
=
None
,
boundsMapPrj
=
None
,
out_epsg
=
None
,
figsize
=
None
,
interpolation
=
'none'
,
vmin
=
None
,
vmax
=
None
,
pmin
=
2
,
pmax
=
98
,
cmap
=
None
,
nodataVal
=
None
,
res_factor
=
None
,
return_map
=
False
):
"""
"""
Show a cartopy map of the associated image data (requires geocoding and projection information).
:param xlim:
:param ylim:
...
...
@@ -1410,10 +1393,7 @@ class GeoArray(object):
plt
.
show
()
def
show_footprint
(
self
):
"""This method is intended to be called from Jupyter Notebook and shows a web map containing the calculated
footprint of GeoArray.
"""
"""Show a web map containing the computed footprint of the GeoArray instance in a Jupyter notebook."""
if
not
find_loader
(
'folium'
)
or
not
find_loader
(
'geojson'
):
raise
ImportError
(
"This method requires the libraries 'folium' and 'geojson'. They can be installed with "
...
...
@@ -1431,7 +1411,6 @@ class GeoArray(object):
def
show_histogram
(
self
,
band
=
1
,
bins
=
200
,
normed
=
False
,
exclude_nodata
=
True
,
vmin
=
None
,
vmax
=
None
,
figsize
=
None
):
# type: (int, int, bool, bool, float, float, tuple) -> None
"""Show a histogram of a given band.
:param band: the band to be used to plot the histogram
...
...
@@ -1442,7 +1421,6 @@ class GeoArray(object):
:param vmax: maximum value for the x-axis of the histogram
:param figsize: figure size (tuple)
"""
from
matplotlib
import
pyplot
as
plt
if
self
.
nodata
is
not
None
and
exclude_nodata
:
...
...
@@ -1504,6 +1482,7 @@ class GeoArray(object):
class
GeneratorLen
(
object
):
"""Generator class adding __len__."""
def
__init__
(
self
,
gen
,
length
):
self
.
gen
=
gen
self
.
length
=
length
...
...
@@ -1526,7 +1505,7 @@ class GeoArray(object):
def
get_mapPos
(
self
,
mapBounds
,
mapBounds_prj
,
band2get
=
None
,
out_prj
=
None
,
arr_gt
=
None
,
arr_prj
=
None
,
fillVal
=
None
,
rspAlg
=
'near'
,
progress
=
None
,
v
=
False
):
# TODO implement slice for indexing bands
# type: (tuple, str, int, str, tuple, str, int, str, bool, bool) -> (np.ndarray, tuple, str)
"""Return
s
the array data of GeoArray at a given geographic position.
"""Return the array data of GeoArray at a given geographic position.
NOTE: The given mapBounds are snapped to the pixel grid of GeoArray. If the given mapBounds include areas
outside of the extent of GeoArray, these areas are filled with the fill value of GeoArray.
...
...
@@ -1674,7 +1653,6 @@ class GeoArray(object):
:param CPUs: <int> number of CPUs to use (default: None -> use all available CPUs)
:return:
"""
assert
(
tgt_prj
and
tgt_xygrid
)
or
prototype
,
"Provide either 'prototype' or 'tgt_prj' and 'tgt_xygrid'!"
tgt_prj
=
tgt_prj
or
prototype
.
prj
tgt_xygrid
=
tgt_xygrid
or
prototype
.
xygrid_specs
...
...
@@ -1721,7 +1699,7 @@ class GeoArray(object):
self
.
footprint_poly
=
self
.
footprint_poly
.
intersection
(
self
.
box
.
mapPoly
)
def
read_pointData
(
self
,
mapXY_points
,
mapXY_points_prj
=
None
,
band
=
None
,
offside_val
=
np
.
nan
):
"""Return
s
the array values for the given set of X/Y coordinates.
"""Return the array values for the given set of X/Y coordinates.
NOTE: If GeoArray has been instanced with a file path, the function will read the dataset into memory.
...
...
@@ -1737,7 +1715,6 @@ class GeoArray(object):
- np.ndarray with shape [Nx1] in case only one band is requested
- np.ndarray with shape [Nx1xbands] in case all bands are requested
"""
mapXY
=
mapXY_points
if
isinstance
(
mapXY_points
,
np
.
ndarray
)
else
np
.
array
(
mapXY_points
).
reshape
(
1
,
2
)
prj
=
mapXY_points_prj
if
mapXY_points_prj
else
self
.
prj
...
...
@@ -1783,15 +1760,15 @@ class GeoArray(object):
return
pointdata
def
to_mem
(
self
):
"""Reads the whole dataset into memory and sets self.arr to the read data."""
"""Read the whole dataset into memory and sets self.arr to the read data."""
self
.
arr
=
self
[:]
return
self
def
to_disk
(
self
):
"""Sets self.arr back to None if GeoArray has been instanced with a file path
and the whole dataset has been read."""
"""Set self.arr back to None for in-memory instances, to release memory.
Note: This requires that the GeoArray was instanced with a file path.
"""
if
self
.
filePath
and
os
.
path
.
isfile
(
self
.
filePath
):
self
.
_arr
=
None
else
:
...
...
@@ -1807,12 +1784,10 @@ class GeoArray(object):
def
cache_array_subset
(
self
,
arr_pos
):
# type: (list) -> None
"""Sets the array cache of the GeoArray instance to the given array in order to speed up calculations
afterwards.
"""Set the array cache of the GeoArray instance to the given array to speed up calculations afterwards.
:param arr_pos: a list of array indices as passed to __getitem__
"""
if
not
self
.
is_inmem
:
# noinspection PyStatementEffect
self
[
arr_pos
]
# runs __getitem__ and sets self._arr_cache
...
...
@@ -1821,7 +1796,6 @@ class GeoArray(object):
def
flush_cache
(
self
):
"""Clear the array cache of the GeoArray instance."""
self
.
_arr_cache
=
None
...
...
@@ -1835,11 +1809,10 @@ def get_GeoArray_from_GDAL_ds(ds):
class
MultiGeoArray
(
object
):
# pragma: no cover
def
__init__
(
self
,
GeoArray_list
):
"""
"""
Get an instance of MultiGeoArray.
:param GeoArray_list: a list of GeoArray instances having a geographic overlap
"""
self
.
_arrs
=
None
self
.
arrs
=
GeoArray_list
...
...
geoarray/subsetting.py
View file @
1aff6e33
...
...
@@ -43,6 +43,7 @@ if TYPE_CHECKING:
def
_clip_array_at_mapPos
(
arr
,
mapBounds
,
arr_gt
,
band2clip
=
None
,
fillVal
=
0
):
# type: (T_ndA_gA, tuple, tuple, int, int) -> (np.ndarray, tuple)
"""
NOTE: asserts that mapBounds have the same projection like the coordinates in arr_gt
:param arr:
...
...
@@ -52,7 +53,6 @@ def _clip_array_at_mapPos(arr, mapBounds, arr_gt, band2clip=None, fillVal=0):
:param fillVal:
:return:
"""
# assertions
assert
isinstance
(
arr_gt
,
(
tuple
,
list
))
assert
isinstance
(
band2clip
,
int
)
or
band2clip
is
None
...
...
@@ -139,7 +139,6 @@ def get_array_at_mapPosOLD(arr, arr_gt, arr_prj, mapBounds, mapBounds_prj, band2
:param fillVal:
:return:
"""
# [print(i,'\n') for i in [arr, arr_gt, arr_prj, mapBounds, mapBounds_prj]]
# check if requested bounds have the same projection like the array
samePrj
=
prj_equal
(
arr_prj
,
mapBounds_prj
)
...
...
@@ -217,7 +216,6 @@ def get_array_at_mapPos(arr, arr_gt, arr_prj, out_prj, mapBounds, mapBounds_prj=
:param progress:
:return:
"""
# check if reprojection is needed
mapBounds_prj
=
mapBounds_prj
if
mapBounds_prj
else
out_prj
samePrj
=
prj_equal
(
arr_prj
,
out_prj
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment