package dods.clients.importwizard.TMAP.convert;
import java.util.StringTokenizer;
/**
* A class for conversions between <tt>double</tt> and
* <tt>String</tt> along a Longitude axis. The axis will have
* a range of acceptable values. This package is designed
* to be hooked up with TextInputFields for region specification
* on database servers.
*
* @version 0.1, Sep 03, 1997
* @author Jonathan Callahan
*
* This class may change substantially when ported to JDK 1.1 which
* contains a java.text.Format class. In the future, Convert and its
* subclasses may extend that class.
*
* This class may change substantially when ported to JDK 1.1 which
* contains a java.text.Format class. In the future, Convert and its
* subclasses may extend that class.
*
* This software was developed by the Thermal Modeling and Analysis
* Project(TMAP) of the National Oceanographic and Atmospheric
* Administration's (NOAA) Pacific Marine Environmental Lab(PMEL),
* hereafter referred to as NOAA/PMEL/TMAP.
*
* 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, the user
* agrees to credit NOAA/PMEL/TMAP in any publications that result from
* the use of this software or in any product that includes this
* software. The names TMAP, NOAA and/or PMEL, 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 NOAA/PMEL/TMAP. The user also understands that NOAA/PMEL/TMAP
* 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 NOAA/PMEL/TMAP "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 NOAA/PMEL/TMAP 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 TORTUOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
public class ConvertLongitude extends Convert {
public static final int M180_180 = 0;
public static final int ZERO_360 = 1;
public static final int E_W = 2;
public static final int SPACE_E_W = 3;
public static final int SPACE_EAST_WEST = 4;
/**
* The strings recognized as valid units. (always lower case)
*
*/
private String recognizedUnits[] = {
"deg", "degrees"
};
/**
* Creates a <B>Convert</B> object.
*
*/
public ConvertLongitude() {
setRange(-180.0, 180.0);
}
/**
* Creates a <B>ConvertLongitude</B> object with a predefined
* outputStyle.
*
* @param <TT>style</TT> one of the supported output styles.
*/
public ConvertLongitude(int style) {
this();
this.outputStyle = style;
}
/**
* Sets the output style for the toString() method.
*
* Supported output styles are:<BR>
* <UL>
* <LI>default (-115.25)
* <LI>ZERO_360 (244.75)
* <LI>E_W (115.25W)
* <LI>SPACE_E_W (115.25 W)
* <LI>SPACE_EAST_WEST (115.25 West)
* </UL>
*/
public void setOutputStyle(int style) {
outputStyle = style;
}
/**
* Sets the valid range for the <b>ConvertLongitude</b> object.
* All values are forced to be in the [-360:360] range. The
* range may have (hi < lo) as in [150:-90] but if more than one
* world is specified, (abs(hi-lo) > 360), all values will
* be converted to [-180:180]. When exactly one world is
* specified then the lo and hi values will be used such that
* (lo < hi).
*
* Thus (20:380) --> [-340:20]
* and (20:378) --> [-340:18]
* but (20:18) --> [20:18]
*
* Keep (150:-70) --> [150:-170]
* (40:280) --> [40:280]
*
* @param lo the lowest acceptable value.
* @param hi the highest acceptable value.
*/
public void setRange(double lo, double hi) throws IllegalArgumentException {
double rlo = lo;
double rhi = hi;
// Something like [20:380] will be converted to [-340:20]
if ( rlo > 0 && rhi > 360 ) {
System.out.println("ConvertLongitude: setRange(" + lo + ", " + hi +
") subtracting 360 to fit within internal [-360:360] range.");
rlo -= 360;
rhi -= 360;
} else if ( rlo < -360 && rhi < 0 ) {
System.out.println("ConvertLongitude: setRange(" + lo + ", " + hi +
") adding 360 to fit within internal [-360:360] range.");
rlo += 360;
rhi += 360;
}
// Preserve -360 and 360 but modulo anything outside that range
if ( rlo < -360 || rlo > 360.0 ) {
rlo = rlo%360;
}
if ( rhi < -360 || rhi > 360.0 ) {
rhi = rhi%360;
}
if ( Math.abs(hi-lo) > 360.0 ) {
// Something is screwy so force conversion to [-180:180]
if (rlo < -180) rlo += 360;
if (rhi < -180) rhi += 360;
if (rlo > 180) rlo -= 360;
if (rhi > 180) rhi -= 360;
} else if ( Math.abs(hi-lo) == 360.0 ) {
// put rlo before rhi if it's the whole world
if (rhi < rlo) {
range[LO] = rhi;
range[HI] = rlo;
}
}
range[LO] = rlo;
range[HI] = rhi;
}
/**
* Converts a Longitude string to a double value.
*
* Acceptable strings are:<BR>
* "45"<BR>
* "45.5"<BR>
* "-45"<BR>
* "45E"<BR>
* "-45W" (same as 45E)<BR>
* "45 54W" (= 45 degrees, 54 minutes West)<BR>
* "45 54.5" (= 45 degrees, 54.5 minutes East)<BR>
* "45 54 30" (= 45 degrees, 54 minutes, 30 seconds East)<BR>
* "455430N" (= 45 degrees, 54 minutes, 30 seconds East (USGS format))<BR>
*
* @param <TT>s</TT> String representing the Longitude.
* @return The double represented by the String.
* @exception IllegalArgumentException if <TT>s</TT> cannot be interpreted.
*
*/
public double toDouble(String s) throws IllegalArgumentException {
StringTokenizer st, check;
double lon = 0;
int Hemi = 1;
String deg, min, sec;
String lons = new String(s); // so we don't corrupt s
lons = lons.trim();
lons = lons.toUpperCase();
// Check if we have "E" or "W" at the end.
// If yes split it off as well as any whitespace.
//
if (lons.endsWith("E")) {
Hemi = 1;
lons = lons.substring(0,lons.length()-1).trim();
}
else if (lons.endsWith("W")) {
Hemi = -1;
lons = lons.substring(0,lons.length()-1).trim();
}
// this hack makes sure we only
// have "good" characters
check = new StringTokenizer(lons," +-.0123456789",false);
if (check.countTokens() > 0) throw new IllegalArgumentException("Bad character in string: \"" + s + "\".");
// break up by spaces
st = new StringTokenizer(lons," ",false);
switch(st.countTokens()) {
// only one token
//
case (1):
if ( lons.length() > 4 && lons.indexOf('.') == -1 ) {
// several digits and no decimal point => format = ddmmss
//
deg = lons.substring(0,lons.length()-4);
min = lons.substring(lons.length()-4,lons.length()-2);
sec = lons.substring(lons.length()-2,lons.length()-0);
try {
lon = Double.valueOf(deg).doubleValue();
lon += Double.valueOf(min).doubleValue() / 60.0;
lon += Double.valueOf(sec).doubleValue() / 3600.0;
} catch (NumberFormatException e) {
throw new IllegalArgumentException(e.toString());
}
} else {
// format = deg with decimals
//
try {
lon = Double.valueOf(lons).doubleValue();
} catch (NumberFormatException e) {
throw new IllegalArgumentException(e.toString());
}
}
break;
// two tokens => format = "deg mm.m"
//
case (2):
deg = st.nextToken();
min = st.nextToken();
try {
lon = Double.valueOf(deg).doubleValue();
lon += Double.valueOf(min).doubleValue() / 60.;
} catch (NumberFormatException e) {
throw new IllegalArgumentException(e.toString());
}
break;
// three tokens => format = "deg min sec"
//
case (3):
deg = st.nextToken();
min = st.nextToken();
sec = st.nextToken();
try {
lon = Double.valueOf(deg).doubleValue();
lon += Double.valueOf(min).doubleValue() / 60.0;
lon += Double.valueOf(sec).doubleValue() / 3600.0;
} catch (NumberFormatException e) {
throw new IllegalArgumentException(e.toString());
}
break;
default:
throw new IllegalArgumentException();
}
lon *= Hemi;
try {
lon = rangeTest(lon);
} finally {
;
}
return lon;
}
/**
* Converts a <tt>double</tt> value to a Longitude <tt>String</tt>.
*
* @param val double value.
* @return String the string representation of the input.
*/
public String toString(double val) {
StringBuffer sbuf = new StringBuffer("");
try {
val = rangeTest(val);
} catch (IllegalArgumentException e) {
System.out.println("toString(" + val + "," + "): " + e);
}
if ( val < 0.0 ) {
switch (outputStyle) {
case ZERO_360:
sbuf.append(val+360);
break;
case E_W:
sbuf.append(-val);
sbuf.append("W");
break;
case SPACE_E_W:
sbuf.append(-val);
sbuf.append(" W");
break;
case SPACE_EAST_WEST:
sbuf.append(-val);
sbuf.append(" West");
break;
default:
sbuf.append(val);
break;
}
} else {
switch (outputStyle) {
case ZERO_360:
sbuf.append(val);
break;
case E_W:
sbuf.append(val);
sbuf.append("E");
break;
case SPACE_E_W:
sbuf.append(val);
sbuf.append(" E");
break;
case SPACE_EAST_WEST:
sbuf.append(val);
sbuf.append(" East");
break;
default:
sbuf.append(val);
break;
}
}
return sbuf.toString();
}
/**
* Returns the nearest value within the range.
*
* @param <TT>lon</TT> The value of the Longitude.
* @return the nearest value within the range.
*/
public double getNearestValue(double val) {
try {
return rangeTest(val);
} catch (IllegalArgumentException e) {
System.out.println("getNearestValue(" + val + "): " + e);
if ( range[LO] < range[HI]) {
if ( val < range[LO] )
return range[LO];
else
return range[HI];
} else {
if ( val > range[LO] )
return range[LO];
else
return range[HI];
}
}
}
/**
* Returns the intersection of the incoming range within the
* internal range. An error is returned if there is no intersection.
*
* @param <TT>val_lo</TT> The lo value of the range to be tested.
* @param <TT>val_hi</TT> The hi value of the range to be tested.
* @return the nearest value within the range.
* @exception IllegalArgumentException <TT>range</TT> is outside
* the internally defined range.
*/
public double [] intersectRange(double val_lo, double val_hi) throws IllegalArgumentException {
double vlo = val_lo;
double vhi = val_hi;
double rlo = range[LO];
double rhi = range[HI];
double [] return_vals = {rlo, rhi};
// Preserve -360 and 360 but modulo anything outside that range
if ( vlo < -360 || vlo > 360.0 )
vlo = vlo%360;
if ( vhi < -360 || vhi > 360.0 )
vhi = vhi%360;
// If the inCOMING range represents the entire globe,
// then return the internal range values.
if ( Math.abs(vhi-vlo) == 360.0 ) {
return return_vals;
}
// If the inTERNAL range represents the entire globe,
// then every incoming range is valid and must only
// be adjusted to fit into the internal range.
if ( Math.abs(rhi-rlo) == 360.0 ) {
try {
return_vals[LO] = rangeTest(vlo);
return_vals[HI] = rangeTest(vhi);
return return_vals;
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("This should never happen! " + e.toString());
}
}
// We have a sub-global internal range and the
// incoming range is also sub-global.
// Convert everything to [-180:180]
if (vlo > 180)
vlo -=360;
else if (vlo < -180)
vlo +=360;
if (vhi > 180)
vhi -=360;
else if (vhi < -180)
vhi +=360;
if (rlo > 180)
rlo -=360;
else if (rlo < -180)
rlo +=360;
if (rhi > 180)
rhi -=360;
else if (rhi < -180)
rhi +=360;
// Adjust ranges that straddle the dateline
// Now everything will be [-180:540] but we are
// guaranteed that (lo < hi)
if ( rhi < rlo )
rhi += 360.0;
if ( vhi < vlo )
vhi += 360.0;
// Now slide the values over by 360 if necessary so that
// we are guaranteed to have an overlap if one exists.
//
// e.g. range=[-180:-150], vals=[-140:-150]-->[-140:410]
// We need to change vals again to vals=[-500:-150].
//
// For double overlap situations: r=[-150:150], v=[120:-120],
// the region returned will always be on the right hand side
// of the internal range.
if ( vlo > rhi ) {
vlo -= 360.0;
vhi -= 360.0;
} else if ( vhi < rlo ) {
vlo += 360.0;
vhi += 360.0;
}
// Now find the overlap.
if ( vlo > rhi || vhi < rlo ) {
throw new IllegalArgumentException("incoming range [" + val_lo + ", " +
val_hi + "] does not intersect range[" + range[LO] + "," + range[HI] + "].");
}
// Now we are guaranteed some overlap.
if ( vlo > rlo ) {
try {
return_vals[LO] = rangeTest(vlo);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("This should never happen! " + e.toString());
}
} else {
return_vals[LO] = range[LO];
}
if ( vhi < rhi ) {
try {
return_vals[HI] = rangeTest(vhi);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("This should never happen! " + e.toString());
}
} else {
return_vals[HI] = range[HI];
}
return return_vals;
}
/**
* Tests a value against the range.
*
* @param <TT>lon</TT> The value of the Longitude.
* @return The value passed in, possibly shifted by 360 deg
* to fit within the range.
* @exception IllegalArgumentException if <TT>val</TT> is
* outisde the specified range.
*/
protected double rangeTest(double val) throws IllegalArgumentException {
double return_val = val;
double newval = val;
double rlo = range[LO];
double rhi = range[HI];
boolean point_range = false;
if ( rlo == rhi )
point_range = true;
// Preserve -360 and 360 but modulo anything outside that range
if ( return_val < -360 || return_val > 360.0 ) {
return_val = return_val%360;
}
if ( newval < -360 || newval > 360.0 ) {
newval = newval%360;
}
// Convert everything to [-180:180]
if (newval > 180)
newval -=360;
else if (newval < -180)
newval +=360;
if (rlo > 180)
rlo -=360;
else if (rlo < -180)
rlo +=360;
if (rhi > 180)
rhi -=360;
else if (rhi < -180)
rhi +=360;
// Adjust rhi as necessary
if ( rhi == rlo && !point_range )
rhi +=360;
// Adjust ranges that straddle the dateline
if ( rhi < rlo ) {
rhi += 360.0;
}
// Give newval one chance to get within the range.
if ( newval < rlo ) {
newval += 360.0;
}
if ( newval < rlo || newval > rhi ) {
throw new IllegalArgumentException("value [" + val + "] outside of range[" +
range[LO] + "," + range[HI] + "].");
} else {
// We know everything is [-360:360] and that
// the value is in the range. So this should
// cover everything
if ( range[HI] < range[LO] ) { // i.e. [150:-150]
if (return_val < (range[LO]-360))
return_val += 360;
else if (return_val > (range[HI]+360))
return_val -= 360;
} else {
if (return_val < range[LO])
return_val += 360;
else if (return_val > range[HI])
return_val -= 360;
}
}
return return_val;
}
}