Skip to content

Commit 02913e3

Browse files
committed
intermediate NDBC reader
1 parent a1cc144 commit 02913e3

File tree

7 files changed

+532
-12
lines changed

7 files changed

+532
-12
lines changed

core/src/main/java/com/bc/fiduceo/reader/insitu/ndbc/NdbcCWReader.java

Lines changed: 221 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,122 @@
22

33
import com.bc.fiduceo.core.Dimension;
44
import com.bc.fiduceo.core.Interval;
5+
import com.bc.fiduceo.core.NodeType;
56
import com.bc.fiduceo.geometry.Polygon;
67
import com.bc.fiduceo.location.PixelLocator;
78
import com.bc.fiduceo.reader.AcquisitionInfo;
89
import com.bc.fiduceo.reader.time.TimeLocator;
10+
import com.bc.fiduceo.reader.time.TimeLocator_MillisSince1970;
11+
import com.bc.fiduceo.util.NetCDFUtils;
12+
import com.bc.fiduceo.util.TimeUtils;
13+
import com.bc.fiduceo.util.VariableProxy;
914
import org.esa.snap.core.util.StringUtils;
15+
import org.esa.snap.core.util.io.FileUtils;
1016
import ucar.ma2.Array;
1117
import ucar.ma2.ArrayInt;
18+
import ucar.ma2.DataType;
1219
import ucar.ma2.InvalidRangeException;
20+
import ucar.nc2.Attribute;
1321
import ucar.nc2.Variable;
1422

23+
import java.io.BufferedReader;
1524
import java.io.File;
25+
import java.io.FileReader;
1626
import java.io.IOException;
27+
import java.util.ArrayList;
1728
import java.util.Calendar;
29+
import java.util.Date;
1830
import java.util.List;
1931

