geoarray 0.17.2__tar.gz → 0.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {geoarray-0.17.2 → geoarray-0.18.0}/.gitlab-ci.yml +9 -0
  2. {geoarray-0.17.2 → geoarray-0.18.0}/AUTHORS.rst +1 -4
  3. {geoarray-0.17.2 → geoarray-0.18.0}/CONTRIBUTING.rst +1 -1
  4. {geoarray-0.17.2 → geoarray-0.18.0}/HISTORY.rst +17 -0
  5. {geoarray-0.17.2/geoarray.egg-info → geoarray-0.18.0}/PKG-INFO +6 -5
  6. {geoarray-0.17.2 → geoarray-0.18.0}/README.rst +1 -1
  7. {geoarray-0.17.2 → geoarray-0.18.0}/examples/notebooks/features_and_usage.ipynb +2 -2
  8. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray/__init__.py +5 -1
  9. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray/baseclasses.py +125 -135
  10. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray/masks.py +1 -1
  11. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray/metadata.py +25 -13
  12. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray/subsetting.py +1 -1
  13. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray/version.py +3 -3
  14. {geoarray-0.17.2 → geoarray-0.18.0/geoarray.egg-info}/PKG-INFO +6 -5
  15. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray.egg-info/requires.txt +3 -3
  16. {geoarray-0.17.2 → geoarray-0.18.0}/setup.py +4 -3
  17. {geoarray-0.17.2 → geoarray-0.18.0}/.coveragerc +0 -0
  18. {geoarray-0.17.2 → geoarray-0.18.0}/.editorconfig +0 -0
  19. {geoarray-0.17.2 → geoarray-0.18.0}/.gitattributes +0 -0
  20. {geoarray-0.17.2 → geoarray-0.18.0}/.github/ISSUE_TEMPLATE.md +0 -0
  21. {geoarray-0.17.2 → geoarray-0.18.0}/.gitignore +0 -0
  22. {geoarray-0.17.2 → geoarray-0.18.0}/LICENSE +0 -0
  23. {geoarray-0.17.2 → geoarray-0.18.0}/MANIFEST.in +0 -0
  24. {geoarray-0.17.2 → geoarray-0.18.0}/Makefile +0 -0
  25. {geoarray-0.17.2 → geoarray-0.18.0}/docs/Makefile +0 -0
  26. {geoarray-0.17.2 → geoarray-0.18.0}/docs/_build/html/_static/file.png +0 -0
  27. {geoarray-0.17.2 → geoarray-0.18.0}/docs/_build/html/_static/minus.png +0 -0
  28. {geoarray-0.17.2 → geoarray-0.18.0}/docs/_build/html/_static/plus.png +0 -0
  29. {geoarray-0.17.2 → geoarray-0.18.0}/docs/authors.rst +0 -0
  30. {geoarray-0.17.2 → geoarray-0.18.0}/docs/conf.py +0 -0
  31. {geoarray-0.17.2 → geoarray-0.18.0}/docs/contributing.rst +0 -0
  32. {geoarray-0.17.2 → geoarray-0.18.0}/docs/history.rst +0 -0
  33. {geoarray-0.17.2 → geoarray-0.18.0}/docs/index.rst +0 -0
  34. {geoarray-0.17.2 → geoarray-0.18.0}/docs/installation.rst +0 -0
  35. {geoarray-0.17.2 → geoarray-0.18.0}/docs/make.bat +0 -0
  36. {geoarray-0.17.2 → geoarray-0.18.0}/docs/readme.rst +0 -0
  37. {geoarray-0.17.2 → geoarray-0.18.0}/docs/usage.rst +0 -0
  38. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray.egg-info/SOURCES.txt +0 -0
  39. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray.egg-info/dependency_links.txt +0 -0
  40. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray.egg-info/not-zip-safe +0 -0
  41. {geoarray-0.17.2 → geoarray-0.18.0}/geoarray.egg-info/top_level.txt +0 -0
  42. {geoarray-0.17.2 → geoarray-0.18.0}/setup.cfg +0 -0
  43. {geoarray-0.17.2 → geoarray-0.18.0}/tox.ini +0 -0
@@ -12,6 +12,10 @@ test_geoarray:
12
12
  stage: test
13
13
  coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+%)/'
14
14
  script:
15
+ # TODO remove as soon as CI runner is rebuilt
16
+ - mamba env remove -n ci_env
17
+ - mamba env create --name ci_env -f tests/CI_docker/context/environment_geoarray.yml
18
+
15
19
  - source activate ci_env
16
20
 
17
21
  # update py_tools_ds
@@ -63,6 +67,10 @@ test_geoarray_install:
63
67
  script:
64
68
  - mamba update -n base mamba conda
65
69
 
70
+ # TODO remove as soon as CI runner is rebuilt
71
+ - pip install --force-reinstall zstandard
72
+ - pip install --force-reinstall zstd
73
+
66
74
  # create geoarray environment from environment_geoarray.yml
67
75
  - mamba env create --name geoarray_test -f tests/CI_docker/context/environment_geoarray.yml
68
76
  - source activate geoarray_test
@@ -71,6 +79,7 @@ test_geoarray_install:
71
79
  - pip install -e .
72
80
  - cd ..
73
81
  - pwd
82
+
74
83
  - ls
75
84
 
76
85
  # test importability
@@ -5,12 +5,9 @@ Credits
5
5
  Development Lead
6
6
  ----------------
7
7
 
8
- * Daniel Scheffler <danschef@gfz-potsdam.de>
9
- ** <https://www.gfz-potsdam.de/staff/daniel.scheffler/sec14/>
10
-
8
+ * `Daniel Scheffler <https://www.gfz-potsdam.de/staff/daniel.scheffler/sec14/>`__ <danschef@gfz-potsdam.de>
11
9
 
