Skip to content

Commit 2e7f524

Browse files
Add Grib 41
1 parent 1ff0c95 commit 2e7f524

4 files changed

Lines changed: 217 additions & 0 deletions

File tree

grib/src/main/java/ucar/nc2/grib/grib2/Grib2Pds.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public static Grib2Pds factory(int template, byte[] input) {
6868
return new Grib2Pds32(input);
6969
case 40:
7070
return new Grib2Pds40(input);
71+
case 41:
72+
return new Grib2Pds41(input);
7173
case 48:
7274
return new Grib2Pds48(input);
7375
case 60:
@@ -1698,6 +1700,65 @@ public int templateLength() {
16981700

16991701
///////////////////////////////////////////////////////////////////////////////
17001702

1703+
/*
1704+
* Product definition template 4.41 – individual ensemble forecast, control and perturbed, for atmospheric
1705+
* constituents
1706+
* Octet No. Contents
1707+
* 10 Parameter category (see Code table 4.1)
1708+
* 11 Parameter number (see Code table 4.2)
1709+
* 12–13 Constituent Type (see Code Table 4.230)
1710+
* 14 Type of Generating Process (see Code table 4.3)
1711+
* 15 Background Process
1712+
* 16 Generating Process Identifier
1713+
* 17–18 Hours of observational data cut-off after reference time (see Note)
1714+
* 19 Minutes of observational data cut-off after reference time
1715+
* 20 Indicator of unit of time range (see Code table 4.4)
1716+
* 21-24 Forecast time in units defined by octet 18
1717+
* 25 Type of first fixed surface (see Code table 4.5)
1718+
* 26 Scale factor of first fixed surface
1719+
* 27–30 Scaled value of first fixed surface
1720+
* 31 Type of second fixed surface (see Code table 4.5)
1721+
* 32 Scale factor of second fixed surface
1722+
* 33-36 Scaled value of second fixed surface
1723+
* 37 Type of ensemble forecast (see Code table 4.6)
1724+
* 38 Perturbation number
1725+
* 39 Number of forecasts in ensemble
1726+
* Note: Hours greater than 65534 will be coded as 65534.
1727+
*/
1728+
1729+
private static class Grib2Pds41 extends Grib2Pds40 implements PdsEnsemble {
1730+
1731+
Grib2Pds41(byte[] input) {
1732+
super(input);
1733+
}
1734+
1735+
public boolean isEnsemble() {
1736+
return true;
1737+
}
1738+
1739+
/* Type of ensemble forecast (see Code table 4.6) */
1740+
public int getPerturbationType() {
1741+
return getOctet(37);
1742+
}
1743+
1744+
/* Perturbation Ensemble Member Number */
1745+
public int getPerturbationNumber() {
1746+
return getOctet(38);
1747+
}
1748+
1749+
/* Number of forecasts in ensemble */
1750+
public int getNumberEnsembleForecasts() {
1751+
return getOctet(39);
1752+
}
1753+
1754+
@Override
1755+
public int templateLength() {
1756+
return 39;
1757+
}
1758+
}
1759+
1760+
///////////////////////////////////////////////////////////////////////////////
1761+
17011762
/*
17021763
* Product definition template 4.48 – analysis or forecast at a horizontal level or in a horizontal layer at a point
17031764
* in time for optical properties of aerosol
Binary file not shown.
Binary file not shown.
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright (c) 2026 University Corporation for Atmospheric Research/Unidata
3+
* See LICENSE for license information.
4+
*/
5+
6+
package ucar.nc2.grib.grib2;
7+
8+
import static com.google.common.truth.Truth.assertThat;
9+
10+
import java.io.IOException;
11+
import java.util.List;
12+
import org.junit.Before;
13+
import org.junit.Test;
14+
import org.junit.runner.RunWith;
15+
import org.junit.runners.JUnit4;
16+
17+
/**
18+
* Test data from github issue https://github.com/Unidata/netcdf-java/issues/41
19+
* Data was used to create a .gbx9 file for testing.
20+
* <p>
21+
* PDS (Secton 4) output from ecCodes grib_dump at the end of the file, uses as a basis for
22+
* testing PDS (Secton 4) parsing
23+
*/
24+
@RunWith(JUnit4.class)
25+
public class TestPds41 {
26+
27+
private Grib2Pds pds;
28+
29+
@Before
30+
public void openTestFile() throws IOException {
31+
String testfile = "../grib/src/test/data/index/icon-ch2-eps-202606230000-0-poacsnc-ctrl.grib2.gbx9";
32+
33+
Grib2Index gi = new Grib2Index();
34+
boolean success = gi.readIndex(testfile, -1);
35+
assertThat(success).isTrue();
36+
List<Grib2Record> records = gi.getRecords();
37+
Grib2Record record = records.get(0);
38+
pds = record.getPDS();
39+
}
40+
41+
@Test
42+
public void testPdsBasic() {
43+
assertThat(pds.getRawLength()).isEqualTo(63);
44+
assertThat(pds.getTemplateNumber()).isEqualTo(41);
45+
}
46+
47+
// check overrides
48+
@Test
49+
public void testGenProcessType() {
50+
assertThat(pds.getGenProcessType()).isEqualTo(4);;
51+
}
52+
53+
@Test
54+
public void testBackProcessId() {
55+
assertThat(pds.getBackProcessId()).isEqualTo(0);;
56+
}
57+
58+
@Test
59+
public void testGenProcessId() {
60+
assertThat(pds.getGenProcessId()).isEqualTo(142);;
61+
}
62+
63+
@Test
64+
public void testTimeUnit() {
65+
assertThat(pds.getTimeUnit()).isEqualTo(0);
66+
}
67+
68+
@Test
69+
public void testLevelType1() {
70+
assertThat(pds.getLevelType1()).isEqualTo(150);
71+
}
72+
73+
@Test
74+
public void testLevelScale1() {
75+
assertThat(pds.getLevelScale1()).isEqualTo(0);
76+
}
77+
78+
@Test
79+
public void testLevelValue1() {
80+
assertThat(pds.getLevelValue1()).isEqualTo(80);
81+
}
82+
83+
@Test
84+
public void testLevelType2() {
85+
assertThat(pds.getLevelType2()).isEqualTo(150);
86+
}
87+
88+
@Test
89+
public void testLevelScale2() {
90+
assertThat(pds.getLevelScale2()).isEqualTo(0);
91+
}
92+
93+
@Test
94+
public void testLevelValue2() {
95+
assertThat(pds.getLevelValue2()).isEqualTo(81);
96+
}
97+
98+
@Test
99+
public void testTemplateLength() {
100+
assertThat(pds.templateLength()).isEqualTo(39);
101+
}
102+
103+
@Test
104+
public void testIsEnsemble() {
105+
assertThat(pds.isEnsemble()).isTrue();
106+
}
107+
108+
@Test
109+
public void testPerturbationType() {
110+
assertThat(((Grib2Pds.PdsEnsemble) pds).getPerturbationType()).isEqualTo(192);
111+
}
112+
113+
@Test
114+
public void testPerturbationNumber() {
115+
assertThat(((Grib2Pds.PdsEnsemble) pds).getPerturbationNumber()).isEqualTo(0);
116+
}
117+
118+
@Test
119+
public void testNumberEnsembleForecasts() {
120+
assertThat(((Grib2Pds.PdsEnsemble) pds).getNumberEnsembleForecasts()).isEqualTo(21);
121+
}
122+
}
123+
124+
// grib_dump -O icon-ch2-eps-202606230000-0-poacsnc-ctrl.grib2
125+
// ***** FILE: icon-ch2-eps-202606230000-0-poacsnc-ctrl.grib2
126+
// ...
127+
// ====================== SECTION_4 ( length=63, padding=0 ) ======================
128+
// 1-4 section4Length = 63
129+
// 5 numberOfSection = 4
130+
// 6-7 NV = 6
131+
// 8-9 productDefinitionTemplateNumber = 41 [Individual ensemble forecast, control and perturbed, at a horizontal level
132+
// or in a horizontal layer at a point in time for atmospheric chemical constituents (grib2/tables/15/4.0.table) ]
133+
// 10 parameterCategory = 20 [Atmospheric chemical constituents (grib2/tables/15/4.1.0.table) ]
134+
// 11 parameterNumber = 60 [Unknown code table entry (grib2/tables/15/4.2.0.20.table) ]
135+
// 12-13 constituentType = 62300 [Unknown code table entry (grib2/tables/15/4.230.table) ]
136+
// 14 typeOfGeneratingProcess = 4 [Ensemble forecast (grib2/tables/15/4.3.table) ]
137+
// 15 backgroundProcess = 0
138+
// 16 generatingProcessIdentifier = 142
139+
// 17-18 hoursAfterDataCutoff = 0
140+
// 19 minutesAfterDataCutoff = 0
141+
// 20 indicatorOfUnitForForecastTime = 0 [Minute (grib2/tables/15/4.4.table) ]
142+
// 21-24 forecastTime = 0
143+
// 25 typeOfFirstFixedSurface = 150 [Generalized vertical height coordinate (grib2/tables/15/4.5.table) ]
144+
// 26 scaleFactorOfFirstFixedSurface = 0
145+
// 27-30 scaledValueOfFirstFixedSurface = 80
146+
// 31 typeOfSecondFixedSurface = 150 [Generalized vertical height coordinate (grib2/tables/15/4.5.table) ]
147+
// 32 scaleFactorOfSecondFixedSurface = 0
148+
// 33-36 scaledValueOfSecondFixedSurface = 81
149+
// 37 typeOfEnsembleForecast = 192 [Unknown code table entry (grib2/tables/15/4.6.table) ]
150+
// 38 perturbationNumber = 0
151+
// 39 numberOfForecastsInEnsemble = 21
152+
// 40-43 nlev = 81
153+
// 44-47 numberOfVGridUsed = 4
154+
// 48-63 uuidOfVGrid = 16 {
155+
// 4d, 01, b8, 87, 1a, 1c, 80, 1c, 03, 16, f2, 80, f2, e6, 2c, c0
156+
// } # bytes uuidOfVGrid

0 commit comments

Comments
 (0)