Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Code Block
languagejava
titleJava 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;
        }
    }

}