12
10
  Contributors
13
11
  ------------
14
12
 
15
13
  * Jessica Palka <jessica.palka@gfz-potsdam.de>
16
-
@@ -101,7 +101,7 @@ Before you submit a pull request, check that it meets these guidelines:
101
101
  2. If the pull request adds functionality, the docs should be updated. Put
102
102
  your new functionality into a function with a docstring, and add the
103
103
  feature to the list in README.rst.
104
- 3. The pull request should work for Python 3.8, 3.9, 3.10, and 3.11. Check
104
+ 3. The pull request should work for Python 3.8, 3.9, 3.10, 3.11, and 3.12. Check
105
105
  https://travis-ci.org/danschef/geoarray/pull_requests
106
106
  and make sure that the tests pass for all supported Python versions.
107
107
 
@@ -2,6 +2,23 @@
2
2
  History
3
3
  =======
4
4
 
5
+ 0.18.0 (05.07.2024)
6
+ -------------------
7
+
8
+ * !47: Fixed missing handling of int64 input values for GDAL_Metadata.__getitem__
9
+ and improved coverage of GDAL_Metadata.
10
+ * !48: Revised tests to use pytest plain assertions.
11
+ * !49: Improved documentation of expected order of array dimensions (closes #43).
12
+ * !50: Fixed #46 (Numpy 2.0 support: `can_cast` not supported for python dtypes).
13
+ * !51: Fixed GDAL-3.9-related RuntimeError within GeoArray.save by using context managers.
14
+ Bumped minimal version of GDAL to 3.8 due to usage of context managers.
15
+ * !52: Use GDAL context managers where ever possible.
16
+ * Added official support for Python 3.12.
17
+ * !53: Removed Test_GeoArray.test_show_map_noepsg because newer versions of pyproj do not
18
+ return None as EPSG code anymore (at least not with the given WKT).
19
+ * !54: Fixed DeprecationWarning (replaced deprecated pkgutil.find_loader with importlib.util.find_spec).
20
+
21
+
5
22
  0.17.2 (23.11.2023)
6
23
  -------------------
7
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geoarray
3
- Version: 0.17.2
3
+ Version: 0.18.0
4
4
  Summary: Fast Python interface for geodata - either on disk or in memory.
5
5
  Home-page: https://git.gfz-potsdam.de/danschef/geoarray
6
6
  Author: Daniel Scheffler
@@ -15,13 +15,14 @@ Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
18
19
  Requires-Python: >=3.8
19
20
  Description-Content-Type: text/x-rst
20
21
  License-File: LICENSE
21
22
  License-File: AUTHORS.rst
22
23
  Requires-Dist: cartopy>=0.20
23
24
  Requires-Dist: dill
24
- Requires-Dist: gdal>=2.1.0
25
+ Requires-Dist: gdal>=3.8.0
25
26
  Requires-Dist: matplotlib
26
27
  Requires-Dist: numpy
27
28
  Requires-Dist: pandas
@@ -38,7 +39,7 @@ Requires-Dist: sphinx_rtd_theme; extra == "doc"
38
39
  Provides-Extra: test
39
40
  Requires-Dist: cartopy>=0.20; extra == "test"
40
41
  Requires-Dist: dill; extra == "test"
41
- Requires-Dist: gdal>=2.1.0; extra == "test"
42
+ Requires-Dist: gdal>=3.8.0; extra == "test"
42
43
  Requires-Dist: matplotlib; extra == "test"
43
44
  Requires-Dist: numpy; extra == "test"
44
45
  Requires-Dist: pandas; extra == "test"
@@ -58,7 +59,7 @@ Provides-Extra: dev
58
59
  Requires-Dist: setuptools-git; extra == "dev"
59
60
  Requires-Dist: cartopy>=0.20; extra == "dev"
60
61
  Requires-Dist: dill; extra == "dev"
61
- Requires-Dist: gdal>=2.1.0; extra == "dev"
62
+ Requires-Dist: gdal>=3.8.0; extra == "dev"
62
63
  Requires-Dist: matplotlib; extra == "dev"
63
64
  Requires-Dist: numpy; extra == "dev"
64
65
  Requires-Dist: pandas; extra == "dev"
@@ -155,7 +156,7 @@ open source packages that may cause problems when installed with pip. Therefore,
155
156
  to resolve the following dependencies before the pip installer is run:
156
157
 
157
158
  * cartopy >=0.20
158
- * gdal >=2.1.0
159
+ * gdal >=3.8.0
159
160
  * geopandas
160
161
  * holoviews # optional, in case you want to use interactive plotting
161
162
  * matplotlib
@@ -77,7 +77,7 @@ open source packages that may cause problems when installed with pip. Therefore,
77
77
  to resolve the following dependencies before the pip installer is run:
78
78
 
79
79
  * cartopy >=0.20
80
- * gdal >=2.1.0
80
+ * gdal >=3.8.0
81
81
  * geopandas
82
82
  * holoviews # optional, in case you want to use interactive plotting
83
83
  * matplotlib
@@ -64,7 +64,7 @@
64
64
  "from geoarray import GeoArray\n",
65
65
  "import numpy as np\n",
66
66
  "\n",
67
- "testarray = np.random.randint(1,10, (250, 250, 8))\n",
67
+ "testarray = np.random.randint(1,10, (250, 250, 8)) # (250 rows, 250 columns, 8 bands)\n",
68
68
  "\n",
69
69
  "gA = GeoArray(testarray,\n",
70
70
  " geotransform=[640605.0, 30.0, -0.0, 5754285.0, -0.0, -30.0],\n",
@@ -767,4 +767,4 @@
767
767
  },
