Skip to content

Commit 9bad704

Browse files
eamonfordEamon Ford
andauthored
SDAP-227 Support i,j indexed lat/lon (#12)
Co-authored-by: Eamon Ford <eamon.d.ford@jpl.nasa.gov>
1 parent b26a22c commit 9bad704

6 files changed

Lines changed: 72 additions & 38 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ pip-selfcheck.json
120120
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
121121

122122
# User-specific stuff:
123+
124+
.vscode
125+
123126
.idea/**/workspace.xml
124127
.idea/**/tasks.xml
125128
.idea/dictionaries

conda-requirements.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
scipy=0.18.1
2-
nco=4.7.1
3-
netcdf4=1.3.1
1+
scipy=1.3.2
2+
nco=4.9.1
3+
netcdf4=1.5.3
4+
xarray=0.15.0

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ werkzeug==0.14.1
22
flask==1.0.2
33
flask-accept==0.0.6
44
nexusproto===1.0.0
5-
numpy==1.12.1
5+
numpy
66
protobuf==3.2.0
77
pytz==2017.2
88
PyYAML==3.12

sdap/processors/tilereadingprocessor.py

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616
import datetime
1717
from collections import OrderedDict
1818
from contextlib import contextmanager
19-
from os import sep, path, remove
19+
from os import path, remove, sep
2020
from urllib.request import urlopen
2121

22-
from nexusproto import DataTile_pb2 as nexusproto
2322
import numpy
24-
from netCDF4 import Dataset, num2date
25-
from nexusproto.serialization import to_shaped_array, to_metadata
23+
import xarray as xr
24+
from cftime import num2date
2625
from pytz import timezone
2726

27+
from nexusproto import DataTile_pb2 as nexusproto
28+
from nexusproto.serialization import to_metadata, to_shaped_array
2829
from sdap.processors import NexusTileProcessor
2930

3031
EPOCH = timezone('UTC').localize(datetime.datetime(1970, 1, 1))
@@ -91,7 +92,7 @@ def to_seconds_from_epoch(date, timeunits=None, start_day=None, timeoffset=None)
9192

9293

9394
def get_ordered_slices(ds, variable, dimension_to_slice):
94-
dimensions_for_variable = [str(dimension) for dimension in ds[variable].dimensions]
95+
dimensions_for_variable = [str(dimension) for dimension in ds[variable].dims]
9596
ordered_slices = OrderedDict()
9697
for dimension in dimensions_for_variable:
9798
ordered_slices[dimension] = dimension_to_slice[dimension]
@@ -132,37 +133,38 @@ def read_data(self, tile_specifications, file_path, output_tile):
132133

133134

134135
class GridReadingProcessor(TileReadingProcessor):
136+
def __init__(self, variable_to_read, latitude, longitude, **kwargs):
137+
super().__init__(variable_to_read, latitude, longitude, **kwargs)
138+
self.x_dim = kwargs.get('x_dim', longitude)
139+
self.y_dim = kwargs.get('y_dim', latitude)
140+
135141
def read_data(self, tile_specifications, file_path, output_tile):
136142
# Time is optional for Grid data
137143
time = self.environ['TIME']
138144

139-
with Dataset(file_path) as ds:
145+
with xr.decode_cf(xr.open_dataset(file_path, decode_cf=False), decode_times=False) as ds:
140146
for section_spec, dimtoslice in tile_specifications:
141147
tile = nexusproto.GridTile()
142148

143-
tile.latitude.CopyFrom(
144-
to_shaped_array(numpy.ma.filled(ds[self.latitude][dimtoslice[self.latitude]], numpy.NaN)))
145-
146-
tile.longitude.CopyFrom(
147-
to_shaped_array(numpy.ma.filled(ds[self.longitude][dimtoslice[self.longitude]], numpy.NaN)))
148-
149+
tile.latitude.CopyFrom(to_shaped_array(numpy.ma.filled(ds[self.latitude].data[dimtoslice[self.y_dim]], numpy.NaN)))
150+
tile.longitude.CopyFrom(to_shaped_array(numpy.ma.filled(ds[self.longitude].data[dimtoslice[self.x_dim]], numpy.NaN)))
149151
# Before we read the data we need to make sure the dimensions are in the proper order so we don't have any
150152
# indexing issues
151153
ordered_slices = get_ordered_slices(ds, self.variable_to_read, dimtoslice)
152154
# Read data using the ordered slices, replacing masked values with NaN
153-
data_array = numpy.ma.filled(ds[self.variable_to_read][tuple(ordered_slices.values())], numpy.NaN)
155+
data_array = numpy.ma.filled(ds[self.variable_to_read].data[tuple(ordered_slices.values())], numpy.NaN)
154156

155157
tile.variable_data.CopyFrom(to_shaped_array(data_array))
156158

157159
if self.metadata is not None:
158160
tile.meta_data.add().CopyFrom(
159-
to_metadata(self.metadata, ds[self.metadata][tuple(ordered_slices.values())]))
161+
to_metadata(self.metadata, ds[self.metadata].data[tuple(ordered_slices.values())]))
160162

