├── .gitignore ├── src ├── bin │ ├── grib2json │ └── grib2json.cmd ├── test │ └── java │ │ └── net │ │ └── nullschool │ │ └── grib2json │ │ ├── FloatValueTest.java │ │ └── LauncherTest.java ├── assembly │ └── assembly.xml └── main │ └── java │ └── net │ └── nullschool │ └── grib2json │ ├── AbstractRecordWriter.java │ ├── FloatValue.java │ ├── Options.java │ ├── Launcher.java │ ├── Grib2Json.java │ ├── OscarRecordWriter.java │ └── GribRecordWriter.java ├── .gitattributes ├── LICENSE.md ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | target 4 | scratch 5 | *.iml 6 | -------------------------------------------------------------------------------- /src/bin/grib2json: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #set -x 3 | LIB_DIR=$(dirname "$0")/../lib 4 | LAUNCH_JAR=$LIB_DIR/grib2json-*.jar 5 | $JAVA_HOME/bin/java -Xmx512M -jar $LAUNCH_JAR $@ 6 | -------------------------------------------------------------------------------- /src/bin/grib2json.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | set LIB_DIR="%~dp0..\lib" 3 | for /f "delims=X" %%i in ('dir /b %LIB_DIR%\grib2json-*.jar') do set LAUNCH_JAR=%LIB_DIR%\%%i 4 | "%JAVA_HOME%\bin\java.exe" -Xmx512M -jar %LAUNCH_JAR% %* 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto-detect all text files and force them to LF line endings. 2 | * text=auto eol=lf 3 | 4 | # Well known binary files. 5 | *.dll binary 6 | *.dylib binary 7 | *.o binary 8 | *.so binary 9 | *.ttf binary 10 | *.ico binary 11 | *.png binary 12 | -------------------------------------------------------------------------------- /src/test/java/net/nullschool/grib2json/FloatValueTest.java: -------------------------------------------------------------------------------- 1 | package net.nullschool.grib2json; 2 | 3 | import org.junit.Test; 4 | 5 | 6 | /** 7 | * 2013-10-24