32+
import static com.bc.fiduceo.util.NetCDFUtils.*;
33+
2034
class NdbcCWReader extends NdbcReader {
2135

36+
public static final String GST = "GST";
37+
public static final String MEASUREMENT_TYPE = "measurement_type";
38+
public static final String ANEMOMETER_HEIGHT = "anemometer_height";
39+
public static final String SST_DEPTH = "sst_depth";
40+
public static final String WSPD = "WSPD";
41+
public static final String GTIME = "GTIME";
2242
private static final String REG_EX_CW = "\\w{5}c\\d{4}.txt";
23-
43+
private static final String STATION_ID = "station_id";
44+
private static final String STATION_TYPE = "station_type";
45+
private static final String LATITUDE = "latitude";
46+
private static final String BAROMETER_HEIGHT = "barometer_height";
47+
private static final String WDIR = "WDIR";
2448
private static StationDatabase stationDatabase;
2549

50+
private ArrayList<CwRecord> records;
51+
private TimeLocator timeLocator;
52+
private Station station;
2653

2754
NdbcCWReader() {
2855
}
2956

3057
@Override
3158
public void open(File file) throws IOException {
3259
ensureStationDatabase();
60+
loadStation(file);
61+
parseFile(file);
62+
}
63+
64+
private void loadStation(File file) {
65+
final String fileName = FileUtils.getFilenameWithoutExtension(file);
66+
final String stationId = fileName.substring(0, 5);
67+
station = stationDatabase.get(stationId);
68+
if (station == null) {
69+
throw new IllegalArgumentException("unsupported station, id = " + stationId);
70+
}
71+
}
72+
73+
private void parseFile(File file) throws IOException {
74+
records = new ArrayList<>();
75+
try (final FileReader fileReader = new FileReader(file)) {
76+
final Calendar calendar = TimeUtils.getUTCCalendar();
77+
final BufferedReader bufferedReader = new BufferedReader(fileReader);
78+
String line;
79+
while ((line = bufferedReader.readLine()) != null) {
80+
if (line.startsWith("#")) {
81+
// skip comment lines tb 2023-02-27
82+
continue;
83+
}
84+
85+
final CwRecord cwRecord = parseLine(line, calendar);
86+
records.add(cwRecord);
87+
}
88+
}
3389
}
3490

3591
@Override
3692
public void close() throws IOException {
93+
if (records != null) {
94+
records.clear();
95+
records = null;
96+
}
97+
98+
timeLocator = null;
3799
}
38100

39101
@Override
40102
public AcquisitionInfo read() throws IOException {
41103
final AcquisitionInfo acquisitionInfo = new AcquisitionInfo();
42104

105+
int minTime = Integer.MAX_VALUE;
106+
int maxTime = Integer.MIN_VALUE;
107+
for (final CwRecord record : records) {
108+
if (record.utc < minTime) {
109+
minTime = record.utc;
110+
}
111+
if (record.utc > maxTime) {
112+
maxTime = record.utc;
113+
}
114+
}
115+
116+
acquisitionInfo.setSensingStart(new Date(minTime * 1000L));
117+
acquisitionInfo.setSensingStop(new Date(maxTime * 1000L));
118+
119+
acquisitionInfo.setNodeType(NodeType.UNDEFINED);
120+
43121
return acquisitionInfo;
44122
}
45123

@@ -50,17 +128,33 @@ public String getRegEx() {
50128

51129
@Override
52130
public PixelLocator getPixelLocator() throws IOException {
53-
throw new RuntimeException("not implemented");
131+
throw new RuntimeException("not implemented"); // intentional tb 2023-02-27
54132
}
55133

56134
@Override
57135
public PixelLocator getSubScenePixelLocator(Polygon sceneGeometry) throws IOException {
58-
throw new RuntimeException("not implemented");
136+
throw new RuntimeException("not implemented"); // intentional tb 2023-02-27
59137
}
60138

61139
@Override
62140
public TimeLocator getTimeLocator() throws IOException {
63-
throw new RuntimeException("not implemented");
141+
if (timeLocator == null) {
142+
createTimeLocator();
143+
}
144+
145+
return timeLocator;
146+
}
147+
148+
private void createTimeLocator() {
149+
long[] timeArray = new long[records.size()];
150+
151+
int i = 0;
152+
for (final CwRecord record : records) {
153+
timeArray[i] = record.utc * 1000L;
154+
i++;
155+
}
156+
157+
timeLocator = new TimeLocator_MillisSince1970(timeArray);
64158
}
65159

66160
@Override
@@ -70,7 +164,37 @@ public int[] extractYearMonthDayFromFilename(String fileName) {
70164

71165
@Override
72166
public Array readRaw(int centerX, int centerY, Interval interval, String variableName) throws IOException, InvalidRangeException {
73-
throw new RuntimeException("not implemented");
167+
if (variableName.equals(STATION_ID)) {
168+
169+
} else if (variableName.equals(STATION_TYPE)) {
170+
final StationType type = station.getType();
171+
return createResultArray(toByte(type), -1, DataType.BYTE, interval);
172+
} else if (variableName.equals(MEASUREMENT_TYPE)) {
173+
final MeasurementType measurementType = station.getMeasurementType();
174+
return createResultArray(toByte(measurementType), -1, DataType.BYTE, interval);
175+
} else if (variableName.equals(LATITUDE)) {
176+
return createResultArray(station.getLat(), Float.NaN, DataType.FLOAT, interval);
177+
} else if (variableName.equals(ANEMOMETER_HEIGHT)) {
178+
return createResultArray(station.getAnemometerHeight(), Float.NaN, DataType.FLOAT, interval);
179+
} else if (variableName.equals(BAROMETER_HEIGHT)) {
180+
return createResultArray(station.getBarometerHeight(), Float.NaN, DataType.FLOAT, interval);
181+
} else if (variableName.equals(SST_DEPTH)) {
182+
return createResultArray(station.getSSTDepth(), Float.NaN, DataType.FLOAT, interval);
183+
} else if (variableName.equals(WDIR)) {
184+
final CwRecord record = records.get(centerY);
185+
return createResultArray(record.windDir, 999, DataType.SHORT, interval);
186+
} else if (variableName.equals(WSPD)) {
187+
final CwRecord record = records.get(centerY);
188+
return createResultArray(record.windSpeed, 99.f, DataType.FLOAT, interval);
189+
} else if (variableName.equals(GST)) {
190+
final CwRecord record = records.get(centerY);
191+
return createResultArray(record.gustSpeed, 99.f, DataType.FLOAT, interval);
192+
} else if (variableName.equals(GTIME)) {
193+
final CwRecord record = records.get(centerY);
194+
return createResultArray(record.gustTime, 9999, DataType.SHORT, interval);
195+
}
196+
197+
return null;
74198
}
75199

76200
@Override
@@ -85,12 +209,99 @@ public ArrayInt.D2 readAcquisitionTime(int x, int y, Interval interval) throws I
85209

86210
@Override
87211
public List<Variable> getVariables() throws InvalidRangeException, IOException {
88-
throw new RuntimeException("not implemented");
212+
final ArrayList<Variable> variables = new ArrayList<>();
213+
214+
List<Attribute> attributes = new ArrayList<>();
215+
attributes.add(new Attribute(CF_LONG_NAME, "Station identifier"));
216+
variables.add(new VariableProxy(STATION_ID, DataType.STRING, attributes));
217+
218+
attributes = new ArrayList<>();
219+
attributes.add(new Attribute(CF_LONG_NAME, "Station type. 0: OCEAN_BUOY, 1: COAST_BUOY, 2: LAKE_BUOY, 3: OCEAN_STATION, 4: COAST_STATION, 5: LAKE_STATION"));
220+
variables.add(new VariableProxy(STATION_TYPE, DataType.BYTE, attributes));
221+
222+
attributes = new ArrayList<>();
223+
attributes.add(new Attribute(CF_LONG_NAME, "Measurement type. 0: CONSTANT_WIND, 1: STANDARD_METEOROLOGICAL"));
224+
variables.add(new VariableProxy(MEASUREMENT_TYPE, DataType.BYTE, attributes));
225+
226+
attributes = new ArrayList<>();
227+
attributes.add(new Attribute(CF_UNITS_NAME, "degree_east"));
228+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, NetCDFUtils.getDefaultFillValue(float.class)));
229+
attributes.add(new Attribute(CF_STANDARD_NAME, "longitude"));
230+
variables.add(new VariableProxy("longitude", DataType.FLOAT, attributes));
231+
232+
attributes = new ArrayList<>();
233+
attributes.add(new Attribute(CF_UNITS_NAME, "degree_north"));
234+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, NetCDFUtils.getDefaultFillValue(float.class)));
235+
attributes.add(new Attribute(CF_STANDARD_NAME, "latitude"));
236+
variables.add(new VariableProxy(LATITUDE, DataType.FLOAT, attributes));
237+
238+
attributes = new ArrayList<>();
239+
attributes.add(new Attribute(CF_UNITS_NAME, "m"));
240+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, Float.NaN));
241+
attributes.add(new Attribute(CF_LONG_NAME, "Height of instrument above site elevation"));
242+
variables.add(new VariableProxy(ANEMOMETER_HEIGHT, DataType.FLOAT, attributes));
243+
244+
attributes = new ArrayList<>();
245+
attributes.add(new Attribute(CF_UNITS_NAME, "m"));
246+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, Float.NaN));
247+
attributes.add(new Attribute(CF_LONG_NAME, "Height of instrument above site elevation"));
248+
variables.add(new VariableProxy("air_temp_height", DataType.FLOAT, attributes));
249+
250+
attributes = new ArrayList<>();
251+
attributes.add(new Attribute(CF_UNITS_NAME, "m"));
252+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, Float.NaN));
253+
attributes.add(new Attribute(CF_LONG_NAME, "Height of instrument above above mean sea level"));
254+
variables.add(new VariableProxy(BAROMETER_HEIGHT, DataType.FLOAT, attributes));
255+
256+
attributes = new ArrayList<>();
257+
attributes.add(new Attribute(CF_UNITS_NAME, "m"));
258+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, Float.NaN));
259+
attributes.add(new Attribute(CF_LONG_NAME, "Depth of instrument below water line"));
260+
variables.add(new VariableProxy(SST_DEPTH, DataType.FLOAT, attributes));
261+
262+
attributes = new ArrayList<>();
263+
attributes.add(new Attribute(CF_UNITS_NAME, "seconds since 1970-01-01"));
264+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, NetCDFUtils.getDefaultFillValue(int.class)));
265+
attributes.add(new Attribute(CF_STANDARD_NAME, "time"));
266+
variables.add(new VariableProxy("time", DataType.INT, attributes));
267+
268+
// @todo 1 tb/tb check CF standard names for the measurement data 2023-02027
269+
attributes = new ArrayList<>();
270+
attributes.add(new Attribute(CF_UNITS_NAME, "degT"));
271+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, 999));
272+
attributes.add(new Attribute(CF_LONG_NAME, "Ten-minute average wind direction measurements in degrees clockwise from true North."));
273+
variables.add(new VariableProxy(WDIR, DataType.SHORT, attributes));
274+
275+
attributes = new ArrayList<>();
276+
attributes.add(new Attribute(CF_UNITS_NAME, "m/s"));
277+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, 99.f));
278+
attributes.add(new Attribute(CF_LONG_NAME, "Ten-minute average wind speed values in m/s."));
279+
variables.add(new VariableProxy(WSPD, DataType.FLOAT, attributes));
280+
281+
attributes = new ArrayList<>();
282+
attributes.add(new Attribute(CF_UNITS_NAME, "degT"));
283+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, 999));
284+
attributes.add(new Attribute(CF_LONG_NAME, "Direction, in degrees clockwise from true North, of the GST, reported at the last hourly 10-minute segment."));
285+
variables.add(new VariableProxy("GDR", DataType.SHORT, attributes));
286+
287+
attributes = new ArrayList<>();
288+
attributes.add(new Attribute(CF_UNITS_NAME, "m/s"));
289+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, 99.f));
290+
attributes.add(new Attribute(CF_LONG_NAME, "Maximum 5-second peak gust during the measurement hour, reported at the last hourly 10-minute segment."));
291+
variables.add(new VariableProxy(GST, DataType.FLOAT, attributes));
292+
293+
attributes = new ArrayList<>();
294+
attributes.add(new Attribute(CF_UNITS_NAME, "hhmm"));
295+
attributes.add(new Attribute(CF_FILL_VALUE_NAME, 9999));
296+
attributes.add(new Attribute(CF_LONG_NAME, "The minute of the hour that the GSP occurred, reported at the last hourly 10-minute segment."));
297+
variables.add(new VariableProxy(GTIME, DataType.SHORT, attributes));
298+
299+
return variables;
89300
}
90301