768
768
  "nbformat": 4,
769
769
  "nbformat_minor": 4
770
- }
770
+ }
@@ -15,7 +15,7 @@
15
15
  # you may not use this file except in compliance with the License.
16
16
  # You may obtain a copy of the License at
17
17
  #
18
- # http://www.apache.org/licenses/LICENSE-2.0
18
+ # https://www.apache.org/licenses/LICENSE-2.0
19
19
  #
20
20
  # Unless required by applicable law or agreed to in writing, software
21
21
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -24,6 +24,7 @@
24
24
  # limitations under the License.
25
25
 
26
26
  import os
27
+ from osgeo import gdal as _gdal
27
28
  if 'MPLBACKEND' not in os.environ:
28
29
  os.environ['MPLBACKEND'] = 'Agg'
29
30
 
@@ -47,6 +48,9 @@ __all__ = ['__version__',
47
48
  'CloudMask'
48
49
  ]
49
50
 
51
+ # enable GDAL exceptions
52
+ _gdal.UseExceptions()
53
+
50
54
  # $PROJ_LIB was renamed to $PROJ_DATA in proj=9.1.1, which leads to issues with fiona>=1.8.20,<1.9
51
55
  # https://github.com/conda-forge/pyproj-feedstock/issues/130
52
56
  # -> fix it by setting PROJ_DATA
@@ -15,7 +15,7 @@
15
15
  # you may not use this file except in compliance with the License.
16
16
  # You may obtain a copy of the License at
17
17
  #
18
- # http://www.apache.org/licenses/LICENSE-2.0
18
+ # https://www.apache.org/licenses/LICENSE-2.0
19
19
  #
20
20
  # Unless required by applicable law or agreed to in writing, software
21
21
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -26,7 +26,7 @@
26
26
 
27
27
  import os
28
28
  import warnings
29
- from pkgutil import find_loader
29
+ from importlib.util import find_spec
30
30
  from collections import OrderedDict
31
31
  from copy import copy, deepcopy
32
32
  from numbers import Number
@@ -104,7 +104,7 @@ class GeoArray(object):
104
104
  ) -> None:
105
105
  """Get an instance of GeoArray.
106
106
 
107
- :param path_or_array: a numpy.ndarray or a valid file path
107
+ :param path_or_array: a numpy.ndarray (rows, columns, bands) or a valid file path
108
108
  :param geotransform: GDAL geotransform of the given array or file on disk
109
109
  :param projection: projection of the given array or file on disk as WKT string
110
110
  (only needed if GeoArray is instanced with an array)
@@ -321,7 +321,7 @@ class GeoArray(object):
321
321
 
322
322
  @geotransform.setter
323
323
  def geotransform(self, gt: Union[tuple, list]):
324
- assert isinstance(gt, (list, tuple)) and len(gt) == 6,\
324
+ assert isinstance(gt, (list, tuple)) and len(gt) == 6, \
325
325
  'geotransform must be a list with 6 numbers. Got %s.' % str(gt)
326
326
 
327
327
  for i in gt:
@@ -549,7 +549,7 @@ class GeoArray(object):
549
549
  # assert self._footprint_poly
550
550
  # for XY in self.corner_coord:
551
551
  # assert self.GeoArray.box.mapPoly.contains(Point(XY)) or self.GeoArray.box.mapPoly.touches(Point(XY)), \
552
- # "The corner position '%s' is outside of the %s." % (XY, self.imName)
552
+ # "The corner position '%s' is outside the %s." % (XY, self.imName)
553
553
 
554
554
  return self._footprint_poly
555
555
 
@@ -823,42 +823,40 @@ class GeoArray(object):
823
823
  """
824
824
  if not self._gdalDataset_meta_already_set:
825
825
  assert self.filePath
826
- ds = gdal.Open(self.filePath)
827
- if not ds:
828
- raise Exception('Error reading file: ' + gdal.GetLastErrorMsg())
829
826
 
830
- # set private class variables (in order to avoid recursion error)
831
- self._shape = tuple([ds.RasterYSize, ds.RasterXSize] + ([ds.RasterCount] if ds.RasterCount > 1 else []))
832
- self._dtype = gdal_array.GDALTypeCodeToNumericTypeCode(ds.GetRasterBand(1).DataType)
833
- self._geotransform = list(ds.GetGeoTransform())
827
+ with gdal.Open(self.filePath) as ds:
828
+ if not ds:
829
+ raise Exception('Error reading file: ' + gdal.GetLastErrorMsg())
834
830
 
835
- # for some reason GDAL reads arbitrary geotransforms as (0, 1, 0, 0, 0, 1) instead of (0, 1, 0, 0, 0, -1)
836
- self._geotransform[5] = -abs(self._geotransform[5]) # => force ygsd to be negative
831
+ # set private class variables (in order to avoid recursion error)
832
+ self._shape = tuple([ds.RasterYSize, ds.RasterXSize] + ([ds.RasterCount] if ds.RasterCount > 1 else []))
833
+ self._dtype = gdal_array.GDALTypeCodeToNumericTypeCode(ds.GetRasterBand(1).DataType)
834
+ self._geotransform = list(ds.GetGeoTransform())
837
835
 
838
- # consequently use WKT1 strings here as GDAL always exports transformation results as WKT1
839
- wkt = ds.GetProjection()
840
- self._projection = CRS(wkt).to_wkt(version="WKT1_GDAL") if not isLocal(wkt) else ''
836
+ # somehow GDAL reads arbitrary geotransforms as (0, 1, 0, 0, 0, 1) instead of (0, 1, 0, 0, 0, -1)
837
+ self._geotransform[5] = -abs(self._geotransform[5]) # => force ygsd to be negative
841
838
 
