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