161163
if time is not None:
162164
timevar = ds[time]
163165
# Note assumption is that index of time is start value in dimtoslice
164-
tile.time = to_seconds_from_epoch(timevar[dimtoslice[time].start],
165-
timeunits=timevar.getncattr('units'),
166+
tile.time = to_seconds_from_epoch(timevar.data[dimtoslice[time].start],
167+
timeunits=timevar.attrs['units'],
166168
timeoffset=self.time_offset)
167169

168170
output_tile.tile.grid_tile.CopyFrom(tile)
@@ -178,25 +180,22 @@ def __init__(self, variable_to_read, latitude, longitude, time, **kwargs):
178180
self.time = time
179181

180182
def read_data(self, tile_specifications, file_path, output_tile):
181-
with Dataset(file_path) as ds:
183+
with xr.decode_cf(xr.open_dataset(file_path, decode_cf=False), decode_times=False) as ds:
182184
for section_spec, dimtoslice in tile_specifications:
183185
tile = nexusproto.SwathTile()
184186
# Time Lat Long Data and metadata should all be indexed by the same dimensions, order the incoming spec once using the data variable
185187
ordered_slices = get_ordered_slices(ds, self.variable_to_read, dimtoslice)
186-
tile.latitude.CopyFrom(
187-
to_shaped_array(numpy.ma.filled(ds[self.latitude][tuple(ordered_slices.values())], numpy.NaN)))
188-
189-
tile.longitude.CopyFrom(
190-
to_shaped_array(numpy.ma.filled(ds[self.longitude][tuple(ordered_slices.values())], numpy.NaN)))
188+
tile.latitude.CopyFrom(to_shaped_array(numpy.ma.filled(ds[self.latitude].data[tuple(ordered_slices.values())], numpy.NaN)))
189+
tile.longitude.CopyFrom(to_shaped_array(numpy.ma.filled(ds[self.longitude].data[tuple(ordered_slices.values())], numpy.NaN)))
191190

192191
timetile = ds[self.time][
193-
tuple([ordered_slices[time_dim] for time_dim in ds[self.time].dimensions])].astype(
192+
tuple([ordered_slices[time_dim] for time_dim in ds[self.time].dims])].astype(
194193
'float64',
195194
casting='same_kind',
196195
copy=False)
197-
timeunits = ds[self.time].getncattr('units')
196+
timeunits = ds[self.time].attrs['units']
198197
try:
199-
start_of_day_date = datetime.datetime.strptime(ds.getncattr(self.start_of_day),
198+
start_of_day_date = datetime.datetime.strptime(ds.attrs[self.start_of_day],
200199
self.start_of_day_pattern)
201200
except Exception:
202201
start_of_day_date = None
@@ -208,12 +207,12 @@ def read_data(self, tile_specifications, file_path, output_tile):
208207
tile.time.CopyFrom(to_shaped_array(timetile))
209208

210209
# Read the data converting masked values to NaN
211-
data_array = numpy.ma.filled(ds[self.variable_to_read][tuple(ordered_slices.values())], numpy.NaN)
210+
data_array = numpy.ma.filled(ds[self.variable_to_read].data[tuple(ordered_slices.values())], numpy.NaN)
212211
tile.variable_data.CopyFrom(to_shaped_array(data_array))
213212

214213
if self.metadata is not None:
215214
tile.meta_data.add().CopyFrom(
216-
to_metadata(self.metadata, ds[self.metadata][tuple(ordered_slices.values())]))
215+
to_metadata(self.metadata, ds[self.metadata].data[tuple(ordered_slices.values())]))
217216

218217
output_tile.tile.swath_tile.CopyFrom(tile)
219218

@@ -228,33 +227,33 @@ def __init__(self, variable_to_read, latitude, longitude, time, **kwargs):
228227
self.time = time
229228

230229
def read_data(self, tile_specifications, file_path, output_tile):
231-
with Dataset(file_path) as ds:
230+
with xr.decode_cf(xr.open_dataset(file_path, decode_cf=False), decode_times=False) as ds:
232231
for section_spec, dimtoslice in tile_specifications:
233232
tile = nexusproto.TimeSeriesTile()
234233

235234
instance_dimension = next(
236-
iter([dim for dim in ds[self.variable_to_read].dimensions if dim != self.time]))
235+
iter([dim for dim in ds[self.variable_to_read].dims if dim != self.time]))
237236

