3 * Copyright (C) 2009 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
10 * This file is an Java adaption from the orginal CoordinateConverter written by Dominik Mikiewicz
11 * @see www.cartomatic.pl
12 * @see http://dev.e-taxonomy.eu/svn/trunk/geo/coordinateConverter/CoordinateConverter.cs
13 * @see http://gis.miiz.waw.pl/webapps/coordinateconverter/
15 package eu
.etaxonomy
.cdm
.strategy
.parser
.location
;
17 import java
.util
.ArrayList
;
18 import java
.util
.Collections
;
19 import java
.util
.Comparator
;
20 import java
.util
.List
;
21 import java
.util
.regex
.Pattern
;
23 import org
.apache
.log4j
.Logger
;
30 public class CoordinateConverter
{
31 @SuppressWarnings("unused")
32 private static final Logger logger
= Logger
.getLogger(CoordinateConverter
.class);
35 private List
<CoordinatePattern
> patterns
;
38 private class CoordinatePattern
{
44 private Comparator
<CustomHemisphereIndicator
> lengthComparator
= new Comparator
<CustomHemisphereIndicator
>(){
45 public int compare(CustomHemisphereIndicator ind1
, CustomHemisphereIndicator ind2
) {
46 return Integer
.valueOf(ind1
.getLength()).compareTo(ind2
.getLength());
51 public CoordinateConverter() {
52 //initialise pattern array
53 patterns
= new ArrayList
<CoordinatePattern
>();
55 //temp pattern variable
56 CoordinatePattern pattern
;
59 //variations of DD.DDD with white space characters
60 pattern
= new CoordinatePattern();
61 pattern
.description
= "Variation of DD.DDD";
63 //+/-/Nn/Ss/Ww/EeDD.DDDD
65 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
66 "((\\d{1,3}(\\.|\\,)?(\\s)*$)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*$))" +
68 ////DD.DDDDNn/Ss/Ww/Ee
70 "(\\s)*((\\d{1,3}(\\.|\\,)?(\\s)*)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*))" +
71 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
73 patterns
.add(pattern
);
76 //Variations of DD(\u00B0|d)MM.MMM' with whitespace characters
77 pattern
= new CoordinatePattern();
78 pattern
.description
= "Variation of DD(\u00B0|d)MM.MMM('|m)";
81 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
82 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*$)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)?(\u02B9|'|M|m)?$)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*(\u02B9|'|M|m)?(\\s)*$))" +
85 "(\\s)*((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)?(\u02B9|'|M|m)?)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*(\u02B9|'|M|m)?(\\s)*))" +
86 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
88 patterns
.add(pattern
);
91 //Variations of DD\u00B0MM'SS.SSS" with whitespace characters
92 pattern
= new CoordinatePattern();
93 pattern
.description
= "Variation of DD(\u00B0|d)MM(\u02B9|m)SS.SSS(\u02BA|s)";
95 //+/-/Nn/Ss/Ww/EeDD\u00B0MM\u02B9SS.SSS
97 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
98 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*$)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*(\u02B9|'|M|m)?(\\s)*$)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*(\u02B9|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*(\u02BA|\"|''|S|s)?(\\s)*$)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*(\u02B9|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*(\u02BA|\"|''|S|s)?(\\s)*$))" +
100 //DD°MM\u02B9SS.SSSNn/Ss/Ww/Ee
102 "(\\s)*((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*(\u02B9|'|M|m)?(\\s)*)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*(\u02B9|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*(\u02BA|\"|''|S|s)?(\\s)*)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*(\u02B9|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*(\u02BA|\"|''|S|s)?(\\s)*))" +
103 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
105 patterns
.add(pattern
);
108 //Variations of DD:MM:SS.SSS with whitespace characters
109 pattern
= new CoordinatePattern();
110 pattern
.description
= "Variation of DD:MM:SS.SSS";
112 // +/-/Nn/Ss/Ww/EeDD:MM:SS.SSS
114 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
115 "((\\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)*$))" +
117 //DD:MM:SS.SSSNn/Ss/Ww/Ee
119 "(\\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)*))" +
120 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
122 patterns
.add(pattern
);
127 //tests if a string matches one of the defined patterns
128 private int matchPattern(String str
){
131 //match the string against each available patern
132 for (int i
= 0; i
< patterns
.size(); i
++){
134 CoordinatePattern pattern
= patterns
.get(i
);
135 Pattern regEx
= Pattern
.compile(pattern
.pattern
);
136 if (regEx
.matcher(str
).find()) {
146 //gets sign of the coordinate (tests for presence of negative sign)
147 private int getSign(String str
){
149 //This regex checks for the negative hemisphere indicator
150 Pattern regexNegative
= Pattern
.compile("(-|S|s|W|w)");
152 //This regex checks if there weren't any other hemisphere indicators
153 //it is needed for the specific case of the DDdMMmSSs S
154 //so it needs to be ensured there where no positive indicators
155 Pattern regexPositive
= Pattern
.compile("(\\+|N|n|E|e)");
157 //if a positive indicator is found no need to search further
158 if (regexPositive
.matcher(str
).find()){
161 //if not check whether there was a negative indicator. if so negate otherwise return positive
162 if (regexNegative
.matcher(str
).find()){
171 //this checks for the coordinate sign by evaluating user supplied data
172 private int getCustomSign(String str
){
174 //Indicators are evaluated from the longest ones to the shortes ones
175 //So when searching for "P" does not affect "PN" as "PN" is evaluated earlier
178 //search for the presence of indicators
179 boolean hasPositive
= false;
180 boolean hasNegative
= false;
182 //keep previous negative indicators here
183 List
<String
> previousNegatives
= new ArrayList
<String
>();
185 //compare the string with user supplied custom pattern
186 for (int x
= customPtrn
.hemisphereIndicators
.size() - 1; x
>= 0; x
--){
188 CustomHemisphereIndicator ind
= customPtrn
.hemisphereIndicators
.get(x
);
190 //test here if the indicator exists (has length >0)
191 if (ind
.getLength() > 0){
193 //check if the supplied pattern was marked as case insensitive?
194 String caseInsensitive
= "";
196 if (customPtrn
.caseInsensitive
){
197 caseInsensitive
= "(?i)";
201 Pattern tempRegex
= Pattern
.compile(caseInsensitive
+ ind
.getIndicator());
203 //if a pattern is found
204 if (tempRegex
.matcher(str
).find()){
205 //check whether it's a positive or negative indicator
206 if (ind
.getPositive()){
208 * See the note below to understand why checking for previous negatives is performed here
211 //check the previous negatives
212 if (previousNegatives
.size() != 0){
213 boolean sameNegative
= false;
215 for (int i
= previousNegatives
.size() - 1; i
>= 0; i
--){
216 if (ind
.getIndicator() == previousNegatives
.get(i
)){
222 //mark as positive only if the previously found negative is the same
227 }else{ //if no negatives before it already marks the sign as positive
233 * save the negative indicator here so it can be compared later if a positive wants to overwrite it!
234 * in a case a longer negative "Pn" has already been found a shorter positive "P" will not overwrite it
235 * and the hasPositive will remain false;
236 * In a case a "P" negative indicator has already been found, positive will mark hasPositive and therefore
237 * later a default positive value will be returned (if the indicators for positive & negative are the same
238 * positive is returned)
239 * testing for previous positives is not required since if a hasPositive is already true method will return
243 previousNegatives
.add(ind
.getIndicator());
254 //positive indicator has priority here - if both indicators supplied by the user are the same, a positive is chosen
255 //if there were no indicator found in the tested coordinate, a positive value is returned by default
270 //returns a currently used decimal separator
271 private String
getDecimalSeparator(){
272 //TODO not yet transformed from C#
273 // return System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
278 //replaces comma or dot for current decimal separator
279 private String
fixDecimalSeparator(String str
){
281 //Coma is replaced as parsers often recognise dot as a decimal separator
282 //Comma or dot is replaced with a decimal separator here (environment settings)
283 //But decimal separator has to be used later too;
285 String regExReplaceComma
= "(\\,|\\.)";
286 str
= str
.replaceAll(regExReplaceComma
, getDecimalSeparator());
293 private String
removeSign(String str
){
294 String regExRemoveSign
= "(\\+|-|S|s|W|w|N|n|E|e)";
295 str
= str
.replaceAll(regExRemoveSign
, "");
299 //removes custom sign indicators
300 private String
removeCustomPatternParts(String str
){
303 * Symbols are added here so the removing tries to not affect the coordinate too much
304 * Strings to be removed then are evaluated from the longest ones to the shortes ones
305 * So when searching for "P" does not affect "PN" as "PN" is evaluated earlier
308 //CustomHemisphereIndicator is used here so another object does not have to be created
309 //only for the string cleanning
310 List
<CustomHemisphereIndicator
> stringsToRemove
= customPtrn
.hemisphereIndicators
;
313 CustomHemisphereIndicator stringToRemove
= new CustomHemisphereIndicator("Degree", customPtrn
.degreeSymbol
,customPtrn
.degreeSymbol
.length(), false);
314 stringsToRemove
.add(stringToRemove
);
317 stringToRemove
= new CustomHemisphereIndicator("Minute", customPtrn
.minuteSymbol
, customPtrn
.minuteSymbol
.length(), false);
318 stringsToRemove
.add(stringToRemove
);
321 stringToRemove
= new CustomHemisphereIndicator("Second", customPtrn
.secondSymbol
, customPtrn
.secondSymbol
.length(), false);
322 stringsToRemove
.add(stringToRemove
);
324 //sort the list (by element's Length property)
325 Collections
.sort(stringsToRemove
, lengthComparator
);
328 // ListSelectionEv.sort(lengthComparator);
331 for (int x
= stringsToRemove
.size() - 1; x
>= 0; x
--){
333 CustomHemisphereIndicator toBeRemoved
= stringsToRemove
.get(x
);
335 //check if the string exists so replacing does not yield errors
336 if (toBeRemoved
.getLength() > 0)
338 //check if the supplied pattern was marked as case insensitive?
339 String CaseInsensitive
= "";
341 if (customPtrn
.caseInsensitive
){
342 CaseInsensitive
= "(?i)";
345 //create regex for replacing
346 String tempRegex
= CaseInsensitive
+ toBeRemoved
.getIndicator();
349 if (toBeRemoved
.getName().equals("Degree") || toBeRemoved
.getName().equals("Minute")) {
350 //replace with a symbol used later for splitting
351 str
= str
.replaceAll(tempRegex
, ":");
354 str
= str
.replaceAll(tempRegex
, "");
363 //removes whitespace characters
364 private String
removeWhiteSpace(String str
){
365 str
= str
.replaceFirst("\\s*", "");
370 //Object for the conversion results
371 public class ConversionResults
{
372 public boolean patternRecognised
;
373 public String patternMatched
;
374 public String patternType
;
376 public boolean conversionSuccessful
;
377 public double convertedCoord
;
378 public boolean canBeLat
;
380 public String conversionComments
;
382 public Boolean isLongitude
;
393 public ConversionResults
tryConvert(String str
){
394 //some local variables
395 int sign
; //sign of the coordinate
396 String
[] decimalBit
, ddmmss
, ddmm
; //arrays for splitting
397 double dd
= 0, mm
= 0, ss
= 0, mmm
= 0, sss
= 0, dec
= 0; //parts of the coordinates
399 String decSeparatorRaw
= String
.valueOf(getDecimalSeparator()); //gets the current decimal separator
400 String decSeparatorRegEx
= decSeparatorRaw
.replace(".", "\\.");
402 ConversionResults results
= new ConversionResults();
404 //Get the matched pattern
405 CoordinatePattern pattern
;
406 int ptrnnum
= matchPattern(str
);
408 pattern
= patterns
.get(ptrnnum
);
410 pattern
= new CoordinatePattern();
411 pattern
.description
= "Unknown";
412 pattern
.pattern
= "No pattern matched";
417 if (pattern
.description
.equals("Variation of DD.DDD")){
419 //Sets pattern machted, successful, pattern type and pattern info
420 initializeResult(results
, pattern
);
424 results
.isLongitude
= getIsLongitude(str
);
426 //Replace comma or dot with a current decimal separator
427 str
= fixDecimalSeparator(str
);
429 //Remove all the unwanted stuff
430 str
= removeSign(str
);
431 str
= removeWhiteSpace(str
);
433 //Since this is already a decimal degree no spliting is needed
434 dd
= Double
.valueOf(str
);
436 checkDegreeRange(dd
, results
);
437 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
439 }else if (pattern
.description
.equals("Variation of DD(\u00B0|d)MM.MMM('|m)")){
441 //Sets pattern machted, successful, pattern type and pattern info
442 initializeResult(results
, pattern
);
446 results
.isLongitude
= getIsLongitude(str
);
448 //Replace comma or dot with a current decimal separator
449 str
= fixDecimalSeparator(str
);
451 //Remove all the unwanted stuff
452 str
= removeSign(str
);
453 str
= removeWhiteSpace(str
);
455 //do some further replacing
456 //Replace degree symbol
457 str
= str
.replaceAll("(\u00B0|\u00BA|D|d)", ":");
459 //remove minute symbol
460 str
= str
.replaceAll("(\u02B9|'|M|m)", "");
462 //Extract decimal part
463 decimalBit
= str
.split(decSeparatorRegEx
);
465 //split degrees and minutes
466 ddmm
= decimalBit
[0].split(":");
469 //extract values from the strings
470 dd
= Integer
.valueOf(ddmm
[0]); //Degrees
472 if (ddmm
.length
> 1){ //Minutes
473 //check if the string is not empty
475 mm
= Integer
.valueOf(ddmm
[1]);
479 if (decimalBit
.length
> 1){//DecimalSeconds
480 //check if the string is not empty
481 if (decimalBit
[1] != "") {
482 mmm
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
486 checkDegreeRange(dd
, results
);
487 checkMinuteRange(mm
, results
);
488 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
490 }else if (pattern
.description
.equals("Variation of DD(\u00B0|d)MM(\u02B9|m)SS.SSS(\u02BA|s)")){
494 * This pattern allows the seconds to be specified with S, s or " or nothing at all
495 * If the seconds are marked with "s" and there is no other indication of the hemisphere
496 * the coordinate will be parsed as southern (negative).
498 * If the N / E / W / + indicator is found the coordinate will be parsed appropriately no matter
499 * what is the second notation
502 //Sets pattern machted, successful, pattern type and pattern info
503 initializeResult(results
, pattern
);
508 results
.isLongitude
= getIsLongitude(str
);
510 //Replace comma or dot with a current decimal separator
511 str
= fixDecimalSeparator(str
);
513 //Remove all the unwanted stuff
514 str
= removeSign(str
);
515 str
= removeWhiteSpace(str
);
517 //remove second symbol (s is removed by the get sign method)
518 //double apostrophe is not removed here as single apostrphe may mark minutes!
519 //it's taken care of later after extracting the decimal part
520 str
= str
.replaceAll("(u\02BA|\")", "");
522 //do some further replacing
523 //Replace degree symbol
524 str
= str
.replaceAll("(\u00B0|\u00B0|D|d|\u02B9|'|M|m)",":");
526 //Extract decimal part
527 decimalBit
= str
.split(decSeparatorRegEx
);
529 //remove : from the decimal part [1]! This is needed when a double apostrophe was used to mark seconds
530 if (decimalBit
.length
> 1)
532 decimalBit
[1].replace(":", "");
535 //split degrees and minutes
536 ddmmss
= decimalBit
[0].split(":");
539 //extract values from the strings
540 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
541 if (ddmmss
.length
> 1){//Minutes
542 //check if the string is not empty
543 if (ddmmss
[1] != "") {
544 mm
= Integer
.valueOf(ddmmss
[1]);
547 if (ddmmss
.length
> 2){//Seconds
548 //check if the string is not empty
549 if (ddmmss
[2] != "") {
550 ss
= Integer
.valueOf(ddmmss
[2]);
553 if (decimalBit
.length
> 1) { //DecimalSeconds
554 //check if the string is not empty
555 if (decimalBit
[1] != "") {
556 sss
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
560 checkDegreeRange(dd
, results
);
561 checkMinuteRange(mm
, results
);
562 checkSecondRange(ss
, results
);
564 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
566 }else if (pattern
.description
.equals("Variation of DD:MM:SS.SSS")){
568 //Sets pattern machted, successful, pattern type and pattern info
569 initializeResult(results
, pattern
);
573 results
.isLongitude
= getIsLongitude(str
);
575 //Replace comma or dot with a current decimal separator
576 str
= fixDecimalSeparator(str
);
578 //Remove all the unwanted stuff
579 str
= removeSign(str
);
580 str
= removeWhiteSpace(str
);
583 decimalBit
= str
.split(decSeparatorRegEx
);
584 ddmmss
= decimalBit
[0].split(":");
587 //extract values from the strings
588 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
589 if (ddmmss
.length
> 1)//Minutes
591 //check if the string is not empty
592 if (ddmmss
[1] != "") { mm
= Integer
.valueOf(ddmmss
[1]); }
594 if (ddmmss
.length
> 2) {//Seconds{
595 //check if the string is not empty
596 if (ddmmss
[2] != "") {
597 ss
= Integer
.valueOf(ddmmss
[2]);
600 if (decimalBit
.length
> 1) { //DecimalSeconds
601 //check if the string is not empty
602 if (decimalBit
[1] != "") {
603 sss
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
607 checkDegreeRange(dd
, results
);
608 checkMinuteRange(mm
, results
);
609 checkSecondRange(ss
, results
);
611 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
613 }else if (pattern
.description
.equals("Custom variation of DD.DDD")){
615 //Sets pattern machted, successful, pattern type and pattern info
616 initializeResult(results
, pattern
);
620 sign
= getCustomSign(str
);
622 //TODO still needs to be adapted to custom pattern
623 results
.isLongitude
= getIsLongitude(str
);
626 //Remove all the unwanted stuff
627 //Note: This method also replaces the symbols with ":"
628 //Note: In certain cases it may make the coord unparsable
629 str
= removeCustomPatternParts(str
);
631 str
= removeWhiteSpace(str
);
633 //Replace comma or dot with a current decimal separator
634 str
= fixDecimalSeparator(str
);
636 //remove the ":" here as it is not needed here for decimal degrees
637 str
= str
.replace(":", "");
640 //Since this is already a decimal degree no spliting is needed
641 dd
= Double
.valueOf(str
);
642 } catch (Exception e
) {
643 results
.conversionSuccessful
= false;
644 results
.convertedCoord
= 99999; //this is to mark an error...
645 results
.conversionComments
=
646 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
647 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
648 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
655 //Since this is already a decimal degree no spliting is needed
656 dd
= Double
.valueOf(str
);
658 checkDegreeRange(dd
, results
);
659 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
662 }else if (pattern
.description
.equals("Custom variation of DD:MM.MMM")){
663 //-------------Customs patterns start here-------------
665 //Sets pattern machted, successful, pattern type and pattern info
666 initializeResult(results
, pattern
);
669 sign
= getCustomSign(str
);
671 //TODO still needs to be adapted to custom pattern
672 results
.isLongitude
= getIsLongitude(str
);
676 //Remove all the unwanted stuff
677 //Note: This method also replaces the symbols with ":"
678 //Note: In certain cases it may make the coord unparsable
679 str
= removeCustomPatternParts(str
);
681 str
= removeWhiteSpace(str
);
683 //Replace comma or dot with a current decimal separator
684 str
= fixDecimalSeparator(str
);
687 //Extract decimal part
688 decimalBit
= str
.split(decSeparatorRegEx
);
690 //split degrees and minutes
691 ddmm
= decimalBit
[0].split(":");
695 //extract values from the strings
696 dd
= Integer
.valueOf(ddmm
[0]); //Degrees
698 if (ddmm
.length
> 1){//Minutes
699 //check if the string is not empty
700 if (ddmm
[1] != "") { mm
= Integer
.valueOf(ddmm
[1]); }
703 if (decimalBit
.length
> 1){//DecimalSeconds
704 //check if the string is not empty
705 if (decimalBit
[1] != ""){
706 //replace the ":" if any (may be here as a result of custom symbol replacement
707 decimalBit
[1] = decimalBit
[1].replace(":", "");
709 mmm
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
712 } catch (Exception e
){
713 results
.conversionSuccessful
= false;
714 results
.convertedCoord
= 99999; //this is to mark an error...
715 results
.conversionComments
=
716 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
717 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
718 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
726 checkDegreeRange(dd
, results
);
727 checkMinuteRange(mm
, results
);
728 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
730 } else if (pattern
.description
.equals("Custom variation of DD:MM:SS.SSS")){
732 //Sets pattern machted, successful, pattern type and pattern info
733 initializeResult(results
, pattern
);
737 sign
= getCustomSign(str
);
739 //TODO still needs to be adapted to custom pattern
740 results
.isLongitude
= getIsLongitude(str
);
743 //Remove all the unwanted stuff
744 //Note: This method also replaces the symbols with ":"
745 //Note: In certain cases it may make the coord unparsable
746 str
= removeCustomPatternParts(str
);
748 str
= removeWhiteSpace(str
);
750 //Replace comma or dot with a current decimal separator
751 str
= fixDecimalSeparator(str
);
754 //Extract decimal part
755 decimalBit
= str
.split(decSeparatorRegEx
);
757 //split degrees and minutes
758 ddmmss
= decimalBit
[0].split(":");
763 //extract values from the strings
764 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
765 if (ddmmss
.length
> 1) {//Minutes
766 //check if the string is not empty
767 if (ddmmss
[1] != "") {
768 mm
= Integer
.valueOf(ddmmss
[1]);
771 if (ddmmss
.length
> 2){ //Seconds
772 //check if the string is not empty
773 if (ddmmss
[2] != "") {
774 ss
= Integer
.valueOf(ddmmss
[2]);
777 if (decimalBit
.length
> 1){ //DecimalSeconds
778 //check if the string is not empty
779 if (decimalBit
[1] != "") {
780 sss
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
783 } catch (Exception e
) {
784 results
.conversionSuccessful
= false;
785 results
.convertedCoord
= 99999; //this is to mark an error...
786 results
.conversionComments
=
787 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
788 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
789 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
797 checkDegreeRange(dd
, results
);
798 checkMinuteRange(mm
, results
);
799 checkSecondRange(ss
, results
);
801 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
803 }else { //default : pattern not recognized
804 results
.patternRecognised
= false;
805 results
.patternType
= pattern
.description
;
806 results
.patternMatched
= pattern
.pattern
;
808 results
.conversionSuccessful
= false;
809 results
.convertedCoord
= 99999; //this is to mark an error...
811 results
.conversionComments
= "Coordinate pattern not recognised!";
815 //do the self check here
816 results
= selfTest(results
);
818 //return conversion results
831 private void doConvertWithCheck(int sign
, double dd
, double mm
, double mmm
, double ss
, double sss
, ConversionResults results
) {
833 //Do the conversion if everything ok
834 if (results
.conversionSuccessful
){
835 results
.conversionComments
= "Conversion successful.";
837 dec
= sign
* (dd
+ (mm
+ mmm
) / 60 + (ss
+ sss
) / 3600);
839 //one more check to ensure a coord does not exceed 180
840 if (dec
> 180 | dec
< -180){
841 results
.conversionSuccessful
= false;
842 results
.convertedCoord
= 99999; //this is to mark an error...
843 results
.conversionComments
+= "Coordinate is either > 180 or < -180; ";
845 results
.convertedCoord
= dec
;
847 results
.conversionComments
= "Conversion successful.";
849 //Check whether the coordinate exceeds +/- 90 and mark it in comments
851 if (dec
<= 90 && dec
>= -90 && (results
.isLongitude
== null || results
.isLongitude
== false) ) {
852 results
.canBeLat
= true;
854 results
.isLongitude
= true;
865 private void checkSecondRange(double ss
, ConversionResults results
) {
866 if (ss
> 59) {//seconds
867 results
.conversionSuccessful
= false;
868 results
.convertedCoord
= 99999; //this is to mark an error...
869 results
.conversionComments
+= "Seconds fall outside the range: MM >= 60; ";
878 private void checkMinuteRange(double mm
, ConversionResults results
) {
879 if (mm
> 59) {//minutes
880 results
.conversionSuccessful
= false;
881 results
.convertedCoord
= 99999; //this is to mark an error...
882 results
.conversionComments
+= "Minutes fall outside the range: MM > 59; ";
891 private void checkDegreeRange(double dd
, ConversionResults results
) {
892 //do some additional checking if the coords fall into the range
893 if (dd
< -180 | dd
> 180){ //degree may require another param specifying whether it's lat or lon...
894 results
.conversionSuccessful
= false;
895 results
.convertedCoord
= 99999; //this is to mark an error...
896 results
.conversionComments
+= "Degrees fall outside the range: DD < -180 | DD > 180; ";
905 private Boolean
getIsLongitude(String str
) {
906 //This regex checks for the negative hemisphere indicator
907 Pattern regexLatitudeNonAmbigous
= Pattern
.compile("(N|n)");
908 Pattern regexLatitudeAmbigous
= Pattern
.compile("(S|s)");
910 //This regex checks if there weren't any other hemisphere indicators
911 //it is needed for the specific case of the DDdMMmSSs S
912 //so it needs to be ensured there where no positive indicators
913 Pattern regexLongitude
= Pattern
.compile("(W|w|E|e)");
915 //if a positive indicator is found no need to search further
916 if (regexLongitude
.matcher(str
).find()){
918 }else if (regexLatitudeNonAmbigous
.matcher(str
).find()){
920 }else if (regexLatitudeAmbigous
.matcher(str
).find()){
921 Pattern regexLiteralUnits
= Pattern
.compile("(D|d|M|m)");
923 //if there are no other literal units we assume that S is a
924 //direction and not a second indicator
925 if (! regexLiteralUnits
.matcher(str
).find()){
927 }else if (regexLatitudeAmbigous
.matcher(str
).groupCount() > 1){
939 * Sets pattern machted, successful, pattern type and pattern info
943 private void initializeResult(ConversionResults results
,
944 CoordinatePattern pattern
) {
946 results
.patternRecognised
= true;
948 //Matching pattern succeeded so intialy the parsing is ok
949 results
.conversionSuccessful
= true;
952 results
.patternType
= pattern
.description
;
953 results
.patternMatched
= pattern
.pattern
;
957 private ConversionResults
selfTest(ConversionResults results
){
959 ConversionResults newresults
= results
;
961 if (results
.conversionSuccessful
!= false){
963 if (Math
.signum(results
.convertedCoord
) < 0) {
967 double decimalDegrees
= sign
* results
.convertedCoord
;
970 double decimalMinutes
;
973 double decimalSeconds
;
977 fullDegrees
= (int)Math
.floor(decimalDegrees
);
980 decimalMinutes
= (decimalDegrees
- fullDegrees
) * 60;
981 fullMinutes
= (int)Math
.floor(decimalMinutes
);
983 decimalSeconds
= (decimalMinutes
- fullMinutes
) * 60;
984 fullSeconds
= (int)Math
.floor(decimalSeconds
);
986 //save the test results
987 newresults
.dd
= fullDegrees
;
988 newresults
.mm
= fullMinutes
;
989 newresults
.mmm
= decimalSeconds
;
990 newresults
.ss
= fullSeconds
;
991 newresults
.sss
= decimalSeconds
;
1001 //------------ CUSTOM PATTERN BUILDER--------------
1003 public class CustomPatternIn
{
1004 public String north
;
1005 public String south
;
1009 public String degreeSymbol
;
1010 public String minuteSymbol
;
1011 public String secondSymbol
;
1013 public boolean caseInsensitive
;
1014 public boolean allowWhiteSpace
;
1015 public boolean priorityOverDefaultPatterns
;
1016 public boolean disableDefaultPatterns
;
1021 private class CustomPattern
{
1023 public List
<CustomHemisphereIndicator
> hemisphereIndicators
;
1025 public String degreeSymbol
;
1026 public String minuteSymbol
;
1027 public String secondSymbol
;
1029 public boolean caseInsensitive
;
1033 //global variable to be used if a custom pattern is used
1034 private CustomPattern customPtrn
;
1036 //escape some of the chars
1037 private String
escapeChars(String str
){
1038 // backslash - first so it is not messed when other escape chars are corrected for being used in a string
1039 str
= str
.replace("\\", "\\\\");
1042 str
= str
.replace(".", "\\.");
1043 str
= str
.replace(",", "\\,");
1046 str
= str
.replace("(", "\\(");
1047 str
= str
.replace(")", "\\)");
1048 str
= str
.replace("[", "\\[");
1049 str
= str
.replace("]", "\\]");
1050 str
= str
.replace("{", "\\{");
1051 str
= str
.replace("}", "\\}");
1053 //other replacements
1054 str
= str
.replace("^", "\\^");
1055 str
= str
.replace("$", "\\$");
1056 str
= str
.replace("+", "\\+");
1057 str
= str
.replace("*", "\\*");
1058 str
= str
.replace("?", "\\?");
1059 str
= str
.replace("|", "\\|");
1065 //this implements sorting by using system.Icomparable - sorting is needed later when replacing
1066 private class CustomHemisphereIndicator
implements Comparable
<CustomHemisphereIndicator
> {
1068 private int m_length
;
1069 private String m_name
;
1070 private String m_indicator
;
1071 private boolean m_positive
;
1074 public CustomHemisphereIndicator(String name
, String indicator
, int length
, boolean positive
){
1076 this.m_indicator
= indicator
;
1077 this.m_length
= length
;
1078 this.m_positive
= positive
;
1083 public String
getName(){
1086 public void setName(String value
){
1087 this.m_name
= value
;
1090 public String
getIndicator(){
1091 return this.m_indicator
;
1093 public void setIndicator(String value
){
1094 this.m_indicator
= value
;
1097 public int getLength(){
1098 return this.m_length
;
1100 public void setLength(int value
){
1101 this.m_length
= value
;
1105 public boolean getPositive(){
1106 return this.m_positive
;
1108 public void setPositive(boolean value
){
1109 this.m_positive
= value
;
1112 /* Less than zero if this instance is less than obj.
1113 * Zero if this instance is equal to obj.
1114 * Greater than zero if this instance is greater than obj.
1116 * This method uses the predefined method Int32.CompareTo
1119 public int compareTo(CustomHemisphereIndicator ind
){
1121 //no need to rewrite the code again, we have Integer.compareTo ready to use
1122 return Integer
.valueOf(this.getLength()).compareTo(Integer
.valueOf(ind
.getLength()));
1128 //This adds custom pattern to a list of already predefined patterns
1129 //useful for batch conversions - allows for totally mixed input data (predefined & custom)
1130 public void addCustomPattern(CustomPatternIn patternIn
){
1132 //new custom pattern object - to pass the needed data farther
1133 CustomPattern pattern
= new CustomPattern();
1135 //keep indicators for parsing
1136 List
<CustomHemisphereIndicator
> indicators
= new ArrayList
<CustomHemisphereIndicator
>();
1139 CustomHemisphereIndicator ind
= new CustomHemisphereIndicator("North", patternIn
.north
, patternIn
.north
.length() ,true);
1140 indicators
.add(ind
);
1143 ind
= new CustomHemisphereIndicator("South", patternIn
.south
, patternIn
.south
.length(), false);
1144 indicators
.add(ind
);
1147 ind
= new CustomHemisphereIndicator("East", patternIn
.east
, patternIn
.east
.length(), true);
1148 indicators
.add(ind
);
1151 ind
= new CustomHemisphereIndicator("West", patternIn
.west
, patternIn
.west
.length(), false);
1152 indicators
.add(ind
);
1154 //sort the arraylist
1155 Collections
.sort(indicators
, lengthComparator
);
1158 //add it to the pattern object
1159 pattern
.hemisphereIndicators
= indicators
;
1162 pattern
.caseInsensitive
= patternIn
.caseInsensitive
;
1164 //keep symbols for parsing
1165 pattern
.degreeSymbol
= patternIn
.degreeSymbol
;
1166 pattern
.minuteSymbol
= patternIn
.minuteSymbol
;
1167 pattern
.secondSymbol
= patternIn
.secondSymbol
;
1171 customPtrn
= pattern
;
1174 //----------------build custom patterns----------------
1176 //prepare hemisphere indicators
1177 String north
= escapeChars(patternIn
.north
);
1178 String south
= escapeChars(patternIn
.south
);
1179 String east
= escapeChars(patternIn
.east
);
1180 String west
= escapeChars(patternIn
.west
);
1183 String degreesymbol
= "";
1184 if (patternIn
.degreeSymbol
!= ""){
1185 degreesymbol
= "(" + escapeChars(patternIn
.degreeSymbol
) + ")?";
1188 String minutesymbol
= "";
1189 if (patternIn
.minuteSymbol
!= ""){
1190 minutesymbol
= "(" + escapeChars(patternIn
.minuteSymbol
) + ")?";
1193 String secondsymbol
= "";
1194 if (escapeChars(patternIn
.secondSymbol
) != ""){
1195 secondsymbol
= "(" + escapeChars(patternIn
.secondSymbol
) + ")?";
1199 //is the pattern to be case insensitive?
1200 String CaseInsensitive
= "";
1201 if (patternIn
.caseInsensitive
){
1202 CaseInsensitive
= "(?i)";
1206 String WhiteSpace
= "";
1207 if (patternIn
.allowWhiteSpace
== true){
1208 WhiteSpace
= "(\\s)*";
1211 //hemisphere indicator
1212 String HemisphereIndicator
= "";
1214 //add north if present
1216 HemisphereIndicator
+= south
;
1218 HemisphereIndicator
+= north
;
1220 HemisphereIndicator
+= "|" + south
;
1225 if (north
== "" & south
== ""){
1226 HemisphereIndicator
+= east
;
1229 HemisphereIndicator
+= "|" + east
;
1234 if (north
== "" & south
== "" & east
== ""){
1235 HemisphereIndicator
+= west
;
1238 HemisphereIndicator
+= "|" + west
;
1242 //add remaining bits if not empty
1243 if (HemisphereIndicator
!= "") {
1244 HemisphereIndicator
= "(" + HemisphereIndicator
+ ")?";
1247 List
<CoordinatePattern
> customPatterns
= new ArrayList
<CoordinatePattern
>();
1249 //create custom patterns based on the specified user's input
1250 CoordinatePattern ptrn
;
1252 //Custom variation of DD.DDD
1253 ptrn
= new CoordinatePattern();
1254 ptrn
.description
= "Custom variation of DD.DDD";
1256 CaseInsensitive
+ "(^" +
1257 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1259 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)" +
1261 "|(^" + WhiteSpace
+
1263 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")" +
1265 HemisphereIndicator
+ WhiteSpace
+ "$" +
1268 customPatterns
.add(ptrn
);
1270 //Custom variation of DD:MM.MMM
1271 ptrn
= new CoordinatePattern();
1272 ptrn
.description
= "Custom variation of DD:MM.MMM";
1274 CaseInsensitive
+ "(^" +
1275 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1277 "(\\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
+ "$)" +
1279 "|(^" + WhiteSpace
+
1281 "(\\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
+ ")" +
1283 HemisphereIndicator
+ WhiteSpace
+ "$" +
1286 customPatterns
.add(ptrn
);
1288 //Custom variation of DD:MM:SS.SSS
1289 ptrn
= new CoordinatePattern();
1290 ptrn
.description
= "Custom variation of DD:MM:SS.SSS";
1292 CaseInsensitive
+ "(^" +
1293 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1295 "(\\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
+ "$)" +
1297 "|(^" + WhiteSpace
+
1299 "(\\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
+ ")" +
1301 HemisphereIndicator
+ WhiteSpace
+ "$" +
1304 customPatterns
.add(ptrn
);
1306 //check if the default patterns are to be used
1307 if (patternIn
.disableDefaultPatterns
) {
1308 patterns
= customPatterns
;
1309 } else { //if all patterns are to be used check which set has the matching priority
1311 //check if the custom patterns are to have priority over the default ones
1312 if (patternIn
.priorityOverDefaultPatterns
){
1314 //add default patterns to the custom patterns
1315 for (int i
= 0; i
< patterns
.size(); i
++){
1316 customPatterns
.add(patterns
.get(i
));
1320 patterns
= customPatterns
;
1323 //add custom patterns to the default patterns
1324 for (int i
= 0; i
< customPatterns
.size(); i
++){
1325 patterns
.add(customPatterns
.get(i
));