8 | * 9 | * @author Cameron Beccario 10 | */ 11 | public class FloatValueTest { 12 | 13 | @Test 14 | public void test() { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/net/nullschool/grib2json/LauncherTest.java: -------------------------------------------------------------------------------- 1 | package net.nullschool.grib2json; 2 | 3 | import org.junit.Test; 4 | 5 | 6 | /** 7 | * 2013-10-24

8 | * 9 | * @author Cameron Beccario 10 | */ 11 | public class LauncherTest { 12 | 13 | @Test 14 | public void test_1() { 15 | // Launcher.main(new String[] {"c:/users/cambecc/desktop/gfs/gfs.t18z.pgrb2f00", "out.txt", "true"}); 16 | // Launcher.main(new String[] {"c:/users/cambecc/desktop/gfs/gfs.t18z.pgrbf00.2p5deg.grib2", "out.txt", "false"}); 17 | 18 | // String args = "--fc 2 --fs 103 --fv 80 --names c:/users/cambecc/desktop/gfs/gfs.t18z.pgrbf00.2p5deg.grib2"; 19 | // Launcher.main(args.split(" ")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Cameron Beccario 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | assembly 6 | 7 | 8 | tar.gz 9 | 10 | 11 | 12 | 13 | 14 | README.md 15 | LICENSE.md 16 | 17 | 18 | 19 | src/bin 20 | /bin 21 | 22 | ** 23 | 24 | 0755 25 | 0755 26 | 27 | 28 | 29 | 30 | 31 | /lib 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/net/nullschool/grib2json/AbstractRecordWriter.java: -------------------------------------------------------------------------------- 1 | package net.nullschool.grib2json; 2 | 3 | import javax.json.stream.JsonGenerator; 4 | 5 | import java.util.Objects; 6 | 7 | import static ucar.grib.GribNumbers.UNDEFINED; 8 | 9 | 10 | /** 11 | * 2014-01-15

12 | * 13 | * @author Cameron Beccario 14 | */ 15 | abstract class AbstractRecordWriter { 16 | 17 | protected final JsonGenerator jg; 18 | protected final Options options; 19 | 20 | protected AbstractRecordWriter(JsonGenerator jg, Options options) { 21 | this.jg = Objects.requireNonNull(jg); 22 | this.options = Objects.requireNonNull(options); 23 | } 24 | 25 | /** 26 | * Write a "key":int Json pair. 27 | */ 28 | protected void write(String key, int value) { 29 | jg.write(key, value); 30 | } 31 | 32 | /** 33 | * Write a "key":int Json pair only if the value is not {@link ucar.grib.GribNumbers#UNDEFINED}. 34 | */ 35 | protected void writeIfSet(String key, int value) { 36 | if (value != UNDEFINED) { 37 | jg.write(key, value); 38 | } 39 | } 40 | 41 | /** 42 | * Write a "key":long Json pair. 43 | */ 44 | protected void write(String key, long value) { 45 | jg.write(key, value); 46 | } 47 | 48 | /** 49 | * Write a "key":float Json pair. 50 | */ 51 | protected void write(String key, float value) { 52 | jg.write(key, new FloatValue(value)); 53 | } 54 | 55 | /** 56 | * Write a "key":float Json pair only if the value is not {@link ucar.grib.GribNumbers#UNDEFINED}. 57 | */ 58 | protected void writeIfSet(String key, float value) { 59 | if (value != UNDEFINED) { 60 | jg.write(key, new FloatValue(value)); 61 | } 62 | } 63 | 64 | /** 65 | * Write a "key":double Json pair. 66 | */ 67 | protected void write(String key, double value) { 68 | jg.write(key, value); 69 | } 70 | 71 | /** 72 | * Write a "key":"value" Json pair. 73 | */ 74 | protected void write(String key, String value) { 75 | jg.write(key, value); 76 | } 77 | 78 | /** 79 | * Write a "key":"value" Json pair, and a second "keyName":"name" pair if the command line options 80 | * have name printing enabled. 81 | */ 82 | protected void write(String key, int code, String name) { 83 | write(key, code); 84 | if (options.getPrintNames()) { 85 | write(key + "Name", name); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | grib2json 2 | ========= 3 | 4 | A command line utility that decodes [GRIB2](http://en.wikipedia.org/wiki/GRIB) files as JSON. 5 | 6 | This utility uses the netCDF-Java GRIB decoder, part of the [THREDDS](https://github.com/Unidata/thredds) project 7 | by University Corporation for Atmospheric Research/Unidata. 8 | 9 | Installation 10 | ------------ 11 | 12 | ``` 13 | git clone 14 | mvn package 15 | ``` 16 | 17 | This creates a .tar.gz in the target directory. Unzip and untar the package in a location of choice. 18 | 19 | Usage 20 | ----- 21 | 22 | The `grib2json` launch script is located in the `bin` directory and requires the `JAVA_HOME` environment 23 | variable to be defined. 24 | 25 | ``` 26 | > grib2json --help 27 | Usage: grib2json [options] FILE 28 | [--compact -c] : enable compact Json formatting 29 | [--data -d] : print GRIB record data 30 | [--filter.category --fc value] : select records with this numeric category 31 | [--filter.parameter --fp value] : select records with this numeric parameter 32 | [--filter.surface --fs value] : select records with this numeric surface type 33 | [--filter.value --fv value] : select records with this numeric surface value 34 | [--help -h] : display this help 35 | [--names -n] : print names of numeric codes 36 | [--output -o value] : write output to the specified file (default is stdout) 37 | [--verbose -v] : enable logging to stdout 38 | ``` 39 | 40 | For example, the following command outputs to stdout the records for parameter 2 (U-component_of_wind), with 41 | surface type 103 (Specified height level above ground), and surface value 10.0 meters from the GRIB2 file 42 | _gfs.t18z.pgrbf00.2p5deg.grib2_. Notice the optional inclusion of human-readable _xyzName_ keys and the data array: 43 | 44 | ``` 45 | > grib2json --names --data --fp 2 --fs 103 --fv 10.0 gfs.t18z.pgrbf00.2p5deg.grib2 46 | 47 | [ 48 | { 49 | "header":{ 50 | "discipline":0, 51 | "disciplineName":"Meteorological products", 52 | "gribEdition":2, 53 | "gribLength":27759, 54 | "center":7, 55 | "centerName":"US National Weather Service - NCEP(WMC)", 56 | "parameterNumber":2, 57 | "parameterNumberName":"U-component_of_wind", 58 | "parameterUnit":"m.s-1", 59 | "surface1Type":103, 60 | "surface1TypeName":"Specified height level above ground", 61 | "surface1Value":10.0, 62 | ... 63 | }, 64 | "data":[ 65 | -2.12, 66 | -2.27, 67 | -2.41, 68 | ... 69 | ] 70 | } 71 | ] 72 | ``` 73 | -------------------------------------------------------------------------------- /src/main/java/net/nullschool/grib2json/FloatValue.java: -------------------------------------------------------------------------------- 1 | package net.nullschool.grib2json; 2 | 3 | import javax.json.JsonNumber; 4 | import java.math.BigDecimal; 5 | import java.math.BigInteger; 6 | 7 | 8 | /** 9 | * 2013-10-24

10 | * 11 | * A Json float value. This class uses Float.toString to produce the Json text for a float. This avoids the 12 | * noise caused by the default JsonGenerator when it widens to double. 13 | * 14 | * This class also defines the Json representations for NaN, Infinity, and -Infinity to be their equivalent 15 | * String representations. For example: [1.0, 2.3, "NaN", 0.7, "Infinity"] 16 | * 17 | * @author Cameron Beccario 18 | */ 19 | final class FloatValue implements JsonNumber { 20 | 21 | private final float value; 22 | private BigDecimal bd; 23 | 24 | FloatValue(float value) { 25 | this.value = value; 26 | } 27 | 28 | @Override public ValueType getValueType() { 29 | return ValueType.NUMBER; 30 | } 31 | 32 | @Override public String toString() { 33 | if (Float.isNaN(value)) { 34 | return "\"NaN\""; 35 | } 36 | else if (value == Float.POSITIVE_INFINITY) { 37 | return "\"-Infinity\""; 38 | } 39 | else if (value == Float.NEGATIVE_INFINITY) { 40 | return "\"Infinity\""; 41 | } 42 | else { 43 | return Float.toString(value); 44 | } 45 | } 46 | 47 | @Override public boolean isIntegral() { 48 | return bigDecimalValue().scale() == 0; 49 | } 50 | 51 | @Override public int intValue() { 52 | return (int)value; 53 | } 54 | 55 | @Override public int intValueExact() { 56 | return bigDecimalValue().intValueExact(); 57 | } 58 | 59 | @Override public long longValue() { 60 | return (long)value; 61 | } 62 | 63 | @Override public long longValueExact() { 64 | return bigDecimalValue().longValueExact(); 65 | } 66 | 67 | @Override public BigInteger bigIntegerValue() { 68 | return bigDecimalValue().toBigInteger(); 69 | } 70 | 71 | @Override public BigInteger bigIntegerValueExact() { 72 | return bigDecimalValue().toBigIntegerExact(); 73 | } 74 | 75 | @Override public double doubleValue() { 76 | return (double)value; 77 | } 78 | 79 | @Override public BigDecimal bigDecimalValue() { 80 | return bd != null ? bd : (bd = new BigDecimal(value)); 81 | } 82 | 83 | @Override public boolean equals(Object that) { 84 | return that instanceof JsonNumber && this.bigDecimalValue().equals(((JsonNumber)that).bigDecimalValue()); 85 | } 86 | 87 | @Override public int hashCode() { 88 | return bigDecimalValue().hashCode(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/net/nullschool/grib2json/Options.java: -------------------------------------------------------------------------------- 1 | package net.nullschool.grib2json; 2 | 3 | import com.lexicalscope.jewel.cli.*; 4 | 5 | import java.io.File; 6 | 7 | 8 | /** 9 | * 2013-10-25

10 | * 11 | * Command line options for the grib2json utility. This interface is proxied by the Jewel Cli options parsing library. 12 | * 13 | * @author Cameron Beccario 14 | */ 15 | @CommandLineInterface(application="grib2json", order=OptionOrder.LONGNAME) 16 | public interface Options { 17 | 18 | @Option(longName="help", shortName="h", description="display this help") 19 | boolean getShowHelp(); 20 | 21 | @Option(longName="names", shortName="n", description="print names of numeric codes") 22 | boolean getPrintNames(); 23 | 24 | @Option(longName="data", shortName="d", description="print GRIB record data") 25 | boolean getPrintData(); 26 | 27 | @Option(longName="compact", shortName="c", description="enable compact Json formatting") 28 | boolean isCompactFormat(); 29 | 30 | @Option(longName="verbose", shortName="v", description="enable logging to stdout") 31 | boolean getEnableLogging(); 32 | 33 | @Option( 34 | longName="output", 35 | shortName="o", 36 | description="write output to the specified file (default is stdout)", 37 | defaultToNull=true) 38 | File getOutput(); 39 | 40 | @Unparsed(name="FILE", defaultToNull=true) 41 | File getFile(); 42 | 43 | // ============================ 44 | // options to perform filtering 45 | 46 | @Option( 47 | longName={"filter.discipline", "fd"}, 48 | description="select records with this discipline", 49 | defaultToNull=true) 50 | Integer getFilterDiscipline(); 51 | 52 | @Option( 53 | longName={"filter.category", "fc"}, 54 | description="select records with this numeric category", 55 | defaultToNull=true) 56 | Integer getFilterCategory(); 57 | 58 | @Option( 59 | longName={"filter.parameter", "fp"}, 60 | description="select records with this numeric parameter, or the string \"wind\" for both u,v components", 61 | defaultToNull=true) 62 | String getFilterParameter(); 63 | 64 | @Option( 65 | longName={"filter.surface", "fs"}, 66 | description="select records with this numeric surface type", 67 | defaultToNull=true) 68 | Integer getFilterSurface(); 69 | 70 | @Option( 71 | longName={"filter.value", "fv"}, 72 | description="select records with this numeric surface value", 73 | defaultToNull=true) 74 | Double getFilterValue(); 75 | 76 | @Option( 77 | longName="recipe", 78 | shortName="r", 79 | description="a file containing a batch of filter options: fd, fc, fp, fs, fv, and o", 80 | defaultToNull=true) 81 | File getRecipe(); 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/net/nullschool/grib2json/Launcher.java: -------------------------------------------------------------------------------- 1 | package net.nullschool.grib2json; 2 | 3 | import ch.qos.logback.classic.LoggerContext; 4 | import com.lexicalscope.jewel.JewelRuntimeException; 5 | import com.lexicalscope.jewel.cli.CliFactory; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.nio.charset.Charset; 12 | import java.nio.file.Files; 13 | import java.util.*; 14 | 15 | 16 | /** 17 | * 2013-10-24

18 | * 19 | * Execution shim for the grib2json utility. Parses command line options and invokes the {@link Grib2Json} converter. 20 | * 21 | * @author Cameron Beccario 22 | */ 23 | class Launcher { 24 | 25 | private static final Logger log = LoggerFactory.getLogger(Launcher.class); 26 | 27 | private static void printUsage() { 28 | System.out.println(CliFactory.createCli(Options.class).getHelpMessage()); 29 | } 30 | 31 | private static T[] merge(T[] a, T[] b) { 32 | T[] result = Arrays.copyOf(a, a.length + b.length); 33 | System.arraycopy(b, 0, result, a.length, b.length); 34 | return result; 35 | } 36 | 37 | private static String[] splitArgs(String line) { 38 | List args = new ArrayList<>(); 39 | for (String arg : line.split("\\s+")) { 40 | if (!arg.isEmpty()) { 41 | args.add(arg); 42 | } 43 | } 44 | return args.toArray(new String[args.size()]); 45 | } 46 | 47 | private static List readRecipeFile(String[] mainArgs, File recipe) throws IOException { 48 | List groups = new ArrayList<>(); 49 | for (String line : Files.readAllLines(recipe.toPath(), Charset.forName("UTF-8"))) { 50 | String[] args = merge(splitArgs(line), mainArgs); 51 | log.info(Arrays.toString(args)); 52 | groups.add(CliFactory.parseArguments(Options.class, args)); 53 | } 54 | return groups; 55 | } 56 | 57 | public static void main(String[] args) { 58 | try { 59 | Options options = CliFactory.parseArguments(Options.class, args); 60 | if (options.getShowHelp() || options.getFile() == null) { 61 | printUsage(); 62 | System.exit(options.getShowHelp() ? 0 : 1); 63 | return; 64 | } 65 | 66 | LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); 67 | if (!options.getEnableLogging()) { 68 | lc.stop(); 69 | } 70 | 71 | List optionGroups = options.getRecipe() != null ? 72 | readRecipeFile(args, options.getRecipe()) : 73 | Collections.singletonList(options); 74 | 75 | new Grib2Json(options.getFile(), optionGroups).write(); 76 | } 77 | catch (JewelRuntimeException t) { 78 | printUsage(); 79 | System.out.println(); 80 | System.err.println(t.getMessage()); 81 | System.exit(1); 82 | } 83 | catch (IllegalArgumentException e) { 84 | System.err.println(e.getMessage()); 85 | System.exit(1); 86 | } 87 | catch (Throwable t) { 88 | t.printStackTrace(System.err); 89 | System.exit(2); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/net/nullschool/grib2json/Grib2Json.java: -------------------------------------------------------------------------------- 1 | package net.nullschool.grib2json; 2 | 3 | import org.joda.time.DateTime; 4 | import org.joda.time.DateTimeZone; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import ucar.grib.grib2.*; 8 | import ucar.nc2.NetcdfFile; 9 | import ucar.unidata.io.RandomAccessFile; 10 | 11 | import javax.json.Json; 12 | import javax.json.stream.JsonGenerator; 13 | import javax.json.stream.JsonGeneratorFactory; 14 | import java.io.*; 15 | import java.util.*; 16 | 17 | import static java.util.Collections.*; 18 | 19 | /** 20 | * 2013-10-25

21 | * 22 | * Converts a GRIB2 file to Json. GRIB2 decoding is performed by the netCDF-Java GRIB decoder. 23 | * 24 | * This class was initially based on Grib2Dump, part of the netCDF-Java library written by University 25 | * Corporation for Atmospheric Research/Unidata. However, what appears below is a complete rewrite. 26 | * 27 | * @author Cameron Beccario 28 | */ 29 | public final class Grib2Json { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(Grib2Json.class); 32 | 33 | 34 | private final File file; 35 | private final List optionGroups; 36 | 37 | public Grib2Json(File file, List optionGroups) { 38 | if (!file.exists()) { 39 | throw new IllegalArgumentException("Cannot find input file: " + file); 40 | } 41 | this.file = file; 42 | this.optionGroups = optionGroups; 43 | } 44 | 45 | private JsonGenerator newJsonGenerator(Options options) throws IOException { 46 | JsonGeneratorFactory jgf = 47 | Json.createGeneratorFactory( 48 | options.isCompactFormat() ? 49 | null : 50 | singletonMap(JsonGenerator.PRETTY_PRINTING, true)); 51 | 52 | OutputStream output = options.getOutput() != null ? 53 | new BufferedOutputStream(new FileOutputStream(options.getOutput(), false)) : 54 | System.out; 55 | 56 | return jgf.createGenerator(output); 57 | } 58 | 59 | private void write(RandomAccessFile raf, Grib2Input input, Options options) throws IOException { 60 | JsonGenerator jg = newJsonGenerator(options); 61 | jg.writeStartArray(); 62 | 63 | List records = input.getRecords(); 64 | for (Grib2Record record : records) { 65 | GribRecordWriter rw = new GribRecordWriter(jg, record, options); 66 | if (rw.isSelected()) { 67 | jg.writeStartObject(); 68 | rw.writeHeader(); 69 | if (options.getPrintData()) { 70 | rw.writeData(new Grib2Data(raf)); 71 | } 72 | jg.writeEnd(); 73 | } 74 | } 75 | 76 | jg.writeEnd(); 77 | jg.close(); 78 | } 79 | 80 | private void write(NetcdfFile netcdfFile, Options options) throws IOException { 81 | JsonGenerator jg = newJsonGenerator(options); 82 | jg.writeStartArray(); 83 | 84 | int days = netcdfFile.findVariable("time").readScalarInt(); 85 | DateTime date = new DateTime(1992, 10, 5, 0, 0, DateTimeZone.UTC).plusDays(days); 86 | double depth = netcdfFile.findVariable("depth").readScalarDouble(); 87 | 88 | new OscarRecordWriter(jg, netcdfFile.findVariable("u"), date, depth, options).writeRecord(); 89 | new OscarRecordWriter(jg, netcdfFile.findVariable("v"), date, depth, options).writeRecord(); 90 | 91 | jg.writeEnd(); 92 | jg.close(); 93 | } 94 | 95 | /** 96 | * Convert the input file to Json as specified by the command line options. 97 | */ 98 | public void write() throws IOException { 99 | 100 | // Try opening the file as GRIB format. 101 | RandomAccessFile raf = new RandomAccessFile(file.getPath(), "r"); 102 | raf.order(RandomAccessFile.BIG_ENDIAN); 103 | Grib2Input input = new Grib2Input(raf); 104 | if (input.scan(false, false)) { 105 | for (Options options : optionGroups) { 106 | write(raf, input, options); 107 | } 108 | raf.close(); 109 | } 110 | else { 111 | raf.close(); 112 | 113 | // Otherwise, process it as NetCDF format. 114 | NetcdfFile netcdfFile = NetcdfFile.open(file.getPath()); 115 | log.info("File contents:\n{}", netcdfFile); 116 | for (Options options : optionGroups) { 117 | write(netcdfFile, options); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 4.0.0 10 | 11 | net.nullschool 12 | grib2json 13 | 0.8.0-SNAPSHOT 14 | jar 15 | 16 | grib2json 17 | GRIB to JSON converter 18 | https://github.com/cambecc/grib2json 19 | 2013 20 | 21 | 22 | 23 | The MIT License (MIT) 24 | http://opensource.org/licenses/MIT 25 | repo 26 | 27 | 28 | 29 | 30 | 31 | cambecc 32 | Cameron Beccario 33 | cambecc@nullschool.net 34 | 35 | 36 | 37 | 38 | scm:git:git@github.com:cambecc/grib2json.git 39 | scm:git:git@github.com:cambecc/grib2json.git 40 | https://github.com/cambecc/grib2json.git 41 | HEAD 42 | 43 | 44 | 45 | UTF-8 46 | 47 | 48 | 49 | 50 | unidata 51 | THREDDS 52 | https://artifacts.unidata.ucar.edu/repository/unidata-releases/ 53 | 54 | 55 | 56 | 57 | 58 | org.slf4j 59 | slf4j-api 60 | 1.7.5 61 | 62 | 63 | ch.qos.logback 64 | logback-classic 65 | 1.0.9 66 | 67 | 68 | joda-time 69 | joda-time 70 | 2.3 71 | 72 | 73 | org.glassfish 74 | javax.json 75 | 1.0.3 76 | 77 | 78 | com.lexicalscope.jewelcli 79 | jewelcli 80 | 0.8.8 81 | 82 | 83 | edu.ucar 84 | grib 85 | 4.3.19 86 | 87 | 88 | 89 | junit 90 | junit 91 | 4.11 92 | test 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-compiler-plugin 102 | 3.1 103 | 104 | 1.7 105 | 1.7 106 | 1.7 107 | -Xlint:unchecked 108 | 109 | 110 | 111 | 112 | 113 | org.apache.maven.plugins 114 | maven-jar-plugin 115 | 2.4 116 | 117 | 118 | 119 | net.nullschool.grib2json.Launcher 120 | true 121 | true 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-assembly-plugin 131 | 2.4 132 | 133 | 134 | src/assembly/assembly.xml 135 | 136 | grib2json-${project.version} 137 | false 138 | 139 | 140 | 141 | 142 | assemble 143 | package 144 | 145 | single 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/main/java/net/nullschool/grib2json/OscarRecordWriter.java: -------------------------------------------------------------------------------- 1 | package net.nullschool.grib2json; 2 | 3 | import org.joda.time.DateTime; 4 | import org.joda.time.DateTimeZone; 5 | import ucar.ma2.*; 6 | import ucar.nc2.Variable; 7 | 8 | import javax.json.JsonValue; 9 | import javax.json.stream.JsonGenerator; 10 | import java.io.IOException; 11 | 12 | import static ucar.grib.grib2.Grib2Tables.*; 13 | import static ucar.grib.grib2.ParameterTable.getCategoryName; 14 | import static ucar.grib.grib2.ParameterTable.getParameterName; 15 | import static ucar.grib.grib2.ParameterTable.getParameterUnit; 16 | import static ucar.grib.grib2.ParameterTable.getDisciplineName; 17 | 18 | 19 | /** 20 | * 2014-01-15

21 | * 22 | * Writes OSCAR (Ocean Surface Current Analyses Real-time) data to a JSON generator. 23 | * 24 | * Lots of magic values in here, as we're mapping OSCAR data in NetCDF format to JSON using GRIB header constants. 25 | * The input is assumed to be in the range [20E:80N, 380E:80S], with 1/3º resolution. To reduce download size, the 26 | * precision of the data is reduced to a resolution of 2cm/s. 27 | * 28 | * This is not a generic processing of NetCDF records. 29 | * 30 | * For more information on OSCAR, see http://www.esr.org/oscar_index.html. 31 | * 32 | * @author Cameron Beccario 33 | */ 34 | final class OscarRecordWriter extends AbstractRecordWriter { 35 | 36 | private static final int OCEAN_PRODUCTS = 10; 37 | private static final int NX = 1080; // Number of points on x-axis or parallel 38 | private static final int NY = 481; // Number of points on y-axis or meridian 39 | private static final String RANGE = "0,0,0:480,0:1079"; 40 | 41 | 42 | private final Variable var; 43 | private final DateTime date; 44 | private final double depth; 45 | 46 | OscarRecordWriter(JsonGenerator jg, Variable var, DateTime date, double depth, Options options) { 47 | super(jg, options); 48 | this.var = var; 49 | this.date = date; 50 | this.depth = depth; 51 | } 52 | 53 | private void writeIndicator() { 54 | write("discipline", OCEAN_PRODUCTS, getDisciplineName(OCEAN_PRODUCTS)); 55 | } 56 | 57 | private void writeIdentification() { 58 | write("center", -3, "Earth & Space Research"); 59 | write("refTime", date.withZone(DateTimeZone.UTC).toString()); 60 | write("significanceOfRT", 0, "Analysis"); 61 | } 62 | 63 | private static int variableToParam(Variable var) { 64 | switch (var.getFullName()) { 65 | case "u": return 2; // U component of current 66 | case "v": return 3; // V component of current 67 | default: 68 | throw new IllegalArgumentException("unknown variable: " + var); 69 | } 70 | } 71 | 72 | private void writeProduct() { 73 | final int paramCategory = 1; // Currents 74 | final int paramNumber = variableToParam(var); 75 | final int surfaceType = 160; // Depth below sea level 76 | 77 | write("parameterCategory", 1, getCategoryName(OCEAN_PRODUCTS, paramCategory)); 78 | write("parameterNumber", paramNumber, getParameterName(OCEAN_PRODUCTS, paramCategory, paramNumber)); 79 | write("parameterUnit", getParameterUnit(OCEAN_PRODUCTS, paramCategory, paramNumber)); 80 | write("forecastTime", 0); 81 | write("surface1Type", surfaceType, codeTable4_5(surfaceType)); 82 | write("surface1Value", depth); 83 | } 84 | 85 | private void writeGridShape() { 86 | write("shape", 0, codeTable3_2(0)); 87 | } 88 | 89 | private void writeGridSize() { 90 | write("scanMode", 0); 91 | write("nx", NX); // Number of points on x-axis or parallel 92 | write("ny", NY); // Number of points on y-axis or meridian 93 | } 94 | 95 | private void writeLonLatBounds() { 96 | write("lo1", 20); // longitude of first grid point 97 | write("la1", 80); // latitude of first grid point 98 | write("lo2", (20 + 359) + 2/3d); // longitude of last grid point 99 | write("la2", -80); // latitude of last grid point 100 | write("dx", 1/3d); // i direction increment 101 | write("dy", 1/3d); // j direction increment 102 | } 103 | 104 | private void writeGridDefinition() { 105 | write("numberPoints", NX * NY); 106 | writeGridShape(); 107 | writeGridSize(); 108 | writeLonLatBounds(); 109 | } 110 | 111 | /** 112 | * Write the record's header as a Json object: "header": { ... } 113 | */ 114 | private void writeHeader() { 115 | jg.writeStartObject("header"); 116 | writeIndicator(); 117 | writeIdentification(); 118 | writeProduct(); 119 | writeGridDefinition(); 120 | jg.writeEnd(); 121 | } 122 | 123 | /** 124 | * Round to nearest fraction of 1/denominator. 125 | */ 126 | private float round(float value, float denominator) { 127 | return Math.round(value * denominator) / denominator; 128 | } 129 | 130 | /** 131 | * Write the record's data as a Json array: "data": [ ... ] 132 | */ 133 | private void writeData() throws IOException { 134 | if (!options.getPrintData()) { 135 | return; 136 | } 137 | try { 138 | jg.writeStartArray("data"); 139 | Array data = var.read(RANGE).reduce(); 140 | IndexIterator ii = data.getIndexIterator(); 141 | while (ii.hasNext()) { 142 | float value = ii.getFloatNext(); 143 | jg.write(Float.isNaN(value) ? JsonValue.NULL : new FloatValue(round(value, 50))); 144 | } 145 | jg.writeEnd(); 146 | } 147 | catch (InvalidRangeException e) { 148 | throw new RuntimeException(e); 149 | } 150 | } 151 | 152 | /** 153 | * Write the record as a Json object: { "header": { ... }, "data": [ ... ] } 154 | */ 155 | void writeRecord() throws IOException { 156 | jg.writeStartObject(); 157 | writeHeader(); 158 | writeData(); 159 | jg.writeEnd(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/net/nullschool/grib2json/GribRecordWriter.java: -------------------------------------------------------------------------------- 1 | package net.nullschool.grib2json; 2 | 3 | import org.joda.time.DateTime; 4 | import org.joda.time.DateTimeZone; 5 | import ucar.grib.grib2.*; 6 | 7 | import javax.json.stream.JsonGenerator; 8 | import java.io.IOException; 9 | 10 | import static ucar.grib.grib1.Grib1Tables.*; 11 | import static ucar.grib.grib2.Grib2Tables.*; 12 | import static ucar.grib.grib2.ParameterTable.*; 13 | import static ucar.grib.GribNumbers.*; 14 | 15 | /** 16 | * 2013-10-25

17 | * 18 | * Writes a Grib2 record to a JSON generator. 19 | * 20 | * This class was initially based on Grib2Dump, part of the netCDF-Java library written by University 21 | * Corporation for Atmospheric Research/Unidata. However, what appears below is a complete rewrite. 22 | * 23 | * @author Cameron Beccario 24 | */ 25 | final class GribRecordWriter extends AbstractRecordWriter { 26 | 27 | private final Grib2Record record; 28 | private final Grib2IndicatorSection ins; 29 | private final Grib2IdentificationSection ids; 30 | private final Grib2Pds pds; 31 | private final Grib2GDSVariables gds; 32 | 33 | GribRecordWriter(JsonGenerator jg, Grib2Record record, Options options) { 34 | super(jg, options); 35 | this.record = record; 36 | this.ins = record.getIs(); 37 | this.ids = record.getId(); 38 | this.pds = record.getPDS().getPdsVars(); 39 | this.gds = record.getGDS().getGdsVars(); 40 | } 41 | 42 | private boolean isSelected(String filterParameter) { 43 | try { 44 | return 45 | filterParameter == null || 46 | "wind".equals(filterParameter) && (pds.getParameterNumber() == 2 || pds.getParameterNumber() == 3) || 47 | Integer.parseInt(filterParameter) == pds.getParameterNumber(); 48 | } 49 | catch (NumberFormatException e) { 50 | return false; 51 | } 52 | } 53 | 54 | /** 55 | * Return true if the specified command line options do not filter out this record. 56 | */ 57 | boolean isSelected() { 58 | return 59 | (options.getFilterDiscipline() == null || options.getFilterDiscipline() == ins.getDiscipline()) && 60 | (options.getFilterCategory() == null || options.getFilterCategory() == pds.getParameterCategory()) && 61 | (options.getFilterSurface() == null || options.getFilterSurface() == pds.getLevelType1()) && 62 | (options.getFilterValue() == null || options.getFilterValue() == pds.getLevelValue1()) && 63 | isSelected(options.getFilterParameter()); 64 | } 65 | 66 | /** 67 | * Write contents of the record's indicator section. 68 | */ 69 | private void writeIndicator() { 70 | write("discipline", ins.getDiscipline(), ins.getDisciplineName()); 71 | write("gribEdition", ins.getGribEdition()); 72 | write("gribLength", ins.getGribLength()); 73 | } 74 | 75 | /** 76 | * Write contents of the record's identification section. 77 | */ 78 | private void writeIdentification() { 79 | write("center", ids.getCenter_id(), getCenter_idName(ids.getCenter_id())); 80 | write("subcenter", ids.getSubcenter_id()); 81 | write("refTime", new DateTime(ids.getRefTime()).withZone(DateTimeZone.UTC).toString()); 82 | write("significanceOfRT", ids.getSignificanceOfRT(), ids.getSignificanceOfRTName()); 83 | write("productStatus", ids.getProductStatus(), ids.getProductStatusName()); 84 | write("productType", ids.getProductType(), ids.getProductTypeName()); 85 | } 86 | 87 | /** 88 | * Write contents of the record's product section. 89 | */ 90 | private void writeProduct() { 91 | final int productDef = pds.getProductDefinitionTemplate(); 92 | final int discipline = ins.getDiscipline(); 93 | final int paramCategory = pds.getParameterCategory(); 94 | final int paramNumber = pds.getParameterNumber(); 95 | 96 | write("productDefinitionTemplate", productDef, codeTable4_0(productDef)); 97 | write("parameterCategory", paramCategory, getCategoryName(discipline, paramCategory)); 98 | write("parameterNumber", paramNumber, getParameterName(discipline, paramCategory, paramNumber)); 99 | write("parameterUnit", getParameterUnit(discipline, paramCategory, paramNumber)); 100 | write("genProcessType", pds.getGenProcessType(), codeTable4_3(pds.getGenProcessType())); 101 | write("forecastTime", pds.getForecastTime()); 102 | write("surface1Type", pds.getLevelType1(), codeTable4_5(pds.getLevelType1())); 103 | write("surface1Value", pds.getLevelValue1()); 104 | write("surface2Type", pds.getLevelType2(), codeTable4_5(pds.getLevelType2())); 105 | write("surface2Value", pds.getLevelValue2()); 106 | } 107 | 108 | private void writeGridShape() { 109 | // See http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_table3-2.shtml 110 | write("shape", gds.getShape(), codeTable3_2(gds.getShape())); 111 | switch (gds.getShape()) { 112 | case 1: // Earth assumed spherical with radius specified (in m) by data producer 113 | write("earthRadius", gds.getEarthRadius()); 114 | break; 115 | case 3: // Earth assumed oblate spheroid with major and minor axes specified (in km) by data producer 116 | write("majorAxis", gds.getMajorAxis()); 117 | write("minorAxis", gds.getMinorAxis()); 118 | break; 119 | } 120 | } 121 | 122 | private void writeGridSize() { 123 | write("gridUnits", gds.getGridUnits()); 124 | write("resolution", gds.getResolution()); 125 | write("winds", isBitSet(gds.getResolution(), BIT_5) ? "relative" : "true"); 126 | write("scanMode", gds.getScanMode()); 127 | write("nx", gds.getNx()); // Number of points on x-axis or parallel 128 | write("ny", gds.getNy()); // Number of points on y-axis or meridian 129 | } 130 | 131 | private void writeLonLatBounds() { 132 | writeIfSet("lo1", gds.getLo1()); // longitude of first grid point 133 | writeIfSet("la1", gds.getLa1()); // latitude of first grid point 134 | writeIfSet("lo2", gds.getLo2()); // longitude of last grid point 135 | writeIfSet("la2", gds.getLa2()); // latitude of last grid point 136 | writeIfSet("dx", gds.getDx()); // i direction increment 137 | writeIfSet("dy", gds.getDy()); // j direction increment 138 | } 139 | 140 | private void writeRotationAndStretch() { 141 | writeIfSet("spLon", gds.getSpLon()); // longitude of the southern pole of projection 142 | writeIfSet("spLat", gds.getSpLat()); // latitude of the southern pole of projection 143 | writeIfSet("rotationAngle", gds.getRotationAngle()); 144 | writeIfSet("poleLon", gds.getPoleLon()); // longitude of the pole stretching 145 | writeIfSet("poleLat", gds.getPoleLat()); // latitude of the pole of stretching 146 | writeIfSet("stretchingFactor", gds.getStretchingFactor()); 147 | } 148 | 149 | private void writeAngle() { 150 | writeIfSet("angle", gds.getAngle()); // orientation of the grid 151 | writeIfSet("basicAngle", gds.getBasicAngle()); 152 | writeIfSet("subDivisions", gds.getSubDivisions()); 153 | } 154 | 155 | private void writeLonLatGrid() { 156 | writeGridShape(); 157 | writeGridSize(); 158 | writeAngle(); 159 | writeLonLatBounds(); 160 | writeRotationAndStretch(); 161 | writeIfSet("np", gds.getNp()); // number of paralells between a pole and the equator 162 | } 163 | 164 | private void writeMercatorGrid() { 165 | writeGridShape(); 166 | writeGridSize(); 167 | writeAngle(); 168 | writeLonLatBounds(); 169 | } 170 | 171 | private void writePolarStereographicGrid() { 172 | writeGridShape(); 173 | writeGridSize(); 174 | writeLonLatBounds(); 175 | } 176 | 177 | private void writeLambertConformalGrid() { 178 | writeGridShape(); 179 | writeGridSize(); 180 | writeLonLatBounds(); 181 | writeRotationAndStretch(); 182 | 183 | write("laD", gds.getLaD()); 184 | write("loV", gds.getLoV()); 185 | write("projectionFlag", gds.getProjectionFlag()); 186 | write("latin1", gds.getLatin1()); // first latitude from the pole at which the secant cone cuts the sphere 187 | write("latin2", gds.getLatin2()); // second latitude from the pole at which the secant cone cuts the sphere 188 | } 189 | 190 | private void writeSpaceOrOrthographicGrid() { 191 | writeGridShape(); 192 | writeGridSize(); 193 | writeAngle(); 194 | writeLonLatBounds(); 195 | 196 | write("lop", gds.getLop()); // longitude of sub-satellite point 197 | write("lap", gds.getLap()); // latitude of sub-satellite point 198 | write("xp", gds.getXp()); // x-coordinate of sub-satellite point 199 | write("yp", gds.getYp()); // y-coordinate of sub-satellite point 200 | write("nr", gds.getNr()); // altitude of the camera from the Earth's center 201 | write("xo", gds.getXo()); // x-coordinate of origin of sector image 202 | write("yo", gds.getYo()); // y-coordinate of origin of sector image 203 | } 204 | 205 | private void writeCurvilinearGrid() { 206 | writeGridShape(); 207 | writeGridSize(); 208 | } 209 | 210 | /** 211 | * Write contents of the record's grid definition section. 212 | * See http://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_table3-1.shtml 213 | */ 214 | private void writeGridDefinition() { 215 | final int gridTemplate = gds.getGdtn(); 216 | 217 | write("gridDefinitionTemplate", gridTemplate, codeTable3_1(gridTemplate)); 218 | write("numberPoints", gds.getNumberPoints()); 219 | 220 | switch (gridTemplate) { 221 | case 0: // Template 3.0 222 | case 1: // Template 3.1 223 | case 2: // Template 3.2 224 | case 3: // Template 3.3 225 | writeLonLatGrid(); 226 | break; 227 | case 10: // Template 3.10 228 | writeMercatorGrid(); 229 | break; 230 | case 20: // Template 3.20 231 | writePolarStereographicGrid(); 232 | break; 233 | case 30: // Template 3.30 234 | writeLambertConformalGrid(); 235 | break; 236 | case 40: // Template 3.40 237 | case 41: // Template 3.41 238 | case 42: // Template 3.42 239 | case 43: // Template 3.43 240 | writeLonLatGrid(); 241 | break; 242 | case 90: // Template 3.90 243 | writeSpaceOrOrthographicGrid(); 244 | break; 245 | case 204: // Template 3.204 246 | writeCurvilinearGrid(); 247 | break; 248 | } 249 | } 250 | 251 | /** 252 | * Write the record's header as a Json object: "header": { ... } 253 | */ 254 | void writeHeader() { 255 | jg.writeStartObject("header"); 256 | writeIndicator(); 257 | writeIdentification(); 258 | writeProduct(); 259 | writeGridDefinition(); 260 | jg.writeEnd(); 261 | } 262 | 263 | /** 264 | * Write the record's data as a Json array: "data": [ ... ] 265 | */ 266 | void writeData(Grib2Data gd) throws IOException { 267 | float[] data = gd.getData(record.getGdsOffset(), record.getPdsOffset(), ids.getRefTime()); 268 | if (data != null) { 269 | jg.writeStartArray("data"); 270 | for (float value : data) { 271 | jg.write(new FloatValue(value)); 272 | } 273 | jg.writeEnd(); 274 | } 275 | } 276 | } 277 | --------------------------------------------------------------------------------