91302
@Override
92303
public Dimension getProductSize() throws IOException {
93-
throw new RuntimeException("not implemented");
304+
return new Dimension("product_size", 1, records.size());
94305
}
95306

96307
@Override
@@ -109,15 +320,15 @@ private void ensureStationDatabase() throws IOException {
109320
}
110321
}
111322

112-
public CwRecord parseLine(String line, Calendar calendar) {
323+
CwRecord parseLine(String line, Calendar calendar) {
113324
final CwRecord cwRecord = new CwRecord();
114325

115-
line = line.replace(" ", " "); // some fields are separated by two blanks (sigh) tb 2023-02-24
326+
line = line.replaceAll(" +", " "); // some fields are separated by two or more blanks (sigh) tb 2023-02-27
116327
final String[] tokens = StringUtils.split(line, new char[]{' '}, true);
117328

118329
calendar.setTimeInMillis(0);
119330
calendar.set(Calendar.YEAR, Integer.parseInt(tokens[0]));
120-
calendar.set(Calendar.MONTH, Integer.parseInt(tokens[1]) - 1);
331+
calendar.set(Calendar.MONTH, Integer.parseInt(tokens[1]) - 1); // calendar wants month zero-based tb 2023-02-27
121332
calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(tokens[2]));
122333
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(tokens[3]));
123334
calendar.set(Calendar.MINUTE, Integer.parseInt(tokens[4]));

