fix #5554 lat/long parsing with single and double quotation mark and whitespace
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / strategy / parser / location / CoordinateConverter.java
1 /**
2 * Copyright (C) 2009 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 *
9 * This file is an Java adaption from the orginal CoordinateConverter written by Dominik Mikiewicz
10 * @see www.cartomatic.pl
11 * @see http://dev.e-taxonomy.eu/svn/trunk/geo/coordinateConverter/CoordinateConverter.cs
12 * @see http://gis.miiz.waw.pl/webapps/coordinateconverter/
13 */
14 package eu.etaxonomy.cdm.strategy.parser.location;
15
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.Comparator;
19 import java.util.List;
20 import java.util.regex.Pattern;
21
22 import org.apache.log4j.Logger;
23
24 import eu.etaxonomy.cdm.common.CdmUtils;
25
26 /**
27 * @author a.mueller
28 * @date 07.06.2010
29 *
30 */
31 public class CoordinateConverter {
32 @SuppressWarnings("unused")
33 private static final Logger logger = Logger.getLogger(CoordinateConverter.class);
34
35 //Patterns
36 private List<CoordinatePattern> patterns;
37
38 private static String minuteUtf8 = "\u02B9|\u00B4|\u02CA|\u0301|\u0374|\u2019";
39 private static String secondUtf8 = "\u02BA|\u030B|\u2033|\u00B4\u00B4|\u201D";
40
41
42 private class CoordinatePattern{
43 String description;
44 String pattern;
45 }
46
47
48 private Comparator<CustomHemisphereIndicator> lengthComparator = new Comparator<CustomHemisphereIndicator>(){
49 @Override
50 public int compare(CustomHemisphereIndicator ind1, CustomHemisphereIndicator ind2) {
51 return Integer.valueOf(ind1.getLength()).compareTo(ind2.getLength());
52 }
53 };
54
55 //Class constructor
56 public CoordinateConverter() {
57 //initialise pattern array
58 patterns = new ArrayList<CoordinatePattern>();
59
60 //temp pattern variable
61 CoordinatePattern pattern;
62
63
64 //variations of DD.DDD with white space characters
65 pattern = new CoordinatePattern();
66 pattern.description = "Variation of DD.DDD";
67 pattern.pattern =
68 //+/-/Nn/Ss/Ww/EeDD.DDDD
69 "(^" +
70 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
71 "((\\d{1,3}(\\.|\\,)?(\\s)*$)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*$))" +
72 ")" +
73 ////DD.DDDDNn/Ss/Ww/Ee
74 "|(^" +
75 "(\\s)*((\\d{1,3}(\\.|\\,)?(\\s)*)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*))" +
76 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
77 ")";
78 patterns.add(pattern);
79
80
81 //Variations of DD(\u00B0|d)MM.MMM' with whitespace characters
82 pattern = new CoordinatePattern();
83 pattern.description = "Variation of DD(\u00B0|d)MM.MMM('|m)";
84 pattern.pattern =
85 "(^" +
86 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
87 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*$)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)?("+ minuteUtf8 + "|'|M|m)?$)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*("+ minuteUtf8 + "|'|M|m)?(\\s)*$))" +
88 ")" +
89 "|(^" +
90 "(\\s)*((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)?("+ minuteUtf8 + "|'|M|m)?)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*("+ minuteUtf8 + "|'|M|m)?(\\s)*))" +
91 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
92 ")";
93 patterns.add(pattern);
94
95
96 //Variations of DD\u00B0MM'SS.SSS" with whitespace characters
97 pattern = new CoordinatePattern();
98 pattern.description = "Variation of DD(\u00B0|d)MM("+ minuteUtf8 + "|m)SS.SSS("+secondUtf8+"|s)";
99 pattern.pattern =
100 //+/-/Nn/Ss/Ww/EeDD\u00B0MM"+ minuteUtf8 + "SS.SSS
101 "(^" +
102 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
103 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*$)" +
104 "|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8 + "|'|M|m)?(\\s)*$)" +
105 "|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8 + "|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*("+secondUtf8+"|\"|''|S|s)?(\\s)*$)" +
106 "|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8 + "|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*("+secondUtf8+"|\"|''|S|s)?(\\s)*$))" +
107 ")" +
108 //DD°MM"+ minuteUtf8 + "SS.SSSNn/Ss/Ww/Ee
109 "|(^(\\s)*" +
110 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*)|" +
111 "(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8 + "|'|M|m)?(\\s)*)|" +
112 "(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8 + "|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*("+secondUtf8+"|\"|''|S|s)?(\\s)*)|" +
113 "(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8 + "|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*("+secondUtf8+"|\"|''|S|s)?(\\s)*))" +
114 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
115 ")";
116 patterns.add(pattern);
117
118
119 //Variations of DD:MM:SS.SSS with whitespace characters
120 pattern = new CoordinatePattern();
121 pattern.description = "Variation of DD:MM:SS.SSS";
122 pattern.pattern =
123 // +/-/Nn/Ss/Ww/EeDD:MM:SS.SSS
124 "(^" +
125 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
126 "((\\d{1,3}(\\s)*\\:?(\\s)*$)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:?(\\s)*$)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*$)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*$))" +
127 ")" +
128 //DD:MM:SS.SSSNn/Ss/Ww/Ee
129 "|(^" +
130 "(\\s)*((\\d{1,3}(\\s)*\\:?(\\s)*)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:?(\\s)*)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*))" +
131 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
132 ")";
133 patterns.add(pattern);
134
135 }
136
137
138 //tests if a string matches one of the defined patterns
139 private int matchPattern(String str){
140 int recognised = -1;
141
142 //match the string against each available patern
143 for (int i = 0; i < patterns.size(); i++){
144
145 CoordinatePattern pattern = patterns.get(i);
146 Pattern regEx = Pattern.compile(pattern.pattern);
147 if (regEx.matcher(str).find()) {
148 recognised = i;
149 break;
150 }
151
152 }
153 return recognised;
154 }
155
156
157 //gets sign of the coordinate (tests for presence of negative sign)
158 private int getSign(String str){
159
160 //This regex checks for the negative hemisphere indicator
161 Pattern regexNegative = Pattern.compile("(-|S|s|W|w)");
162
163 //This regex checks if there weren't any other hemisphere indicators
164 //it is needed for the specific case of the DDdMMmSSs S
165 //so it needs to be ensured there where no positive indicators
166 Pattern regexPositive = Pattern.compile("(\\+|N|n|E|e)");
167
168 //if a positive indicator is found no need to search further
169 if (regexPositive.matcher(str).find()){
170 return 1;
171 }else{
172 //if not check whether there was a negative indicator. if so negate otherwise return positive
173 if (regexNegative.matcher(str).find()){
174 return -1;
175 }else{
176 return 1;
177 }
178 }
179 }
180
181
182 //this checks for the coordinate sign by evaluating user supplied data
183 private int getCustomSign(String str){
184 //Note:
185 //Indicators are evaluated from the longest ones to the shortes ones
186 //So when searching for "P" does not affect "PN" as "PN" is evaluated earlier
187
188
189 //search for the presence of indicators
190 boolean hasPositive = false;
191 boolean hasNegative = false;
192
193 //keep previous negative indicators here
194 List<String> previousNegatives = new ArrayList<String>();
195
196 //compare the string with user supplied custom pattern
197 for (int x = customPtrn.hemisphereIndicators.size() - 1; x >= 0; x--){
198
199 CustomHemisphereIndicator ind = customPtrn.hemisphereIndicators.get(x);
200
201 //test here if the indicator exists (has length >0)
202 if (ind.getLength() > 0){
203
204 //check if the supplied pattern was marked as case insensitive?
205 String caseInsensitive = "";
206
207 if (customPtrn.caseInsensitive){
208 caseInsensitive = "(?i)";
209 }
210
211 //create a regex
212 Pattern tempRegex = Pattern.compile(caseInsensitive + ind.getIndicator());
213
214 //if a pattern is found
215 if (tempRegex.matcher(str).find()){
216 //check whether it's a positive or negative indicator
217 if (ind.getPositive()){
218 /* Note:
219 * See the note below to understand why checking for previous negatives is performed here
220 */
221
222 //check the previous negatives
223 if (previousNegatives.size() != 0){
224 boolean sameNegative = false;
225
226 for (int i = previousNegatives.size() - 1; i >= 0; i--){
227 if (ind.getIndicator() == previousNegatives.get(i)){
228 sameNegative = true;
229 break;
230 }
231 }
232
233 //mark as positive only if the previously found negative is the same
234 if (sameNegative){
235 hasPositive = true;
236 }
237
238 }else{ //if no negatives before it already marks the sign as positive
239 hasPositive = true;
240 }
241
242 } else {
243 /* Note:
244 * save the negative indicator here so it can be compared later if a positive wants to overwrite it!
245 * in a case a longer negative "Pn" has already been found a shorter positive "P" will not overwrite it
246 * and the hasPositive will remain false;
247 * In a case a "P" negative indicator has already been found, positive will mark hasPositive and therefore
248 * later a default positive value will be returned (if the indicators for positive & negative are the same
249 * positive is returned)
250 * testing for previous positives is not required since if a hasPositive is already true method will return
251 * true anyway
252 *
253 */
254 previousNegatives.add(ind.getIndicator());
255
256 hasNegative = true;
257
258 }
259 }
260 }
261
262 }
263
264 //Note:
265 //positive indicator has priority here - if both indicators supplied by the user are the same, a positive is chosen
266 //if there were no indicator found in the tested coordinate, a positive value is returned by default
267
268 if (hasPositive){
269 return 1;
270 } else {
271 if (hasNegative) {
272 return -1;
273 } else {
274 return 1;
275 }
276 }
277 }
278
279
280
281 //returns a currently used decimal separator
282 private String getDecimalSeparator(){
283 //TODO not yet transformed from C#
284 // return System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
285 return ".";
286 }
287
288
289 //replaces comma or dot for current decimal separator
290 private String fixDecimalSeparator(String str){
291 //Note:
292 //Coma is replaced as parsers often recognise dot as a decimal separator
293 //Comma or dot is replaced with a decimal separator here (environment settings)
294 //But decimal separator has to be used later too;
295
296 String regExReplaceComma = "(\\,|\\.)";
297 str = str.replaceAll(regExReplaceComma, getDecimalSeparator());
298
299 return str;
300 }
301
302
303 //removes sign
304 private String removeSign(String str){
305 String regExRemoveSign = "(\\+|-|S|s|W|w|N|n|E|e)";
306 str = str.replaceAll(regExRemoveSign, "");
307 return str;
308 }
309
310 //removes custom sign indicators
311 private String removeCustomPatternParts(String str){
312
313 /* Note:
314 * Symbols are added here so the removing tries to not affect the coordinate too much
315 * Strings to be removed then are evaluated from the longest ones to the shortes ones
316 * So when searching for "P" does not affect "PN" as "PN" is evaluated earlier
317 * */
318
319 //CustomHemisphereIndicator is used here so another object does not have to be created
320 //only for the string cleanning
321 List<CustomHemisphereIndicator> stringsToRemove = customPtrn.hemisphereIndicators;
322
323 //add degree symbol
324 CustomHemisphereIndicator stringToRemove = new CustomHemisphereIndicator("Degree", customPtrn.degreeSymbol,customPtrn.degreeSymbol.length(), false);
325 stringsToRemove.add(stringToRemove);
326
327 //add minute symbol
328 stringToRemove = new CustomHemisphereIndicator("Minute", customPtrn.minuteSymbol, customPtrn.minuteSymbol.length(), false);
329 stringsToRemove.add(stringToRemove);
330
331 //add second symbol
332 stringToRemove = new CustomHemisphereIndicator("Second", customPtrn.secondSymbol, customPtrn.secondSymbol.length(), false);
333 stringsToRemove.add(stringToRemove);
334
335 //sort the list (by element's Length property)
336 Collections.sort(stringsToRemove, lengthComparator);
337
338
339 // ListSelectionEv.sort(lengthComparator);
340
341
342 for (int x = stringsToRemove.size() - 1; x >= 0; x--){
343
344 CustomHemisphereIndicator toBeRemoved = stringsToRemove.get(x);
345
346 //check if the string exists so replacing does not yield errors
347 if (toBeRemoved.getLength() > 0)
348 {
349 //check if the supplied pattern was marked as case insensitive?
350 String CaseInsensitive = "";
351
352 if (customPtrn.caseInsensitive){
353 CaseInsensitive = "(?i)";
354 }
355
356 //create regex for replacing
357 String tempRegex = CaseInsensitive + toBeRemoved.getIndicator();
358
359
360 if (toBeRemoved.getName().equals("Degree") || toBeRemoved.getName().equals("Minute")) {
361 //replace with a symbol used later for splitting
362 str = str.replaceAll(tempRegex, ":");
363 } else {
364 //remove the string
365 str = str.replaceAll(tempRegex, "");
366 }
367 }
368 }
369 return str;
370 }
371
372
373
374 //removes whitespace characters
375 private String removeWhiteSpace(String str){
376 str = str.replaceFirst("\\s+", "");
377 return str;
378 }
379
380
381 //Object for the conversion results
382 public class ConversionResults{
383 public boolean patternRecognised;
384 public String patternMatched;
385 public String patternType;
386
387 public boolean conversionSuccessful;
388 public double convertedCoord;
389 public boolean canBeLat;
390
391 public String conversionComments;
392
393 public Boolean isLongitude;
394
395 public int dd;
396 public int mm;
397 public double mmm;
398 public int ss;
399 public double sss;
400
401 }
402
403
404 public ConversionResults tryConvert(String str){
405 //some local variables
406 int sign; //sign of the coordinate
407 String[] decimalBit, ddmmss, ddmm; //arrays for splitting
408 double dd = 0, mm = 0, ss = 0, mmm = 0, sss = 0, dec = 0; //parts of the coordinates
409
410 String decSeparatorRaw = String.valueOf(getDecimalSeparator()); //gets the current decimal separator
411 String decSeparatorRegEx = decSeparatorRaw.replace(".", "\\.");
412
413 ConversionResults results = new ConversionResults();
414
415 //Get the matched pattern
416 CoordinatePattern pattern;
417 int ptrnnum = matchPattern(str);
418 if (ptrnnum != -1) {
419 pattern = patterns.get(ptrnnum);
420 } else {
421 pattern = new CoordinatePattern();
422 pattern.description = "Unknown";
423 pattern.pattern = "No pattern matched";
424 }
425
426
427
428 if (pattern.description.equals("Variation of DD.DDD")){
429
430 //Sets pattern machted, successful, pattern type and pattern info
431 initializeResult(results, pattern);
432
433 //get sign
434 sign = getSign(str);
435 results.isLongitude = getIsLongitude(str);
436
437 //Replace comma or dot with a current decimal separator
438 str = fixDecimalSeparator(str);
439
440 //Remove all the unwanted stuff
441 str = removeSign(str);
442 str = removeWhiteSpace(str);
443
444 //Since this is already a decimal degree no spliting is needed
445 dd = Double.valueOf(str);
446
447 checkDegreeRange(dd, results);
448 doConvertWithCheck(sign, dd, mm, mmm, ss, sss, results);
449
450 }else if (pattern.description.equals("Variation of DD(\u00B0|d)MM.MMM('|m)")){
451
452 //Sets pattern machted, successful, pattern type and pattern info
453 initializeResult(results, pattern);
454
455 //get sign
456 sign = getSign(str);
457 results.isLongitude = getIsLongitude(str);
458
459 //Replace comma or dot with a current decimal separator
460 str = fixDecimalSeparator(str);
461
462 //Remove all the unwanted stuff
463 str = removeSign(str);
464 str = removeWhiteSpace(str);
465
466 //do some further replacing
467 //Replace degree symbol
468 str = str.replaceAll("(\u00B0|\u00BA|D|d)", ":");
469
470 //remove minute symbol
471 str = str.replaceAll("("+ minuteUtf8 + "|'|M|m)", "");
472
473 //Extract decimal part
474 decimalBit = str.split(decSeparatorRegEx);
475
476 //split degrees and minutes
477 ddmm = decimalBit[0].split(":");
478
479
480 //extract values from the strings
481 dd = Integer.valueOf(ddmm[0]); //Degrees
482
483 if (ddmm.length > 1){ //Minutes
484 //check if the string is not empty
485 if (ddmm[1] != "") {
486 mm = Integer.valueOf(ddmm[1]);
487 }
488 }
489
490 if (decimalBit.length > 1){//DecimalSeconds
491 //check if the string is not empty
492 if (decimalBit[1] != "") {
493 mmm = Double.valueOf(decimalBit[1]) / Math.pow(10, (decimalBit[1].length()));
494 }
495 }
496
497 checkDegreeRange(dd, results);
498 checkMinuteRange(mm, results);
499 doConvertWithCheck(sign, dd, mm, mmm, ss, sss, results);
500
501 }else if (pattern.description.equals("Variation of DD(\u00B0|d)MM("+ minuteUtf8 + "|m)SS.SSS("+secondUtf8+"|s)")){
502
503 /*
504 * Note:
505 * This pattern allows the seconds to be specified with S, s or " or nothing at all
506 * If the seconds are marked with "s" and there is no other indication of the hemisphere
507 * the coordinate will be parsed as southern (negative).
508 *
509 * If the N / E / W / + indicator is found the coordinate will be parsed appropriately no matter
510 * what is the second notation
511 */
512
513 //Sets pattern machted, successful, pattern type and pattern info
514 initializeResult(results, pattern);
515
516 //get sign
517 sign = getSign(str);
518 //TODO test S
519 results.isLongitude = getIsLongitude(str);
520
521 //Replace comma or dot with a current decimal separator
522 str = fixDecimalSeparator(str);
523
524 //Remove all the unwanted stuff
525 str = removeSign(str);
526 str = removeWhiteSpace(str);
527
528 //remove second symbol (s is removed by the get sign method)
529 //double apostrophe is not removed here as single apostrphe may mark minutes!
530 //it's taken care of later after extracting the decimal part
531 str = str.replaceAll("("+secondUtf8+"|\")", "");
532
533 //do some further replacing
534 //Replace degree symbol
535 str = str.replaceAll("(\u00B0|\u00B0|D|d|"+ minuteUtf8 + "|'|M|m)",":");
536
537 //Extract decimal part
538 decimalBit = str.split(decSeparatorRegEx);
539
540 //remove : from the decimal part [1]! This is needed when a double apostrophe was used to mark seconds
541 if (decimalBit.length > 1)
542 {
543 decimalBit[1].replace(":", "");
544 }
545
546 //split degrees and minutes
547 ddmmss = decimalBit[0].split(":");
548
549
550 //extract values from the strings
551 dd = Integer.valueOf(ddmmss[0]); //Degrees
552 if (ddmmss.length > 1){//Minutes
553 //check if the string is not empty
554 if (ddmmss[1] != "") {
555 mm = Integer.valueOf(ddmmss[1]);
556 }
557 }
558 if (ddmmss.length > 2){//Seconds
559 //check if the string is not empty
560 if (ddmmss[2] != "") {
561 ss = Integer.valueOf(Nz(ddmmss[2]).trim());
562 }
563 }
564 if (decimalBit.length > 1) { //DecimalSeconds
565 //check if the string is not empty
566 if (decimalBit[1] != "") {
567 sss = Double.valueOf(decimalBit[1]) / Math.pow(10, (decimalBit[1].length()));
568 }
569 }
570
571 checkDegreeRange(dd, results);
572 checkMinuteRange(mm, results);
573 checkSecondRange(ss, results);
574
575 doConvertWithCheck(sign, dd, mm, mmm, ss, sss, results);
576
577 }else if (pattern.description.equals("Variation of DD:MM:SS.SSS")){
578
579 //Sets pattern machted, successful, pattern type and pattern info
580 initializeResult(results, pattern);
581
582 //get sign
583 sign = getSign(str);
584 results.isLongitude = getIsLongitude(str);
585
586 //Replace comma or dot with a current decimal separator
587 str = fixDecimalSeparator(str);
588
589 //Remove all the unwanted stuff
590 str = removeSign(str);
591 str = removeWhiteSpace(str);
592
593 //Do some splitting
594 decimalBit = str.split(decSeparatorRegEx);
595 ddmmss = decimalBit[0].split(":");
596
597
598 //extract values from the strings
599 dd = Integer.valueOf(ddmmss[0]); //Degrees
600 if (ddmmss.length > 1)//Minutes
601 {
602 //check if the string is not empty
603 if (ddmmss[1] != "") { mm = Integer.valueOf(ddmmss[1]); }
604 }
605 if (ddmmss.length > 2) {//Seconds{
606 //check if the string is not empty
607 if (ddmmss[2] != "") {
608 ss = Integer.valueOf(ddmmss[2]);
609 }
610 }
611 if (decimalBit.length > 1) { //DecimalSeconds
612 //check if the string is not empty
613 if (decimalBit[1] != "") {
614 sss = Double.valueOf(decimalBit[1]) / Math.pow(10, (decimalBit[1].length()));
615 }
616 }
617
618 checkDegreeRange(dd, results);
619 checkMinuteRange(mm, results);
620 checkSecondRange(ss, results);
621
622 doConvertWithCheck(sign, dd, mm, mmm, ss, sss, results);
623
624 }else if (pattern.description.equals("Custom variation of DD.DDD")){
625
626 //Sets pattern machted, successful, pattern type and pattern info
627 initializeResult(results, pattern);
628
629
630 //get sign
631 sign = getCustomSign(str);
632
633 //TODO still needs to be adapted to custom pattern
634 results.isLongitude = getIsLongitude(str);
635
636
637 //Remove all the unwanted stuff
638 //Note: This method also replaces the symbols with ":"
639 //Note: In certain cases it may make the coord unparsable
640 str = removeCustomPatternParts(str);
641
642 str = removeWhiteSpace(str);
643
644 //Replace comma or dot with a current decimal separator
645 str = fixDecimalSeparator(str);
646
647 //remove the ":" here as it is not needed here for decimal degrees
648 str = str.replace(":", "");
649
650 try {
651 //Since this is already a decimal degree no spliting is needed
652 dd = Double.valueOf(str);
653 } catch (Exception e) {
654 results.conversionSuccessful = false;
655 results.convertedCoord = 99999; //this is to mark an error...
656 results.conversionComments =
657 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
658 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
659 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
660 ;
661
662 //exit method
663 return results;
664 }
665
666 //Since this is already a decimal degree no spliting is needed
667 dd = Double.valueOf(str);
668
669 checkDegreeRange(dd, results);
670 doConvertWithCheck(sign, dd, mm, mmm, ss, sss, results);
671
672
673 }else if (pattern.description.equals("Custom variation of DD:MM.MMM")){
674 //-------------Customs patterns start here-------------
675
676 //Sets pattern machted, successful, pattern type and pattern info
677 initializeResult(results, pattern);
678
679 //get sign
680 sign = getCustomSign(str);
681
682 //TODO still needs to be adapted to custom pattern
683 results.isLongitude = getIsLongitude(str);
684
685
686
687 //Remove all the unwanted stuff
688 //Note: This method also replaces the symbols with ":"
689 //Note: In certain cases it may make the coord unparsable
690 str = removeCustomPatternParts(str);
691
692 str = removeWhiteSpace(str);
693
694 //Replace comma or dot with a current decimal separator
695 str = fixDecimalSeparator(str);
696
697
698 //Extract decimal part
699 decimalBit = str.split(decSeparatorRegEx);
700
701 //split degrees and minutes
702 ddmm = decimalBit[0].split(":");
703
704
705 try {
706 //extract values from the strings
707 dd = Integer.valueOf(ddmm[0]); //Degrees
708
709 if (ddmm.length > 1){//Minutes
710 //check if the string is not empty
711 if (ddmm[1] != "") { mm = Integer.valueOf(ddmm[1]); }
712 }
713
714 if (decimalBit.length > 1){//DecimalSeconds
715 //check if the string is not empty
716 if (decimalBit[1] != ""){
717 //replace the ":" if any (may be here as a result of custom symbol replacement
718 decimalBit[1] = decimalBit[1].replace(":", "");
719
720 mmm = Double.valueOf(decimalBit[1]) / Math.pow(10, (decimalBit[1].length()));
721 }
722 }
723 } catch (Exception e){
724 results.conversionSuccessful = false;
725 results.convertedCoord = 99999; //this is to mark an error...
726 results.conversionComments =
727 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
728 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
729 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
730 ;
731
732 //exit method
733 return results;
734 }
735
736
737 checkDegreeRange(dd, results);
738 checkMinuteRange(mm, results);
739 doConvertWithCheck(sign, dd, mm, mmm, ss, sss, results);
740
741 } else if (pattern.description.equals("Custom variation of DD:MM:SS.SSS")){
742
743 //Sets pattern machted, successful, pattern type and pattern info
744 initializeResult(results, pattern);
745
746
747 //get sign
748 sign = getCustomSign(str);
749
750 //TODO still needs to be adapted to custom pattern
751 results.isLongitude = getIsLongitude(str);
752
753
754 //Remove all the unwanted stuff
755 //Note: This method also replaces the symbols with ":"
756 //Note: In certain cases it may make the coord unparsable
757 str = removeCustomPatternParts(str);
758
759 str = removeWhiteSpace(str);
760
761 //Replace comma or dot with a current decimal separator
762 str = fixDecimalSeparator(str);
763
764
765 //Extract decimal part
766 decimalBit = str.split(decSeparatorRegEx);
767
768 //split degrees and minutes
769 ddmmss = decimalBit[0].split(":");
770
771
772 try {
773
774 //extract values from the strings
775 dd = Integer.valueOf(ddmmss[0]); //Degrees
776 if (ddmmss.length > 1) {//Minutes
777 //check if the string is not empty
778 if (ddmmss[1] != "") {
779 mm = Integer.valueOf(ddmmss[1]);
780 }
781 }
782 if (ddmmss.length > 2){ //Seconds
783 //check if the string is not empty
784 if (ddmmss[2] != "") {
785 ss = Integer.valueOf(ddmmss[2]);
786 }
787 }
788 if (decimalBit.length > 1){ //DecimalSeconds
789 //check if the string is not empty
790 if (decimalBit[1] != "") {
791 sss = Double.valueOf(decimalBit[1]) / Math.pow(10, (decimalBit[1].length()));
792 }
793 }
794 } catch (Exception e) {
795 results.conversionSuccessful = false;
796 results.convertedCoord = 99999; //this is to mark an error...
797 results.conversionComments =
798 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
799 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
800 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
801 ;
802
803 //exit method
804 return results;
805 }
806
807
808 checkDegreeRange(dd, results);
809 checkMinuteRange(mm, results);
810 checkSecondRange(ss, results);
811
812 doConvertWithCheck(sign, dd, mm, mmm, ss, sss, results);
813
814 }else { //default : pattern not recognized
815 results.patternRecognised = false;
816 results.patternType = pattern.description;
817 results.patternMatched = pattern.pattern;
818
819 results.conversionSuccessful = false;
820 results.convertedCoord = 99999; //this is to mark an error...
821
822 results.conversionComments = "Coordinate pattern not recognised!";
823
824 }
825
826 //do the self check here
827 results = selfTest(results);
828
829 //return conversion results
830 return results;
831 }
832
833
834 /**
835 * @param string
836 * @return
837 */
838 private String Nz(String string) {
839 return CdmUtils.Nz(string);
840 }
841
842
843 /**
844 * @param sign
845 * @param dd
846 * @param mm
847 * @param ss
848 * @param sss
849 * @param results
850 */
851 private void doConvertWithCheck(int sign, double dd, double mm, double mmm, double ss, double sss, ConversionResults results) {
852 double dec;
853 //Do the conversion if everything ok
854 if (results.conversionSuccessful){
855 results.conversionComments = "Conversion successful.";
856
857 dec = sign * (dd + (mm + mmm) / 60 + (ss + sss) / 3600);
858
859 //one more check to ensure a coord does not exceed 180
860 if (dec > 180 | dec < -180){
861 results.conversionSuccessful = false;
862 results.convertedCoord = 99999; //this is to mark an error...
863 results.conversionComments += "Coordinate is either > 180 or < -180; ";
864 } else {
865 results.convertedCoord = dec;
866
867 results.conversionComments = "Conversion successful.";
868
869 //Check whether the coordinate exceeds +/- 90 and mark it in comments
870
871 if (dec <= 90 && dec >= -90 && (results.isLongitude == null || results.isLongitude == false) ) {
872 results.canBeLat = true;
873 }else{
874 results.isLongitude = true;
875 }
876 }
877 }
878 }
879
880
881 /**
882 * @param ss
883 * @param results
884 */
885 private void checkSecondRange(double ss, ConversionResults results) {
886 if (ss > 59) {//seconds
887 results.conversionSuccessful = false;
888 results.convertedCoord = 99999; //this is to mark an error...
889 results.conversionComments += "Seconds fall outside the range: MM >= 60; ";
890 }
891 }
892
893
894 /**
895 * @param mm
896 * @param results
897 */
898 private void checkMinuteRange(double mm, ConversionResults results) {
899 if (mm > 59) {//minutes
900 results.conversionSuccessful = false;
901 results.convertedCoord = 99999; //this is to mark an error...
902 results.conversionComments += "Minutes fall outside the range: MM > 59; ";
903 }
904 }
905
906
907 /**
908 * @param dd
909 * @param results
910 */
911 private void checkDegreeRange(double dd, ConversionResults results) {
912 //do some additional checking if the coords fall into the range
913 if (dd < -180 | dd > 180){ //degree may require another param specifying whether it's lat or lon...
914 results.conversionSuccessful = false;
915 results.convertedCoord = 99999; //this is to mark an error...
916 results.conversionComments += "Degrees fall outside the range: DD < -180 | DD > 180; ";
917 }
918 }
919
920
921 /**
922 * @param str
923 * @return
924 */
925 private Boolean getIsLongitude(String str) {
926 //This regex checks for the negative hemisphere indicator
927 Pattern regexLatitudeNonAmbigous = Pattern.compile("(N|n)");
928 Pattern regexLatitudeAmbigous = Pattern.compile("(S|s)");
929
930 //This regex checks if there weren't any other hemisphere indicators
931 //it is needed for the specific case of the DDdMMmSSs S
932 //so it needs to be ensured there where no positive indicators
933 Pattern regexLongitude = Pattern.compile("(W|w|E|e)");
934
935 //if a positive indicator is found no need to search further
936 if (regexLongitude.matcher(str).find()){
937 return true;
938 }else if (regexLatitudeNonAmbigous.matcher(str).find()){
939 return false;
940 }else if (regexLatitudeAmbigous.matcher(str).find()){
941 Pattern regexLiteralUnits = Pattern.compile("(D|d|M|m)");
942
943 //if there are no other literal units we assume that S is a
944 //direction and not a second indicator
945 if (! regexLiteralUnits.matcher(str).find()){
946 return false;
947 }else if (regexLatitudeAmbigous.matcher(str).groupCount() > 1){
948 return false;
949 }else{
950 return null;
951 }
952 }else{
953 return null;
954 }
955 }
956
957
958 /**
959 * Sets pattern machted, successful, pattern type and pattern info
960 * @param results
961 * @param pattern
962 */
963 private void initializeResult(ConversionResults results,
964 CoordinatePattern pattern) {
965 //Pattern matched
966 results.patternRecognised = true;
967
968 //Matching pattern succeeded so intialy the parsing is ok
969 results.conversionSuccessful = true;
970
971 //pattern info
972 results.patternType = pattern.description;
973 results.patternMatched = pattern.pattern;
974 }
975
976
977 private ConversionResults selfTest(ConversionResults results){
978
979 ConversionResults newresults = results;
980
981 if (results.conversionSuccessful != false){
982 int sign = 1;
983 if (Math.signum(results.convertedCoord) < 0) {
984 sign = -1;
985 }
986
987 double decimalDegrees = sign * results.convertedCoord;
988 int fullDegrees;
989
990 double decimalMinutes;
991 int fullMinutes;
992
993 double decimalSeconds;
994 int fullSeconds;
995
996 //Get full degrees
997 fullDegrees = (int)Math.floor(decimalDegrees);
998
999 //get minutes
1000 decimalMinutes = (decimalDegrees - fullDegrees) * 60;
1001 fullMinutes = (int)Math.floor(decimalMinutes);
1002
1003 decimalSeconds = (decimalMinutes - fullMinutes) * 60;
1004 fullSeconds = (int)Math.floor(decimalSeconds);
1005
1006 //save the test results
1007 newresults.dd = fullDegrees;
1008 newresults.mm = fullMinutes;
1009 newresults.mmm = decimalSeconds;
1010 newresults.ss = fullSeconds;
1011 newresults.sss = decimalSeconds;
1012
1013 }
1014
1015 return newresults;
1016
1017 }
1018
1019
1020
1021 //------------ CUSTOM PATTERN BUILDER--------------
1022
1023 public class CustomPatternIn {
1024 public String north;
1025 public String south;
1026 public String east;
1027 public String west;
1028
1029 public String degreeSymbol;
1030 public String minuteSymbol;
1031 public String secondSymbol;
1032
1033 public boolean caseInsensitive;
1034 public boolean allowWhiteSpace;
1035 public boolean priorityOverDefaultPatterns;
1036 public boolean disableDefaultPatterns;
1037
1038 }
1039
1040
1041 private class CustomPattern{
1042
1043 public List<CustomHemisphereIndicator> hemisphereIndicators;
1044
1045 public String degreeSymbol;
1046 public String minuteSymbol;
1047 public String secondSymbol;
1048
1049 public boolean caseInsensitive;
1050
1051 }
1052
1053 //global variable to be used if a custom pattern is used
1054 private CustomPattern customPtrn;
1055
1056 //escape some of the chars
1057 private String escapeChars(String str){
1058 // backslash - first so it is not messed when other escape chars are corrected for being used in a string
1059 str = str.replace("\\", "\\\\");
1060
1061 //dot and comma
1062 str = str.replace(".", "\\.");
1063 str = str.replace(",", "\\,");
1064
1065 //brackets
1066 str = str.replace("(", "\\(");
1067 str = str.replace(")", "\\)");
1068 str = str.replace("[", "\\[");
1069 str = str.replace("]", "\\]");
1070 str = str.replace("{", "\\{");
1071 str = str.replace("}", "\\}");
1072
1073 //other replacements
1074 str = str.replace("^", "\\^");
1075 str = str.replace("$", "\\$");
1076 str = str.replace("+", "\\+");
1077 str = str.replace("*", "\\*");
1078 str = str.replace("?", "\\?");
1079 str = str.replace("|", "\\|");
1080
1081 return str;
1082 }
1083
1084
1085 //this implements sorting by using system.Icomparable - sorting is needed later when replacing
1086 private class CustomHemisphereIndicator implements Comparable<CustomHemisphereIndicator> {
1087 //private variables
1088 private int m_length;
1089 private String m_name;
1090 private String m_indicator;
1091 private boolean m_positive;
1092
1093 //constructor
1094 public CustomHemisphereIndicator(String name, String indicator, int length, boolean positive){
1095 this.m_name = name;
1096 this.m_indicator = indicator;
1097 this.m_length = length;
1098 this.m_positive = positive;
1099 }
1100
1101 //properties
1102
1103 public String getName(){
1104 return this.m_name;
1105 }
1106 public void setName(String value){
1107 this.m_name = value;
1108 }
1109
1110 public String getIndicator(){
1111 return this.m_indicator;
1112 }
1113 public void setIndicator(String value){
1114 this.m_indicator = value;
1115 }
1116
1117 public int getLength(){
1118 return this.m_length;
1119 }
1120 public void setLength(int value){
1121 this.m_length = value;
1122 }
1123
1124
1125 public boolean getPositive(){
1126 return this.m_positive;
1127 }
1128 public void setPositive(boolean value){
1129 this.m_positive = value;
1130 }
1131
1132 /* Less than zero if this instance is less than obj.
1133 * Zero if this instance is equal to obj.
1134 * Greater than zero if this instance is greater than obj.
1135 *
1136 * This method uses the predefined method Int32.CompareTo
1137 * */
1138
1139 @Override
1140 public int compareTo(CustomHemisphereIndicator ind){
1141
1142 //no need to rewrite the code again, we have Integer.compareTo ready to use
1143 return Integer.valueOf(this.getLength()).compareTo(Integer.valueOf(ind.getLength()));
1144 }
1145 }
1146
1147
1148
1149 //This adds custom pattern to a list of already predefined patterns
1150 //useful for batch conversions - allows for totally mixed input data (predefined & custom)
1151 public void addCustomPattern(CustomPatternIn patternIn){
1152
1153 //new custom pattern object - to pass the needed data farther
1154 CustomPattern pattern = new CustomPattern();
1155
1156 //keep indicators for parsing
1157 List<CustomHemisphereIndicator> indicators = new ArrayList<CustomHemisphereIndicator>();
1158
1159 //north
1160 CustomHemisphereIndicator ind = new CustomHemisphereIndicator("North", patternIn.north, patternIn.north.length() ,true);
1161 indicators.add(ind);
1162
1163 //south
1164 ind = new CustomHemisphereIndicator("South", patternIn.south, patternIn.south.length(), false);
1165 indicators.add(ind);
1166
1167 //east
1168 ind = new CustomHemisphereIndicator("East", patternIn.east, patternIn.east.length(), true);
1169 indicators.add(ind);
1170
1171 //west
1172 ind = new CustomHemisphereIndicator("West", patternIn.west, patternIn.west.length(), false);
1173 indicators.add(ind);
1174
1175 //sort the arraylist
1176 Collections.sort(indicators, lengthComparator);
1177
1178
1179 //add it to the pattern object
1180 pattern.hemisphereIndicators = indicators;
1181
1182 //case insensitive
1183 pattern.caseInsensitive = patternIn.caseInsensitive;
1184
1185 //keep symbols for parsing
1186 pattern.degreeSymbol = patternIn.degreeSymbol;
1187 pattern.minuteSymbol = patternIn.minuteSymbol;
1188 pattern.secondSymbol = patternIn.secondSymbol;
1189
1190
1191 //save the data
1192 customPtrn = pattern;
1193
1194
1195 //----------------build custom patterns----------------
1196
1197 //prepare hemisphere indicators
1198 String north = escapeChars(patternIn.north);
1199 String south = escapeChars(patternIn.south);
1200 String east = escapeChars(patternIn.east);
1201 String west = escapeChars(patternIn.west);
1202
1203 //prepare symbols
1204 String degreesymbol = "";
1205 if (patternIn.degreeSymbol != ""){
1206 degreesymbol = "(" + escapeChars(patternIn.degreeSymbol) + ")?";
1207 }
1208
1209 String minutesymbol = "";
1210 if (patternIn.minuteSymbol != ""){
1211 minutesymbol = "(" + escapeChars(patternIn.minuteSymbol) + ")?";
1212 }
1213
1214 String secondsymbol = "";
1215 if (escapeChars(patternIn.secondSymbol) != ""){
1216 secondsymbol = "(" + escapeChars(patternIn.secondSymbol) + ")?";
1217 }
1218
1219
1220 //is the pattern to be case insensitive?
1221 String CaseInsensitive = "";
1222 if (patternIn.caseInsensitive){
1223 CaseInsensitive = "(?i)";
1224 }
1225
1226 //allow whitespace
1227 String WhiteSpace = "";
1228 if (patternIn.allowWhiteSpace == true){
1229 WhiteSpace = "(\\s)*";
1230 }
1231
1232 //hemisphere indicator
1233 String HemisphereIndicator = "";
1234
1235 //add north if present
1236 if (north == ""){
1237 HemisphereIndicator += south;
1238 }else{
1239 HemisphereIndicator += north;
1240 if (south != ""){
1241 HemisphereIndicator += "|" + south;
1242 }
1243 }
1244
1245 //add east
1246 if (north == "" & south == ""){
1247 HemisphereIndicator += east;
1248 } else {
1249 if (east != ""){
1250 HemisphereIndicator += "|" + east;
1251 }
1252 }
1253
1254 //add west
1255 if (north == "" & south == "" & east == ""){
1256 HemisphereIndicator += west;
1257 } else {
1258 if (west != "") {
1259 HemisphereIndicator += "|" + west;
1260 }
1261 }
1262
1263 //add remaining bits if not empty
1264 if (HemisphereIndicator != "") {
1265 HemisphereIndicator = "(" + HemisphereIndicator + ")?";
1266 }
1267
1268 List<CoordinatePattern> customPatterns = new ArrayList<CoordinatePattern>();
1269
1270 //create custom patterns based on the specified user's input
1271 CoordinatePattern ptrn;
1272
1273 //Custom variation of DD.DDD
1274 ptrn = new CoordinatePattern();
1275 ptrn.description = "Custom variation of DD.DDD";
1276 ptrn.pattern =
1277 CaseInsensitive + "(^" +
1278 WhiteSpace + HemisphereIndicator + WhiteSpace +
1279 "(" +
1280 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace + degreesymbol + WhiteSpace + "$)|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace + degreesymbol + WhiteSpace + "$)" +
1281 ")" +
1282 "|(^" + WhiteSpace +
1283 "(" +
1284 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace + degreesymbol + WhiteSpace + ")|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace + degreesymbol + WhiteSpace + ")" +
1285 ")" +
1286 HemisphereIndicator + WhiteSpace + "$" +
1287 "))"
1288 ;
1289 customPatterns.add(ptrn);
1290
1291 //Custom variation of DD:MM.MMM
1292 ptrn = new CoordinatePattern();
1293 ptrn.description = "Custom variation of DD:MM.MMM";
1294 ptrn.pattern =
1295 CaseInsensitive + "(^" +
1296 WhiteSpace + HemisphereIndicator + WhiteSpace +
1297 "(" +
1298 "(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "$)|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}(\\.|\\,)?" + WhiteSpace + minutesymbol + WhiteSpace + "$)|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}(\\.|\\,)\\d+" + WhiteSpace + minutesymbol + WhiteSpace + "$)" +
1299 ")" +
1300 "|(^" + WhiteSpace +
1301 "(" +
1302 "(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + ")|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}(\\.|\\,)?" + WhiteSpace + minutesymbol + WhiteSpace + ")|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}(\\.|\\,)\\d+" + WhiteSpace + minutesymbol + WhiteSpace + ")" +
1303 ")" +
1304 HemisphereIndicator + WhiteSpace + "$" +
1305 "))"
1306 ;
1307 customPatterns.add(ptrn);
1308
1309 //Custom variation of DD:MM:SS.SSS
1310 ptrn = new CoordinatePattern();
1311 ptrn.description = "Custom variation of DD:MM:SS.SSS";
1312 ptrn.pattern =
1313 CaseInsensitive + "(^" +
1314 WhiteSpace + HemisphereIndicator + WhiteSpace +
1315 "(" +
1316 "(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "$)|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}" + WhiteSpace + minutesymbol + WhiteSpace + "$)|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}" + WhiteSpace + minutesymbol + WhiteSpace + "\\d{1,2}(\\.|\\,)?" + WhiteSpace + secondsymbol + WhiteSpace + "$)|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}" + WhiteSpace + minutesymbol + WhiteSpace + "\\d{1,2}(\\.|\\,)\\d+" + WhiteSpace + secondsymbol + WhiteSpace + "$)" +
1317 ")" +
1318 "|(^" + WhiteSpace +
1319 "(" +
1320 "(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + ")|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}" + WhiteSpace + minutesymbol + WhiteSpace + ")|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}" + WhiteSpace + minutesymbol + WhiteSpace + "\\d{1,2}(\\.|\\,)?" + WhiteSpace + secondsymbol + WhiteSpace + ")|(\\d{1,3}" + WhiteSpace + degreesymbol + WhiteSpace + "\\d{1,2}" + WhiteSpace + minutesymbol + WhiteSpace + "\\d{1,2}(\\.|\\,)\\d+" + WhiteSpace + secondsymbol + WhiteSpace + ")" +
1321 ")" +
1322 HemisphereIndicator + WhiteSpace + "$" +
1323 "))"
1324 ;
1325 customPatterns.add(ptrn);
1326
1327 //check if the default patterns are to be used
1328 if (patternIn.disableDefaultPatterns) {
1329 patterns = customPatterns;
1330 } else { //if all patterns are to be used check which set has the matching priority
1331
1332 //check if the custom patterns are to have priority over the default ones
1333 if (patternIn.priorityOverDefaultPatterns){
1334
1335 //add default patterns to the custom patterns
1336 for (int i = 0; i < patterns.size(); i++){
1337 customPatterns.add(patterns.get(i));
1338 }
1339
1340 //swap array lists
1341 patterns = customPatterns;
1342
1343 }else{
1344 //add custom patterns to the default patterns
1345 for (int i = 0; i < customPatterns.size(); i++){
1346 patterns.add(customPatterns.get(i));
1347
1348 }
1349 }
1350 }
1351 }
1352
1353 }