/*
* Copyright (c) 1998 - 2011. University Corporation for Atmospheric Research/Unidata
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/**
*/
package ucar.nc2.grib.grib1;
import net.jcip.annotations.Immutable;
import ucar.nc2.grib.GribNumbers;
import ucar.nc2.grib.GribUtils;
import ucar.nc2.grib.grib1.tables.Grib1Customizer;
import ucar.nc2.grib.grib1.tables.Grib1ParamTableReader;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.wmo.CommonCodeTable;
import ucar.unidata.io.RandomAccessFile;
import java.io.IOException;
import java.util.Formatter;
import java.util.zip.CRC32;
/**
* The Product Definition Section for GRIB-1 files
*
* @author caron
*/
@Immutable
public final class Grib1SectionProductDefinition {
private final byte[] rawData;
/**
* Read Product Definition section from raf.
*
* @param raf RandomAccessFile, with pointer at start of section
* @throws java.io.IOException on I/O error
* @throws IllegalArgumentException if not a GRIB-2 record
*/
public Grib1SectionProductDefinition(RandomAccessFile raf) throws IOException {
int length = GribNumbers.uint3(raf);
rawData = new byte[length];
raf.skipBytes(-3);
raf.readFully(rawData);
}
/**
* Set PDS section from byte array.
*
* @param rawData the byte array
*/
public Grib1SectionProductDefinition(byte[] rawData) {
this.rawData = rawData;
}
/**
* get the raw bytes of the PDS.
*
* @return PDS as byte[]
*/
public byte[] getRawBytes() {
return rawData;
}
public int getLength() {
return GribNumbers.uint3(getOctet(1),getOctet(2),getOctet(3));
}
/**
* gets the Table version (octet 4).
*
* @return table version
*/
public final int getTableVersion() {
return getOctet(4);
}
/**
* Center (octet 5) common code C-1.
*
* @return center id
*/
public final int getCenter() {
return getOctet(5);
}
/**
* Generating Process (octet 6).
*
* @return typeGenProcess
*/
public final int getGenProcess() {
return getOctet(6);
}
/**
* Grid Definition (octet 7).
* "Number of grid used – from catalogue defined by originating centre". So this is center dependent.
* "Where octet 7 defines a catalogued grid, that grid should also be defined in Section 2, provided the flag in octet 8
* indicates inclusion of Section 2.
* Octet 7 must be set to 255 to indicate a non-catalogued grid, in which case the grid will be defined in Section 2."
*
* @return Grid Definition.
*/
public final int getGridDefinition() {
return getOctet(7);
}
/**
* Flag (octet 8).
*
* @return Flag
*/
public final int getFlag() {
return getOctet(8);
}
/**
* Parameter number (octet 9) - code table 2.
*
* @return index number of parameter in table
*/
public final int getParameterNumber() {
return getOctet(9);
}
/**
* Level type (octet 10) - code table 3.
*
* @return level type
*/
public final int getLevelType() {
return getOctet(10);
}
/**
* Level value1 (octet 11).
*
* @return level value1
*/
public final int getLevelValue1() {
return getOctet(11);
}
/**
* Level value2 (octet 12).
*
* @return level value2
*/
public final int getLevelValue2() {
return getOctet(12);
}
/**
* Reference Date (octet 13-17).
* Reference time of data – date and time of start of averaging or accumulation period.
*
* @return Reference Date as CalendarDate.
*/
public final CalendarDate getReferenceDate() {
int century = getReferenceCentury() - 1;
if (century == -1) century = 20;
int year = getOctet(13);
int month = getOctet(14);
int day = getOctet(15);
int hour = getOctet(16);
int minute = getOctet(17);
return CalendarDate.of(null, century * 100 + year, month, day, hour, minute, 0);
}
/**
* Time unit (octet 18) - code table 4. Same as GRIB2 Table 4.4
*
* @return time unit
*/
public final int getTimeUnit() {
return getOctet(18);
}
/**
* Time value 1 (octet 19).
* Period of time (number of time units) (0 for analyses or initialized analyses).
* Units of time given by octet 18
*
* @return time value 1
*/
public final int getTimeValue1() {
return getOctet(19);
}
/**
* Time value 2 (octet 20).
* Period of time (number of time units); or Time interval between successive analyses,
* initialized analyses or forecasts, undergoing averaging or accumulation.
* Units of time given by octet 18
*
* @return time value 2
*/
public final int getTimeValue2() {
return getOctet(20);
}
/**
* Time range indicator (octet 21) - code table 5.
*
* @return Time range indicator
*/
public final int getTimeRangeIndicator() {
return getOctet(21);
}
/**
* Number included in statistics (octet 22-23).
* Number included in calculation when octet 21 (Code table 5) refers to a statistical
* process, such as average or accumulation; otherwise set to zero
*
* @return Number included in statistics
*/
public final int getNincluded() {
return GribNumbers.int2(getOctet(22), getOctet(23));
}
/**
* Number missing in statistics (octet 24).
* Number missing from calculation in case of statistical process
*
* @return Number missing in statistics
*/
public final int getNmissing() {
return getOctet(24);
}
/**
* Century of reference (octet 25).
*
* @return Century of reference
*/
public final int getReferenceCentury() {
return getOctet(25);
}
/**
* Center (octet 26) common code C-12.
*
* @return subcenter id
*/
public final int getSubCenter() {
return getOctet(26);
}
/**
* Units decimal scale factor (octet 27-28) .
*
* @return Units decimal scale factor
*/
public final int getDecimalScale() {
return GribNumbers.int2(getOctet(27), getOctet(28));
}
/**
* Check if GDS exists from the flag.
*
* @return true, if GDS exists
*/
public final boolean gdsExists() {
return (getFlag() & 128) == 128;
}
/**
* Check if BMS exists from the flag
*
* @return true, if BMS exists
*/
public final boolean bmsExists() {
return (getFlag() & 64) == 64;
}
/**
* Get the indexth byte in the PDS as an integer.
* THIS IS ONE BASED (not zero) to correspond with the manual
*
* @param index 1 based index
* @return rawData[index-1] & 0xff
*/
private int getOctet(int index) {
if (index > rawData.length) return GribNumbers.UNDEFINED;
return rawData[index - 1] & 0xff;
}
/////////////////////////////////////////////////////////////////////
private String getCalendarPeriodAsString() {
try {
return GribUtils.getCalendarPeriod(getTimeUnit()).toString();
} catch (UnsupportedOperationException e) {
return "Unknown Time Unit";
}
}
public void showPds(Grib1Customizer cust, Formatter f) {
f.format("5 Originating Center : (%d) %s%n", getCenter(), CommonCodeTable.getCenterName(getCenter(), 1));
f.format("26 Originating SubCenter : (%d) %s%n", getSubCenter(), cust.getSubCenterName( getSubCenter()));
f.format("4 Table Version : %d%n", getTableVersion());
Grib1Parameter parameter = cust.getParameter(getCenter(), getSubCenter(), getTableVersion(), getParameterNumber());
if (parameter != null) {
Grib1ParamTableReader ptable = parameter.getTable();
f.format(" Parameter Table : (%d-%d-%d) %s%n", getCenter(), getSubCenter(), getTableVersion(), (ptable == null) ? "MISSING" : ptable.getPath());
f.format(" Parameter Name : (%d) %s%n", getParameterNumber(), parameter.getName());
f.format(" Parameter Desc : %s%n", parameter.getDescription());
f.format(" Parameter Units : %s%n", parameter.getUnit());
} else {
f.format(" Parameter %d not found%n", getParameterNumber());
}
f.format("6 Generating Process Type : (%d) %s%n", getGenProcess(), cust.getGeneratingProcessName(getGenProcess()));
f.format("7 Grid Definition : (%d) %n", getGridDefinition());
f.format("8 Flag : (%d) %n", getFlag());
f.format("13-17 Reference Time : %s%n", getReferenceDate());
f.format("18 Time Units : (%d) %s%n", getTimeUnit(), getCalendarPeriodAsString());
Grib1ParamTime ptime = cust.getParamTime(this);;
f.format("19 Time 1 (P1) : %d%n", getTimeValue1());
f.format("20 Time 2 (P2) : %d%n", getTimeValue2());
f.format("21 Time Range Indicator : (%d) %s%n", getTimeRangeIndicator(), ptime.getTimeTypeName());
f.format("22-23 N in statistic : (%d)%n", getNincluded());
f.format("24 N missing : (%d)%n", getNmissing());
f.format(" Time coord : %s%n", ptime.getTimeCoord());
Grib1ParamLevel plevel = cust.getParamLevel(this);
f.format("10 Level Type : (%d) %s%n", getLevelType(), plevel.getNameShort());
f.format(" Level Description : %s%n", plevel.getDescription());
f.format(" Level Value 1 : %f%n", plevel.getValue1());
f.format(" Level Value 2 : %f%n", plevel.getValue2());
f.format("27-28 Decimal Scale Factor : %d%n", getDecimalScale());
f.format(" GDS Exists : %s%n", gdsExists());
f.format(" BMS Exists : %s%n", bmsExists());
}
////////////////////////////////////////////////////////
// Ensembles
/* http://www.ecmwf.int/publications/manuals/d/gribapi/fm92/grib1/show/local/
1)
see " local definition # 1"
from Ernst de Vreede via Jitka 01/31/2012
ECMWF operational Ensemble Prediction System (EPS) have extra information in the PDS:
Octet 41 = 1: ECMWF local GRIB extension: 1 = Mars labelling or ensemble forecast data&
Octet 42 = 1: ECMWF GRIB class, 1=operational data
Octet 43 = 10/11: ECMWF GRIB type: 10=Control forecast, 11=Perturbed forecast
Octet 44,45 = 1035, ECMWF GRIB stream 1035 = Ensemble Prediction System
Octet 50 = perturbationNumber (0 for control, 1-50 for perturbed forecasts)
Octet 51 = numberOfForecastsInEnsemble (0 if not ensemble, 1-50 for perturbed forecasts)
The really distinguishing octets are: octet 41 =1, octet 42=1, octet 43=10 or 11, octet 51>0 with octet 50 giving the perturbation number.
The other parameters octets 42 (operational stream) and octet 44/45 might be too specific, might narrow down too much.
2)
http://www.ecmwf.int/publications/manuals/d/gribapi/fm92/grib1/detail/local/30/
*/
/*
* NCEP Appendix C Manual 388
* http://www.nco.ncep.noaa.gov/pmb/docs/on388/appendixc.html
* states that if the PDS is > 28 bytes and octet 41 == 1
* then it's an ensemble an product.
*/
public boolean isEnsemble() {
switch (getCenter()) {
case 7:
return ((rawData.length >= 43) && (getOctet(41) == 1));
case 98:
return ((rawData.length >= 51) &&
(getOctet(41) == 1 || getOctet(41) == 30) &&
(getOctet(43) == 10 || getOctet(43) == 11));
}
return false;
}
public final int getPerturbationType() {
if (!isEnsemble()) return GribNumbers.UNDEFINED;
switch (getCenter()) {
case 7: return getOctet(42);
case 98: return getOctet(43);
}
return GribNumbers.UNDEFINED;
}
public final int getPerturbationNumber() {
if (!isEnsemble()) return GribNumbers.UNDEFINED;
switch (getCenter()) {
case 7: {
int type = getOctet(42);
int id = getOctet(43);
if (type == 1) return 0;
if (type == 2) return id;
if (type == 3) return 5 + id;
}
case 98: return getOctet(50);
}
return GribNumbers.UNDEFINED;
}
public long calcCRC() {
CRC32 crc32 = new CRC32();
crc32.update(rawData);
return crc32.getValue();
}
}