shancx 1.8.92__py3-none-any.whl → 1.9.33.218__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.
Files changed (166) hide show
  1. shancx/3D/__init__.py +25 -0
  2. shancx/Algo/Class.py +11 -0
  3. shancx/Algo/CudaPrefetcher1.py +112 -0
  4. shancx/Algo/Fake_image.py +24 -0
  5. shancx/Algo/Hsml.py +391 -0
  6. shancx/Algo/L2Loss.py +10 -0
  7. shancx/Algo/MetricTracker.py +132 -0
  8. shancx/Algo/Normalize.py +66 -0
  9. shancx/Algo/OptimizerWScheduler.py +38 -0
  10. shancx/Algo/Rmageresize.py +79 -0
  11. shancx/Algo/Savemodel.py +33 -0
  12. shancx/Algo/SmoothL1_losses.py +27 -0
  13. shancx/Algo/Tqdm.py +62 -0
  14. shancx/Algo/__init__.py +121 -0
  15. shancx/Algo/checknan.py +28 -0
  16. shancx/Algo/iouJU.py +83 -0
  17. shancx/Algo/mask.py +25 -0
  18. shancx/Algo/psnr.py +9 -0
  19. shancx/Algo/ssim.py +70 -0
  20. shancx/Algo/structural_similarity.py +308 -0
  21. shancx/Algo/tool.py +704 -0
  22. shancx/Calmetrics/__init__.py +97 -0
  23. shancx/Calmetrics/calmetrics.py +14 -0
  24. shancx/Calmetrics/calmetricsmatrixLib.py +147 -0
  25. shancx/Calmetrics/rmseR2score.py +35 -0
  26. shancx/Clip/__init__.py +50 -0
  27. shancx/Cmd.py +126 -0
  28. shancx/Config_.py +26 -0
  29. shancx/Df/DataFrame.py +11 -2
  30. shancx/Df/__init__.py +17 -0
  31. shancx/Df/tool.py +0 -0
  32. shancx/Diffm/Psamples.py +18 -0
  33. shancx/Diffm/__init__.py +0 -0
  34. shancx/Diffm/test.py +207 -0
  35. shancx/Doc/__init__.py +214 -0
  36. shancx/E/__init__.py +178 -152
  37. shancx/Fillmiss/__init__.py +0 -0
  38. shancx/Fillmiss/imgidwJU.py +46 -0
  39. shancx/Fillmiss/imgidwLatLonJU.py +82 -0
  40. shancx/Gpu/__init__.py +55 -0
  41. shancx/H9/__init__.py +126 -0
  42. shancx/H9/ahi_read_hsd.py +877 -0
  43. shancx/H9/ahisearchtable.py +298 -0
  44. shancx/H9/geometry.py +2439 -0
  45. shancx/Hug/__init__.py +81 -0
  46. shancx/Inst.py +22 -0
  47. shancx/Lib.py +31 -0
  48. shancx/Mos/__init__.py +37 -0
  49. shancx/NN/__init__.py +235 -106
  50. shancx/Path1.py +161 -0
  51. shancx/Plot/GlobMap.py +276 -116
  52. shancx/Plot/__init__.py +491 -1
  53. shancx/Plot/draw_day_CR_PNG.py +4 -21
  54. shancx/Plot/exam.py +116 -0
  55. shancx/Plot/plotGlobal.py +325 -0
  56. shancx/{radar_nmc.py → Plot/radarNmc.py} +4 -34
  57. shancx/{subplots_single_china_map.py → Plot/single_china_map.py} +1 -1
  58. shancx/Point.py +46 -0
  59. shancx/QC.py +223 -0
  60. shancx/RdPzl/__init__.py +32 -0
  61. shancx/Read.py +72 -0
  62. shancx/Resize.py +79 -0
  63. shancx/SN/__init__.py +62 -123
  64. shancx/Time/GetTime.py +9 -3
  65. shancx/Time/__init__.py +66 -1
  66. shancx/Time/timeCycle.py +302 -0
  67. shancx/Time/tool.py +0 -0
  68. shancx/Train/__init__.py +74 -0
  69. shancx/Train/makelist.py +187 -0
  70. shancx/Train/multiGpu.py +27 -0
  71. shancx/Train/prepare.py +161 -0
  72. shancx/Train/renet50.py +157 -0
  73. shancx/ZR.py +12 -0
  74. shancx/__init__.py +333 -262
  75. shancx/args.py +27 -0
  76. shancx/bak.py +768 -0
  77. shancx/df2database.py +62 -2
  78. shancx/geosProj.py +80 -0
  79. shancx/info.py +38 -0
  80. shancx/netdfJU.py +231 -0
  81. shancx/sendM.py +59 -0
  82. shancx/tensBoard/__init__.py +28 -0
  83. shancx/wait.py +246 -0
  84. {shancx-1.8.92.dist-info → shancx-1.9.33.218.dist-info}/METADATA +15 -5
  85. shancx-1.9.33.218.dist-info/RECORD +91 -0
  86. {shancx-1.8.92.dist-info → shancx-1.9.33.218.dist-info}/WHEEL +1 -1
  87. my_timer_decorator/__init__.py +0 -10
  88. shancx/Dsalgor/__init__.py +0 -19
  89. shancx/E/DFGRRIB.py +0 -30
  90. shancx/EN/DFGRRIB.py +0 -30
  91. shancx/EN/__init__.py +0 -148
  92. shancx/FileRead.py +0 -44
  93. shancx/Gray2RGB.py +0 -86
  94. shancx/M/__init__.py +0 -137
  95. shancx/MN/__init__.py +0 -133
  96. shancx/N/__init__.py +0 -131
  97. shancx/Plot/draw_day_CR_PNGUS.py +0 -206
  98. shancx/Plot/draw_day_CR_SVG.py +0 -275
  99. shancx/Plot/draw_day_pre_PNGUS.py +0 -205
  100. shancx/Plot/glob_nation_map.py +0 -116
  101. shancx/Plot/radar_nmc.py +0 -61
  102. shancx/Plot/radar_nmc_china_map_compare1.py +0 -50
  103. shancx/Plot/radar_nmc_china_map_f.py +0 -121
  104. shancx/Plot/radar_nmc_us_map_f.py +0 -128
  105. shancx/Plot/subplots_compare_devlop.py +0 -36
  106. shancx/Plot/subplots_single_china_map.py +0 -45
  107. shancx/S/__init__.py +0 -138
  108. shancx/W/__init__.py +0 -132
  109. shancx/WN/__init__.py +0 -132
  110. shancx/code.py +0 -331
  111. shancx/draw_day_CR_PNG.py +0 -200
  112. shancx/draw_day_CR_PNGUS.py +0 -206
  113. shancx/draw_day_CR_SVG.py +0 -275
  114. shancx/draw_day_pre_PNGUS.py +0 -205
  115. shancx/makenetCDFN.py +0 -42
  116. shancx/mkIMGSCX.py +0 -92
  117. shancx/netCDF.py +0 -130
  118. shancx/radar_nmc_china_map_compare1.py +0 -50
  119. shancx/radar_nmc_china_map_f.py +0 -125
  120. shancx/radar_nmc_us_map_f.py +0 -67
  121. shancx/subplots_compare_devlop.py +0 -36
  122. shancx/tool.py +0 -18
  123. shancx/user/H8mess.py +0 -317
  124. shancx/user/__init__.py +0 -137
  125. shancx/user/cinradHJN.py +0 -496
  126. shancx/user/examMeso.py +0 -293
  127. shancx/user/hjnDAAS.py +0 -26
  128. shancx/user/hjnFTP.py +0 -81
  129. shancx/user/hjnGIS.py +0 -320
  130. shancx/user/hjnGPU.py +0 -21
  131. shancx/user/hjnIDW.py +0 -68
  132. shancx/user/hjnKDTree.py +0 -75
  133. shancx/user/hjnLAPSTransform.py +0 -47
  134. shancx/user/hjnMiscellaneous.py +0 -182
  135. shancx/user/hjnProj.py +0 -162
  136. shancx/user/inotify.py +0 -41
  137. shancx/user/matplotlibMess.py +0 -87
  138. shancx/user/mkNCHJN.py +0 -623
  139. shancx/user/newTypeRadar.py +0 -492
  140. shancx/user/test.py +0 -6
  141. shancx/user/tlogP.py +0 -129
  142. shancx/util_log.py +0 -33
  143. shancx/wtx/H8mess.py +0 -315
  144. shancx/wtx/__init__.py +0 -151
  145. shancx/wtx/cinradHJN.py +0 -496
  146. shancx/wtx/colormap.py +0 -64
  147. shancx/wtx/examMeso.py +0 -298
  148. shancx/wtx/hjnDAAS.py +0 -26
  149. shancx/wtx/hjnFTP.py +0 -81
  150. shancx/wtx/hjnGIS.py +0 -330
  151. shancx/wtx/hjnGPU.py +0 -21
  152. shancx/wtx/hjnIDW.py +0 -68
  153. shancx/wtx/hjnKDTree.py +0 -75
  154. shancx/wtx/hjnLAPSTransform.py +0 -47
  155. shancx/wtx/hjnLog.py +0 -78
  156. shancx/wtx/hjnMiscellaneous.py +0 -201
  157. shancx/wtx/hjnProj.py +0 -161
  158. shancx/wtx/inotify.py +0 -41
  159. shancx/wtx/matplotlibMess.py +0 -87
  160. shancx/wtx/mkNCHJN.py +0 -613
  161. shancx/wtx/newTypeRadar.py +0 -492
  162. shancx/wtx/test.py +0 -6
  163. shancx/wtx/tlogP.py +0 -129
  164. shancx-1.8.92.dist-info/RECORD +0 -99
  165. /shancx/{Dsalgor → Algo}/dsalgor.py +0 -0
  166. {shancx-1.8.92.dist-info → shancx-1.9.33.218.dist-info}/top_level.txt +0 -0