842
- if 'nodata' not in self._initParams or self._initParams['nodata'] is None:
843
- band = ds.GetRasterBand(1)
844
- # FIXME this does not support different nodata values within the same file
845
- self.nodata = band.GetNoDataValue()
846
- self.nodata = band.GetNoDataValue()
839
+ # consequently use WKT1 strings here as GDAL always exports transformation results as WKT1
840
+ wkt = ds.GetProjection()
841
+ self._projection = CRS(wkt).to_wkt(version="WKT1_GDAL") if not isLocal(wkt) else ''
847
842
 
848
- # set metadata attribute
849
- if self.is_inmem or not self.filePath:
850
- # metadata cannot be read from disk -> set it to the default
851
- self._metadata = GDAL_Metadata(nbands=self.bands, nodata_allbands=self._nodata)
843
+ if 'nodata' not in self._initParams or self._initParams['nodata'] is None:
844
+ band = ds.GetRasterBand(1)
845
+ # FIXME this does not support different nodata values within the same file
846
+ self.nodata = band.GetNoDataValue()
847
+ self.nodata = band.GetNoDataValue()
852
848
 
853
- else:
854
- self._metadata = GDAL_Metadata(filePath=self.filePath)
849
+ # set metadata attribute
850
+ if self.is_inmem or not self.filePath:
851
+ # metadata cannot be read from disk -> set it to the default
852
+ self._metadata = GDAL_Metadata(nbands=self.bands, nodata_allbands=self._nodata)
855
853
 
856
- # copy over the band names
857
- if 'band_names' in self.metadata.band_meta and self.metadata.band_meta['band_names']:
858
- self.bandnames = self.metadata.band_meta['band_names']
854
+ else:
855
+ self._metadata = GDAL_Metadata(filePath=self.filePath)
859
856
 
860
- # noinspection PyUnusedLocal
861
- ds = None
857
+ # copy over the band names
858
+ if 'band_names' in self.metadata.band_meta and self.metadata.band_meta['band_names']:
859
+ self.bandnames = self.metadata.band_meta['band_names']
862
860
 
863
861
  self._gdalDataset_meta_already_set = True
864
862
 
@@ -871,12 +869,11 @@ class GeoArray(object):
871
869
  :param getitem_params: a list of slices in the form [row_slice, col_slice, band_slice]
872
870
  :return out_arr: the output array
