]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/data/Coordinate.java
8a81850cba90e7c429696a3062dc3e5f96681a66
[GpsPrune.git] / tim / prune / data / Coordinate.java
1 package tim.prune.data;
2
3 /**
4  * Class to represent a lat/long coordinate
5  * and provide conversion functions
6  */
7 public abstract class Coordinate
8 {
9         public static final int NORTH = 0;
10         public static final int EAST = 1;
11         public static final int SOUTH = 2;
12         public static final int WEST = 3;
13         public static final char[] PRINTABLE_CARDINALS = {'N', 'E', 'S', 'W'};
14         public static final int FORMAT_DEG_MIN_SEC = 10;
15         public static final int FORMAT_DEG_MIN = 11;
16         public static final int FORMAT_DEG = 12;
17         public static final int FORMAT_DEG_WITHOUT_CARDINAL = 13;
18         public static final int FORMAT_DEG_WHOLE_MIN = 14;
19         public static final int FORMAT_DEG_MIN_SEC_WITH_SPACES = 15;
20         public static final int FORMAT_CARDINAL = 16;
21         public static final int FORMAT_NONE = 19;
22
23         // Instance variables
24         private boolean _valid = false;
25         protected int _cardinal = NORTH;
26         private int _degrees = 0;
27         private int _minutes = 0;
28         private int _seconds = 0;
29         private int _fracs = 0;
30         private String _originalString = null;
31         private int _originalFormat = FORMAT_NONE;
32         private double _asDouble = 0.0;
33
34
35         /**
36          * Constructor given String
37          * @param inString string to parse
38          */
39         public Coordinate(String inString)
40         {
41                 _originalString = inString;
42                 int strLen = 0;
43                 if (inString != null)
44                 {
45                         inString = inString.trim();
46                         strLen = inString.length();
47                 }
48                 if (strLen > 1)
49                 {
50                         // Check for leading character NSEW
51                         _cardinal = getCardinal(inString.charAt(0));
52                         // count numeric fields - 1=d, 2=dm, 3=dm.m/dms, 4=dms.s
53                         int numFields = 0;
54                         boolean inNumeric = false;
55                         char currChar;
56                         long[] fields = new long[4];
57                         long[] denoms = new long[4];
58                         try
59                         {
60                                 for (int i=0; i<strLen; i++)
61                                 {
62                                         currChar = inString.charAt(i);
63                                         if (currChar >= '0' && currChar <= '9')
64                                         {
65                                                 if (!inNumeric)
66                                                 {
67                                                         inNumeric = true;
68                                                         numFields++;
69                                                         denoms[numFields-1] = 1;
70                                                 }
71                                                 fields[numFields-1] = fields[numFields-1] * 10 + (currChar - '0');
72                                                 denoms[numFields-1] *= 10;
73                                         }
74                                         else
75                                         {
76                                                 inNumeric = false;
77                                         }
78                                 }
79                                 _valid = (numFields > 0);
80                         }
81                         catch (ArrayIndexOutOfBoundsException obe)
82                         {
83                                 // more than four fields found - unable to parse
84                                 _valid = false;
85                         }
86                         // parse fields according to number found
87                         _degrees = (int) fields[0];
88                         _originalFormat = FORMAT_DEG;
89                         if (numFields == 2)
90                         {
91                                 // String is just decimal degrees
92                                 double numMins = fields[1] * 60.0 / denoms[1];
93                                 _minutes = (int) numMins;
94                                 double numSecs = (numMins - _minutes) * 60.0;
95                                 _seconds = (int) numSecs;
96                                 _fracs = (int) ((numSecs - _seconds) * 10);
97                         }
98                         else if (numFields == 3)
99                         {
100                                 // String is degrees-minutes.fractions
101                                 _originalFormat = FORMAT_DEG_MIN;
102                                 _minutes = (int) fields[1];
103                                 double numSecs = fields[2] * 60.0 / denoms[2];
104                                 _seconds = (int) numSecs;
105                                 _fracs = (int) ((numSecs - _seconds) * 10);
106                         }
107                         else if (numFields == 4)
108                         {
109                                 _originalFormat = FORMAT_DEG_MIN_SEC;
110                                 // String is degrees-minutes-seconds.fractions
111                                 _minutes = (int) fields[1];
112                                 _seconds = (int) fields[2];
113                                 _fracs = (int) fields[3];
114                         }
115                         _asDouble = 1.0 * _degrees + (_minutes / 60.0) + (_seconds / 3600.0) + (_fracs / 36000.0);
116                         if (_cardinal == WEST || _cardinal == SOUTH || inString.charAt(0) == '-')
117                                 _asDouble = -_asDouble;
118                 }
119                 else _valid = false;
120         }
121
122
123         /**
124          * Get the cardinal from the given character
125          * @param inChar character from file
126          */
127         protected abstract int getCardinal(char inChar);
128
129
130         /**
131          * Constructor
132          * @param inValue value of coordinate
133          * @param inFormat format to use
134          * @param inCardinal cardinal
135          */
136         protected Coordinate(double inValue, int inFormat, int inCardinal)
137         {
138                 _asDouble = inValue;
139                 // Calculate degrees, minutes, seconds
140                 _degrees = (int) Math.abs(inValue);
141                 double numMins = (Math.abs(_asDouble)-_degrees) * 60.0;
142                 _minutes = (int) numMins;
143                 double numSecs = (numMins - _minutes) * 60.0;
144                 _seconds = (int) numSecs;
145                 _fracs = (int) ((numSecs - _seconds) * 10);
146                 // Make a string to display on screen
147                 _cardinal = inCardinal;
148                 _originalFormat = FORMAT_NONE;
149                 if (inFormat == FORMAT_NONE) inFormat = FORMAT_DEG_WITHOUT_CARDINAL;
150                 _originalString = output(inFormat);
151                 _originalFormat = inFormat;
152                 _valid = true;
153         }
154
155
156         /**
157          * @return coordinate as a double
158          */
159         public double getDouble()
160         {
161                 return _asDouble;
162         }
163
164         /**
165          * @return true if Coordinate is valid
166          */
167         public boolean isValid()
168         {
169                 return _valid;
170         }
171
172         /**
173          * Compares two Coordinates for equality
174          * @param inOther other Coordinate object with which to compare
175          * @return true if the two objects are equal
176          */
177         public boolean equals(Coordinate inOther)
178         {
179                 return (inOther != null && _cardinal == inOther._cardinal
180                         && _degrees == inOther._degrees
181                         && _minutes == inOther._minutes
182                         && _seconds == inOther._seconds
183                         && _fracs == inOther._fracs);
184         }
185
186
187         /**
188          * Output the Coordinate in the given format
189          * @param inOriginalString the original String to use as default
190          * @param inFormat format to use, eg FORMAT_DEG_MIN_SEC
191          * @return String for output
192          */
193         public String output(int inFormat)
194         {
195                 String answer = _originalString;
196                 if (inFormat != FORMAT_NONE && inFormat != _originalFormat)
197                 {
198                         // TODO: allow specification of precision for output of d-m and d
199                         // format as specified
200                         switch (inFormat)
201                         {
202                                 case FORMAT_DEG_MIN_SEC:
203                                 {
204                                         StringBuffer buffer = new StringBuffer();
205                                         buffer.append(PRINTABLE_CARDINALS[_cardinal])
206                                                 .append(threeDigitString(_degrees)).append('°')
207                                                 .append(twoDigitString(_minutes)).append('\'')
208                                                 .append(twoDigitString(_seconds)).append('.')
209                                                 .append(_fracs);
210                                         answer = buffer.toString();
211                                         break;
212                                 }
213                                 case FORMAT_DEG_MIN:
214                                 {
215                                         answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "°"
216                                                 + (_minutes + _seconds / 60.0 + _fracs / 600.0) + "'";
217                                         break;
218                                 }
219                                 case FORMAT_DEG_WHOLE_MIN:
220                                 {
221                                         answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "°"
222                                                 + (int) Math.floor(_minutes + _seconds / 60.0 + _fracs / 600.0 + 0.5) + "'";
223                                         break;
224                                 }
225                                 case FORMAT_DEG:
226                                 case FORMAT_DEG_WITHOUT_CARDINAL:
227                                 {
228                                         answer = (_asDouble<0.0?"-":"")
229                                                 + (_degrees + _minutes / 60.0 + _seconds / 3600.0 + _fracs / 36000.0);
230                                         break;
231                                 }
232                                 case FORMAT_DEG_MIN_SEC_WITH_SPACES:
233                                 {
234                                         answer = "" + _degrees + " " + _minutes + " " + _seconds + "." + _fracs;
235                                         break;
236                                 }
237                                 case FORMAT_CARDINAL:
238                                 {
239                                         answer = "" + PRINTABLE_CARDINALS[_cardinal];
240                                         break;
241                                 }
242                         }
243                 }
244                 return answer;
245         }
246
247
248         /**
249          * Format an integer to a two-digit String
250          * @param inNumber number to format
251          * @return two-character String
252          */
253         private static String twoDigitString(int inNumber)
254         {
255                 if (inNumber <= 0) return "00";
256                 if (inNumber < 10) return "0" + inNumber;
257                 if (inNumber < 100) return "" + inNumber;
258                 return "" + (inNumber % 100);
259         }
260
261
262         /**
263          * Format an integer to a three-digit String for degrees
264          * @param inNumber number to format
265          * @return three-character String
266          */
267         private static String threeDigitString(int inNumber)
268         {
269                 if (inNumber <= 0) return "000";
270                 if (inNumber < 10) return "00" + inNumber;
271                 if (inNumber < 100) return "0" + inNumber;
272                 return "" + (inNumber % 1000);
273         }
274
275
276         /**
277          * Create a new Coordinate between two others
278          * @param inStart start coordinate
279          * @param inEnd end coordinate
280          * @param inIndex index of point
281          * @param inNumPoints number of points to interpolate
282          * @return new Coordinate object
283          */
284         public static Coordinate interpolate(Coordinate inStart, Coordinate inEnd,
285                 int inIndex, int inNumPoints)
286         {
287                 double startValue = inStart.getDouble();
288                 double endValue = inEnd.getDouble();
289                 double newValue = startValue + (endValue - startValue) * (inIndex+1) / (inNumPoints + 1);
290                 Coordinate answer = inStart.makeNew(newValue, inStart._originalFormat);
291                 return answer;
292         }
293
294
295         /**
296          * Make a new Coordinate according to subclass
297          * @param inValue double value
298          * @param inFormat format to use
299          * @return object of Coordinate subclass
300          */
301         protected abstract Coordinate makeNew(double inValue, int inFormat);
302
303
304         /**
305          * Create a String representation for debug
306          */
307         public String toString()
308         {
309                 return "Coord: " + _cardinal + " (" + _degrees + ") (" + _minutes + ") (" + _seconds + "." + _fracs + ") = " + _asDouble;
310         }
311 }