2 * Copyright (C) 2009 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
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.
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/
14 package eu
.etaxonomy
.cdm
.strategy
.parser
.location
;
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
;
22 import org
.apache
.logging
.log4j
.LogManager
;
23 import org
.apache
.logging
.log4j
.Logger
;
25 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
31 public class CoordinateConverter
{
33 @SuppressWarnings("unused")
34 private static final Logger logger
= LogManager
.getLogger();
37 private List
<CoordinatePattern
> patterns
;
39 private static String minuteUtf8
= "\u02B9|\u00B4|\u02CA|\u0301|\u0374|\u2019";
40 private static String secondUtf8
= "\u02BA|\u030B|\u2033|\u00B4\u00B4|\u201D|''";
43 private class CoordinatePattern
{
49 private Comparator
<CustomHemisphereIndicator
> lengthComparator
= new Comparator
<CustomHemisphereIndicator
>(){
51 public int compare(CustomHemisphereIndicator ind1
, CustomHemisphereIndicator ind2
) {
52 return Integer
.valueOf(ind1
.getLength()).compareTo(ind2
.getLength());
57 public CoordinateConverter() {
58 //initialise pattern array
59 patterns
= new ArrayList
<CoordinatePattern
>();
61 //temp pattern variable
62 CoordinatePattern pattern
;
65 //variations of DD.DDD with white space characters
66 pattern
= new CoordinatePattern();
67 pattern
.description
= "Variation of DD.DDD";
69 //+/-/Nn/Ss/Ww/EeDD.DDDD
71 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
72 "((\\d{1,3}(\\.|\\,)?(\\s)*$)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*$))" +
74 ////DD.DDDDNn/Ss/Ww/Ee
76 "(\\s)*((\\d{1,3}(\\.|\\,)?(\\s)*)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*))" +
77 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
79 patterns
.add(pattern
);
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)";
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)*$))" +
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)*$" +
94 patterns
.add(pattern
);
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)";
101 //+/-/Nn/Ss/Ww/EeDD\u00B0MM"+ minuteUtf8 + "SS.SSS
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)*$))" +
109 //DD°MM"+ minuteUtf8 + "SS.SSSNn/Ss/Ww/Ee
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)*$" +
117 patterns
.add(pattern
);
120 //Variations of DD:MM:SS.SSS with whitespace characters
121 pattern
= new CoordinatePattern();
122 pattern
.description
= "Variation of DD:MM:SS.SSS";
124 // +/-/Nn/Ss/Ww/EeDD:MM:SS.SSS
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)*$))" +
129 //DD:MM:SS.SSSNn/Ss/Ww/Ee
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)*$" +
134 patterns
.add(pattern
);
140 * tests if a string matches one of the defined patterns, returns -1 if no pattern matches
144 private int matchPattern(String str
){
147 //match the string against each available pattern
148 for (int i
= 0; i
< patterns
.size(); i
++){
150 CoordinatePattern pattern
= patterns
.get(i
);
151 Pattern regEx
= Pattern
.compile(pattern
.pattern
);
152 if (regEx
.matcher(str
).find()) {
162 //gets sign of the coordinate (tests for presence of negative sign)
163 private int getSign(String str
){
165 //This regex checks for the negative hemisphere indicator
166 Pattern regexNegative
= Pattern
.compile("(-|S|s|W|w)");
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)");
173 //if a positive indicator is found no need to search further
174 if (regexPositive
.matcher(str
).find()){
177 //if not check whether there was a negative indicator. if so negate otherwise return positive
178 if (regexNegative
.matcher(str
).find()){
187 //this checks for the coordinate sign by evaluating user supplied data
188 private int getCustomSign(String str
){
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
194 //search for the presence of indicators
195 boolean hasPositive
= false;
196 boolean hasNegative
= false;
198 //keep previous negative indicators here
199 List
<String
> previousNegatives
= new ArrayList
<String
>();
201 //compare the string with user supplied custom pattern
202 for (int x
= customPtrn
.hemisphereIndicators
.size() - 1; x
>= 0; x
--){
204 CustomHemisphereIndicator ind
= customPtrn
.hemisphereIndicators
.get(x
);
206 //test here if the indicator exists (has length >0)
207 if (ind
.getLength() > 0){
209 //check if the supplied pattern was marked as case insensitive?
210 String caseInsensitive
= "";
212 if (customPtrn
.caseInsensitive
){
213 caseInsensitive
= "(?i)";
217 Pattern tempRegex
= Pattern
.compile(caseInsensitive
+ ind
.getIndicator());
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()){
224 * See the note below to understand why checking for previous negatives is performed here
227 //check the previous negatives
228 if (previousNegatives
.size() != 0){
229 boolean sameNegative
= false;
231 for (int i
= previousNegatives
.size() - 1; i
>= 0; i
--){
232 if (ind
.getIndicator() == previousNegatives
.get(i
)){
238 //mark as positive only if the previously found negative is the same
243 }else{ //if no negatives before it already marks the sign as positive
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
259 previousNegatives
.add(ind
.getIndicator());
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
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;
294 //replaces comma or dot for current decimal separator
295 private String
fixDecimalSeparator(String str
){
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;
301 String regExReplaceComma
= "(\\,|\\.)";
302 str
= str
.replaceAll(regExReplaceComma
, getDecimalSeparator());
309 private String
removeSign(String str
){
310 String regExRemoveSign
= "(\\+|-|S|s|W|w|N|n|E|e)";
311 str
= str
.replaceAll(regExRemoveSign
, "");
315 //removes custom sign indicators
316 private String
removeCustomPatternParts(String str
){
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
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
;
329 CustomHemisphereIndicator stringToRemove
= new CustomHemisphereIndicator("Degree", customPtrn
.degreeSymbol
,customPtrn
.degreeSymbol
.length(), false);
330 stringsToRemove
.add(stringToRemove
);
333 stringToRemove
= new CustomHemisphereIndicator("Minute", customPtrn
.minuteSymbol
, customPtrn
.minuteSymbol
.length(), false);
334 stringsToRemove
.add(stringToRemove
);
337 stringToRemove
= new CustomHemisphereIndicator("Second", customPtrn
.secondSymbol
, customPtrn
.secondSymbol
.length(), false);
338 stringsToRemove
.add(stringToRemove
);
340 //sort the list (by element's Length property)
341 Collections
.sort(stringsToRemove
, lengthComparator
);
344 // ListSelectionEv.sort(lengthComparator);
347 for (int x
= stringsToRemove
.size() - 1; x
>= 0; x
--){
349 CustomHemisphereIndicator toBeRemoved
= stringsToRemove
.get(x
);
351 //check if the string exists so replacing does not yield errors
352 if (toBeRemoved
.getLength() > 0)
354 //check if the supplied pattern was marked as case insensitive?
355 String CaseInsensitive
= "";
357 if (customPtrn
.caseInsensitive
){
358 CaseInsensitive
= "(?i)";
361 //create regex for replacing
362 String tempRegex
= CaseInsensitive
+ toBeRemoved
.getIndicator();
365 if (toBeRemoved
.getName().equals("Degree") || toBeRemoved
.getName().equals("Minute")) {
366 //replace with a symbol used later for splitting
367 str
= str
.replaceAll(tempRegex
, ":");
370 str
= str
.replaceAll(tempRegex
, "");
379 //removes whitespace characters
380 private String
removeWhiteSpace(String str
){
381 str
= str
.replaceFirst("\\s+", "");
386 //Object for the conversion results
387 public class ConversionResults
{
388 public boolean patternRecognised
;
389 public String patternMatched
;
390 public String patternType
;
392 public boolean conversionSuccessful
;
393 public double convertedCoord
;
394 public boolean canBeLat
;
396 public String conversionComments
;
398 public Boolean isLongitude
;
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
415 String decSeparatorRaw
= String
.valueOf(getDecimalSeparator()); //gets the current decimal separator
416 String decSeparatorRegEx
= decSeparatorRaw
.replace(".", "\\.");
418 ConversionResults results
= new ConversionResults();
420 //Get the matched pattern
421 CoordinatePattern pattern
;
422 int ptrnnum
= matchPattern(str
);
424 pattern
= patterns
.get(ptrnnum
);
426 pattern
= new CoordinatePattern();
427 pattern
.description
= "Unknown";
428 pattern
.pattern
= "No pattern matched";
433 if (pattern
.description
.equals("Variation of DD.DDD")){
435 //Sets pattern machted, successful, pattern type and pattern info
436 initializeResult(results
, pattern
);
440 results
.isLongitude
= getIsLongitude(str
);
442 //Replace comma or dot with a current decimal separator
443 str
= fixDecimalSeparator(str
);
445 //Remove all the unwanted stuff
446 str
= removeSign(str
);
447 str
= removeWhiteSpace(str
);
449 //Since this is already a decimal degree no spliting is needed
450 dd
= Double
.valueOf(str
);
452 checkDegreeRange(dd
, results
);
453 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
455 }else if (pattern
.description
.equals("Variation of DD(\u00B0|d)MM.MMM('|m)")){
457 //Sets pattern machted, successful, pattern type and pattern info
458 initializeResult(results
, pattern
);
462 results
.isLongitude
= getIsLongitude(str
);
464 //Replace comma or dot with a current decimal separator
465 str
= fixDecimalSeparator(str
);
467 //Remove all the unwanted stuff
468 str
= removeSign(str
);
469 str
= removeWhiteSpace(str
);
471 //do some further replacing
472 //Replace degree symbol
473 str
= str
.replaceAll("(\u00B0|\u00BA|D|d)", ":");
475 //remove minute symbol
476 str
= str
.replaceAll("("+ minuteUtf8
+ "|'|M|m)", "");
478 //Extract decimal part
479 decimalBit
= str
.split(decSeparatorRegEx
);
481 //split degrees and minutes
482 ddmm
= decimalBit
[0].split(":");
485 //extract values from the strings
486 dd
= Integer
.valueOf(ddmm
[0]); //Degrees
488 if (ddmm
.length
> 1){ //Minutes
489 //check if the string is not empty
491 mm
= Integer
.valueOf(ddmm
[1]);
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()));
502 checkDegreeRange(dd
, results
);
503 checkMinuteRange(mm
, results
);
504 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
506 }else if (pattern
.description
.equals("Variation of DD(\u00B0|d)MM("+ minuteUtf8
+ "|m)SS.SSS("+secondUtf8
+"|s)")){
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).
514 * If the N / E / W / + indicator is found the coordinate will be parsed appropriately no matter
515 * what is the second notation
518 //Sets pattern matched, successful, pattern type and pattern info
519 initializeResult(results
, pattern
);
524 results
.isLongitude
= getIsLongitude(str
);
526 //Replace comma or dot with a current decimal separator
527 str
= fixDecimalSeparator(str
);
529 //Remove all the unwanted stuff
530 str
= removeSign(str
);
531 str
= removeWhiteSpace(str
);
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
+"|\")", "");
538 //do some further replacing
539 //Replace degree symbol
540 str
= str
.replaceAll("(\u00B0|\u00B0|D|d|"+ minuteUtf8
+ "|'|M|m)",":");
542 //Extract decimal part
543 decimalBit
= str
.split(decSeparatorRegEx
);
545 //remove : from the decimal part [1]! This is needed when a double apostrophe was used to mark seconds
546 if (decimalBit
.length
> 1)
548 decimalBit
[1].replace(":", "");
551 //split degrees and minutes
552 ddmmss
= decimalBit
[0].split(":");
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]);
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());
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()));
576 checkDegreeRange(dd
, results
);
577 checkMinuteRange(mm
, results
);
578 checkSecondRange(ss
, results
);
580 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
582 }else if (pattern
.description
.equals("Variation of DD:MM:SS.SSS")){
584 //Sets pattern machted, successful, pattern type and pattern info
585 initializeResult(results
, pattern
);
589 results
.isLongitude
= getIsLongitude(str
);
591 //Replace comma or dot with a current decimal separator
592 str
= fixDecimalSeparator(str
);
594 //Remove all the unwanted stuff
595 str
= removeSign(str
);
596 str
= removeWhiteSpace(str
);
599 decimalBit
= str
.split(decSeparatorRegEx
);
600 ddmmss
= decimalBit
[0].split(":");
603 //extract values from the strings
604 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
605 if (ddmmss
.length
> 1)//Minutes
607 //check if the string is not empty
608 if (ddmmss
[1] != "") { mm
= Integer
.valueOf(ddmmss
[1]); }
610 if (ddmmss
.length
> 2) {//Seconds{
611 //check if the string is not empty
612 if (ddmmss
[2] != "") {
613 ss
= Integer
.valueOf(ddmmss
[2]);
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()));
623 checkDegreeRange(dd
, results
);
624 checkMinuteRange(mm
, results
);
625 checkSecondRange(ss
, results
);
627 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
629 }else if (pattern
.description
.equals("Custom variation of DD.DDD")){
631 //Sets pattern machted, successful, pattern type and pattern info
632 initializeResult(results
, pattern
);
636 sign
= getCustomSign(str
);
638 //TODO still needs to be adapted to custom pattern
639 results
.isLongitude
= getIsLongitude(str
);
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
);
647 str
= removeWhiteSpace(str
);
649 //Replace comma or dot with a current decimal separator
650 str
= fixDecimalSeparator(str
);
652 //remove the ":" here as it is not needed here for decimal degrees
653 str
= str
.replace(":", "");
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."
671 //Since this is already a decimal degree no spliting is needed
672 dd
= Double
.valueOf(str
);
674 checkDegreeRange(dd
, results
);
675 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
678 }else if (pattern
.description
.equals("Custom variation of DD:MM.MMM")){
679 //-------------Customs patterns start here-------------
681 //Sets pattern machted, successful, pattern type and pattern info
682 initializeResult(results
, pattern
);
685 sign
= getCustomSign(str
);
687 //TODO still needs to be adapted to custom pattern
688 results
.isLongitude
= getIsLongitude(str
);
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
);
697 str
= removeWhiteSpace(str
);
699 //Replace comma or dot with a current decimal separator
700 str
= fixDecimalSeparator(str
);
703 //Extract decimal part
704 decimalBit
= str
.split(decSeparatorRegEx
);
706 //split degrees and minutes
707 ddmm
= decimalBit
[0].split(":");
711 //extract values from the strings
712 dd
= Integer
.valueOf(ddmm
[0]); //Degrees
714 if (ddmm
.length
> 1){//Minutes
715 //check if the string is not empty
716 if (ddmm
[1] != "") { mm
= Integer
.valueOf(ddmm
[1]); }
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(":", "");
725 mmm
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
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."
742 checkDegreeRange(dd
, results
);
743 checkMinuteRange(mm
, results
);
744 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
746 } else if (pattern
.description
.equals("Custom variation of DD:MM:SS.SSS")){
748 //Sets pattern machted, successful, pattern type and pattern info
749 initializeResult(results
, pattern
);
753 sign
= getCustomSign(str
);
755 //TODO still needs to be adapted to custom pattern
756 results
.isLongitude
= getIsLongitude(str
);
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
);
764 str
= removeWhiteSpace(str
);
766 //Replace comma or dot with a current decimal separator
767 str
= fixDecimalSeparator(str
);
770 //Extract decimal part
771 decimalBit
= str
.split(decSeparatorRegEx
);
773 //split degrees and minutes
774 ddmmss
= decimalBit
[0].split(":");
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]);
787 if (ddmmss
.length
> 2){ //Seconds
788 //check if the string is not empty
789 if (ddmmss
[2] != "") {
790 ss
= Integer
.valueOf(ddmmss
[2]);
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()));
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."
813 checkDegreeRange(dd
, results
);
814 checkMinuteRange(mm
, results
);
815 checkSecondRange(ss
, results
);
817 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
819 }else { //default : pattern not recognized
820 results
.patternRecognised
= false;
821 results
.patternType
= pattern
.description
;
822 results
.patternMatched
= pattern
.pattern
;
824 results
.conversionSuccessful
= false;
825 results
.convertedCoord
= 99999; //this is to mark an error...
827 results
.conversionComments
= "Coordinate pattern not recognised!";
831 //do the self check here
832 results
= selfTest(results
);
834 //return conversion results
843 private String
Nz(String string
) {
844 return CdmUtils
.Nz(string
);
856 private void doConvertWithCheck(int sign
, double dd
, double mm
, double mmm
, double ss
, double sss
, ConversionResults results
) {
858 //Do the conversion if everything ok
859 if (results
.conversionSuccessful
){
860 results
.conversionComments
= "Conversion successful.";
862 dec
= sign
* (dd
+ (mm
+ mmm
) / 60 + (ss
+ sss
) / 3600);
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; ";
870 results
.convertedCoord
= dec
;
872 results
.conversionComments
= "Conversion successful.";
874 //Check whether the coordinate exceeds +/- 90 and mark it in comments
876 if (dec
<= 90 && dec
>= -90 && (results
.isLongitude
== null || results
.isLongitude
== false) ) {
877 results
.canBeLat
= true;
879 results
.isLongitude
= true;
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; ";
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; ";
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; ";
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)");
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)");
940 //if a positive indicator is found no need to search further
941 if (regexLongitude
.matcher(str
).find()){
943 }else if (regexLatitudeNonAmbigous
.matcher(str
).find()){
945 }else if (regexLatitudeAmbigous
.matcher(str
).find()){
946 Pattern regexLiteralUnits
= Pattern
.compile("(D|d|M|m)");
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()){
952 }else if (regexLatitudeAmbigous
.matcher(str
).groupCount() > 1){
964 * Sets pattern machted, successful, pattern type and pattern info
968 private void initializeResult(ConversionResults results
,
969 CoordinatePattern pattern
) {
971 results
.patternRecognised
= true;
973 //Matching pattern succeeded so intialy the parsing is ok
974 results
.conversionSuccessful
= true;
977 results
.patternType
= pattern
.description
;
978 results
.patternMatched
= pattern
.pattern
;
982 private ConversionResults
selfTest(ConversionResults results
){
984 ConversionResults newresults
= results
;
986 if (results
.conversionSuccessful
!= false){
988 if (Math
.signum(results
.convertedCoord
) < 0) {
992 double decimalDegrees
= sign
* results
.convertedCoord
;
995 double decimalMinutes
;
998 double decimalSeconds
;
1002 fullDegrees
= (int)Math
.floor(decimalDegrees
);
1005 decimalMinutes
= (decimalDegrees
- fullDegrees
) * 60;
1006 fullMinutes
= (int)Math
.floor(decimalMinutes
);
1008 decimalSeconds
= (decimalMinutes
- fullMinutes
) * 60;
1009 fullSeconds
= (int)Math
.floor(decimalSeconds
);
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
;
1026 //------------ CUSTOM PATTERN BUILDER--------------
1028 public class CustomPatternIn
{
1029 public String north
;
1030 public String south
;
1034 public String degreeSymbol
;
1035 public String minuteSymbol
;
1036 public String secondSymbol
;
1038 public boolean caseInsensitive
;
1039 public boolean allowWhiteSpace
;
1040 public boolean priorityOverDefaultPatterns
;
1041 public boolean disableDefaultPatterns
;
1046 private class CustomPattern
{
1048 public List
<CustomHemisphereIndicator
> hemisphereIndicators
;
1050 public String degreeSymbol
;
1051 public String minuteSymbol
;
1052 public String secondSymbol
;
1054 public boolean caseInsensitive
;
1058 //global variable to be used if a custom pattern is used
1059 private CustomPattern customPtrn
;
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("\\", "\\\\");
1067 str
= str
.replace(".", "\\.");
1068 str
= str
.replace(",", "\\,");
1071 str
= str
.replace("(", "\\(");
1072 str
= str
.replace(")", "\\)");
1073 str
= str
.replace("[", "\\[");
1074 str
= str
.replace("]", "\\]");
1075 str
= str
.replace("{", "\\{");
1076 str
= str
.replace("}", "\\}");
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("|", "\\|");
1090 //this implements sorting by using system.Icomparable - sorting is needed later when replacing
1091 private class CustomHemisphereIndicator
implements Comparable
<CustomHemisphereIndicator
> {
1093 private int m_length
;
1094 private String m_name
;
1095 private String m_indicator
;
1096 private boolean m_positive
;
1099 public CustomHemisphereIndicator(String name
, String indicator
, int length
, boolean positive
){
1101 this.m_indicator
= indicator
;
1102 this.m_length
= length
;
1103 this.m_positive
= positive
;
1108 public String
getName(){
1111 public void setName(String value
){
1112 this.m_name
= value
;
1115 public String
getIndicator(){
1116 return this.m_indicator
;
1118 public void setIndicator(String value
){
1119 this.m_indicator
= value
;
1122 public int getLength(){
1123 return this.m_length
;
1125 public void setLength(int value
){
1126 this.m_length
= value
;
1130 public boolean getPositive(){
1131 return this.m_positive
;
1133 public void setPositive(boolean value
){
1134 this.m_positive
= value
;
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.
1141 * This method uses the predefined method Int32.CompareTo
1145 public int compareTo(CustomHemisphereIndicator ind
){
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()));
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
){
1158 //new custom pattern object - to pass the needed data farther
1159 CustomPattern pattern
= new CustomPattern();
1161 //keep indicators for parsing
1162 List
<CustomHemisphereIndicator
> indicators
= new ArrayList
<CustomHemisphereIndicator
>();
1165 CustomHemisphereIndicator ind
= new CustomHemisphereIndicator("North", patternIn
.north
, patternIn
.north
.length() ,true);
1166 indicators
.add(ind
);
1169 ind
= new CustomHemisphereIndicator("South", patternIn
.south
, patternIn
.south
.length(), false);
1170 indicators
.add(ind
);
1173 ind
= new CustomHemisphereIndicator("East", patternIn
.east
, patternIn
.east
.length(), true);
1174 indicators
.add(ind
);
1177 ind
= new CustomHemisphereIndicator("West", patternIn
.west
, patternIn
.west
.length(), false);
1178 indicators
.add(ind
);
1180 //sort the arraylist
1181 Collections
.sort(indicators
, lengthComparator
);
1184 //add it to the pattern object
1185 pattern
.hemisphereIndicators
= indicators
;
1188 pattern
.caseInsensitive
= patternIn
.caseInsensitive
;
1190 //keep symbols for parsing
1191 pattern
.degreeSymbol
= patternIn
.degreeSymbol
;
1192 pattern
.minuteSymbol
= patternIn
.minuteSymbol
;
1193 pattern
.secondSymbol
= patternIn
.secondSymbol
;
1197 customPtrn
= pattern
;
1200 //----------------build custom patterns----------------
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
);
1209 String degreesymbol
= "";
1210 if (patternIn
.degreeSymbol
!= ""){
1211 degreesymbol
= "(" + escapeChars(patternIn
.degreeSymbol
) + ")?";
1214 String minutesymbol
= "";
1215 if (patternIn
.minuteSymbol
!= ""){
1216 minutesymbol
= "(" + escapeChars(patternIn
.minuteSymbol
) + ")?";
1219 String secondsymbol
= "";
1220 if (escapeChars(patternIn
.secondSymbol
) != ""){
1221 secondsymbol
= "(" + escapeChars(patternIn
.secondSymbol
) + ")?";
1225 //is the pattern to be case insensitive?
1226 String CaseInsensitive
= "";
1227 if (patternIn
.caseInsensitive
){
1228 CaseInsensitive
= "(?i)";
1232 String WhiteSpace
= "";
1233 if (patternIn
.allowWhiteSpace
== true){
1234 WhiteSpace
= "(\\s)*";
1237 //hemisphere indicator
1238 String HemisphereIndicator
= "";
1240 //add north if present
1242 HemisphereIndicator
+= south
;
1244 HemisphereIndicator
+= north
;
1246 HemisphereIndicator
+= "|" + south
;
1251 if (north
== "" & south
== ""){
1252 HemisphereIndicator
+= east
;
1255 HemisphereIndicator
+= "|" + east
;
1260 if (north
== "" & south
== "" & east
== ""){
1261 HemisphereIndicator
+= west
;
1264 HemisphereIndicator
+= "|" + west
;
1268 //add remaining bits if not empty
1269 if (HemisphereIndicator
!= "") {
1270 HemisphereIndicator
= "(" + HemisphereIndicator
+ ")?";
1273 List
<CoordinatePattern
> customPatterns
= new ArrayList
<CoordinatePattern
>();
1275 //create custom patterns based on the specified user's input
1276 CoordinatePattern ptrn
;
1278 //Custom variation of DD.DDD
1279 ptrn
= new CoordinatePattern();
1280 ptrn
.description
= "Custom variation of DD.DDD";
1282 CaseInsensitive
+ "(^" +
1283 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1285 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)" +
1287 "|(^" + WhiteSpace
+
1289 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")" +
1291 HemisphereIndicator
+ WhiteSpace
+ "$" +
1294 customPatterns
.add(ptrn
);
1296 //Custom variation of DD:MM.MMM
1297 ptrn
= new CoordinatePattern();
1298 ptrn
.description
= "Custom variation of DD:MM.MMM";
1300 CaseInsensitive
+ "(^" +
1301 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
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
+ "$)" +
1305 "|(^" + WhiteSpace
+
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
+ ")" +
1309 HemisphereIndicator
+ WhiteSpace
+ "$" +
1312 customPatterns
.add(ptrn
);
1314 //Custom variation of DD:MM:SS.SSS
1315 ptrn
= new CoordinatePattern();
1316 ptrn
.description
= "Custom variation of DD:MM:SS.SSS";
1318 CaseInsensitive
+ "(^" +
1319 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
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
+ "$)" +
1323 "|(^" + WhiteSpace
+
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
+ ")" +
1327 HemisphereIndicator
+ WhiteSpace
+ "$" +
1330 customPatterns
.add(ptrn
);
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
1337 //check if the custom patterns are to have priority over the default ones
1338 if (patternIn
.priorityOverDefaultPatterns
){
1340 //add default patterns to the custom patterns
1341 for (int i
= 0; i
< patterns
.size(); i
++){
1342 customPatterns
.add(patterns
.get(i
));
1346 patterns
= customPatterns
;
1349 //add custom patterns to the default patterns
1350 for (int i
= 0; i
< customPatterns
.size(); i
++){
1351 patterns
.add(customPatterns
.get(i
));