873
871
  """
874
- ds = gdal.Open(path)
875
- if not ds:
876
- raise Exception('Error reading file: ' + gdal.GetLastErrorMsg())
872
+ with gdal.Open(path) as ds:
873
+ if not ds:
874
+ raise Exception('Error reading file: ' + gdal.GetLastErrorMsg())
877
875
 
878
- R, C, B = ds.RasterYSize, ds.RasterXSize, ds.RasterCount
879
- del ds
876
+ R, C, B = ds.RasterYSize, ds.RasterXSize, ds.RasterCount
880
877
 
881
878
  # convert getitem_params to subset area to be read #
882
879
  rS, rE, cS, cE, bS, bE, bL = [None] * 7
@@ -996,23 +993,21 @@ class GeoArray(object):
996
993
  if out_arr is None:
997
994
  raise Exception('Error reading file: ' + gdal.GetLastErrorMsg())
998
995
  else:
999
- ds = gdal.Open(path)
1000
- if len(bL) == 1:
1001
- band = ds.GetRasterBand(bL[0] + 1)
1002
- out_arr = band.ReadAsArray(cS, rS, cE - cS + 1, rE - rS + 1)
1003
- if out_arr is None:
1004
- raise Exception('Error reading file: ' + gdal.GetLastErrorMsg())
1005
- del band
1006
- else:
1007
- out_arr = np.empty((rE - rS + 1, cE - cS + 1, len(bL)))
1008
- for i, bIdx in enumerate(bL):
1009
- band = ds.GetRasterBand(bIdx + 1)
1010
- out_arr[:, :, i] = band.ReadAsArray(cS, rS, cE - cS + 1, rE - rS + 1)
996
+ with gdal.Open(path) as ds:
997
+ if len(bL) == 1:
998
+ band = ds.GetRasterBand(bL[0] + 1)
999
+ out_arr = band.ReadAsArray(cS, rS, cE - cS + 1, rE - rS + 1)
1011
1000
  if out_arr is None:
1012
1001
  raise Exception('Error reading file: ' + gdal.GetLastErrorMsg())
1013
1002
  del band
1014
-
1015
- del ds
1003
+ else:
1004
+ out_arr = np.empty((rE - rS + 1, cE - cS + 1, len(bL)))
1005
+ for i, bIdx in enumerate(bL):
1006
+ band = ds.GetRasterBand(bIdx + 1)
1007
+ out_arr[:, :, i] = band.ReadAsArray(cS, rS, cE - cS + 1, rE - rS + 1)
1008
+ if out_arr is None:
1009
+ raise Exception('Error reading file: ' + gdal.GetLastErrorMsg())
1010
+ del band
1016
1011
 
1017
1012
  out_arr = _ensure_np_shape_consistency_3D_2D(out_arr)
1018
1013
 
@@ -1076,111 +1071,102 @@ class GeoArray(object):
1076
1071
  #########################################
1077
1072
  # write output dataset and set metadata #
1078
1073
  #########################################
1074
+ with gdal.config_options(
1075
+ dict(
1076
+ GDAL_PAM_ENABLED='NO', # disable to write separate metadata XML files
1077
+ )
1078
+ ):
1079
1079
 
1080
- try:
1081
1080
  # ENVI #
1082
1081
  ########
1083
1082
  if fmt == 'ENVI':
1084
- # NOTE: The dataset has to be written BEFORE metadata are added. Otherwise, metadata are not written.
1083
+ # NOTE: The dataset must be written BEFORE metadata are added. Otherwise, metadata are not written.
1085
1084
 
1086
1085
  # write ds_src to disk and re-open it to add the metadata
1087
1086
  gdal.Translate(out_path, ds_src, format=fmt, creationOptions=creationOptions)
1088
1087
  del ds_src
1089
- # os.environ['GDAL_PAM_ENABLED'] = 'NO'
1090
- ds_out = gdal.Open(out_path, gdal.GA_Update)
1091
-
1092
- for bidx in range(self.bands):
1093
- band = ds_out.GetRasterBand(bidx + 1)
1094
-
1095
- if 'band_names' in envi_metadict:
1096
- bandname = str(self.metadata.band_meta['band_names'][bidx]).strip()
1097
- band.SetDescription(bandname)
1098
- assert band.GetDescription() == bandname
1099
1088
 
1100
- del band
1089
+ with gdal.Open(out_path, gdal.GA_Update) as ds_out:
1101
1090
 
1102
- # avoid that band names are written to global meta
1103
- if 'band_names' in envi_metadict:
1104
- del envi_metadict['band_names']
1091
+ for bidx in range(self.bands):
1092
+ band = ds_out.GetRasterBand(bidx + 1)
1105
1093
 
1106
- # the expected key name is 'data_ignore_value', see below
1107
- if 'nodata' in envi_metadict:
1108
- del envi_metadict['nodata']
1094
+ if 'band_names' in envi_metadict:
1095
+ bandname = str(self.metadata.band_meta['band_names'][bidx]).strip()
1096
+ band.SetDescription(bandname)
1097
+ assert band.GetDescription() == bandname
1109
1098
 
1110
- # set data_ignore_value in case self.metadata.band_meta contains a unique nodata value
1111
- if 'nodata' in self.metadata.band_meta:
1112
- if len(set(self.metadata.band_meta['nodata'])) == 1:
1113
- envi_metadict['data_ignore_value'] = str(self.metadata.band_meta['nodata'][0])
1114
- else:
1115
- warnings.warn("Band-specific nodata values are not supported by the ENVI header format.")
1099
+ # avoid that band names are written to global meta
1100
+ if 'band_names' in envi_metadict:
1101
+ del envi_metadict['band_names']
1116
1102
 
1117
- ds_out.SetMetadata(envi_metadict, 'ENVI')
1103
+ # the expected key name is 'data_ignore_value', see below
1104
+ if 'nodata' in envi_metadict:
1105
+ del envi_metadict['nodata']
1118
1106
 
1119
- if 'description' in envi_metadict:
1120
- ds_out.SetDescription(envi_metadict['description'])
1107
+ # set data_ignore_value in case self.metadata.band_meta contains a unique nodata value
1108
+ if 'nodata' in self.metadata.band_meta:
1109
+ if len(set(self.metadata.band_meta['nodata'])) == 1:
1110
+ envi_metadict['data_ignore_value'] = str(self.metadata.band_meta['nodata'][0])
1111
+ else:
1112
+ warnings.warn("Band-specific nodata values are not supported "
1113
+ "by the ENVI header format.")
1121
1114
 
1122
- ds_out.FlushCache()
1115
+ ds_out.SetMetadata(envi_metadict, 'ENVI')
1123
1116
 
1124
- # NOTE: In case of ENVI format and GDAL_PAM_ENABLED=NO, the metadata is not written
1125
- gdal.Unlink(out_path + '.aux.xml')
1117
+ if 'description' in envi_metadict:
1118
+ ds_out.SetDescription(envi_metadict['description'])
1126
1119
 
1127
1120
  else:
1128
- # disable to write separate metadata XML files
1129
- os.environ['GDAL_PAM_ENABLED'] = 'NO'
1130
-
1131
- ds_out = ds_src
1132
-
1133
- # set metadata
1134
- if self.metadata.all_meta:
1135
-
1136
- # set global domain metadata
1137
- if self.metadata.global_meta:
1138
- ds_out.SetMetadata(dict((k, repr(v)) for k, v in self.metadata.global_meta.items()))
1121
+ try:
1122
+ ds_out = ds_src
1123
+ del ds_src
1139
1124
 
1140
- if 'description' in envi_metadict:
1141
- ds_out.SetDescription(envi_metadict['description'])
1125
+ # set metadata
1126
+ if self.metadata.all_meta:
1142
1127
 
1143
- # set band domain metadata
1144
- bandmeta_dict = self.metadata.to_DataFrame().astype(str).to_dict()
1128
+ # set global domain metadata
1129
+ if self.metadata.global_meta:
1130
+ ds_out.SetMetadata(dict((k, repr(v)) for k, v in self.metadata.global_meta.items()))
1145
1131
 
1146
- for bidx in range(self.bands):
1147
- band = ds_out.GetRasterBand(bidx + 1)
1148
- bandmeta = bandmeta_dict[bidx].copy()
1132
+ if 'description' in envi_metadict:
1133
+ ds_out.SetDescription(envi_metadict['description'])
1149
1134
 
1150
- # filter global metadata out
1151
- bandmeta = {k: v for k, v in bandmeta.items() if k not in self.metadata.global_meta}
1152
- # meta2write = dict((k, repr(v)) for k, v in self.metadata.band_meta.items() if v is not np.nan)
1135
+ # set band domain metadata
1136
+ bandmeta_dict = self.metadata.to_DataFrame().astype(str).to_dict()
1153
1137
 
1154
- if 'band_names' in bandmeta:
1155
- bandname = str(self.metadata.band_meta['band_names'][bidx]).strip()
1156
- band.SetDescription(bandname)
1157
- del bandmeta['band_names']
1138
+ for bidx in range(self.bands):
1139
+ band = ds_out.GetRasterBand(bidx + 1)
1140
+ bandmeta = bandmeta_dict[bidx].copy()
1158
1141
 
1159
- if 'nodata' in bandmeta:
1160
- band.SetNoDataValue(self.metadata.band_meta['nodata'][bidx])
1161
- del bandmeta['nodata']
1142
+ # filter global metadata out
1143
+ bandmeta = {k: v for k, v in bandmeta.items() if k not in self.metadata.global_meta}
1144
+ # meta2write = dict((k, repr(v))
1145
+ # for k, v in self.metadata.band_meta.items() if v is not np.nan)
1162
1146
 
1163
- if bandmeta:
1164
- band.SetMetadata(bandmeta)
1147
+ if 'band_names' in bandmeta:
1148
+ bandname = str(self.metadata.band_meta['band_names'][bidx]).strip()
1149
+ band.SetDescription(bandname)
1150
+ del bandmeta['band_names']
1165
1151
 
1166
- band.FlushCache()
1167
- del band
1152
+ if 'nodata' in bandmeta:
1153
+ band.SetNoDataValue(self.metadata.band_meta['nodata'][bidx])
1154
+ del bandmeta['nodata']
1168
1155
 
1169
- ds_out.FlushCache()
1156
+ if bandmeta:
1157
+ band.SetMetadata(bandmeta)
1170
1158
 
1171
- # write ds_out to disk,
1172
- # -> writes the in-memory array or transforms the linked dataset into the target format
1173
- gdal.Translate(out_path, ds_out, format=fmt, creationOptions=creationOptions)
1174
- del ds_src
1159
+ ds_out.FlushCache()
1175
1160
 
1176
- finally:
1177
- if 'GDAL_PAM_ENABLED' in os.environ:
1178
- del os.environ['GDAL_PAM_ENABLED']
1161
+ # write ds_out to disk,
1162
+ # -> writes the in-memory array or transforms the linked dataset into the target format
1163
+ gdal.Translate(out_path, ds_out, format=fmt, creationOptions=creationOptions)
1179
1164
 
1180
- if not os.path.exists(out_path):
1181
- raise Exception(gdal.GetLastErrorMsg())
1165
+ finally:
1166
+ del ds_out
1182
1167
 
1183
- del ds_out
1168
+ if not os.path.exists(out_path):
1169
+ raise Exception(gdal.GetLastErrorMsg())
1184
1170
 
1185
1171
  def dump(self, out_path: str) -> None:
1186
1172
  """Serialize the whole object instance to disk using dill."""
@@ -1215,10 +1201,14 @@ class GeoArray(object):
1215
1201
  xdim, ydim = None, None
1216
1202
  in_nodata = nodataVal if nodataVal is not None else self.nodata
1217
1203
  out_nodata = in_nodata if in_nodata is not None else -9999
1218
- if not np.can_cast(out_nodata, image2plot.dtype):
1219
- image2plot = image2plot.astype(np.int32)
1220
- if image2plot.dtype == bool:
1221
- image2plot = image2plot.astype(int)
1204
+
1205
+ # use meaningful numeric data type for output image, compatible to the output nodata value
1206
+ out_dtype = np.result_type(np.array(out_nodata), image2plot)
1207
+ if not np.can_cast(out_dtype, np.float32):
1208
+ out_dtype = np.float32 # more than float32 is not needed
1209
+ elif out_dtype == bool:
1210
+ out_dtype = int
1211
+ image2plot = image2plot.astype(out_dtype)
1222
1212
 
1223
1213
  # rotated images always have to be resampled for plotting
1224
1214
  if not ignore_rotation and self.is_rotated:
@@ -1351,7 +1341,7 @@ class GeoArray(object):
1351
1341
  image2plot = np.ma.masked_equal(image2plot, nodataVal)
1352
1342
 
1353
1343
  # check availability of holoviews
1354
- if interactive and not find_loader('holoviews'):
1344
+ if interactive and not find_spec('holoviews'):
1355
1345
  warnings.warn("Interactive mode requires holoviews. Install it by running, e.g., "
1356
1346
  "'conda install -c conda-forge holoviews'. Using non-interactive mode.")
1357
1347
  interactive = False
@@ -1507,7 +1497,7 @@ class GeoArray(object):
1507
1497
 
1508
1498
  def show_footprint(self):
1509
1499
  """Show a web map containing the computed footprint of the GeoArray instance in a Jupyter notebook."""
1510
- if not find_loader('folium') or not find_loader('geojson'):
1500
+ if not find_spec('folium') or not find_spec('geojson'):
1511
1501
  raise ImportError(
1512
1502
  "This method requires the libraries 'folium' and 'geojson'. They can be installed with "
1513
1503
  "the shell command 'pip install folium geojson'.")
@@ -1554,7 +1544,7 @@ class GeoArray(object):
1554
1544
  image2plot = data
1555
1545
 
1556
1546
  plt.figure(figsize=figsize)
1557
- plt.hist(list(image2plot.flat), density=normed, bins=bins, color='gray', range=[vmin, vmax])
1547
+ plt.hist(list(image2plot.flat), density=normed, bins=bins, color='gray', range=(vmin, vmax))
1558
1548
  plt.xlabel('Pixel value')
1559
1549
  plt.ylabel('Probabilty' if normed else 'Count')
1560
1550
  plt.show()
@@ -1761,7 +1751,7 @@ class GeoArray(object):
1761
1751
  """Return the array data of GeoArray at a given geographic position.
