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
.log4j
.Logger
;
29 public class CoordinateConverter
{
30 @SuppressWarnings("unused")
31 private static final Logger logger
= Logger
.getLogger(CoordinateConverter
.class);
34 private List
<CoordinatePattern
> patterns
;
36 private static String minuteUtf8
= "\u02B9|\u00B4|\u02CA|\u0301|\u0374";
37 private static String secondUtf8
= "\u02BA|\u030B|\u2033|\u00B4\u00B4";
40 private class CoordinatePattern
{
46 private Comparator
<CustomHemisphereIndicator
> lengthComparator
= new Comparator
<CustomHemisphereIndicator
>(){
48 public int compare(CustomHemisphereIndicator ind1
, CustomHemisphereIndicator ind2
) {
49 return Integer
.valueOf(ind1
.getLength()).compareTo(ind2
.getLength());
54 public CoordinateConverter() {
55 //initialise pattern array
56 patterns
= new ArrayList
<CoordinatePattern
>();
58 //temp pattern variable
59 CoordinatePattern pattern
;
62 //variations of DD.DDD with white space characters
63 pattern
= new CoordinatePattern();
64 pattern
.description
= "Variation of DD.DDD";
66 //+/-/Nn/Ss/Ww/EeDD.DDDD
68 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
69 "((\\d{1,3}(\\.|\\,)?(\\s)*$)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*$))" +
71 ////DD.DDDDNn/Ss/Ww/Ee
73 "(\\s)*((\\d{1,3}(\\.|\\,)?(\\s)*)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*))" +
74 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
76 patterns
.add(pattern
);
79 //Variations of DD(\u00B0|d)MM.MMM' with whitespace characters
80 pattern
= new CoordinatePattern();
81 pattern
.description
= "Variation of DD(\u00B0|d)MM.MMM('|m)";
84 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
85 "((\\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 "(\\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)*))" +
89 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
91 patterns
.add(pattern
);
94 //Variations of DD\u00B0MM'SS.SSS" with whitespace characters
95 pattern
= new CoordinatePattern();
96 pattern
.description
= "Variation of DD(\u00B0|d)MM("+ minuteUtf8
+ "|m)SS.SSS("+secondUtf8
+"|s)";
98 //+/-/Nn/Ss/Ww/EeDD\u00B0MM"+ minuteUtf8 + "SS.SSS
100 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
101 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*$)" +
102 "|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8
+ "|'|M|m)?(\\s)*$)" +
103 "|(\\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)*$)" +
104 "|(\\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)*$))" +
106 //DD°MM"+ minuteUtf8 + "SS.SSSNn/Ss/Ww/Ee
108 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*)|" +
109 "(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8
+ "|'|M|m)?(\\s)*)|" +
110 "(\\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)*)|" +
111 "(\\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)*))" +
112 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
114 patterns
.add(pattern
);
117 //Variations of DD:MM:SS.SSS with whitespace characters
118 pattern
= new CoordinatePattern();
119 pattern
.description
= "Variation of DD:MM:SS.SSS";
121 // +/-/Nn/Ss/Ww/EeDD:MM:SS.SSS
123 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
124 "((\\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)*$))" +
126 //DD:MM:SS.SSSNn/Ss/Ww/Ee
128 "(\\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)*))" +
129 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
131 patterns
.add(pattern
);
136 //tests if a string matches one of the defined patterns
137 private int matchPattern(String str
){
140 //match the string against each available patern
141 for (int i
= 0; i
< patterns
.size(); i
++){
143 CoordinatePattern pattern
= patterns
.get(i
);
144 Pattern regEx
= Pattern
.compile(pattern
.pattern
);
145 if (regEx
.matcher(str
).find()) {
155 //gets sign of the coordinate (tests for presence of negative sign)
156 private int getSign(String str
){
158 //This regex checks for the negative hemisphere indicator
159 Pattern regexNegative
= Pattern
.compile("(-|S|s|W|w)");
161 //This regex checks if there weren't any other hemisphere indicators
162 //it is needed for the specific case of the DDdMMmSSs S
163 //so it needs to be ensured there where no positive indicators
164 Pattern regexPositive
= Pattern
.compile("(\\+|N|n|E|e)");
166 //if a positive indicator is found no need to search further
167 if (regexPositive
.matcher(str
).find()){
170 //if not check whether there was a negative indicator. if so negate otherwise return positive
171 if (regexNegative
.matcher(str
).find()){
180 //this checks for the coordinate sign by evaluating user supplied data
181 private int getCustomSign(String str
){
183 //Indicators are evaluated from the longest ones to the shortes ones
184 //So when searching for "P" does not affect "PN" as "PN" is evaluated earlier
187 //search for the presence of indicators
188 boolean hasPositive
= false;
189 boolean hasNegative
= false;
191 //keep previous negative indicators here
192 List
<String
> previousNegatives
= new ArrayList
<String
>();
194 //compare the string with user supplied custom pattern
195 for (int x
= customPtrn
.hemisphereIndicators
.size() - 1; x
>= 0; x
--){
197 CustomHemisphereIndicator ind
= customPtrn
.hemisphereIndicators
.get(x
);
199 //test here if the indicator exists (has length >0)
200 if (ind
.getLength() > 0){
202 //check if the supplied pattern was marked as case insensitive?
203 String caseInsensitive
= "";
205 if (customPtrn
.caseInsensitive
){
206 caseInsensitive
= "(?i)";
210 Pattern tempRegex
= Pattern
.compile(caseInsensitive
+ ind
.getIndicator());
212 //if a pattern is found
213 if (tempRegex
.matcher(str
).find()){
214 //check whether it's a positive or negative indicator
215 if (ind
.getPositive()){
217 * See the note below to understand why checking for previous negatives is performed here
220 //check the previous negatives
221 if (previousNegatives
.size() != 0){
222 boolean sameNegative
= false;
224 for (int i
= previousNegatives
.size() - 1; i
>= 0; i
--){
225 if (ind
.getIndicator() == previousNegatives
.get(i
)){
231 //mark as positive only if the previously found negative is the same
236 }else{ //if no negatives before it already marks the sign as positive
242 * save the negative indicator here so it can be compared later if a positive wants to overwrite it!
243 * in a case a longer negative "Pn" has already been found a shorter positive "P" will not overwrite it
244 * and the hasPositive will remain false;
245 * In a case a "P" negative indicator has already been found, positive will mark hasPositive and therefore
246 * later a default positive value will be returned (if the indicators for positive & negative are the same
247 * positive is returned)
248 * testing for previous positives is not required since if a hasPositive is already true method will return
252 previousNegatives
.add(ind
.getIndicator());
263 //positive indicator has priority here - if both indicators supplied by the user are the same, a positive is chosen
264 //if there were no indicator found in the tested coordinate, a positive value is returned by default
279 //returns a currently used decimal separator
280 private String
getDecimalSeparator(){
281 //TODO not yet transformed from C#
282 // return System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
287 //replaces comma or dot for current decimal separator
288 private String
fixDecimalSeparator(String str
){
290 //Coma is replaced as parsers often recognise dot as a decimal separator
291 //Comma or dot is replaced with a decimal separator here (environment settings)
292 //But decimal separator has to be used later too;
294 String regExReplaceComma
= "(\\,|\\.)";
295 str
= str
.replaceAll(regExReplaceComma
, getDecimalSeparator());
302 private String
removeSign(String str
){
303 String regExRemoveSign
= "(\\+|-|S|s|W|w|N|n|E|e)";
304 str
= str
.replaceAll(regExRemoveSign
, "");
308 //removes custom sign indicators
309 private String
removeCustomPatternParts(String str
){
312 * Symbols are added here so the removing tries to not affect the coordinate too much
313 * Strings to be removed then are evaluated from the longest ones to the shortes ones
314 * So when searching for "P" does not affect "PN" as "PN" is evaluated earlier
317 //CustomHemisphereIndicator is used here so another object does not have to be created
318 //only for the string cleanning
319 List
<CustomHemisphereIndicator
> stringsToRemove
= customPtrn
.hemisphereIndicators
;
322 CustomHemisphereIndicator stringToRemove
= new CustomHemisphereIndicator("Degree", customPtrn
.degreeSymbol
,customPtrn
.degreeSymbol
.length(), false);
323 stringsToRemove
.add(stringToRemove
);
326 stringToRemove
= new CustomHemisphereIndicator("Minute", customPtrn
.minuteSymbol
, customPtrn
.minuteSymbol
.length(), false);
327 stringsToRemove
.add(stringToRemove
);
330 stringToRemove
= new CustomHemisphereIndicator("Second", customPtrn
.secondSymbol
, customPtrn
.secondSymbol
.length(), false);
331 stringsToRemove
.add(stringToRemove
);
333 //sort the list (by element's Length property)
334 Collections
.sort(stringsToRemove
, lengthComparator
);
337 // ListSelectionEv.sort(lengthComparator);
340 for (int x
= stringsToRemove
.size() - 1; x
>= 0; x
--){
342 CustomHemisphereIndicator toBeRemoved
= stringsToRemove
.get(x
);
344 //check if the string exists so replacing does not yield errors
345 if (toBeRemoved
.getLength() > 0)
347 //check if the supplied pattern was marked as case insensitive?
348 String CaseInsensitive
= "";
350 if (customPtrn
.caseInsensitive
){
351 CaseInsensitive
= "(?i)";
354 //create regex for replacing
355 String tempRegex
= CaseInsensitive
+ toBeRemoved
.getIndicator();
358 if (toBeRemoved
.getName().equals("Degree") || toBeRemoved
.getName().equals("Minute")) {
359 //replace with a symbol used later for splitting
360 str
= str
.replaceAll(tempRegex
, ":");
363 str
= str
.replaceAll(tempRegex
, "");
372 //removes whitespace characters
373 private String
removeWhiteSpace(String str
){
374 str
= str
.replaceFirst("\\s+", "");
379 //Object for the conversion results
380 public class ConversionResults
{
381 public boolean patternRecognised
;
382 public String patternMatched
;
383 public String patternType
;
385 public boolean conversionSuccessful
;
386 public double convertedCoord
;
387 public boolean canBeLat
;
389 public String conversionComments
;
391 public Boolean isLongitude
;
402 public ConversionResults
tryConvert(String str
){
403 //some local variables
404 int sign
; //sign of the coordinate
405 String
[] decimalBit
, ddmmss
, ddmm
; //arrays for splitting
406 double dd
= 0, mm
= 0, ss
= 0, mmm
= 0, sss
= 0, dec
= 0; //parts of the coordinates
408 String decSeparatorRaw
= String
.valueOf(getDecimalSeparator()); //gets the current decimal separator
409 String decSeparatorRegEx
= decSeparatorRaw
.replace(".", "\\.");
411 ConversionResults results
= new ConversionResults();
413 //Get the matched pattern
414 CoordinatePattern pattern
;
415 int ptrnnum
= matchPattern(str
);
417 pattern
= patterns
.get(ptrnnum
);
419 pattern
= new CoordinatePattern();
420 pattern
.description
= "Unknown";
421 pattern
.pattern
= "No pattern matched";
426 if (pattern
.description
.equals("Variation of DD.DDD")){
428 //Sets pattern machted, successful, pattern type and pattern info
429 initializeResult(results
, pattern
);
433 results
.isLongitude
= getIsLongitude(str
);
435 //Replace comma or dot with a current decimal separator
436 str
= fixDecimalSeparator(str
);
438 //Remove all the unwanted stuff
439 str
= removeSign(str
);
440 str
= removeWhiteSpace(str
);
442 //Since this is already a decimal degree no spliting is needed
443 dd
= Double
.valueOf(str
);
445 checkDegreeRange(dd
, results
);
446 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
448 }else if (pattern
.description
.equals("Variation of DD(\u00B0|d)MM.MMM('|m)")){
450 //Sets pattern machted, successful, pattern type and pattern info
451 initializeResult(results
, pattern
);
455 results
.isLongitude
= getIsLongitude(str
);
457 //Replace comma or dot with a current decimal separator
458 str
= fixDecimalSeparator(str
);
460 //Remove all the unwanted stuff
461 str
= removeSign(str
);
462 str
= removeWhiteSpace(str
);
464 //do some further replacing
465 //Replace degree symbol
466 str
= str
.replaceAll("(\u00B0|\u00BA|D|d)", ":");
468 //remove minute symbol
469 str
= str
.replaceAll("("+ minuteUtf8
+ "|'|M|m)", "");
471 //Extract decimal part
472 decimalBit
= str
.split(decSeparatorRegEx
);
474 //split degrees and minutes
475 ddmm
= decimalBit
[0].split(":");
478 //extract values from the strings
479 dd
= Integer
.valueOf(ddmm
[0]); //Degrees
481 if (ddmm
.length
> 1){ //Minutes
482 //check if the string is not empty
484 mm
= Integer
.valueOf(ddmm
[1]);
488 if (decimalBit
.length
> 1){//DecimalSeconds
489 //check if the string is not empty
490 if (decimalBit
[1] != "") {
491 mmm
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
495 checkDegreeRange(dd
, results
);
496 checkMinuteRange(mm
, results
);
497 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
499 }else if (pattern
.description
.equals("Variation of DD(\u00B0|d)MM("+ minuteUtf8
+ "|m)SS.SSS("+secondUtf8
+"|s)")){
503 * This pattern allows the seconds to be specified with S, s or " or nothing at all
504 * If the seconds are marked with "s" and there is no other indication of the hemisphere
505 * the coordinate will be parsed as southern (negative).
507 * If the N / E / W / + indicator is found the coordinate will be parsed appropriately no matter
508 * what is the second notation
511 //Sets pattern machted, successful, pattern type and pattern info
512 initializeResult(results
, pattern
);
517 results
.isLongitude
= getIsLongitude(str
);
519 //Replace comma or dot with a current decimal separator
520 str
= fixDecimalSeparator(str
);
522 //Remove all the unwanted stuff
523 str
= removeSign(str
);
524 str
= removeWhiteSpace(str
);
526 //remove second symbol (s is removed by the get sign method)
527 //double apostrophe is not removed here as single apostrphe may mark minutes!
528 //it's taken care of later after extracting the decimal part
529 str
= str
.replaceAll("("+secondUtf8
+"|\")", "");
531 //do some further replacing
532 //Replace degree symbol
533 str
= str
.replaceAll("(\u00B0|\u00B0|D|d|"+ minuteUtf8
+ "|'|M|m)",":");
535 //Extract decimal part
536 decimalBit
= str
.split(decSeparatorRegEx
);
538 //remove : from the decimal part [1]! This is needed when a double apostrophe was used to mark seconds
539 if (decimalBit
.length
> 1)
541 decimalBit
[1].replace(":", "");
544 //split degrees and minutes
545 ddmmss
= decimalBit
[0].split(":");
548 //extract values from the strings
549 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
550 if (ddmmss
.length
> 1){//Minutes
551 //check if the string is not empty
552 if (ddmmss
[1] != "") {
553 mm
= Integer
.valueOf(ddmmss
[1]);
556 if (ddmmss
.length
> 2){//Seconds
557 //check if the string is not empty
558 if (ddmmss
[2] != "") {
559 ss
= Integer
.valueOf(ddmmss
[2]);
562 if (decimalBit
.length
> 1) { //DecimalSeconds
563 //check if the string is not empty
564 if (decimalBit
[1] != "") {
565 sss
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
569 checkDegreeRange(dd
, results
);
570 checkMinuteRange(mm
, results
);
571 checkSecondRange(ss
, results
);
573 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
575 }else if (pattern
.description
.equals("Variation of DD:MM:SS.SSS")){
577 //Sets pattern machted, successful, pattern type and pattern info
578 initializeResult(results
, pattern
);
582 results
.isLongitude
= getIsLongitude(str
);
584 //Replace comma or dot with a current decimal separator
585 str
= fixDecimalSeparator(str
);
587 //Remove all the unwanted stuff
588 str
= removeSign(str
);
589 str
= removeWhiteSpace(str
);
592 decimalBit
= str
.split(decSeparatorRegEx
);
593 ddmmss
= decimalBit
[0].split(":");
596 //extract values from the strings
597 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
598 if (ddmmss
.length
> 1)//Minutes
600 //check if the string is not empty
601 if (ddmmss
[1] != "") { mm
= Integer
.valueOf(ddmmss
[1]); }
603 if (ddmmss
.length
> 2) {//Seconds{
604 //check if the string is not empty
605 if (ddmmss
[2] != "") {
606 ss
= Integer
.valueOf(ddmmss
[2]);
609 if (decimalBit
.length
> 1) { //DecimalSeconds
610 //check if the string is not empty
611 if (decimalBit
[1] != "") {
612 sss
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
616 checkDegreeRange(dd
, results
);
617 checkMinuteRange(mm
, results
);
618 checkSecondRange(ss
, results
);
620 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
622 }else if (pattern
.description
.equals("Custom variation of DD.DDD")){
624 //Sets pattern machted, successful, pattern type and pattern info
625 initializeResult(results
, pattern
);
629 sign
= getCustomSign(str
);
631 //TODO still needs to be adapted to custom pattern
632 results
.isLongitude
= getIsLongitude(str
);
635 //Remove all the unwanted stuff
636 //Note: This method also replaces the symbols with ":"
637 //Note: In certain cases it may make the coord unparsable
638 str
= removeCustomPatternParts(str
);
640 str
= removeWhiteSpace(str
);
642 //Replace comma or dot with a current decimal separator
643 str
= fixDecimalSeparator(str
);
645 //remove the ":" here as it is not needed here for decimal degrees
646 str
= str
.replace(":", "");
649 //Since this is already a decimal degree no spliting is needed
650 dd
= Double
.valueOf(str
);
651 } catch (Exception e
) {
652 results
.conversionSuccessful
= false;
653 results
.convertedCoord
= 99999; //this is to mark an error...
654 results
.conversionComments
=
655 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
656 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
657 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
664 //Since this is already a decimal degree no spliting is needed
665 dd
= Double
.valueOf(str
);
667 checkDegreeRange(dd
, results
);
668 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
671 }else if (pattern
.description
.equals("Custom variation of DD:MM.MMM")){
672 //-------------Customs patterns start here-------------
674 //Sets pattern machted, successful, pattern type and pattern info
675 initializeResult(results
, pattern
);
678 sign
= getCustomSign(str
);
680 //TODO still needs to be adapted to custom pattern
681 results
.isLongitude
= getIsLongitude(str
);
685 //Remove all the unwanted stuff
686 //Note: This method also replaces the symbols with ":"
687 //Note: In certain cases it may make the coord unparsable
688 str
= removeCustomPatternParts(str
);
690 str
= removeWhiteSpace(str
);
692 //Replace comma or dot with a current decimal separator
693 str
= fixDecimalSeparator(str
);
696 //Extract decimal part
697 decimalBit
= str
.split(decSeparatorRegEx
);
699 //split degrees and minutes
700 ddmm
= decimalBit
[0].split(":");
704 //extract values from the strings
705 dd
= Integer
.valueOf(ddmm
[0]); //Degrees
707 if (ddmm
.length
> 1){//Minutes
708 //check if the string is not empty
709 if (ddmm
[1] != "") { mm
= Integer
.valueOf(ddmm
[1]); }
712 if (decimalBit
.length
> 1){//DecimalSeconds
713 //check if the string is not empty
714 if (decimalBit
[1] != ""){
715 //replace the ":" if any (may be here as a result of custom symbol replacement
716 decimalBit
[1] = decimalBit
[1].replace(":", "");
718 mmm
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
721 } catch (Exception e
){
722 results
.conversionSuccessful
= false;
723 results
.convertedCoord
= 99999; //this is to mark an error...
724 results
.conversionComments
=
725 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
726 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
727 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
735 checkDegreeRange(dd
, results
);
736 checkMinuteRange(mm
, results
);
737 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
739 } else if (pattern
.description
.equals("Custom variation of DD:MM:SS.SSS")){
741 //Sets pattern machted, successful, pattern type and pattern info
742 initializeResult(results
, pattern
);
746 sign
= getCustomSign(str
);
748 //TODO still needs to be adapted to custom pattern
749 results
.isLongitude
= getIsLongitude(str
);
752 //Remove all the unwanted stuff
753 //Note: This method also replaces the symbols with ":"
754 //Note: In certain cases it may make the coord unparsable
755 str
= removeCustomPatternParts(str
);
757 str
= removeWhiteSpace(str
);
759 //Replace comma or dot with a current decimal separator
760 str
= fixDecimalSeparator(str
);
763 //Extract decimal part
764 decimalBit
= str
.split(decSeparatorRegEx
);
766 //split degrees and minutes
767 ddmmss
= decimalBit
[0].split(":");
772 //extract values from the strings
773 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
774 if (ddmmss
.length
> 1) {//Minutes
775 //check if the string is not empty
776 if (ddmmss
[1] != "") {
777 mm
= Integer
.valueOf(ddmmss
[1]);
780 if (ddmmss
.length
> 2){ //Seconds
781 //check if the string is not empty
782 if (ddmmss
[2] != "") {
783 ss
= Integer
.valueOf(ddmmss
[2]);
786 if (decimalBit
.length
> 1){ //DecimalSeconds
787 //check if the string is not empty
788 if (decimalBit
[1] != "") {
789 sss
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
792 } catch (Exception e
) {
793 results
.conversionSuccessful
= false;
794 results
.convertedCoord
= 99999; //this is to mark an error...
795 results
.conversionComments
=
796 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
797 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
798 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
806 checkDegreeRange(dd
, results
);
807 checkMinuteRange(mm
, results
);
808 checkSecondRange(ss
, results
);
810 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
812 }else { //default : pattern not recognized
813 results
.patternRecognised
= false;
814 results
.patternType
= pattern
.description
;
815 results
.patternMatched
= pattern
.pattern
;
817 results
.conversionSuccessful
= false;
818 results
.convertedCoord
= 99999; //this is to mark an error...
820 results
.conversionComments
= "Coordinate pattern not recognised!";
824 //do the self check here
825 results
= selfTest(results
);
827 //return conversion results
840 private void doConvertWithCheck(int sign
, double dd
, double mm
, double mmm
, double ss
, double sss
, ConversionResults results
) {
842 //Do the conversion if everything ok
843 if (results
.conversionSuccessful
){
844 results
.conversionComments
= "Conversion successful.";
846 dec
= sign
* (dd
+ (mm
+ mmm
) / 60 + (ss
+ sss
) / 3600);
848 //one more check to ensure a coord does not exceed 180
849 if (dec
> 180 | dec
< -180){
850 results
.conversionSuccessful
= false;
851 results
.convertedCoord
= 99999; //this is to mark an error...
852 results
.conversionComments
+= "Coordinate is either > 180 or < -180; ";
854 results
.convertedCoord
= dec
;
856 results
.conversionComments
= "Conversion successful.";
858 //Check whether the coordinate exceeds +/- 90 and mark it in comments
860 if (dec
<= 90 && dec
>= -90 && (results
.isLongitude
== null || results
.isLongitude
== false) ) {
861 results
.canBeLat
= true;
863 results
.isLongitude
= true;
874 private void checkSecondRange(double ss
, ConversionResults results
) {
875 if (ss
> 59) {//seconds
876 results
.conversionSuccessful
= false;
877 results
.convertedCoord
= 99999; //this is to mark an error...
878 results
.conversionComments
+= "Seconds fall outside the range: MM >= 60; ";
887 private void checkMinuteRange(double mm
, ConversionResults results
) {
888 if (mm
> 59) {//minutes
889 results
.conversionSuccessful
= false;
890 results
.convertedCoord
= 99999; //this is to mark an error...
891 results
.conversionComments
+= "Minutes fall outside the range: MM > 59; ";
900 private void checkDegreeRange(double dd
, ConversionResults results
) {
901 //do some additional checking if the coords fall into the range
902 if (dd
< -180 | dd
> 180){ //degree may require another param specifying whether it's lat or lon...
903 results
.conversionSuccessful
= false;
904 results
.convertedCoord
= 99999; //this is to mark an error...
905 results
.conversionComments
+= "Degrees fall outside the range: DD < -180 | DD > 180; ";
914 private Boolean
getIsLongitude(String str
) {
915 //This regex checks for the negative hemisphere indicator
916 Pattern regexLatitudeNonAmbigous
= Pattern
.compile("(N|n)");
917 Pattern regexLatitudeAmbigous
= Pattern
.compile("(S|s)");
919 //This regex checks if there weren't any other hemisphere indicators
920 //it is needed for the specific case of the DDdMMmSSs S
921 //so it needs to be ensured there where no positive indicators
922 Pattern regexLongitude
= Pattern
.compile("(W|w|E|e)");
924 //if a positive indicator is found no need to search further
925 if (regexLongitude
.matcher(str
).find()){
927 }else if (regexLatitudeNonAmbigous
.matcher(str
).find()){
929 }else if (regexLatitudeAmbigous
.matcher(str
).find()){
930 Pattern regexLiteralUnits
= Pattern
.compile("(D|d|M|m)");
932 //if there are no other literal units we assume that S is a
933 //direction and not a second indicator
934 if (! regexLiteralUnits
.matcher(str
).find()){
936 }else if (regexLatitudeAmbigous
.matcher(str
).groupCount() > 1){
948 * Sets pattern machted, successful, pattern type and pattern info
952 private void initializeResult(ConversionResults results
,
953 CoordinatePattern pattern
) {
955 results
.patternRecognised
= true;
957 //Matching pattern succeeded so intialy the parsing is ok
958 results
.conversionSuccessful
= true;
961 results
.patternType
= pattern
.description
;
962 results
.patternMatched
= pattern
.pattern
;
966 private ConversionResults
selfTest(ConversionResults results
){
968 ConversionResults newresults
= results
;
970 if (results
.conversionSuccessful
!= false){
972 if (Math
.signum(results
.convertedCoord
) < 0) {
976 double decimalDegrees
= sign
* results
.convertedCoord
;
979 double decimalMinutes
;
982 double decimalSeconds
;
986 fullDegrees
= (int)Math
.floor(decimalDegrees
);
989 decimalMinutes
= (decimalDegrees
- fullDegrees
) * 60;
990 fullMinutes
= (int)Math
.floor(decimalMinutes
);
992 decimalSeconds
= (decimalMinutes
- fullMinutes
) * 60;
993 fullSeconds
= (int)Math
.floor(decimalSeconds
);
995 //save the test results
996 newresults
.dd
= fullDegrees
;
997 newresults
.mm
= fullMinutes
;
998 newresults
.mmm
= decimalSeconds
;
999 newresults
.ss
= fullSeconds
;
1000 newresults
.sss
= decimalSeconds
;
1010 //------------ CUSTOM PATTERN BUILDER--------------
1012 public class CustomPatternIn
{
1013 public String north
;
1014 public String south
;
1018 public String degreeSymbol
;
1019 public String minuteSymbol
;
1020 public String secondSymbol
;
1022 public boolean caseInsensitive
;
1023 public boolean allowWhiteSpace
;
1024 public boolean priorityOverDefaultPatterns
;
1025 public boolean disableDefaultPatterns
;
1030 private class CustomPattern
{
1032 public List
<CustomHemisphereIndicator
> hemisphereIndicators
;
1034 public String degreeSymbol
;
1035 public String minuteSymbol
;
1036 public String secondSymbol
;
1038 public boolean caseInsensitive
;
1042 //global variable to be used if a custom pattern is used
1043 private CustomPattern customPtrn
;
1045 //escape some of the chars
1046 private String
escapeChars(String str
){
1047 // backslash - first so it is not messed when other escape chars are corrected for being used in a string
1048 str
= str
.replace("\\", "\\\\");
1051 str
= str
.replace(".", "\\.");
1052 str
= str
.replace(",", "\\,");
1055 str
= str
.replace("(", "\\(");
1056 str
= str
.replace(")", "\\)");
1057 str
= str
.replace("[", "\\[");
1058 str
= str
.replace("]", "\\]");
1059 str
= str
.replace("{", "\\{");
1060 str
= str
.replace("}", "\\}");
1062 //other replacements
1063 str
= str
.replace("^", "\\^");
1064 str
= str
.replace("$", "\\$");
1065 str
= str
.replace("+", "\\+");
1066 str
= str
.replace("*", "\\*");
1067 str
= str
.replace("?", "\\?");
1068 str
= str
.replace("|", "\\|");
1074 //this implements sorting by using system.Icomparable - sorting is needed later when replacing
1075 private class CustomHemisphereIndicator
implements Comparable
<CustomHemisphereIndicator
> {
1077 private int m_length
;
1078 private String m_name
;
1079 private String m_indicator
;
1080 private boolean m_positive
;
1083 public CustomHemisphereIndicator(String name
, String indicator
, int length
, boolean positive
){
1085 this.m_indicator
= indicator
;
1086 this.m_length
= length
;
1087 this.m_positive
= positive
;
1092 public String
getName(){
1095 public void setName(String value
){
1096 this.m_name
= value
;
1099 public String
getIndicator(){
1100 return this.m_indicator
;
1102 public void setIndicator(String value
){
1103 this.m_indicator
= value
;
1106 public int getLength(){
1107 return this.m_length
;
1109 public void setLength(int value
){
1110 this.m_length
= value
;
1114 public boolean getPositive(){
1115 return this.m_positive
;
1117 public void setPositive(boolean value
){
1118 this.m_positive
= value
;
1121 /* Less than zero if this instance is less than obj.
1122 * Zero if this instance is equal to obj.
1123 * Greater than zero if this instance is greater than obj.
1125 * This method uses the predefined method Int32.CompareTo
1129 public int compareTo(CustomHemisphereIndicator ind
){
1131 //no need to rewrite the code again, we have Integer.compareTo ready to use
1132 return Integer
.valueOf(this.getLength()).compareTo(Integer
.valueOf(ind
.getLength()));
1138 //This adds custom pattern to a list of already predefined patterns
1139 //useful for batch conversions - allows for totally mixed input data (predefined & custom)
1140 public void addCustomPattern(CustomPatternIn patternIn
){
1142 //new custom pattern object - to pass the needed data farther
1143 CustomPattern pattern
= new CustomPattern();
1145 //keep indicators for parsing
1146 List
<CustomHemisphereIndicator
> indicators
= new ArrayList
<CustomHemisphereIndicator
>();
1149 CustomHemisphereIndicator ind
= new CustomHemisphereIndicator("North", patternIn
.north
, patternIn
.north
.length() ,true);
1150 indicators
.add(ind
);
1153 ind
= new CustomHemisphereIndicator("South", patternIn
.south
, patternIn
.south
.length(), false);
1154 indicators
.add(ind
);
1157 ind
= new CustomHemisphereIndicator("East", patternIn
.east
, patternIn
.east
.length(), true);
1158 indicators
.add(ind
);
1161 ind
= new CustomHemisphereIndicator("West", patternIn
.west
, patternIn
.west
.length(), false);
1162 indicators
.add(ind
);
1164 //sort the arraylist
1165 Collections
.sort(indicators
, lengthComparator
);
1168 //add it to the pattern object
1169 pattern
.hemisphereIndicators
= indicators
;
1172 pattern
.caseInsensitive
= patternIn
.caseInsensitive
;
1174 //keep symbols for parsing
1175 pattern
.degreeSymbol
= patternIn
.degreeSymbol
;
1176 pattern
.minuteSymbol
= patternIn
.minuteSymbol
;
1177 pattern
.secondSymbol
= patternIn
.secondSymbol
;
1181 customPtrn
= pattern
;
1184 //----------------build custom patterns----------------
1186 //prepare hemisphere indicators
1187 String north
= escapeChars(patternIn
.north
);
1188 String south
= escapeChars(patternIn
.south
);
1189 String east
= escapeChars(patternIn
.east
);
1190 String west
= escapeChars(patternIn
.west
);
1193 String degreesymbol
= "";
1194 if (patternIn
.degreeSymbol
!= ""){
1195 degreesymbol
= "(" + escapeChars(patternIn
.degreeSymbol
) + ")?";
1198 String minutesymbol
= "";
1199 if (patternIn
.minuteSymbol
!= ""){
1200 minutesymbol
= "(" + escapeChars(patternIn
.minuteSymbol
) + ")?";
1203 String secondsymbol
= "";
1204 if (escapeChars(patternIn
.secondSymbol
) != ""){
1205 secondsymbol
= "(" + escapeChars(patternIn
.secondSymbol
) + ")?";
1209 //is the pattern to be case insensitive?
1210 String CaseInsensitive
= "";
1211 if (patternIn
.caseInsensitive
){
1212 CaseInsensitive
= "(?i)";
1216 String WhiteSpace
= "";
1217 if (patternIn
.allowWhiteSpace
== true){
1218 WhiteSpace
= "(\\s)*";
1221 //hemisphere indicator
1222 String HemisphereIndicator
= "";
1224 //add north if present
1226 HemisphereIndicator
+= south
;
1228 HemisphereIndicator
+= north
;
1230 HemisphereIndicator
+= "|" + south
;
1235 if (north
== "" & south
== ""){
1236 HemisphereIndicator
+= east
;
1239 HemisphereIndicator
+= "|" + east
;
1244 if (north
== "" & south
== "" & east
== ""){
1245 HemisphereIndicator
+= west
;
1248 HemisphereIndicator
+= "|" + west
;
1252 //add remaining bits if not empty
1253 if (HemisphereIndicator
!= "") {
1254 HemisphereIndicator
= "(" + HemisphereIndicator
+ ")?";
1257 List
<CoordinatePattern
> customPatterns
= new ArrayList
<CoordinatePattern
>();
1259 //create custom patterns based on the specified user's input
1260 CoordinatePattern ptrn
;
1262 //Custom variation of DD.DDD
1263 ptrn
= new CoordinatePattern();
1264 ptrn
.description
= "Custom variation of DD.DDD";
1266 CaseInsensitive
+ "(^" +
1267 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1269 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)" +
1271 "|(^" + WhiteSpace
+
1273 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")" +
1275 HemisphereIndicator
+ WhiteSpace
+ "$" +
1278 customPatterns
.add(ptrn
);
1280 //Custom variation of DD:MM.MMM
1281 ptrn
= new CoordinatePattern();
1282 ptrn
.description
= "Custom variation of DD:MM.MMM";
1284 CaseInsensitive
+ "(^" +
1285 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1287 "(\\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
+ "$)" +
1289 "|(^" + WhiteSpace
+
1291 "(\\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
+ ")" +
1293 HemisphereIndicator
+ WhiteSpace
+ "$" +
1296 customPatterns
.add(ptrn
);
1298 //Custom variation of DD:MM:SS.SSS
1299 ptrn
= new CoordinatePattern();
1300 ptrn
.description
= "Custom variation of DD:MM:SS.SSS";
1302 CaseInsensitive
+ "(^" +
1303 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1305 "(\\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
+ "$)" +
1307 "|(^" + WhiteSpace
+
1309 "(\\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
+ ")" +
1311 HemisphereIndicator
+ WhiteSpace
+ "$" +
1314 customPatterns
.add(ptrn
);
1316 //check if the default patterns are to be used
1317 if (patternIn
.disableDefaultPatterns
) {
1318 patterns
= customPatterns
;
1319 } else { //if all patterns are to be used check which set has the matching priority
1321 //check if the custom patterns are to have priority over the default ones
1322 if (patternIn
.priorityOverDefaultPatterns
){
1324 //add default patterns to the custom patterns
1325 for (int i
= 0; i
< patterns
.size(); i
++){
1326 customPatterns
.add(patterns
.get(i
));
1330 patterns
= customPatterns
;
1333 //add custom patterns to the default patterns
1334 for (int i
= 0; i
< customPatterns
.size(); i
++){
1335 patterns
.add(customPatterns
.get(i
));