ucar.nc2.iosp.netcdf3.N3streamWriter Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/
// $Id: $
package ucar.nc2.iosp.netcdf3;
import ucar.nc2.*;
import ucar.ma2.DataType;
import ucar.nc2.constants.CDM;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;
import java.io.DataOutputStream;
/**
* Common superclass for N3outputStreamWriter and N3channelStreamWriter.
* Experimental
* @author john
*/
public abstract class N3streamWriter {
////////////////////////////////////////////////////////////////////////////////////////////////////////
protected ucar.nc2.NetcdfFile ncfile;
protected Map vinfoMap = new HashMap();
protected List vinfoList = new ArrayList(); // output order of the variables
protected boolean debug=false, debugPos=true, debugWriteData = false;
protected int recStart, recSize;
protected boolean usePadding = true;
protected long filePos = 0;
protected N3streamWriter(ucar.nc2.NetcdfFile ncfile) {
this.ncfile = ncfile;
}
/**
* Write the header to a stream.
*
* @param stream write to this stream.
* @param numrec pass in number of record is you know it, else -1 for "streaming" format variant
* @throws IOException if write fails
*/
public void writeHeader(DataOutputStream stream, int numrec) throws IOException {
// make sure ncfile structures were finished
ncfile.finish();
// magic number
stream.write(N3header.MAGIC);
int count = N3header.MAGIC.length;
// numrecs
Dimension udim = ncfile.getUnlimitedDimension();
if (numrec < 0) {
numrec = (udim == null) ? 0 : -1; // -1 means "streaming" - calc numrec through file length
}
stream.writeInt(numrec);
count += 4;
// dims
List dims = ncfile.getDimensions();
int numdims = dims.size();
if (numdims == 0) {
stream.writeInt(0);
stream.writeInt(0);
} else {
stream.writeInt(N3header.MAGIC_DIM);
stream.writeInt(numdims);
}
count += 8;
for (int i = 0; i < numdims; i++) {
Dimension dim = (Dimension) dims.get(i);
count += writeString(stream, N3iosp.makeValidNetcdfObjectName( dim.getShortName()));
stream.writeInt(dim.isUnlimited() ? 0 : dim.getLength());
count += 4;
}
// global attributes
count += writeAtts(stream, ncfile.getGlobalAttributes());
if (debug) System.out.println("vars header starts at "+count);
// variables
List vars = ncfile.getVariables();
int nvars = vars.size();
if (nvars == 0) {
stream.writeInt(0);
stream.writeInt(0);
} else {
stream.writeInt(N3header.MAGIC_VAR);
stream.writeInt(nvars);
}
count += 8;
/* Note on padding: In the special case of only a single record variable of character, byte, or short
// type, no padding is used between data values.
if (nvars == 1) {
Variable var = vars.get(0);
DataType dtype = var.getDataType();
if ((dtype == DataType.CHAR) || (dtype == DataType.BYTE) || (dtype == DataType.SHORT))
usePadding = false;
} */
// we have to calculate how big the header is before we can actually write it
// so we set stream = null
for (int i = 0; i < nvars; i++) {
Variable var = (Variable) vars.get(i);
if (var instanceof Structure) continue;
Vinfo vinfo = writeVar(null, var, 0);
count += vinfo.hsize;
}
// now calculate where things go
int dataStart = count; // data starts right after the header
int offset = dataStart; // track data offset
if (debug) System.out.println(" non-record vars start at "+dataStart);
// do all non-record variables first
for (int i = 0; i < nvars; i++) {
Variable var = (Variable) vars.get(i);
//if (var instanceof Structure) continue;
if (!var.isUnlimited()) {
Vinfo vinfo = writeVar(stream, var, offset);
vinfoMap.put(var, vinfo);
if (debugPos)
System.out.println(" " + var.getNameAndDimensions() + " begin at = " + offset + " end=" + (offset + vinfo.vsize));
offset += vinfo.vsize;
vinfoList.add(vinfo);
}
}
if (debug) System.out.println(" record vars start at "+offset);
recStart = offset; // record variables' data starts here
recSize = 0;
// do all record variables
for (int i = 0; i < nvars; i++) {
Variable var = (Variable) vars.get(i);
if (var.isUnlimited()) {
if (var instanceof Structure) continue;
Vinfo vinfo = writeVar(stream, var, offset);
vinfoMap.put(var, vinfo);
if (debugPos)
System.out.println(" " + var.getNameAndDimensions() + "(record) begin at = " + offset + " end=" + (offset + vinfo.vsize) + " size=" + vinfo.vsize);
offset += vinfo.vsize;
recSize += vinfo.vsize;
vinfoList.add(vinfo);
}
}
filePos = count;
if (debugPos) System.out.println("header written filePos= " + filePos+" recsize= "+recSize);
}
private Vinfo writeVar(DataOutputStream stream, Variable var, int offset) throws IOException {
int hsize = 0;
hsize += writeString(stream, N3iosp.makeValidNetcdfObjectName( var.getShortName()));
// dimensions
int vsize = var.getDataType().getSize();
List dims = var.getDimensions();
if (null != stream) stream.writeInt(dims.size());
hsize += 4;
for (Dimension dim : dims) {
int dimIndex = findDimensionIndex(dim);
if (null != stream) stream.writeInt(dimIndex);
hsize += 4;
if (!dim.isUnlimited())
vsize *= dim.getLength();
}
int pad = (usePadding) ? N3header.padding(vsize) : 0;
vsize += pad;
// variable attributes
hsize += writeAtts(stream, var.getAttributes());
// data type, variable size, beginning file position
int type = N3header.getType(var.getDataType());
if (null != stream) {
stream.writeInt(type);
stream.writeInt(vsize);
stream.writeInt(offset);
}
hsize += 12;
//if (debug) out.println(" name= "+name+" type="+type+" vsize="+vsize+" begin= "+begin+" isRecord="+isRecord+"\n");
return new Vinfo(var, hsize, vsize, offset, pad, var.isUnlimited());
}
private int writeAtts(DataOutputStream stream, List atts) throws IOException {
int natts = atts.size();
if (null != stream) {
if (natts == 0) {
stream.writeInt(0);
stream.writeInt(0);
} else {
stream.writeInt(N3header.MAGIC_ATT);
stream.writeInt(natts);
}
}
int hsize = 8;
for (int i = 0; i < natts; i++) {
Attribute att = atts.get(i);
hsize += writeString(stream, N3iosp.makeValidNetcdfObjectName( att.getShortName()));
int type = N3header.getType(att.getDataType());
if (null != stream) stream.writeInt(type);
hsize += 4;
if (type == 2) {
hsize += writeStringValues(stream, att);
} else {
int nelems = att.getLength();
if (null != stream) stream.writeInt(nelems);
hsize += 4;
int nbytes = 0;
for (int j = 0; j < nelems; j++)
nbytes += writeAttributeValue(stream, att.getNumericValue(j));
hsize += nbytes;
hsize += pad(stream, nbytes, (byte) 0);
}
}
return hsize;
}
private int writeStringValues(DataOutputStream stream, Attribute att) throws IOException {
int n = att.getLength();
if (n == 1)
return writeString(stream, att.getStringValue());
else {
StringBuilder values = new StringBuilder();
for (int i = 0; i < n; i++)
values.append(att.getStringValue(i));
return writeString(stream, values.toString());
}
}
private int writeAttributeValue(DataOutputStream stream, Number numValue) throws IOException {
if (numValue instanceof Byte) {
if (null != stream) stream.write(numValue.byteValue());
return 1;
} else if (numValue instanceof Short) {
if (null != stream) stream.writeShort(numValue.shortValue());
return 2;
} else if (numValue instanceof Integer) {
if (null != stream) stream.writeInt(numValue.intValue());
return 4;
} else if (numValue instanceof Float) {
if (null != stream) stream.writeFloat(numValue.floatValue());
return 4;
} else if (numValue instanceof Double) {
if (null != stream) stream.writeDouble(numValue.doubleValue());
return 8;
}
throw new IllegalStateException("unknown attribute type == " + numValue.getClass().getName());
}
// write a string then pad to 4 byte boundary
private int writeString(DataOutputStream stream, String s) throws IOException {
byte[] b = s.getBytes(CDM.utf8Charset);
if (null != stream) {
stream.writeInt(b.length);
stream.write(b);
}
int n = pad(stream, b.length, (byte) 0);
return n + 4 + b.length;
}
private int findDimensionIndex(Dimension wantDim) {
List dims = ncfile.getDimensions();
for (int i = 0; i < dims.size(); i++) {
Dimension dim = (Dimension) dims.get(i);
if (dim.equals(wantDim)) return i;
}
throw new IllegalStateException("unknown Dimension == " + wantDim);
}
// pad to a 4 byte boundary
protected int pad(DataOutputStream stream, int nbytes, byte fill) throws IOException {
int pad = N3header.padding(nbytes);
if (null != stream) {
for (int i = 0; i < pad; i++)
stream.write(fill);
}
return pad;
}
// variable info for reading/writing
static protected class Vinfo {
Variable v;
int hsize; // header size
int vsize; // size of array in bytes. if isRecord, size per record. includes padding
int offset; // offset of start of data from start of file
int pad; // number of padding bytes
boolean isRecord; // is it a record variable?
Vinfo(Variable v, int hsize, int vsize, int offset, int pad, boolean isRecord) {
this.v = v;
this.hsize = hsize;
this.vsize = vsize;
this.offset = offset;
this.pad = pad;
this.isRecord = isRecord;
}
public String toString() { return v.getFullName()+" vsize= "+vsize+" pad="+pad; }
}
}