//
// AreaFile.java
//
/*
This source file is part of the edu.wisc.ssec.mcidas package and is
Copyright (C) 1998 - 2017 by Tom Whittaker, Tommy Jasmin, Tom Rink,
Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
and others.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package edu.wisc.ssec.mcidas;
import java.applet.Applet;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
import edu.wisc.ssec.mcidas.adde.GetAreaGUI;
/**
* AreaFile interface with McIDAS 'area' file format image data.
*
* <p>This will allow 'area' format data to be read from disk; the
* navigation block is made available (see GVARnav for example).</p>
*
* <p>Calibration is handled via classes that implement the {@link Calibrator}
* interface.</p>
*
* <p>This implementation does not check the 'valcode' on each line.</p>
*
* @author Tom Whittaker, SSEC
* @author Tommy Jasmin, SSEC
*
*/
public class AreaFile implements java.io.Serializable {
/** */
static final long serialVersionUID = 3145724093430859967L;
// indeces used by this and client classes
/** AD_STATUS - old status field, now used as position num in ADDE */
public static final int AD_STATUS = 0;
/** AD_VERSION - McIDAS area file format version number */
public static final int AD_VERSION = 1;
/** AD_SENSORID - McIDAS sensor identifier */
public static final int AD_SENSORID = 2;
/** AD_IMGDATE - nominal year and day of the image, YYYDDD format */
public static final int AD_IMGDATE = 3;
/** AD_IMGTIME - nominal time of the image, HHMMSS format */
public static final int AD_IMGTIME = 4;
/** AD_STLINE - upper left image line coordinate */
public static final int AD_STLINE = 5;
/** AD_STELEM - upper left image element coordinate */
public static final int AD_STELEM = 6;
/** AD_NUMLINES - number of lines in the image */
public static final int AD_NUMLINES = 8;
/** AD_NUMELEMS - number of data points per line */
public static final int AD_NUMELEMS = 9;
/** AD_DATAWIDTH - number of bytes per data point */
public static final int AD_DATAWIDTH = 10;
/** AD_LINERES - data resolution in line direction */
public static final int AD_LINERES = 11;
/** AD_ELEMRES - data resolution in element direction */
public static final int AD_ELEMRES = 12;
/** AD_NUMBANDS - number of spectral bands, or channels, in image */
public static final int AD_NUMBANDS = 13;
/** AD_PFXSIZE - length in bytes of line prefix section */
public static final int AD_PFXSIZE = 14;
/** AD_PROJNUM - SSEC project number used in creating image */
public static final int AD_PROJNUM = 15;
/** AD_CRDATE - year and day image was created, CCYYDDD format */
public static final int AD_CRDATE = 16;
/** AD_CRTIME - time image was created, HHMMSS format */
public static final int AD_CRTIME = 17;
/** AD_BANDMAP - spectral band map, bit set for each of 32 bands present */
public static final int AD_BANDMAP = 18;
/** AD_DATAOFFSET - byte offset to start of data block */
public static final int AD_DATAOFFSET = 33;
/** AD_NAVOFFSET - byte offset to start of navigation block */
public static final int AD_NAVOFFSET = 34;
/** AD_VALCODE - validity code */
public static final int AD_VALCODE = 35;
/** AD_STARTDATE - actual image start year and Julian day, yyyddd format */
public static final int AD_STARTDATE = 45;
/**
* AD_STARTTIME - actual image start time, hhmmss;
* in milliseconds for POES data
*/
public static final int AD_STARTTIME = 46;
/** AD_STARTSCAN - starting scan number (sensor based) of image */
public static final int AD_STARTSCAN = 47;
/** AD_DOCLENGTH - length in bytes of line prefix documentation */
public static final int AD_DOCLENGTH = 48;
/** AD_CALLENGTH - length in bytes of line prefix calibration information */
public static final int AD_CALLENGTH = 49;
/** AD_LEVLENGTH - length in bytes of line prefix level section */
public static final int AD_LEVLENGTH = 50;
/** AD_SRCTYPE - McIDAS source type (ascii, satellite specific) */
public static final int AD_SRCTYPE = 51;
/** AD_CALTYPE - McIDAS calibration type (ascii, satellite specific) */
public static final int AD_CALTYPE = 52;
/** AD_AVGSMPFLAG - data is averaged (1), or sampled (0) */
public static final int AD_AVGSMPFLAG = 53;
/** AD_SRCTYPEORIG - original source type (ascii, satellite specific) */
public static final int AD_SRCTYPEORIG = 56;
/** AD_CALTYPEUNIT - calibration unit */
public static final int AD_CALTYPEUNIT = 57;
/** AD_CALTYPESCALE - calibration scaling factor */
public static final int AD_CALTYPESCALE = 58;
/** AD_AUXOFFSET - byte offset to start of auxilliary data section */
public static final int AD_AUXOFFSET = 59;
/** AD_CALOFFSET - byte offset to start of calibration section */
public static final int AD_CALOFFSET = 62;
/** AD_NUMCOMMENTS - number of 80 character comment cards */
public static final int AD_NUMCOMMENTS = 63;
/** AD_DIRSIZE - size in 4 byte words of an image directory block */
public static final int AD_DIRSIZE = 64;
/** VERSION_NUMBER - version number for a valid AREA file (since 1985) */
public static final int VERSION_NUMBER = 4;
/** flag for whether a handler was loaded */
private static boolean handlerLoaded = false;
// load protocol handler for ADDE URLs
// See java.net.URL for explanation of URL handling
static {
try {
String handlers = System.getProperty("java.protocol.handler.pkgs");
String newProperty = null;
if (handlers == null) newProperty = "edu.wisc.ssec.mcidas";
else if (handlers.indexOf("edu.wisc.ssec.mcidas") < 0)
newProperty = "edu.wisc.ssec.mcidas | " + handlers;
if (newProperty != null) { // was set above
//Properties sysP = System.getProperties();
//sysP.put("java.protocol.handler.pkgs", newProperty);
//System.setProperties(sysP);
System.setProperty("java.protocol.handler.pkgs", newProperty);
}
handlerLoaded = true;
}
catch (Exception e) {
System.out.println(
"Unable to set System Property: java.protocol.handler.pkgs");
}
handlerLoaded = true;
}
/**
*
*
* @return status
*/
public static boolean isURLHandlerLoaded() {
return handlerLoaded;
}
/** flag for whether words should be flipped */
private boolean flipwords;
/** file ok flag */
private boolean fileok;
/** flag for whether or not the readData() has been called */
private boolean hasReadData;
/** the DataInputStream */
transient private DataInputStream af;
/** status flag */
// private int status = 0;
/** location of nav, cal, aux and data blocks */
private int navLoc, calLoc, auxLoc, datLoc;
/** the bytes for navigation, calibration and aux data */
private int navbytes, calbytes, auxbytes;
/** original line data length, line length, num lines/eles bands */
private int lineDataLen, lineLength, origNumLines, origNumElements,
origNumBands;
/** line prefix length */
private int linePrefixLength;
/** position indicator */
private long position;
/** bytes to skip */
private int skipByteCount;
/** new position */
private long newPosition;
/** the directory block */
int[] dir;
/** the nav block */
int[] nav = null;
/** the calibration block */
int[] cal = null;
/** the aux block */
int[] aux = null;
/** the data */
int[][][] data;
/** the AreaDirectory representing this image */
private AreaDirectory areaDirectory;
/** the image source */
private String imageSource = null;
/** the AREAnav for this image */
private AREAnav areaNav;
/** the calibration type */
private int calType = Calibrator.CAL_NONE;
/** flag for remote data */
private boolean isRemote = false;
// master of all subsetting paramters
private class Subset {
/** */
int lineNumber, numLines, lineMag, eleNumber, numEles, eleMag, bandNumber;
/**
*
*
* @return
*/
public String toString() {
return "Start_line:" + lineNumber + " Num_lines:" + numLines +
" Line_mag:" + lineMag + " Start_ele:" + eleNumber +
" Num_ele:" + numEles + " Ele_mag:" + eleMag + " Band:" +
bandNumber;
}
}
/** subset info holder - if subset is not null, we have been subsetted */
private Subset subset = null;
/**
* Creates an AreaFile object that allows reading
* of McIDAS 'area' file format image data. allows reading
* either from a disk file, or a server via ADDE.
*
* @param source the file name, ADDE URL, or local file URL to read from
*
* @exception AreaFileException if file cannot be opened
*/
public AreaFile(String source) throws AreaFileException {
imageSource = source;
if (imageSource.startsWith("adde://") &&
(imageSource.endsWith("image?") ||
imageSource.endsWith("imagedata?"))) {
GetAreaGUI gag = new GetAreaGUI((Frame)null, true, "Get data", false,
true);
gag.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
imageSource = e.getActionCommand();
}
});
gag.show();
}
// try as a disk file first
try {
af = new DataInputStream(new BufferedInputStream(new FileInputStream(imageSource),
2048));
}
catch (IOException eIO) {
// if opening as a file failed, try as a URL
URL url;
try {
url = new URL(imageSource);
// System.out.println(url);
URLConnection urlc = url.openConnection();
InputStream is = urlc.getInputStream();
af = new DataInputStream(new BufferedInputStream(is));
}
catch (IOException e) {
fileok = false;
throw new AreaFileException("Error opening AreaFile", e);
}
isRemote = url.getProtocol().equalsIgnoreCase("adde");
}
fileok = true;
position = 0;
readMetaData();
}
/**
* creates an AreaFile object that allows reading
* of McIDAS 'area' file format image data from an applet
*
* @param filename the disk filename (incl path) to read from
* @param parent the parent applet
*
* @exception AreaFileException if file cannot be opened
*
*/
public AreaFile(String filename, Applet parent) throws AreaFileException {
URL url;
imageSource = filename;
try {
url = new URL(parent.getDocumentBase(), filename);
}
catch (MalformedURLException e) {
fileok = false;
throw new AreaFileException("Error opening URL for AreaFile", e);
}
try {
af = new DataInputStream(new BufferedInputStream(url.openStream()));
}
catch (IOException e) {
fileok = false;
throw new AreaFileException("Error opening AreaFile", e);
}
isRemote = url.getProtocol().equalsIgnoreCase("adde");
fileok = true;
position = 0;
readMetaData();
}
private static final Logger LOGGER =
Logger.getLogger(AreaFile.class.getName());
/**
* create an <code>AreaFile</code> that allows reading of McIDAS 'area'
* file format image data from a <code>URL</code> with a protocol of either
* <code>file</code> or <code>ADDE</code>. See
* {@link edu.wisc.ssec.mcidas.adde.AddeURLConnection} for more information on
* constructing ADDE urls.
*
*
* @param url
* @exception AreaFileException if file cannot be opened
*/
public AreaFile(URL url) throws AreaFileException {
imageSource = url.toString();
try {
af = new DataInputStream(new BufferedInputStream(url.openStream()));
}
catch (IOException e) {
fileok = false;
LOGGER.log(Level.SEVERE, "UGH", e);
throw new AreaFileException("Error opening URL for AreaFile", e);
}
isRemote = url.getProtocol().equalsIgnoreCase("adde");
fileok = true;
position = 0;
readMetaData();
}
/**
* Create a subsetted instance. When subsetted, the data not included in the
* subset is no longer available to this instance due to the directory block
* being updated with the subsetting parameters.
*
*
* @param source the path to the file
* @param startLine the starting image line
* @param numLines the total number of lines to return
* @param lineMag the line magnification. Valid values are <= -1.
* -1, 0, and 1 are all taken to be full line resolution,
* -2 is every other line, -3 every third, etc...
* @param startElem the starting image element
* @param numEles the total number of elements to return
* @param eleMag the element magnification. Valid values are <= -1.
* -1, 0, and 1 are all taken to be full element resolution,
* -2 is every other element, -3 every third, etc...
* @param band the 1-based band number for the subset, which must be present
* in the directory blocks band map or -1 for the first band
*
* @throws AreaFileException if file cannot be opened
* @throws IllegalArgumentException If the magnification is greater than 1,
* the band number is not in the band map, or either of the following are
* true:
* <pre>
* startLine + (numLines * abs(lineMag)) > total number of lines
* startElem + (numEles * abs(eleMag)) > total number of elements
* </pre>
*/
public AreaFile(String source, int startLine, int numLines, int lineMag,
int startElem, int numEles, int eleMag, int band)
throws AreaFileException {
this(source);
// must have subsetted in the url
if (isSubsetted()) return;
// 0, 1, -1 are all full res
if (eleMag == 0) eleMag = 1;
if (lineMag == 0) lineMag = 1;
if (lineMag > 1 || eleMag > 1) {
throw new IllegalArgumentException("Magnifications greater that 1 are not currently supported");
}
else if (startLine + numLines * Math.abs(lineMag) > origNumLines ||
startElem + numEles * Math.abs(eleMag) > origNumElements) {
throw new IllegalArgumentException("Arguments outside of file line/element counts");
}
int bandIdx = -1;
int[] bands = getAreaDirectory().getBands();
if (band == -1) {
bandIdx = 0;
}
else {
for (int i = 0; i < bands.length; i++) {
if (bands[i] == band) bandIdx = i;
}
}
if (bandIdx == -1) {
throw new IllegalArgumentException("Band not found in band map");
}
// save subset data
subset = new Subset();
subset.lineNumber = startLine;
subset.numLines = numLines;
subset.lineMag = lineMag;
subset.eleNumber = startElem;
subset.numEles = numEles;
subset.eleMag = eleMag;
subset.bandNumber = band;
int newDatOffset = startLine * lineLength;
newDatOffset += linePrefixLength;
newDatOffset += startElem * (origNumBands * dir[AD_DATAWIDTH]);
newDatOffset += bandIdx * dir[AD_DATAWIDTH];
// reflect subset in directory
dir[AD_DATAOFFSET] = newDatOffset;
dir[AD_NUMLINES] = numLines;
dir[AD_NUMELEMS] = numEles;
dir[AD_NUMBANDS] = 1;
dir[AD_STLINE] = dir[AD_STLINE] + (startLine * dir[AD_LINERES]);
dir[AD_STELEM] = dir[AD_STELEM] + (startElem * dir[AD_ELEMRES]);
// if mag is positive divide to get resolution, otherwise mult.
// NOTE: resolution in area file is not necessarily full resolution, full
// resolution is based on the instrument, not the file.
if (lineMag < 0) {
dir[AD_LINERES] = dir[AD_LINERES] * Math.abs(lineMag);
}
else {
dir[AD_LINERES] = dir[AD_LINERES] / lineMag;;
}
if (eleMag < 0) {
dir[AD_ELEMRES] = dir[AD_ELEMRES] * Math.abs(eleMag);
}
else {
dir[AD_ELEMRES] = dir[AD_ELEMRES] / eleMag;
}
// set the band in the band map. Bandmap is in words 18 and 19
if (band <= 32) {
dir[AD_BANDMAP] = 1 << (band - 1);
}
else {
dir[AD_BANDMAP + 1] = 1 << (band - 32);
}
// FIXME update word 14?
// create new directory with subsetted parameters
areaDirectory = new AreaDirectory(dir);
}
/**
* Is this <code>AreaFile</code> instance subseted.
*
* @return True if this instance represents a subset of the total data
* available.
*/
public boolean isSubsetted() {
return !(subset == null);
}
/**
* Was this instance create with a remote data source.
* @return True if created with an ADDE url
*/
public boolean isRemote() {
return isRemote;
}
/**
* Read the metadata for an area file (directory, nav, and cal).
*
* @exception AreaFileException
* if there is a problem reading any portion of the metadata.
*
*/
private void readMetaData() throws AreaFileException {
int i;
hasReadData = false;
if (!fileok) {
throw new AreaFileException("Error reading AreaFile directory");
}
dir = new int[AD_DIRSIZE];
for (i = 0; i < AD_DIRSIZE; i++) {
try {
dir[i] = af.readInt();
}
catch (IOException e) {
throw new AreaFileException("Error reading AreaFile directory", e);
}
}
position += AD_DIRSIZE * 4;
// see if the directory needs to be byte-flipped
if (dir[AD_VERSION] != VERSION_NUMBER) {
McIDASUtil.flip(dir, 0, 19);
// check again
if (dir[AD_VERSION] != VERSION_NUMBER)
throw new AreaFileException("Invalid version number - probably not an AREA file");
// word 20 may contain characters -- if small integer, flip it...
if ((dir[20] & 0xffff) == 0) McIDASUtil.flip(dir, 20, 20);
McIDASUtil.flip(dir, 21, 23);
// words 24-31 contain memo field
McIDASUtil.flip(dir, 32, 50);
// words 51-2 contain cal info
McIDASUtil.flip(dir, 53, 55);
// word 56 contains original source type (ascii)
McIDASUtil.flip(dir, 57, 63);
flipwords = true;
}
areaDirectory = new AreaDirectory(dir);
// pull together some values needed by other methods
navLoc = dir[AD_NAVOFFSET];
calLoc = dir[AD_CALOFFSET];
auxLoc = dir[AD_AUXOFFSET];
datLoc = dir[AD_DATAOFFSET];
origNumBands = dir[AD_NUMBANDS];
linePrefixLength = dir[AD_DOCLENGTH] + dir[AD_CALLENGTH] +
dir[AD_LEVLENGTH];
if (dir[AD_VALCODE] != 0) linePrefixLength = linePrefixLength + 4;
if (linePrefixLength != dir[AD_PFXSIZE])
throw new AreaFileException("Invalid line prefix length in AREA file.");
lineDataLen = origNumBands * dir[AD_NUMELEMS] * dir[AD_DATAWIDTH];
lineLength = linePrefixLength + lineDataLen;
origNumLines = dir[AD_NUMLINES];
origNumElements = dir[AD_NUMELEMS];
if (datLoc > 0 && datLoc != McIDASUtil.MCMISSING) {
navbytes = datLoc - navLoc;
calbytes = datLoc - calLoc;
auxbytes = datLoc - auxLoc;
}
if (auxLoc > 0 && auxLoc != McIDASUtil.MCMISSING) {
navbytes = auxLoc - navLoc;
calbytes = auxLoc - calLoc;
}
if (calLoc > 0 && calLoc != McIDASUtil.MCMISSING) {
navbytes = calLoc - navLoc;
}
// Read in nav block
if (navLoc > 0 && navbytes > 0) {
nav = new int[navbytes / 4];
newPosition = (long)navLoc;
skipByteCount = (int)(newPosition - position);
try {
af.skipBytes(skipByteCount);
}
catch (IOException e) {
throw new AreaFileException("Error skipping AreaFile bytes", e);
}
for (i = 0; i < navbytes / 4; i++) {
try {
nav[i] = af.readInt();
}
catch (IOException e) {
throw new AreaFileException("Error reading AreaFile navigation", e);
}
}
if (flipwords) flipnav(nav);
position = navLoc + navbytes;
}
// Read in cal block
if (calLoc > 0 && calbytes > 0) {
cal = new int[calbytes / 4];
newPosition = (long)calLoc;
skipByteCount = (int)(newPosition - position);
try {
af.skipBytes(skipByteCount);
}
catch (IOException e) {
throw new AreaFileException("Error skipping AreaFile bytes", e);
}
for (i = 0; i < calbytes / 4; i++) {
try {
cal[i] = af.readInt();
}
catch (IOException e) {
throw new AreaFileException("Error reading AreaFile calibration", e);
}
}
if (flipwords) //flipcal(cal);
McIDASUtil.flip(cal, 0, cal.length-1);
position = calLoc + calbytes;
}
// Read in aux block
if (auxLoc > 0 && auxbytes > 0) {
aux = new int[auxbytes / 4];
newPosition = (long)auxLoc;
skipByteCount = (int)(newPosition - position);
try {
af.skipBytes(skipByteCount);
}
catch (IOException e) {
throw new AreaFileException("Error skipping AreaFile bytes", e);
}
for (i = 0; i < auxbytes / 4; i++) {
try {
aux[i] = af.readInt();
}
catch (IOException e) {
throw new AreaFileException("Error reading AreaFile aux block", e);
}
}
position = auxLoc + auxbytes;
}
// now return the Dir, as requested...
return;
}
/**
* returns the string of the image source location
*
* @return name of image source
*
*/
public String getImageSource() {
return imageSource;
}
/**
* Returns the directory block
*
* @return an integer array containing the area directory
*
*
*/
public int[] getDir() {
return dir;
}
/**
* Returns the AreaDirectory object for this AreaFile
*
* @return AreaDirectory
*
*
*/
public AreaDirectory getAreaDirectory() {
return areaDirectory;
}
/**
* Returns the navigation block
*
* @return an integer array containing the nav block data
*
*
*/
public int[] getNav() {
if (navLoc <= 0 || navLoc == McIDASUtil.MCMISSING) {
nav = null;
}
return nav;
}
/**
* Get the navigation, and pre-set it
* for the ImageStart and Res (from Directory block), and
* the file start (0,0), and Mag (1,1).
*
* @return AREAnav for this image (may be null)
*
* @throws AreaFileException
*/
public AREAnav getNavigation() throws AreaFileException {
if (areaNav == null) {
// make the nav module
try {
areaNav = AREAnav.makeAreaNav(getNav(), getAux());
areaNav.setImageStart(dir[AD_STLINE], dir[AD_STELEM]);
areaNav.setRes(dir[AD_LINERES], dir[AD_ELEMRES]);
areaNav.setStart(0, 0);
areaNav.setMag(1, 1);
}
catch (McIDASException excp) {
areaNav = null;
}
}
return areaNav;
}
/**
* Returns calibration block
*
* @return an integer array containing the nav block data
*
*
*/
public int[] getCal() {
if (calLoc <= 0 || calLoc == McIDASUtil.MCMISSING) {
cal = null;
}
return cal;
}
/**
* Returns AUX block
*
* @return an integer array containing the aux block data
*
*
*/
public int[] getAux() {
if (auxLoc <= 0 || auxLoc == McIDASUtil.MCMISSING) {
aux = null;
}
return aux;
}
/**
* Read the AREA data.
*
* @return int array[band][lines][element] - If the <code>AreaFile</code>
* was created as a subset only the band and subset indicated are returned,
* otherwise all bands are returned.
*
* @exception AreaFileException if there is a problem
*
*/
public int[][][] getData() throws AreaFileException {
data = new int[origNumBands][dir[AD_NUMLINES]][dir[AD_NUMELEMS]];
return getData(data);
}
/**
* Read AREA file data by reference. After reading the internal
* data array is will be a reference to the target array, so any changes made
* to the target array will be reflected in the internal data array.
*
* @param target Array to use as the destination of the data read. This array
* must be appropriately dimensioned as [#bands][#lines][#elems].
* @return int array[band][lines][element] - If the <code>AreaFile</code>
* was created as a subset only the band and subset indicated are returned,
* otherwise all bands are returned.
* @throws IllegalArgumentException If the target array is not dimensioned
* according to the subset, if subsetted, or otherwise has dimensions other than
* [bands][lines][elements].
* @throws AreaFileException If an error occurs while reading data.
*/
public int[][][] getData(int[][][] target) throws AreaFileException {
if (target == null ||
(isSubsetted() && target.length != 1 && target[0].length != subset.numLines
&& target[0][0].length != subset.numEles)
|| (target.length != origNumBands && target[0].length != dir[AD_NUMLINES]
&& target[0][0].length != dir[AD_NUMELEMS])) {
throw new IllegalArgumentException("target array is not dimensioned correctly");
}
if (!hasReadData) {
if (subset == null) {
readData(target);
}
else {
readData(target,
subset.lineNumber, subset.numLines, subset.lineMag,
subset.eleNumber, subset.numEles, subset.eleMag, subset.bandNumber);
}
}
hasReadData = true;
data = target;
return data;
}
/**
* Set the calibration type that will be used on data returned from
* <code>getCalibratedData()</code>. This must be called before
* <code>getCalibratedData()</code> to get calibrated data, otherwise it will
* just return the data in the format specified in the directory.
* @param cal calibration type from {@link Calibrator}.
*/
public void setCalType(int cal) {
calType = cal;
}
/**
* Get the calibration type that will be used on data returned form
* <code>getCalibratedData()</code>.
* @return calibration type from {@link Calibrator}.
*/
public int getCalType() {
return calType;
}
/**
* Read the AREA file and return the contents as floats. If the calibration
* type has been set via <code>setCalType()</code>, <code>isRemote()</code>
* is false, and a <code>Calibrator</code> is available data is calibrated
* before returning, otherwise the calibration type is ignored.
* The original data is alway preserved.
*
* @return data[band][lines][elements] as described above
* @throws AreaFileException on error reading data.
* @see Calibrator
*/
public float[][][] getFloatData() throws AreaFileException {
int[][][] inData = getData();
float[][][] outData =
new float[dir[AD_NUMBANDS]][dir[AD_NUMLINES]][dir[AD_NUMELEMS]];
// create the appropriate calibrator
Calibrator calibrator = null;
int origType =
AreaFileFactory.calStrToInt(areaDirectory.getCalibrationType());
if (!isRemote() && getCalType() != Calibrator.CAL_NONE &&
origType != getCalType()) {
try {
calibrator =
CalibratorFactory.getCalibrator(areaDirectory.getSensorID(), cal);
}
catch (CalibratorException e) {
// can't calibrate
}
}
// get all bands
if (subset == null) {
for (int band_idx = 0; band_idx < inData.length; band_idx++) {
for (int line = 0; line < inData[0].length; line++) {
for (int elem = 0; elem < inData[0][0].length; elem++) {
if (calibrator != null) {
outData[band_idx][line][elem] =
calibrator.calibrate((float)inData[band_idx][line][elem],
band_idx + 1, calType);
}
else {
outData[band_idx][line][elem] = inData[band_idx][line][elem];
}
}
}
}
// just subsetted band
}
else {
for (int line = 0; line < inData[0].length; line++) {
for (int elem = 0; elem < inData[0][0].length; elem++) {
if (!isRemote && calType != Calibrator.CAL_NONE &&
calibrator != null) {
outData[0][line][elem] =
calibrator.calibrate((float)inData[0][line][elem],
subset.bandNumber, calType);
}
else {
outData[0][line][elem] = inData[0][line][elem];
}
}
}
}
return outData;
}
/**
* Read the specified 2-dimensional array of
* data values from the AREA file. Values will always be returned
* as int regardless of whether they are 1, 2, or 4 byte values.
*
* @param lineNumber the file-relative image line number that will
* be put in array[0][j]
* @param eleNumber the file-relative image element number that will
* be put into array[i][0]
* @param numLines the number of lines to return
* @param numEles the number of elements to return for each line
*
* @return int array[lines][elements] with data values.
* @deprecated Use one of the factory methods from {@link AreaFileFactory}
* with the appropriate subsetting parameters.
* @exception AreaFileException if the is a problem reading the file
*/
public int[][] getData(int lineNumber, int eleNumber, int numLines,
int numEles)
throws AreaFileException {
return getData(lineNumber, eleNumber, numLines, numEles, 1);
}
/**
* Read the specified 2-dimensional array of
* data values from the AREA file. Values will always be returned
* as int regardless of whether they are 1, 2, or 4 byte values.
*
* @param lineNumber the file-relative image line number that will
* be put in array[0][j]
* @param eleNumber the file-relative image element number that will
* be put into array[i][0]
* @param numLines the number of lines to return
* @param numEles the number of elements to return for each line
* @param bandNumber the spectral band to return
*
* @return int array[lines][elements] with data values.
* @deprecated Use one of the factory methods from {@link AreaFileFactory}
* with the appropriate subsetting parameters.
* @exception AreaFileException if the is a problem reading the file
*/
public int[][] getData(int lineNumber, int eleNumber, int numLines,
int numEles, int bandNumber)
throws AreaFileException {
//data = new int[1][numLines][numEles];
if (!hasReadData) {
data = new int[origNumBands][dir[AD_NUMLINES]][dir[AD_NUMELEMS]];
readData(data);
}
int[][] subset = new int[numLines][numEles];
for (int i = 0; i < numLines; i++) {
int ii = i + lineNumber;
for (int j = 0; j < numEles; j++) {
int jj = j + eleNumber;
if (ii < 0 || ii > (dir[AD_NUMLINES] - 1) || jj < 0 ||
jj > (dir[AD_NUMELEMS] - 1)) {
subset[i][j] = 0;
}
else {
subset[i][j] = data[bandNumber - 1][ii][jj];
}
}
}
return subset;
}
/**
*
*
* @param s
*
* @return flipped value
*/
private int flipShort(short s) {
return (int)(((s >> 8) & 0xff) | ((s << 8) & 0xff00));
}
/**
*
*
* @param i
*
* @return flipped value
*/
private int flipInt(int i) {
return ((i >>> 24) & 0xff) | ((i >>> 8) & 0xff00) | ((i & 0xff) << 24)
| ((i & 0xff00) << 8);
}
/**
* Read a single band of subsetted data.
*
* @param lineNumber
* @param numLines
* @param lineMag
* @param eleNumber
* @param numEles
* @param eleMag
* @param bandNumber
*
* @throws AreaFileException
*/
private void readData(int[][][] target,
int lineNumber, int numLines, int lineMag,
int eleNumber, int numEles, int eleMag,
int bandNumber)
throws AreaFileException {
if (!fileok) {
throw new AreaFileException("Error reading AreaFile data");
}
// multipliers for line/element skips
int lineMagMult = (lineMag >= 1)
? 0
: Math.abs(lineMag) - 1;
int eleMagMult = (eleMag >= 1)
? 0
: Math.abs(eleMag) - 1;
int startLoc = dir[AD_DATAOFFSET];
int elementSize = origNumBands * dir[AD_DATAWIDTH];
int readElements = eleMagMult == 0
? numEles
: numEles * Math.abs(eleMag);
// num of lines to skip due to line resolution
int lineSkip = lineMagMult * lineLength;
// skip to read position on next line
int readSkip = (origNumElements - readElements) * elementSize +
linePrefixLength;
// number of element to skip due to resolution
int elementSkip = eleMagMult * elementSize;
// skip for bands we are not interrested in
int bandSkip = elementSize - dir[AD_DATAWIDTH];
int nextReadSkip = readSkip + lineSkip;
int nextElementSkip = bandSkip + elementSkip;
short shdata;
int intdata;
try {
DataInputStream df = getInputStreamForData();
if (df != af) {
af = df;
}
}
catch (IOException ioe) {
throw new AreaFileException("Error getting input stream for data", ioe);
}
try {
af.skipBytes(startLoc);
}
catch (IOException e) {
throw new AreaFileException("Error skipping to start of data", e);
}
for (int i = 0; i < numLines; i++) {
for (int j = 0; j < numEles; j++) {
try {
// all 1- and 2-byte data are un-signed!
if (dir[AD_DATAWIDTH] == 1) {
target[0][i][j] = ((int)af.readByte()) & 0xff;
}
else if (dir[AD_DATAWIDTH] == 2) {
shdata = af.readShort();
if (flipwords) {
target[0][i][j] = flipShort(shdata) & 0xffff;
}
else {
target[0][i][j] = ((int)shdata) & 0xffff;
}
}
else if (dir[AD_DATAWIDTH] == 4) {
intdata = af.readInt();
if (flipwords) {
target[0][i][j] = flipInt(intdata);
}
else {
target[0][i][j] = intdata;
}
}
af.skipBytes(nextElementSkip);
}
catch (IOException e) {
throw new AreaFileException("Error reading element " + i +
" in line " + j, e);
}
}
// done with line, skip to relavent element in next relavent line
try {
af.skipBytes(nextReadSkip);
}
catch (IOException e) {
throw new AreaFileException("Error skipping to next line", e);
}
}
}
/**
* Read all data including all bands.
*
* @throws AreaFileException
*/
private void readData(int[][][] target) throws AreaFileException {
int i, j, k;
int numLines = dir[AD_NUMLINES], numEles = dir[AD_NUMELEMS];
if (!fileok) {
throw new AreaFileException("Error reading AreaFile data");
}
try {
DataInputStream df = getInputStreamForData();
if (df != af) {
datLoc = 0;
position = 0;
af = df;
}
}
catch (IOException ioe) {
throw new AreaFileException("Error getting input stream for data", ioe);
}
short shdata;
int intdata;
for (i = 0; i < numLines; i++) {
try {
newPosition = (long)(datLoc + linePrefixLength + i * lineLength);
skipByteCount = (int)(newPosition - position);
af.skipBytes(skipByteCount);
position = newPosition;
}
catch (IOException e) {
for (j = 0; j < numEles; j++) {
for (k = 0; k < origNumBands; k++) {
target[k][i][j] = 0;
}
}
break;
}
for (j = 0; j < numEles; j++) {
for (k = 0; k < origNumBands; k++) {
if (j > lineDataLen) {
target[k][i][j] = 0;
}
else {
try {
// all 1- and 2-byte data are un-signed!
if (dir[AD_DATAWIDTH] == 1) {
target[k][i][j] = ((int)af.readByte()) & 0xff;
position = position + 1;
}
else if (dir[AD_DATAWIDTH] == 2) {
shdata = af.readShort();
if (flipwords) {
target[k][i][j] = flipShort(shdata) & 0xffff;
}
else {
target[k][i][j] = ((int)shdata) & 0xffff;
}
position = position + 2;
}
else if (dir[AD_DATAWIDTH] == 4) {
intdata = af.readInt();
if (flipwords) {
target[k][i][j] = flipInt(intdata);
}
else {
target[k][i][j] = intdata;
}
position = position + 4;
}
}
catch (IOException e) {
target[k][i][j] = 0;
}
}
}
}
}
hasReadData = true;
try {
af.close();
}
catch (IOException excp) {
System.out.println("Couldn't close input stream for " + imageSource);
}
return;
} // end of areaReadData method
/**
* Selectively flip the bytes of words in nav block
*
* @param nav array of nav parameters
*/
private void flipnav(int[] nav) {
// first word is always the satellite id in ASCII
// check on which type:
if (nav[0] == AREAnav.GVAR) {
McIDASUtil.flip(nav, 2, 126);
McIDASUtil.flip(nav, 129, 254);
McIDASUtil.flip(nav, 257, 382);
McIDASUtil.flip(nav, 385, 510);
McIDASUtil.flip(nav, 513, 638);
}
else if (nav[0] == AREAnav.DMSP) {
McIDASUtil.flip(nav, 1, 43);
McIDASUtil.flip(nav, 45, 51);
}
else if (nav[0] == AREAnav.POES) {
McIDASUtil.flip(nav, 1, 119);
}
else if (nav[0] == AREAnav.GMSX) {}
else {
McIDASUtil.flip(nav, 1, nav.length - 1);
}
return;
}
/**
* Get a String representation of this image metadata/information
*
* @return string of metadata
*/
public String toString() {
AreaDirectory dir = getAreaDirectory();
String EOL = "\n";
StringBuffer buff = new StringBuffer();
buff.append("Directory values =========" + EOL);
buff.append("Num Lines: " + dir.getLines() + EOL);
buff.append("Num Elements: " + dir.getElements() + EOL);
buff.append("Start Line: " + dir.getDirectoryBlock()[AD_STLINE] + EOL);
buff.append("Start Element: " + dir.getDirectoryBlock()[AD_STELEM] + EOL);
buff.append("Line Res: " + dir.getDirectoryBlock()[AD_LINERES] + EOL);
buff.append("Elem Res: " + dir.getDirectoryBlock()[AD_ELEMRES] + EOL);
buff.append("Bands:");
for (int i = 0; i < dir.getBands().length; i++)
buff.append(" " + dir.getBands()[i]);
buff.append(EOL);
buff.append("Source Type: " + dir.getSourceType() + EOL);
buff.append("Sensor Type: " + dir.getSensorType() + EOL);
buff.append("Sensor ID: " + dir.getSensorID() + EOL);
buff.append("Cal Type: " + dir.getCalibrationType() + EOL);
buff.append(
"Nominal Time: " + dir.getDirectoryBlock()[AD_IMGDATE] + " " +
dir.getDirectoryBlock()[AD_IMGTIME] + EOL);
buff.append("==========================" + EOL);
try {
buff.append("Nav: " + getNavigation() + EOL);
}
catch (AreaFileException e) {
}
buff.append(
"User Cal Type: " +
AreaFileFactory.calIntToStr(getCalType()).toUpperCase());
return buff.toString();
}
/**
* Test Method.
* <pre>
* USAGE: AreaFile <source> [(raw|temp|brit|rad|refl)]
* </pre>
* <p>If source is a file path or url without subsetting information directory
* information is printed. If source is a local file url with subsetting
* information data is printed according to the parameters.</p>
*
* <p>This has not been tested with an ADDE url, but it should work ...
* maybe.</p>
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
if (args == null || args.length == 0) {
System.out.println();
System.out.println("USAGE: AreaFile <URL or filepath> <show_vals>");
System.out.println();
System.exit(1);
}
AreaFile af = AreaFileFactory.getAreaFileInstance(args[0]);
System.out.println(af);
System.out.println();
System.out.println(af.subset);
System.out.println();
long time = System.currentTimeMillis();
System.out.print("Getting data ... ");
float data[][][] = af.getFloatData();
System.out.println(
"" + (System.currentTimeMillis() - time) + "ms to retrieve " +
AreaFileFactory.calIntToStr(af.getCalType()).toUpperCase() + " data");
System.out.println();
System.out.println(
"DATA [" + data.length + "][" + data[0].length + "][" +
data[0][0].length + "]");
if (args.length > 1 && !af.isSubsetted()) {
System.err.println("Sorry, I won't print an unsubsetted file");
System.exit(0);
}
if (args.length > 1) {
// write data to std err so it may be piped to file w/o all the
// other garbage
for (int i = 0; i < data[0].length; i++) {
for (int j = 0; j < data[0][0].length; j++) {
System.err.print("" + data[0][i][j] + " ");
}
System.err.println();
}
}
}
/**
* Get the input stream for the image. Handles Unidata PNG
* compressed images.
*
* @return the input stream for the reading
*
* @throws IOException
*/
private DataInputStream getInputStreamForData() throws IOException {
if (af.markSupported()) {
//System.out.println("mark is supported");
// calculate offset to potentially compressed data block
int numComments = dir[AD_NUMCOMMENTS];
// System.out.println("number of comment cards = " + numComments);
int compressedDataStart = numComments * 80 + datLoc;
af.mark((int)(compressedDataStart - position + 10));
af.skip(compressedDataStart - position);
byte[] test = new byte[8];
af.read(test);
af.reset();
if (isPNG(test)) {
//System.out.println("isPNG");
if (numComments > 0) {
byte[] comments = new byte[numComments * 80];
af.read(comments);
/*
for (int i = 0; i < numComments; i++) {
byte[] comment = new byte[80];
System.arraycopy(comments, i*80, comment, 0, 80);
System.out.println("card["+i+"] = " + new String(comment));
}
*/
}
int available = af.available();
byte[] data = new byte[available];
af.readFully(data);
af.close();
ByteArrayInputStream ios = new ByteArrayInputStream(data);
BufferedImage image = javax.imageio.ImageIO.read(ios);
Raster raster = image.getData();
DataBuffer db = raster.getDataBuffer();
if (db instanceof DataBufferByte) {
DataBufferByte dbb = (DataBufferByte)db;
byte[] udata = dbb.getData();
ByteArrayInputStream newios = new ByteArrayInputStream(udata);
return new DataInputStream(newios);
}
}
else {
return af;
}
}
return af;
}
/**
* Check if this is a PNG compressed image
*
* @param bytes bytes to check
*
* @return true if it fits the profile
*/
private boolean isPNG(byte[] bytes) {
if (bytes.length != 8) return false;
return bytes[0] == -119 && bytes[1] == 80 && // P
bytes[2] == 78 && // N
bytes[3] == 71 && // G
bytes[4] == 13 && bytes[5] == 10 && bytes[6] == 26 &&
bytes[7] == 10;
}
/**
* Close this instance.
*/
public void close() {
if (af == null) return;
try {
af.close();
}
catch (IOException ioe) {
}
}
/**
* Save this AreaFile to the output location
* @param outputFile path to the output file
*
* @throws AreaFileException problem saving to the file
*/
public void save(String outputFile) throws AreaFileException {
save(outputFile, false);
}
/**
* Save this AreaFile to the output location
* @param outputFile path to the output file
* @param verbose true to print out status messages
* @throws AreaFileException on any error writing the file
*/
public void save(String outputFile, boolean verbose)
throws AreaFileException {
int[] dir = getDir();
if (dir == null) {
System.out.println("No AREA file directory!");
return;
}
if (verbose) {
System.out.println("Length of directory = " + dir.length);
for (int i = 0; i < dir.length; i++) {
System.out.println(" index " + i + " = " + dir[i]);
}
}
int[] nav = getNav();
if (nav == null) {
if (verbose) System.out.println("No navigation block!");
}
else {
if (verbose) System.out.println("Length of nav block = " + nav.length);
}
int[] cal = getCal();
if (cal == null) {
if (verbose) System.out.println("No calibration block!");
}
else {
if (verbose) System.out.println("Length of cal block = " + cal.length);
}
int[] aux = getAux();
if (aux == null) {
if (verbose) System.out.println("No aux block");
}
else {
if (verbose) System.out.println("Length of aux block = " + aux.length);
}
int NL = dir[8];
int NE = dir[9];
if (verbose)
System.out.println("Start reading data, num points=" + (NL * NE));
int[][] data;
data = getData(0, 0, NL, NE);
if (verbose) System.out.println("Finished reading data");
try {
RandomAccessFile raf = new RandomAccessFile(outputFile, "rw");
if (verbose) System.out.println("Dir to word 0");
raf.seek(0);
dir[0] = 0; // make sure this is zero!!
for (int i = 0; i < dir.length; i++)
raf.writeInt(dir[i]);
if (verbose) System.out.println("Nav to word " + dir[AD_NAVOFFSET]);
if (nav != null && dir[AD_NAVOFFSET] > 0) {
raf.seek(dir[AD_NAVOFFSET]);
for (int i = 0; i < nav.length; i++)
raf.writeInt(nav[i]);
}
if (verbose) System.out.println("Cal to word " + dir[AD_CALOFFSET]);
if (cal != null && dir[AD_NAVOFFSET] > 0) {
raf.seek(dir[AD_CALOFFSET]);
for (int i = 0; i < cal.length; i++)
raf.writeInt(cal[i]);
}
if (verbose) System.out.println("Aux to word " + dir[AD_AUXOFFSET]);
if (aux != null && dir[AD_NAVOFFSET] > 0) {
raf.seek(dir[AD_AUXOFFSET]);
for (int i = 0; i < aux.length; i++)
raf.writeInt(aux[i]);
}
if (verbose) System.out.println("Data to word " + dir[AD_DATAOFFSET]);
if (dir[AD_NAVOFFSET] > 0) {
raf.seek(dir[AD_DATAOFFSET]);
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
if (dir[AD_DATAWIDTH] == 1) {
raf.writeByte(data[i][j]);
}
else if (dir[AD_DATAWIDTH] == 2) {
raf.writeShort(data[i][j]);
}
else if (dir[AD_DATAWIDTH] == 4) {
raf.writeInt(data[i][j]);
}
}
}
}
raf.close();
}
catch (Exception we) {
throw new AreaFileException("Unable to save file", we);
}
if (verbose)
System.out.println("Completed. Data saved to: " + outputFile);
}
}