GRSdriver 1.0.0__tar.gz

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.
@@ -0,0 +1,9 @@
1
+ '''
2
+ v1.0.0:
3
+ first commit from S2driver package v1.0.6
4
+ '''
5
+
6
+ __version__='1.0.0'
7
+
8
+ from .driver_landsat_col2 import LandsatDriver
9
+ from .driver_S2_SAFE import Sentinel2Driver
@@ -0,0 +1,518 @@
1
+ import glob
2
+ import os
3
+ import xml.etree.ElementTree as ET
4
+
5
+ import numpy as np
6
+ from numba import jit
7
+
8
+ import pandas as pd
9
+ import geopandas as gpd
10
+ import xarray as xr
11
+ import xmltodict
12
+
13
+ from rasterio.features import rasterize
14
+ import scipy.odr as odr
15
+ from affine import Affine
16
+ from osgeo import gdal, ogr
17
+ #import cartopy.crs as ccrs
18
+ from pyproj import CRS
19
+ import eoreader as eo
20
+
21
+ from eoreader.reader import Reader
22
+ from eoreader.env_vars import USE_DASK
23
+
24
+ # EOReader uses dask if == 1
25
+ os.environ[USE_DASK] = "0"
26
+
27
+ opj = os.path.join
28
+ BAND_NAMES = np.array(['B01', 'B02', 'B03', 'B04', 'B05', 'B06', 'B07', 'B08', 'B8A', 'B09', 'B10', 'B11', 'B12'])
29
+ BAND_NAMES_EOREADER = np.array(['CA', 'BLUE', 'GREEN', 'RED', 'VRE_1',
30
+ 'VRE_2', 'VRE_3', 'NIR', 'NARROW_NIR',
31
+ 'WV', 'SWIR_CIRRUS', 'SWIR_1', 'SWIR_2'])
32
+
33
+ BAND_ID = [b.replace('B', '') for b in BAND_NAMES]
34
+ NATIVE_RESOLUTION = [60, 10, 10, 10, 20, 20, 20, 10, 20, 60, 60, 20, 20]
35
+ WAVELENGTH = np.array([443, 490, 560, 665, 705, 740, 783, 842, 865, 945, 1375, 1610, 2190])
36
+ BAND_WIDTH = [20, 65, 35, 30, 15, 15, 20, 115, 20, 20, 30, 90, 180]
37
+
38
+ INFO = pd.DataFrame({'bandId': range(13),
39
+ 'ESA': BAND_NAMES,
40
+ 'EOREADER': BAND_NAMES_EOREADER,
41
+ 'Wavelength (nm)': WAVELENGTH,
42
+ 'Band width (nm)': BAND_WIDTH,
43
+ 'Resolution (m)': NATIVE_RESOLUTION}).set_index('bandId').T
44
+
45
+
46
+ class Sentinel2Driver():
47
+ def __init__(self, imageSAFE,
48
+ band_idx=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
49
+ band_tbp_idx=[0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12],
50
+ subset=None,
51
+ resolution=20,
52
+ resolution_angle=60,
53
+ verbose=False,
54
+ **kwargs):
55
+
56
+ abspath = os.path.abspath(imageSAFE)
57
+ dirroot, basename = os.path.split(abspath)
58
+ self.verbose = verbose
59
+ self.band_idx = band_idx
60
+ self.band_tbp_idx = band_tbp_idx
61
+ self.subset = subset
62
+ self.resolution_angle = resolution_angle
63
+ self.resolution = resolution
64
+ self.INFO = INFO[band_idx]
65
+
66
+ # -----------------------------------------------------
67
+ # define prod and geom where data will be loaded
68
+ # -----------------------------------------------------
69
+ self.prod = xr.Dataset()
70
+ self.geom = xr.Dataset()
71
+
72
+ # --------------------------------
73
+ # define interpolation parameters
74
+ # --------------------------------
75
+ # tile for 10m resolution: width,height = 10980,10980
76
+ # tile for 20m resolution: width,height = 5490,5490
77
+ # tile for 60m resolution: width,height = 1830,1830
78
+ if resolution_angle == 10:
79
+ self.width, self.height = 10980, 10980
80
+ elif resolution_angle == 20:
81
+ self.width, self.height = 5490, 5490
82
+ elif resolution_angle == 60:
83
+ self.width, self.height = 1830, 1830
84
+ try:
85
+ self.xml_granule = glob.glob(opj(imageSAFE, 'GRANULE', '*', 'MTD_TL.xml'))[0]
86
+ self.xml_file = glob.glob(opj(imageSAFE, 'MTD*.xml'))[0]
87
+ except:
88
+ print('old or deprecated SAFE format detected')
89
+ self.xml_granule = glob.glob(opj(imageSAFE, 'GRANULE', '*', '*MTD*.xml'))[0]
90
+ self.xml_file = glob.glob(opj(imageSAFE, '*MTD*.xml'))[0]
91
+
92
+ # get metadata and angle file
93
+ ds = gdal.Open(self.xml_file)
94
+ self.metadata = ds.GetMetadata()
95
+ self.metadata2 = xmltodict.parse(ds.GetMetadata('xml:SENTINEL2')[0])
96
+ __ = []
97
+ for _ in self.metadata2['n1:Level-1C_User_Product']['n1:General_Info']['Product_Image_Characteristics'][
98
+ 'Reflectance_Conversion']['Solar_Irradiance_List']['SOLAR_IRRADIANCE']:
99
+ __.append([int(_['@bandId']), float(_['#text'])])
100
+ self.solar_irradiance = np.array(__)
101
+
102
+ # Spectral Response Functions
103
+ SRFs = []
104
+ wl_hr = np.arange(400, 2350)
105
+ for _ in self.metadata2['n1:Level-1C_User_Product'][
106
+ 'n1:General_Info']['Product_Image_Characteristics']['Spectral_Information_List']['Spectral_Information']:
107
+
108
+ bandID = int(_['@bandId'])
109
+ if not self.band_idx.__contains__(bandID):
110
+ continue
111
+
112
+ wl_min, wl_max = float(_['Wavelength']['MIN']['#text']), float(_['Wavelength']['MAX']['#text'])
113
+ step = float(_['Spectral_Response']['STEP']['#text'])
114
+ wl = np.arange(wl_min, wl_max + step, step)
115
+ SRF = np.asarray(_['Spectral_Response']['VALUES'].split(), dtype=np.float32)
116
+ SRFs.append(
117
+ xr.DataArray(SRF, coords=dict(wl_hr=wl[:len(SRF)]), name='SRF').interp(wl_hr=wl_hr).assign_coords(
118
+ dict(wl=WAVELENGTH[bandID])))
119
+ self.SRFs = xr.concat(SRFs, dim='wl')
120
+ self.SRFs.attrs['description'] = 'Spectral response function of each band'
121
+
122
+ # Open instance of eoreader
123
+ reader = Reader()
124
+
125
+ # Open the product
126
+ reader = reader.open(imageSAFE, remove_tmp=True, **kwargs)
127
+ self.reader = reader
128
+ self.processing_baseline = reader._processing_baseline
129
+ self.datetime = reader.datetime
130
+
131
+ # save geographic data
132
+ self.extent = reader.extent()
133
+ self.bounds = self.extent.bounds
134
+ minx, miny, maxx, maxy = self.bounds.values[0]
135
+ self.crs = self.reader.crs()
136
+ self.epsg = self.extent.crs.to_epsg()
137
+ # str_epsg = str(self.epsg)
138
+ # zone = str_epsg[-2:]
139
+ # is_south = str_epsg[2] == 7
140
+ # self.proj = ccrs.UTM(zone, is_south)
141
+ # self.proj = CRS.from_dict({'proj': 'utm', 'zone': zone, 'south': is_south})
142
+ self.transform = Affine(resolution, 0., minx, 0., -resolution, maxy)
143
+
144
+ # -------------------------
145
+ # interpolation
146
+ # -------------------------
147
+ # set the indexing of numpy and xarray
148
+ self.indexing = 'xy'
149
+ self.new_x = np.linspace(minx, maxx, self.width)
150
+ self.new_y = np.linspace(miny, maxy, self.height)[::-1]
151
+
152
+ # ---------------------------------
153
+ # getting appropriate version of mask driver
154
+ # ---------------------------------
155
+ # TODO generalize to load all available masks
156
+ if self.processing_baseline < 4:
157
+ self._open_mask = reader._open_mask_lt_4_0
158
+ else:
159
+ self._open_mask = reader._open_mask_gt_4_0
160
+
161
+ def load_product(self,
162
+ add_time=True,
163
+ **kwargs):
164
+
165
+ self.load_bands(subset=self.subset, add_time=add_time, **kwargs)
166
+
167
+ self.load_geom()
168
+ #if self.resolution_angle != self.resolution:
169
+ self.geom=self.geom.interp(x=self.prod.x, y=self.prod.y, method='nearest')
170
+ self.prod = xr.merge([self.prod, self.geom])
171
+ del self.geom
172
+
173
+ # add native metadata
174
+ for item in self.metadata:
175
+ self.prod.attrs[item] = self.metadata[item]
176
+
177
+ self.prod.attrs['satellite'] = self.prod.attrs['PRODUCT_URI'].split('_')[0]
178
+ self.prod.attrs['tile'] = self.prod.attrs['PRODUCT_URI'].split('_')[5][1:]
179
+ self.prod.attrs['solar_irradiance'] = self.solar_irradiance[:, 1]
180
+ self.prod.attrs['solar_irradiance_unit'] = 'W/m²/µm'
181
+ self.prod.attrs['mean_solar_azimuth'] = self.mean_sun_azi
182
+ self.prod.attrs['mean_solar_zenith_angle'] = self.mean_sza
183
+ self.prod.attrs['acquisition_date'] = self.prod.attrs['DATATAKE_1_DATATAKE_SENSING_START']
184
+
185
+ def load_bands(self, subset=None,
186
+ add_time=True,
187
+ **kwargs):
188
+
189
+ # ----------------------------------
190
+ # getting bands
191
+ # ----------------------------------
192
+ window=None
193
+ if subset is not None:
194
+
195
+ if subset.dtype == 'geometry':
196
+ window = subset.to_crs(self.crs).bounds.values[0]
197
+ else:
198
+ window = subset[0]
199
+ bands = self.reader.stack(list(BAND_NAMES_EOREADER[self.band_idx]),
200
+ resolution=self.resolution,
201
+ window=window,
202
+ **kwargs)
203
+
204
+ # fix for naming in differnt EOreader versions
205
+ if 'z' in bands.coords:
206
+ bands = bands.rename({'z': 'bands'})
207
+
208
+ # ----------------------------------
209
+ # setting up coordinates and dimensions
210
+ # ----------------------------------
211
+ self.prod = bands.assign_coords(wl=('bands', self.INFO.loc['Wavelength (nm)'])). \
212
+ swap_dims({'bands': 'wl'}).drop({'band', 'bands', 'variable'})
213
+ self.prod = self.prod.assign_coords(bandID=('wl', self.INFO.loc['ESA'].values))
214
+ self.prod = self.prod.to_dataset(name='bands', promote_attrs=True)
215
+ self.prod.attrs['wl_to_process'] = WAVELENGTH[self.band_tbp_idx]
216
+
217
+ # add spectral response function
218
+ self.prod = xr.merge([self.prod, self.SRFs.sel(wl=self.prod.wl.values)]).drop_vars('bandID')
219
+
220
+ # compute central wavelengths
221
+ wl_true = []
222
+ for wl_, srf in self.prod.SRF.groupby('wl', squeeze=False):
223
+ srf = srf.dropna('wl_hr')
224
+ wl_true.append((srf.wl_hr * srf).integrate('wl_hr') / srf.integrate('wl_hr'))
225
+ wl_true = xr.concat(wl_true, dim='wl')
226
+ wl_true.name = 'wl_true'
227
+ self.prod = xr.merge([self.prod, wl_true])
228
+
229
+ # add time
230
+ if add_time:
231
+ self.prod = self.prod.assign_coords(time=self.datetime) #.expand_dims('time')
232
+ # self.prod.clear()
233
+
234
+ @staticmethod
235
+ def parse_angular_grid_node(node):
236
+ '''
237
+ Internal parsing function for angular grids
238
+ :return:
239
+ '''
240
+ values = []
241
+ for c in node.find('Values_List'):
242
+ values.append(np.array([float(t) for t in c.text.split()]))
243
+ values_array = np.stack(values)
244
+ return values_array
245
+
246
+ @staticmethod
247
+ def set_crs(arr, crs):
248
+ arr.rio.set_crs(crs, inplace=True)
249
+ arr.rio.write_crs(inplace=True)
250
+
251
+ def get_raw_angles(self):
252
+
253
+ minx, miny, maxx, maxy = self.bounds.values[0]
254
+ with open(self.xml_granule) as xml_file:
255
+ tree = ET.parse(xml_file)
256
+ root = tree.getroot()
257
+
258
+ raw_sza = self.parse_angular_grid_node(root.find('.//Tile_Angles/Sun_Angles_Grid/Zenith'))
259
+ raw_sazi = self.parse_angular_grid_node(root.find('.//Tile_Angles/Sun_Angles_Grid/Azimuth'))
260
+ self.mean_sza= np.nanmean(raw_sza)
261
+ self.mean_sun_azi = np.nanmean(raw_sazi)
262
+
263
+ # compute x and y for angle grids
264
+ # check dimension
265
+ Nx, Ny = raw_sza.shape
266
+
267
+ xang = np.linspace(minx, maxx, Nx)
268
+ yang = np.linspace(miny, maxy, Ny)[::-1]
269
+
270
+ raw_sun_ang = xr.Dataset(data_vars=dict(sza=(['y', 'x'], raw_sza),
271
+ sazi=(['y', 'x'], raw_sazi)),
272
+ coords=dict(x=xang, y=yang))
273
+ self.set_crs(raw_sun_ang, self.crs)
274
+ self.raw_sun_ang = raw_sun_ang
275
+
276
+ # ---------------------------------
277
+ # getting viewing geometry datacube
278
+ # ---------------------------------
279
+ bandIds, detectorIds = [], []
280
+ for angleID in root.findall('.//Tile_Angles/Viewing_Incidence_Angles_Grids'):
281
+ bandIds.append(int(angleID.attrib['bandId']))
282
+ detectorIds.append(int(angleID.attrib['detectorId']))
283
+ Nband, Ndetector = np.max(bandIds) + 1, np.max(detectorIds) + 1
284
+
285
+ # allocate/fill rasters
286
+ vza = np.full((Nband, Ndetector, Nx, Ny), np.nan, dtype=float)
287
+ vazi = np.full((Nband, Ndetector, Nx, Ny), np.nan, dtype=float)
288
+
289
+ for angleID in root.findall('.//Tile_Angles/Viewing_Incidence_Angles_Grids'):
290
+ iband = int(angleID.attrib['bandId'])
291
+ idetector = int(angleID.attrib['detectorId'])
292
+ vza[iband, idetector] = self.parse_angular_grid_node(angleID.find('Zenith'))
293
+ vazi[iband, idetector] = self.parse_angular_grid_node(angleID.find('Azimuth'))
294
+
295
+ raw_view_ang = xr.Dataset(data_vars=dict(vza=(['bandId', 'detectorId', 'y', 'x'], vza),
296
+ vazi=(['bandId', 'detectorId', 'y', 'x'], vazi)),
297
+ coords=dict(bandId=range(Nband),
298
+ detectorId=range(Ndetector),
299
+ x=xang, y=yang))
300
+ self.set_crs(raw_view_ang, self.crs)
301
+
302
+ # clean up Dataset (remove empty slices)
303
+ raw_view_ang = raw_view_ang.dropna('detectorId', how='all')
304
+ self.raw_view_ang = raw_view_ang
305
+
306
+ # set number of different detectors
307
+ self.detector_num = len(raw_view_ang.detectorId)
308
+
309
+ return
310
+
311
+ def load_geom(self,
312
+ method='linear'):
313
+
314
+ self.get_raw_angles()
315
+ self.get_all_band_angles( method=method)
316
+
317
+ @staticmethod
318
+ def linfit(beta, x):
319
+ return beta[0] * x[0] + beta[1] * x[1] + beta[2]
320
+
321
+ @staticmethod
322
+ @jit(nopython=True) # "uint16[:,:](float64[:],float64[:],float64[:,:],float64[:,:],intp,intp)",
323
+ def lin2D(arr, x, y, mask, betas, detector_offset=0, scale_factor=100):
324
+
325
+ Nx, Ny = mask.shape
326
+
327
+ for ii in range(Nx):
328
+ for jj in range(Ny):
329
+ detect = mask[ii, jj]
330
+ if detect >= detector_offset:
331
+ #continue
332
+ beta = betas[detect - detector_offset]
333
+ val = beta[0] * x[jj] + beta[1] * y[ii] + beta[2]
334
+ # compression using simple int8 and scale factor
335
+ arr[ii, jj] = (val * scale_factor)
336
+
337
+ def data_fitting(self, x0, y0, arr):
338
+ # ---------------------------------
339
+ # ODR multilinear regression
340
+ # ---------------------------------
341
+ xgrid, ygrid = np.meshgrid(x0, y0, indexing=self.indexing)
342
+
343
+ # vectorize
344
+ values = arr.values.flatten()
345
+ x_ = xgrid.flatten()
346
+ y_ = ygrid.flatten()
347
+
348
+ # remove NaN
349
+ idx = ~np.isnan(values)
350
+ values = values[idx]
351
+ points = np.empty((2, len(values)))
352
+ points[0] = x_[idx]
353
+ points[1] = y_[idx]
354
+
355
+ # set ODR fitting
356
+ mean = np.nanmean(values)
357
+ linear = odr.Model(self.linfit)
358
+ data = odr.Data(points, values)
359
+ beta0 = [0, 0, mean]
360
+
361
+ # proceed with ODR fitting
362
+ fit = odr.ODR(data, linear, beta0=beta0)
363
+ resfit = fit.run()
364
+
365
+ if self.verbose:
366
+ resfit.pprint()
367
+
368
+ return resfit.beta
369
+
370
+ def get_detector_mask(self, bandId=0,
371
+ resolution=20,
372
+ detector_mask_name='DETFOO'):
373
+
374
+ if self.processing_baseline < 4:
375
+ mask_df = self._open_mask(detector_mask_name, BAND_ID[bandId])
376
+ detector_num = mask_df.gml_id.str.split('-', expand=True).values[:, 2]
377
+ poly_shp = [[geom, int(value)] for geom, value in zip(mask_df.geometry, detector_num)]
378
+
379
+ mask = rasterize(shapes=poly_shp,
380
+ out_shape=(self.height, self.width),
381
+ transform=self.transform)
382
+ else:
383
+ # TODO deprecate 'resolution' argument, 'pixel_size' is used instead since EOReader 0.20
384
+ mask = self._open_mask(detector_mask_name, BAND_ID[bandId], resolution=resolution,
385
+ pixel_size=resolution).astype(np.int8)
386
+
387
+ mask = mask.squeeze()
388
+ return np.array(mask)
389
+
390
+ def get_band_angle_as_numpy(self, xarr,
391
+ bandId=0,
392
+ resolution=20,
393
+ detector_mask_name='DETFOO',
394
+ compress=False,
395
+ ):
396
+ '''
397
+
398
+ :param xarr:
399
+ :param bandId:
400
+ :param resolution:
401
+ :param detector_mask_name:
402
+
403
+ :return:
404
+ '''
405
+
406
+ detector_offset = int(xarr.detectorId.min())
407
+ mask = self.get_detector_mask(bandId=bandId, resolution=resolution)
408
+
409
+ # TODO check how to avoid taking the nodata value "0" when coarsening the raster
410
+ # TODO for the moment this induces bad detector number at the edge of the image swath
411
+ # mask = _open_mask(detector_mask_name, BAND_ID[bandId], resolution=NATIVE_RESOLUTION[bandId])
412
+ # # mask nodata value
413
+ # mask = mask.where(mask!=0)
414
+ # # resample mask at the desired resolution
415
+ # if resolution != NATIVE_RESOLUTION[iband]:
416
+ # mask = mask.interp(x=new_x, y=new_y, method='nearest')
417
+ # # compress mask into int8
418
+ # mask = mask.astype(np.int8)
419
+
420
+ x, y = self.new_x, self.new_y
421
+ # self.prod.clear()
422
+ betas = np.full((self.detector_num, 3), np.nan)
423
+ xarr_ = xarr.sel(bandId=bandId)
424
+ for id in range(self.detector_num):
425
+ # --------------------------------------------------------------
426
+ # Linear 2D-fitting to get the function of the regression plane
427
+ # --------------------------------------------------------------
428
+ arr = xarr_.isel(detectorId=id).dropna('y', how='all').dropna('x', how='all')
429
+ x0, y0 = arr.x.values, arr.y.values
430
+ betas[id, :] = self.data_fitting(x0, y0, arr)
431
+
432
+ if compress:
433
+ # TODO experimental, needs to be tested
434
+ # compression in uint16 (NB: range 0-65535)
435
+ new_arr = np.full((self.width, self.height), np.nan, dtype=np.int16)
436
+ # compute angles from betas values for each detector and band
437
+ self.lin2D(new_arr, x, y, mask, betas, detector_offset=detector_offset, scale_factor=100)
438
+ else:
439
+ # compression in uint16 (NB: range 0-65535)
440
+ new_arr = np.full((self.width, self.height), np.nan, dtype=np.float32)
441
+ # compute angles from betas values for each detector and band
442
+ self.lin2D(new_arr, x, y, mask, betas, detector_offset=detector_offset, scale_factor=1)
443
+
444
+ del mask
445
+ return new_arr
446
+
447
+ @staticmethod
448
+ def scat_angle(sza, vza, azi):
449
+ '''
450
+ self.azi: azimuth in rad for convention azi=180 when sun-sensor in opposition
451
+ :return: scattering angle in deg
452
+ '''
453
+
454
+ sza = np.radians(sza)
455
+ vza = np.radians(vza)
456
+ azi = np.radians(azi)
457
+ ang = -np.cos(sza) * np.cos(vza) - np.sin(sza) * np.sin(vza) * np.cos(azi)
458
+ ang = np.arccos(ang)
459
+ return np.degrees(ang)
460
+
461
+ def get_all_band_angles(self, method='linear'):
462
+
463
+ new_x, new_y = self.new_x, self.new_y
464
+ band_idx = self.band_idx
465
+ # -----------------------------------------------------------------
466
+ # Sun angles (easy!) based on standard bidimensional interpolation
467
+ # -----------------------------------------------------------------
468
+ new_sun_ang = self.raw_sun_ang.interp(x=new_x, y=new_y, method=method)
469
+
470
+ # ------------------------------------------------------
471
+ # Viewing angles (not easy!) based on 2D-plane fitting
472
+ # ------------------------------------------------------
473
+ raw_vza = self.raw_view_ang.vza
474
+ raw_vazi = self.raw_view_ang.vazi
475
+
476
+ # ---------------------------------------------------------
477
+ # convert vza, azi angles into cartesian vector components
478
+ # (NOT NEEDED FOR THE MOMENT!!)
479
+ # ---------------------------------------------------------
480
+ # np.tan(np.deg2rad(view_ang.vza)) * np.sin(np.deg2rad(view_ang.vazi))
481
+ # np.tan(np.deg2rad(view_ang.vza)) * np.cos(np.deg2rad(view_ang.vazi))
482
+ # dx.name = 'dx'
483
+ # dy.name = 'dy'
484
+
485
+ new_vza, new_vazi = [], []
486
+ for ibandId, bandId in enumerate(band_idx):
487
+ if self.verbose:
488
+ print('Band number ' + str(bandId) + ' is being loaded')
489
+ new_vza.append(self.get_band_angle_as_numpy(raw_vza, bandId=bandId, resolution=self.resolution_angle))
490
+ new_vazi.append(self.get_band_angle_as_numpy(raw_vazi, bandId=bandId, resolution=self.resolution_angle))
491
+ raa = (np.array(new_vazi) - new_sun_ang.sazi.values) % 360
492
+
493
+ self.geom['vza'] = xr.DataArray(np.array(new_vza), dims=['wl', 'y', 'x'])
494
+ self.geom['raa'] = xr.DataArray(raa, dims=['wl', 'y', 'x'])
495
+ self.geom['sza'] = xr.DataArray(new_sun_ang.sza.values, dims=['y', 'x'])
496
+ #
497
+ # xr.Dataset(data_vars=dict(vza=(['wl', 'y', 'x'], )),
498
+ # coords=dict(wl=self.INFO.loc['Wavelength (nm)'],
499
+ #
500
+ # vazi = xr.Dataset(data_vars=dict(vazi=(['wl', 'y', 'x'], np.array(new_vazi))),
501
+ # coords=dict(wl=self.INFO.loc['Wavelength (nm)'], x=new_x, y=new_y))
502
+ # new_ang = xr.Dataset(data_vars=dict(vza=(['wl', 'y', 'x'], np.array(new_vza))),
503
+ # coords=dict(wl=self.INFO.loc['Wavelength (nm)'], x=new_x, y=new_y))
504
+ #
505
+ # new_ang['sza'] = new_sun_ang.sza
506
+ # new_ang['razi'] = vazi.vazi - new_sun_ang.sazi) % 360
507
+ # # new_ang['scat_ang'] = scat_angle(new_ang.sza, new_ang.vza, new_ang.razi)
508
+ self.geom['x']=self.new_x
509
+ self.geom['y']=self.new_y
510
+ self.set_crs(self.geom, self.crs)
511
+
512
+ del new_vza, new_vazi, raa, new_sun_ang
513
+
514
+ return
515
+
516
+ # ------------------------------------------------------------------------------------------
517
+ # ------------------------------------------------------------------------------------------
518
+ # ------------------------------------------------------------------------------------------