/* * Copyright 1998-2009 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.misc; import ucar.ma2.*; import ucar.nc2.*; import ucar.nc2.constants.AxisType; import ucar.nc2.constants.CDM; import ucar.nc2.constants.CF; import ucar.nc2.util.CancelTask; import ucar.unidata.io.RandomAccessFile; import java.io.IOException; import java.nio.ByteBuffer; /** * National Lightning Detection Network (NLDN) * * @author caron * @see "http://www.unidata.ucar.edu/data/lightning.html" * @since Nov 19, 2008 */ public class Nldn extends AbstractLightningIOSP { /* Field Example -------------------+--------------------- date/time (msec) 09/22/93 10:22:33.334 latitude 47.33 longitude -87.116 polarity/signal strength -188.7 multiplicity 6 ellipse angle 174 semi-major axis 6.0 eccentricity 2.0 chi-square 1.0 The specifics for the binary NLDN data record contained in the IDD is: Size Name Description --------+---------+---------------------------------------------------- char[4] NLDN 'NLDN' marks the start of record int[4] tsec time in seconds since 1970 int[4] nsec nanoseconds since tsec (seems to be thousandths) int[4] lat latitude [deg] * 1000 int[4] lon longitude [deg] * 1000 short[2] fill padding short[2] sgnl signal strength * 10 [150 NLDN measures ~= 30 kAmps] short[2] fill padding short[2] mult multiplicity [#strokes per flash] char[1] fill padding char[1] semimaj semi-major axis char[1] eccent eccentricity char[1] angle ellipse angle char[1] chisqr chi-square */ /** * The magic mushroom */ private static final String MAGIC = "NLDN"; /** * The data structure */ private Structure seq; /** * The structure members */ private StructureMembers sm; /** * The time in seconds variable name */ private static final String TSEC = "tsec"; /** * The time in nanoseconds from TSEC variable name */ private static final String NSEC = "nsec"; /** * The chi squared variable name */ private static final String CHISQR = "chisqr"; /** * The fill variable name */ private static final String FILL = "fill"; /** * header size */ private static final int recHeader = 84; /** * record size */ private static final int recSize = 28; /** * Check if this is a valid file for this IOServiceProvider. * You must make this method thread safe, ie dont keep any state. * * @param raf RandomAccessFile * @return true if valid. * @throws IOException if read error */ public boolean isValidFile(RandomAccessFile raf) throws IOException { raf.seek(0); String test = raf.readString(MAGIC.length()); return test.equals(MAGIC); } /** * Get a unique id for this file type. * * @return registered id of the file type * @see "http://www.unidata.ucar.edu/software/netcdf-java/formats/FileTypes.html" */ public String getFileTypeId() { return "NLDN"; } /** * Get a human-readable description for this file type. * * @return description of the file type * @see "http://www.unidata.ucar.edu/software/netcdf-java/formats/FileTypes.html" */ public String getFileTypeDescription() { return "National Lightning Detection Network"; } /** * Open existing file, and populate ncfile with it. This method is only * called by the NetcdfFile constructor on itself. The provided NetcdfFile * object will be empty except for the location String and the * IOServiceProvider associated with this NetcdfFile object. * * @param raf the file to work on, it has already passed the * isValidFile() test. * @param ncfile add objects to this empty NetcdfFile * @param cancelTask used to monitor user cancellation; may be null. * @throws IOException if read error */ public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException { super.open(raf, ncfile, cancelTask); seq = new Sequence(ncfile, null, null, RECORD); ncfile.addVariable(null, seq); /* makeLightningVariable(NetcdfFile ncfile, Group group, Structure seq, String name, DataType dataType, String dims, String longName, String cfName, String units, AxisType type) { */ Variable v = makeLightningVariable(ncfile, null, seq, TSEC, DataType.INT, "", "time of stroke", null, secondsSince1970, AxisType.Time); seq.addMemberVariable(v); v = makeLightningVariable(ncfile, null, seq, "nsec", DataType.INT, "", "nanoseconds since tsec", null, "1.0e-9 s", null); seq.addMemberVariable(v); v = makeLightningVariable(ncfile, null, seq, LAT, DataType.INT, "", "latitude", "latitude", CDM.LAT_UNITS, AxisType.Lat); v.addAttribute(new Attribute(CDM.SCALE_FACTOR, new Float(1.0e-3))); seq.addMemberVariable(v); v = makeLightningVariable(ncfile, null, seq, LON, DataType.INT, "", "longitude", "longitude", CDM.LON_UNITS, AxisType.Lon); v.addAttribute(new Attribute(CDM.SCALE_FACTOR, new Float(1.0e-3))); seq.addMemberVariable(v); v = makeLightningVariable( ncfile, null, seq, SIGNAL, DataType.SHORT, "", "signal strength/polarity [150 NLDN measures ~= 30 kAmps]", null, "", null); v.addAttribute(new Attribute(CDM.SCALE_FACTOR, new Float(1.0e-1))); seq.addMemberVariable(v); v = makeLightningVariable(ncfile, null, seq, MULTIPLICITY, DataType.BYTE, "", "multiplicity [#strokes per flash]", null, "", null); seq.addMemberVariable(v); v = new Variable(ncfile, null, seq, FILL); v.setDataType(DataType.BYTE); v.setDimensions(""); seq.addMemberVariable(v); v = makeLightningVariable(ncfile, null, seq, MAJOR_AXIS, DataType.BYTE, "", "error ellipse semi-major axis", null, "", null); seq.addMemberVariable(v); v = makeLightningVariable(ncfile, null, seq, ECCENTRICITY, DataType.BYTE, "", "error ellipse eccentricity ", null, "", null); seq.addMemberVariable(v); v = makeLightningVariable(ncfile, null, seq, ELLIPSE_ANGLE, DataType.BYTE, "", "error ellipse axis angle of orientation ", null, "degrees", null); seq.addMemberVariable(v); v = makeLightningVariable(ncfile, null, seq, CHISQR, DataType.BYTE, "", "chi-squared", null, "", null); seq.addMemberVariable(v); addLightningGlobalAttributes(ncfile); ncfile.finish(); sm = seq.makeStructureMembers(); sm.findMember(TSEC).setDataParam(0); sm.findMember(NSEC).setDataParam(4); sm.findMember(LAT).setDataParam(8); sm.findMember(LON).setDataParam(12); sm.findMember(SIGNAL).setDataParam(18); sm.findMember(MULTIPLICITY).setDataParam(22); sm.findMember(FILL).setDataParam(23); sm.findMember(MAJOR_AXIS).setDataParam(24); sm.findMember(ECCENTRICITY).setDataParam(25); sm.findMember(ELLIPSE_ANGLE).setDataParam(26); sm.findMember(CHISQR).setDataParam(27); sm.setStructureSize(recSize); } /** * Add the global attributes. * * @param ncfile the file to add to */ protected void addLightningGlobalAttributes(NetcdfFile ncfile) { super.addLightningGlobalAttributes(ncfile); ncfile.addAttribute(null, new Attribute("title", "NLDN Lightning Data")); ncfile.addAttribute(null, new Attribute(CDM.CONVENTIONS, "NLDN-CDM")); } /* The specifics for the binary NLDN data record contained in the IDD is: Size Name Description --------+---------+---------------------------------------------------- char[4] NLDN 'NLDN' marks the start of record int[4] tsec time in seconds since 1970 int[4] nsec nanoseconds since tsec (seems to be thousandths) int[4] lat latitude [deg] * 1000 int[4] lon longitude [deg] * 1000 short[2] fill padding short[2] sgnl signal strength * 10 [150 NLDN measures ~= 30 kAmps] short[2] fill padding short[2] mult multiplicity [#strokes per flash] char[1] fill padding char[1] semimaj semi-major axis char[1] eccent eccentricity char[1] angle ellipse angle char[1] chisqr chi-square */ /* public Array readData(Variable v2, Section section) throws IOException, InvalidRangeException { Range r = section.getRange(0); int nrecs = r.length(); byte[] bb = new byte[nrecs * recSize]; int pos = 0; Range.Iterator iter = r.getIterator(); while (iter.hasNext()) { int index = iter.next(); raf.seek(recHeader + index * recSize); raf.read(bb, pos, recSize); pos += recSize; } return new ArrayStructureBB(sm, new int[]{nrecs}, ByteBuffer.wrap(bb), 0); } */ /** * Read data from a top level Variable and return a memory resident Array. * This Array has the same element type as the Variable, and the requested shape. * * @param v2 a top-level Variable * @param section the section of data to read. * There must be a Range for each Dimension in the variable, in order. * Note: no nulls allowed. IOSP may not modify. * @return the requested data in a memory-resident Array * @throws IOException if read error * @throws InvalidRangeException if invalid section * @see ucar.ma2.Range */ public Array readData(Variable v2, Section section) throws IOException, InvalidRangeException { return new ArraySequence(sm, new SeqIter(), nelems); } private int nelems = -1; /** * Get the structure iterator * * @param s the Structure * @param bufferSize the buffersize * @return the data iterator * @throws java.io.IOException if problem reading data */ public StructureDataIterator getStructureIterator(Structure s, int bufferSize) throws java.io.IOException { return new SeqIter(); } /** * Sequence Iterator * * @author Unidata Development Team */ private class SeqIter implements StructureDataIterator { /** * done? */ private int done = 0; /** * number bytes already read */ private int alreadyRead = 0; /** * next index */ private int nextIndex = 0; /** * the wrapped asbb */ private ArrayStructureBB asbb = null; /** * total bytes */ private long totalBytes; /** * bytes read */ private long bytesRead; /** * Create a new one * * @throws IOException problem reading the file */ SeqIter() throws IOException { totalBytes = (int) raf.length(); raf.seek(0); } @Override public StructureDataIterator reset() { done = 0; alreadyRead = 0; bytesRead = 0; nextIndex = 0; try { raf.seek(0); } catch (IOException e) { throw new RuntimeException(e); } return this; } @Override public boolean hasNext() throws IOException { if (done < alreadyRead) { return true; } return readHeader(); } @Override public StructureData next() throws IOException { done++; return asbb.getStructureData(nextIndex++); } /** * Read the header * * @return true if okay * @throws IOException problem reading file */ private boolean readHeader() throws IOException { if ((bytesRead + recHeader) > totalBytes) { nelems = done; // record the number of elements for next time return false; } byte[] b = new byte[recHeader]; raf.readFully(b); bytesRead += recHeader; ByteBuffer bb = ByteBuffer.wrap(b); int count = bb.getInt(8); if (count == 0) { return readHeader(); } if ((bytesRead + count * recSize) > totalBytes) { return false; } byte[] data = new byte[count * recSize]; raf.readFully(data); bytesRead += count * recSize; alreadyRead += count; nextIndex = 0; ByteBuffer bbdata = ByteBuffer.wrap(data); asbb = new ArrayStructureBB(sm, new int[]{count}, bbdata, 0); return true; } @Override public void setBufferSize(int bytes) { } @Override public int getCurrentRecno() { return done - 1; } @Override public void finish() { // ignored } } // this is thye start of a buffererd iterator /* private class MySDIter implements StructureDataIterator { private int done = 0; private int readStart = 0; private int recsAlreadyRead = 0; private int readAtaTime; private ArrayStructureBB asbb = null; private int recsLeft; private int recsDone; MySDIter(int bufferSize) throws IOException { setBufferSize( bufferSize); recsLeft = (int) raf.length() / recSize; recsDone = 0; } public boolean hasNext() { if (done < recsAlreadyRead) return true; return (recsLeft > 0); } public StructureDataIterator reset() { done = 0; readStart = 0; readRead = 0; return this; } public StructureData next() throws IOException { if (done >= readStart) { readNextBuffer(); } done++; return asbb.getStructureData( readRead++); } private void readNextBuffer() throws IOException { bytesLeft = (int)(raf.length() - raf.getFilePointer()); int recsLeft = bytesLeft / recSize; int recsToRead = Math.min(recsLeft, readAtaTime); byte[] bb = new byte[recsToRead * recSize]; ByteBuffer.wrap(bb); asbb = new ArrayStructureBB(sm, new int[]{nrecs}, bb, 0); recsAlreadyRead += recsToRead; readRead = 0; } public void setBufferSize(int bytes) { if (done > 0) return; // too late if (bytes <= 0) bytes = defaultBufferSize; readAtaTime = Math.max( 10, bytes / recSize); } } */ }