package edu.colostate.vchill.netcdf;
import edu.colostate.vchill.ProgressMonitorInputStream;
import edu.colostate.vchill.ScaleManager;
import edu.colostate.vchill.chill.ChillFieldInfo;
import edu.colostate.vchill.chill.ChillMomentFieldScale;
import edu.colostate.vchill.file.*;
import edu.colostate.vchill.file.FileFunctions.Moment;
import edu.colostate.vchill.gui.GUIUtil;
import ucar.ma2.Array;
import ucar.ma2.ArrayDouble;
import ucar.ma2.DataType;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.Dimension;
import ucar.nc2.NetcdfFileWriteable;
import javax.swing.*;
import java.awt.*;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.LinkedList;
import java.util.TimeZone;
/**
* Class to translate CHILL DRX files to CASA NetCDF
*
* @author Jochen Deyke
* @author jpont
* @version 2010-08-30
*/
public class CreateCASANetCDFFile {
private static final ScaleManager sm = ScaleManager.getInstance();
private static final SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'-'HHmmss");
static {
df.setTimeZone(TimeZone.getTimeZone("UTC"));
for (ChillFieldInfo info : ChillFieldInfo.types) {
Moment type = Moment.values()[info.fieldNumber];
sm.putScale(new ChillMomentFieldScale(info, type.ACCELERATOR, type.UNITS, 0, 0, 0));
}
}
/**
* Translates a sweep from CHILL DRX format to a CASA NetCDF file
*
* @param sweep the sweep to translate
* @param dir the directory to write to
*/
public static void createNetCDFFile(final FileSweep sweep, final String dir) throws InvalidRangeException, IOException {
final String radarname = sweep.paramD.getRadarName();
final String scanmode = sweep.paramD.getScanMode();
final String starttime = df.format(sweep.data.get(0).dataH.time * 1000l);
final String filename = dir + "/" + radarname + starttime + ".nc";
System.out.println(" Writing to file " + filename);
final Collection<String> availableMoments = FileFunctions.getAvailableMoments(sweep.paramD);
int numRays = sweep.data.size();
double[] azimuth = new double[numRays];
double[] elevation = new double[numRays];
double[] gateWidth = new double[numRays];
double[] startRange = new double[numRays];
int[] time = new int[numRays];
int[] timenSec = new int[numRays];
double[] txFrequency = new double[numRays];
double[] txLength = new double[numRays];
double[] txPower = new double[numRays];
byte[][][] data = new byte[availableMoments.size()][numRays][];
{
int r = 0;
for (FileRay ray : sweep.data) {
azimuth[r] = ray.dataH.azimuth * 1e-6;
elevation[r] = ray.dataH.elevation * 1e-6;
gateWidth[r] = sweep.paramD.gate_spacing;
startRange[r] = sweep.paramD.start_range;
time[r] = ray.dataH.time;
//timenSec stays blank; nanosecond precision not available
txFrequency[r] = sweep.paramD.prf * 1e-3f;
txLength[r] = sweep.paramD.pulse_width;
txPower[r] = (sweep.paramD.txmit_power_H + sweep.paramD.txmit_power_V) / 200.0;
{
int m = 0;
for (String moment : availableMoments) {
data[m][r] = ray.data[sm.getScale(moment).fieldNumber];
++m;
}
}
++r;
}
}
NetcdfFileWriteable ncfile = NetcdfFileWriteable.createNew(filename, false);
Dimension gate = ncfile.addDimension("Gate", sweep.paramD.ngates);
Dimension radial = ncfile.addDimension("Radial", numRays);
radial.setUnlimited(true);
ncfile.addVariable("Nyquist_Velocity", DataType.FLOAT, new Dimension[0]);
ncfile.addVariableAttribute("Nyquist_Velocity", "units", "meters/second");
ncfile.addVariable("Wavelength", DataType.FLOAT, new Dimension[0]);
ncfile.addVariableAttribute("Wavelength", "units", "meters");
//----------------------------------------------------------------------
ncfile.addVariable("Azimuth", DataType.DOUBLE, new Dimension[]{radial});
ncfile.addVariableAttribute("Azimuth", "Units", "Degrees");
ncfile.addVariable("Elevation", DataType.DOUBLE, new Dimension[]{radial});
ncfile.addVariableAttribute("Elevation", "Units", "Degrees");
ncfile.addVariable("GateWidth", DataType.DOUBLE, new Dimension[]{radial});
ncfile.addVariableAttribute("GateWidth", "Units", "Millimeters");
ncfile.addVariable("StartRange", DataType.DOUBLE, new Dimension[]{radial});
ncfile.addVariableAttribute("StartRange", "Units", "Millimeters");
ncfile.addVariable("Time", DataType.INT, new Dimension[]{radial});
ncfile.addVariableAttribute("Time", "Units", "Seconds");
ncfile.addVariable("TimenSec", DataType.INT, new Dimension[]{radial});
ncfile.addVariableAttribute("Time", "Units", "NanoSeconds");
ncfile.addVariable("TxFrequency", DataType.DOUBLE, new Dimension[]{radial});
ncfile.addVariableAttribute("TxFrequency", "Units", "Hertz");
ncfile.addVariable("TxLength", DataType.DOUBLE, new Dimension[]{radial});
ncfile.addVariableAttribute("TxLength", "Units", "Seconds");
ncfile.addVariable("TxPower", DataType.DOUBLE, new Dimension[]{radial});
ncfile.addVariableAttribute("TxPower", "Units", "dBm");
//data
for (String moment : availableMoments) {
ncfile.addVariable(moment, DataType.FLOAT, new Dimension[]{radial, gate});
ChillMomentFieldScale scale = sm.getScale(moment);
ncfile.addVariableAttribute(moment, "Units", scale.units == null ? "" : scale.units);
ncfile.addVariableAttribute(moment, "MaxValue", scale.maxValue);
ncfile.addVariableAttribute(moment, "MinValue", scale.minValue);
ncfile.addVariableAttribute(moment, "Factor", scale.factor);
ncfile.addVariableAttribute(moment, "Scale", scale.scale);
ncfile.addVariableAttribute(moment, "Bias", scale.bias);
}
ncfile.addGlobalAttribute("RadarName", radarname);
ncfile.addGlobalAttribute("Latitude", sweep.paramD.latitude * 1e-6);
ncfile.addGlobalAttribute("Longitude", sweep.paramD.longitude * 1e-6);
ncfile.addGlobalAttribute("Height", sweep.paramD.altitude * 1.0);
ncfile.addGlobalAttribute("NumGates", sweep.paramD.ngates);
ncfile.addGlobalAttribute("AntennaBeamwidth", sweep.paramD.beam_width * 1e-6);
ncfile.addGlobalAttribute("Scan_Mode", scanmode); //PPI/RHI
ncfile.addGlobalAttribute("Start_Time", starttime);
ncfile.create();
ncfile.write("Nyquist_Velocity", factory((sweep.paramD.wavelength * 1e-6f / 2) * sweep.paramD.prf * 1e-3f));
ncfile.write("Wavelength", factory(sweep.paramD.wavelength * 1e-6f));
//----------------------------------------------------------------------
ncfile.write("Azimuth", Array.factory(azimuth));
ncfile.write("Elevation", Array.factory(elevation));
ncfile.write("GateWidth", Array.factory(gateWidth));
ncfile.write("StartRange", Array.factory(startRange));
ncfile.write("Time", Array.factory(time));
ncfile.write("TimenSec", Array.factory(timenSec));
ncfile.write("TxFrequency", Array.factory(txFrequency));
ncfile.write("TxLength", Array.factory(txLength));
ncfile.write("TxPower", Array.factory(txPower));
{
int i = 0;
for (String moment : availableMoments) {
ncfile.write(moment, Array.factory(decode(data[i], sweep.fieldScalings[sm.getScale(moment).fieldNumber])));
++i;
}
}
ncfile.close();
}
private static ArrayDouble factory(final double number) {
ArrayDouble.D0 array = new ArrayDouble.D0();
array.set(number);
return array;
}
/**
* Translates a file from CHILL DRX format to a series of CASA NetCDF files - one per sweep
*
* @param in the file to read from
* @param out the directory to write to
*/
private static void processFile(final File in, final String out, final JFrame win) throws Throwable {
final JProgressBar progressBar = GUIUtil.addProgressBar(win);
DataInputStream input = new DataInputStream(new ProgressMonitorInputStream(new FileInputStream(in), progressBar));
EventQueue.invokeLater(new Runnable() {
public void run() {
progressBar.setMaximum((int) in.length());
progressBar.setStringPainted(true);
}
});
/**
* The actual data file, as a series of objects.
* The first is always a FileSKUHeader which describes what the next object
* will be, followed by that object. The Integer size trailer that would come
* in the actual data file after each described object is discarded for easier
* updating/adding of types. This pattern (SKU Header, described object)
* repeats indefinitely.
*/
LinkedList<Object> file;
/**
* Only the data portion, as a series of FileSweep objects, for easier manipulation.
* References the same data arrays as <code>file</code>.
*/
LinkedList<FileSweep> sweeps;
{
int sweepnum = 0;
while (input.available() > 0) { //each sweep
file = new LinkedList<Object>();
System.out.println("Sweep " + sweepnum + ":");
System.out.println(" Reading from file " + in);
sweeps = FileAlterer.load(file, input);
EventQueue.invokeLater(new Runnable() {
public void run() {
progressBar.setIndeterminate(true);
}
});
for (FileSweep sweep : sweeps) createNetCDFFile(sweep, out);
EventQueue.invokeLater(new Runnable() {
public void run() {
progressBar.setIndeterminate(false);
}
});
++sweepnum;
}
}
input.close();
System.out.println("All done!");
}
public static void main(final String[] args) throws Throwable {
processFile(new File(args[0]), args.length > 1 ? args[1] : "..", GUIUtil.startGUI("Java VCHILL Data File Translator"));
System.exit(0);
}
/**
* Uncompresses compressed radar data based on the provided scaling information
*
* @param data the data to uncompress
* @param fieldScaling the scaling information to use
* @return the uncompressed data
*/
static float[][] decode(final byte[][] data, final FileFieldScalingInfo fieldScaling) {
float[][] result = new float[data.length][];
for (int i = 0; i < data.length; ++i) {
result[i] = new float[data[i].length];
for (int j = 0; j < data[i].length; ++j) {
if (data[i][j] == 0) result[i][j] = Float.NaN;
else
result[i][j] = ((data[i][j] & 0xff) * fieldScaling.scale + fieldScaling.bias) / (float) fieldScaling.factor;
}
}
return result;
}
}