shancx/H9/geometry.py ADDED
@@ -0,0 +1,2439 @@
1
+ # -*- coding:utf-8 -*-
2
+ '''
3
+ @Project : fypy
4
+
5
+ @File : geometry.py
6
+
7
+ @Modify Time : 2022/11/10 14:45
8
+
9
+ @Author : fypy Team
10
+
11
+ @Version : 1.0
12
+
13
+ @Description :
14
+
15
+ '''
16
+
17
+ import hashlib
18
+ import warnings
19
+ from collections import OrderedDict
20
+ from logging import getLogger
21
+
22
+ import numpy as np
23
+ import yaml
24
+ from pyproj import Geod, transform, Proj
25
+
26
+ from pyresample import CHUNK_SIZE
27
+ from pyresample._spatial_mp import Cartesian, Cartesian_MP, Proj_MP
28
+ from pyresample.boundary import AreaDefBoundary, Boundary, SimpleBoundary
29
+ from pyresample.utils import (proj4_str_to_dict, proj4_dict_to_str,
30
+ convert_proj_floats, proj4_radius_parameters,
31
+ check_slice_orientation, load_cf_area)
32
+ from pyresample.area_config import create_area_def
33
+
34
+ try:
35
+ from xarray import DataArray
36
+ except ImportError:
37
+ DataArray = np.ndarray
38
+
39
+ try:
40
+ from pyproj import CRS
41
+ except ImportError:
42
+ CRS = None
43
+
44
+ logger = getLogger(__name__)
45
+
46
+
47
+ class DimensionError(ValueError):
48
+ """Wrap ValueError."""
49
+
50
+ pass
51
+
52
+
53
+ class IncompatibleAreas(ValueError):
54
+ """Error when the areas to combine are not compatible."""
55
+
56
+ pass
57
+
58
+
59
+ class BaseDefinition(object):
60
+ """Base class for geometry definitions.
61
+
62
+ .. versionchanged:: 1.8.0
63
+
64
+ `BaseDefinition` no longer checks the validity of the provided
65
+ longitude and latitude coordinates to improve performance. Longitude
66
+ arrays are expected to be between -180 and 180 degrees, latitude -90
67
+ to 90 degrees. Use :func:`~pyresample.utils.check_and_wrap` to preprocess
68
+ your arrays.
69
+
70
+ """
71
+
72
+ def __init__(self, lons=None, lats=None, nprocs=1):
73
+ """Initialize BaseDefinition."""
74
+ if type(lons) != type(lats):
75
+ raise TypeError('lons and lats must be of same type')
76
+ elif lons is not None:
77
+ if not isinstance(lons, (np.ndarray, DataArray)):
78
+ lons = np.asanyarray(lons)
79
+ lats = np.asanyarray(lats)
80
+ if lons.shape != lats.shape:
81
+ raise ValueError('lons and lats must have same shape')
82
+
83
+ self.nprocs = nprocs
84
+ self.lats = lats
85
+ self.lons = lons
86
+ self.ndim = None
87
+ self.cartesian_coords = None
88
+ self.hash = None
89
+
90
+ def __getitem__(self, key):
91
+ """Slice a 2D geographic definition."""
92
+ y_slice, x_slice = key
93
+ return self.__class__(
94
+ lons=self.lons[y_slice, x_slice],
95
+ lats=self.lats[y_slice, x_slice],
96
+ nprocs=self.nprocs
97
+ )
98
+
99
+ def __hash__(self):
100
+ """Compute the hash of this object."""
101
+ if self.hash is None:
102
+ self.hash = int(self.update_hash().hexdigest(), 16)
103
+ return self.hash
104
+
105
+ def __eq__(self, other):
106
+ """Test for approximate equality."""
107
+ if self is other:
108
+ return True
109
+ if not isinstance(other, BaseDefinition):
110
+ return False
111
+ if other.lons is None or other.lats is None:
112
+ other_lons, other_lats = other.get_lonlats()
113
+ else:
114
+ other_lons = other.lons
115
+ other_lats = other.lats
116
+
117
+ if self.lons is None or self.lats is None:
118
+ self_lons, self_lats = self.get_lonlats()
119
+ else:
120
+ self_lons = self.lons
121
+ self_lats = self.lats
122
+
123
+ if self_lons is other_lons and self_lats is other_lats:
124
+ return True
125
+ if isinstance(self_lons, DataArray) and np.ndarray is not DataArray:
126
+ self_lons = self_lons.data
127
+ self_lats = self_lats.data
128
+ if isinstance(other_lons, DataArray) and np.ndarray is not DataArray:
129
+ other_lons = other_lons.data
130
+ other_lats = other_lats.data
131
+ try:
132
+ from dask.array import allclose
133
+ except ImportError:
134
+ from numpy import allclose
135
+ try:
136
+ return (allclose(self_lons, other_lons, atol=1e-6, rtol=5e-9, equal_nan=True) and
137
+ allclose(self_lats, other_lats, atol=1e-6, rtol=5e-9, equal_nan=True))
138
+ except (AttributeError, ValueError):
139
+ return False
140
+
141
+ def __ne__(self, other):
142
+ """Test for approximate equality."""
143
+ return not self.__eq__(other)
144
+
145
+ def get_area_extent_for_subset(self, row_LR, col_LR, row_UL, col_UL):
146
+ """Calculate extent for a subdomain of this area.
147
+
148
+ Rows are counted from upper left to lower left and columns are
149
+ counted from upper left to upper right.
150
+
151
+ Args:
152
+ row_LR (int): row of the lower right pixel
153
+ col_LR (int): col of the lower right pixel
154
+ row_UL (int): row of the upper left pixel
155
+ col_UL (int): col of the upper left pixel
156
+
157
+ Returns:
158
+ area_extent (tuple):
159
+ Area extent (LL_x, LL_y, UR_x, UR_y) of the subset
160
+
161
+ Author:
162
+ Ulrich Hamann
163
+
164
+ """
165
+ (a, b) = self.get_proj_coords(data_slice=(row_LR, col_LR))
166
+ a = a - 0.5 * self.pixel_size_x
167
+ b = b - 0.5 * self.pixel_size_y
168
+ (c, d) = self.get_proj_coords(data_slice=(row_UL, col_UL))
169
+ c = c + 0.5 * self.pixel_size_x
170
+ d = d + 0.5 * self.pixel_size_y
171
+
172
+ return a, b, c, d
173
+
174
+ def get_lonlat(self, row, col):
175
+ """Retrieve lon and lat of single pixel.
176
+
177
+ Parameters
178
+ ----------
179
+ row : int
180
+ col : int
181
+
182
+ Returns
183
+ -------
184
+ (lon, lat) : tuple of floats
185
+
186
+ """
187
+ if self.ndim != 2:
188
+ raise DimensionError(('operation undefined '
189
+ 'for %sD geometry ') % self.ndim)
190
+ elif self.lons is None or self.lats is None:
191
+ raise ValueError('lon/lat values are not defined')
192
+ return self.lons[row, col], self.lats[row, col]
193
+
194
+ def get_lonlats(self, data_slice=None, chunks=None, **kwargs):
195
+ """Get longitude and latitude arrays representing this geometry.
196
+
197
+ Returns
198
+ -------
199
+ (lon, lat) : tuple of numpy arrays
200
+ If `chunks` is provided then the arrays will be dask arrays
201
+ with the provided chunk size. If `chunks` is not provided then
202
+ the returned arrays are the same as the internal data types
203
+ of this geometry object (numpy or dask).
204
+
205
+ """
206
+ lons = self.lons
207
+ lats = self.lats
208
+ if lons is None or lats is None:
209
+ raise ValueError('lon/lat values are not defined')
210
+ elif DataArray is not np.ndarray and isinstance(lons, DataArray):
211
+ # lons/lats are xarray DataArray objects, use numpy/dask array underneath
212
+ lons = lons.data
213
+ lats = lats.data
214
+
215
+ if chunks is not None:
216
+ import dask.array as da
217
+ if isinstance(lons, da.Array):
218
+ # rechunk to this specific chunk size
219
+ lons = lons.rechunk(chunks)
220
+ lats = lats.rechunk(chunks)
221
+ elif not isinstance(lons, da.Array):
222
+ # convert numpy array to dask array
223
+ lons = da.from_array(np.asanyarray(lons), chunks=chunks)
224
+ lats = da.from_array(np.asanyarray(lats), chunks=chunks)
225
+ if data_slice is not None:
226
+ lons, lats = lons[data_slice], lats[data_slice]
227
+ return lons, lats
228
+
229
+ def get_lonlats_dask(self, chunks=None):
230
+ """Get the lon lats as a single dask array."""
231
+ warnings.warn("'get_lonlats_dask' is deprecated, please use "
232
+ "'get_lonlats' with the 'chunks' keyword argument specified.", DeprecationWarning)
233
+ if chunks is None:
234
+ chunks = CHUNK_SIZE # FUTURE: Use a global config object instead
235
+ return self.get_lonlats(chunks=chunks)
236
+
237
+ def get_boundary_lonlats(self):
238
+ """Return Boundary objects."""
239
+ s1_lon, s1_lat = self.get_lonlats(data_slice=(0, slice(None)))
240
+ s2_lon, s2_lat = self.get_lonlats(data_slice=(slice(None), -1))
241
+ s3_lon, s3_lat = self.get_lonlats(data_slice=(-1, slice(None, None, -1)))
242
+ s4_lon, s4_lat = self.get_lonlats(data_slice=(slice(None, None, -1), 0))
243
+ return (SimpleBoundary(s1_lon.squeeze(), s2_lon.squeeze(), s3_lon.squeeze(), s4_lon.squeeze()),
244
+ SimpleBoundary(s1_lat.squeeze(), s2_lat.squeeze(), s3_lat.squeeze(), s4_lat.squeeze()))
245
+
246
+ def get_bbox_lonlats(self):
247
+ """Return the bounding box lons and lats."""
248
+ s1_lon, s1_lat = self.get_lonlats(data_slice=(0, slice(None)))
249
+ s2_lon, s2_lat = self.get_lonlats(data_slice=(slice(None), -1))
250
+ s3_lon, s3_lat = self.get_lonlats(data_slice=(-1, slice(None, None, -1)))
251
+ s4_lon, s4_lat = self.get_lonlats(data_slice=(slice(None, None, -1), 0))
252
+ return zip(*[(s1_lon.squeeze(), s1_lat.squeeze()),
253
+ (s2_lon.squeeze(), s2_lat.squeeze()),
254
+ (s3_lon.squeeze(), s3_lat.squeeze()),
255
+ (s4_lon.squeeze(), s4_lat.squeeze())])
256
+
257
+ def get_cartesian_coords(self, nprocs=None, data_slice=None, cache=False):
258
+ """Retrieve cartesian coordinates of geometry definition.
259
+
260
+ Parameters
261
+ ----------
262
+ nprocs : int, optional
263
+ Number of processor cores to be used.
264
+ Defaults to the nprocs set when instantiating object
265
+ data_slice : slice object, optional
266
+ Calculate only cartesian coordnates for the defined slice
267
+ cache : bool, optional
268
+ Store result the result. Requires data_slice to be None
269
+
270
+ Returns
271
+ -------
272
+ cartesian_coords : numpy array
273
+
274
+ """
275
+ if cache:
276
+ warnings.warn("'cache' keyword argument will be removed in the "
277
+ "future and data will not be cached.", PendingDeprecationWarning)
278
+
279
+ if self.cartesian_coords is None:
280
+ # Coordinates are not cached
281
+ if nprocs is None:
282
+ nprocs = self.nprocs
283
+
284
+ if data_slice is None:
285
+ # Use full slice
286
+ data_slice = slice(None)
287
+
288
+ lons, lats = self.get_lonlats(nprocs=nprocs, data_slice=data_slice)
289
+
290
+ if nprocs > 1:
291
+ cartesian = Cartesian_MP(nprocs)
292
+ else:
293
+ cartesian = Cartesian()
294
+
295
+ cartesian_coords = cartesian.transform_lonlats(np.ravel(lons), np.ravel(lats))
296
+ if isinstance(lons, np.ndarray) and lons.ndim > 1:
297
+ # Reshape to correct shape
298
+ cartesian_coords = cartesian_coords.reshape(lons.shape[0], lons.shape[1], 3)
299
+
300
+ if cache and data_slice is None:
301
+ self.cartesian_coords = cartesian_coords
302
+ else:
303
+ # Coordinates are cached
304
+ if data_slice is None:
305
+ cartesian_coords = self.cartesian_coords
306
+ else:
307
+ cartesian_coords = self.cartesian_coords[data_slice]
308
+
309
+ return cartesian_coords
310
+
311
+ @property
312
+ def corners(self):
313
+ """Return the corners of the current area."""
314
+ from pyresample.spherical_geometry import Coordinate
315
+ return [Coordinate(*self.get_lonlat(0, 0)),
316
+ Coordinate(*self.get_lonlat(0, -1)),
317
+ Coordinate(*self.get_lonlat(-1, -1)),
318
+ Coordinate(*self.get_lonlat(-1, 0))]
319
+
320
+ def __contains__(self, point):
321
+ """Check if a point is inside the 4 corners of the current area.
322
+
323
+ This uses great circle arcs as area boundaries.
324
+
325
+ """
326
+ from pyresample.spherical_geometry import point_inside, Coordinate
327
+ corners = self.corners
328
+
329
+ if isinstance(point, tuple):
330
+ return point_inside(Coordinate(*point), corners)
331
+ else:
332
+ return point_inside(point, corners)
333
+
334
+ def overlaps(self, other):
335
+ """Test if the current area overlaps the *other* area.
336
+
337
+ This is based solely on the corners of areas, assuming the
338
+ boundaries to be great circles.
339
+
340
+ Parameters
341
+ ----------
342
+ other : object
343
+ Instance of subclass of BaseDefinition
344
+
345
+ Returns
346
+ -------
347
+ overlaps : bool
348
+
349
+ """
350
+ from pyresample.spherical_geometry import Arc
351
+
352
+ self_corners = self.corners
353
+
354
+ other_corners = other.corners
355
+
356
+ for i in self_corners:
357
+ if i in other:
358
+ return True
359
+ for i in other_corners:
360
+ if i in self:
361
+ return True
362
+
363
+ self_arc1 = Arc(self_corners[0], self_corners[1])
364
+ self_arc2 = Arc(self_corners[1], self_corners[2])
365
+ self_arc3 = Arc(self_corners[2], self_corners[3])
366
+ self_arc4 = Arc(self_corners[3], self_corners[0])
367
+
368
+ other_arc1 = Arc(other_corners[0], other_corners[1])
369
+ other_arc2 = Arc(other_corners[1], other_corners[2])
370
+ other_arc3 = Arc(other_corners[2], other_corners[3])
371
+ other_arc4 = Arc(other_corners[3], other_corners[0])
372
+
373
+ for i in (self_arc1, self_arc2, self_arc3, self_arc4):
374
+ for j in (other_arc1, other_arc2, other_arc3, other_arc4):
375
+ if i.intersects(j):
376
+ return True
377
+ return False
378
+
379
+ def get_area(self):
380
+ """Get the area of the convex area defined by the corners of the curren area."""
381
+ from pyresample.spherical_geometry import get_polygon_area
382
+
383
+ return get_polygon_area(self.corners)
384
+
385
+ def intersection(self, other):
386
+ """Return the corners of the intersection polygon of the current area with *other*.
387
+
388
+ Parameters
389
+ ----------
390
+ other : object
391
+ Instance of subclass of BaseDefinition
392
+
393
+ Returns
394
+ -------
395
+ (corner1, corner2, corner3, corner4) : tuple of points
396
+
397
+ """
398
+ from pyresample.spherical_geometry import intersection_polygon
399
+ return intersection_polygon(self.corners, other.corners)
400
+
401
+ def overlap_rate(self, other):
402
+ """Get how much the current area overlaps an *other* area.
403
+
404
+ Parameters
405
+ ----------
406
+ other : object
407
+ Instance of subclass of BaseDefinition
408
+
409
+ Returns
410
+ -------
411
+ overlap_rate : float
412
+
413
+ """
414
+ from pyresample.spherical_geometry import get_polygon_area
415
+ other_area = other.get_area()
416
+ inter_area = get_polygon_area(self.intersection(other))
417
+ return inter_area / other_area
418
+
419
+ def get_area_slices(self, area_to_cover):
420
+ """Compute the slice to read based on an `area_to_cover`."""
421
+ raise NotImplementedError
422
+
423
+
424
+ class CoordinateDefinition(BaseDefinition):
425
+ """Base class for geometry definitions defined by lons and lats only."""
426
+
427
+ def __init__(self, lons, lats, nprocs=1):
428
+ """Initialize CoordinateDefinition."""
429
+ if not isinstance(lons, (np.ndarray, DataArray)):
430
+ lons = np.asanyarray(lons)
431
+ lats = np.asanyarray(lats)
432
+ super(CoordinateDefinition, self).__init__(lons, lats, nprocs)
433
+ if lons.shape == lats.shape and lons.dtype == lats.dtype:
434
+ self.shape = lons.shape
435
+ self.size = lons.size
436
+ self.ndim = lons.ndim
437
+ self.dtype = lons.dtype
438
+ else:
439
+ raise ValueError(('%s must be created with either '
440
+ 'lon/lats of the same shape with same dtype') %
441
+ self.__class__.__name__)
442
+
443
+ def concatenate(self, other):
444
+ """Concatenate coordinate definitions."""
445
+ if self.ndim != other.ndim:
446
+ raise DimensionError(('Unable to concatenate %sD and %sD '
447
+ 'geometries') % (self.ndim, other.ndim))
448
+ klass = _get_highest_level_class(self, other)
449
+ lons = np.concatenate((self.lons, other.lons))
450
+ lats = np.concatenate((self.lats, other.lats))
451
+ nprocs = min(self.nprocs, other.nprocs)
452
+ return klass(lons, lats, nprocs=nprocs)
453
+
454
+ def append(self, other):
455
+ """Append another coordinate definition to existing one."""
456
+ if self.ndim != other.ndim:
457
+ raise DimensionError(('Unable to append %sD and %sD '
458
+ 'geometries') % (self.ndim, other.ndim))
459
+ self.lons = np.concatenate((self.lons, other.lons))
460
+ self.lats = np.concatenate((self.lats, other.lats))
461
+ self.shape = self.lons.shape
462
+ self.size = self.lons.size
463
+
464
+ def __str__(self):
465
+ """Return string representation of the coordinate definition."""
466
+ # Rely on numpy's object printing
467
+ return ('Shape: %s\nLons: %s\nLats: %s') % (str(self.shape),
468
+ str(self.lons),
469
+ str(self.lats))
470
+
471
+ def geocentric_resolution(self, ellps='WGS84', radius=None, nadir_factor=2):
472
+ """Calculate maximum geocentric pixel resolution.
473
+
474
+ If `lons` is a :class:`xarray.DataArray` object with a `resolution`
475
+ attribute, this will be used instead of loading the longitude and
476
+ latitude data. In this case the resolution attribute is assumed to
477
+ mean the nadir resolution of a swath and will be multiplied by the
478
+ `nadir_factor` to adjust for increases in the spatial resolution
479
+ towards the limb of the swath.
480
+
481
+ Args:
482
+ ellps (str): PROJ Ellipsoid for the Cartographic projection
483
+ used as the target geocentric coordinate reference system.
484
+ Default: 'WGS84'. Ignored if `radius` is provided.
485
+ radius (float): Spherical radius of the Earth to use instead of
486
+ the definitions in `ellps`.
487
+ nadir_factor (int): Number to multiply the nadir resolution
488
+ attribute by to reflect pixel size on the limb of the swath.
489
+
490
+ Returns: Estimated maximum pixel size in meters on a geocentric
491
+ coordinate system (X, Y, Z) representing the Earth.
492
+
493
+ Raises: RuntimeError if a simple search for valid longitude/latitude
494
+ data points found no valid data points.
495
+
496
+ """
497
+ if hasattr(self.lons, 'attrs') and \
498
+ self.lons.attrs.get('resolution') is not None:
499
+ return self.lons.attrs['resolution'] * nadir_factor
500
+ if self.ndim == 1:
501
+ raise RuntimeError("Can't confidently determine geocentric "
502
+ "resolution for 1D swath.")
503
+ from pyproj import transform
504
+ rows = self.shape[0]
505
+ start_row = rows // 2 # middle row
506
+ src = Proj('+proj=latlong +datum=WGS84')
507
+ if radius:
508
+ dst = Proj("+proj=cart +a={} +b={}".format(radius, radius))
509
+ else:
510
+ dst = Proj("+proj=cart +ellps={}".format(ellps))
511
+ # simply take the first two columns of the middle of the swath
512
+ lons = self.lons[start_row: start_row + 1, :2]
513
+ lats = self.lats[start_row: start_row + 1, :2]
514
+ if hasattr(lons.data, 'compute'):
515
+ # dask arrays, compute them together
516
+ import dask.array as da
517
+ lons, lats = da.compute(lons, lats)
518
+ if hasattr(lons, 'values'):
519
+ # convert xarray to numpy array
520
+ lons = lons.values
521
+ lats = lats.values
522
+ lons = lons.ravel()
523
+ lats = lats.ravel()
524
+ alt = np.zeros_like(lons)
525
+
526
+ xyz = np.stack(transform(src, dst, lons, lats, alt), axis=1)
527
+ dist = np.linalg.norm(xyz[1] - xyz[0])
528
+ dist = dist[np.isfinite(dist)]
529
+ if not dist.size:
530
+ raise RuntimeError("Could not calculate geocentric resolution")
531
+ return dist[0]
532
+
533
+
534
+ class GridDefinition(CoordinateDefinition):
535
+ """Grid defined by lons and lats.
536
+
537
+ Parameters
538
+ ----------
539
+ lons : numpy array
540
+ lats : numpy array
541
+ nprocs : int, optional
542
+ Number of processor cores to be used for calculations.
543
+
544
+ Attributes
545
+ ----------
546
+ shape : tuple
547
+ Grid shape as (rows, cols)
548
+ size : int
549
+ Number of elements in grid
550
+ lons : object
551
+ Grid lons
552
+ lats : object
553
+ Grid lats
554
+ cartesian_coords : object
555
+ Grid cartesian coordinates
556
+
557
+ """
558
+
559
+ def __init__(self, lons, lats, nprocs=1):
560
+ """Initialize GridDefinition."""
561
+ super(GridDefinition, self).__init__(lons, lats, nprocs)
562
+ if lons.shape != lats.shape:
563
+ raise ValueError('lon and lat grid must have same shape')
564
+ elif lons.ndim != 2:
565
+ raise ValueError('2 dimensional lon lat grid expected')
566
+
567
+
568
+ def get_array_hashable(arr):
569
+ """Compute a hashable form of the array `arr`.
570
+
571
+ Works with numpy arrays, dask.array.Array, and xarray.DataArray.
572
+ """
573
+ # look for precomputed value
574
+ if isinstance(arr, DataArray) and np.ndarray is not DataArray:
575
+ return arr.attrs.get('hash', get_array_hashable(arr.data))
576
+ else:
577
+ try:
578
+ return arr.name.encode('utf-8') # dask array
579
+ except AttributeError:
580
+ return np.asarray(arr).view(np.uint8) # np array
581
+
582
+
583
+ class SwathDefinition(CoordinateDefinition):
584
+ """Swath defined by lons and lats.
585
+
586
+ Parameters
587
+ ----------
588
+ lons : numpy array
589
+ lats : numpy array
590
+ nprocs : int, optional
591
+ Number of processor cores to be used for calculations.
592
+
593
+ Attributes
594
+ ----------
595
+ shape : tuple
596
+ Swath shape
597
+ size : int
598
+ Number of elements in swath
599
+ ndims : int
600
+ Swath dimensions
601
+ lons : object
602
+ Swath lons
603
+ lats : object
604
+ Swath lats
605
+ cartesian_coords : object
606
+ Swath cartesian coordinates
607
+
608
+ """
609
+
610
+ def __init__(self, lons, lats, nprocs=1):
611
+ """Initialize SwathDefinition."""
612
+ if not isinstance(lons, (np.ndarray, DataArray)):
613
+ lons = np.asanyarray(lons)
614
+ lats = np.asanyarray(lats)
615
+ super(SwathDefinition, self).__init__(lons, lats, nprocs)
616
+ if lons.shape != lats.shape:
617
+ raise ValueError('lon and lat arrays must have same shape')
618
+ elif lons.ndim > 2:
619
+ raise ValueError('Only 1 and 2 dimensional swaths are allowed')
620
+
621
+ def copy(self):
622
+ """Copy the current swath."""
623
+ return SwathDefinition(self.lons, self.lats)
624
+
625
+ @staticmethod
626
+ def _do_transform(src, dst, lons, lats, alt):
627
+ """Run pyproj.transform and stack the results."""
628
+ x, y, z = transform(src, dst, lons, lats, alt)
629
+ return np.dstack((x, y, z))
630
+
631
+ def aggregate(self, **dims):
632
+ """Aggregate the current swath definition by averaging.
633
+
634
+ For example, averaging over 2x2 windows:
635
+ `sd.aggregate(x=2, y=2)`
636
+ """
637
+ import pyproj
638
+ import dask.array as da
639
+
640
+ geocent = pyproj.Proj(proj='geocent')
641
+ latlong = pyproj.Proj(proj='latlong')
642
+ res = da.map_blocks(self._do_transform, latlong, geocent,
643
+ self.lons.data, self.lats.data,
644
+ da.zeros_like(self.lons.data), new_axis=[2],
645
+ chunks=(self.lons.chunks[0], self.lons.chunks[1], 3))
646
+ res = DataArray(res, dims=['y', 'x', 'coord'], coords=self.lons.coords)
647
+ res = res.coarsen(**dims).mean()
648
+ lonlatalt = da.map_blocks(self._do_transform, geocent, latlong,
649
+ res[:, :, 0].data, res[:, :, 1].data,
650
+ res[:, :, 2].data, new_axis=[2],
651
+ chunks=res.data.chunks)
652
+ lons = DataArray(lonlatalt[:, :, 0], dims=self.lons.dims,
653
+ coords=res.coords, attrs=self.lons.attrs.copy())
654
+ lats = DataArray(lonlatalt[:, :, 1], dims=self.lons.dims,
655
+ coords=res.coords, attrs=self.lons.attrs.copy())
656
+ try:
657
+ resolution = lons.attrs['resolution'] * ((dims.get('x', 1) + dims.get('y', 1)) / 2)
658
+ lons.attrs['resolution'] = resolution
659
+ lats.attrs['resolution'] = resolution
660
+ except KeyError:
661
+ pass
662
+ return SwathDefinition(lons, lats)
663
+
664
+ def __hash__(self):
665
+ """Compute the hash of this object."""
666
+ if self.hash is None:
667
+ self.hash = int(self.update_hash().hexdigest(), 16)
668
+ return self.hash
669
+
670
+ def update_hash(self, the_hash=None):
671
+ """Update the hash."""
672
+ if the_hash is None:
673
+ the_hash = hashlib.sha1()
674
+ the_hash.update(get_array_hashable(self.lons))
675
+ the_hash.update(get_array_hashable(self.lats))
676
+ try:
677
+ if self.lons.mask is not np.bool_(False):
678
+ the_hash.update(get_array_hashable(self.lons.mask))
679
+ except AttributeError:
680
+ pass
681
+ return the_hash
682
+
683
+ def _compute_omerc_parameters(self, ellipsoid):
684
+ """Compute the oblique mercator projection bouding box parameters."""
685
+ lines, cols = self.lons.shape
686
+ lon1, lon2 = np.asanyarray(self.lons[[0, -1], int(cols / 2)])
687
+ lat1, lat, lat2 = np.asanyarray(
688
+ self.lats[[0, int(lines / 2), -1], int(cols / 2)])
689
+ if any(np.isnan((lon1, lon2, lat1, lat, lat2))):
690
+ thelons = self.lons[:, int(cols / 2)]
691
+ thelons = thelons.where(thelons.notnull(), drop=True)
692
+ thelats = self.lats[:, int(cols / 2)]
693
+ thelats = thelats.where(thelats.notnull(), drop=True)
694
+ lon1, lon2 = np.asanyarray(thelons[[0, -1]])
695
+ lines = len(thelats)
696
+ lat1, lat, lat2 = np.asanyarray(thelats[[0, int(lines / 2), -1]])
697
+
698
+ proj_dict2points = {'proj': 'omerc', 'lat_0': lat, 'ellps': ellipsoid,
699
+ 'lat_1': lat1, 'lon_1': lon1,
700
+ 'lat_2': lat2, 'lon_2': lon2,
701
+ 'no_rot': True
702
+ }
703
+
704
+ # We need to compute alpha-based omerc for geotiff support
705
+ lonc, lat0 = Proj(**proj_dict2points)(0, 0, inverse=True)
706
+ az1, az2, _ = Geod(**proj_dict2points).inv(lonc, lat0, lon2, lat2)
707
+ azimuth = az1
708
+ az1, az2, _ = Geod(**proj_dict2points).inv(lonc, lat0, lon1, lat1)
709
+ if abs(az1 - azimuth) > 1:
710
+ if abs(az2 - azimuth) > 1:
711
+ logger.warning("Can't find appropriate azimuth.")
712
+ else:
713
+ azimuth += az2
714
+ azimuth /= 2
715
+ else:
716
+ azimuth += az1
717
+ azimuth /= 2
718
+ if abs(azimuth) > 90:
719
+ azimuth = 180 + azimuth
720
+
721
+ prj_params = {'proj': 'omerc', 'alpha': float(azimuth), 'lat_0': float(lat0), 'lonc': float(lonc),
722
+ 'gamma': 0,
723
+ 'ellps': ellipsoid}
724
+
725
+ return prj_params
726
+
727
+ def _compute_generic_parameters(self, projection, ellipsoid):
728
+ """Compute the projection bb parameters for most projections."""
729
+ lines, cols = self.lons.shape
730
+ lat_0 = self.lats[int(lines / 2), int(cols / 2)]
731
+ lon_0 = self.lons[int(lines / 2), int(cols / 2)]
732
+ return {'proj': projection, 'ellps': ellipsoid,
733
+ 'lat_0': lat_0, 'lon_0': lon_0}
734
+
735
+ def get_edge_lonlats(self):
736
+ """Get the concatenated boundary of the current swath."""
737
+ lons, lats = self.get_bbox_lonlats()
738
+ blons = np.ma.concatenate(lons)
739
+ blats = np.ma.concatenate(lats)
740
+ return blons, blats
741
+
742
+ def compute_bb_proj_params(self, proj_dict):
743
+ """Compute BB projection parameters."""
744
+ projection = proj_dict['proj']
745
+ if projection == 'omerc':
746
+ ellipsoid = proj_dict.get('ellps', 'sphere')
747
+ return self._compute_omerc_parameters(ellipsoid)
748
+ else:
749
+ ellipsoid = proj_dict.get('ellps', 'WGS84')
750
+ new_proj = self._compute_generic_parameters(projection, ellipsoid)
751
+ new_proj.update(proj_dict)
752
+ return new_proj
753
+
754
+ def _compute_uniform_shape(self):
755
+ """Compute the height and width of a domain to have uniform resolution across dimensions."""
756
+ g = Geod(ellps='WGS84')
757
+
758
+ def notnull(arr):
759
+ try:
760
+ return arr.where(arr.notnull(), drop=True)
761
+ except AttributeError:
762
+ return arr[np.isfinite(arr)]
763
+ leftlons = self.lons[:, 0]
764
+ rightlons = self.lons[:, -1]
765
+ middlelons = self.lons[:, int(self.lons.shape[1] / 2)]
766
+ leftlats = self.lats[:, 0]
767
+ rightlats = self.lats[:, -1]
768
+ middlelats = self.lats[:, int(self.lats.shape[1] / 2)]
769
+ try:
770
+ import dask.array as da
771
+ except ImportError:
772
+ pass
773
+ else:
774
+ leftlons, rightlons, middlelons, leftlats, rightlats, middlelats = da.compute(leftlons, rightlons,
775
+ middlelons, leftlats,
776
+ rightlats, middlelats)
777
+ leftlons = notnull(leftlons)
778
+ rightlons = notnull(rightlons)
779
+ middlelons = notnull(middlelons)
780
+ leftlats = notnull(leftlats)
781
+ rightlats = notnull(rightlats)
782
+ middlelats = notnull(middlelats)
783
+
784
+ az1, az2, width1 = g.inv(leftlons[0], leftlats[0], rightlons[0], rightlats[0])
785
+ az1, az2, width2 = g.inv(leftlons[-1], leftlats[-1], rightlons[-1], rightlats[-1])
786
+ az1, az2, height = g.inv(middlelons[0], middlelats[0], middlelons[-1], middlelats[-1])
787
+ width = min(width1, width2)
788
+ vresolution = height * 1.0 / self.lons.shape[0]
789
+ hresolution = width * 1.0 / self.lons.shape[1]
790
+ resolution = min(vresolution, hresolution)
791
+ width = int(width * 1.1 / resolution)
792
+ height = int(height * 1.1 / resolution)
793
+ return height, width
794
+
795
+ def compute_optimal_bb_area(self, proj_dict=None):
796
+ """Compute the "best" bounding box area for this swath with `proj_dict`.
797
+
798
+ By default, the projection is Oblique Mercator (`omerc` in proj.4), in
799
+ which case the right projection angle `alpha` is computed from the
800
+ swath centerline. For other projections, only the appropriate center of
801
+ projection and area extents are computed.
802
+
803
+ The height and width are computed so that the resolution is
804
+ approximately the same across dimensions.
805
+ """
806
+ if proj_dict is None:
807
+ proj_dict = {}
808
+ projection = proj_dict.setdefault('proj', 'omerc')
809
+ area_id = projection + '_otf'
810
+ description = 'On-the-fly ' + projection + ' area'
811
+ height, width = self._compute_uniform_shape()
812
+ proj_dict = self.compute_bb_proj_params(proj_dict)
813
+
814
+ area = DynamicAreaDefinition(area_id, description, proj_dict)
815
+ lons, lats = self.get_edge_lonlats()
816
+ return area.freeze((lons, lats), shape=(height, width))
817
+
818
+
819
+ class DynamicAreaDefinition(object):
820
+ """An AreaDefintion containing just a subset of the needed parameters.
821
+
822
+ The purpose of this class is to be able to adapt the area extent and shape
823
+ of the area to a given set of longitudes and latitudes, such that e.g.
824
+ polar satellite granules can be resampled optimally to a given projection.
825
+
826
+ Parameters
827
+ ----------
828
+ area_id:
829
+ The name of the area.
830
+ description:
831
+ The description of the area.
832
+ projection:
833
+ The dictionary or string of projection parameters. Doesn't have to
834
+ be complete. If not complete, ``proj_info`` must be provided to
835
+ ``freeze`` to "fill in" any missing parameters.
836
+ width:
837
+ x dimension in number of pixels, aka number of grid columns
838
+ height:
839
+ y dimension in number of pixels, aka number of grid rows
840
+ shape:
841
+ Corresponding array shape as (height, width)
842
+ area_extent:
843
+ The area extent of the area.
844
+ pixel_size_x:
845
+ Pixel width in projection units
846
+ pixel_size_y:
847
+ Pixel height in projection units
848
+ resolution:
849
+ Resolution of the resulting area as (pixel_size_x, pixel_size_y) or a scalar if pixel_size_x == pixel_size_y.
850
+ optimize_projection:
851
+ Whether the projection parameters have to be optimized.
852
+ rotation:
853
+ Rotation in degrees (negative is cw)
854
+
855
+ """
856
+
857
+ def __init__(self, area_id=None, description=None, projection=None,
858
+ width=None, height=None, area_extent=None,
859
+ resolution=None, optimize_projection=False, rotation=None):
860
+ """Initialize the DynamicAreaDefinition."""
861
+ self.area_id = area_id
862
+ self.description = description
863
+ self.width = width
864
+ self.height = height
865
+ self.shape = (self.height, self.width)
866
+ self.area_extent = area_extent
867
+ self.optimize_projection = optimize_projection
868
+ if isinstance(resolution, (int, float)):
869
+ resolution = (resolution, resolution)
870
+ self.resolution = resolution
871
+ self.rotation = rotation
872
+ self._projection = projection
873
+
874
+ # check if non-dict projections are valid
875
+ # dicts may be updated later
876
+ if not isinstance(self._projection, dict):
877
+ Proj(projection)
878
+
879
+ def _get_proj_dict(self):
880
+ projection = self._projection
881
+ if CRS is not None:
882
+ try:
883
+ crs = CRS(projection)
884
+ except RuntimeError:
885
+ # could be incomplete dictionary
886
+ return projection
887
+ if hasattr(crs, 'to_dict'):
888
+ # pyproj 2.2+
889
+ proj_dict = crs.to_dict()
890
+ else:
891
+ proj_dict = proj4_str_to_dict(crs.to_proj4())
892
+ else:
893
+ if isinstance(projection, str):
894
+ proj_dict = proj4_str_to_dict(projection)
895
+ elif isinstance(projection, dict):
896
+ proj_dict = projection.copy()
897
+ else:
898
+ raise TypeError('Wrong type for projection: {0}. Expected '
899
+ 'dict or string.'.format(type(projection)))
900
+
901
+ return proj_dict
902
+
903
+ @property
904
+ def pixel_size_x(self):
905
+ """Return pixel size in X direction."""
906
+ if self.resolution is None:
907
+ return None
908
+ return self.resolution[0]
909
+
910
+ @property
911
+ def pixel_size_y(self):
912
+ """Return pixel size in Y direction."""
913
+ if self.resolution is None:
914
+ return None
915
+ return self.resolution[1]
916
+
917
+ def compute_domain(self, corners, resolution=None, shape=None):
918
+ """Compute shape and area_extent from corners and [shape or resolution] info.
919
+
920
+ Corners represents the center of pixels, while area_extent represents the edge of pixels.
921
+
922
+ Note that ``shape`` is (rows, columns) and ``resolution`` is
923
+ (x_size, y_size); the dimensions are flipped.
924
+
925
+ """
926
+ if resolution is not None and shape is not None:
927
+ raise ValueError("Both resolution and shape can't be provided.")
928
+ elif resolution is None and shape is None:
929
+ raise ValueError("Either resolution or shape must be provided.")
930
+
931
+ if shape:
932
+ height, width = shape
933
+ x_resolution = (corners[2] - corners[0]) * 1.0 / (width - 1)
934
+ y_resolution = (corners[3] - corners[1]) * 1.0 / (height - 1)
935
+ else:
936
+ if isinstance(resolution, (int, float)):
937
+ resolution = (resolution, resolution)
938
+ x_resolution, y_resolution = resolution
939
+ width = int(np.rint((corners[2] - corners[0]) * 1.0
940
+ / x_resolution + 1))
941
+ height = int(np.rint((corners[3] - corners[1]) * 1.0
942
+ / y_resolution + 1))
943
+
944
+ area_extent = (corners[0] - x_resolution / 2,
945
+ corners[1] - y_resolution / 2,
946
+ corners[2] + x_resolution / 2,
947
+ corners[3] + y_resolution / 2)
948
+ return area_extent, width, height
949
+
950
+ def freeze(self, lonslats=None, resolution=None, shape=None, proj_info=None):
951
+ """Create an AreaDefinition from this area with help of some extra info.
952
+
953
+ Parameters
954
+ ----------
955
+ lonlats : SwathDefinition or tuple
956
+ The geographical coordinates to contain in the resulting area.
957
+ A tuple should be ``(lons, lats)``.
958
+ resolution:
959
+ the resolution of the resulting area.
960
+ shape:
961
+ the shape of the resulting area.
962
+ proj_info:
963
+ complementing parameters to the projection info.
964
+
965
+ Resolution and shape parameters are ignored if the instance is created
966
+ with the `optimize_projection` flag set to True.
967
+
968
+ """
969
+ proj_dict = self._get_proj_dict()
970
+ projection = self._projection
971
+ if proj_info is not None:
972
+ # this is now our complete projection information
973
+ proj_dict.update(proj_info)
974
+ projection = proj_dict
975
+
976
+ if self.optimize_projection:
977
+ return lonslats.compute_optimal_bb_area(proj_dict)
978
+ if resolution is None:
979
+ resolution = self.resolution
980
+ if shape is None:
981
+ shape = self.shape
982
+ height, width = shape
983
+ shape = None if None in shape else shape
984
+ area_extent = self.area_extent
985
+ if not area_extent or not width or not height:
986
+ proj4 = Proj(proj_dict)
987
+ try:
988
+ lons, lats = lonslats
989
+ except (TypeError, ValueError):
990
+ lons, lats = lonslats.get_lonlats()
991
+ xarr, yarr = proj4(np.asarray(lons), np.asarray(lats))
992
+ xarr[xarr > 9e29] = np.nan
993
+ yarr[yarr > 9e29] = np.nan
994
+ corners = [np.nanmin(xarr), np.nanmin(yarr),
995
+ np.nanmax(xarr), np.nanmax(yarr)]
996
+ area_extent, width, height = self.compute_domain(corners, resolution, shape)
997
+ return AreaDefinition(self.area_id, self.description, '',
998
+ projection, width, height,
999
+ area_extent, self.rotation)
1000
+
1001
+
1002
+ def invproj(data_x, data_y, proj_dict):
1003
+ """Perform inverse projection."""
1004
+ # XXX: does pyproj copy arrays? What can we do so it doesn't?
1005
+ target_proj = Proj(proj_dict)
1006
+ return np.dstack(target_proj(data_x, data_y, inverse=True))
1007
+
1008
+
1009
+ class AreaDefinition(BaseDefinition):
1010
+ """Holds definition of an area.
1011
+
1012
+ Parameters
1013
+ ----------
1014
+ area_id : str
1015
+ Identifier for the area
1016
+ description : str
1017
+ Human-readable description of the area
1018
+ proj_id : str
1019
+ ID of projection
1020
+ projection: dict or str or pyproj.crs.CRS
1021
+ Dictionary of PROJ parameters or string of PROJ or WKT parameters.
1022
+ Can also be a :class:`pyproj.crs.CRS` object.
1023
+ width : int
1024
+ x dimension in number of pixels, aka number of grid columns
1025
+ height : int
1026
+ y dimension in number of pixels, aka number of grid rows
1027
+ area_extent : list
1028
+ Area extent as a list (lower_left_x, lower_left_y, upper_right_x, upper_right_y)
1029
+ rotation: float, optional
1030
+ rotation in degrees (negative is clockwise)
1031
+ nprocs : int, optional
1032
+ Number of processor cores to be used for certain calculations
1033
+
1034
+ Attributes
1035
+ ----------
1036
+ area_id : str
1037
+ Identifier for the area
1038
+ description : str
1039
+ Human-readable description of the area
1040
+ proj_id : str
1041
+ ID of projection
1042
+ projection : dict or str
1043
+ Dictionary or string with Proj.4 parameters
1044
+ width : int
1045
+ x dimension in number of pixels, aka number of grid columns
1046
+ height : int
1047
+ y dimension in number of pixels, aka number of grid rows
1048
+ rotation: float
1049
+ rotation in degrees (negative is cw)
1050
+ size : int
1051
+ Number of points in grid
1052
+ area_extent : tuple
1053
+ Area extent as a tuple (lower_left_x, lower_left_y, upper_right_x, upper_right_y)
1054
+ area_extent_ll : tuple
1055
+ Area extent in lons lats as a tuple (lower_left_lon, lower_left_lat, upper_right_lon, upper_right_lat)
1056
+ pixel_size_x : float
1057
+ Pixel width in projection units
1058
+ pixel_size_y : float
1059
+ Pixel height in projection units
1060
+ upper_left_extent : tuple
1061
+ Coordinates (x, y) of upper left corner of upper left pixel in projection units
1062
+ pixel_upper_left : tuple
1063
+ Coordinates (x, y) of center of upper left pixel in projection units
1064
+ pixel_offset_x : float
1065
+ x offset between projection center and upper left corner of upper
1066
+ left pixel in units of pixels.
1067
+ pixel_offset_y : float
1068
+ y offset between projection center and upper left corner of upper
1069
+ left pixel in units of pixels..
1070
+ crs : pyproj.crs.CRS
1071
+ Coordinate reference system object similar to the PROJ parameters in
1072
+ `proj_dict` and `proj_str`. This is the preferred attribute to use
1073
+ when working with the `pyproj` library. Note, however, that this
1074
+ object is not thread-safe and should not be passed between threads.
1075
+ crs_wkt : str
1076
+ WellKnownText version of the CRS object. This is the preferred
1077
+ way of describing CRS information as a string.
1078
+ cartesian_coords : object
1079
+ Grid cartesian coordinates
1080
+
1081
+ """
1082
+
1083
+ def __init__(self, area_id, description, proj_id, projection, width, height,
1084
+ area_extent, rotation=None, nprocs=1, lons=None, lats=None,
1085
+ dtype=np.float64):
1086
+ """Initialize AreaDefinition."""
1087
+ super(AreaDefinition, self).__init__(lons, lats, nprocs)
1088
+ self.area_id = area_id
1089
+ self.description = description
1090
+ self.proj_id = proj_id
1091
+ self.width = int(width)
1092
+ self.height = int(height)
1093
+ self.crop_offset = (0, 0)
1094
+ try:
1095
+ self.rotation = float(rotation)
1096
+ except TypeError:
1097
+ self.rotation = 0
1098
+ if lons is not None:
1099
+ if lons.shape != self.shape:
1100
+ raise ValueError('Shape of lon lat grid must match '
1101
+ 'area definition')
1102
+ self.size = height * width
1103
+ self.ndim = 2
1104
+ self.pixel_size_x = (area_extent[2] - area_extent[0]) / float(width)
1105
+ self.pixel_size_y = (area_extent[3] - area_extent[1]) / float(height)
1106
+ self._area_extent = tuple(area_extent)
1107
+ if CRS is not None:
1108
+ self.crs_wkt = CRS(projection).to_wkt()
1109
+ self._proj_dict = None
1110
+ self.crs = self._crs # see _crs property for details
1111
+ else:
1112
+ if isinstance(projection, str):
1113
+ proj_dict = proj4_str_to_dict(projection)
1114
+ elif isinstance(projection, dict):
1115
+ # use the float-converted dict to pass to Proj
1116
+ projection = convert_proj_floats(projection.items())
1117
+ proj_dict = projection
1118
+ else:
1119
+ raise TypeError('Wrong type for projection: {0}. Expected dict or string.'.format(type(projection)))
1120
+ self._proj_dict = proj_dict
1121
+
1122
+ # Calculate area_extent in lon lat
1123
+ proj = Proj(projection)
1124
+ corner_lons, corner_lats = proj((area_extent[0], area_extent[2]),
1125
+ (area_extent[1], area_extent[3]),
1126
+ inverse=True)
1127
+ self.area_extent_ll = (corner_lons[0], corner_lats[0],
1128
+ corner_lons[1], corner_lats[1])
1129
+
1130
+ # Calculate projection coordinates of extent of upper left pixel
1131
+ self.upper_left_extent = (float(area_extent[0]), float(area_extent[3]))
1132
+ self.pixel_upper_left = (float(area_extent[0]) + float(self.pixel_size_x) / 2,
1133
+ float(area_extent[3]) - float(self.pixel_size_y) / 2)
1134
+
1135
+ # Pixel_offset defines the distance to projection center from origin
1136
+ # (UL) of image in units of pixels.
1137
+ self.pixel_offset_x = -self.area_extent[0] / self.pixel_size_x
1138
+ self.pixel_offset_y = self.area_extent[3] / self.pixel_size_y
1139
+
1140
+ self._projection_x_coords = None
1141
+ self._projection_y_coords = None
1142
+
1143
+ self.dtype = dtype
1144
+
1145
+ @property
1146
+ def _crs(self):
1147
+ """Wrap the `crs` property in a helper property.
1148
+
1149
+ The :class:`pyproj.crs.CRS` object is not thread-safe. To avoid
1150
+ accidentally passing it between threads, we only create it when it
1151
+ is requested (the `self.crs` property). The alternative of storing it
1152
+ as a normal instance attribute could cause issues between threads.
1153
+
1154
+ For backwards compatibility, we only create the `.crs` property if
1155
+ pyproj 2.0+ is installed. Users can then check
1156
+ `hasattr(area_def, 'crs')` to easily support older versions of
1157
+ pyresample and pyproj.
1158
+
1159
+ """
1160
+ return CRS.from_wkt(self.crs_wkt)
1161
+
1162
+ @property
1163
+ def proj_dict(self):
1164
+ """Return the PROJ projection dictionary.
1165
+
1166
+ This is no longer the preferred way of describing CRS information.
1167
+ Switch to the `crs` or `crs_wkt` properties for the most flexibility.
1168
+ """
1169
+ if self._proj_dict is None and hasattr(self, 'crs'):
1170
+ if hasattr(self.crs, 'to_dict'):
1171
+ # pyproj 2.2+
1172
+ self._proj_dict = self.crs.to_dict()
1173
+ else:
1174
+ self._proj_dict = proj4_str_to_dict(self.crs.to_proj4())
1175
+ return self._proj_dict
1176
+
1177
+ @property
1178
+ def area_extent(self):
1179
+ return self._area_extent
1180
+
1181
+ def copy(self, **override_kwargs):
1182
+ """Make a copy of the current area.
1183
+
1184
+ This replaces the current values with anything in *override_kwargs*.
1185
+ """
1186
+ kwargs = {'area_id': self.area_id,
1187
+ 'description': self.description,
1188
+ 'proj_id': self.proj_id,
1189
+ 'projection': self.proj_dict,
1190
+ 'width': self.width,
1191
+ 'height': self.height,
1192
+ 'area_extent': self.area_extent,
1193
+ 'rotation': self.rotation}
1194
+ kwargs.update(override_kwargs)
1195
+ return AreaDefinition(**kwargs)
1196
+
1197
+ def aggregate(self, **dims):
1198
+ """Return an aggregated version of the area."""
1199
+ width = int(self.width / dims.get('x', 1))
1200
+ height = int(self.height / dims.get('y', 1))
1201
+ return self.copy(height=height, width=width)
1202
+
1203
+ @property
1204
+ def shape(self):
1205
+ """Return area shape."""
1206
+ return self.height, self.width
1207
+
1208
+ @property
1209
+ def resolution(self):
1210
+ """Return area resolution in X and Y direction."""
1211
+ return self.pixel_size_x, self.pixel_size_y
1212
+
1213
+ @property
1214
+ def name(self):
1215
+ """Return area name."""
1216
+ warnings.warn("'name' is deprecated, use 'description' instead.", PendingDeprecationWarning)
1217
+ return self.description
1218
+
1219
+ @property
1220
+ def x_size(self):
1221
+ """Return area width."""
1222
+ warnings.warn("'x_size' is deprecated, use 'width' instead.", PendingDeprecationWarning)
1223
+ return self.width
1224
+
1225
+ @property
1226
+ def y_size(self):
1227
+ """Return area height."""
1228
+ warnings.warn("'y_size' is deprecated, use 'height' instead.", PendingDeprecationWarning)
1229
+ return self.height
1230
+
1231
+ @classmethod
1232
+ def from_epsg(cls, code, resolution):
1233
+ """Create an AreaDefinition object from an epsg core (string or int) and a resolution."""
1234
+ if CRS is None:
1235
+ raise NotImplementedError
1236
+ crs = CRS('EPSG:' + str(code))
1237
+ bounds = crs.area_of_use.bounds
1238
+ proj = Proj(crs)
1239
+ left1, low1 = proj(bounds[0], bounds[1])
1240
+ right1, up1 = proj(bounds[2], bounds[3])
1241
+ left2, up2 = proj(bounds[0], bounds[3])
1242
+ right2, low2 = proj(bounds[2], bounds[1])
1243
+ left = min(left1, left2)
1244
+ right = max(right1, right2)
1245
+ up = max(up1, up2)
1246
+ low = min(low1, low2)
1247
+ area_extent = (left, low, right, up)
1248
+ return create_area_def(crs.name, crs.to_dict(), area_extent=area_extent, resolution=resolution)
1249
+
1250
+ @classmethod
1251
+ def from_extent(cls, area_id, projection, shape, area_extent, units=None, **kwargs):
1252
+ """Create an AreaDefinition object from area_extent and shape.
1253
+
1254
+ Parameters
1255
+ ----------
1256
+ area_id : str
1257
+ ID of area
1258
+ projection : dict or str
1259
+ Projection parameters as a proj4_dict or proj4_string
1260
+ shape : list
1261
+ Number of pixels in the y and x direction (height, width)
1262
+ area_extent : list
1263
+ Area extent as a list (lower_left_x, lower_left_y, upper_right_x, upper_right_y)
1264
+ units : str, optional
1265
+ Units that provided arguments should be interpreted as. This can be
1266
+ one of 'deg', 'degrees', 'meters', 'metres', and any
1267
+ parameter supported by the
1268
+ `cs2cs -lu <https://proj4.org/apps/cs2cs.html#cmdoption-cs2cs-lu>`_
1269
+ command. Units are determined in the following priority:
1270
+
1271
+ 1. units expressed with each variable through a DataArray's attrs attribute.
1272
+ 2. units passed to ``units``
1273
+ 3. units used in ``projection``
1274
+ 4. meters
1275
+
1276
+ description : str, optional
1277
+ Description/name of area. Defaults to area_id
1278
+ proj_id : str, optional
1279
+ ID of projection
1280
+ rotation: float, optional
1281
+ rotation in degrees (negative is cw)
1282
+ nprocs : int, optional
1283
+ Number of processor cores to be used
1284
+ lons : numpy array, optional
1285
+ Grid lons
1286
+ lats : numpy array, optional
1287
+ Grid lats
1288
+
1289
+ Returns
1290
+ -------
1291
+ AreaDefinition : AreaDefinition
1292
+
1293
+ """
1294
+ return create_area_def(area_id, projection, shape=shape, area_extent=area_extent, units=units, **kwargs)
1295
+
1296
+ @classmethod
1297
+ def from_circle(cls, area_id, projection, center, radius, shape=None, resolution=None, units=None, **kwargs):
1298
+ """Create an AreaDefinition from center, radius, and shape or from center, radius, and resolution.
1299
+
1300
+ Parameters
1301
+ ----------
1302
+ area_id : str
1303
+ ID of area
1304
+ projection : dict or str
1305
+ Projection parameters as a proj4_dict or proj4_string
1306
+ center : list
1307
+ Center of projection (x, y)
1308
+ radius : list or float
1309
+ Length from the center to the edges of the projection (dx, dy)
1310
+ shape : list, optional
1311
+ Number of pixels in the y and x direction (height, width)
1312
+ resolution : list or float, optional
1313
+ Size of pixels: (dx, dy)
1314
+ units : str, optional
1315
+ Units that provided arguments should be interpreted as. This can be
1316
+ one of 'deg', 'degrees', 'meters', 'metres', and any
1317
+ parameter supported by the
1318
+ `cs2cs -lu <https://proj4.org/apps/cs2cs.html#cmdoption-cs2cs-lu>`_
1319
+ command. Units are determined in the following priority:
1320
+
1321
+ 1. units expressed with each variable through a DataArray's attrs attribute.
1322
+ 2. units passed to ``units``
1323
+ 3. units used in ``projection``
1324
+ 4. meters
1325
+
1326
+ description : str, optional
1327
+ Description/name of area. Defaults to area_id
1328
+ proj_id : str, optional
1329
+ ID of projection
1330
+ rotation: float, optional
1331
+ rotation in degrees (negative is cw)
1332
+ nprocs : int, optional
1333
+ Number of processor cores to be used
1334
+ lons : numpy array, optional
1335
+ Grid lons
1336
+ lats : numpy array, optional
1337
+ Grid lats
1338
+ optimize_projection:
1339
+ Whether the projection parameters have to be optimized for a DynamicAreaDefinition.
1340
+
1341
+ Returns
1342
+ -------
1343
+ AreaDefinition or DynamicAreaDefinition : AreaDefinition or DynamicAreaDefinition
1344
+ If shape or resolution are provided, an AreaDefinition object is returned.
1345
+ Else a DynamicAreaDefinition object is returned
1346
+
1347
+ Notes
1348
+ -----
1349
+ * ``resolution`` and ``radius`` can be specified with one value if dx == dy
1350
+
1351
+ """
1352
+ return create_area_def(area_id, projection, shape=shape, center=center, radius=radius,
1353
+ resolution=resolution, units=units, **kwargs)
1354
+
1355
+ @classmethod
1356
+ def from_area_of_interest(cls, area_id, projection, shape, center, resolution, units=None, **kwargs):
1357
+ """Create an AreaDefinition from center, resolution, and shape.
1358
+
1359
+ Parameters
1360
+ ----------
1361
+ area_id : str
1362
+ ID of area
1363
+ projection : dict or str
1364
+ Projection parameters as a proj4_dict or proj4_string
1365
+ shape : list
1366
+ Number of pixels in the y and x direction (height, width)
1367
+ center : list
1368
+ Center of projection (x, y)
1369
+ resolution : list or float
1370
+ Size of pixels: (dx, dy). Can be specified with one value if dx == dy
1371
+ units : str, optional
1372
+ Units that provided arguments should be interpreted as. This can be
1373
+ one of 'deg', 'degrees', 'meters', 'metres', and any
1374
+ parameter supported by the
1375
+ `cs2cs -lu <https://proj4.org/apps/cs2cs.html#cmdoption-cs2cs-lu>`_
1376
+ command. Units are determined in the following priority:
1377
+
1378
+ 1. units expressed with each variable through a DataArray's attrs attribute.
1379
+ 2. units passed to ``units``
1380
+ 3. units used in ``projection``
1381
+ 4. meters
1382
+
1383
+ description : str, optional
1384
+ Description/name of area. Defaults to area_id
1385
+ proj_id : str, optional
1386
+ ID of projection
1387
+ rotation: float, optional
1388
+ rotation in degrees (negative is cw)
1389
+ nprocs : int, optional
1390
+ Number of processor cores to be used
1391
+ lons : numpy array, optional
1392
+ Grid lons
1393
+ lats : numpy array, optional
1394
+ Grid lats
1395
+
1396
+ Returns
1397
+ -------
1398
+ AreaDefinition : AreaDefinition
1399
+
1400
+ """
1401
+ return create_area_def(area_id, projection, shape=shape, center=center,
1402
+ resolution=resolution, units=units, **kwargs)
1403
+
1404
+ @classmethod
1405
+ def from_ul_corner(cls, area_id, projection, shape, upper_left_extent, resolution, units=None, **kwargs):
1406
+ """Create an AreaDefinition object from upper_left_extent, resolution, and shape.
1407
+
1408
+ Parameters
1409
+ ----------
1410
+ area_id : str
1411
+ ID of area
1412
+ projection : dict or str
1413
+ Projection parameters as a proj4_dict or proj4_string
1414
+ shape : list
1415
+ Number of pixels in the y and x direction (height, width)
1416
+ upper_left_extent : list
1417
+ Upper left corner of upper left pixel (x, y)
1418
+ resolution : list or float
1419
+ Size of pixels in **meters**: (dx, dy). Can be specified with one value if dx == dy
1420
+ units : str, optional
1421
+ Units that provided arguments should be interpreted as. This can be
1422
+ one of 'deg', 'degrees', 'meters', 'metres', and any
1423
+ parameter supported by the
1424
+ `cs2cs -lu <https://proj4.org/apps/cs2cs.html#cmdoption-cs2cs-lu>`_
1425
+ command. Units are determined in the following priority:
1426
+
1427
+ 1. units expressed with each variable through a DataArray's attrs attribute.
1428
+ 2. units passed to ``units``
1429
+ 3. units used in ``projection``
1430
+ 4. meters
1431
+
1432
+ description : str, optional
1433
+ Description/name of area. Defaults to area_id
1434
+ proj_id : str, optional
1435
+ ID of projection
1436
+ rotation: float, optional
1437
+ rotation in degrees (negative is cw)
1438
+ nprocs : int, optional
1439
+ Number of processor cores to be used
1440
+ lons : numpy array, optional
1441
+ Grid lons
1442
+ lats : numpy array, optional
1443
+ Grid lats
1444
+
1445
+ Returns
1446
+ -------
1447
+ AreaDefinition : AreaDefinition
1448
+
1449
+ """
1450
+ return create_area_def(area_id, projection, shape=shape, upper_left_extent=upper_left_extent,
1451
+ resolution=resolution, units=units, **kwargs)
1452
+
1453
+ @classmethod
1454
+ def from_cf(cls, cf_file, variable=None, y=None, x=None):
1455
+ """Create an AreaDefinition object from a netCDF/CF file.
1456
+
1457
+ Parameters
1458
+ ----------
1459
+ nc_file : string or object
1460
+ path to a netCDF/CF file, or opened xarray.Dataset object
1461
+ variable : string, optional
1462
+ name of the variable to load the AreaDefinition from
1463
+ If variable is None the file will be searched for valid CF
1464
+ area definitions
1465
+ y : string, optional
1466
+ name of the variable to use as 'y' axis of the CF area definition
1467
+ If y is None an appropriate 'y' axis will be deduced from the CF file
1468
+ x : string, optional
1469
+ name of the variable to use as 'x' axis of the CF area definition
1470
+ If x is None an appropriate 'x' axis will be deduced from the CF file
1471
+
1472
+ Returns
1473
+ -------
1474
+ AreaDefinition : AreaDefinition
1475
+
1476
+ """
1477
+ return load_cf_area(cf_file, variable=variable, y=y, x=x)[0]
1478
+
1479
+ def __hash__(self):
1480
+ """Compute the hash of this object."""
1481
+ if self.hash is None:
1482
+ self.hash = int(self.update_hash().hexdigest(), 16)
1483
+ return self.hash
1484
+
1485
+ @property
1486
+ def proj_str(self):
1487
+ """Return PROJ projection string.
1488
+
1489
+ This is no longer the preferred way of describing CRS information.
1490
+ Switch to the `crs` or `crs_wkt` properties for the most flexibility.
1491
+
1492
+ """
1493
+ proj_dict = self.proj_dict.copy()
1494
+ if 'towgs84' in proj_dict and isinstance(proj_dict['towgs84'], list):
1495
+ # pyproj 2+ creates a list in the dictionary
1496
+ # but the string should be comma-separated
1497
+ if all(x == 0 for x in proj_dict['towgs84']):
1498
+ # all 0s in towgs84 are technically equal to not having them
1499
+ # specified, but PROJ considers them different
1500
+ proj_dict.pop('towgs84')
1501
+ else:
1502
+ proj_dict['towgs84'] = ','.join(str(x) for x in proj_dict['towgs84'])
1503
+ return proj4_dict_to_str(proj_dict, sort=True)
1504
+
1505
+ def __str__(self):
1506
+ """Return string representation of the AreaDefinition."""
1507
+ # We need a sorted dictionary for a unique hash of str(self)
1508
+ proj_dict = self.proj_dict
1509
+ proj_str = ('{' +
1510
+ ', '.join(["'%s': '%s'" % (str(k), str(proj_dict[k]))
1511
+ for k in sorted(proj_dict.keys())]) +
1512
+ '}')
1513
+ if not self.proj_id:
1514
+ third_line = ""
1515
+ else:
1516
+ third_line = "Projection ID: {0}\n".format(self.proj_id)
1517
+ return ('Area ID: {0}\nDescription: {1}\n{2}'
1518
+ 'Projection: {3}\nNumber of columns: {4}\nNumber of rows: {5}\n'
1519
+ 'Area extent: {6}').format(self.area_id, self.description, third_line,
1520
+ proj_str, self.width, self.height,
1521
+ tuple(round(x, 4) for x in self.area_extent))
1522
+
1523
+ __repr__ = __str__
1524
+
1525
+ def to_cartopy_crs(self):
1526
+ """Convert projection to cartopy CRS object."""
1527
+ from pyresample.utils.cartopy import from_proj
1528
+ bounds = (self.area_extent[0],
1529
+ self.area_extent[2],
1530
+ self.area_extent[1],
1531
+ self.area_extent[3])
1532
+ if hasattr(self, 'crs') and self.crs.to_epsg() is not None:
1533
+ proj_params = "EPSG:{}".format(self.crs.to_epsg())
1534
+ else:
1535
+ proj_params = self.proj_str
1536
+ if Proj(proj_params).is_latlong():
1537
+ # Convert area extent from degrees to radians
1538
+ bounds = np.deg2rad(bounds)
1539
+ crs = from_proj(proj_params, bounds=bounds)
1540
+ return crs
1541
+
1542
+ def create_areas_def(self):
1543
+ """Generate YAML formatted representation of this area."""
1544
+ if hasattr(self, 'crs') and self.crs.to_epsg() is not None:
1545
+ proj_dict = {'EPSG': self.crs.to_epsg()}
1546
+ else:
1547
+ proj_dict = self.proj_dict
1548
+ # pyproj 2.0+ adds a '+type=crs' parameter
1549
+ proj_dict.pop('type', None)
1550
+
1551
+ res = OrderedDict(description=self.description,
1552
+ projection=OrderedDict(proj_dict),
1553
+ shape=OrderedDict([('height', self.height), ('width', self.width)]))
1554
+ units = res['projection'].pop('units', None)
1555
+ extent = OrderedDict([('lower_left_xy', list(self.area_extent[:2])),
1556
+ ('upper_right_xy', list(self.area_extent[2:]))])
1557
+ if units is not None:
1558
+ extent['units'] = units
1559
+ res['area_extent'] = extent
1560
+
1561
+ return ordered_dump(OrderedDict([(self.area_id, res)]))
1562
+
1563
+ def create_areas_def_legacy(self):
1564
+ """Create area definition in legacy format."""
1565
+ proj_dict = self.proj_dict
1566
+ proj_str = ','.join(["%s=%s" % (str(k), str(proj_dict[k]))
1567
+ for k in sorted(proj_dict.keys())])
1568
+
1569
+ fmt = "REGION: {name} {{\n"
1570
+ fmt += "\tNAME:\t{name}\n"
1571
+ fmt += "\tPCS_ID:\t{area_id}\n"
1572
+ fmt += "\tPCS_DEF:\t{proj_str}\n"
1573
+ fmt += "\tXSIZE:\t{x_size}\n"
1574
+ fmt += "\tYSIZE:\t{y_size}\n"
1575
+ # fmt += "\tROTATION:\t{rotation}\n"
1576
+ fmt += "\tAREA_EXTENT: {area_extent}\n}};\n"
1577
+ area_def_str = fmt.format(name=self.description, area_id=self.area_id,
1578
+ proj_str=proj_str, x_size=self.width,
1579
+ y_size=self.height,
1580
+ area_extent=self.area_extent)
1581
+ return area_def_str
1582
+
1583
+ def __eq__(self, other):
1584
+ """Test for equality."""
1585
+ try:
1586
+ return ((self.proj_str == other.proj_str) and
1587
+ (self.shape == other.shape) and
1588
+ (np.allclose(self.area_extent, other.area_extent)))
1589
+ except AttributeError:
1590
+ return super(AreaDefinition, self).__eq__(other)
1591
+
1592
+ def __ne__(self, other):
1593
+ """Test for equality."""
1594
+ return not self.__eq__(other)
1595
+
1596
+ def update_hash(self, the_hash=None):
1597
+ """Update a hash, or return a new one if needed."""
1598
+ if the_hash is None:
1599
+ the_hash = hashlib.sha1()
1600
+ the_hash.update(self.proj_str.encode('utf-8'))
1601
+ the_hash.update(np.array(self.shape))
1602
+ the_hash.update(np.array(self.area_extent))
1603
+ return the_hash
1604
+
1605
+ def colrow2lonlat(self, cols, rows):
1606
+ """Return lons and lats for the given image columns and rows.
1607
+
1608
+ Both scalars and arrays are supported. To be used with scarse
1609
+ data points instead of slices (see get_lonlats).
1610
+
1611
+ """
1612
+ p = Proj(self.proj_str)
1613
+ x = self.projection_x_coords
1614
+ y = self.projection_y_coords
1615
+ return p(x[cols], y[rows], inverse=True)
1616
+
1617
+ def lonlat2colrow(self, lons, lats):
1618
+ """Return image columns and rows for the given lons and lats.
1619
+
1620
+ Both scalars and arrays are supported. Same as
1621
+ get_xy_from_lonlat, renamed for convenience.
1622
+
1623
+ """
1624
+ return self.get_xy_from_lonlat(lons, lats)
1625
+
1626
+ def get_xy_from_lonlat(self, lon, lat):
1627
+ """Retrieve closest x and y coordinates.
1628
+
1629
+ Retrieve closest x and y coordinates (column, row indices) for the
1630
+ specified geolocation (lon,lat) if inside area. If lon,lat is a point a
1631
+ ValueError is raised if the return point is outside the area domain. If
1632
+ lon,lat is a tuple of sequences of longitudes and latitudes, a tuple of
1633
+ masked arrays are returned.
1634
+
1635
+ :Input:
1636
+
1637
+ lon : point or sequence (list or array) of longitudes
1638
+ lat : point or sequence (list or array) of latitudes
1639
+
1640
+ :Returns:
1641
+
1642
+ (x, y) : tuple of integer points/arrays
1643
+
1644
+ """
1645
+ if isinstance(lon, list):
1646
+ lon = np.array(lon)
1647
+ if isinstance(lat, list):
1648
+ lat = np.array(lat)
1649
+
1650
+ if ((isinstance(lon, np.ndarray) and
1651
+ not isinstance(lat, np.ndarray)) or (not isinstance(lon, np.ndarray) and isinstance(lat, np.ndarray))):
1652
+ raise ValueError("Both lon and lat needs to be of " +
1653
+ "the same type and have the same dimensions!")
1654
+
1655
+ if isinstance(lon, np.ndarray) and isinstance(lat, np.ndarray):
1656
+ if lon.shape != lat.shape:
1657
+ raise ValueError("lon and lat is not of the same shape!")
1658
+
1659
+ pobj = Proj(self.proj_str)
1660
+ xm_, ym_ = pobj(lon, lat)
1661
+
1662
+ return self.get_xy_from_proj_coords(xm_, ym_)
1663
+
1664
+ def get_xy_from_proj_coords(self, xm, ym):
1665
+ """Find closest grid cell index for a specified projection coordinate.
1666
+
1667
+ If xm, ym is a tuple of sequences of projection coordinates, a tuple
1668
+ of masked arrays are returned.
1669
+
1670
+ Args:
1671
+ xm (list or array): point or sequence of x-coordinates in
1672
+ meters (map projection)
1673
+ ym (list or array): point or sequence of y-coordinates in
1674
+ meters (map projection)
1675
+
1676
+ Returns:
1677
+ x, y : column and row grid cell indexes as 2 scalars or arrays
1678
+
1679
+ Raises:
1680
+ ValueError: if the return point is outside the area domain
1681
+
1682
+ """
1683
+ if isinstance(xm, list):
1684
+ xm = np.array(xm)
1685
+ if isinstance(ym, list):
1686
+ ym = np.array(ym)
1687
+
1688
+ if ((isinstance(xm, np.ndarray) and
1689
+ not isinstance(ym, np.ndarray)) or (not isinstance(xm, np.ndarray) and isinstance(ym, np.ndarray))):
1690
+ raise ValueError("Both projection coordinates xm and ym needs to be of " +
1691
+ "the same type and have the same dimensions!")
1692
+
1693
+ if isinstance(xm, np.ndarray) and isinstance(ym, np.ndarray):
1694
+ if xm.shape != ym.shape:
1695
+ raise ValueError(
1696
+ "projection coordinates xm and ym is not of the same shape!")
1697
+
1698
+ upl_x = self.area_extent[0]
1699
+ upl_y = self.area_extent[3]
1700
+ xscale = (self.area_extent[2] -
1701
+ self.area_extent[0]) / float(self.width)
1702
+ # because rows direction is the opposite of y's
1703
+ yscale = (self.area_extent[1] -
1704
+ self.area_extent[3]) / float(self.height)
1705
+
1706
+ x__ = (xm - upl_x) / xscale
1707
+ y__ = (ym - upl_y) / yscale
1708
+
1709
+ if isinstance(x__, np.ndarray) and isinstance(y__, np.ndarray):
1710
+ mask = (((x__ < 0) | (x__ >= self.width)) |
1711
+ ((y__ < 0) | (y__ >= self.height)))
1712
+ return (np.ma.masked_array(x__.astype('int'), mask=mask,
1713
+ fill_value=-1, copy=False),
1714
+ np.ma.masked_array(y__.astype('int'), mask=mask,
1715
+ fill_value=-1, copy=False))
1716
+ else:
1717
+ if ((x__ < 0 or x__ >= self.width) or
1718
+ (y__ < 0 or y__ >= self.height)):
1719
+ raise ValueError('Point outside area:( %f %f)' % (x__, y__))
1720
+ return int(x__), int(y__)
1721
+
1722
+ def get_lonlat(self, row, col):
1723
+ """Retrieve lon and lat values of single point in area grid.
1724
+
1725
+ Parameters
1726
+ ----------
1727
+ row : int
1728
+ col : int
1729
+
1730
+ Returns
1731
+ -------
1732
+ (lon, lat) : tuple of floats
1733
+
1734
+ """
1735
+ lon, lat = self.get_lonlats(nprocs=None, data_slice=(row, col))
1736
+ return lon.item(), lat.item()
1737
+
1738
+ @staticmethod
1739
+ def _do_rotation(xspan, yspan, rot_deg=0):
1740
+ """Apply a rotation factor to a matrix of points."""
1741
+ if hasattr(xspan, 'chunks'):
1742
+ # we were given dask arrays, use dask functions
1743
+ import dask.array as numpy
1744
+ else:
1745
+ numpy = np
1746
+ rot_rad = numpy.radians(rot_deg)
1747
+ rot_mat = numpy.array([[np.cos(rot_rad), np.sin(rot_rad)], [-np.sin(rot_rad), np.cos(rot_rad)]])
1748
+ x, y = numpy.meshgrid(xspan, yspan)
1749
+ return numpy.einsum('ji, mni -> jmn', rot_mat, numpy.dstack([x, y]))
1750
+
1751
+ def get_proj_vectors_dask(self, chunks=None, dtype=None):
1752
+ """Get projection vectors."""
1753
+ warnings.warn("'get_proj_vectors_dask' is deprecated, please use "
1754
+ "'get_proj_vectors' with the 'chunks' keyword argument specified.", DeprecationWarning)
1755
+ if chunks is None:
1756
+ chunks = CHUNK_SIZE # FUTURE: Use a global config object instead
1757
+ return self.get_proj_vectors(dtype=dtype, chunks=chunks)
1758
+
1759
+ def _get_proj_vectors(self, dtype=None, check_rotation=True, chunks=None):
1760
+ """Get 1D projection coordinates."""
1761
+ x_kwargs = {}
1762
+ y_kwargs = {}
1763
+
1764
+ if chunks is not None and not isinstance(chunks, int):
1765
+ y_chunks = chunks[0]
1766
+ x_chunks = chunks[1]
1767
+ else:
1768
+ y_chunks = x_chunks = chunks
1769
+
1770
+ if x_chunks is not None or y_chunks is not None:
1771
+ # use dask functions instead of numpy
1772
+ from dask.array import arange
1773
+ x_kwargs = {'chunks': x_chunks}
1774
+ y_kwargs = {'chunks': y_chunks}
1775
+ else:
1776
+ arange = np.arange
1777
+ if check_rotation and self.rotation != 0:
1778
+ warnings.warn("Projection vectors will not be accurate because rotation is not 0", RuntimeWarning)
1779
+ if dtype is None:
1780
+ dtype = self.dtype
1781
+ x_kwargs['dtype'] = dtype
1782
+ y_kwargs['dtype'] = dtype
1783
+
1784
+ target_x = arange(self.width, **x_kwargs) * self.pixel_size_x + self.pixel_upper_left[0]
1785
+ target_y = arange(self.height, **y_kwargs) * -self.pixel_size_y + self.pixel_upper_left[1]
1786
+ return target_x, target_y
1787
+
1788
+ def get_proj_vectors(self, dtype=None, chunks=None):
1789
+ """Calculate 1D projection coordinates for the X and Y dimension.
1790
+
1791
+ Parameters
1792
+ ----------
1793
+ dtype : numpy.dtype
1794
+ Numpy data type for the returned arrays
1795
+ chunks : int or tuple
1796
+ Return dask arrays with the chunk size specified. If this is a
1797
+ tuple then the first element is the Y array's chunk size and the
1798
+ second is the X array's chunk size.
1799
+
1800
+ Returns
1801
+ -------
1802
+ tuple: (X, Y) where X and Y are 1-dimensional numpy arrays
1803
+
1804
+ The data type of the returned arrays can be controlled with the
1805
+ `dtype` keyword argument. If `chunks` is provided then dask arrays
1806
+ are returned instead.
1807
+
1808
+ """
1809
+ return self._get_proj_vectors(dtype=dtype, chunks=chunks)
1810
+
1811
+ def get_proj_coords_dask(self, chunks=None, dtype=None):
1812
+ """Get projection coordinates."""
1813
+ warnings.warn("'get_proj_coords_dask' is deprecated, please use "
1814
+ "'get_proj_coords' with the 'chunks' keyword argument specified.", DeprecationWarning)
1815
+ if chunks is None:
1816
+ chunks = CHUNK_SIZE # FUTURE: Use a global config object instead
1817
+ return self.get_proj_coords(chunks=chunks, dtype=dtype)
1818
+
1819
+ def get_proj_coords(self, data_slice=None, dtype=None, chunks=None):
1820
+ """Get projection coordinates of grid.
1821
+
1822
+ Parameters
1823
+ ----------
1824
+ data_slice : slice object, optional
1825
+ Calculate only coordinates for specified slice
1826
+ dtype : numpy.dtype, optional
1827
+ Data type of the returned arrays
1828
+ chunks: int or tuple, optional
1829
+ Create dask arrays and use this chunk size
1830
+
1831
+ Returns
1832
+ -------
1833
+ (target_x, target_y) : tuple of numpy arrays
1834
+ Grids of area x- and y-coordinates in projection units
1835
+
1836
+ .. versionchanged:: 1.11.0
1837
+
1838
+ Removed 'cache' keyword argument and add 'chunks' for creating
1839
+ dask arrays.
1840
+
1841
+ """
1842
+ target_x, target_y = self._get_proj_vectors(dtype=dtype, check_rotation=False, chunks=chunks)
1843
+ if data_slice is not None and isinstance(data_slice, slice):
1844
+ target_y = target_y[data_slice]
1845
+ elif data_slice is not None:
1846
+ target_y = target_y[data_slice[0]]
1847
+ target_x = target_x[data_slice[1]]
1848
+
1849
+ if self.rotation != 0:
1850
+ res = self._do_rotation(target_x, target_y, self.rotation)
1851
+ target_x, target_y = res[0, :, :], res[1, :, :]
1852
+ elif chunks is not None:
1853
+ import dask.array as da
1854
+ target_x, target_y = da.meshgrid(target_x, target_y)
1855
+ else:
1856
+ target_x, target_y = np.meshgrid(target_x, target_y)
1857
+
1858
+ return target_x, target_y
1859
+
1860
+ @property
1861
+ def projection_x_coords(self):
1862
+ """Return projection X coordinates."""
1863
+ if self.rotation != 0:
1864
+ # rotation is only supported in 'get_proj_coords' right now
1865
+ return self.get_proj_coords(data_slice=(0, slice(None)))[0].squeeze()
1866
+ return self.get_proj_vectors()[0]
1867
+
1868
+ @property
1869
+ def projection_y_coords(self):
1870
+ """Return projection Y coordinates."""
1871
+ if self.rotation != 0:
1872
+ # rotation is only supported in 'get_proj_coords' right now
1873
+ return self.get_proj_coords(data_slice=(slice(None), 0))[1].squeeze()
1874
+ return self.get_proj_vectors()[1]
1875
+
1876
+ @property
1877
+ def outer_boundary_corners(self):
1878
+ """Return the lon,lat of the outer edges of the corner points."""
1879
+ from pyresample.spherical_geometry import Coordinate
1880
+ proj = Proj(**self.proj_dict)
1881
+
1882
+ corner_lons, corner_lats = proj((self.area_extent[0], self.area_extent[2],
1883
+ self.area_extent[2], self.area_extent[0]),
1884
+ (self.area_extent[3], self.area_extent[3],
1885
+ self.area_extent[1], self.area_extent[1]),
1886
+ inverse=True)
1887
+ return [Coordinate(corner_lons[0], corner_lats[0]),
1888
+ Coordinate(corner_lons[1], corner_lats[1]),
1889
+ Coordinate(corner_lons[2], corner_lats[2]),
1890
+ Coordinate(corner_lons[3], corner_lats[3])]
1891
+
1892
+ def get_lonlats_dask(self, chunks=None, dtype=None):
1893
+ """Get longitudes and latitudes."""
1894
+ warnings.warn("'get_lonlats_dask' is deprecated, please use "
1895
+ "'get_lonlats' with the 'chunks' keyword argument specified.", DeprecationWarning)
1896
+ if chunks is None:
1897
+ chunks = CHUNK_SIZE # FUTURE: Use a global config object instead
1898
+ return self.get_lonlats(chunks=chunks, dtype=dtype)
1899
+
1900
+ def get_lonlats(self, nprocs=None, data_slice=None, cache=False, dtype=None, chunks=None):
1901
+ """Return lon and lat arrays of area.
1902
+
1903
+ Parameters
1904
+ ----------
1905
+ nprocs : int, optional
1906
+ Number of processor cores to be used.
1907
+ Defaults to the nprocs set when instantiating object
1908
+ data_slice : slice object, optional
1909
+ Calculate only coordinates for specified slice
1910
+ cache : bool, optional
1911
+ Store result the result. Requires data_slice to be None
1912
+ dtype : numpy.dtype, optional
1913
+ Data type of the returned arrays
1914
+ chunks: int or tuple, optional
1915
+ Create dask arrays and use this chunk size
1916
+
1917
+ Returns
1918
+ -------
1919
+ (lons, lats) : tuple of numpy arrays
1920
+ Grids of area lons and and lats
1921
+
1922
+ """
1923
+ if cache:
1924
+ warnings.warn("'cache' keyword argument will be removed in the "
1925
+ "future and data will not be cached.", PendingDeprecationWarning)
1926
+ if dtype is None:
1927
+ dtype = self.dtype
1928
+
1929
+ if self.lons is not None:
1930
+ # Data is cache already
1931
+ lons = self.lons
1932
+ lats = self.lats
1933
+ if data_slice is not None:
1934
+ lons = lons[data_slice]
1935
+ lats = lats[data_slice]
1936
+ return lons, lats
1937
+
1938
+ # Get X/Y coordinates for the whole area
1939
+ target_x, target_y = self.get_proj_coords(data_slice=data_slice, chunks=chunks, dtype=dtype)
1940
+ if nprocs is None and not hasattr(target_x, 'chunks'):
1941
+ nprocs = self.nprocs
1942
+ if nprocs is not None and hasattr(target_x, 'chunks'):
1943
+ # we let 'get_proj_coords' decide if dask arrays should be made
1944
+ # but if the user provided nprocs then this doesn't make sense
1945
+ raise ValueError("Can't specify 'nprocs' and 'chunks' at the same time")
1946
+
1947
+ # Proj.4 definition of target area projection
1948
+ proj_def = self.crs_wkt if hasattr(self, 'crs_wkt') else self.proj_dict
1949
+ if hasattr(target_x, 'chunks'):
1950
+ # we are using dask arrays, map blocks to th
1951
+ from dask.array import map_blocks
1952
+ res = map_blocks(invproj, target_x, target_y,
1953
+ chunks=(target_x.chunks[0], target_x.chunks[1], 2),
1954
+ new_axis=[2], proj_dict=proj_def).astype(dtype)
1955
+ return res[:, :, 0], res[:, :, 1]
1956
+
1957
+ if nprocs > 1:
1958
+ target_proj = Proj_MP(proj_def)
1959
+ else:
1960
+ target_proj = Proj(proj_def)
1961
+
1962
+ # Get corresponding longitude and latitude values
1963
+ lons, lats = target_proj(target_x, target_y, inverse=True, nprocs=nprocs)
1964
+ lons = np.asanyarray(lons, dtype=dtype)
1965
+ lats = np.asanyarray(lats, dtype=dtype)
1966
+
1967
+ if cache and data_slice is None:
1968
+ # Cache the result if requested
1969
+ self.lons = lons
1970
+ self.lats = lats
1971
+
1972
+ return lons, lats
1973
+
1974
+ @property
1975
+ def proj4_string(self):
1976
+ """Return projection definition as Proj.4 string."""
1977
+ warnings.warn("'proj4_string' is deprecated, please use 'proj_str' "
1978
+ "instead.", DeprecationWarning)
1979
+ return proj4_dict_to_str(self.proj_dict)
1980
+
1981
+ def _get_slice_starts_stops(self, area_to_cover):
1982
+ """Get x and y start and stop points for slicing."""
1983
+ llx, lly, urx, ury = area_to_cover.area_extent
1984
+ x, y = self.get_xy_from_proj_coords([llx, urx], [lly, ury])
1985
+
1986
+ if self.area_extent[0] > self.area_extent[2]:
1987
+ xstart = 0 if x[1] is np.ma.masked else x[1]
1988
+ xstop = self.width if x[0] is np.ma.masked else x[0] + 1
1989
+ else:
1990
+ xstart = 0 if x[0] is np.ma.masked else x[0]
1991
+ xstop = self.width if x[1] is np.ma.masked else x[1] + 1
1992
+ if self.area_extent[1] > self.area_extent[3]:
1993
+ ystart = 0 if y[0] is np.ma.masked else y[0]
1994
+ ystop = self.height if y[1] is np.ma.masked else y[1] + 1
1995
+ else:
1996
+ ystart = 0 if y[1] is np.ma.masked else y[1]
1997
+ ystop = self.height if y[0] is np.ma.masked else y[0] + 1
1998
+
1999
+ return xstart, xstop, ystart, ystop
2000
+
2001
+ def get_area_slices(self, area_to_cover, shape_divisible_by=None):
2002
+ """Compute the slice to read based on an `area_to_cover`."""
2003
+ if not isinstance(area_to_cover, AreaDefinition):
2004
+ raise NotImplementedError('Only AreaDefinitions can be used')
2005
+
2006
+ # Intersection only required for two different projections
2007
+ proj_def_to_cover = area_to_cover.crs if hasattr(area_to_cover, 'crs') else area_to_cover.proj_str
2008
+ proj_def = self.crs if hasattr(self, 'crs') else self.proj_str
2009
+ if proj_def_to_cover == proj_def:
2010
+ logger.debug('Projections for data and slice areas are'
2011
+ ' identical: %s',
2012
+ proj_def_to_cover)
2013
+ # Get slice parameters
2014
+ xstart, xstop, ystart, ystop = self._get_slice_starts_stops(
2015
+ area_to_cover)
2016
+
2017
+ return (check_slice_orientation(slice(xstart, xstop)),
2018
+ check_slice_orientation(slice(ystart, ystop)))
2019
+
2020
+ if self.proj_dict.get('proj') != 'geos':
2021
+ raise NotImplementedError("Source projection must be 'geos' if "
2022
+ "source/target projections are not "
2023
+ "equal.")
2024
+
2025
+ data_boundary = Boundary(*get_geostationary_bounding_box(self))
2026
+ if area_to_cover.proj_dict.get('proj') == 'geos':
2027
+ area_boundary = Boundary(
2028
+ *get_geostationary_bounding_box(area_to_cover))
2029
+ else:
2030
+ area_boundary = AreaDefBoundary(area_to_cover, 100)
2031
+
2032
+ intersection = data_boundary.contour_poly.intersection(
2033
+ area_boundary.contour_poly)
2034
+ if intersection is None:
2035
+ logger.debug('Cannot determine appropriate slicing. '
2036
+ "Data and projection area do not overlap.")
2037
+ raise NotImplementedError
2038
+ x, y = self.get_xy_from_lonlat(np.rad2deg(intersection.lon),
2039
+ np.rad2deg(intersection.lat))
2040
+ x_slice = slice(np.ma.min(x), np.ma.max(x) + 1)
2041
+ y_slice = slice(np.ma.min(y), np.ma.max(y) + 1)
2042
+ if shape_divisible_by is not None:
2043
+ x_slice = _make_slice_divisible(x_slice, self.width,
2044
+ factor=shape_divisible_by)
2045
+ y_slice = _make_slice_divisible(y_slice, self.height,
2046
+ factor=shape_divisible_by)
2047
+
2048
+ return (check_slice_orientation(x_slice),
2049
+ check_slice_orientation(y_slice))
2050
+
2051
+ def crop_around(self, other_area):
2052
+ """Crop this area around `other_area`."""
2053
+ xslice, yslice = self.get_area_slices(other_area)
2054
+ return self[yslice, xslice]
2055
+
2056
+ def __getitem__(self, key):
2057
+ """Apply slices to the area_extent and size of the area."""
2058
+ yslice, xslice = key
2059
+ # Get actual values, replace Nones
2060
+ yindices = yslice.indices(self.height)
2061
+ total_rows = int((yindices[1] - yindices[0]) / yindices[2])
2062
+ ystopactual = yindices[1] - (yindices[1] - 1) % yindices[2]
2063
+ xindices = xslice.indices(self.width)
2064
+ total_cols = int((xindices[1] - xindices[0]) / xindices[2])
2065
+ xstopactual = xindices[1] - (xindices[1] - 1) % xindices[2]
2066
+ yslice = slice(yindices[0], ystopactual, yindices[2])
2067
+ xslice = slice(xindices[0], xstopactual, xindices[2])
2068
+
2069
+ new_area_extent = ((self.pixel_upper_left[0] + (xslice.start - 0.5) * self.pixel_size_x),
2070
+ (self.pixel_upper_left[1] - (yslice.stop - 0.5) * self.pixel_size_y),
2071
+ (self.pixel_upper_left[0] + (xslice.stop - 0.5) * self.pixel_size_x),
2072
+ (self.pixel_upper_left[1] - (yslice.start - 0.5) * self.pixel_size_y))
2073
+
2074
+ new_area = AreaDefinition(self.area_id, self.description,
2075
+ self.proj_id, self.proj_dict,
2076
+ total_cols,
2077
+ total_rows,
2078
+ new_area_extent)
2079
+ new_area.crop_offset = (self.crop_offset[0] + yslice.start,
2080
+ self.crop_offset[1] + xslice.start)
2081
+ return new_area
2082
+
2083
+ def geocentric_resolution(self, ellps='WGS84', radius=None):
2084
+ """Find best estimate for overall geocentric resolution.
2085
+
2086
+ This method is extremely important to the results of KDTree-based
2087
+ resamplers like the nearest neighbor resampling. This is used to
2088
+ determine how far the KDTree should be queried for valid pixels
2089
+ before giving up (`radius_of_influence`). This method attempts to
2090
+ make a best guess at what geocentric resolution (the units used by
2091
+ the KDTree) represents the majority of an area.
2092
+
2093
+ To do this this method will:
2094
+
2095
+ 1. Create a vertical mid-line and a horizontal mid-line.
2096
+ 2. Convert these coordinates to geocentric coordinates.
2097
+ 3. Compute the distance between points along these lines.
2098
+ 4. Take the histogram of each set of distances and find the
2099
+ bin with the most points.
2100
+ 5. Take the average of the edges of that bin.
2101
+ 6. Return the maximum of the vertical and horizontal bin
2102
+ edge averages.
2103
+
2104
+ """
2105
+ from pyproj import transform
2106
+ rows, cols = self.shape
2107
+ mid_row = rows // 2
2108
+ mid_col = cols // 2
2109
+ x, y = self.get_proj_vectors()
2110
+ mid_col_x = np.repeat(x[mid_col], y.size)
2111
+ mid_row_y = np.repeat(y[mid_row], x.size)
2112
+ src = Proj(getattr(self, 'crs', self.proj_dict))
2113
+ if radius:
2114
+ dst = Proj("+proj=cart +a={} +b={}".format(radius, radius))
2115
+ else:
2116
+ dst = Proj("+proj=cart +ellps={}".format(ellps))
2117
+ # need some altitude, go with the surface (0)
2118
+ alt_x = np.zeros(x.size)
2119
+ alt_y = np.zeros(y.size)
2120
+ # convert our midlines to (X, Y, Z) geocentric coordinates
2121
+ hor_xyz = np.stack(transform(src, dst, x, mid_row_y, alt_x), axis=1)
2122
+ vert_xyz = np.stack(transform(src, dst, mid_col_x, y, alt_y), axis=1)
2123
+ # Find the distance in meters along our midlines
2124
+ hor_dist = np.linalg.norm(np.diff(hor_xyz, axis=0), axis=1)
2125
+ vert_dist = np.linalg.norm(np.diff(vert_xyz, axis=0), axis=1)
2126
+ # Get rid of any NaNs or infinite values
2127
+ hor_dist = hor_dist[np.isfinite(hor_dist)]
2128
+ vert_dist = vert_dist[np.isfinite(vert_dist)]
2129
+ # use the average of the largest histogram bin to avoid
2130
+ # outliers and really large values.
2131
+ # Very useful near edge of disk geostationary areas.
2132
+ hor_res = vert_res = 0
2133
+ if hor_dist.size:
2134
+ hor_res = np.mean(np.histogram_bin_edges(hor_dist)[:2])
2135
+ if vert_dist.size:
2136
+ vert_res = np.mean(np.histogram_bin_edges(vert_dist)[:2])
2137
+ # Use the maximum distance between the two midlines instead of
2138
+ # binning both of them together. If we binned them together then
2139
+ # we are highly dependent on the shape of the area (more rows in
2140
+ # the area would almost always mean that we resulted in the vertical
2141
+ # midline's distance).
2142
+ res = max(hor_res, vert_res)
2143
+ if not res:
2144
+ raise RuntimeError("Could not calculate geocentric resolution")
2145
+ # return np.max(np.concatenate(vert_dist, hor_dist)) # alternative to histogram
2146
+ return res
2147
+
2148
+
2149
+ def _make_slice_divisible(sli, max_size, factor=2):
2150
+ """Make the given slice even in size."""
2151
+ rem = (sli.stop - sli.start) % factor
2152
+ if rem != 0:
2153
+ adj = factor - rem
2154
+ if sli.stop + 1 + rem < max_size:
2155
+ sli = slice(sli.start, sli.stop + adj)
2156
+ elif sli.start > 0:
2157
+ sli = slice(sli.start - adj, sli.stop)
2158
+ else:
2159
+ sli = slice(sli.start, sli.stop - rem)
2160
+
2161
+ return sli
2162
+
2163
+
2164
+ def get_geostationary_angle_extent(geos_area):
2165
+ """Get the max earth (vs space) viewing angles in x and y."""
2166
+ # get some projection parameters
2167
+ a, b = proj4_radius_parameters(geos_area.proj_dict)
2168
+ req = a / 1000.0
2169
+ rp = b / 1000.0
2170
+ h = geos_area.proj_dict['h'] / 1000.0 + req
2171
+
2172
+ # compute some constants
2173
+ aeq = 1 - req ** 2 / (h ** 2)
2174
+ ap_ = 1 - rp ** 2 / (h ** 2)
2175
+
2176
+ # generate points around the north hemisphere in satellite projection
2177
+ # make it a bit smaller so that we stay inside the valid area
2178
+ xmax = np.arccos(np.sqrt(aeq))
2179
+ ymax = np.arccos(np.sqrt(ap_))
2180
+ return xmax, ymax
2181
+
2182
+
2183
+ def get_geostationary_bounding_box(geos_area, nb_points=50):
2184
+ """Get the bbox in lon/lats of the valid pixels inside `geos_area`.
2185
+
2186
+ Args:
2187
+ nb_points: Number of points on the polygon
2188
+
2189
+ """
2190
+ xmax, ymax = get_geostationary_angle_extent(geos_area)
2191
+
2192
+ # generate points around the north hemisphere in satellite projection
2193
+ # make it a bit smaller so that we stay inside the valid area
2194
+ x = np.cos(np.linspace(-np.pi, 0, int(nb_points / 2.0))) * (xmax - 0.0001)
2195
+ y = -np.sin(np.linspace(-np.pi, 0, int(nb_points / 2.0))) * (ymax - 0.0001)
2196
+
2197
+ ll_x, ll_y, ur_x, ur_y = geos_area.area_extent
2198
+
2199
+ x *= geos_area.proj_dict['h']
2200
+ y *= geos_area.proj_dict['h']
2201
+
2202
+ x = np.clip(np.concatenate([x, x[::-1]]), min(ll_x, ur_x), max(ll_x, ur_x))
2203
+ y = np.clip(np.concatenate([y, -y]), min(ll_y, ur_y), max(ll_y, ur_y))
2204
+
2205
+ return Proj(**geos_area.proj_dict)(x, y, inverse=True)
2206
+
2207
+
2208
+ def combine_area_extents_vertical(area1, area2):
2209
+ """Combine the area extents of areas 1 and 2."""
2210
+ if (area1.area_extent[0] == area2.area_extent[0]
2211
+ and area1.area_extent[2] == area2.area_extent[2]):
2212
+ current_extent = list(area1.area_extent)
2213
+ if np.isclose(area1.area_extent[1], area2.area_extent[3]):
2214
+ current_extent[1] = area2.area_extent[1]
2215
+ elif np.isclose(area1.area_extent[3], area2.area_extent[1]):
2216
+ current_extent[3] = area2.area_extent[3]
2217
+ else:
2218
+ raise IncompatibleAreas(
2219
+ "Can't concatenate non-contiguous area definitions: "
2220
+ "{0} and {1}".format(area1, area2))
2221
+ else:
2222
+ raise IncompatibleAreas(
2223
+ "Can't concatenate area definitions with "
2224
+ "incompatible area extents: "
2225
+ "{0} and {1}".format(area1, area2))
2226
+ return current_extent
2227
+
2228
+
2229
+ def concatenate_area_defs(area1, area2, axis=0):
2230
+ """Append *area2* to *area1* and return the results."""
2231
+ different_items = (set(area1.proj_dict.items()) ^
2232
+ set(area2.proj_dict.items()))
2233
+ if axis == 0:
2234
+ same_size = area1.width == area2.width
2235
+ else:
2236
+ raise NotImplementedError('Only vertical contatenation is supported.')
2237
+ if different_items or not same_size:
2238
+ raise IncompatibleAreas("Can't concatenate area definitions with "
2239
+ "different projections: "
2240
+ "{0} and {1}".format(area1, area2))
2241
+
2242
+ if axis == 0:
2243
+ area_extent = combine_area_extents_vertical(area1, area2)
2244
+ x_size = int(area1.width)
2245
+ y_size = int(area1.height + area2.height)
2246
+ else:
2247
+ raise NotImplementedError('Only vertical contatenation is supported.')
2248
+ return AreaDefinition(area1.area_id, area1.description, area1.proj_id,
2249
+ area1.proj_dict, x_size, y_size,
2250
+ area_extent)
2251
+
2252
+
2253
+ class StackedAreaDefinition(BaseDefinition):
2254
+ """Definition based on muliple vertically stacked AreaDefinitions."""
2255
+
2256
+ def __init__(self, *definitions, **kwargs):
2257
+ """Initialize StackedAreaDefinition based on *definitions*.
2258
+
2259
+ *kwargs* used here are `nprocs` and `dtype` (see AreaDefinition).
2260
+
2261
+ """
2262
+ nprocs = kwargs.get('nprocs', 1)
2263
+ super(StackedAreaDefinition, self).__init__(nprocs=nprocs)
2264
+ self.dtype = kwargs.get('dtype', np.float64)
2265
+ self.defs = []
2266
+ self.proj_dict = {}
2267
+ for definition in definitions:
2268
+ self.append(definition)
2269
+
2270
+ @property
2271
+ def width(self):
2272
+ """Return width of the area definition."""
2273
+ return self.defs[0].width
2274
+
2275
+ @property
2276
+ def x_size(self):
2277
+ """Return width of the area definition."""
2278
+ warnings.warn("'x_size' is deprecated, use 'width' instead.", PendingDeprecationWarning)
2279
+ return self.width
2280
+
2281
+ @property
2282
+ def height(self):
2283
+ """Return height of the area definition."""
2284
+ return sum(definition.height for definition in self.defs)
2285
+
2286
+ @property
2287
+ def y_size(self):
2288
+ """Return height of the area definition."""
2289
+ warnings.warn("'y_size' is deprecated, use 'height' instead.", PendingDeprecationWarning)
2290
+ return self.height
2291
+
2292
+ @property
2293
+ def size(self):
2294
+ """Return size of the area definition."""
2295
+ return self.height * self.width
2296
+
2297
+ @property
2298
+ def shape(self):
2299
+ """Return shape of the area definition."""
2300
+ return (self.height, self.width)
2301
+
2302
+ def append(self, definition):
2303
+ """Append another definition to the area."""
2304
+ if isinstance(definition, StackedAreaDefinition):
2305
+ for area in definition.defs:
2306
+ self.append(area)
2307
+ return
2308
+ if definition.height == 0:
2309
+ return
2310
+ if not self.defs:
2311
+ self.proj_dict = definition.proj_dict
2312
+ elif self.proj_dict != definition.proj_dict:
2313
+ raise NotImplementedError('Cannot append areas:'
2314
+ ' Proj.4 dict mismatch')
2315
+ try:
2316
+ self.defs[-1] = concatenate_area_defs(self.defs[-1], definition)
2317
+ except (IncompatibleAreas, IndexError):
2318
+ self.defs.append(definition)
2319
+
2320
+ def get_lonlats(self, nprocs=None, data_slice=None, cache=False, dtype=None, chunks=None):
2321
+ """Return lon and lat arrays of the area."""
2322
+ if chunks is not None:
2323
+ from dask.array import vstack
2324
+ else:
2325
+ vstack = np.vstack
2326
+
2327
+ llons = []
2328
+ llats = []
2329
+ try:
2330
+ row_slice, col_slice = data_slice
2331
+ except TypeError:
2332
+ row_slice = slice(0, self.height)
2333
+ col_slice = slice(0, self.width)
2334
+ offset = 0
2335
+ for definition in self.defs:
2336
+ local_row_slice = slice(max(row_slice.start - offset, 0),
2337
+ min(max(row_slice.stop - offset, 0), definition.height),
2338
+ row_slice.step)
2339
+ lons, lats = definition.get_lonlats(nprocs=nprocs, data_slice=(local_row_slice, col_slice),
2340
+ cache=cache, dtype=dtype, chunks=chunks)
2341
+
2342
+ llons.append(lons)
2343
+ llats.append(lats)
2344
+ offset += lons.shape[0]
2345
+
2346
+ self.lons = vstack(llons)
2347
+ self.lats = vstack(llats)
2348
+
2349
+ return self.lons, self.lats
2350
+
2351
+ def get_lonlats_dask(self, chunks=None, dtype=None):
2352
+ """Return lon and lat dask arrays of the area."""
2353
+ warnings.warn("'get_lonlats_dask' is deprecated, please use "
2354
+ "'get_lonlats' with the 'chunks' keyword argument specified.",
2355
+ DeprecationWarning)
2356
+ if chunks is None:
2357
+ chunks = CHUNK_SIZE # FUTURE: Use a global config object instead
2358
+ return self.get_lonlats(chunks=chunks, dtype=dtype)
2359
+
2360
+ def squeeze(self):
2361
+ """Generate a single AreaDefinition if possible."""
2362
+ if len(self.defs) == 1:
2363
+ return self.defs[0]
2364
+ else:
2365
+ return self
2366
+
2367
+ @property
2368
+ def proj4_string(self):
2369
+ """Return projection definition as Proj.4 string."""
2370
+ warnings.warn("'proj4_string' is deprecated, please use 'proj_str' "
2371
+ "instead.", DeprecationWarning)
2372
+ return self.defs[0].proj_str
2373
+
2374
+ @property
2375
+ def proj_str(self):
2376
+ """Return projection definition as Proj.4 string."""
2377
+ return self.defs[0].proj_str
2378
+
2379
+ def update_hash(self, the_hash=None):
2380
+ """Update the hash."""
2381
+ for areadef in self.defs:
2382
+ the_hash = areadef.update_hash(the_hash)
2383
+ return the_hash
2384
+
2385
+
2386
+ def _get_slice(segments, shape):
2387
+ """Segment a 1D or 2D array."""
2388
+ if not (1 <= len(shape) <= 2):
2389
+ raise ValueError('Cannot segment array of shape: %s' % str(shape))
2390
+ else:
2391
+ size = shape[0]
2392
+ slice_length = int(np.ceil(float(size) / segments))
2393
+ start_idx = 0
2394
+ end_idx = slice_length
2395
+ while start_idx < size:
2396
+ if len(shape) == 1:
2397
+ yield slice(start_idx, end_idx)
2398
+ else:
2399
+ yield (slice(start_idx, end_idx), slice(None))
2400
+ start_idx = end_idx
2401
+ end_idx = min(start_idx + slice_length, size)
2402
+
2403
+
2404
+ def _flatten_cartesian_coords(cartesian_coords):
2405
+ """Flatten array to (n, 3) shape."""
2406
+ shape = cartesian_coords.shape
2407
+ if len(shape) > 2:
2408
+ cartesian_coords = cartesian_coords.reshape(shape[0] *
2409
+ shape[1], 3)
2410
+ return cartesian_coords
2411
+
2412
+
2413
+ def _get_highest_level_class(obj1, obj2):
2414
+ if (not issubclass(obj1.__class__, obj2.__class__) or
2415
+ not issubclass(obj2.__class__, obj1.__class__)):
2416
+ raise TypeError('No common superclass for %s and %s' %
2417
+ (obj1.__class__, obj2.__class__))
2418
+
2419
+ if obj1.__class__ == obj2.__class__:
2420
+ klass = obj1.__class__
2421
+ elif issubclass(obj1.__class__, obj2.__class__):
2422
+ klass = obj2.__class__
2423
+ else:
2424
+ klass = obj1.__class__
2425
+ return klass
2426
+
2427
+
2428
+ def ordered_dump(data, stream=None, Dumper=yaml.Dumper, **kwds):
2429
+ """Dump the data to YAML in ordered fashion."""
2430
+ class OrderedDumper(Dumper):
2431
+ pass
2432
+
2433
+ def _dict_representer(dumper, data):
2434
+ return dumper.represent_mapping(
2435
+ yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
2436
+ data.items(), flow_style=False)
2437
+
2438
+ OrderedDumper.add_representer(OrderedDict, _dict_representer)
2439
+ return yaml.dump(data, stream, OrderedDumper, **kwds)