pyopenrivercam 0.8.6__py3-none-any.whl → 0.8.7__py3-none-any.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.
- {pyopenrivercam-0.8.6.dist-info → pyopenrivercam-0.8.7.dist-info}/METADATA +4 -4
- {pyopenrivercam-0.8.6.dist-info → pyopenrivercam-0.8.7.dist-info}/RECORD +14 -14
- pyorc/__init__.py +1 -1
- pyorc/api/cameraconfig.py +140 -37
- pyorc/api/cross_section.py +58 -8
- pyorc/api/frames.py +19 -58
- pyorc/api/plot.py +75 -27
- pyorc/cli/cli_utils.py +12 -2
- pyorc/cli/main.py +12 -3
- pyorc/cv.py +111 -24
- pyorc/service/velocimetry.py +236 -174
- {pyopenrivercam-0.8.6.dist-info → pyopenrivercam-0.8.7.dist-info}/WHEEL +0 -0
- {pyopenrivercam-0.8.6.dist-info → pyopenrivercam-0.8.7.dist-info}/entry_points.txt +0 -0
- {pyopenrivercam-0.8.6.dist-info → pyopenrivercam-0.8.7.dist-info}/licenses/LICENSE +0 -0
pyorc/api/plot.py
CHANGED
|
@@ -6,8 +6,7 @@ import functools
|
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
7
|
import matplotlib.ticker as mticker
|
|
8
8
|
import numpy as np
|
|
9
|
-
from matplotlib import patheffects
|
|
10
|
-
from matplotlib.collections import QuadMesh
|
|
9
|
+
from matplotlib import colors, patheffects
|
|
11
10
|
|
|
12
11
|
from pyorc import helpers
|
|
13
12
|
|
|
@@ -132,7 +131,7 @@ def _base_plot(plot_func):
|
|
|
132
131
|
|
|
133
132
|
# check if dataset is a transect or not
|
|
134
133
|
is_transect = True if "points" in ref._obj.dims else False
|
|
135
|
-
|
|
134
|
+
kwargs = set_default_kwargs(kwargs, method=plot_func.__name__, mode=mode)
|
|
136
135
|
assert mode in ["local", "geographical", "camera"], 'Mode must be "local", "geographical" or "camera"'
|
|
137
136
|
if mode == "local":
|
|
138
137
|
x = ref._obj["x"].values
|
|
@@ -279,23 +278,22 @@ def _frames_plot(ref, ax=None, mode="local", **kwargs):
|
|
|
279
278
|
x = "xp"
|
|
280
279
|
y = "yp"
|
|
281
280
|
assert all(v in ref._obj.coords for v in [x, y]), f'required coordinates "{x}" and/or "{y}" are not available'
|
|
282
|
-
if
|
|
283
|
-
#
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
ref._obj[
|
|
289
|
-
ref._obj.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
# array has dims
|
|
296
|
-
QuadMesh.set_array(primitive, None)
|
|
297
|
-
else:
|
|
281
|
+
if x == "x":
|
|
282
|
+
# use a simple imshow, much faster
|
|
283
|
+
dx = abs(float(ref._obj[x][1] - ref._obj[x][0])) # x grid cell size
|
|
284
|
+
dy = abs(float(ref._obj[y][1] - ref._obj[y][0])) # y grid cell size
|
|
285
|
+
# Calculate extent with half grid cell expansion
|
|
286
|
+
extent = [
|
|
287
|
+
ref._obj[x].min().item() - 0.5 * dx,
|
|
288
|
+
ref._obj[x].max().item() + 0.5 * dx,
|
|
289
|
+
ref._obj[y].min().item() - 0.5 * dy,
|
|
290
|
+
ref._obj[y].max().item() + 0.5 * dy,
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
if x != "x":
|
|
298
294
|
primitive = ax.pcolormesh(ref._obj[x], ref._obj[y], ref._obj, **kwargs)
|
|
295
|
+
else:
|
|
296
|
+
primitive = ax.imshow(ref._obj, origin="upper", extent=extent, aspect="auto", **kwargs)
|
|
299
297
|
# fix axis limits to min and max of extent of frames
|
|
300
298
|
if mode == "geographical":
|
|
301
299
|
ax.set_extent(
|
|
@@ -371,7 +369,8 @@ class _Transect_PlotMethods:
|
|
|
371
369
|
_u = self._obj[v_eff] * np.sin(self._obj[v_dir])
|
|
372
370
|
_v = self._obj[v_eff] * np.cos(self._obj[v_dir])
|
|
373
371
|
s = np.abs(self._obj[v_eff].values)
|
|
374
|
-
x_moved, y_moved = x + _u * dt, y + _v * dt
|
|
372
|
+
# x_moved, y_moved = x + _u * dt, y + _v * dt
|
|
373
|
+
x_moved, y_moved = x + _u, y + _v
|
|
375
374
|
# transform to real-world
|
|
376
375
|
cols_moved, rows_moved = x_moved / camera_config.resolution, y_moved / camera_config.resolution
|
|
377
376
|
rows_moved = camera_config.shape[0] - rows_moved
|
|
@@ -496,9 +495,11 @@ class _Velocimetry_PlotMethods:
|
|
|
496
495
|
"""
|
|
497
496
|
# select lon and lat variables as coordinates
|
|
498
497
|
velocimetry = self._obj.velocimetry
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
498
|
+
v_x = self._obj["v_x"].values
|
|
499
|
+
v_y = self._obj["v_y"].values
|
|
500
|
+
u = v_x / (2 * 1e5) # 1e5 is aobut the amount of meters per degree
|
|
501
|
+
v = -v_y / (2 * 1e5)
|
|
502
|
+
s = (v_x**2 + v_y**2) ** 0.5
|
|
502
503
|
aff = velocimetry.camera_config.transform
|
|
503
504
|
theta = np.arctan2(aff.d, aff.a)
|
|
504
505
|
# rotate velocity vectors along angle theta to match the requested projection. this only changes values
|
|
@@ -519,9 +520,14 @@ class _Velocimetry_PlotMethods:
|
|
|
519
520
|
scalar velocity
|
|
520
521
|
|
|
521
522
|
"""
|
|
522
|
-
u
|
|
523
|
-
|
|
524
|
-
|
|
523
|
+
# u and v should be scaled to a nice proportionality for local plots. This is typically about 2x smaller
|
|
524
|
+
# than the m/s values
|
|
525
|
+
v_x = self._obj["v_x"].values
|
|
526
|
+
v_y = self._obj["v_y"].values
|
|
527
|
+
|
|
528
|
+
u = v_x / 2
|
|
529
|
+
v = -v_y / 2
|
|
530
|
+
s = (v_x**2 + v_y**2) ** 0.5
|
|
525
531
|
return u, v, s
|
|
526
532
|
|
|
527
533
|
def get_uv_camera(self, dt=0.1):
|
|
@@ -552,7 +558,8 @@ class _Velocimetry_PlotMethods:
|
|
|
552
558
|
yi = np.flipud(yi)
|
|
553
559
|
|
|
554
560
|
# follow the velocity vector over a short distance (dt*velocity)
|
|
555
|
-
x_moved, y_moved = xi + self._obj["v_x"] * dt, yi + self._obj["v_y"] * dt
|
|
561
|
+
# x_moved, y_moved = xi + self._obj["v_x"] * dt, yi + self._obj["v_y"] * dt
|
|
562
|
+
x_moved, y_moved = xi + self._obj["v_x"] / 2, yi + self._obj["v_y"] / 2
|
|
556
563
|
# transform to real-world
|
|
557
564
|
cols_moved, rows_moved = x_moved / camera_config.resolution, y_moved / camera_config.resolution
|
|
558
565
|
xs_moved, ys_moved = helpers.get_xs_ys(cols_moved, rows_moved, camera_config.transform)
|
|
@@ -578,10 +585,51 @@ class _Velocimetry_PlotMethods:
|
|
|
578
585
|
return u, v, s
|
|
579
586
|
|
|
580
587
|
|
|
588
|
+
def set_default_kwargs(kwargs, method="quiver", mode="local"):
|
|
589
|
+
"""Set color mapping default kwargs if no vmin and/or vmax is supplied."""
|
|
590
|
+
if mode == "local":
|
|
591
|
+
# width scale is in cm
|
|
592
|
+
width_scale = 0.02
|
|
593
|
+
elif mode == "geographical":
|
|
594
|
+
# widths are in degrees
|
|
595
|
+
width_scale = 0.00000025
|
|
596
|
+
elif mode == "camera":
|
|
597
|
+
# widths in pixels
|
|
598
|
+
width_scale = 3.0
|
|
599
|
+
else:
|
|
600
|
+
raise ValueError("mode must be one of 'local', 'geographical' or 'camera'")
|
|
601
|
+
if "cmap" not in kwargs:
|
|
602
|
+
kwargs["cmap"] = "rainbow" # the famous rainbow colormap!
|
|
603
|
+
if "vmin" not in kwargs and "vmax" not in kwargs and "norm" not in kwargs:
|
|
604
|
+
# set a normalization array
|
|
605
|
+
norm = [0, 0.05, 0.1, 0.2, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0]
|
|
606
|
+
kwargs["norm"] = colors.BoundaryNorm(norm, ncolors=256, extend="max")
|
|
607
|
+
if method == "quiver":
|
|
608
|
+
if "scale" not in kwargs:
|
|
609
|
+
kwargs["scale"] = 1 # larger quiver arrows
|
|
610
|
+
if "units" not in kwargs:
|
|
611
|
+
kwargs["units"] = "xy"
|
|
612
|
+
# for width, it will matter a lot what mode is used, width should be a dimensionless number
|
|
613
|
+
if "width" not in kwargs:
|
|
614
|
+
kwargs["width"] = width_scale
|
|
615
|
+
else:
|
|
616
|
+
kwargs["width"] *= width_scale # multiply
|
|
617
|
+
|
|
618
|
+
return kwargs
|
|
619
|
+
|
|
620
|
+
|
|
581
621
|
@_base_plot
|
|
582
622
|
def quiver(_, x, y, u, v, s=None, ax=None, **kwargs):
|
|
583
623
|
"""Create quiver plot from velocimetry results on new or existing axes.
|
|
584
624
|
|
|
625
|
+
Note that the `width` parameter is always in a unitless scale, with `1` providing a nice-looking default value
|
|
626
|
+
for all plot modes. The default value is usually very nice. If you do want to change:
|
|
627
|
+
|
|
628
|
+
The `scale` parameter defaults to 1, providing a nice looking arrow length. With a smaller (larger) value,
|
|
629
|
+
quivers will become longer (shorter).
|
|
630
|
+
|
|
631
|
+
The `width` parameter is in a unitless scale, with `1` providing a nice-looking default value.
|
|
632
|
+
|
|
585
633
|
Wraps :py:func:`matplotlib:matplotlib.pyplot.quiver`.
|
|
586
634
|
"""
|
|
587
635
|
if "color" in kwargs:
|
pyorc/cli/cli_utils.py
CHANGED
|
@@ -136,7 +136,12 @@ def get_gcps_optimized_fit(src, dst, height, width, c=2.0, camera_matrix=None, d
|
|
|
136
136
|
src_est, jacobian = cv2.projectPoints(_dst, rvec, tvec, camera_matrix, np.array(dist_coeffs))
|
|
137
137
|
src_est = np.array([list(point[0]) for point in src_est])
|
|
138
138
|
dst_est = cv.unproject_points(_src, _dst[:, -1], rvec, tvec, camera_matrix, dist_coeffs)
|
|
139
|
+
# add mean coordinates to estimated locations
|
|
139
140
|
dst_est = np.array(dst_est)[:, 0 : len(coord_mean)] + coord_mean
|
|
141
|
+
# also reverse rvec and tvec to real-world (rw), add mean coordinate, and reverse again.
|
|
142
|
+
rvec_cam, tvec_cam = cv.pose_world_to_camera(rvec, tvec)
|
|
143
|
+
tvec_cam += coord_mean
|
|
144
|
+
rvec, tvec = cv.pose_world_to_camera(rvec_cam, tvec_cam)
|
|
140
145
|
return src_est, dst_est, camera_matrix, dist_coeffs, rvec, tvec, err
|
|
141
146
|
|
|
142
147
|
|
|
@@ -327,8 +332,13 @@ def read_shape_as_gdf(fn=None, geojson=None, gdf=None):
|
|
|
327
332
|
gdf = gpd.read_file(fn)
|
|
328
333
|
crs = gdf.crs if hasattr(gdf, "crs") else None
|
|
329
334
|
# also read raw json, and check if crs attribute exists
|
|
330
|
-
|
|
331
|
-
|
|
335
|
+
if isinstance(fn, str):
|
|
336
|
+
with open(fn, "r") as f:
|
|
337
|
+
raw_json = json.load(f)
|
|
338
|
+
else:
|
|
339
|
+
# apparently a file object was provided
|
|
340
|
+
fn.seek(0)
|
|
341
|
+
raw_json = json.load(fn)
|
|
332
342
|
if "crs" not in raw_json:
|
|
333
343
|
# override the crs
|
|
334
344
|
crs = None
|
pyorc/cli/main.py
CHANGED
|
@@ -312,9 +312,17 @@ def camera_config(
|
|
|
312
312
|
@click.option(
|
|
313
313
|
"--cross",
|
|
314
314
|
type=click.Path(exists=True, resolve_path=True, dir_okay=False, file_okay=True),
|
|
315
|
+
help="Cross section file (*.geojson). This will be used for discharge estimation if the `transect` "
|
|
316
|
+
" section is provided in your recipe.",
|
|
317
|
+
callback=cli_utils.parse_cross_section_gdf,
|
|
318
|
+
required=False,
|
|
319
|
+
)
|
|
320
|
+
@click.option(
|
|
321
|
+
"--cross_wl",
|
|
322
|
+
type=click.Path(exists=True, resolve_path=True, dir_okay=False, file_okay=True),
|
|
315
323
|
help="Cross section file (*.geojson). If you provide this, you may add water level retrieval settings to the"
|
|
316
|
-
" recipe section
|
|
317
|
-
" [PyORC docs](https://localdevices.github.io/pyorc/user-guide/
|
|
324
|
+
" recipe in the section `water_level`. For more information see"
|
|
325
|
+
" [PyORC docs](https://localdevices.github.io/pyorc/user-guide/cross_section/index.html)",
|
|
318
326
|
callback=cli_utils.parse_cross_section_gdf,
|
|
319
327
|
required=False,
|
|
320
328
|
)
|
|
@@ -333,7 +341,7 @@ def camera_config(
|
|
|
333
341
|
)
|
|
334
342
|
@verbose_opt
|
|
335
343
|
@click.pass_context
|
|
336
|
-
def velocimetry(ctx, output, videofile, recipe, cameraconfig, prefix, h_a, cross, update, lowmem, verbose):
|
|
344
|
+
def velocimetry(ctx, output, videofile, recipe, cameraconfig, prefix, h_a, cross, cross_wl, update, lowmem, verbose):
|
|
337
345
|
"""CLI subcommand for velocimetry."""
|
|
338
346
|
log_level = max(10, 20 - 10 * verbose)
|
|
339
347
|
logger = log.setuplog("velocimetry", os.path.abspath("pyorc.log"), append=False, log_level=log_level)
|
|
@@ -345,6 +353,7 @@ def velocimetry(ctx, output, videofile, recipe, cameraconfig, prefix, h_a, cross
|
|
|
345
353
|
cameraconfig=cameraconfig,
|
|
346
354
|
h_a=h_a,
|
|
347
355
|
cross=cross,
|
|
356
|
+
cross_wl=cross_wl,
|
|
348
357
|
prefix=prefix,
|
|
349
358
|
output=output,
|
|
350
359
|
update=update,
|
pyorc/cv.py
CHANGED
|
@@ -9,7 +9,7 @@ import numpy as np
|
|
|
9
9
|
import rasterio
|
|
10
10
|
from scipy import optimize
|
|
11
11
|
from shapely.affinity import rotate
|
|
12
|
-
from shapely.geometry import LineString, Polygon
|
|
12
|
+
from shapely.geometry import LineString, Point, Polygon
|
|
13
13
|
from tqdm import tqdm
|
|
14
14
|
|
|
15
15
|
from . import helpers
|
|
@@ -88,6 +88,56 @@ def _combine_m(m1, m2):
|
|
|
88
88
|
return m_combi
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def _get_aoi_corners(dst_corners, resolution=None):
|
|
92
|
+
polygon = Polygon(dst_corners)
|
|
93
|
+
coords = np.array(polygon.exterior.coords)
|
|
94
|
+
# estimate the angle of the bounding box
|
|
95
|
+
# retrieve average line across AOI
|
|
96
|
+
point1 = (coords[0] + coords[3]) / 2
|
|
97
|
+
point2 = (coords[1] + coords[2]) / 2
|
|
98
|
+
diff = point2 - point1
|
|
99
|
+
angle = np.arctan2(diff[1], diff[0])
|
|
100
|
+
# rotate the polygon over this angle to get a proper bounding box
|
|
101
|
+
polygon_rotate = rotate(polygon, -angle, origin=tuple(dst_corners[0]), use_radians=True)
|
|
102
|
+
|
|
103
|
+
xmin, ymin, xmax, ymax = polygon_rotate.bounds
|
|
104
|
+
if resolution is not None:
|
|
105
|
+
xmin = helpers.round_to_multiple(xmin, resolution)
|
|
106
|
+
xmax = helpers.round_to_multiple(xmax, resolution)
|
|
107
|
+
ymin = helpers.round_to_multiple(ymin, resolution)
|
|
108
|
+
ymax = helpers.round_to_multiple(ymax, resolution)
|
|
109
|
+
|
|
110
|
+
bbox_coords = [(xmin, ymax), (xmax, ymax), (xmax, ymin), (xmin, ymin), (xmin, ymax)]
|
|
111
|
+
bbox = Polygon(bbox_coords)
|
|
112
|
+
# now rotate back
|
|
113
|
+
bbox = rotate(bbox, angle, origin=tuple(dst_corners[0]), use_radians=True)
|
|
114
|
+
return bbox
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _get_aoi_width_length(dst_corners):
|
|
118
|
+
points = [Point(x, y) for x, y, _ in dst_corners]
|
|
119
|
+
linecross = LineString([points[0], points[1]])
|
|
120
|
+
# linecross = LineString(dst_corners[0:2])
|
|
121
|
+
length = np.abs(_get_perpendicular_distance(points[-1], linecross))
|
|
122
|
+
point1 = np.array(dst_corners[0][0:2])
|
|
123
|
+
point2 = np.array(dst_corners[1][0:2])
|
|
124
|
+
diff = np.array(point2 - point1)
|
|
125
|
+
angle = np.arctan2(diff[1], diff[0])
|
|
126
|
+
|
|
127
|
+
# compute xy distance from line to other line making up the bounding box
|
|
128
|
+
xy_diff = np.array([np.sin(-angle) * length, np.cos(angle) * length])
|
|
129
|
+
points_pol = np.array([point1 - xy_diff, point1 + xy_diff, point2 + xy_diff, point2 - xy_diff])
|
|
130
|
+
# always make sure the order of the points of upstream-left, downstream-left, downstream-right, upstream-right
|
|
131
|
+
# if length <= 0:
|
|
132
|
+
# # negative length means the selected length is selected upstream of left-right cross section
|
|
133
|
+
# points_pol = np.array([point1 + xy_diff, point1, point2, point2 + xy_diff])
|
|
134
|
+
# else:
|
|
135
|
+
# # postive means it is selected downstream of left-right cross section
|
|
136
|
+
# points_pol = np.array([point1, point1 + xy_diff, point2 + xy_diff, point2])
|
|
137
|
+
|
|
138
|
+
return Polygon(points_pol)
|
|
139
|
+
|
|
140
|
+
|
|
91
141
|
def _smooth(img, stride):
|
|
92
142
|
"""Blur image through gaussian smoothing.
|
|
93
143
|
|
|
@@ -152,6 +202,52 @@ def _get_dist_coefs(k1):
|
|
|
152
202
|
return dist
|
|
153
203
|
|
|
154
204
|
|
|
205
|
+
def _get_perpendicular_distance(point, line):
|
|
206
|
+
"""Calculate perpendicular distance from point to line.
|
|
207
|
+
|
|
208
|
+
Line is extended if perpendicular distance is larger than the distance to the endpoints.
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
point : shapely.geometry.Point
|
|
213
|
+
x, y coordinates of point
|
|
214
|
+
line : shapely.geometry.LineString
|
|
215
|
+
line to calculate distance to
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
float
|
|
220
|
+
perpendicular distance from point to line
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
# Get coordinates of line endpoints
|
|
224
|
+
p1 = np.array(line.coords[0])
|
|
225
|
+
p2 = np.array(line.coords[1])
|
|
226
|
+
# Convert point to numpy array
|
|
227
|
+
p3 = np.array(point.coords[0])
|
|
228
|
+
|
|
229
|
+
# Calculate line vector
|
|
230
|
+
line_vector = p2 - p1
|
|
231
|
+
# Calculate vector from point to line start
|
|
232
|
+
point_vector = p3 - p1
|
|
233
|
+
|
|
234
|
+
# Calculate unit vector of line
|
|
235
|
+
unit_line = line_vector / np.linalg.norm(line_vector)
|
|
236
|
+
|
|
237
|
+
# Calculate projection length
|
|
238
|
+
projection_length = np.dot(point_vector, unit_line)
|
|
239
|
+
|
|
240
|
+
# Calculate perpendicular vector
|
|
241
|
+
perpendicular_vector = point_vector - projection_length * unit_line
|
|
242
|
+
perpendicular_distance = np.linalg.norm(perpendicular_vector)
|
|
243
|
+
|
|
244
|
+
# Use cross product to calculate side
|
|
245
|
+
cross_product = np.cross(line_vector, point_vector)
|
|
246
|
+
|
|
247
|
+
# Determine the sign of the perpendicular distance
|
|
248
|
+
return perpendicular_distance if cross_product > 0 else -perpendicular_distance
|
|
249
|
+
|
|
250
|
+
|
|
155
251
|
def get_cam_mtx(height, width, c=2.0, focal_length=None):
|
|
156
252
|
"""Compute camera matrix based on the given parameters for height, width, scaling factor, and focal length.
|
|
157
253
|
|
|
@@ -907,15 +1003,19 @@ def get_ortho(img, M, shape, flags=cv2.INTER_AREA):
|
|
|
907
1003
|
return cv2.warpPerspective(img, M, shape, flags=flags)
|
|
908
1004
|
|
|
909
1005
|
|
|
910
|
-
def get_aoi(dst_corners, resolution=None):
|
|
911
|
-
"""Get rectangular AOI from 4 user defined points within frames.
|
|
1006
|
+
def get_aoi(dst_corners, resolution=None, method="corners"):
|
|
1007
|
+
"""Get rectangular AOI from 3 or 4 user defined points within frames.
|
|
912
1008
|
|
|
913
1009
|
Parameters
|
|
914
1010
|
----------
|
|
915
1011
|
dst_corners : np.ndarray
|
|
916
|
-
corners of aoi, in order:
|
|
1012
|
+
corners of aoi, with `method="width_length"` in order: left-bank, right-bank, up/downstream point,
|
|
1013
|
+
with `method="corners"` in order: upstream-left, downstream-left, downstream-right, upstream-right.
|
|
917
1014
|
resolution : float
|
|
918
1015
|
resolution of intended reprojection, used to round the bbox to a whole number of intended pixels
|
|
1016
|
+
method : str
|
|
1017
|
+
can be "corners" or "width_length". With "corners", the AOI is defined by the four corners of the rectangle.
|
|
1018
|
+
With "width" length, the AOI is defined by the width (2 points) and length (1 point) of the rectangle.
|
|
919
1019
|
|
|
920
1020
|
Returns
|
|
921
1021
|
-------
|
|
@@ -923,27 +1023,14 @@ def get_aoi(dst_corners, resolution=None):
|
|
|
923
1023
|
bounding box of aoi (with rotated affine)
|
|
924
1024
|
|
|
925
1025
|
"""
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
angle = np.arctan2(diff[1], diff[0])
|
|
934
|
-
# rotate the polygon over this angle to get a proper bounding box
|
|
935
|
-
polygon_rotate = rotate(polygon, -angle, origin=tuple(dst_corners[0]), use_radians=True)
|
|
936
|
-
xmin, ymin, xmax, ymax = polygon_rotate.bounds
|
|
937
|
-
if resolution is not None:
|
|
938
|
-
xmin = helpers.round_to_multiple(xmin, resolution)
|
|
939
|
-
xmax = helpers.round_to_multiple(xmax, resolution)
|
|
940
|
-
ymin = helpers.round_to_multiple(ymin, resolution)
|
|
941
|
-
ymax = helpers.round_to_multiple(ymax, resolution)
|
|
1026
|
+
if method == "corners":
|
|
1027
|
+
bbox = _get_aoi_corners(dst_corners, resolution)
|
|
1028
|
+
elif method == "width_length":
|
|
1029
|
+
bbox = _get_aoi_width_length(dst_corners)
|
|
1030
|
+
|
|
1031
|
+
else:
|
|
1032
|
+
raise ValueError("method must be 'corners' or 'width_length'")
|
|
942
1033
|
|
|
943
|
-
bbox_coords = [(xmin, ymax), (xmax, ymax), (xmax, ymin), (xmin, ymin), (xmin, ymax)]
|
|
944
|
-
bbox = Polygon(bbox_coords)
|
|
945
|
-
# now rotate back
|
|
946
|
-
bbox = rotate(bbox, angle, origin=tuple(dst_corners[0]), use_radians=True)
|
|
947
1034
|
return bbox
|
|
948
1035
|
|
|
949
1036
|
|