1762
1752
 
1763
1753
  NOTE: The given mapBounds are snapped to the pixel grid of GeoArray. If the given mapBounds include areas
1764
- outside of the extent of GeoArray, these areas are filled with the fill value of GeoArray.
1754
+ outside the extent of GeoArray, these areas are filled with the fill value of GeoArray.
1765
1755
 
1766
1756
  :param mapBounds: xmin, ymin, xmax, ymax
1767
1757
  :param mapBounds_prj: WKT projection string or EPSG code corresponding to mapBounds
@@ -1980,7 +1970,7 @@ class GeoArray(object):
1980
1970
  coordinates.
1981
1971
  :param band: the band index of the band of interest. If None, the values of all bands are
1982
1972
  returned.
1983
- :param offside_val: fill value in case input coordinates are geographically outside of the GeoArray
1973
+ :param offside_val: fill value in case input coordinates are geographically outside the GeoArray
1984
1974
  instance
1985
1975
  :return: - int in case only a singe coordinate is passed
1986
1976
  - np.ndarray with shape [Nx1] in case only one band is requested
@@ -15,7 +15,7 @@
15
15
  # you may not use this file except in compliance with the License.
16
16
  # You may obtain a copy of the License at
17
17
  #
18
- # http://www.apache.org/licenses/LICENSE-2.0
18
+ # https://www.apache.org/licenses/LICENSE-2.0
19
19
  #
