//
// AreaFileFactory.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.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import edu.wisc.ssec.mcidas.adde.AddeURLException;
/**
* Utility class for creating <code>AreaFile</code> instances. This class
* handles subsetting local files using urls whereas the <code>AreaFile</code>
* constructors do not.
*
* <p>No instances of this class can be created.</p>
* @version $Id: AreaFileFactory.java,v 1.8 2009-07-01 12:51:04 donm Exp $
* @author Bruce Flynn, SSEC
*/
public final class AreaFileFactory {
/** No-op constructor preventing instatiation of this class. */
private AreaFileFactory() {}
/**
* String to calibration int.
* @param cal calibration type string
*
* @return calibration type integer, Calibrator.CAL_NONE if unknown
*/
public final static int calStrToInt(String cal) {
int ret = Calibrator.CAL_NONE;
String calType = cal.trim();
if (calType.equalsIgnoreCase("temp")) ret = Calibrator.CAL_TEMP;
else if (calType.equalsIgnoreCase("brit")) ret = Calibrator.CAL_BRIT;
else if (calType.equalsIgnoreCase("rad")) ret = Calibrator.CAL_RAD;
else if (calType.equalsIgnoreCase("raw")) ret = Calibrator.CAL_RAW;
else if (calType.equalsIgnoreCase("refl")) ret = Calibrator.CAL_ALB;
return ret;
}
/**
* Calibration int to string.
* @param cal calibration type
*
* @return calibration type string, raw if unknown
*/
public final static String calIntToStr(int cal) {
String ret = "raw";
switch (cal) {
case Calibrator.CAL_ALB:
ret = "refl";
break;
case Calibrator.CAL_BRIT:
ret = "brit";
break;
case Calibrator.CAL_RAD:
ret = "rad";
break;
case Calibrator.CAL_RAW:
ret = "raw";
break;
case Calibrator.CAL_TEMP:
ret = "temp";
break;
}
return ret;
}
/**
* Construct a url query string for subsetting a local file.
* @param sl starting line number.
* @param nl number of lines.
* @param lm line magnification.
* @param se starting element.
* @param ne number of elements.
* @param em element magnification.
* @param b band number.
* @return "?band=B&linele=SL SE&size=NL NE&mag=LM EM"
*/
public final static String makeLocalQuery(int sl, int nl, int lm, int se,
int ne, int em, int b) {
return "?band=" + b + "&linele=" + sl + " " + se + "&size=" + nl + " " +
ne + "&mag=" + lm + " " + em;
}
/**
* Construct a url query string for subsetting a local file.
* @param sl starting line number.
* @param nl number of lines.
* @param lm line magnification.
* @param se starting element.
* @param ne number of elements.
* @param em element magnification.
* @param b band number.
* @param c calibration type
* @return "?band=B&linele=SL SE&size=NL NE&mag=LM EM"&unit=C;
*/
public final static String makeLocalQuery(int sl, int nl, int lm, int se,
int ne, int em, int b, int c) {
String ct = calIntToStr(c);
return "?band=" + b + "&linele=" + sl + " " + se + "&size=" + nl + " " +
ne + "&mag=" + lm + " " + em + "&unit=" + ct;
}
/**
* Construct a url query string for subsetting a local file.
* @param sl starting line number.
* @param nl number of lines.
* @param lm line magnification.
* @param se starting element.
* @param ne number of elements.
* @param em element magnification.
* @param b band number.
* @param c calibration type as a string
* @return "?band=B&linele=SL SE&size=NL NE&mag=LM EM"&unit=C;
*/
public final static String makeLocalQuery(int sl, int nl, int lm, int se,
int ne, int em, int b, String c) {
return "?band=" + b + "&linele=" + sl + " " + se + "&size=" + nl + " " +
ne + "&mag=" + lm + " " + em + "&unit=" + c;
}
/**
* Construct a {@link java.net.URL} for subsetting a local file.
* @param fpath canonical path to an area file.
* @param sl starting line number.
* @param nl number of lines.
* @param lm line magnification.
* @param se starting element.
* @param ne number of elements.
* @param em element magnification.
* @param b band number.
* @param u calibration unit
* @return a string of the format <code>"file://FPATH?band=B&
* linele=SL SE&size=NL NE&mag=LM EM"&unit=U</code>
*
* @throws MalformedURLException bad URL specification
*/
public final static URL makeLocalSubsetURL(String fpath, int sl, int nl,
int lm, int se, int ne, int em,
int b, String u)
throws MalformedURLException {
String surl = "file://" + fpath +
makeLocalQuery(sl, nl, lm, se, ne, em, b, u);
URL url = null;
try {
url = new URL(surl);
}
catch (MalformedURLException e) {
// this cannot occur because the protocol is hardcoded to a known one
}
return url;
}
/**
* Create an initialized <code>AreaFile</code> instance. First, an attempt is
* made to create a <code>URL</code> from <code>src</code>, if an error
* occurrs, meaning <code>src</code> is not a valid url, <code>src</code> is
* interpreted as a file path.
* @param src A relative or canonical path to a local file as a string or a
* string representation of a url appropriate for creating an
* <code>AreaFile</code> instance. For more information on urls appropriate
* for creating <code>AreaFile</code> instances see
* {@link #getAreaFileInstance(URL)}.
* <p>URLs containing encoded characters in their query strings will be decoded
* before parsing.
* </p>
* @return an initialized, possibly subsetted, instance
* @throws AreaFileException on any error constructing the instance.
* @throws AddeURLException If the source is a URL and the query string is
* not formatted correctly.
*/
public static final AreaFile getAreaFileInstance(final String src)
throws AreaFileException, AddeURLException {
// handle the special "pop up a gui" case for adde://image?
if (src.startsWith("adde://") &&
(src.endsWith("image?") || src.endsWith("imagedata?"))) {
return new AreaFile(src);
}
// Try to create an instance as a URL first. If a valid URL instance cannot
// be created lets try 'src' as a filename.
// NOTE: it's important to make sure the AreaFile and AddeURL exceptions
// are propagated from constructor or factory method.
AreaFile af = null;
URL url = null;
try {
url = new URL(src);
af = getAreaFileInstance(url);
}
catch (MalformedURLException e) {
af = new AreaFile(src);
}
return af;
}
/**
* Create an initialized <code>AreaFile</code> instance.
*
* <p>A url appropriate for creating an instance will have a protocol of
* either <code>adde</code> for remote ADDE data or <code>file</code> for
* files on the local disk. Information on consructing ADDE urls can be found
* in the {@link edu.wisc.ssec.mcidas.adde.AddeURLConnection} class.</p>
*
* <p>A local file url may either be a standard file url such as
* file:///<absolute file path> or it may specify subsetting
* information. If specifying subsetting information, the url can contain the
* following parameters from the ADDE image data url specification with the
* specified defaults:
* <pre>
* NAME DEFAULT
* linele - 0 0 a Must be used with size.
* NOTE: only type 'a' is supported at this time
* size - 0 0 Must be used with linele.
* mag - 1 1 Only with linele and size, but not required.
* band - 1 Can be used separately, not required.
* unit - RAW Calibration type
* </pre>
* A file url might look like:
* <pre>
* file://<abs file path>?linele=10 10&band=3&mag=-2 -4&size=500 500&unit=BRIT
* </pre>
* </p>
* <p>URLs containing encoded characters in their query strings will be decoded
* before parsing.
* </p>
* @param url - the url as described above
* @return an initialized, possibly subsetted, instance
* @throws AreaFileException on any error constructing the instance.
* @throws AddeURLException if the query string is not a valid ADDE query
* stirng.
*/
public static final AreaFile getAreaFileInstance(final URL url)
throws AddeURLException, AreaFileException {
// it's a local file, investigate further
if (url.getProtocol().equalsIgnoreCase("file")) {
AreaFile af = null;
if (url.getQuery() == null) {
af = new AreaFile(url);
}
else {
// open w/o query string
af = new AreaFile(url.toString().split("\\?")[0]);
}
// is it a local url with subsetting
String query = url.getQuery();
if (query != null && query.contains("%")) {
try {
query = url.toURI().getQuery(); // URI queries are decoded
}
catch (URISyntaxException e) {
throw new AddeURLException("URL decoding failed", e);
}
}
if (query != null) {
final String whtspc = "(\\s)+";
int startLine = 0;
int numLines = af.getAreaDirectory().getLines();
int lineMag = 1;
int startElem = 0;
int numEles = af.getAreaDirectory().getElements();
int eleMag = 1;
int band = -1;
int calType = Calibrator.CAL_NONE;
boolean linele = false;
boolean size = false;
boolean mag = false;
int availableLines = numLines;
int availableEles = numEles;
// parse query string
String[] props = query.split("&");
for (int i = 0; i < props.length; i++) {
String[] kv = props[i].split("=");
if (kv[0].equalsIgnoreCase("mag")) {
lineMag = Integer.parseInt(kv[1].split(whtspc)[0]);
eleMag = Integer.parseInt(kv[1].split(whtspc)[1]);
mag = true;
}
if (kv[0].equalsIgnoreCase("size")) {
numLines = Integer.parseInt(kv[1].split(whtspc)[0]);
numEles = Integer.parseInt(kv[1].split(whtspc)[1]);
size = true;
}
if (kv[0].equalsIgnoreCase("band")) {
int bandIdx = -1;
int[] bands = af.getAreaDirectory().getBands();
if (band == -1) {
bandIdx = 0;
}
else {
for (int j = 0; i < bands.length; j++) {
if (bands[j] == band) {
bandIdx = j;
}
}
}
band = bands[bandIdx];
}
if (kv[0].equalsIgnoreCase("linele")) {
String[] vals = kv[1].split(whtspc);
startLine = Integer.parseInt(vals[0]);
startElem = Integer.parseInt(vals[1]);
if (vals.length >= 3 && !vals[2].equalsIgnoreCase("a")) {
throw new AddeURLException("Image and earth types are not currenly supported");
}
availableLines -= startLine;
availableEles -= startElem;
linele = true;
}
if (kv[0].equalsIgnoreCase("unit")) {
calType = calStrToInt(kv[1]);
switch (calType) {
case Calibrator.CAL_ALB:
case Calibrator.CAL_BRIT:
case Calibrator.CAL_RAD:
case Calibrator.CAL_TEMP:
case Calibrator.CAL_RAW:
case Calibrator.CAL_NONE:
break;
default:
throw new AreaFileException("Unsupported calibration type: " +
kv[1]);
}
}
}
if (mag) {
availableLines = availableLines / (Math.abs(lineMag) == 0
? 1
: Math.abs(lineMag));
availableEles = availableEles / (Math.abs(eleMag) == 0
? 1
: Math.abs(eleMag));
if (size) {
numLines = Math.min(availableLines, numLines);
numEles = Math.min(availableEles, numEles);
}
else {
numLines = availableLines;
numEles = availableEles;
}
}
// recreate with new parameters
af = new AreaFile(url.getPath(), startLine, numLines, lineMag,
startElem, numEles, eleMag, band);
af.setCalType(calType);
}
return af;
}
else {
return new AreaFile(url);
}
}
/**
* See {@link AreaFile#AreaFile(String, int, int, int, int, int, int, int)}
*
* @param fpath 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
*
* @return the AreaFile instance
*
* @throws AreaFileException
*/
public final static AreaFile getAreaFileInstance(String fpath,
int startLine, int numLines, int lineMag, int startElem,
int numEles, int eleMag, int band)
throws AreaFileException {
return new AreaFile(fpath, startLine, numLines, lineMag, startElem,
numEles, eleMag, band);
}
/**
* Copy an area file from one place to another
* @param source source file or ADDE url
* @param outputFile name of the output file
* @throws AreaFileException on any error constructing the instance.
* @throws AddeURLException If the source is a URL and the query string is
* not formatted correctly.
*/
public static void copyAreaFile(String source, String outputFile)
throws AddeURLException, AreaFileException {
copyAreaFile(source, outputFile, false);
}
/**
* Copy an area file from one place to another
* @param source source file or ADDE url
* @param outputFile name of the output file
* @param verbose true to print out status messages
* @throws AreaFileException on any error constructing the instance.
* @throws AddeURLException If the source is a URL and the query string is
* not formatted correctly.
*/
public static void copyAreaFile(String source, String outputFile,
boolean verbose)
throws AddeURLException, AreaFileException {
AreaFile area = getAreaFileInstance(source);
area.save(outputFile, verbose);
}
}