/*
* Copyright 1998-2015 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.iosp.gempak;
import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.iosp.AbstractIOServiceProvider;
import ucar.nc2.util.CancelTask;
import ucar.unidata.io.RandomAccessFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
/**
* An IOSP for Gempak Station (SF,SN) data.
*
* @author dmurray
*/
public abstract class GempakStationFileIOSP extends AbstractIOServiceProvider {
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GempakStationFileIOSP.class);
/**
* Gempak file reader
*/
protected AbstractGempakStationFileReader gemreader;
/**
* place to store debug stuff
*/
protected StringBuilder parseInfo = new StringBuilder();
/**
* Float missing attribute
*/
protected final static Number RMISS = GempakConstants.RMISSD;
/**
* Integer missing attribute
*/
protected final static Number IMISS = GempakConstants.IMISSD;
/**
* static for shared dimension of length 4
*/
protected final static Dimension DIM_LEN8 = new Dimension("len8", 8, true);
/**
* static for shared dimension of length 4
*/
protected final static Dimension DIM_LEN4 = new Dimension("len4", 4, true);
/**
* static for shared dimension of length 2
*/
protected final static Dimension DIM_LEN2 = new Dimension("len2", 2, true);
/**
* name for the time variable
*/
protected final static String TIME_VAR = "time";
/**
* name for the time variable
*/
protected final static String MISSING_VAR = "_isMissing";
/**
* station variable names
*/
private static String[] stnVarNames = {
GempakStation.STID, GempakStation.STNM, GempakStation.SLAT,
GempakStation.SLON, GempakStation.SELV, GempakStation.STAT,
GempakStation.COUN, GempakStation.STD2, GempakStation.SPRI,
GempakStation.SWFO, GempakStation.WFO2
};
/**
* lengths of station variable
*/
private static int[] stnVarSizes = {
8, 4, 4, 4, 4, 2, 2, 4, 4, 4, 4
};
/**
* Is this a valid file?
*
* @param raf RandomAccessFile to check
* @return true if a valid Gempak grid file
* @throws IOException problem reading file
*/
@Override
public boolean isValidFile(RandomAccessFile raf) throws IOException {
try {
gemreader = makeStationReader();
return gemreader.init(raf, false);
} catch (Exception ioe) {
return false;
}
}
/**
* Make the appropriate station file reader, subclasses need to implement
* this
*
* @return the appropriate reader for that subclass
*/
protected abstract AbstractGempakStationFileReader makeStationReader();
/**
* Open the service provider for reading.
*
* @param raf file to read from
* @param ncfile netCDF file we are writing to (memory)
* @param cancelTask task for cancelling
* @throws IOException problem reading file
*/
@Override
public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
//System.out.printf("GempakSurfaceIOSP open %s (%s) %n", raf.getLocation(), Calendar.getInstance().getTime());
super.open(raf, ncfile, cancelTask);
if (gemreader == null) {
gemreader = makeStationReader();
}
initTables();
gemreader.init(raf, true);
buildNCFile();
}
/**
* Initialize the parameter tables.
*/
private void initTables() throws IOException {
GempakParameters.addParameters("resources/nj22/tables/gempak/params.tbl");
}
/**
* Get the detail information
*
* @return the detail info
*/
public String getDetailInfo() {
Formatter ff = new Formatter();
ff.format("%s", super.getDetailInfo());
ff.format("%s", parseInfo);
return ff.toString();
}
/*
* Sync the file
*
* @return true if needed to sync
* @throws IOException problem synching the file
*
public boolean sync() throws IOException {
//printStack("***************************** sync ************************", 100);
//System.out.printf("check sync on %s (%s) %n", raf.getLocation(), Calendar.getInstance().getTime());
if (gemreader.getInitFileSize() < raf.length()) {
long start = System.currentTimeMillis();
log.debug("GEMPAKStationIOSP.sync: file " + raf.getLocation()
+ " is bigger: " + raf.length() + " > "
+ gemreader.getInitFileSize());
gemreader.init(raf, true);
// reconstruct the ncfile objects
buildNCFile();
//System.out.printf("sync on %s took %d msecs%n", raf.getLocation(), (System.currentTimeMillis()-start));
return true;
}
return false;
} */
/**
* Build the netCDF file
*
* @throws IOException problem reading the file
*/
protected void buildNCFile() throws IOException {
ncfile.empty();
fillNCFile();
addGlobalAttributes();
ncfile.finish();
//System.out.println(ncfile);
}
/**
* Fill the contents of the netCDF file. Assumes that the file has been
* cleared.
*
* @throws IOException problem reading the file
*/
protected abstract void fillNCFile() throws IOException;
/**
* Make a structure for the part
*
* @param partName partname
* @param dimensions dimensions for the structure
* @param includeMissing true to include the missing variable
* @return a Structure
*/
protected Structure makeStructure(String partName, List<Dimension> dimensions, boolean includeMissing) {
List<GempakParameter> params = gemreader.getParameters(partName);
if (params == null) {
return null;
}
Structure sVar = new Structure(ncfile, null, null, partName);
sVar.setDimensions(dimensions);
for (GempakParameter param : params) {
sVar.addMemberVariable(makeParamVariable(param, null));
}
if (includeMissing) {
sVar.addMemberVariable(makeMissingVariable());
}
return sVar;
}
/**
* Make the missing variable
*
* @return the missing variable
*/
protected Variable makeMissingVariable() {
Variable var = new Variable(ncfile, null, null, MISSING_VAR);
var.setDataType(DataType.BYTE);
var.setDimensions((List<Dimension>) null);
var.addAttribute(new Attribute("description", "missing flag - 1 means all params are missing"));
var.addAttribute(new Attribute(CDM.MISSING_VALUE, (byte) 1));
return var;
}
/**
* Make a variable from a GempakParmaeter
*
* @param param GempakParameter
* @param dims Variable dimensions
* @return the Variable
*/
protected Variable makeParamVariable(GempakParameter param, List<Dimension> dims) {
Variable var = new Variable(ncfile, null, null, param.getName());
var.setDataType(DataType.FLOAT);
var.setDimensions(dims);
var.addAttribute(new Attribute(CDM.LONG_NAME, param.getDescription()));
String units = param.getUnit();
if ((units != null) && !units.equals("")) {
var.addAttribute(new Attribute(CDM.UNITS, units));
}
var.addAttribute(new Attribute(CDM.MISSING_VALUE, RMISS));
return var;
}
/**
* Add on global attributes for all types
*/
protected void addGlobalAttributes() {
// global stuff
ncfile.addAttribute(null, new Attribute(CDM.CONVENTIONS, getConventions()));
String fileType = "GEMPAK " + gemreader.getFileType();
ncfile.addAttribute(null, new Attribute("file_format", fileType));
ncfile.addAttribute(null, new Attribute("history", "Direct read of " + fileType + " into NetCDF-Java API"));
ncfile.addAttribute(null, new Attribute(CF.FEATURE_TYPE, getCFFeatureType()));
}
/**
* Get the netCDF conventions identifier.
*
* @return the convention name
*/
public String getConventions() {
return "GEMPAK/CDM";
}
/**
* Get the CF feature type, subclasses should override
*
* @return the feature type
*/
public String getCFFeatureType() {
return CF.FeatureType.point.toString();
}
/**
* Get the size of a particular station variable
*
* @param name name of the variable (key)
* @return size or -1
*/
protected int getStnVarSize(String name) {
int size = -1;
for (int i = 0; i < stnVarNames.length; i++) {
if (name.equals(stnVarNames[i])) {
size = stnVarSizes[i];
break;
}
}
return size;
}
/**
* Make the station variables from a representative station
*
* @param stations list of stations
* @param dim station dimension
* @return the list of variables
*/
protected List<Variable> makeStationVars(List<GempakStation> stations, Dimension dim) {
int numStations = stations.size();
List<Variable> vars = new ArrayList<>();
List<String> stnKeyNames = gemreader.getStationKeyNames();
for (String varName : stnKeyNames) {
Variable v = makeStationVariable(varName, dim);
Attribute stIDAttr = new Attribute(CF.STANDARD_NAME, "station_id");
if (varName.equals(GempakStation.STID)) { // Use STID as the station_id for the dataset.
v.addAttribute(stIDAttr);
}
vars.add(v);
}
// see if we fill these in completely now
if ((dim != null) && (numStations > 0)) {
for (Variable v : vars) {
Array varArray;
if (v.getDataType().equals(DataType.CHAR)) {
int[] shape = v.getShape();
varArray = new ArrayChar.D2(shape[0], shape[1]);
} else {
varArray = get1DArray(v.getDataType(), numStations);
}
assert varArray != null;
int index = 0;
String varname = v.getFullName();
for (GempakStation stn : stations) {
String test = "";
switch (varname) {
case GempakStation.STID:
test = stn.getName();
break;
case GempakStation.STNM:
((ArrayInt.D1) varArray).set(index, stn.getSTNM());
break;
case GempakStation.SLAT:
((ArrayFloat.D1) varArray).set(index, (float) stn.getLatitude());
break;
case GempakStation.SLON:
((ArrayFloat.D1) varArray).set(index, (float) stn.getLongitude());
break;
case GempakStation.SELV:
((ArrayFloat.D1) varArray).set(index, (float) stn.getAltitude());
break;
case GempakStation.STAT:
test = stn.getSTAT();
break;
case GempakStation.COUN:
test = stn.getCOUN();
break;
case GempakStation.STD2:
test = stn.getSTD2();
break;
case GempakStation.SPRI:
((ArrayInt.D1) varArray).set(index, stn.getSPRI());
break;
case GempakStation.SWFO:
test = stn.getSWFO();
break;
case GempakStation.WFO2:
test = stn.getWFO2();
break;
}
if (!test.equals("")) {
((ArrayChar.D2) varArray).setString(index, test);
}
index++;
}
v.setCachedData(varArray, false);
}
}
return vars;
}
/**
* Get a 1DArray for the type and length
*
* @param type DataType
* @param len length
* @return the array
*/
private Array get1DArray(DataType type, int len) {
Array varArray = null;
if (type.equals(DataType.FLOAT)) {
varArray = new ArrayFloat.D1(len);
} else if (type.equals(DataType.DOUBLE)) {
varArray = new ArrayDouble.D1(len);
} else if (type.equals(DataType.INT)) {
varArray = new ArrayInt.D1(len);
}
return varArray;
}
/**
* Make a station variable
*
* @param varname variable name
* @param firstDim station dimension
* @return corresponding variable
*/
protected Variable makeStationVariable(String varname, Dimension firstDim) {
String longName = varname;
String unit = null;
DataType type = DataType.CHAR;
List<Dimension> dims = new ArrayList<>();
List<Attribute> attrs = new ArrayList<>();
if (firstDim != null) {
dims.add(firstDim);
}
switch (varname) {
case GempakStation.STID:
longName = "Station identifier";
dims.add(DIM_LEN8);
break;
case GempakStation.STNM:
longName = "WMO station id";
type = DataType.INT;
break;
case GempakStation.SLAT:
longName = "latitude";
unit = CDM.LAT_UNITS;
type = DataType.FLOAT;
attrs.add(new Attribute(CF.STANDARD_NAME, "latitude"));
break;
case GempakStation.SLON:
longName = "longitude";
unit = CDM.LON_UNITS;
type = DataType.FLOAT;
attrs.add(new Attribute(CF.STANDARD_NAME, "longitude"));
break;
case GempakStation.SELV:
longName = "altitude";
unit = "meter";
type = DataType.FLOAT;
attrs.add(new Attribute(CF.POSITIVE, CF.POSITIVE_UP));
attrs.add(new Attribute(CF.STANDARD_NAME, CF.STATION_ALTITUDE));
break;
case GempakStation.STAT:
longName = "state or province";
dims.add(DIM_LEN2);
break;
case GempakStation.COUN:
longName = "country code";
dims.add(DIM_LEN2);
break;
case GempakStation.STD2:
longName = "Extended station id";
dims.add(DIM_LEN4);
break;
case GempakStation.SPRI:
longName = "Station priority";
type = DataType.INT;
break;
case GempakStation.SWFO:
longName = "WFO code";
dims.add(DIM_LEN4);
break;
case GempakStation.WFO2:
longName = "Second WFO code";
dims.add(DIM_LEN4);
break;
}
Variable v = new Variable(ncfile, null, null, varname);
v.setDataType(type);
v.addAttribute(new Attribute(CDM.LONG_NAME, longName));
if (unit != null) {
v.addAttribute(new Attribute(CDM.UNITS, unit));
}
if (type.equals(DataType.FLOAT)) {
v.addAttribute(new Attribute(CDM.MISSING_VALUE, RMISS));
} else if (type.equals(DataType.INT)) {
v.addAttribute(new Attribute(CDM.MISSING_VALUE, IMISS));
}
if (!attrs.isEmpty()) {
for (Attribute attr : attrs) {
v.addAttribute(attr);
}
}
if (!dims.isEmpty()) {
v.setDimensions(dims);
} else {
v.setDimensions((String) null);
}
return v;
}
}