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

75 statements  

1# -*- coding: utf-8 -*- 

2 

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. 

26 

27import warnings 

28from typing import Iterable, Tuple # noqa: F401 

29import numpy as np 

30 

31# custom 

32from shapely.geometry import Polygon 

33 

34__author__ = "Daniel Scheffler" 

35 

36 

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 

49 

50 

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. 

54 

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. 

57 

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 [] 

69 

70 rows, cols = mask_1bit.shape[:2] 

71 

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'." 

77 

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 

86 

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] 

95 

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) 

100 

101 def get_dist(P1yx, P2yx): 

102 return np.sqrt((P1yx[0] - P2yx[0]) ** 2 + (P1yx[1] - P2yx[1]) ** 2) 

103 

104 def getClosest(tgtYX, srcYXs): 

105 return srcYXs[np.array([get_dist(tgtYX, srcYX) for srcYX in srcYXs]).argmin()] 

106 

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 

111 

112 else: # alg='numpy' 

113 cols_containing_data = np.any(mask_1bit, axis=0) 

114 rows_containing_data = np.any(mask_1bit, axis=1) 

115 

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) 

120 

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)] 

129 

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)] 

155 

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 

166 

167 

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. 

171 

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) 

177 

178 return xmin, xmax, ymin, ymax