core/src/main/java/com/bc/fiduceo/reader/insitu/ndbc/NdbcReader.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package com.bc.fiduceo.reader.insitu.ndbc;
22

3+
import com.bc.fiduceo.core.Interval;
34
import com.bc.fiduceo.reader.Reader;
5+
import com.bc.fiduceo.util.NetCDFUtils;
6+
import ucar.ma2.Array;
7+
import ucar.ma2.DataType;
48

59
import java.io.IOException;
610
import java.io.InputStream;
@@ -20,4 +24,47 @@ StationDatabase parseStationDatabase(String resourceName) throws IOException {
2024

2125
return sdb;
2226
}
27+
28+
static byte toByte(StationType stationType) {
29+
switch (stationType) {
30+
case OCEAN_BUOY:
31+
return 0;
32+
case COAST_BUOY:
33+
return 1;
34+
case LAKE_BUOY:
35+
return 2;
36+
case OCEAN_STATION:
37+
return 3;
38+
case COAST_STATION:
39+
return 4;
40+
case LAKE_STATION:
41+
return 5;
42+
default:
43+
return -1;
44+
}
45+
}
46+
47+
static byte toByte(MeasurementType measurementType) {
48+
switch (measurementType) {
49+
case CONSTANT_WIND:
50+
return 0;
51+
case STANDARD_METEOROLOGICAL:
52+
return 1;
53+
default:
54+
return -1;
55+
}
56+
}
57+
58+
protected Array createResultArray(Number value, Number fillValue, DataType dataType, Interval interval) {
59+
final int windowHeight = interval.getY();
60+
final int windowWidth = interval.getX();
61+
final Array windowArray = NetCDFUtils.create(dataType,
62+
new int[]{windowHeight, windowWidth},
63+
fillValue);
64+
65+
final int windowCenterX = windowWidth / 2;
66+
final int windowCenterY = windowHeight / 2;
67+
windowArray.setObject(windowWidth * windowCenterY + windowCenterX, value);
68+
return windowArray;
69+
}
2370
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.bc.fiduceo.reader.insitu.ndbc;
2+
3+
class SmRecord {
4+
5+
int utc;
6+
7+
}

0 commit comments

Comments
 (0)