diff --git a/grib/src/main/java/ucar/nc2/grib/grib2/Grib2Pds.java b/grib/src/main/java/ucar/nc2/grib/grib2/Grib2Pds.java index e652628f46..9cefd5865a 100644 --- a/grib/src/main/java/ucar/nc2/grib/grib2/Grib2Pds.java +++ b/grib/src/main/java/ucar/nc2/grib/grib2/Grib2Pds.java @@ -68,6 +68,8 @@ public static Grib2Pds factory(int template, byte[] input) { return new Grib2Pds32(input); case 40: return new Grib2Pds40(input); + case 41: + return new Grib2Pds41(input); case 48: return new Grib2Pds48(input); case 60: @@ -1698,6 +1700,65 @@ public int templateLength() { /////////////////////////////////////////////////////////////////////////////// + /* + * Product definition template 4.41 – individual ensemble forecast, control and perturbed, for atmospheric + * constituents + * Octet No. Contents + * 10 Parameter category (see Code table 4.1) + * 11 Parameter number (see Code table 4.2) + * 12–13 Constituent Type (see Code Table 4.230) + * 14 Type of Generating Process (see Code table 4.3) + * 15 Background Process + * 16 Generating Process Identifier + * 17–18 Hours of observational data cut-off after reference time (see Note) + * 19 Minutes of observational data cut-off after reference time + * 20 Indicator of unit of time range (see Code table 4.4) + * 21-24 Forecast time in units defined by octet 18 + * 25 Type of first fixed surface (see Code table 4.5) + * 26 Scale factor of first fixed surface + * 27–30 Scaled value of first fixed surface + * 31 Type of second fixed surface (see Code table 4.5) + * 32 Scale factor of second fixed surface + * 33-36 Scaled value of second fixed surface + * 37 Type of ensemble forecast (see Code table 4.6) + * 38 Perturbation number + * 39 Number of forecasts in ensemble + * Note: Hours greater than 65534 will be coded as 65534. + */ + + private static class Grib2Pds41 extends Grib2Pds40 implements PdsEnsemble { + + Grib2Pds41(byte[] input) { + super(input); + } + + public boolean isEnsemble() { + return true; + } + + /* Type of ensemble forecast (see Code table 4.6) */ + public int getPerturbationType() { + return getOctet(37); + } + + /* Perturbation Ensemble Member Number */ + public int getPerturbationNumber() { + return getOctet(38); + } + + /* Number of forecasts in ensemble */ + public int getNumberEnsembleForecasts() { + return getOctet(39); + } + + @Override + public int templateLength() { + return 39; + } + } + + /////////////////////////////////////////////////////////////////////////////// + /* * Product definition template 4.48 – analysis or forecast at a horizontal level or in a horizontal layer at a point * in time for optical properties of aerosol diff --git a/grib/src/test/data/index/icon-ch2-eps-202606230000-0-poacsnc-ctrl.grib2.gbx9 b/grib/src/test/data/index/icon-ch2-eps-202606230000-0-poacsnc-ctrl.grib2.gbx9 new file mode 100644 index 0000000000..d6c6a8b57a Binary files /dev/null and b/grib/src/test/data/index/icon-ch2-eps-202606230000-0-poacsnc-ctrl.grib2.gbx9 differ diff --git a/grib/src/test/java/ucar/nc2/grib/grib2/TestPds41.java b/grib/src/test/java/ucar/nc2/grib/grib2/TestPds41.java new file mode 100644 index 0000000000..27cbc7c085 --- /dev/null +++ b/grib/src/test/java/ucar/nc2/grib/grib2/TestPds41.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2026 University Corporation for Atmospheric Research/Unidata + * See LICENSE for license information. + */ + +package ucar.nc2.grib.grib2; + +import static com.google.common.truth.Truth.assertThat; + +import java.io.IOException; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test data from Meteo Swiss CH2 https://data.geo.admin.ch/browser/#/collections/ch.meteoschweiz.ogd-forecasting-icon-ch2?.language=en + * Pollen Grasses Data (POACsnc parameter) was used to create a .gbx9 file for testing. + *
+ * PDS (Secton 4) output from ecCodes grib_dump at the end of the file, uses as a basis for
+ * testing PDS (Secton 4) parsing
+ */
+@RunWith(JUnit4.class)
+public class TestPds41 {
+
+ private Grib2Pds pds;
+
+ @Before
+ public void openTestFile() throws IOException {
+ String testfile = "../grib/src/test/data/index/icon-ch2-eps-202606230000-0-poacsnc-ctrl.grib2.gbx9";
+
+ Grib2Index gi = new Grib2Index();
+ boolean success = gi.readIndex(testfile, -1);
+ assertThat(success).isTrue();
+ List