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.
- shancx/3D/__init__.py +25 -0
- shancx/Algo/Class.py +11 -0
- shancx/Algo/CudaPrefetcher1.py +112 -0
- shancx/Algo/Fake_image.py +24 -0
- shancx/Algo/Hsml.py +391 -0
- shancx/Algo/L2Loss.py +10 -0
- shancx/Algo/MetricTracker.py +132 -0
- shancx/Algo/Normalize.py +66 -0
- shancx/Algo/OptimizerWScheduler.py +38 -0
- shancx/Algo/Rmageresize.py +79 -0
- shancx/Algo/Savemodel.py +33 -0
- shancx/Algo/SmoothL1_losses.py +27 -0
- shancx/Algo/Tqdm.py +62 -0
- shancx/Algo/__init__.py +121 -0
- shancx/Algo/checknan.py +28 -0
- shancx/Algo/iouJU.py +83 -0
- shancx/Algo/mask.py +25 -0
- shancx/Algo/psnr.py +9 -0
- shancx/Algo/ssim.py +70 -0
- shancx/Algo/structural_similarity.py +308 -0
- shancx/Algo/tool.py +704 -0
- shancx/Calmetrics/__init__.py +97 -0
- shancx/Calmetrics/calmetrics.py +14 -0
- shancx/Calmetrics/calmetricsmatrixLib.py +147 -0
- shancx/Calmetrics/rmseR2score.py +35 -0
- shancx/Clip/__init__.py +50 -0
- shancx/Cmd.py +126 -0
- shancx/Config_.py +26 -0
- shancx/Df/DataFrame.py +11 -2
- shancx/Df/__init__.py +17 -0
- shancx/Df/tool.py +0 -0
- shancx/Diffm/Psamples.py +18 -0
- shancx/Diffm/__init__.py +0 -0
- shancx/Diffm/test.py +207 -0
- shancx/Doc/__init__.py +214 -0
- shancx/E/__init__.py +178 -152
- shancx/Fillmiss/__init__.py +0 -0
- shancx/Fillmiss/imgidwJU.py +46 -0
- shancx/Fillmiss/imgidwLatLonJU.py +82 -0
- shancx/Gpu/__init__.py +55 -0
- shancx/H9/__init__.py +126 -0
- shancx/H9/ahi_read_hsd.py +877 -0
- shancx/H9/ahisearchtable.py +298 -0
- shancx/H9/geometry.py +2439 -0
- shancx/Hug/__init__.py +81 -0
- shancx/Inst.py +22 -0
- shancx/Lib.py +31 -0
- shancx/Mos/__init__.py +37 -0
- shancx/NN/__init__.py +235 -106
- shancx/Path1.py +161 -0
- shancx/Plot/GlobMap.py +276 -116
- shancx/Plot/__init__.py +491 -1
- shancx/Plot/draw_day_CR_PNG.py +4 -21
- shancx/Plot/exam.py +116 -0
- shancx/Plot/plotGlobal.py +325 -0
- shancx/{radar_nmc.py → Plot/radarNmc.py} +4 -34
- shancx/{subplots_single_china_map.py → Plot/single_china_map.py} +1 -1
- shancx/Point.py +46 -0
- shancx/QC.py +223 -0
- shancx/RdPzl/__init__.py +32 -0
- shancx/Read.py +72 -0
- shancx/Resize.py +79 -0
- shancx/SN/__init__.py +62 -123
- shancx/Time/GetTime.py +9 -3
- shancx/Time/__init__.py +66 -1
- shancx/Time/timeCycle.py +302 -0
- shancx/Time/tool.py +0 -0
- shancx/Train/__init__.py +74 -0
- shancx/Train/makelist.py +187 -0
- shancx/Train/multiGpu.py +27 -0
- shancx/Train/prepare.py +161 -0
- shancx/Train/renet50.py +157 -0
- shancx/ZR.py +12 -0
- shancx/__init__.py +333 -262
- shancx/args.py +27 -0
- shancx/bak.py +768 -0
- shancx/df2database.py +62 -2
- shancx/geosProj.py +80 -0
- shancx/info.py +38 -0
- shancx/netdfJU.py +231 -0
- shancx/sendM.py +59 -0
- shancx/tensBoard/__init__.py +28 -0
- shancx/wait.py +246 -0
- {shancx-1.8.92.dist-info → shancx-1.9.33.218.dist-info}/METADATA +15 -5
- shancx-1.9.33.218.dist-info/RECORD +91 -0
- {shancx-1.8.92.dist-info → shancx-1.9.33.218.dist-info}/WHEEL +1 -1
- my_timer_decorator/__init__.py +0 -10
- shancx/Dsalgor/__init__.py +0 -19
- shancx/E/DFGRRIB.py +0 -30
- shancx/EN/DFGRRIB.py +0 -30
- shancx/EN/__init__.py +0 -148
- shancx/FileRead.py +0 -44
- shancx/Gray2RGB.py +0 -86
- shancx/M/__init__.py +0 -137
- shancx/MN/__init__.py +0 -133
- shancx/N/__init__.py +0 -131
- shancx/Plot/draw_day_CR_PNGUS.py +0 -206
- shancx/Plot/draw_day_CR_SVG.py +0 -275
- shancx/Plot/draw_day_pre_PNGUS.py +0 -205
- shancx/Plot/glob_nation_map.py +0 -116
- shancx/Plot/radar_nmc.py +0 -61
- shancx/Plot/radar_nmc_china_map_compare1.py +0 -50
- shancx/Plot/radar_nmc_china_map_f.py +0 -121
- shancx/Plot/radar_nmc_us_map_f.py +0 -128
- shancx/Plot/subplots_compare_devlop.py +0 -36
- shancx/Plot/subplots_single_china_map.py +0 -45
- shancx/S/__init__.py +0 -138
- shancx/W/__init__.py +0 -132
- shancx/WN/__init__.py +0 -132
- shancx/code.py +0 -331
- shancx/draw_day_CR_PNG.py +0 -200
- shancx/draw_day_CR_PNGUS.py +0 -206
- shancx/draw_day_CR_SVG.py +0 -275
- shancx/draw_day_pre_PNGUS.py +0 -205
- shancx/makenetCDFN.py +0 -42
- shancx/mkIMGSCX.py +0 -92
- shancx/netCDF.py +0 -130
- shancx/radar_nmc_china_map_compare1.py +0 -50
- shancx/radar_nmc_china_map_f.py +0 -125
- shancx/radar_nmc_us_map_f.py +0 -67
- shancx/subplots_compare_devlop.py +0 -36
- shancx/tool.py +0 -18
- shancx/user/H8mess.py +0 -317
- shancx/user/__init__.py +0 -137
- shancx/user/cinradHJN.py +0 -496
- shancx/user/examMeso.py +0 -293
- shancx/user/hjnDAAS.py +0 -26
- shancx/user/hjnFTP.py +0 -81
- shancx/user/hjnGIS.py +0 -320
- shancx/user/hjnGPU.py +0 -21
- shancx/user/hjnIDW.py +0 -68
- shancx/user/hjnKDTree.py +0 -75
- shancx/user/hjnLAPSTransform.py +0 -47
- shancx/user/hjnMiscellaneous.py +0 -182
- shancx/user/hjnProj.py +0 -162
- shancx/user/inotify.py +0 -41
- shancx/user/matplotlibMess.py +0 -87
- shancx/user/mkNCHJN.py +0 -623
- shancx/user/newTypeRadar.py +0 -492
- shancx/user/test.py +0 -6
- shancx/user/tlogP.py +0 -129
- shancx/util_log.py +0 -33
- shancx/wtx/H8mess.py +0 -315
- shancx/wtx/__init__.py +0 -151
- shancx/wtx/cinradHJN.py +0 -496
- shancx/wtx/colormap.py +0 -64
- shancx/wtx/examMeso.py +0 -298
- shancx/wtx/hjnDAAS.py +0 -26
- shancx/wtx/hjnFTP.py +0 -81
- shancx/wtx/hjnGIS.py +0 -330
- shancx/wtx/hjnGPU.py +0 -21
- shancx/wtx/hjnIDW.py +0 -68
- shancx/wtx/hjnKDTree.py +0 -75
- shancx/wtx/hjnLAPSTransform.py +0 -47
- shancx/wtx/hjnLog.py +0 -78
- shancx/wtx/hjnMiscellaneous.py +0 -201
- shancx/wtx/hjnProj.py +0 -161
- shancx/wtx/inotify.py +0 -41
- shancx/wtx/matplotlibMess.py +0 -87
- shancx/wtx/mkNCHJN.py +0 -613
- shancx/wtx/newTypeRadar.py +0 -492
- shancx/wtx/test.py +0 -6
- shancx/wtx/tlogP.py +0 -129
- shancx-1.8.92.dist-info/RECORD +0 -99
- /shancx/{Dsalgor → Algo}/dsalgor.py +0 -0
- {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)
|