X-Git-Url: https://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2Fdata%2FCoordinate.java;h=8b7904910b96e5da13445c7daa0aa352f15cdf41;hb=112bb0c9b46894adca9a33ed8c99ea712b253185;hp=6d8de61197bec7ae945658662c1060c4dd0e7663;hpb=d3679d647d57c2ee7376ddbf6def2d5b23c04307;p=GpsPrune.git diff --git a/tim/prune/data/Coordinate.java b/tim/prune/data/Coordinate.java index 6d8de61..8b79049 100644 --- a/tim/prune/data/Coordinate.java +++ b/tim/prune/data/Coordinate.java @@ -1,22 +1,38 @@ 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 abstract class Coordinate { + public static final int NO_CARDINAL = -1; public static final int NORTH = 0; 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_WITHOUT_CARDINAL = 13; + 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; protected int _cardinal = NORTH; @@ -24,6 +40,7 @@ public abstract class Coordinate private int _minutes = 0; private int _seconds = 0; private int _fracs = 0; + private int _fracDenom = 0; private String _originalString = null; private int _originalFormat = FORMAT_NONE; private double _asDouble = 0.0; @@ -44,16 +61,25 @@ public abstract class Coordinate } if (strLen > 1) { - // Check for leading character NSEW - _cardinal = getCardinal(inString.charAt(0)); + // Check for cardinal character either at beginning or end + boolean hasCardinal = true; + _cardinal = getCardinal(inString.charAt(0), inString.charAt(strLen-1)); + if (_cardinal == NO_CARDINAL) { + hasCardinal = false; + // use default from concrete subclass + _cardinal = getDefaultCardinal(); + } + // count numeric fields - 1=d, 2=dm, 3=dm.m/dms, 4=dms.s int numFields = 0; boolean inNumeric = false; char currChar; - long[] fields = new long[4]; + long[] fields = new long[4]; // needs to be long for lengthy decimals long[] denoms = new long[4]; + boolean[] otherDelims = new boolean[5]; // remember whether delimiters have non-decimal chars try { + // Loop over characters in input string, populating fields array for (int i=0; i 0); @@ -82,17 +110,32 @@ public abstract class Coordinate } // parse fields according to number found _degrees = (int) fields[0]; - _originalFormat = FORMAT_DEG; + _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); + } } - else if (numFields == 3) + // Differentiate between d-m.f and d-m-s using . or , + else if (numFields == 3 && !otherDelims[2]) { // String is degrees-minutes.fractions _originalFormat = FORMAT_DEG_MIN; @@ -100,29 +143,61 @@ public abstract class Coordinate 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) + else if (numFields == 4 || numFields == 3) { - _originalFormat = FORMAT_DEG_MIN_SEC; // String is degrees-minutes-seconds.fractions + _originalFormat = FORMAT_DEG_MIN_SEC; _minutes = (int) fields[1]; _seconds = (int) fields[2]; _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 / 36000.0); if (_cardinal == WEST || _cardinal == SOUTH || inString.charAt(0) == '-') _asDouble = -_asDouble; + // validate fields + _valid = _valid && (_degrees <= getMaxDegrees() && _minutes < 60 && _seconds < 60 && _fracs < _fracDenom); } else _valid = false; } + /** + * Get the cardinal from the given character + * @param inFirstChar first character from file + * @param inLastChar last character from file + */ + protected int getCardinal(char inFirstChar, char inLastChar) + { + // Try leading character first + int cardinal = getCardinal(inFirstChar); + // if not there, try trailing character + if (cardinal == NO_CARDINAL) { + cardinal = getCardinal(inLastChar); + } + return cardinal; + } + + /** * Get the cardinal from the given character * @param inChar character from file */ protected abstract int getCardinal(char inChar); + /** + * @return the default cardinal for the subclass + */ + protected abstract int getDefaultCardinal(); + + /** + * @return the maximum degree range for this coordinate + */ + protected abstract int getMaxDegrees(); + /** * Constructor @@ -134,12 +209,13 @@ public abstract class Coordinate { _asDouble = inValue; // Calculate degrees, minutes, seconds - _degrees = (int) inValue; - double numMins = (Math.abs(_asDouble)-Math.abs(_degrees)) * 60.0; + _degrees = (int) Math.abs(inValue); + double numMins = (Math.abs(_asDouble)-_degrees) * 60.0; _minutes = (int) numMins; double numSecs = (numMins - _minutes) * 60.0; _seconds = (int) numSecs; _fracs = (int) ((numSecs - _seconds) * 10); + _fracDenom = 10; // fixed for now // Make a string to display on screen _cardinal = inCardinal; _originalFormat = FORMAT_NONE; @@ -183,7 +259,6 @@ public abstract class Coordinate /** * Output the Coordinate in the given format - * @param inOriginalString the original String to use as default * @param inFormat format to use, eg FORMAT_DEG_MIN_SEC * @return String for output */ @@ -200,28 +275,70 @@ public abstract class Coordinate { 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(_fracs); - answer = buffer.toString(); break; + .append(formatFraction(_fracs, _fracDenom)); + answer = buffer.toString(); + break; } case FORMAT_DEG_MIN: { - answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "°" - + (_minutes + _seconds / 60.0 + _fracs / 600.0); break; + 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) + "\u00B0" + + (int) Math.floor(_minutes + _seconds / 60.0 + _fracs / 60.0 / _fracDenom + 0.5) + "'"; + break; } case FORMAT_DEG: case FORMAT_DEG_WITHOUT_CARDINAL: { answer = (_asDouble<0.0?"-":"") - + (_degrees + _minutes / 60.0 + _seconds / 3600.0 + _fracs / 36000.0); break; + + (_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 + 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 + answer = "" + _degrees + " " + _minutes + " " + _seconds + "." + formatFraction(_fracs, _fracDenom); + break; + } + case FORMAT_CARDINAL: + { + answer = "" + PRINTABLE_CARDINALS[_cardinal]; + break; } } } return answer; } + /** + * Format the fraction part of seconds value + * @param inFrac fractional part eg 123 + * @param inDenom denominator of fraction eg 10000 + * @return String describing fraction, in this case 0123 + */ + private static final String formatFraction(int inFrac, int inDenom) + { + if (inDenom <= 1 || inFrac == 0) {return "" + inFrac;} + String denomString = "" + inDenom; + int reqdLen = denomString.length() - 1; + String result = denomString + inFrac; + int resultLen = result.length(); + return result.substring(resultLen - reqdLen); + } + /** * Format an integer to a two-digit String @@ -261,10 +378,24 @@ public abstract class Coordinate */ public static Coordinate interpolate(Coordinate inStart, Coordinate inEnd, int inIndex, int inNumPoints) + { + return interpolate(inStart, inEnd, 1.0 * (inIndex+1) / (inNumPoints + 1)); + } + + + /** + * Create a new Coordinate between two others + * @param inStart start coordinate + * @param inEnd end coordinate + * @param inFraction fraction from start to end + * @return new Coordinate object + */ + public static Coordinate interpolate(Coordinate inStart, Coordinate inEnd, + double inFraction) { double startValue = inStart.getDouble(); double endValue = inEnd.getDouble(); - double newValue = startValue + (endValue - startValue) * (inIndex+1) / (inNumPoints + 1); + double newValue = startValue + (endValue - startValue) * inFraction; Coordinate answer = inStart.makeNew(newValue, inStart._originalFormat); return answer; } @@ -281,9 +412,11 @@ public abstract class Coordinate /** * Create a String representation for debug + * @return String describing coordinate value */ public String toString() { - return "Coord: " + _cardinal + " (" + _degrees + ") (" + _minutes + ") (" + _seconds + "." + _fracs + ") = " + _asDouble; + return "Coord: " + _cardinal + " (" + _degrees + ") (" + _minutes + ") (" + _seconds + "." + + formatFraction(_fracs, _fracDenom) + ") = " + _asDouble; } }