Skip to content
This repository was archived by the owner on Sep 1, 2022. It is now read-only.

Commit 16349d6

Browse files
authored
Merge pull request #1337 from lesserwhirls/gini
Correctly handle GINI negative calibration values
2 parents 34784e6 + 21f138b commit 16349d6

File tree

2 files changed

+74
-2
lines changed

2 files changed

+74
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package ucar.nc2.iosp.gini;
2+
3+
import java.io.IOException;
4+
import org.junit.Assert;
5+
import org.junit.Test;
6+
import org.junit.experimental.categories.Category;
7+
import ucar.ma2.Array;
8+
import ucar.ma2.MAMath;
9+
import ucar.ma2.MAMath.MinMax;
10+
import ucar.nc2.NetcdfFile;
11+
import ucar.nc2.Variable;
12+
import ucar.unidata.util.test.TestDir;
13+
import ucar.unidata.util.test.category.NeedsCdmUnitTest;
14+
15+
@Category(NeedsCdmUnitTest.class)
16+
public class TestGiniNegativeCalibrationValues {
17+
18+
private static final double comparisonTolerance = 1e-6;
19+
20+
@Test
21+
public void testMinMaxValues() throws IOException {
22+
MinMax expectedMinMax = new MinMax(-30.0, 60.0);
23+
try (NetcdfFile ncfile =
24+
NetcdfFile.open(TestDir.cdmUnitTestDir + "formats/gini/images_sat_NEXRCOMP_1km_n0r_n0r_20200907_0740")) {
25+
Variable variable = ncfile.findVariable("Reflectivity");
26+
Array array = variable.read();
27+
MinMax minMax = MAMath.getMinMax(array);
28+
// If the bug reported in https://github.com/Unidata/netcdf-java/issues/480 shows up, and we are
29+
// incorrectly reading the calibration coefficients from the GINI file headers "Unidata cal block"
30+
// (in the case of the original bug, we were decoding negative numbers as intended), the data values
31+
// will be on the order of -3.9 x 10^5 for the minimum, and -2.1 x 10^5 for the maximum. Those
32+
// values (radar reflectivity) should be -30 dBZ to 60 dBZ.
33+
Assert.assertTrue(Math.abs(minMax.min - expectedMinMax.min) < comparisonTolerance);
34+
Assert.assertTrue(Math.abs(minMax.max - expectedMinMax.max) < comparisonTolerance);
35+
}
36+
}
37+
}

clcommon/src/main/java/ucar/nc2/iosp/gini/Giniheader.java

+37-2
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,19 @@
2828
*/
2929

3030
class Giniheader {
31+
// Unidata Gini table:
32+
// https://github.com/Unidata/gempak/blob/master/gempak/tables/unidata/nex2gini.tbl
33+
// Special instructions for handling negative numbers
34+
// https://github.com/Unidata/gempak/blob/master/gempak/source/gemlib/mv/Linux/mvitob.c
35+
//
36+
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Giniheader.class);
37+
3138
static private final int GINI_PIB_LEN = 21; // gini product identification block
3239
static private final int GINI_PDB_LEN = 512; // gini product description block
3340
static private final int GINI_HED_LEN = GINI_PDB_LEN + GINI_PIB_LEN; // gini product header
3441
static private final double DEG_TO_RAD = 0.017453292;
3542
private boolean debug = false;
3643
private ucar.nc2.NetcdfFile ncfile;
37-
static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Giniheader.class);
3844
int dataStart = 0; // where the data starts
3945
protected int Z_type = 0;
4046

@@ -379,7 +385,7 @@ void read(ucar.unidata.io.RandomAccessFile raf, ucar.nc2.NetcdfFile ncfile) thro
379385
break;
380386

381387
default:
382-
System.out.println("unimplemented projection");
388+
log.warn("GINI: Unimplemented projection!");
383389
}
384390

