package tim.prune.data;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Locale;
+
/**
* Class to represent a lat/long coordinate
* and provide conversion functions
public static final int EAST = 1;
public static final int SOUTH = 2;
public static final int WEST = 3;
- public static final char[] PRINTABLE_CARDINALS = {'N', 'E', 'S', 'W'};
+ private static final char[] PRINTABLE_CARDINALS = {'N', 'E', 'S', 'W'};
public static final int FORMAT_DEG_MIN_SEC = 10;
public static final int FORMAT_DEG_MIN = 11;
public static final int FORMAT_DEG = 12;
public static final int FORMAT_DEG_WHOLE_MIN = 14;
public static final int FORMAT_DEG_MIN_SEC_WITH_SPACES = 15;
public static final int FORMAT_CARDINAL = 16;
+ public static final int FORMAT_DECIMAL_FORCE_POINT = 17;
public static final int FORMAT_NONE = 19;
+ /** Number formatter for fixed decimals with forced decimal point */
+ private static final NumberFormat EIGHT_DP = NumberFormat.getNumberInstance(Locale.UK);
+ // Select the UK locale for this formatter so that decimal point is always used (not comma)
+ static {
+ if (EIGHT_DP instanceof DecimalFormat) ((DecimalFormat) EIGHT_DP).applyPattern("0.00000000");
+ }
+
// Instance variables
private boolean _valid = false;
+ private boolean _cardinalGuessed = false;
protected int _cardinal = NORTH;
private int _degrees = 0;
private int _minutes = 0;
hasCardinal = false;
// use default from concrete subclass
_cardinal = getDefaultCardinal();
+ _cardinalGuessed = true;
+ }
+ else if (isJustNumber(inString)) {
+ // it's just a number
+ hasCardinal = false;
+ _cardinalGuessed = true;
}
// count numeric fields - 1=d, 2=dm, 3=dm.m/dms, 4=dms.s
char currChar;
long[] fields = new long[4]; // needs to be long for lengthy decimals
long[] denoms = new long[4];
- String secondDelim = "";
+ boolean[] otherDelims = new boolean[5]; // remember whether delimiters have non-decimal chars
try
{
// Loop over characters in input string, populating fields array
else
{
inNumeric = false;
- // Remember second delimiter
- if (numFields == 2) {
- secondDelim += currChar;
- }
+ // Remember delimiters
+ if (currChar != ',' && currChar != '.') {otherDelims[numFields] = true;}
}
}
_valid = (numFields > 0);
}
// parse fields according to number found
_degrees = (int) fields[0];
+ _asDouble = _degrees;
_originalFormat = hasCardinal?FORMAT_DEG:FORMAT_DEG_WITHOUT_CARDINAL;
_fracDenom = 10;
if (numFields == 2)
{
- // String is just decimal degrees
- double numMins = fields[1] * 60.0 / denoms[1];
- _minutes = (int) numMins;
- double numSecs = (numMins - _minutes) * 60.0;
- _seconds = (int) numSecs;
- _fracs = (int) ((numSecs - _seconds) * 10);
+ if (!otherDelims[1])
+ {
+ // String is just decimal degrees
+ double numMins = fields[1] * 60.0 / denoms[1];
+ _minutes = (int) numMins;
+ double numSecs = (numMins - _minutes) * 60.0;
+ _seconds = (int) numSecs;
+ _fracs = (int) ((numSecs - _seconds) * 10);
+ _asDouble = _degrees + 1.0 * fields[1] / denoms[1];
+ }
+ else
+ {
+ // String is degrees and minutes (due to non-decimal separator)
+ _originalFormat = FORMAT_DEG_MIN;
+ _minutes = (int) fields[1];
+ _seconds = 0;
+ _fracs = 0;
+ _asDouble = 1.0 * _degrees + (_minutes / 60.0);
+ }
}
// Differentiate between d-m.f and d-m-s using . or ,
- else if (numFields == 3 && (secondDelim.equals(".") || secondDelim.equals(",")))
+ else if (numFields == 3 && !otherDelims[2])
{
// String is degrees-minutes.fractions
_originalFormat = FORMAT_DEG_MIN;
double numSecs = fields[2] * 60.0 / denoms[2];
_seconds = (int) numSecs;
_fracs = (int) ((numSecs - _seconds) * 10);
+ _asDouble = 1.0 * _degrees + (_minutes / 60.0) + (numSecs / 3600.0);
}
else if (numFields == 4 || numFields == 3)
{
_fracs = (int) fields[3];
_fracDenom = (int) denoms[3];
if (_fracDenom < 1) {_fracDenom = 1;}
+ _asDouble = 1.0 * _degrees + (_minutes / 60.0) + (_seconds / 3600.0) + (_fracs / 3600.0 / _fracDenom);
}
- _asDouble = 1.0 * _degrees + (_minutes / 60.0) + (_seconds / 3600.0) + (_fracs / 3600.0 / _fracDenom);
if (_cardinal == WEST || _cardinal == SOUTH || inString.charAt(0) == '-')
_asDouble = -_asDouble;
// validate fields
return cardinal;
}
+ /**
+ * @return true if cardinal was guessed, false if parsed
+ */
+ public boolean getCardinalGuessed() {
+ return _cardinalGuessed;
+ }
/**
* Get the cardinal from the given character
*/
public boolean equals(Coordinate inOther)
{
- return (inOther != null && _cardinal == inOther._cardinal
- && _degrees == inOther._degrees
- && _minutes == inOther._minutes
- && _seconds == inOther._seconds
- && _fracs == inOther._fracs);
+ return (_asDouble == inOther._asDouble);
}
{
StringBuffer buffer = new StringBuffer();
buffer.append(PRINTABLE_CARDINALS[_cardinal])
- .append(threeDigitString(_degrees)).append('°')
+ .append(threeDigitString(_degrees)).append('\u00B0')
.append(twoDigitString(_minutes)).append('\'')
.append(twoDigitString(_seconds)).append('.')
.append(formatFraction(_fracs, _fracDenom));
}
case FORMAT_DEG_MIN:
{
- answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "°"
+ answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "\u00B0"
+ (_minutes + _seconds / 60.0 + _fracs / 60.0 / _fracDenom) + "'";
break;
}
case FORMAT_DEG_WHOLE_MIN:
{
- answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "°"
- + (int) Math.floor(_minutes + _seconds / 60.0 + _fracs / 60.0 / _fracDenom + 0.5) + "'";
+ int deg = _degrees;
+ int min = (int) Math.floor(_minutes + _seconds / 60.0 + _fracs / 60.0 / _fracDenom + 0.5);
+ if (min == 60) {
+ min = 0; deg++;
+ }
+ answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(deg) + "\u00B0" + min + "'";
break;
}
case FORMAT_DEG:
+ (_degrees + _minutes / 60.0 + _seconds / 3600.0 + _fracs / 3600.0 / _fracDenom);
break;
}
+ case FORMAT_DECIMAL_FORCE_POINT:
+ {
+ // Forcing a decimal point instead of system-dependent commas etc
+ if (_originalFormat != FORMAT_DEG_WITHOUT_CARDINAL || answer.indexOf('.') < 0) {
+ answer = EIGHT_DP.format(_asDouble);
+ }
+ break;
+ }
case FORMAT_DEG_MIN_SEC_WITH_SPACES:
{
// Note: cardinal not needed as this format is only for exif, which has cardinal separately
*/
protected abstract Coordinate makeNew(double inValue, int inFormat);
+ /**
+ * Try to parse the given string
+ * @param inString string to check
+ * @return true if it can be parsed as a number
+ */
+ private static boolean isJustNumber(String inString)
+ {
+ boolean justNum = false;
+ try {
+ double x = Double.parseDouble(inString);
+ justNum = (x >= -180.0 && x <= 360.0);
+ }
+ catch (NumberFormatException nfe) {} // flag remains false
+ return justNum;
+ }
/**
* Create a String representation for debug