├── .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 |
--------------------------------------------------------------------------------