Coverage for py_tools_ds/geo/coord_calc.py: 28%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
3# py_tools_ds - A collection of geospatial data analysis tools that simplify standard
4# operations when handling geospatial raster and vector data as well as projections.
5#
6# Copyright (C) 2016-2021
7# - Daniel Scheffler (GFZ Potsdam, daniel.scheffler@gfz-potsdam.de)
8# - Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences Potsdam,
9# Germany (https://www.gfz-potsdam.de/)
10#
11# This software was developed within the context of the GeoMultiSens project funded
12# by the German Federal Ministry of Education and Research
13# (project grant code: 01 IS 14 010 A-C).
14#
15# Licensed under the Apache License, Version 2.0 (the "License");
16# you may not use this file except in compliance with the License.
17# You may obtain a copy of the License at
18#
19# http://www.apache.org/licenses/LICENSE-2.0
20#
21# Unless required by applicable law or agreed to in writing, software
22# distributed under the License is distributed on an "AS IS" BASIS,
23# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24# See the License for the specific language governing permissions and
25# limitations under the License.
27import warnings
28from typing import Iterable, Tuple # noqa: F401
29import numpy as np
31# custom
32from shapely.geometry import Polygon
34__author__ = "Daniel Scheffler"
37def get_corner_coordinates(gt, cols, rows):
38 """Return (ULxy, LLxy, LRxy, URxy) in the same coordinate units like the given geotransform."""
39 ext = []
40 xarr = [0, cols]
41 yarr = [0, rows]
42 for px in xarr:
43 for py in yarr:
44 x = gt[0] + (px * gt[1]) + (py * gt[2])
45 y = gt[3] + (px * gt[4]) + (py * gt[5])
46 ext.append([x, y])
47 yarr.reverse()
48 return ext
51def calc_FullDataset_corner_positions(mask_1bit, assert_four_corners=True, algorithm='shapely'):
52 # type: (np.ndarray, bool, str) -> list
53 """Calculate the image coordinates of the true data corners from a nodata mask.
55 NOTE: Algorithm 'shapely' calculates the corner coordinates of the convex hull of the given mask. Since the convex
56 hull not always reflects all of the true corner coordinates the result can have a limitation in this regard.
58 :param mask_1bit: 2D-numpy array 1bit
59 :param assert_four_corners: <bool> whether to assert four corners or not
60 :param algorithm: <str> 'shapely' or 'numpy' (default: 'shapely')
61 :return: [UL, UR, LL, LR] as [(ULrow,ULcol),(URrow,URcol),...]
62 """
63 # check if the mask extent covers real data or only nodata
64 pixVals = np.unique(mask_1bit)
65 if not (True in pixVals or 1 in pixVals):
66 # 'According to the given mask the mask extent is completely outside the image.
67 # No calculation of image corner coordinates possible.'
68 return []
70 rows, cols = mask_1bit.shape[:2]
72 if sum([mask_1bit[0, 0], mask_1bit[0, cols - 1], mask_1bit[rows - 1, 0], mask_1bit[rows - 1, cols - 1]]) == 4:
73 # if mask contains pixel value 1 in all of the four corners: return outer corner coords
74 out = [(0, 0), (0, cols - 1), (rows - 1, 0), (rows - 1, cols - 1)] # UL, UR, LL, LR
75 else:
76 assert algorithm in ['shapely', 'numpy'], "Algorithm '%' does not exist. Choose between 'shapely' and 'numpy'."
78 if algorithm == 'shapely':
79 # get outline
80 topbottom = np.empty((1, 2 * mask_1bit.shape[1]), dtype=np.uint16)
81 topbottom[0, 0:mask_1bit.shape[1]] = np.argmax(mask_1bit, axis=0)
82 topbottom[0, mask_1bit.shape[1]:] = (mask_1bit.shape[0] - 1) - np.argmax(np.flipud(mask_1bit), axis=0)
83 mask = np.tile(np.any(mask_1bit, axis=0), (2,))
84 xvalues = np.tile(np.arange(mask_1bit.shape[1]), (1, 2))
85 outline = np.vstack([topbottom, xvalues])[:, mask].T
87 with warnings.catch_warnings():
88 warnings.simplefilter('ignore') # FIXME not working
89 poly = Polygon(outline).convex_hull
90 poly = poly.simplify(20, preserve_topology=True)
91 poly = Polygon(list(set(
92 poly.exterior.coords))).convex_hull # eliminiate duplicates except of 1 (because poly is closed)
93 unique_corn_YX = sorted(set(poly.exterior.coords),
94 key=lambda x: poly.exterior.coords[:].index(x)) # [(rows,cols),rows,cols]
96 if assert_four_corners or len(unique_corn_YX) == 4:
97 # sort calculated corners like this: [UL, UR, LL, LR]
98 assert len(unique_corn_YX) == 4, \
99 'Found %s unique corner coordinates instead of 4. Exiting.' % len(unique_corn_YX)
101 def get_dist(P1yx, P2yx):
102 return np.sqrt((P1yx[0] - P2yx[0]) ** 2 + (P1yx[1] - P2yx[1]) ** 2)
104 def getClosest(tgtYX, srcYXs):
105 return srcYXs[np.array([get_dist(tgtYX, srcYX) for srcYX in srcYXs]).argmin()]
107 out = [getClosest(tgtYX, unique_corn_YX) for tgtYX in
108 [(0, 0), (0, cols - 1), (rows - 1, 0), (rows - 1, cols - 1)]]
109 else:
110 out = unique_corn_YX
112 else: # alg='numpy'
113 cols_containing_data = np.any(mask_1bit, axis=0)
114 rows_containing_data = np.any(mask_1bit, axis=1)
116 first_dataCol = list(cols_containing_data).index(True)
117 last_dataCol = cols - (list(reversed(cols_containing_data)).index(True) + 1)
118 first_dataRow = list(rows_containing_data).index(True)
119 last_dataRow = rows - (list(reversed(rows_containing_data)).index(True) + 1)
121 StartStopRows_in_first_dataCol = [list(mask_1bit[:, first_dataCol]).index(True),
122 (rows - list(reversed(mask_1bit[:, first_dataCol])).index(True) + 1)]
123 StartStopRows_in_last_dataCol = [list(mask_1bit[:, last_dataCol]).index(True),
124 (rows - list(reversed(mask_1bit[:, last_dataCol])).index(True) + 1)]
125 StartStopCols_in_first_dataRow = [list(mask_1bit[first_dataRow, :]).index(True),
126 (cols - list(reversed(mask_1bit[first_dataRow, :])).index(True) + 1)]
127 StartStopCols_in_last_dataRow = [list(mask_1bit[last_dataRow, :]).index(True),
128 (cols - list(reversed(mask_1bit[last_dataRow, :])).index(True) + 1)]
130 if True in [abs(np.diff(i)[0]) > 10 for i in
131 [StartStopRows_in_first_dataCol, StartStopRows_in_last_dataCol,
132 StartStopCols_in_first_dataRow, StartStopCols_in_last_dataRow]]:
133 # In case of cut image corners (e.g. ALOS AVNIR-2 Level-1B2):
134 # Calculation of trueDataCornerPos outside of the image.'''
135 line_N = ((StartStopCols_in_first_dataRow[1], first_dataRow),
136 (last_dataCol, StartStopRows_in_last_dataCol[0]))
137 line_S = ((first_dataCol, StartStopRows_in_first_dataCol[1]),
138 (StartStopCols_in_last_dataRow[0], last_dataRow))
139 line_W = ((StartStopCols_in_first_dataRow[0], first_dataRow),
140 (first_dataCol, StartStopRows_in_first_dataCol[0]))
141 line_O = ((last_dataCol, StartStopRows_in_last_dataCol[1]),
142 (StartStopCols_in_last_dataRow[1], last_dataRow))
143 from .vector.topology import find_line_intersection_point # import here avoids circular dependencies
144 corners = [list(reversed(find_line_intersection_point(line_N, line_W))),
145 list(reversed(find_line_intersection_point(line_N, line_O))),
146 list(reversed(find_line_intersection_point(line_S, line_W))),
147 list(reversed(find_line_intersection_point(line_S, line_O)))]
148 else:
149 dataRow_in_first_dataCol = np.mean(StartStopRows_in_first_dataCol)
150 dataRow_in_last_dataCol = np.mean(StartStopRows_in_last_dataCol)
151 dataCol_in_first_dataRow = np.mean(StartStopCols_in_first_dataRow)
152 dataCol_in_last_dataRow = np.mean(StartStopCols_in_last_dataRow)
153 corners = [(first_dataRow, dataCol_in_first_dataRow), (dataRow_in_last_dataCol, last_dataCol),
154 (last_dataRow, dataCol_in_last_dataRow), (dataRow_in_first_dataCol, first_dataCol)]
156 def getClosest(refR, refC):
157 return corners[np.array([np.sqrt((refR - c[0]) ** 2 + (refC - c[1]) ** 2) for c in
158 corners]).argmin()] # FIXME this can also result in only 2 corners
159 out = [getClosest(refR, refC) for refR, refC in
160 [(0, 0), (0, cols - 1), (rows - 1, 0), (rows - 1, cols - 1)]]
161 # if UL[0] == 0 and UR[0] == 0:
162 # envi.save_image(os.path.abspath('./testing/out/__faulty_mask_1bit.hdr'),
163 # self.mask_1bit, dtype= 'uint8', interleave='bsq', ext='.bsq',force=True)
164 # print(UL,UR,LL,LR)
165 return out
168def corner_coord_to_minmax(corner_coords):
169 # type: (Iterable[Tuple[float, float], Tuple[float, float], Tuple[float, float], Tuple[float, float]]) -> tuple
170 """Return the bounding coordinates for a given set of XY coordinates.
172 :param corner_coords: four XY tuples of corner coordinates. Their order does not matter.
173 :return: xmin,xmax,ymin,ymax
174 """
175 x_vals, y_vals = zip(*corner_coords)
176 xmin, xmax, ymin, ymax = min(x_vals), max(x_vals), min(y_vals), max(y_vals)
178 return xmin, xmax, ymin, ymax