385391
this.ncfile.addAttribute(null, new Attribute("title", gini_GetEntityID(ent_id)));
@@ -492,6 +498,7 @@ void read(ucar.unidata.io.RandomAccessFile raf, ucar.nc2.NetcdfFile ncfile) thro
492498
xaxis.setDataType(DataType.DOUBLE);
493499
xaxis.setDimensions("x");
494500
xaxis.addAttribute(new Attribute(CDM.LONG_NAME, "projection x coordinate"));
501+
xaxis.addAttribute(new Attribute(CF.STANDARD_NAME, "projection_x_coordinate"));
495502
xaxis.addAttribute(new Attribute(CDM.UNITS, "km"));
496503
xaxis.addAttribute(new Attribute(_Coordinate.AxisType, "GeoX"));
497504
double[] data = new double[nx];
@@ -520,6 +527,7 @@ void read(ucar.unidata.io.RandomAccessFile raf, ucar.nc2.NetcdfFile ncfile) thro
520527
yaxis.setDataType(DataType.DOUBLE);
521528
yaxis.setDimensions("y");
522529
yaxis.addAttribute(new Attribute(CDM.LONG_NAME, "projection y coordinate"));
530+
yaxis.addAttribute(new Attribute(CF.STANDARD_NAME, "projection_y_coordinate"));
523531
yaxis.addAttribute(new Attribute(CDM.UNITS, "km"));
524532
yaxis.addAttribute(new Attribute(_Coordinate.AxisType, "GeoY"));
525533
data = new double[ny];
@@ -556,6 +564,8 @@ void read(ucar.unidata.io.RandomAccessFile raf, ucar.nc2.NetcdfFile ncfile) thro
556564
ncfile.addVariable(null, ct);
557565
ncfile.addAttribute(null, new Attribute(CDM.CONVENTIONS, _Coordinate.Convention));
558566

567+
var.addAttribute(new Attribute(CF.GRID_MAPPING, ct.getFullName()));
568+
559569
// finish
560570
ncfile.finish();
561571
}
@@ -581,10 +591,35 @@ int[] getCalibrationInfo(ByteBuffer bos, int phys_elem, int ent_id) {
581591
for (int i = 0; i < calcod; i++) {
582592

583593
bos.position(56 + i * 16);
594+
// We expect these to always be positive, so just read the int as-is
584595
int minb = bos.getInt() / 10000; /* min brightness values */
585596
int maxb = bos.getInt() / 10000; /* max brightness values */
597+
598+
// Since these are data values, we need to consider the case of them being positive or negative
599+
// Careful though, it's not that easy. Step one is to read the 32-bits like before...
586600
int mind = bos.getInt(); /* min data values */
601+
// Now for the fun.
602+
// According to https://github.com/Unidata/gempak/blob/master/gempak/source/gemlib/mv/Linux/mvitob.c,
603+
// which is used when writing gini files, if the first bit is set, that indicates that we have a negative
604+
// value and need to do a bit more work.
605+
// Check if left-most bit is 1. Right shift bit pattern of value 31 times. This will make all bits 0, or
606+
// all bits 1. If all 0, then the result will equal zero. If all 1's, then the result will equal -1, and
607+
// that's how we know we need to do more work.
608+
if ((mind >> 31) == -1) {
609+
// Set the first bit to zero, and negate the value (again, described in mvitob.c from gempak)
610+
// 0x7FFFFFFF -> left-most bit is zero, all the rest are 1's.
611+
// The bit-wise & results in flipping the first bit of "mind" (because we know it is 1 at this point, and
612+
// 1 & 0 -> 0) while retaining the rest of the pattern of mind (because 0 & 1 -> 0, 1 & 1 -> 1).
613+
// To negate, just use the negative sign...we could do ~(mind & 0x7FFFFFFF) + 1, but let's not hang out
614+
// in bit operator land longer than we need to.
615+
mind = -(mind & 0x7FFFFFFF);
616+
}
617+
618+
// same thing as above
587619
int maxd = bos.getInt(); /* max data values */
620+
if ((maxd >> 31) == -1) {
621+
maxd = -(maxd & 0x7FFFFFFF);
622+
}
588623

589624
int idscal = 1;
590625
while (mind % idscal == 0 && maxd % idscal == 0) {

0 commit comments

Comments
 (0)