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