Java class example adapter for netcdf gridded time series
package netcdfexample.reading; import nl.wldelft.util.TextUtils; import ucar.ma2.Array; import ucar.ma2.ArrayDouble; import ucar.ma2.ArrayFloat; import ucar.ma2.DataType; import ucar.ma2.InvalidRangeException; import ucar.nc2.Attribute; import ucar.nc2.Dimension; import ucar.nc2.NetcdfFile; import ucar.nc2.NetcdfFileWriteable; import ucar.nc2.Variable; import ucar.nc2.dataset.NetcdfDataset; import ucar.nc2.units.DateUnit; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Date; import java.util.List; public class NetcdfGridAdapter { public static final String AIRPRESSURE_FORECAST = "airpressure_forecast"; public static final String LON = "lon"; public static final String LAT = "lat"; public static final String X = "x"; public static final String Y = "y"; public static final String TIME = "time"; public static void main(String[] args) throws Exception { if (args.length != 1) throw new Exception("Specify only the netcdf run file as argument"); File runFile = new File(args[0]); if (!runFile.exists()) throw new Exception("Can not find run file specified as argument " + runFile); //Reading netcdf run file Path runPath = runFile.getParentFile().toPath(); NetcdfDataset netcdfRunFileDataset = new NetcdfDataset(NetcdfDataset.openFile(runFile.getAbsolutePath(), null)); Variable startTimeVar = netcdfRunFileDataset.findVariable("start_time"); //Start and end time can be used for model specific settings double startDateTime = startTimeVar.readScalarDouble(); double endDateTime = netcdfRunFileDataset.findVariable("end_time").readScalarDouble(); Attribute units = startTimeVar.findAttribute("units"); String tunitsString = units.getStringValue(); DateUnit referenceUnit = new DateUnit(tunitsString); //The time of date origin can be used to determine the real time values long currentReferenceTime = referenceUnit.getDateOrigin().getTime(); //The variable work_dir specifies the working directory relative to the directory of the run file String relativeWorkDirString = netcdfRunFileDataset.findVariable("work_dir").readScalarString().trim(); String workDir; //A point "." means the work dir is the same as the directory of the run file if (TextUtils.equals(relativeWorkDirString, ".")) { workDir = runPath.toString(); } else { workDir = runPath.resolve(new File(relativeWorkDirString).toPath()).toString(); } //Reading variable that specifies input netcdf files Variable inputNetcdfVar = netcdfRunFileDataset.findVariable("input_netcdf_files"); Array inputNetcdfArray = inputNetcdfVar.read(); Object inputTimeSeriesFilesNDArrayObject = inputNetcdfArray.copyToNDJavaArray(); char[][] inputTimeSeriesFilesChar = (char[][]) inputTimeSeriesFilesNDArrayObject; //Converting 2 dimensional char array to list of strings List<String> inputTimeSeriesFilesList = new ArrayList<>(); for (char[] chArray : inputTimeSeriesFilesChar) { inputTimeSeriesFilesList.add(String.valueOf(chArray).trim()); } //Done reading netcdf run file //Processing input files for (String inputFilePath : inputTimeSeriesFilesList) { File inputFileRelativePath = new File(inputFilePath); String absoluteFilePath = runPath.resolve(inputFileRelativePath.toPath()).toString(); File inputNetcdfFile = new File(absoluteFilePath); //Should not happen often in practice because run file only specifies input netcdf files that are actually exported //But could happen when running adapter with older run info file if (!inputNetcdfFile.exists()) throw new RuntimeException("Input file does not exist: " + absoluteFilePath); processAirPressure2DimensionalGrid(absoluteFilePath, workDir, tunitsString); } } //Example how to read gridded data from netcdf file exported by FEWS public static void processAirPressure2DimensionalGrid(String absoluteFilePath, String workDir, String tunitsString) throws Exception { NetcdfFile netcdfDatasetInputFile = new NetcdfDataset(NetcdfDataset.openFile(absoluteFilePath, null)); try { //Reading air pressure variable Variable airPressureVariable = netcdfDatasetInputFile.findVariable(AIRPRESSURE_FORECAST); if (airPressureVariable == null) throw new Exception("Air pressure variable '" + AIRPRESSURE_FORECAST + "' not found in example.nc"); //Reading time variable Dimension timeDimension = airPressureVariable.getDimension(0); Variable timeVariable = netcdfDatasetInputFile.findVariable(timeDimension.getName()); long[] convertedTimeArray = readTimes(timeVariable); //Reading latitude variable Variable latitudeVariable = netcdfDatasetInputFile.findVariable(LAT); Array latitudeArray = latitudeVariable.read(); double[][] latitudeValues = (double[][]) latitudeArray.copyToNDJavaArray(); //Reading longitude variable Variable longitudeVariable = netcdfDatasetInputFile.findVariable(LON); Array longitudeArray = longitudeVariable.read(); double[][] longitudeValues = (double[][]) longitudeArray.copyToNDJavaArray(); //Reading x variable Variable xVariable = netcdfDatasetInputFile.findVariable(X); Array xArray = xVariable.read(); double[] xValues = (double[]) xArray.copyTo1DJavaArray(); //Reading y variable Variable yVariable = netcdfDatasetInputFile.findVariable(Y); Array yArray = yVariable.read(); double[] yValues = (double[]) yArray.copyTo1DJavaArray(); //Reading air pressure values Array airPressureArray = airPressureVariable.read(); float[][][] floatValues = (float[][][]) airPressureArray.copyToNDJavaArray(); File outputNetcdfFile = new File(workDir, "grid.nc"); NetcdfFileWriteable gridNetcdf = NetcdfFileWriteable.createNew(outputNetcdfFile.getPath(), false); try { writeNetcdfGrid(gridNetcdf, floatValues, tunitsString, convertedTimeArray, latitudeValues, longitudeValues, xValues, yValues); } finally { gridNetcdf.close(); } } finally { netcdfDatasetInputFile.close(); } } //Example how to write netcdf for FEWS private static void writeNetcdfGrid(NetcdfFileWriteable netcdfFile, float[][][] floatValues, String timeUnitsString, long[] convertedTimeArray, double[][] latitudeValues, double[][] longitudeValues, double[] xValues, double[] yValues) throws IOException, InvalidRangeException { Dimension timeDimension = netcdfFile.addDimension(TIME, floatValues.length); Dimension yDimension = netcdfFile.addDimension(Y, floatValues[0].length); Dimension xDimension = netcdfFile.addDimension(X, floatValues[0][0].length); ArrayList<Dimension> timeDimensions = new ArrayList<>(); timeDimensions.add(timeDimension); addNetcdfVariable(netcdfFile, timeDimensions, TIME, DataType.DOUBLE, TIME, TIME, timeUnitsString, "T"); ArrayList<Dimension> xDimensions = new ArrayList<>(); xDimensions.add(xDimension); addNetcdfVariable(netcdfFile, xDimensions, X, DataType.DOUBLE, "projection_x_coordinate", "x coordinate according to CH1903", "degrees_east", "X"); ArrayList<Dimension> yDimensions = new ArrayList<>(); yDimensions.add(yDimension); addNetcdfVariable(netcdfFile, yDimensions, Y, DataType.DOUBLE, "projection_y_coordinate", "y coordinate according to CH1903", "degrees_north", "Y"); ArrayList<Dimension> latLonDimensions = new ArrayList<>(); latLonDimensions.add(yDimension); latLonDimensions.add(xDimension); addNetcdfVariable(netcdfFile, latLonDimensions, LAT, DataType.DOUBLE, "latitude", "latitude", "degrees_north", "Y"); addNetcdfVariable(netcdfFile, latLonDimensions, LON, DataType.DOUBLE, "longitude", "longitude", "degrees_north", "Y"); ArrayList<Dimension> airPressureDimensions = new ArrayList<>(); airPressureDimensions.add(timeDimension); airPressureDimensions.add(yDimension); airPressureDimensions.add(xDimension); addNetcdfVariable(netcdfFile, airPressureDimensions, AIRPRESSURE_FORECAST, DataType.FLOAT, null, AIRPRESSURE_FORECAST, "hPa", null, -999, "y x"); //First define all variable and dimensions, then create the netcdf, after creation values can be written to variables netcdfFile.create(); //Write values to variable writeTime(netcdfFile, convertedTimeArray); write1DValues(netcdfFile, xValues, X); write1DValues(netcdfFile, yValues, Y); write2DValues(netcdfFile, latitudeValues, LAT); write2DValues(netcdfFile, longitudeValues, LON); writeAirPressure(netcdfFile, floatValues); } private static void addNetcdfVariable(NetcdfFileWriteable netcdfFile, ArrayList<Dimension> dimensions, String variableName, DataType dataType, String standardName, String longName, String units, String axis) { netcdfFile.addVariable(variableName, dataType, dimensions); netcdfFile.addVariableAttribute(variableName, "standard_name", standardName); netcdfFile.addVariableAttribute(variableName, "long_name", longName); netcdfFile.addVariableAttribute(variableName, "units", units); netcdfFile.addVariableAttribute(variableName, "axis", axis); } private static void addNetcdfVariable(NetcdfFileWriteable netcdfFile, ArrayList<Dimension> dimensions, String variableName, DataType dataType, String standardName, String longName, String units, String axis, int fillValue, String coordinates) { netcdfFile.addVariable(variableName, dataType, dimensions); if (standardName != null) netcdfFile.addVariableAttribute(variableName, "standard_name", standardName); if (longName != null) netcdfFile.addVariableAttribute(variableName, "long_name", longName); if (units != null) netcdfFile.addVariableAttribute(variableName, "units", units); if (axis != null) netcdfFile.addVariableAttribute(variableName, "axis", axis); if (fillValue != Integer.MIN_VALUE) netcdfFile.addVariableAttribute(variableName, "_FillValue", fillValue); if (coordinates != null) netcdfFile.addVariableAttribute(variableName, "coordinates", coordinates); } private static void writeAirPressure(NetcdfFileWriteable netcdfFile, float[][][] floatValues) throws IOException, InvalidRangeException { ArrayFloat.D3 airPressureArray = new ArrayFloat.D3(floatValues.length, floatValues[0].length, floatValues[0][0].length); for (int i = 0; i < floatValues.length; i++) { for (int j = 0; j < floatValues[0].length; j++) { for (int k = 0; k < floatValues[0][0].length; k++) { airPressureArray.set(i, j, k, floatValues[i][j][k]); } } } netcdfFile.write(AIRPRESSURE_FORECAST, airPressureArray); } private static void write2DValues(NetcdfFileWriteable netcdfFile, double[][] latValues, String variableName) throws IOException, InvalidRangeException { ArrayDouble.D2 latArray = new ArrayDouble.D2(latValues.length, latValues[0].length); for (int i = 0; i < latValues.length; i++) { for (int j = 0; j < latValues[0].length; j++) { latArray.set(i, j, latValues[i][j]); } } netcdfFile.write(variableName, latArray); } private static void writeTime(NetcdfFileWriteable netcdfFile, long[] convertedTimeArray) throws IOException, InvalidRangeException { ArrayDouble.D1 timeArray = new ArrayDouble.D1(convertedTimeArray.length); for (int i = 0; i < convertedTimeArray.length; i++) { timeArray.set(i, convertedTimeArray[i]); } netcdfFile.write(TIME, timeArray); } private static void write1DValues(NetcdfFileWriteable netcdfFile, double[] yValues, String variableName) throws IOException, InvalidRangeException { ArrayDouble.D1 yArray = new ArrayDouble.D1(yValues.length); for (int i = 0; i < yValues.length; i++) { yArray.set(i, yValues[i]); } netcdfFile.write(variableName, yArray); } private static long[] readTimes(Variable timeVariable) throws IOException { if (timeVariable == null) throw new RuntimeException("Time variable not present"); //read times. Array timeArray = timeVariable.read(); double[] times = (double[]) timeArray.get1DJavaArray(Double.class); //convert times. long[] convertedTimes = new long[times.length]; DateUnit dateUnit = readTimeUnit(timeVariable); if (dateUnit == null) { throw new IOException("Invalid date time unit string has been coded in the file: '" + timeVariable.getUnitsString() + "'. Unit string should be for example: 'seconds since 2012-01-30 00:00:00'"); } for (int i = 0; i < times.length; i++) { Date date = dateUnit.makeDate(times[i]); if (date != null) { convertedTimes[i] = date.getTime(); } else {//if date is null. convertedTimes[i] = 0; } } return convertedTimes; } public static DateUnit readTimeUnit(Variable timeVariable) { try { String unitString = timeVariable.getUnitsString(); //if present, replace . by : in the timeZone specification in the unitString, //e.g. change "days since 2011-09-19 06:0:0.0 0.00" to "days since 2011-09-19 06:0:0.0 0:00". //This is a workaround implemented for FEWS-6544. String[] parts = unitString.split("\\s+"); if (parts.length > 0 && parts[parts.length - 1].matches(".?\\d{1,2}\\.\\d{2}")) { //if a timeZone specification is present, then it is always the last part, see //http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.5/cf-conventions.html#time-coordinate parts[parts.length - 1] = parts[parts.length - 1].replaceFirst("\\.", ":"); StringBuilder buffer = new StringBuilder(parts[0]); for (int n = 1; n < parts.length; n++) { buffer.append(' ').append(parts[n]); } unitString = buffer.toString(); } return new DateUnit(unitString); } catch (Exception e) { //if the given variable does not have a unit of time. return null; } } }