src/main/java/net/nullschool/grib2json/OscarRecordWriter.java
package net.nullschool.grib2json;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import ucar.ma2.*;
import ucar.nc2.Variable;
import javax.json.JsonValue;
import javax.json.stream.JsonGenerator;
import java.io.IOException;
import static ucar.grib.grib2.Grib2Tables.*;
import static ucar.grib.grib2.ParameterTable.getCategoryName;
import static ucar.grib.grib2.ParameterTable.getParameterName;
import static ucar.grib.grib2.ParameterTable.getParameterUnit;
import static ucar.grib.grib2.ParameterTable.getDisciplineName;
/**
* 2014-01-15<p/>
*
* Writes OSCAR (Ocean Surface Current Analyses Real-time) data to a JSON generator.
*
* Lots of magic values in here, as we're mapping OSCAR data in NetCDF format to JSON using GRIB header constants.
* The input is assumed to be in the range [20E:80N, 380E:80S], with 1/3ยบ resolution. To reduce download size, the
* precision of the data is reduced to a resolution of 2cm/s.
*
* This is not a generic processing of NetCDF records.
*
* For more information on OSCAR, see http://www.esr.org/oscar_index.html.
*
* @author Cameron Beccario
*/
final class OscarRecordWriter extends AbstractRecordWriter {
private static final int OCEAN_PRODUCTS = 10;
private static final int NX = 1080; // Number of points on x-axis or parallel
private static final int NY = 481; // Number of points on y-axis or meridian
private static final String RANGE = "0,0,0:480,0:1079";
private final Variable var;
private final DateTime date;
private final double depth;
OscarRecordWriter(JsonGenerator jg, Variable var, DateTime date, double depth, Options options) {
super(jg, options);
this.var = var;
this.date = date;
this.depth = depth;
}
private void writeIndicator() {
write("discipline", OCEAN_PRODUCTS, getDisciplineName(OCEAN_PRODUCTS));
}
private void writeIdentification() {
write("center", -3, "Earth & Space Research");
write("refTime", date.withZone(DateTimeZone.UTC).toString());
write("significanceOfRT", 0, "Analysis");
}
private static int variableToParam(Variable var) {
switch (var.getFullName()) {
case "u": return 2; // U component of current
case "v": return 3; // V component of current
default:
throw new IllegalArgumentException("unknown variable: " + var);
}
}
private void writeProduct() {
final int paramCategory = 1; // Currents
final int paramNumber = variableToParam(var);
final int surfaceType = 160; // Depth below sea level
write("parameterCategory", 1, getCategoryName(OCEAN_PRODUCTS, paramCategory));
write("parameterNumber", paramNumber, getParameterName(OCEAN_PRODUCTS, paramCategory, paramNumber));
write("parameterUnit", getParameterUnit(OCEAN_PRODUCTS, paramCategory, paramNumber));
write("forecastTime", 0);
write("surface1Type", surfaceType, codeTable4_5(surfaceType));
write("surface1Value", depth);
}
private void writeGridShape() {
write("shape", 0, codeTable3_2(0));
}
private void writeGridSize() {
write("scanMode", 0);
write("nx", NX); // Number of points on x-axis or parallel
write("ny", NY); // Number of points on y-axis or meridian
}
private void writeLonLatBounds() {
write("lo1", 20); // longitude of first grid point
write("la1", 80); // latitude of first grid point
write("lo2", (20 + 359) + 2/3d); // longitude of last grid point
write("la2", -80); // latitude of last grid point
write("dx", 1/3d); // i direction increment
write("dy", 1/3d); // j direction increment
}
private void writeGridDefinition() {
write("numberPoints", NX * NY);
writeGridShape();
writeGridSize();
writeLonLatBounds();
}
/**
* Write the record's header as a Json object: "header": { ... }
*/
private void writeHeader() {
jg.writeStartObject("header");
writeIndicator();
writeIdentification();
writeProduct();
writeGridDefinition();
jg.writeEnd();
}
/**
* Round to nearest fraction of 1/denominator.
*/
private float round(float value, float denominator) {
return Math.round(value * denominator) / denominator;
}
/**
* Write the record's data as a Json array: "data": [ ... ]
*/
private void writeData() throws IOException {
if (!options.getPrintData()) {
return;
}
try {
jg.writeStartArray("data");
Array data = var.read(RANGE).reduce();
IndexIterator ii = data.getIndexIterator();
while (ii.hasNext()) {
float value = ii.getFloatNext();
jg.write(Float.isNaN(value) ? JsonValue.NULL : new FloatValue(round(value, 50)));
}
jg.writeEnd();
}
catch (InvalidRangeException e) {
throw new RuntimeException(e);
}
}
/**
* Write the record as a Json object: { "header": { ... }, "data": [ ... ] }
*/
void writeRecord() throws IOException {
jg.writeStartObject();
writeHeader();
writeData();
jg.writeEnd();
}
}