diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..52f404e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Cameron Beccario + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/pom.xml b/pom.xml index 853b061..0b24806 100644 --- a/pom.xml +++ b/pom.xml @@ -18,15 +18,13 @@ https://github.com/cambecc/grib2json 2013 - diff --git a/src/bin/grib2json b/src/bin/grib2json index 57aafd9..5a37315 100644 --- a/src/bin/grib2json +++ b/src/bin/grib2json @@ -1,6 +1,5 @@ #!/bin/sh #set -x - LIB_DIR=$(dirname "$0")/../lib LAUNCH_JAR=$LIB_DIR/grib2json-*.jar $JAVA_HOME/bin/java -Xmx1G -jar $LAUNCH_JAR $@ diff --git a/src/main/java/net/nullschool/grib2json/FloatValue.java b/src/main/java/net/nullschool/grib2json/FloatValue.java index 21d26c5..35ddad8 100644 --- a/src/main/java/net/nullschool/grib2json/FloatValue.java +++ b/src/main/java/net/nullschool/grib2json/FloatValue.java @@ -8,6 +8,12 @@ /** * 2013-10-24

* + * A Json float value. This class uses Float.toString to produce the Json text for a float. This avoids the + * noise caused by the default JsonGenerator when it widens to double. + * + * This class also defines the Json representations for NaN, Infinity, and -Infinity to be their equivalent + * String representations. For example: [1.0, 2.3, "NaN", 0.7, "Infinity"] + * * @author Cameron Beccario */ final class FloatValue implements JsonNumber { diff --git a/src/main/java/net/nullschool/grib2json/Grib2Json.java b/src/main/java/net/nullschool/grib2json/Grib2Json.java index 95a0385..853763a 100644 --- a/src/main/java/net/nullschool/grib2json/Grib2Json.java +++ b/src/main/java/net/nullschool/grib2json/Grib2Json.java @@ -14,6 +14,11 @@ /** * 2013-10-25

* + * Converts a GRIB2 file to Json. GRIB2 decoding is performed by the NetCDF GRIB decoder. + * + * This class was initially based on Grib2Dump, part of the NetCDF Java library written by University + * Corporation for Atmospheric Research/Unidata. However, what appears below is a complete rewrite. + * * @author Cameron Beccario */ public final class Grib2Json { @@ -27,6 +32,9 @@ public Grib2Json(Options options) { this.options = options; } + /** + * Convert the GRIB2 file to Json as specified by the command line options. + */ public void write() throws IOException { RandomAccessFile raf = new RandomAccessFile(options.getFile().getPath(), "r"); @@ -42,7 +50,7 @@ public void write() throws IOException { JsonGeneratorFactory jgf = Json.createGeneratorFactory( - options.isCompact() ? + options.isCompactFormat() ? null : singletonMap(JsonGenerator.PRETTY_PRINTING, true)); JsonGenerator jg = jgf.createGenerator(output); @@ -55,7 +63,7 @@ public void write() throws IOException { if (rw.isSelected()) { jg.writeStartObject(); rw.writeHeader(); - if (options.isData()) { + if (options.getPrintData()) { rw.writeData(new Grib2Data(raf)); } jg.writeEnd(); diff --git a/src/main/java/net/nullschool/grib2json/Launcher.java b/src/main/java/net/nullschool/grib2json/Launcher.java index 615fe11..c515087 100644 --- a/src/main/java/net/nullschool/grib2json/Launcher.java +++ b/src/main/java/net/nullschool/grib2json/Launcher.java @@ -8,6 +8,8 @@ /** * 2013-10-24

* + * Execution shim for the grib2json utility. Parses command line options and invokes the {@link Grib2Json} converter. + * * @author Cameron Beccario */ class Launcher { @@ -30,13 +32,13 @@ public static void main(String args[]) { return; } - if (options.isHelp() || options.getFile() == null) { + if (options.getShowHelp() || options.getFile() == null) { printUsage(); return; } LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); - if (!options.isVerbose()) { + if (!options.getEnableLogging()) { lc.stop(); } diff --git a/src/main/java/net/nullschool/grib2json/Options.java b/src/main/java/net/nullschool/grib2json/Options.java index 761ed90..aad095f 100644 --- a/src/main/java/net/nullschool/grib2json/Options.java +++ b/src/main/java/net/nullschool/grib2json/Options.java @@ -8,28 +8,29 @@ /** * 2013-10-25

* + * Command line options for the grib2json utility. This interface is proxied by the Jewel Cli options parsing library. + * * @author Cameron Beccario */ - @CommandLineInterface(application="grib2json") public interface Options { - @Option(shortName={"h", "?"}, description="display help") - boolean isHelp(); + @Option(longName="help", shortName={"h", "?"}, description="display help") + boolean getShowHelp(); - @Option(shortName="n", description="print names of codes") - boolean isNames(); + @Option(longName="names", shortName="n", description="print names of the numeric codes") + boolean getPrintNames(); - @Option(shortName="d", description="print record data") - boolean isData(); + @Option(longName="data", shortName="d", description="print record data") + boolean getPrintData(); - @Option(shortName="c", description="enable compact formatting") - boolean isCompact(); + @Option(longName="compact", shortName="c", description="enable compact formatting") + boolean isCompactFormat(); - @Option(shortName="v", description="enable logging") - boolean isVerbose(); + @Option(longName="verbose", shortName="v", description="enable logging") + boolean getEnableLogging(); - @Option(shortName="o", description="print to specified file", defaultToNull=true) + @Option(longName="output", shortName="o", description="print to specified file", defaultToNull=true) File getOutput(); @Unparsed(name="FILE", defaultToNull=true) diff --git a/src/main/java/net/nullschool/grib2json/RecordWriter.java b/src/main/java/net/nullschool/grib2json/RecordWriter.java index d47e6e3..1e4bb52 100644 --- a/src/main/java/net/nullschool/grib2json/RecordWriter.java +++ b/src/main/java/net/nullschool/grib2json/RecordWriter.java @@ -3,6 +3,7 @@ import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import ucar.grib.grib2.*; +import ucar.grib.GribNumbers; import javax.json.stream.JsonGenerator; import java.io.IOException; @@ -16,6 +17,11 @@ /** * 2013-10-25

* + * Writes a Grib2 record to a JSON generator. + * + * This class was initially based on Grib2Dump, part of the NetCDF Java library written by University + * Corporation for Atmospheric Research/Unidata. However, what appears below is a complete rewrite. + * * @author Cameron Beccario */ final class RecordWriter { @@ -28,8 +34,6 @@ final class RecordWriter { private final Grib2GDSVariables gds; private final Options options; - private Set keys = new HashSet<>(); - RecordWriter(JsonGenerator jg, Grib2Record record, Options options) { this.jg = Objects.requireNonNull(jg); this.record = record; @@ -40,6 +44,9 @@ final class RecordWriter { this.options = options; } + /** + * Return true if the specified command line options do not filter out this record. + */ boolean isSelected() { return (options.getFilterCategory() == null || options.getFilterCategory() == pds.getParameterCategory()) && @@ -48,131 +55,155 @@ boolean isSelected() { (options.getFilterValue() == null || options.getFilterValue() == pds.getLevelValue1()); } - private boolean isUnique(String key) { - return keys.add(key); - } - - private void write(String name, int value) { - assert isUnique(name); - jg.write(name, value); + /** + * Write a "key":int Json pair. + */ + private void write(String key, int value) { + jg.write(key, value); } - private void writeIfSet(String name, int value) { - assert isUnique(name); + /** + * Write a "key":int Json pair only if the value is not {@link GribNumbers#UNDEFINED}. + */ + private void writeIfSet(String key, int value) { if (value != UNDEFINED) { - jg.write(name, value); + jg.write(key, value); } } - private void write(String name, long value) { - assert isUnique(name); - jg.write(name, value); + /** + * Write a "key":long Json pair. + */ + private void write(String key, long value) { + jg.write(key, value); } - private void write(String name, float value) { - assert isUnique(name); - jg.write(name, new FloatValue(value)); + /** + * Write a "key":float Json pair. + */ + private void write(String key, float value) { + jg.write(key, new FloatValue(value)); } - private void writeIfSet(String name, float value) { - assert isUnique(name); + /** + * Write a "key":float Json pair only if the value is not {@link GribNumbers#UNDEFINED}. + */ + private void writeIfSet(String key, float value) { if (value != UNDEFINED) { - jg.write(name, new FloatValue(value)); + jg.write(key, new FloatValue(value)); } } - private void write(String name, double value) { - assert isUnique(name); - jg.write(name, value); + /** + * Write a "key":double Json pair. + */ + private void write(String key, double value) { + jg.write(key, value); } - private void write(String name, String value) { - assert isUnique(name); - jg.write(name, value); + /** + * Write a "key":"value" Json pair. + */ + private void write(String key, String value) { + jg.write(key, value); } - private void write(String name, int code, String description) { - write(name, code); - if (options.isNames()) { - write(name + "Name", description); + /** + * Write a "key":"value" Json pair, and a second "keyName":"name" pair if the command line options + * have name printing enabled. + */ + private void write(String key, int code, String name) { + write(key, code); + if (options.getPrintNames()) { + write(key + "Name", name); } } + /** + * Write contents of the record's indicator section. + */ private void writeIndicator() { write("discipline", ins.getDiscipline(), ins.getDisciplineName()); write("gribEdition", ins.getGribEdition()); write("gribLength", ins.getGribLength()); } + /** + * Write contents of the record's identification section. + */ private void writeIdentification() { - write("center_id", ids.getCenter_id(), getCenter_idName(ids.getCenter_id())); // Originating Center - write("subcenter_id", ids.getSubcenter_id()); // Originating Sub-Center - write("refTime", new DateTime(ids.getRefTime()).withZone(DateTimeZone.UTC).toString()); // Reference Time - write("significanceOfRT", ids.getSignificanceOfRT(), ids.getSignificanceOfRTName()); // Significance of Reference Time + write("center", ids.getCenter_id(), getCenter_idName(ids.getCenter_id())); + write("subcenter", ids.getSubcenter_id()); + write("refTime", new DateTime(ids.getRefTime()).withZone(DateTimeZone.UTC).toString()); + write("significanceOfRT", ids.getSignificanceOfRT(), ids.getSignificanceOfRTName()); write("productStatus", ids.getProductStatus(), ids.getProductStatusName()); write("productType", ids.getProductType(), ids.getProductTypeName()); } + /** + * Write contents of the record's product section. + */ private void writeProduct() { final int productDef = pds.getProductDefinitionTemplate(); final int discipline = ins.getDiscipline(); final int paramCategory = pds.getParameterCategory(); final int paramNumber = pds.getParameterNumber(); - write("productDefinition", productDef, codeTable4_0(productDef)); + write("productDefinitionTemplate", productDef, codeTable4_0(productDef)); write("parameterCategory", paramCategory, getCategoryName(discipline, paramCategory)); - write("parameter", paramNumber, getParameterName(discipline, paramCategory, paramNumber)); + write("parameterNumber", paramNumber, getParameterName(discipline, paramCategory, paramNumber)); write("parameterUnit", getParameterUnit(discipline, paramCategory, paramNumber)); - write("genProcessType", pds.getGenProcessType(), codeTable4_3(pds.getGenProcessType())); // Generating Process Type + write("genProcessType", pds.getGenProcessType(), codeTable4_3(pds.getGenProcessType())); write("forecastTime", pds.getForecastTime()); - write("levelType1", pds.getLevelType1(), codeTable4_5(pds.getLevelType1())); // First Surface Type - write("levelValue1", pds.getLevelValue1()); - write("levelType2", pds.getLevelType2(), codeTable4_5(pds.getLevelType2())); // Second Surface Type - write("levelValue2", pds.getLevelValue2()); + write("surface1Type", pds.getLevelType1(), codeTable4_5(pds.getLevelType1())); + write("surface1Value", pds.getLevelValue1()); + write("surface2Type", pds.getLevelType2(), codeTable4_5(pds.getLevelType2())); + write("surface2Value", pds.getLevelValue2()); } private void writeGridShape() { - write("shape", gds.getShape(), codeTable3_2(gds.getShape())); // grid shape + // See http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_table3-2.shtml + write("shape", gds.getShape(), codeTable3_2(gds.getShape())); switch (gds.getShape()) { - case 1: - write("earthRadius", gds.getEarthRadius()); // Spherical earth radius + case 1: // Earth assumed spherical with radius specified (in m) by data producer + write("earthRadius", gds.getEarthRadius()); break; - case 3: - write("majorAxis", gds.getMajorAxis()); // Oblate earth major axis - write("minorAxis", gds.getMinorAxis()); // Oblate earth minor axis + case 3: // Earth assumed oblate spheroid with major and minor axes specified (in km) by data producer + write("majorAxis", gds.getMajorAxis()); + write("minorAxis", gds.getMinorAxis()); break; } } private void writeGridSize() { write("gridUnits", gds.getGridUnits()); - write("resolution", gds.getResolution()); // Resolution & Component flags + write("resolution", gds.getResolution()); write("winds", isBitSet(gds.getResolution(), BIT_5) ? "relative" : "true"); write("scanMode", gds.getScanMode()); - write("nx", gds.getNx()); // Number of points along parallel - write("ny", gds.getNy()); // Number of points along meridian + write("nx", gds.getNx()); // Number of points on x-axis or parallel + write("ny", gds.getNy()); // Number of points on y-axis or meridian } private void writeLatLongBounds() { - writeIfSet("la1", gds.getLa1()); // Latitude of first grid point - writeIfSet("lo1", gds.getLo1()); // Longitude of first grid point - writeIfSet("la2", gds.getLa2()); // Latitude of last grid point - writeIfSet("lo2", gds.getLo2()); // Longitude of last grid point + writeIfSet("la1", gds.getLa1()); // latitude of first grid point + writeIfSet("lo1", gds.getLo1()); // longitude of first grid point + writeIfSet("la2", gds.getLa2()); // latitude of last grid point + writeIfSet("lo2", gds.getLo2()); // longitude of last grid point writeIfSet("dx", gds.getDx()); // i direction increment writeIfSet("dy", gds.getDy()); // j direction increment } private void writeRotationAndStretch() { - writeIfSet("spLat", gds.getSpLat()); // Latitude of southern pole - writeIfSet("spLon", gds.getSpLon()); // Longitude of southern pole + writeIfSet("spLat", gds.getSpLat()); // latitude of the southern pole of projection + writeIfSet("spLon", gds.getSpLon()); // longitude of the southern pole of projection writeIfSet("rotationAngle", gds.getRotationAngle()); - writeIfSet("poleLat", gds.getPoleLat()); - writeIfSet("poleLon", gds.getPoleLon()); + writeIfSet("poleLat", gds.getPoleLat()); // latitude of the pole of stretching + writeIfSet("poleLon", gds.getPoleLon()); // longitude of the pole stretching writeIfSet("stretchingFactor", gds.getStretchingFactor()); } private void writeAngle() { - writeIfSet("angle", gds.getAngle()); + writeIfSet("angle", gds.getAngle()); // orientation of the grid writeIfSet("basicAngle", gds.getBasicAngle()); writeIfSet("subDivisions", gds.getSubDivisions()); } @@ -183,7 +214,7 @@ private void writeLatLongGrid() { writeAngle(); writeLatLongBounds(); writeRotationAndStretch(); - writeIfSet("np", gds.getNp()); // Number of parallels + writeIfSet("np", gds.getNp()); // number of paralells between a pole and the equator } private void writeMercatorGrid() { @@ -207,9 +238,9 @@ private void writeLambertConformalGrid() { write("laD", gds.getLaD()); write("loV", gds.getLoV()); - write("projectionFlag", gds.getProjectionFlag()); // projection center - write("latin1", gds.getLatin1()); - write("latin2", gds.getLatin2()); + write("projectionFlag", gds.getProjectionFlag()); + write("latin1", gds.getLatin1()); // first latitude from the pole at which the secant cone cuts the sphere + write("latin2", gds.getLatin2()); // second latitude from the pole at which the secant cone cuts the sphere } private void writeSpaceOrOrthographicGrid() { @@ -218,13 +249,13 @@ private void writeSpaceOrOrthographicGrid() { writeAngle(); writeLatLongBounds(); - write("lap", gds.getLap()); // Latitude of sub-satellite point - write("lop", gds.getLop()); // Longitude of sub-satellite pt - write("xp", gds.getXp()); // Xp-coordinate of sub-satellite - write("yp", gds.getYp()); // Yp-coordinate of sub-satellite - write("nr", gds.getNr()); // Nr Altitude of the camera - write("xo", gds.getXo()); // Xo-coordinate of origin - write("yo", gds.getYo()); // Yo-coordinate of origin + write("lap", gds.getLap()); // latitude of sub-satellite point + write("lop", gds.getLop()); // longitude of sub-satellite point + write("xp", gds.getXp()); // x-coordinate of sub-satellite point + write("yp", gds.getYp()); // y-coordinate of sub-satellite point + write("nr", gds.getNr()); // altitude of the camera from the Earth's center + write("xo", gds.getXo()); // x-coordinate of origin of sector image + write("yo", gds.getYo()); // y-coordinate of origin of sector image } private void writeCurvilinearGrid() { @@ -232,43 +263,50 @@ private void writeCurvilinearGrid() { writeGridSize(); } + /** + * Write contents of the record's grid definition section. + * See http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_table3-1.shtml + */ private void writeGridDefinition() { final int gridTemplate = gds.getGdtn(); - write("gridDefinition", gridTemplate, codeTable3_1(gridTemplate)); // Grid Name - write("numberPoints", gds.getNumberPoints()); // Number of data points + write("gridDefinitionTemplate", gridTemplate, codeTable3_1(gridTemplate)); + write("numberPoints", gds.getNumberPoints()); switch (gridTemplate) { - case 0: - case 1: - case 2: - case 3: + case 0: // Template 3.0 + case 1: // Template 3.1 + case 2: // Template 3.2 + case 3: // Template 3.3 writeLatLongGrid(); break; - case 10: + case 10: // Template 3.10 writeMercatorGrid(); break; - case 20: + case 20: // Template 3.20 writePolarStereographicGrid(); break; - case 30: + case 30: // Template 3.30 writeLambertConformalGrid(); break; - case 40: - case 41: - case 42: - case 43: + case 40: // Template 3.40 + case 41: // Template 3.41 + case 42: // Template 3.42 + case 43: // Template 3.43 writeLatLongGrid(); break; - case 90: + case 90: // Template 3.90 writeSpaceOrOrthographicGrid(); break; - case 204: + case 204: // Template 3.204 writeCurvilinearGrid(); break; } } + /** + * Write the record's header as a Json object: "header": { ... } + */ void writeHeader() { jg.writeStartObject("header"); writeIndicator(); @@ -278,6 +316,9 @@ void writeHeader() { jg.writeEnd(); } + /** + * Write the record's data as a Json array: "data": [ ... ] + */ void writeData(Grib2Data gd) throws IOException { float[] data = gd.getData(record.getGdsOffset(), record.getPdsOffset(), ids.getRefTime()); if (data != null) {