20
20
  # Unless required by applicable law or agreed to in writing, software
21
21
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -15,7 +15,7 @@
15
15
  # you may not use this file except in compliance with the License.
16
16
  # You may obtain a copy of the License at
17
17
  #
18
- # http://www.apache.org/licenses/LICENSE-2.0
18
+ # https://www.apache.org/licenses/LICENSE-2.0
19
19
  #
20
20
  # Unless required by applicable law or agreed to in writing, software
21
21
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -160,9 +160,7 @@ class GDAL_Metadata(object):
160
160
  not filePath.startswith('NETCDF'):
161
161
  raise FileNotFoundError(filePath)
162
162
 
163
- ds = gdal.Open(filePath)
164
-
165
- try:
163
+ with gdal.Open(filePath) as ds:
166
164
  if not ds:
167
165
  raise Exception('Error reading file: ' + gdal.GetLastErrorMsg())
168
166
 
@@ -230,9 +228,6 @@ class GDAL_Metadata(object):
230
228
  # self.df[b] = meta_gs
231
229
  del band
232
230
 
233
- finally:
234
- del ds
235
-
236
231
  return self.all_meta
237
232
 
238
233
  def __repr__(self) -> str:
@@ -251,7 +246,21 @@ class GDAL_Metadata(object):
251
246
  # subset bands
252
247
  if bands2extract is not None:
253
248
  if isinstance(bands2extract, list):
254
- bands2extract = np.array(bands2extract)
249
+ if not len(set([type(i) for i in bands2extract])) == 1:
250
+ raise ValueError("List items of 'bands2extract' should all have the same type.")
251
+ if not isinstance(bands2extract[0], str):
252
+ bands2extract = np.array(bands2extract)
253
+ else:
254
+ if 'band_names' not in meta_sub.band_meta:
255
+ raise ValueError('String input is only supported if band names are set.')
256
+ bandnames = meta_sub.band_meta['band_names']
257
+ out = []
258
+ for b in bands2extract:
259
+ if b in bandnames:
260
+ out.append(bandnames.index(b))
261
+ else:
262
+ raise ValueError(f"'{b}' is not a valid band name.")
263
+ bands2extract = np.array(out)
255
264
  elif isinstance(bands2extract, (np.ndarray, slice)):
256
265
  pass # all fine
257
266
  else:
@@ -267,6 +276,9 @@ class GDAL_Metadata(object):
267
276
 
268
277
  # subset metadata keys
269
278
  if keys2extract:
279
+ if isinstance(keys2extract, list) and not list(set([type(i) for i in keys2extract])) == [str]:
280
+ raise ValueError("List items of 'keys2extract' should all be of type string.")
281
+
270
282
  keys2extract = [keys2extract] if isinstance(keys2extract, str) else keys2extract
271
283
 
272
284
  # global_meta = meta_sub.global_meta.copy()
@@ -284,22 +296,22 @@ class GDAL_Metadata(object):
284
296
  return meta_sub
285
297
 
286
298
  def __getitem__(self, given: Union[int, slice, str, list, np.ndarray]) -> 'GDAL_Metadata':
287
- if isinstance(given, int):
299
+ if isinstance(given, (int, np.integer)):
288
300
  return self.get_subset(bands2extract=slice(given, given + 1))
289
301
  elif isinstance(given, slice):
290
302
  return self.get_subset(bands2extract=given)
291
303
  elif isinstance(given, str):
292
- return self.get_subset(keys2extract=given)
304
+ return self.get_subset(bands2extract=[given])
293
305
  elif isinstance(given, list):
294
306
  if isinstance(given[0], str):
295
- return self.get_subset(keys2extract=given)
296
- elif isinstance(given[0], int):
307
+ return self.get_subset(bands2extract=given)
308
+ elif isinstance(given[0], (int, np.integer)):
297
309
  return self.get_subset(bands2extract=given)
298
310
  else:
299
311
  raise TypeError(given, 'Given list must contain string or integer items.')
300
312
  elif isinstance(given, np.ndarray):
301
313
  if given.ndim != 1:
302
- raise TypeError(given, 'Given numpy array must be one-dimensional.')
314
+ raise ValueError(given, 'Given numpy array must be one-dimensional.')
303
315
  return self.get_subset(bands2extract=given)
304
316
  else:
305
317
  raise TypeError(given)
@@ -15,7 +15,7 @@
15
15
  # you may not use this file except in compliance with the License.
16
16
  # You may obtain a copy of the License at
