sgspy 1.0.2__cp311-cp311-manylinux_2_28_x86_64.whl
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.
- sgspy/__init__.py +82 -0
- sgspy/_sgs.cpython-311-x86_64-linux-gnu.so +0 -0
- sgspy/calculate/__init__.py +18 -0
- sgspy/calculate/pca/__init__.py +2 -0
- sgspy/calculate/pca/pca.py +158 -0
- sgspy/calculate/representation/__init__.py +2 -0
- sgspy/calculate/representation/representation.py +3 -0
- sgspy/sample/__init__.py +30 -0
- sgspy/sample/ahels/__init__.py +2 -0
- sgspy/sample/ahels/ahels.py +3 -0
- sgspy/sample/clhs/__init__.py +2 -0
- sgspy/sample/clhs/clhs.py +202 -0
- sgspy/sample/nc/__init__.py +2 -0
- sgspy/sample/nc/nc.py +3 -0
- sgspy/sample/srs/__init__.py +2 -0
- sgspy/sample/srs/srs.py +228 -0
- sgspy/sample/strat/__init__.py +2 -0
- sgspy/sample/strat/strat.py +394 -0
- sgspy/sample/systematic/__init__.py +2 -0
- sgspy/sample/systematic/systematic.py +233 -0
- sgspy/stratify/__init__.py +27 -0
- sgspy/stratify/breaks/__init__.py +2 -0
- sgspy/stratify/breaks/breaks.py +222 -0
- sgspy/stratify/kmeans/__init__.py +2 -0
- sgspy/stratify/kmeans/kmeans.py +3 -0
- sgspy/stratify/map/__init__.py +2 -0
- sgspy/stratify/map/map_stratifications.py +244 -0
- sgspy/stratify/poly/__init__.py +2 -0
- sgspy/stratify/poly/poly.py +170 -0
- sgspy/stratify/quantiles/__init__.py +2 -0
- sgspy/stratify/quantiles/quantiles.py +276 -0
- sgspy/utils/__init__.py +18 -0
- sgspy/utils/plot.py +143 -0
- sgspy/utils/raster.py +605 -0
- sgspy/utils/vector.py +268 -0
- sgspy-1.0.2.data/data/sgspy/libonedal.so.3 +0 -0
- sgspy-1.0.2.data/data/sgspy/proj.db +0 -0
- sgspy-1.0.2.dist-info/METADATA +13 -0
- sgspy-1.0.2.dist-info/RECORD +40 -0
- sgspy-1.0.2.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
#
|
|
3
|
+
# Project: sgs
|
|
4
|
+
# Purpose: simple random sampling (srs)
|
|
5
|
+
# Author: Joseph Meyer
|
|
6
|
+
# Date: June, 2025
|
|
7
|
+
#
|
|
8
|
+
# ******************************************************************************
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# @defgroup user_systematic systematic
|
|
12
|
+
# @ingroup user_sample
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import site
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
import matplotlib.pyplot as plt
|
|
21
|
+
|
|
22
|
+
from sgspy.utils import (
|
|
23
|
+
SpatialRaster,
|
|
24
|
+
SpatialVector,
|
|
25
|
+
plot,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
#ensure _sgs binary can be found
|
|
29
|
+
site_packages = list(filter(lambda x : 'site-packages' in x, site.getsitepackages()))[0]
|
|
30
|
+
sys.path.append(os.path.join(site_packages, "sgspy"))
|
|
31
|
+
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
|
32
|
+
from _sgs import systematic_cpp
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# @ingroup user_systematic
|
|
36
|
+
# This function conducts systematic sampling within the extent of
|
|
37
|
+
# the raster given. The 'cellsize' parameter specifies the grid size,
|
|
38
|
+
# the 'shape' parameter specifies the grid shape, and the 'location'
|
|
39
|
+
# parameter specifies where in the grid a sample should fall into.
|
|
40
|
+
#
|
|
41
|
+
# shape can be one of 'square', and 'hexagon'.
|
|
42
|
+
# location can be one of 'corners', 'centers', 'random'.
|
|
43
|
+
#
|
|
44
|
+
# An access vector of LineString or MultiLineString type can be provided.
|
|
45
|
+
# buff_outer specifies the buffer distance around the geometry which is
|
|
46
|
+
# allowed to be included in the sampling, buff_inner specifies the geometry
|
|
47
|
+
# which is not allowed to be included in the sampling. buff_outer must
|
|
48
|
+
# be larger than buff_inner. For a multi-layer vector, layer_name
|
|
49
|
+
# must be provided.
|
|
50
|
+
#
|
|
51
|
+
# A vector containing existing sample points can be provided. If this is
|
|
52
|
+
# the case then all of the points in the existing sample are automatically
|
|
53
|
+
# added and random samples are then chosen as required until num_samples
|
|
54
|
+
# number of samples are chosen.
|
|
55
|
+
#
|
|
56
|
+
# If the force parameter is True, then the the samples are forced to
|
|
57
|
+
# fall on an index which is NOT a no data value. This may result
|
|
58
|
+
# in some grids not being sampled.
|
|
59
|
+
#
|
|
60
|
+
# Examples
|
|
61
|
+
# --------------------
|
|
62
|
+
# rast = sgspy.SpatialRaster("raster.tif") @n
|
|
63
|
+
# samples = sgspy.sample.systematic(rast, 500, "hexagon", "centers")
|
|
64
|
+
#
|
|
65
|
+
# rast = sgspy.SpatialRaster("raster.tif") @n
|
|
66
|
+
# samples = sgspy.sample.systematic(rast, 500, "square", "corners", plot=True, filename="systematic_samples.shp")
|
|
67
|
+
#
|
|
68
|
+
# rast = sgspy.SpatialRaster("raster.tif") @n
|
|
69
|
+
# samples = sgspy.sample.systematic(rast, 500, "hexagon", "random", force=True)
|
|
70
|
+
#
|
|
71
|
+
# rast = sgspy.SpatialRaster("raster.tif") @n
|
|
72
|
+
# access = sgspy.SpatialVector("access_network.shp") @n
|
|
73
|
+
# samples = sgspy.sample.systematic(rast, 500, "hexagon", "corners", access=access, buff_outer=300)
|
|
74
|
+
#
|
|
75
|
+
# rast = sgspy.SpatialRaster("raster.tif") @n
|
|
76
|
+
# access = sgspy.SpatialVector("existing_samples.shp") @n
|
|
77
|
+
# samples = sgspy.sample.systematic(rast, 500, "hexagon", "corners", existing=existing)
|
|
78
|
+
#
|
|
79
|
+
# Parameters
|
|
80
|
+
# --------------------
|
|
81
|
+
# rast : SpatialRaster @n
|
|
82
|
+
# the raster to be sampled @n @n
|
|
83
|
+
# cellsize : float @n
|
|
84
|
+
# the size of the grid cells to be sampled @n @n
|
|
85
|
+
# shape : str @n
|
|
86
|
+
# the shape of the grid cells to be sampled @n @n
|
|
87
|
+
# location : str @n
|
|
88
|
+
# the location within the grid cell to be sampled @n @n
|
|
89
|
+
# existing (optional) : SpatialVector @n
|
|
90
|
+
# a vector specifying existing sample points @n @n
|
|
91
|
+
# access (optional) : SpatialVector @n
|
|
92
|
+
# a vector specifying access network @n @n
|
|
93
|
+
# layer_name (optional) : str @n
|
|
94
|
+
# the layer within access that is to be used for sampling @n @n
|
|
95
|
+
# buff_inner (optional) : int | float @n
|
|
96
|
+
# buffer boundary specifying distance from access which CANNOT be sampled @n @n
|
|
97
|
+
# buff_outer (optional) : int | float @n
|
|
98
|
+
# buffer boundary specifying distance from access which CAN be sampled @n @n
|
|
99
|
+
# force : bool @n
|
|
100
|
+
# True if samples are not allowed to fall on a nodata pixel @n @n
|
|
101
|
+
# plot : bool @n
|
|
102
|
+
# whether or not to plot the resulting samples @n @n
|
|
103
|
+
# filename : str @n
|
|
104
|
+
# the filename to write to or "" if not to write @n @n
|
|
105
|
+
#
|
|
106
|
+
# Returns
|
|
107
|
+
# --------------------
|
|
108
|
+
# a SpatialVector object containing point geometries of sample locations
|
|
109
|
+
def systematic(
|
|
110
|
+
rast: SpatialRaster,
|
|
111
|
+
cellsize: int | float,
|
|
112
|
+
shape: str = "square",
|
|
113
|
+
location: str = "centers",
|
|
114
|
+
existing: Optional[SpatialVector] = None,
|
|
115
|
+
access: Optional[SpatialVector] = None,
|
|
116
|
+
layer_name: Optional[str] = None,
|
|
117
|
+
buff_inner: Optional[int | float] = None,
|
|
118
|
+
buff_outer: Optional[int | float] = None,
|
|
119
|
+
force: bool = False,
|
|
120
|
+
plot: bool = False,
|
|
121
|
+
filename: str = ""):
|
|
122
|
+
|
|
123
|
+
if type(rast) is not SpatialRaster:
|
|
124
|
+
raise TypeError("'rast' parameter must be of type sgspy.SpatialRaster.")
|
|
125
|
+
|
|
126
|
+
if type(cellsize) not in [int, float]:
|
|
127
|
+
raise TypeError("'cellsize' parameter must be of type int or float.")
|
|
128
|
+
|
|
129
|
+
if type(shape) is not str:
|
|
130
|
+
raise TypeError("'shape' paramter must be of type str.")
|
|
131
|
+
|
|
132
|
+
if type(location) is not str:
|
|
133
|
+
raise TypeError("'location' parameter must be of type str.")
|
|
134
|
+
|
|
135
|
+
if existing is not None and type(existing) is not SpatialVector:
|
|
136
|
+
raise TypeError("'existing' parameter, if given, must be of type sgspy.SpatialVector.")
|
|
137
|
+
|
|
138
|
+
if access is not None and type(access) is not SpatialVector:
|
|
139
|
+
raise TypeError("'access' parameter, if given, must be of type sgspy.SpatialVector.")
|
|
140
|
+
|
|
141
|
+
if layer_name is not None and type(layer_name) is not str:
|
|
142
|
+
raise TypeError("'layer_name' parameter, if given, must be of type str.")
|
|
143
|
+
|
|
144
|
+
if buff_inner is not None and type(buff_inner) not in [int, float]:
|
|
145
|
+
raise TypeError("'buff_inner' parameter, if given, must be of type int or float.")
|
|
146
|
+
|
|
147
|
+
if buff_outer is not None and type(buff_outer) not in [int, float]:
|
|
148
|
+
raise TypeError("'buff_outer' parameter, if given, must be of type int or float.")
|
|
149
|
+
|
|
150
|
+
if type(force) is not bool:
|
|
151
|
+
raise TypeError("'force' parameter must be of type bool.")
|
|
152
|
+
|
|
153
|
+
if type(plot) is not bool:
|
|
154
|
+
raise TypeError("'plot' parameter must be of type bool.")
|
|
155
|
+
|
|
156
|
+
if type(filename) is not str:
|
|
157
|
+
raise TypeError("'filename' parameter must be of type str.")
|
|
158
|
+
|
|
159
|
+
if rast.closed:
|
|
160
|
+
raise RuntimeError("the C++ object which the raster object wraps has been cleaned up and closed.")
|
|
161
|
+
|
|
162
|
+
if cellsize <= 0:
|
|
163
|
+
raise ValueError("cellsize must be greater than 0")
|
|
164
|
+
|
|
165
|
+
if shape not in ["square", "hexagon"]:
|
|
166
|
+
raise ValueError("shape parameter must be one of 'square', 'hexagon'")
|
|
167
|
+
|
|
168
|
+
if location not in ["centers", "corners", "random"]:
|
|
169
|
+
raise ValueError("location parameter must be one of 'centers', 'corners', 'random'")
|
|
170
|
+
|
|
171
|
+
if (access):
|
|
172
|
+
if layer_name is None:
|
|
173
|
+
if len(access.layers) > 1:
|
|
174
|
+
raise ValueError("if there are multiple layers in the access vector, layer_name parameter must be passed.")
|
|
175
|
+
layer_name = access.layers[0]
|
|
176
|
+
|
|
177
|
+
if layer_name not in access.layers:
|
|
178
|
+
raise ValueError("layer specified by 'layer_name' does not exist in the access vector")
|
|
179
|
+
|
|
180
|
+
if buff_inner is None or buff_inner < 0:
|
|
181
|
+
buff_inner = 0
|
|
182
|
+
|
|
183
|
+
if buff_outer is None or buff_outer < 0:
|
|
184
|
+
raise ValueError("if an access vector is given, buff_outer must be a float greater than 0.")
|
|
185
|
+
|
|
186
|
+
if buff_inner >= buff_outer:
|
|
187
|
+
raise ValueError("buff_outer must be greater than buff_inner")
|
|
188
|
+
|
|
189
|
+
access_vector = access.cpp_vector
|
|
190
|
+
else:
|
|
191
|
+
access_vector = None
|
|
192
|
+
layer_name = ""
|
|
193
|
+
buff_inner = -1
|
|
194
|
+
buff_outer = -1
|
|
195
|
+
|
|
196
|
+
if (existing):
|
|
197
|
+
existing_vector = existing.cpp_vector
|
|
198
|
+
else:
|
|
199
|
+
existing_vector = None
|
|
200
|
+
|
|
201
|
+
[samples, points, grid] = systematic_cpp(
|
|
202
|
+
rast.cpp_raster,
|
|
203
|
+
cellsize,
|
|
204
|
+
shape,
|
|
205
|
+
location,
|
|
206
|
+
existing_vector,
|
|
207
|
+
access_vector,
|
|
208
|
+
layer_name,
|
|
209
|
+
buff_inner,
|
|
210
|
+
buff_outer,
|
|
211
|
+
force,
|
|
212
|
+
plot,
|
|
213
|
+
filename
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
#plot new vector if requested
|
|
217
|
+
if plot:
|
|
218
|
+
fig, ax = plt.subplots()
|
|
219
|
+
ax.set_xlim([rast.xmin, rast.xmax])
|
|
220
|
+
ax.set_ylim([rast.ymin, rast.ymax])
|
|
221
|
+
rast.plot(ax, band=rast.bands[0])
|
|
222
|
+
title="samples on " + rast.bands[0]
|
|
223
|
+
|
|
224
|
+
#plot grid
|
|
225
|
+
for shape in grid:
|
|
226
|
+
ax.plot(shape[0], shape[1], '-k')
|
|
227
|
+
|
|
228
|
+
#plot sample points
|
|
229
|
+
ax.plot(points[0], points[1], '.r')
|
|
230
|
+
ax.set_title(label=title)
|
|
231
|
+
plt.show()
|
|
232
|
+
|
|
233
|
+
return SpatialVector(samples)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
##
|
|
2
|
+
# @defgroup user_stratify stratify
|
|
3
|
+
# @ingroup user
|
|
4
|
+
#
|
|
5
|
+
# Documentation for the stratification functions.
|
|
6
|
+
|
|
7
|
+
from . import (
|
|
8
|
+
breaks,
|
|
9
|
+
kmeans,
|
|
10
|
+
poly,
|
|
11
|
+
quantiles,
|
|
12
|
+
map,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .breaks import breaks
|
|
16
|
+
from .kmeans import kmeans
|
|
17
|
+
from .poly import poly
|
|
18
|
+
from .quantiles import quantiles
|
|
19
|
+
from .map import map
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"breaks",
|
|
23
|
+
"kmeans",
|
|
24
|
+
"poly",
|
|
25
|
+
"quantiles",
|
|
26
|
+
"map",
|
|
27
|
+
]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
#
|
|
3
|
+
# Project: sgs
|
|
4
|
+
# Purpose: simple random sampling (srs)
|
|
5
|
+
# Author: Joseph Meyer
|
|
6
|
+
# Date: June, 2025
|
|
7
|
+
#
|
|
8
|
+
# ******************************************************************************
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# @defgroup user_breaks breaks
|
|
12
|
+
# @ingroup user_stratify
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import site
|
|
17
|
+
import tempfile
|
|
18
|
+
import numpy as np
|
|
19
|
+
from sgspy.utils import SpatialRaster
|
|
20
|
+
|
|
21
|
+
#ensure _sgs binary can be found
|
|
22
|
+
site_packages = list(filter(lambda x : 'site-packages' in x, site.getsitepackages()))[0]
|
|
23
|
+
sys.path.append(os.path.join(site_packages, "sgspy"))
|
|
24
|
+
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
|
|
25
|
+
from _sgs import breaks_cpp
|
|
26
|
+
|
|
27
|
+
GIGABYTE = 1073741824
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# @ingroup user_breaks
|
|
31
|
+
# This function conducts stratification on the raster given
|
|
32
|
+
# according to the user defined breaks.
|
|
33
|
+
#
|
|
34
|
+
# The breaks may be defined as a single list of ints or floats
|
|
35
|
+
# in the case of a raster with a single band. Or, they may be defined
|
|
36
|
+
# as a list of ints or floats where the index indicates the raster band.
|
|
37
|
+
# Or, they may be defined as a dict where the (str) key represents
|
|
38
|
+
# the raster band and the value is a list of ints or floats.
|
|
39
|
+
#
|
|
40
|
+
# if the map parameter is given, an extra output band will be used which combines
|
|
41
|
+
# all stratifications from the previous bands used. A single value in the mapped
|
|
42
|
+
# output band corresponds to a single combination of values from the previous
|
|
43
|
+
# bands.
|
|
44
|
+
#
|
|
45
|
+
# the filename parameter specifies an output file name. Right now the only file format
|
|
46
|
+
# excepted is GTiff (.tif).
|
|
47
|
+
#
|
|
48
|
+
# the thread_count parameter specifies the number of threads which this function will
|
|
49
|
+
# utilize in the case where the raster is large and may not fit in memory. If the full
|
|
50
|
+
# raster can fit in memory and does not need to be processed in blocks, this argument
|
|
51
|
+
# will be ignored. The default is 8 threads, although the optimal number will depend significantly
|
|
52
|
+
# on the hardware being used and my be less or more than 8.
|
|
53
|
+
#
|
|
54
|
+
# The driver_options parameter is used to specify creation options for a the output raster.
|
|
55
|
+
# See options for the Gtiff driver here: https://gdal.org/en/stable/drivers/raster/gtiff.html#creation-options
|
|
56
|
+
# The keys in the driver_options dict must be strings, the values are converted to string.
|
|
57
|
+
# The options must be valid for the driver corresponding to the filename, and if filename is not given
|
|
58
|
+
# they must be valid for the GTiff format, as that is the format used to store temporary raster files.
|
|
59
|
+
# Note that if this parameter is given, but filename is not and the raster fits entirely in memory, the
|
|
60
|
+
# driver_options parameter will be ignored.
|
|
61
|
+
#
|
|
62
|
+
# Examples
|
|
63
|
+
# --------------------
|
|
64
|
+
# rast = sgspy.SpatialRaster("multi_band_rast.tif") @n
|
|
65
|
+
# srast = sgspy.stratify.breaks(rast, breaks={"band_name1": [3, 5, 11, 18]})
|
|
66
|
+
#
|
|
67
|
+
# rast = sgspy.SpatialRaster("single_band_rast.tif") @n
|
|
68
|
+
# srast = sgspy.stratify.breaks(rast, breaks=[20, 40, 60, 80], filename="breaks.tif", driver_options={"COMPRESS", "LZW"}))
|
|
69
|
+
#
|
|
70
|
+
# rast = sgspy.SpatialRaster("multi_band_rast.tif") @n
|
|
71
|
+
# srast = sgspy.stratify.breaks(rast, breaks={"band_name1": [3, 5, 11, 10], "band_name2": [20, 40, 60, 80]}, map=True)
|
|
72
|
+
#
|
|
73
|
+
# rast = sgspy.SpatialRaster("multi_band_rast.tif") @n
|
|
74
|
+
# srast = sgspy.stratify.breaks(rast, breaks=[[3, 5, 11, 18], [40, 60, 80], [2, 5]])
|
|
75
|
+
#
|
|
76
|
+
# Parameters
|
|
77
|
+
# --------------------
|
|
78
|
+
# rast : SpatialRaster @n
|
|
79
|
+
# raster data structure containing the raster to stratify @n @n
|
|
80
|
+
# breaks : list[float | list[float]] | dict[str, list[float]], @n
|
|
81
|
+
# user defined breaks to stratify @n @n
|
|
82
|
+
# map : bool @n
|
|
83
|
+
# whether to map the stratification of multiple raster bands onto a single band @n @n
|
|
84
|
+
# filename : str @n
|
|
85
|
+
# filename to write to or '' if no file should be written @n @n
|
|
86
|
+
# thread_count : int @n
|
|
87
|
+
# the number of threads to use when multithreading large images @n @n
|
|
88
|
+
# driver_options : dict[] @n
|
|
89
|
+
# the creation options as defined by GDAL which will be passed when creating output files @n @n
|
|
90
|
+
#
|
|
91
|
+
# Returns
|
|
92
|
+
# --------------------
|
|
93
|
+
# a SpatialRaster object containing stratified raster bands.
|
|
94
|
+
def breaks(
|
|
95
|
+
rast: SpatialRaster,
|
|
96
|
+
breaks: list[float | list[float]] | dict[str, list[float]],
|
|
97
|
+
map: bool = False,
|
|
98
|
+
filename: str = '',
|
|
99
|
+
thread_count: int = 8,
|
|
100
|
+
driver_options: dict = None
|
|
101
|
+
):
|
|
102
|
+
|
|
103
|
+
MAX_STRATA_VAL = 2147483647 #maximum value stored within a 32-bit signed integer to ensure no overflow
|
|
104
|
+
|
|
105
|
+
if type(rast) is not SpatialRaster:
|
|
106
|
+
raise TypeError("'rast' parameter must be of type sgspy.SpatialRaster")
|
|
107
|
+
|
|
108
|
+
if type(breaks) not in [list, dict]:
|
|
109
|
+
raise TypeError("'breaks' parameter must be of type list or dict.")
|
|
110
|
+
|
|
111
|
+
if type(map) is not bool:
|
|
112
|
+
raise TypeError("'map' parameter must be of type bool.")
|
|
113
|
+
|
|
114
|
+
if type(filename) is not str:
|
|
115
|
+
raise TypeError("'filename' parameter must be of type str.")
|
|
116
|
+
|
|
117
|
+
if type(thread_count) is not int:
|
|
118
|
+
raise TypeError("'thread_count' parameter must be of type int.")
|
|
119
|
+
|
|
120
|
+
if driver_options is not None and type(driver_options) is not dict:
|
|
121
|
+
raise TypeError("'driver_options' parameter, if givne, must be of type dict.")
|
|
122
|
+
|
|
123
|
+
if rast.closed:
|
|
124
|
+
raise RuntimeError("the C++ object which the raster object wraps has been cleaned up and closed.")
|
|
125
|
+
|
|
126
|
+
breaks_dict = {}
|
|
127
|
+
large_raster = False
|
|
128
|
+
temp_folder = ""
|
|
129
|
+
|
|
130
|
+
if type(breaks) is list and len(breaks) < 1:
|
|
131
|
+
raise ValueError("breaks list must contain at least one element.")
|
|
132
|
+
|
|
133
|
+
if type(breaks) is list and type(breaks[0]) is list:
|
|
134
|
+
#error check number of rasters bands
|
|
135
|
+
if len(breaks) != rast.band_count:
|
|
136
|
+
raise ValueError("number of lists of breaks must be equal to the number of raster bands.")
|
|
137
|
+
|
|
138
|
+
for i in range(len(breaks)):
|
|
139
|
+
breaks_dict[i] = breaks[i]
|
|
140
|
+
|
|
141
|
+
elif type(breaks) is list and type(breaks[0]) in [int, float]:
|
|
142
|
+
#error check number of raster bands
|
|
143
|
+
if rast.band_count != 1:
|
|
144
|
+
raise ValueError("if breaks is a single list, raster must have a single band (has {}).".format(rast.band_count))
|
|
145
|
+
|
|
146
|
+
breaks_dict[0] = breaks
|
|
147
|
+
|
|
148
|
+
elif type(breaks) is list:
|
|
149
|
+
raise TypeError("if 'breaks' parameter is of type list, it must be filled with with values of type list, int, or float.")
|
|
150
|
+
|
|
151
|
+
else: #breaks is a dict
|
|
152
|
+
for key, val in breaks.items():
|
|
153
|
+
if type(key) is not str:
|
|
154
|
+
raise TypeError("if 'breaks' parameter is a dict, all keys must be of type str.")
|
|
155
|
+
if type(val) is not list:
|
|
156
|
+
raise TypeError("if 'breaks' parameter is a dict, all values in the key values pairs must be of type list[float].")
|
|
157
|
+
if key not in rast.bands:
|
|
158
|
+
raise ValueError("breaks dict key must be a valid band name (see SpatialRaster.bands for list of names)")
|
|
159
|
+
|
|
160
|
+
breaks_dict[rast.band_name_dict[key]] = val
|
|
161
|
+
|
|
162
|
+
#error check max value for potential overflow error
|
|
163
|
+
max_mapped_strata = int(map)
|
|
164
|
+
for _, val in breaks_dict.items():
|
|
165
|
+
strata_count = len(val) + 1
|
|
166
|
+
if strata_count > MAX_STRATA_VAL:
|
|
167
|
+
raise ValueError("one of the breaks given will cause an integer overflow error because the max strata number is too large.")
|
|
168
|
+
|
|
169
|
+
max_mapped_strata = max_mapped_strata * strata_count
|
|
170
|
+
|
|
171
|
+
if max_mapped_strata > MAX_STRATA_VAL:
|
|
172
|
+
raise ValueError("the mapped strata will cause an overflow error because the max strata number is too large.")
|
|
173
|
+
|
|
174
|
+
if thread_count < 1:
|
|
175
|
+
raise ValueError("number of threads can't be less than 1.")
|
|
176
|
+
|
|
177
|
+
#ensure driver options keys are string, and convert driver options vals to string
|
|
178
|
+
driver_options_str = {}
|
|
179
|
+
if driver_options:
|
|
180
|
+
for (key, val) in driver_options.items():
|
|
181
|
+
if type(key) is not str:
|
|
182
|
+
raise ValueError("the key for all key/value pairs in the driver_options dict must be a string.")
|
|
183
|
+
driver_options_str[key] = str(val)
|
|
184
|
+
|
|
185
|
+
raster_size_bytes = 0
|
|
186
|
+
height = rast.height
|
|
187
|
+
width = rast.width
|
|
188
|
+
for key, _ in breaks_dict.items():
|
|
189
|
+
pixel_size = rast.cpp_raster.get_raster_band_type_size(key)
|
|
190
|
+
band_size = height * width * pixel_size
|
|
191
|
+
raster_size_bytes += band_size
|
|
192
|
+
if band_size >= GIGABYTE:
|
|
193
|
+
large_raster = True
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
#if large_raster is true, the C++ function will process the raster in blocks
|
|
197
|
+
large_raster = large_raster or (raster_size_bytes > GIGABYTE * 4)
|
|
198
|
+
|
|
199
|
+
#make a temp directory which will be deleted if there is any problem when calling the cpp function
|
|
200
|
+
temp_dir = tempfile.mkdtemp()
|
|
201
|
+
rast.have_temp_dir = True
|
|
202
|
+
rast.temp_dir = temp_dir
|
|
203
|
+
|
|
204
|
+
#call stratify breaks function
|
|
205
|
+
srast = SpatialRaster(breaks_cpp(
|
|
206
|
+
rast.cpp_raster,
|
|
207
|
+
breaks_dict,
|
|
208
|
+
map,
|
|
209
|
+
filename,
|
|
210
|
+
large_raster,
|
|
211
|
+
thread_count,
|
|
212
|
+
temp_dir,
|
|
213
|
+
driver_options_str
|
|
214
|
+
))
|
|
215
|
+
|
|
216
|
+
#now that it's created, give the cpp raster object ownership of the temporary directory
|
|
217
|
+
rast.have_temp_dir = False
|
|
218
|
+
srast.cpp_raster.set_temp_dir(temp_dir)
|
|
219
|
+
srast.temp_dataset = filename == "" and large_raster
|
|
220
|
+
srast.filename = filename
|
|
221
|
+
|
|
222
|
+
return srast
|