238237
tile.latitude.CopyFrom(
239-
to_shaped_array(numpy.ma.filled(ds[self.latitude][dimtoslice[instance_dimension]], numpy.NaN)))
238+
to_shaped_array(numpy.ma.filled(ds[self.latitude].data[dimtoslice[instance_dimension]], numpy.NaN)))
240239

241240
tile.longitude.CopyFrom(
242-
to_shaped_array(numpy.ma.filled(ds[self.longitude][dimtoslice[instance_dimension]], numpy.NaN)))
241+
to_shaped_array(numpy.ma.filled(ds[self.longitude].data[dimtoslice[instance_dimension]], numpy.NaN)))
243242

244243
# Before we read the data we need to make sure the dimensions are in the proper order so we don't
245244
# have any indexing issues
246245
ordered_slices = get_ordered_slices(ds, self.variable_to_read, dimtoslice)
247246
# Read data using the ordered slices, replacing masked values with NaN
248-
data_array = numpy.ma.filled(ds[self.variable_to_read][tuple(ordered_slices.values())], numpy.NaN)
247+
data_array = numpy.ma.filled(ds[self.variable_to_read].data[tuple(ordered_slices.values())], numpy.NaN)
249248

250249
tile.variable_data.CopyFrom(to_shaped_array(data_array))
251250

252251
if self.metadata is not None:
253252
tile.meta_data.add().CopyFrom(
254-
to_metadata(self.metadata, ds[self.metadata][tuple(ordered_slices.values())]))
253+
to_metadata(self.metadata, ds[self.metadata].data[tuple(ordered_slices.values())]))
255254

256255
tile.time.CopyFrom(
257-
to_shaped_array(numpy.ma.filled(ds[self.time][dimtoslice[self.time]], numpy.NaN)))
256+
to_shaped_array(numpy.ma.filled(ds[self.time].data[dimtoslice[self.time]], numpy.NaN)))
258257

259258
output_tile.tile.time_series_tile.CopyFrom(tile)
260259

tests/datafiles/OBP_2017_01.nc

2.01 MB
Binary file not shown.

tests/tilereadingprocessor_test.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,5 +311,36 @@ def test_read_not_empty_wswm(self):
311311
places=3)
312312

313313

314+
class TestReadInterpEccoData(unittest.TestCase):
315+
def setUp(self):
316+
self.module = sdap.processors.GridReadingProcessor('OBP', 'latitude', 'longitude', x_dim='i', y_dim='j', time='time')
317+
318+
def test_read_indexed_ecco(self):
319+
test_file = path.join(path.dirname(__file__), 'datafiles', 'OBP_2017_01.nc')
320+
321+
input_tile = nexusproto.NexusTile()
322+
tile_summary = nexusproto.TileSummary()
323+
tile_summary.granule = "file:%s" % test_file
324+
tile_summary.section_spec = "time:0:1,j:0:10,i:0:10"
325+
input_tile.summary.CopyFrom(tile_summary)
326+
327+
results = list(self.module.process(input_tile))
328+
329+
self.assertEqual(1, len(results))
330+
331+
for nexus_tile in results:
332+
self.assertTrue(nexus_tile.HasField('tile'))
333+
self.assertTrue(nexus_tile.tile.HasField('grid_tile'))
334+
335+
tile = nexus_tile.tile.grid_tile
336+
self.assertEqual(10, len(from_shaped_array(tile.latitude)))
337+
self.assertEqual(10, len(from_shaped_array(tile.longitude)))
338+
339+
the_data = np.ma.masked_invalid(from_shaped_array(tile.variable_data))
340+
self.assertEqual((1, 10, 10), the_data.shape)
341+
self.assertEqual(100, np.ma.count(the_data))
342+
self.assertEqual(1484568000, tile.time)
343+
344+
314345
if __name__ == '__main__':
315346
unittest.main()

0 commit comments

Comments
 (0)