1 package tim.prune.load;
3 import tim.prune.I18nManager;
4 import tim.prune.data.Field;
5 import tim.prune.data.Latitude;
6 import tim.prune.data.Longitude;
7 import tim.prune.data.Timestamp;
10 * Class to try to match data with field names,
11 * using a variety of guessing techniques
13 public abstract class FieldGuesser
16 * Try to guess whether the given line is a header line or data
17 * @param inValues array of values from first non-blank line of file
18 * @return true if it looks like a header row, false if it looks like data
20 private static boolean isHeaderRow(String[] inValues)
22 // Loop over values seeing if any are mostly numeric
25 for (String value : inValues)
27 if (fieldLooksNumeric(value)) {return false;}
30 // No (mostly) numeric values found so presume header
36 * See if a field looks numeric or not by comparing the number of numeric vs non-numeric characters
37 * @param inValue field value to check
38 * @return true if there are more numeric characters than not
40 private static boolean fieldLooksNumeric(String inValue)
42 if (inValue == null) {
45 final int numChars = inValue.length();
46 if (numChars < 3) {return false;} // Don't care about one or two character values
47 // Loop through characters seeing which ones are numeric and which not
49 for (int i=0; i<numChars; i++)
51 char currChar = inValue.charAt(i);
52 if (currChar >= '0' && currChar <= '9') {numNums++;}
54 // Return true if more than half the characters are numeric
55 return numNums > (numChars/2);
59 * Try to guess the fields for the given values from the file
60 * @param inValues array of values from first non-blank line of file
61 * @return array of fields which hopefully match
63 public static Field[] guessFields(String[] inValues)
65 // Guess whether it's a header line or not
66 boolean isHeader = isHeaderRow(inValues);
67 // make array of Fields
68 int numFields = inValues.length;
69 Field[] fields = new Field[numFields];
70 // Loop over fields to try to guess the main ones
71 for (int f=0; f<numFields; f++)
73 if (inValues[f] != null) {
74 String value = inValues[f].trim();
76 if (!checkArrayHasField(fields, Field.LATITUDE) && fieldLooksLikeLatitude(value, isHeader))
78 fields[f] = Field.LATITUDE;
81 // check for longitude
82 if (!checkArrayHasField(fields, Field.LONGITUDE) && fieldLooksLikeLongitude(value, isHeader))
84 fields[f] = Field.LONGITUDE;
88 if (!checkArrayHasField(fields, Field.ALTITUDE) && fieldLooksLikeAltitude(value, isHeader))
90 fields[f] = Field.ALTITUDE;
93 // check for waypoint name
94 if (!checkArrayHasField(fields, Field.WAYPT_NAME) && fieldLooksLikeName(value, isHeader))
96 fields[f] = Field.WAYPT_NAME;
99 // check for timestamp
100 if (!checkArrayHasField(fields, Field.TIMESTAMP) && fieldLooksLikeTimestamp(value, isHeader))
102 fields[f] = Field.TIMESTAMP;
105 // check for tracksegment
106 if (!checkArrayHasField(fields, Field.NEW_SEGMENT) && fieldLooksLikeSegment(value, isHeader))
108 fields[f] = Field.NEW_SEGMENT;
111 // check for waypoint type
112 if (!checkArrayHasField(fields, Field.WAYPT_TYPE) && fieldLooksLikeWaypointType(value, isHeader))
114 fields[f] = Field.WAYPT_TYPE;
119 // Fill in the rest of the fields using just custom fields
120 // Could try to guess other fields (waypoint type, segment) or unguessed altitude, name, but keep simple for now
121 String customPrefix = I18nManager.getText("fieldname.prefix") + " ";
122 int customFieldNum = 0;
123 for (int f=0; f<numFields; f++) {
124 if (fields[f] == null)
126 // Make sure lat and long are filled in if not already
127 if (!checkArrayHasField(fields, Field.LATITUDE)) {
128 fields[f] = Field.LATITUDE;
130 else if (!checkArrayHasField(fields, Field.LONGITUDE)) {
131 fields[f] = Field.LONGITUDE;
135 // Can we use the field name given?
136 Field customField = null;
137 if (isHeader && inValues[f] != null && inValues[f].length() > 0) {
138 customField = new Field(inValues[f]);
140 // Find an unused field number
141 while (customField == null || checkArrayHasField(fields, customField))
144 customField = new Field(customPrefix + (customFieldNum));
146 fields[f] = customField;
151 // Do a final check to make sure lat and long are in there
152 if (!checkArrayHasField(fields, Field.LATITUDE)) {
153 fields[0] = Field.LATITUDE;
155 else if (!checkArrayHasField(fields, Field.LONGITUDE)) {
156 fields[1] = Field.LONGITUDE;
158 // Longitude _could_ have overwritten latitude in position 1
159 if (!checkArrayHasField(fields, Field.LATITUDE)) {
160 fields[0] = Field.LATITUDE;
167 * Check whether the given field array has the specified field
169 * @param inCheckField
170 * @return true if Field is contained within the array
172 private static boolean checkArrayHasField(Field[] inFields, Field inCheckField)
174 for (int f=0; f<inFields.length; f++)
176 if (inFields[f] != null && inFields[f].equals(inCheckField)) {
186 * Check whether the given String looks like a Latitude value
187 * @param inValue value from file
188 * @param inIsHeader true if this is a header line, false for data
189 * @return true if it could be latitude
191 private static boolean fieldLooksLikeLatitude(String inValue, boolean inIsHeader)
193 if (inValue == null || inValue.equals("")) {return false;}
196 // This is a header line so look for english or local text
197 String upperValue = inValue.toUpperCase();
198 return (upperValue.equals("LATITUDE")
199 || upperValue.equals(I18nManager.getText("fieldname.latitude").toUpperCase()));
203 // Note this will also catch longitudes too
204 Latitude lat = new Latitude(inValue);
205 return lat.isValid();
210 * Check whether the given String looks like a Longitude value
211 * @param inValue value from file
212 * @param inIsHeader true if this is a header line, false for data
213 * @return true if it could be longitude
215 private static boolean fieldLooksLikeLongitude(String inValue, boolean inIsHeader)
217 if (inValue == null || inValue.equals("")) {return false;}
220 // This is a header line so look for english or local text
221 String upperValue = inValue.toUpperCase();
222 return (upperValue.equals("LONGITUDE")
223 || upperValue.equals(I18nManager.getText("fieldname.longitude").toUpperCase()));
227 // Note this will also catch latitudes too
228 Longitude lon = new Longitude(inValue);
229 return lon.isValid();
234 * Check whether the given String looks like an Altitude value
235 * @param inValue value from file
236 * @param inIsHeader true if this is a header line, false for data
237 * @return true if it could be altitude
239 private static boolean fieldLooksLikeAltitude(String inValue, boolean inIsHeader)
241 if (inValue == null || inValue.equals("")) {return false;}
244 // This is a header line so look for english or local text
245 String upperValue = inValue.toUpperCase();
246 return (upperValue.equals("ALTITUDE")
247 || upperValue.equals("ALT")
248 || upperValue.equals("HMSL") // height above mean sea level
249 || upperValue.equals(I18nManager.getText("fieldname.altitude").toUpperCase()));
253 // Look for a number less than 100000
256 int intValue = Integer.parseInt(inValue);
257 return (intValue > 0 && intValue < 100000);
259 catch (NumberFormatException nfe) {}
266 * Check whether the given String looks like a waypoint name
267 * @param inValue value from file
268 * @param inIsHeader true if this is a header line, false for data
269 * @return true if it could be a name
271 private static boolean fieldLooksLikeName(String inValue, boolean inIsHeader)
273 if (inValue == null || inValue.equals("")) {return false;}
276 // This is a header line so look for english or local text
277 String upperValue = inValue.toUpperCase();
278 return (upperValue.equals("NAME")
279 || upperValue.equals("LABEL")
280 || upperValue.equals(I18nManager.getText("fieldname.waypointname").toUpperCase()));
284 // Look for at least two letters in it
286 for (int i=0; i<inValue.length(); i++)
288 char currChar = inValue.charAt(i);
289 if (Character.isLetter(currChar)) {
292 // Not interested if it contains ":" or "."
293 if (currChar == ':' || currChar == '.') {return false;}
295 return numLetters >= 2;
300 * Check whether the given String looks like a timestamp
301 * @param inValue value from file
302 * @param inIsHeader true if this is a header line, false for data
303 * @return true if it could be a timestamp
305 private static boolean fieldLooksLikeTimestamp(String inValue, boolean inIsHeader)
307 if (inValue == null || inValue.equals("")) {return false;}
310 String upperValue = inValue.toUpperCase();
311 // This is a header line so look for english or local text
312 return (upperValue.equals("TIMESTAMP")
313 || upperValue.equals("TIME")
314 || upperValue.equals(I18nManager.getText("fieldname.timestamp").toUpperCase()));
318 // must be at least 7 characters long
319 if (inValue.length() < 7) {return false;}
320 Timestamp stamp = new Timestamp(inValue);
321 return stamp.isValid();
326 * Check whether the given String looks like a track segment field
327 * @param inValue value from file
328 * @param inIsHeader true if this is a header line, false for data
329 * @return true if it could be a track segment
331 private static boolean fieldLooksLikeSegment(String inValue, boolean inIsHeader)
333 if (inValue == null || inValue.equals("")) {return false;}
336 String upperValue = inValue.toUpperCase();
337 // This is a header line so look for english or local text
338 return upperValue.equals("SEGMENT")
339 || upperValue.equals(I18nManager.getText("fieldname.newsegment").toUpperCase());
343 // can't reliably identify it just using the value
349 * Check whether the given String looks like a waypoint type
350 * @param inValue value from file
351 * @param inIsHeader true if this is a header line, false for data
352 * @return true if it could be a waypoint type
354 private static boolean fieldLooksLikeWaypointType(String inValue, boolean inIsHeader)
356 if (inValue == null || inValue.equals("")) {return false;}
359 String upperValue = inValue.toUpperCase();
360 // This is a header line so look for english or local text
361 return (upperValue.equals("TYPE")
362 || upperValue.equals(I18nManager.getText("fieldname.waypointtype").toUpperCase()));
366 // can't reliably identify it just using the value