17
17
  #
18
- # http://www.apache.org/licenses/LICENSE-2.0
18
+ # https://www.apache.org/licenses/LICENSE-2.0
19
19
  #
20
20
  # Unless required by applicable law or agreed to in writing, software
21
21
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,7 +13,7 @@
13
13
  # you may not use this file except in compliance with the License.
14
14
  # You may obtain a copy of the License at
15
15
  #
16
- # http://www.apache.org/licenses/LICENSE-2.0
16
+ # https://www.apache.org/licenses/LICENSE-2.0
17
17
  #
18
18
  # Unless required by applicable law or agreed to in writing, software
19
19
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -22,5 +22,5 @@
22
22
  # limitations under the License.
23
23
 
24
24
 
25
- __version__ = '0.17.2'
26
- __versionalias__ = '20231123.01'
25
+ __version__ = '0.18.0'
26
+ __versionalias__ = '20240705.01'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: geoarray
3
- Version: 0.17.2
3
+ Version: 0.18.0
4
4
  Summary: Fast Python interface for geodata - either on disk or in memory.
5
5
  Home-page: https://git.gfz-potsdam.de/danschef/geoarray
6
6
  Author: Daniel Scheffler
@@ -15,13 +15,14 @@ Classifier: Programming Language :: Python :: 3.8
15
15
  Classifier: Programming Language :: Python :: 3.9
16
16
  Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
18
19
  Requires-Python: >=3.8
19
20
  Description-Content-Type: text/x-rst
20
21
  License-File: LICENSE
21
22
  License-File: AUTHORS.rst
22
23
  Requires-Dist: cartopy>=0.20
23
24
  Requires-Dist: dill
24
- Requires-Dist: gdal>=2.1.0
25
+ Requires-Dist: gdal>=3.8.0
25
26
  Requires-Dist: matplotlib
26
27
  Requires-Dist: numpy
27
28
  Requires-Dist: pandas
@@ -38,7 +39,7 @@ Requires-Dist: sphinx_rtd_theme; extra == "doc"
38
39
  Provides-Extra: test
39
40
  Requires-Dist: cartopy>=0.20; extra == "test"
40
41
  Requires-Dist: dill; extra == "test"
41
- Requires-Dist: gdal>=2.1.0; extra == "test"
42
+ Requires-Dist: gdal>=3.8.0; extra == "test"
42
43
  Requires-Dist: matplotlib; extra == "test"
43
44
  Requires-Dist: numpy; extra == "test"
44
45
  Requires-Dist: pandas; extra == "test"
@@ -58,7 +59,7 @@ Provides-Extra: dev
58
59
  Requires-Dist: setuptools-git; extra == "dev"
59
60
  Requires-Dist: cartopy>=0.20; extra == "dev"
60
61
  Requires-Dist: dill; extra == "dev"
61
- Requires-Dist: gdal>=2.1.0; extra == "dev"
62
+ Requires-Dist: gdal>=3.8.0; extra == "dev"
62
63
  Requires-Dist: matplotlib; extra == "dev"
63
64
  Requires-Dist: numpy; extra == "dev"
64
65
  Requires-Dist: pandas; extra == "dev"
@@ -155,7 +156,7 @@ open source packages that may cause problems when installed with pip. Therefore,
155
156
  to resolve the following dependencies before the pip installer is run:
156
157
 
157
158
  * cartopy >=0.20
158
- * gdal >=2.1.0
159
+ * gdal >=3.8.0
159
160
  * geopandas
160
161
  * holoviews # optional, in case you want to use interactive plotting
161
162
  * matplotlib
@@ -1,6 +1,6 @@
1
1
  cartopy>=0.20
2
2
  dill
3
- gdal>=2.1.0
3
+ gdal>=3.8.0
4
4
  matplotlib
5
5
  numpy
6
6
  pandas
@@ -12,7 +12,7 @@ shapely
12
12
  setuptools-git
13
13
  cartopy>=0.20
14
14
  dill
15
- gdal>=2.1.0
15
+ gdal>=3.8.0
16
16
  matplotlib
17
17
  numpy
18
18
  pandas
@@ -47,7 +47,7 @@ pydocstyle
47
47
  [test]
48
48
  cartopy>=0.20
49
49
  dill
50
- gdal>=2.1.0
50
+ gdal>=3.8.0
51
51
  matplotlib
52
52
  numpy
53
53
  pandas
@@ -16,7 +16,7 @@
16
16
  # you may not use this file except in compliance with the License.
17
17
  # You may obtain a copy of the License at
18
18
  #
19
- # http://www.apache.org/licenses/LICENSE-2.0
19
+ # https://www.apache.org/licenses/LICENSE-2.0
20
20
  #
21
21
  # Unless required by applicable law or agreed to in writing, software
22
22
  # distributed under the License is distributed on an "AS IS" BASIS,
@@ -40,7 +40,7 @@ with open("geoarray/version.py") as version_file:
40
40
  req = [
41
41
  'cartopy>=0.20', # older versions require pyepsg
42
42
  'dill',
43
- 'gdal>=2.1.0',
43
+ 'gdal>=3.8.0', # use of context managers
44
44
  'matplotlib',
45
45
  'numpy',
46
46
  'pandas',
@@ -89,7 +89,8 @@ setup(
89
89
  'Programming Language :: Python :: 3.8',
90
90
  'Programming Language :: Python :: 3.9',
91
91
  'Programming Language :: Python :: 3.10',
92
- 'Programming Language :: Python :: 3.11'
92
+ 'Programming Language :: Python :: 3.11',
93
+ 'Programming Language :: Python :: 3.12'
93
94
  ],
94
95
  python_requires='>=3.8',
95
96
  test_suite='tests',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes