]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/load/FieldGuesser.java
Version 19, May 2018
[GpsPrune.git] / tim / prune / load / FieldGuesser.java
1 package tim.prune.load;
2
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.TimestampUtc;
8
9 /**
10  * Class to try to match data with field names,
11  * using a variety of guessing techniques
12  */
13 public abstract class FieldGuesser
14 {
15         /**
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
19          */
20         private static boolean isHeaderRow(String[] inValues)
21         {
22                 // Loop over values seeing if any are mostly numeric
23                 if (inValues != null)
24                 {
25                         for (String value : inValues)
26                         {
27                                 if (fieldLooksNumeric(value)) {return false;}
28                         }
29                 }
30                 // No (mostly) numeric values found so presume header
31                 return true;
32         }
33
34
35         /**
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
39          */
40         private static boolean fieldLooksNumeric(String inValue)
41         {
42                 if (inValue == null) {
43                         return false;
44                 }
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
48                 int numNums = 0;
49                 for (int i=0; i<numChars; i++)
50                 {
51                         char currChar = inValue.charAt(i);
52                         if (currChar >= '0' && currChar <= '9') {numNums++;}
53                 }
54                 // Return true if more than half the characters are numeric
55                 return numNums > (numChars/2);
56         }
57
58         /**
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
62          */
63         public static Field[] guessFields(String[] inValues)
64         {
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++)
72                 {
73                         if (inValues[f] != null) {
74                                 String value = inValues[f].trim();
75                                 // check for latitude
76                                 if (!checkArrayHasField(fields, Field.LATITUDE) && fieldLooksLikeLatitude(value, isHeader))
77                                 {
78                                         fields[f] = Field.LATITUDE;
79                                         continue;
80                                 }
81                                 // check for longitude
82                                 if (!checkArrayHasField(fields, Field.LONGITUDE) && fieldLooksLikeLongitude(value, isHeader))
83                                 {
84                                         fields[f] = Field.LONGITUDE;
85                                         continue;
86                                 }
87                                 // check for altitude
88                                 if (!checkArrayHasField(fields, Field.ALTITUDE) && fieldLooksLikeAltitude(value, isHeader))
89                                 {
90                                         fields[f] = Field.ALTITUDE;
91                                         continue;
92                                 }
93                                 // check for waypoint name
94                                 if (!checkArrayHasField(fields, Field.WAYPT_NAME) && fieldLooksLikeName(value, isHeader))
95                                 {
96                                         fields[f] = Field.WAYPT_NAME;
97                                         continue;
98                                 }
99                                 // check for timestamp
100                                 if (!checkArrayHasField(fields, Field.TIMESTAMP) && fieldLooksLikeTimestamp(value, isHeader))
101                                 {
102                                         fields[f] = Field.TIMESTAMP;
103                                         continue;
104                                 }
105                                 // check for tracksegment
106                                 if (!checkArrayHasField(fields, Field.NEW_SEGMENT) && fieldLooksLikeSegment(value, isHeader))
107                                 {
108                                         fields[f] = Field.NEW_SEGMENT;
109                                         continue;
110                                 }
111                                 // check for waypoint type
112                                 if (!checkArrayHasField(fields, Field.WAYPT_TYPE) && fieldLooksLikeWaypointType(value, isHeader))
113                                 {
114                                         fields[f] = Field.WAYPT_TYPE;
115                                         continue;
116                                 }
117                         }
118                 }
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)
125                         {
126                                 // Make sure lat and long are filled in if not already
127                                 if (!checkArrayHasField(fields, Field.LATITUDE)) {
128                                         fields[f] = Field.LATITUDE;
129                                 }
130                                 else if (!checkArrayHasField(fields, Field.LONGITUDE)) {
131                                         fields[f] = Field.LONGITUDE;
132                                 }
133                                 else
134                                 {
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]);
139                                         }
140                                         // Find an unused field number
141                                         while (customField == null || checkArrayHasField(fields, customField))
142                                         {
143                                                 customFieldNum++;
144                                                 customField = new Field(customPrefix + (customFieldNum));
145                                         }
146                                         fields[f] = customField;
147                                 }
148                         }
149                 }
150
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;
154                 }
155                 else if (!checkArrayHasField(fields, Field.LONGITUDE)) {
156                         fields[1] = Field.LONGITUDE;
157                 }
158                 // Longitude _could_ have overwritten latitude in position 1
159                 if (!checkArrayHasField(fields, Field.LATITUDE)) {
160                         fields[0] = Field.LATITUDE;
161                 }
162                 return fields;
163         }
164
165
166         /**
167          * Check whether the given field array has the specified field
168          * @param inFields
169          * @param inCheckField
170          * @return true if Field is contained within the array
171          */
172         private static boolean checkArrayHasField(Field[] inFields, Field inCheckField)
173         {
174                 for (int f=0; f<inFields.length; f++)
175                 {
176                         if (inFields[f] != null && inFields[f].equals(inCheckField)) {
177                                 return true;
178                         }
179                 }
180                 // not found
181                 return false;
182         }
183
184
185         /**
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
190          */
191         private static boolean fieldLooksLikeLatitude(String inValue, boolean inIsHeader)
192         {
193                 if (inValue == null || inValue.equals("")) {return false;}
194                 if (inIsHeader)
195                 {
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()));
200                 }
201                 else
202                 {
203                         // Note this will also catch longitudes too
204                         Latitude lat = new Latitude(inValue);
205                         return lat.isValid();
206                 }
207         }
208
209         /**
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
214          */
215         private static boolean fieldLooksLikeLongitude(String inValue, boolean inIsHeader)
216         {
217                 if (inValue == null || inValue.equals("")) {return false;}
218                 if (inIsHeader)
219                 {
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()));
224                 }
225                 else
226                 {
227                         // Note this will also catch latitudes too
228                         Longitude lon = new Longitude(inValue);
229                         return lon.isValid();
230                 }
231         }
232
233         /**
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
238          */
239         private static boolean fieldLooksLikeAltitude(String inValue, boolean inIsHeader)
240         {
241                 if (inValue == null || inValue.equals("")) {return false;}
242                 if (inIsHeader)
243                 {
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()));
250                 }
251                 else
252                 {
253                         // Look for a number less than 100000
254                         try
255                         {
256                                 int intValue = Integer.parseInt(inValue);
257                                 return (intValue > 0 && intValue < 100000);
258                         }
259                         catch (NumberFormatException nfe) {}
260                         return false;
261                 }
262         }
263
264
265         /**
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
270          */
271         private static boolean fieldLooksLikeName(String inValue, boolean inIsHeader)
272         {
273                 if (inValue == null || inValue.equals("")) {return false;}
274                 if (inIsHeader)
275                 {
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()));
281                 }
282                 else
283                 {
284                         // Look for at least two letters in it
285                         int numLetters = 0;
286                         for (int i=0; i<inValue.length(); i++)
287                         {
288                                 char currChar = inValue.charAt(i);
289                                 if (Character.isLetter(currChar)) {
290                                         numLetters++;
291                                 }
292                                 // Not interested if it contains ":" or "."
293                                 if (currChar == ':' || currChar == '.') {return false;}
294                         }
295                         return numLetters >= 2;
296                 }
297         }
298
299         /**
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
304          */
305         private static boolean fieldLooksLikeTimestamp(String inValue, boolean inIsHeader)
306         {
307                 if (inValue == null || inValue.equals("")) {return false;}
308                 if (inIsHeader)
309                 {
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()));
315                 }
316                 else
317                 {
318                         // must be at least 7 characters long
319                         if (inValue.length() < 7) {return false;}
320                         TimestampUtc stamp = new TimestampUtc(inValue);
321                         return stamp.isValid();
322                 }
323         }
324
325         /**
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
330          */
331         private static boolean fieldLooksLikeSegment(String inValue, boolean inIsHeader)
332         {
333                 if (inValue == null || inValue.equals("")) {return false;}
334                 if (inIsHeader)
335                 {
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());
340                 }
341                 else
342                 {
343                         // can't reliably identify it just using the value
344                         return false;
345                 }
346         }
347
348         /**
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
353          */
354         private static boolean fieldLooksLikeWaypointType(String inValue, boolean inIsHeader)
355         {
356                 if (inValue == null || inValue.equals("")) {return false;}
357                 if (inIsHeader)
358                 {
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()));
363                 }
364                 else
365                 {
366                         // can't reliably identify it just using the value
367                         return false;
368                 }
369         }
370 }