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
;
24 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
31 public class CoordinateConverter
{
32 @SuppressWarnings("unused")
33 private static final Logger logger
= Logger
.getLogger(CoordinateConverter
.class);
36 private List
<CoordinatePattern
> patterns
;
38 private static String minuteUtf8
= "\u02B9|\u00B4|\u02CA|\u0301|\u0374|\u2019";
39 private static String secondUtf8
= "\u02BA|\u030B|\u2033|\u00B4\u00B4|\u201D";
42 private class CoordinatePattern
{
48 private Comparator
<CustomHemisphereIndicator
> lengthComparator
= new Comparator
<CustomHemisphereIndicator
>(){
50 public int compare(CustomHemisphereIndicator ind1
, CustomHemisphereIndicator ind2
) {
51 return Integer
.valueOf(ind1
.getLength()).compareTo(ind2
.getLength());
56 public CoordinateConverter() {
57 //initialise pattern array
58 patterns
= new ArrayList
<CoordinatePattern
>();
60 //temp pattern variable
61 CoordinatePattern pattern
;
64 //variations of DD.DDD with white space characters
65 pattern
= new CoordinatePattern();
66 pattern
.description
= "Variation of DD.DDD";
68 //+/-/Nn/Ss/Ww/EeDD.DDDD
70 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
71 "((\\d{1,3}(\\.|\\,)?(\\s)*$)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*$))" +
73 ////DD.DDDDNn/Ss/Ww/Ee
75 "(\\s)*((\\d{1,3}(\\.|\\,)?(\\s)*)|(\\d{1,3}(\\.|\\,)\\d+(\\s)*))" +
76 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
78 patterns
.add(pattern
);
81 //Variations of DD(\u00B0|d)MM.MMM' with whitespace characters
82 pattern
= new CoordinatePattern();
83 pattern
.description
= "Variation of DD(\u00B0|d)MM.MMM('|m)";
86 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
87 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*$)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)?("+ minuteUtf8
+ "|'|M|m)?$)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*("+ minuteUtf8
+ "|'|M|m)?(\\s)*$))" +
90 "(\\s)*((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)?("+ minuteUtf8
+ "|'|M|m)?)|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*("+ minuteUtf8
+ "|'|M|m)?(\\s)*))" +
91 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
93 patterns
.add(pattern
);
96 //Variations of DD\u00B0MM'SS.SSS" with whitespace characters
97 pattern
= new CoordinatePattern();
98 pattern
.description
= "Variation of DD(\u00B0|d)MM("+ minuteUtf8
+ "|m)SS.SSS("+secondUtf8
+"|s)";
100 //+/-/Nn/Ss/Ww/EeDD\u00B0MM"+ minuteUtf8 + "SS.SSS
102 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
103 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*$)" +
104 "|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8
+ "|'|M|m)?(\\s)*$)" +
105 "|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8
+ "|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*("+secondUtf8
+"|\"|''|S|s)?(\\s)*$)" +
106 "|(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8
+ "|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*("+secondUtf8
+"|\"|''|S|s)?(\\s)*$))" +
108 //DD°MM"+ minuteUtf8 + "SS.SSSNn/Ss/Ww/Ee
110 "((\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)?(\\s)*)|" +
111 "(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8
+ "|'|M|m)?(\\s)*)|" +
112 "(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8
+ "|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*("+secondUtf8
+"|\"|''|S|s)?(\\s)*)|" +
113 "(\\d{1,3}(\\s)*(\u00B0|\u00BA|D|d)(\\s)*\\d{1,2}(\\s)*("+ minuteUtf8
+ "|'|M|m)(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*("+secondUtf8
+"|\"|''|S|s)?(\\s)*))" +
114 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
116 patterns
.add(pattern
);
119 //Variations of DD:MM:SS.SSS with whitespace characters
120 pattern
= new CoordinatePattern();
121 pattern
.description
= "Variation of DD:MM:SS.SSS";
123 // +/-/Nn/Ss/Ww/EeDD:MM:SS.SSS
125 "(\\s)*(\\+|-|W|w|E|e|N|n|S|s)?(\\s)*" +
126 "((\\d{1,3}(\\s)*\\:?(\\s)*$)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:?(\\s)*$)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*$)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*$))" +
128 //DD:MM:SS.SSSNn/Ss/Ww/Ee
130 "(\\s)*((\\d{1,3}(\\s)*\\:?(\\s)*)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:?(\\s)*)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:(\\s)*\\d{1,2}(\\.|\\,)?(\\s)*)|(\\d{1,3}(\\s)*\\:(\\s)*\\d{1,2}(\\s)*\\:(\\s)*\\d{1,2}(\\.|\\,)\\d+(\\s)*))" +
131 "(W|w|E|e|N|n|S|s)?(\\s)*$" +
133 patterns
.add(pattern
);
138 //tests if a string matches one of the defined patterns
139 private int matchPattern(String str
){
142 //match the string against each available patern
143 for (int i
= 0; i
< patterns
.size(); i
++){
145 CoordinatePattern pattern
= patterns
.get(i
);
146 Pattern regEx
= Pattern
.compile(pattern
.pattern
);
147 if (regEx
.matcher(str
).find()) {
157 //gets sign of the coordinate (tests for presence of negative sign)
158 private int getSign(String str
){
160 //This regex checks for the negative hemisphere indicator
161 Pattern regexNegative
= Pattern
.compile("(-|S|s|W|w)");
163 //This regex checks if there weren't any other hemisphere indicators
164 //it is needed for the specific case of the DDdMMmSSs S
165 //so it needs to be ensured there where no positive indicators
166 Pattern regexPositive
= Pattern
.compile("(\\+|N|n|E|e)");
168 //if a positive indicator is found no need to search further
169 if (regexPositive
.matcher(str
).find()){
172 //if not check whether there was a negative indicator. if so negate otherwise return positive
173 if (regexNegative
.matcher(str
).find()){
182 //this checks for the coordinate sign by evaluating user supplied data
183 private int getCustomSign(String str
){
185 //Indicators are evaluated from the longest ones to the shortes ones
186 //So when searching for "P" does not affect "PN" as "PN" is evaluated earlier
189 //search for the presence of indicators
190 boolean hasPositive
= false;
191 boolean hasNegative
= false;
193 //keep previous negative indicators here
194 List
<String
> previousNegatives
= new ArrayList
<String
>();
196 //compare the string with user supplied custom pattern
197 for (int x
= customPtrn
.hemisphereIndicators
.size() - 1; x
>= 0; x
--){
199 CustomHemisphereIndicator ind
= customPtrn
.hemisphereIndicators
.get(x
);
201 //test here if the indicator exists (has length >0)
202 if (ind
.getLength() > 0){
204 //check if the supplied pattern was marked as case insensitive?
205 String caseInsensitive
= "";
207 if (customPtrn
.caseInsensitive
){
208 caseInsensitive
= "(?i)";
212 Pattern tempRegex
= Pattern
.compile(caseInsensitive
+ ind
.getIndicator());
214 //if a pattern is found
215 if (tempRegex
.matcher(str
).find()){
216 //check whether it's a positive or negative indicator
217 if (ind
.getPositive()){
219 * See the note below to understand why checking for previous negatives is performed here
222 //check the previous negatives
223 if (previousNegatives
.size() != 0){
224 boolean sameNegative
= false;
226 for (int i
= previousNegatives
.size() - 1; i
>= 0; i
--){
227 if (ind
.getIndicator() == previousNegatives
.get(i
)){
233 //mark as positive only if the previously found negative is the same
238 }else{ //if no negatives before it already marks the sign as positive
244 * save the negative indicator here so it can be compared later if a positive wants to overwrite it!
245 * in a case a longer negative "Pn" has already been found a shorter positive "P" will not overwrite it
246 * and the hasPositive will remain false;
247 * In a case a "P" negative indicator has already been found, positive will mark hasPositive and therefore
248 * later a default positive value will be returned (if the indicators for positive & negative are the same
249 * positive is returned)
250 * testing for previous positives is not required since if a hasPositive is already true method will return
254 previousNegatives
.add(ind
.getIndicator());
265 //positive indicator has priority here - if both indicators supplied by the user are the same, a positive is chosen
266 //if there were no indicator found in the tested coordinate, a positive value is returned by default
281 //returns a currently used decimal separator
282 private String
getDecimalSeparator(){
283 //TODO not yet transformed from C#
284 // return System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
289 //replaces comma or dot for current decimal separator
290 private String
fixDecimalSeparator(String str
){
292 //Coma is replaced as parsers often recognise dot as a decimal separator
293 //Comma or dot is replaced with a decimal separator here (environment settings)
294 //But decimal separator has to be used later too;
296 String regExReplaceComma
= "(\\,|\\.)";
297 str
= str
.replaceAll(regExReplaceComma
, getDecimalSeparator());
304 private String
removeSign(String str
){
305 String regExRemoveSign
= "(\\+|-|S|s|W|w|N|n|E|e)";
306 str
= str
.replaceAll(regExRemoveSign
, "");
310 //removes custom sign indicators
311 private String
removeCustomPatternParts(String str
){
314 * Symbols are added here so the removing tries to not affect the coordinate too much
315 * Strings to be removed then are evaluated from the longest ones to the shortes ones
316 * So when searching for "P" does not affect "PN" as "PN" is evaluated earlier
319 //CustomHemisphereIndicator is used here so another object does not have to be created
320 //only for the string cleanning
321 List
<CustomHemisphereIndicator
> stringsToRemove
= customPtrn
.hemisphereIndicators
;
324 CustomHemisphereIndicator stringToRemove
= new CustomHemisphereIndicator("Degree", customPtrn
.degreeSymbol
,customPtrn
.degreeSymbol
.length(), false);
325 stringsToRemove
.add(stringToRemove
);
328 stringToRemove
= new CustomHemisphereIndicator("Minute", customPtrn
.minuteSymbol
, customPtrn
.minuteSymbol
.length(), false);
329 stringsToRemove
.add(stringToRemove
);
332 stringToRemove
= new CustomHemisphereIndicator("Second", customPtrn
.secondSymbol
, customPtrn
.secondSymbol
.length(), false);
333 stringsToRemove
.add(stringToRemove
);
335 //sort the list (by element's Length property)
336 Collections
.sort(stringsToRemove
, lengthComparator
);
339 // ListSelectionEv.sort(lengthComparator);
342 for (int x
= stringsToRemove
.size() - 1; x
>= 0; x
--){
344 CustomHemisphereIndicator toBeRemoved
= stringsToRemove
.get(x
);
346 //check if the string exists so replacing does not yield errors
347 if (toBeRemoved
.getLength() > 0)
349 //check if the supplied pattern was marked as case insensitive?
350 String CaseInsensitive
= "";
352 if (customPtrn
.caseInsensitive
){
353 CaseInsensitive
= "(?i)";
356 //create regex for replacing
357 String tempRegex
= CaseInsensitive
+ toBeRemoved
.getIndicator();
360 if (toBeRemoved
.getName().equals("Degree") || toBeRemoved
.getName().equals("Minute")) {
361 //replace with a symbol used later for splitting
362 str
= str
.replaceAll(tempRegex
, ":");
365 str
= str
.replaceAll(tempRegex
, "");
374 //removes whitespace characters
375 private String
removeWhiteSpace(String str
){
376 str
= str
.replaceFirst("\\s+", "");
381 //Object for the conversion results
382 public class ConversionResults
{
383 public boolean patternRecognised
;
384 public String patternMatched
;
385 public String patternType
;
387 public boolean conversionSuccessful
;
388 public double convertedCoord
;
389 public boolean canBeLat
;
391 public String conversionComments
;
393 public Boolean isLongitude
;
404 public ConversionResults
tryConvert(String str
){
405 //some local variables
406 int sign
; //sign of the coordinate
407 String
[] decimalBit
, ddmmss
, ddmm
; //arrays for splitting
408 double dd
= 0, mm
= 0, ss
= 0, mmm
= 0, sss
= 0, dec
= 0; //parts of the coordinates
410 String decSeparatorRaw
= String
.valueOf(getDecimalSeparator()); //gets the current decimal separator
411 String decSeparatorRegEx
= decSeparatorRaw
.replace(".", "\\.");
413 ConversionResults results
= new ConversionResults();
415 //Get the matched pattern
416 CoordinatePattern pattern
;
417 int ptrnnum
= matchPattern(str
);
419 pattern
= patterns
.get(ptrnnum
);
421 pattern
= new CoordinatePattern();
422 pattern
.description
= "Unknown";
423 pattern
.pattern
= "No pattern matched";
428 if (pattern
.description
.equals("Variation of DD.DDD")){
430 //Sets pattern machted, successful, pattern type and pattern info
431 initializeResult(results
, pattern
);
435 results
.isLongitude
= getIsLongitude(str
);
437 //Replace comma or dot with a current decimal separator
438 str
= fixDecimalSeparator(str
);
440 //Remove all the unwanted stuff
441 str
= removeSign(str
);
442 str
= removeWhiteSpace(str
);
444 //Since this is already a decimal degree no spliting is needed
445 dd
= Double
.valueOf(str
);
447 checkDegreeRange(dd
, results
);
448 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
450 }else if (pattern
.description
.equals("Variation of DD(\u00B0|d)MM.MMM('|m)")){
452 //Sets pattern machted, successful, pattern type and pattern info
453 initializeResult(results
, pattern
);
457 results
.isLongitude
= getIsLongitude(str
);
459 //Replace comma or dot with a current decimal separator
460 str
= fixDecimalSeparator(str
);
462 //Remove all the unwanted stuff
463 str
= removeSign(str
);
464 str
= removeWhiteSpace(str
);
466 //do some further replacing
467 //Replace degree symbol
468 str
= str
.replaceAll("(\u00B0|\u00BA|D|d)", ":");
470 //remove minute symbol
471 str
= str
.replaceAll("("+ minuteUtf8
+ "|'|M|m)", "");
473 //Extract decimal part
474 decimalBit
= str
.split(decSeparatorRegEx
);
476 //split degrees and minutes
477 ddmm
= decimalBit
[0].split(":");
480 //extract values from the strings
481 dd
= Integer
.valueOf(ddmm
[0]); //Degrees
483 if (ddmm
.length
> 1){ //Minutes
484 //check if the string is not empty
486 mm
= Integer
.valueOf(ddmm
[1]);
490 if (decimalBit
.length
> 1){//DecimalSeconds
491 //check if the string is not empty
492 if (decimalBit
[1] != "") {
493 mmm
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
497 checkDegreeRange(dd
, results
);
498 checkMinuteRange(mm
, results
);
499 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
501 }else if (pattern
.description
.equals("Variation of DD(\u00B0|d)MM("+ minuteUtf8
+ "|m)SS.SSS("+secondUtf8
+"|s)")){
505 * This pattern allows the seconds to be specified with S, s or " or nothing at all
506 * If the seconds are marked with "s" and there is no other indication of the hemisphere
507 * the coordinate will be parsed as southern (negative).
509 * If the N / E / W / + indicator is found the coordinate will be parsed appropriately no matter
510 * what is the second notation
513 //Sets pattern machted, successful, pattern type and pattern info
514 initializeResult(results
, pattern
);
519 results
.isLongitude
= getIsLongitude(str
);
521 //Replace comma or dot with a current decimal separator
522 str
= fixDecimalSeparator(str
);
524 //Remove all the unwanted stuff
525 str
= removeSign(str
);
526 str
= removeWhiteSpace(str
);
528 //remove second symbol (s is removed by the get sign method)
529 //double apostrophe is not removed here as single apostrphe may mark minutes!
530 //it's taken care of later after extracting the decimal part
531 str
= str
.replaceAll("("+secondUtf8
+"|\")", "");
533 //do some further replacing
534 //Replace degree symbol
535 str
= str
.replaceAll("(\u00B0|\u00B0|D|d|"+ minuteUtf8
+ "|'|M|m)",":");
537 //Extract decimal part
538 decimalBit
= str
.split(decSeparatorRegEx
);
540 //remove : from the decimal part [1]! This is needed when a double apostrophe was used to mark seconds
541 if (decimalBit
.length
> 1)
543 decimalBit
[1].replace(":", "");
546 //split degrees and minutes
547 ddmmss
= decimalBit
[0].split(":");
550 //extract values from the strings
551 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
552 if (ddmmss
.length
> 1){//Minutes
553 //check if the string is not empty
554 if (ddmmss
[1] != "") {
555 mm
= Integer
.valueOf(ddmmss
[1]);
558 if (ddmmss
.length
> 2){//Seconds
559 //check if the string is not empty
560 if (ddmmss
[2] != "") {
561 ss
= Integer
.valueOf(Nz(ddmmss
[2]).trim());
564 if (decimalBit
.length
> 1) { //DecimalSeconds
565 //check if the string is not empty
566 if (decimalBit
[1] != "") {
567 sss
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
571 checkDegreeRange(dd
, results
);
572 checkMinuteRange(mm
, results
);
573 checkSecondRange(ss
, results
);
575 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
577 }else if (pattern
.description
.equals("Variation of DD:MM:SS.SSS")){
579 //Sets pattern machted, successful, pattern type and pattern info
580 initializeResult(results
, pattern
);
584 results
.isLongitude
= getIsLongitude(str
);
586 //Replace comma or dot with a current decimal separator
587 str
= fixDecimalSeparator(str
);
589 //Remove all the unwanted stuff
590 str
= removeSign(str
);
591 str
= removeWhiteSpace(str
);
594 decimalBit
= str
.split(decSeparatorRegEx
);
595 ddmmss
= decimalBit
[0].split(":");
598 //extract values from the strings
599 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
600 if (ddmmss
.length
> 1)//Minutes
602 //check if the string is not empty
603 if (ddmmss
[1] != "") { mm
= Integer
.valueOf(ddmmss
[1]); }
605 if (ddmmss
.length
> 2) {//Seconds{
606 //check if the string is not empty
607 if (ddmmss
[2] != "") {
608 ss
= Integer
.valueOf(ddmmss
[2]);
611 if (decimalBit
.length
> 1) { //DecimalSeconds
612 //check if the string is not empty
613 if (decimalBit
[1] != "") {
614 sss
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
618 checkDegreeRange(dd
, results
);
619 checkMinuteRange(mm
, results
);
620 checkSecondRange(ss
, results
);
622 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
624 }else if (pattern
.description
.equals("Custom variation of DD.DDD")){
626 //Sets pattern machted, successful, pattern type and pattern info
627 initializeResult(results
, pattern
);
631 sign
= getCustomSign(str
);
633 //TODO still needs to be adapted to custom pattern
634 results
.isLongitude
= getIsLongitude(str
);
637 //Remove all the unwanted stuff
638 //Note: This method also replaces the symbols with ":"
639 //Note: In certain cases it may make the coord unparsable
640 str
= removeCustomPatternParts(str
);
642 str
= removeWhiteSpace(str
);
644 //Replace comma or dot with a current decimal separator
645 str
= fixDecimalSeparator(str
);
647 //remove the ":" here as it is not needed here for decimal degrees
648 str
= str
.replace(":", "");
651 //Since this is already a decimal degree no spliting is needed
652 dd
= Double
.valueOf(str
);
653 } catch (Exception e
) {
654 results
.conversionSuccessful
= false;
655 results
.convertedCoord
= 99999; //this is to mark an error...
656 results
.conversionComments
=
657 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
658 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
659 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
666 //Since this is already a decimal degree no spliting is needed
667 dd
= Double
.valueOf(str
);
669 checkDegreeRange(dd
, results
);
670 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
673 }else if (pattern
.description
.equals("Custom variation of DD:MM.MMM")){
674 //-------------Customs patterns start here-------------
676 //Sets pattern machted, successful, pattern type and pattern info
677 initializeResult(results
, pattern
);
680 sign
= getCustomSign(str
);
682 //TODO still needs to be adapted to custom pattern
683 results
.isLongitude
= getIsLongitude(str
);
687 //Remove all the unwanted stuff
688 //Note: This method also replaces the symbols with ":"
689 //Note: In certain cases it may make the coord unparsable
690 str
= removeCustomPatternParts(str
);
692 str
= removeWhiteSpace(str
);
694 //Replace comma or dot with a current decimal separator
695 str
= fixDecimalSeparator(str
);
698 //Extract decimal part
699 decimalBit
= str
.split(decSeparatorRegEx
);
701 //split degrees and minutes
702 ddmm
= decimalBit
[0].split(":");
706 //extract values from the strings
707 dd
= Integer
.valueOf(ddmm
[0]); //Degrees
709 if (ddmm
.length
> 1){//Minutes
710 //check if the string is not empty
711 if (ddmm
[1] != "") { mm
= Integer
.valueOf(ddmm
[1]); }
714 if (decimalBit
.length
> 1){//DecimalSeconds
715 //check if the string is not empty
716 if (decimalBit
[1] != ""){
717 //replace the ":" if any (may be here as a result of custom symbol replacement
718 decimalBit
[1] = decimalBit
[1].replace(":", "");
720 mmm
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
723 } catch (Exception e
){
724 results
.conversionSuccessful
= false;
725 results
.convertedCoord
= 99999; //this is to mark an error...
726 results
.conversionComments
=
727 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
728 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
729 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
737 checkDegreeRange(dd
, results
);
738 checkMinuteRange(mm
, results
);
739 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
741 } else if (pattern
.description
.equals("Custom variation of DD:MM:SS.SSS")){
743 //Sets pattern machted, successful, pattern type and pattern info
744 initializeResult(results
, pattern
);
748 sign
= getCustomSign(str
);
750 //TODO still needs to be adapted to custom pattern
751 results
.isLongitude
= getIsLongitude(str
);
754 //Remove all the unwanted stuff
755 //Note: This method also replaces the symbols with ":"
756 //Note: In certain cases it may make the coord unparsable
757 str
= removeCustomPatternParts(str
);
759 str
= removeWhiteSpace(str
);
761 //Replace comma or dot with a current decimal separator
762 str
= fixDecimalSeparator(str
);
765 //Extract decimal part
766 decimalBit
= str
.split(decSeparatorRegEx
);
768 //split degrees and minutes
769 ddmmss
= decimalBit
[0].split(":");
774 //extract values from the strings
775 dd
= Integer
.valueOf(ddmmss
[0]); //Degrees
776 if (ddmmss
.length
> 1) {//Minutes
777 //check if the string is not empty
778 if (ddmmss
[1] != "") {
779 mm
= Integer
.valueOf(ddmmss
[1]);
782 if (ddmmss
.length
> 2){ //Seconds
783 //check if the string is not empty
784 if (ddmmss
[2] != "") {
785 ss
= Integer
.valueOf(ddmmss
[2]);
788 if (decimalBit
.length
> 1){ //DecimalSeconds
789 //check if the string is not empty
790 if (decimalBit
[1] != "") {
791 sss
= Double
.valueOf(decimalBit
[1]) / Math
.pow(10, (decimalBit
[1].length()));
794 } catch (Exception e
) {
795 results
.conversionSuccessful
= false;
796 results
.convertedCoord
= 99999; //this is to mark an error...
797 results
.conversionComments
=
798 "It looks like the supplied pattern has some ambiguous elements and the parser was unable to parse the coordinate." +
799 "<br/>If the supplied symbols used for marking degrees, minutes or seconds contain hemisphere indicators, " +
800 "the parser is likely to fail or yield rubbish results even though the pattern itself has been recognised."
808 checkDegreeRange(dd
, results
);
809 checkMinuteRange(mm
, results
);
810 checkSecondRange(ss
, results
);
812 doConvertWithCheck(sign
, dd
, mm
, mmm
, ss
, sss
, results
);
814 }else { //default : pattern not recognized
815 results
.patternRecognised
= false;
816 results
.patternType
= pattern
.description
;
817 results
.patternMatched
= pattern
.pattern
;
819 results
.conversionSuccessful
= false;
820 results
.convertedCoord
= 99999; //this is to mark an error...
822 results
.conversionComments
= "Coordinate pattern not recognised!";
826 //do the self check here
827 results
= selfTest(results
);
829 //return conversion results
838 private String
Nz(String string
) {
839 return CdmUtils
.Nz(string
);
851 private void doConvertWithCheck(int sign
, double dd
, double mm
, double mmm
, double ss
, double sss
, ConversionResults results
) {
853 //Do the conversion if everything ok
854 if (results
.conversionSuccessful
){
855 results
.conversionComments
= "Conversion successful.";
857 dec
= sign
* (dd
+ (mm
+ mmm
) / 60 + (ss
+ sss
) / 3600);
859 //one more check to ensure a coord does not exceed 180
860 if (dec
> 180 | dec
< -180){
861 results
.conversionSuccessful
= false;
862 results
.convertedCoord
= 99999; //this is to mark an error...
863 results
.conversionComments
+= "Coordinate is either > 180 or < -180; ";
865 results
.convertedCoord
= dec
;
867 results
.conversionComments
= "Conversion successful.";
869 //Check whether the coordinate exceeds +/- 90 and mark it in comments
871 if (dec
<= 90 && dec
>= -90 && (results
.isLongitude
== null || results
.isLongitude
== false) ) {
872 results
.canBeLat
= true;
874 results
.isLongitude
= true;
885 private void checkSecondRange(double ss
, ConversionResults results
) {
886 if (ss
> 59) {//seconds
887 results
.conversionSuccessful
= false;
888 results
.convertedCoord
= 99999; //this is to mark an error...
889 results
.conversionComments
+= "Seconds fall outside the range: MM >= 60; ";
898 private void checkMinuteRange(double mm
, ConversionResults results
) {
899 if (mm
> 59) {//minutes
900 results
.conversionSuccessful
= false;
901 results
.convertedCoord
= 99999; //this is to mark an error...
902 results
.conversionComments
+= "Minutes fall outside the range: MM > 59; ";
911 private void checkDegreeRange(double dd
, ConversionResults results
) {
912 //do some additional checking if the coords fall into the range
913 if (dd
< -180 | dd
> 180){ //degree may require another param specifying whether it's lat or lon...
914 results
.conversionSuccessful
= false;
915 results
.convertedCoord
= 99999; //this is to mark an error...
916 results
.conversionComments
+= "Degrees fall outside the range: DD < -180 | DD > 180; ";
925 private Boolean
getIsLongitude(String str
) {
926 //This regex checks for the negative hemisphere indicator
927 Pattern regexLatitudeNonAmbigous
= Pattern
.compile("(N|n)");
928 Pattern regexLatitudeAmbigous
= Pattern
.compile("(S|s)");
930 //This regex checks if there weren't any other hemisphere indicators
931 //it is needed for the specific case of the DDdMMmSSs S
932 //so it needs to be ensured there where no positive indicators
933 Pattern regexLongitude
= Pattern
.compile("(W|w|E|e)");
935 //if a positive indicator is found no need to search further
936 if (regexLongitude
.matcher(str
).find()){
938 }else if (regexLatitudeNonAmbigous
.matcher(str
).find()){
940 }else if (regexLatitudeAmbigous
.matcher(str
).find()){
941 Pattern regexLiteralUnits
= Pattern
.compile("(D|d|M|m)");
943 //if there are no other literal units we assume that S is a
944 //direction and not a second indicator
945 if (! regexLiteralUnits
.matcher(str
).find()){
947 }else if (regexLatitudeAmbigous
.matcher(str
).groupCount() > 1){
959 * Sets pattern machted, successful, pattern type and pattern info
963 private void initializeResult(ConversionResults results
,
964 CoordinatePattern pattern
) {
966 results
.patternRecognised
= true;
968 //Matching pattern succeeded so intialy the parsing is ok
969 results
.conversionSuccessful
= true;
972 results
.patternType
= pattern
.description
;
973 results
.patternMatched
= pattern
.pattern
;
977 private ConversionResults
selfTest(ConversionResults results
){
979 ConversionResults newresults
= results
;
981 if (results
.conversionSuccessful
!= false){
983 if (Math
.signum(results
.convertedCoord
) < 0) {
987 double decimalDegrees
= sign
* results
.convertedCoord
;
990 double decimalMinutes
;
993 double decimalSeconds
;
997 fullDegrees
= (int)Math
.floor(decimalDegrees
);
1000 decimalMinutes
= (decimalDegrees
- fullDegrees
) * 60;
1001 fullMinutes
= (int)Math
.floor(decimalMinutes
);
1003 decimalSeconds
= (decimalMinutes
- fullMinutes
) * 60;
1004 fullSeconds
= (int)Math
.floor(decimalSeconds
);
1006 //save the test results
1007 newresults
.dd
= fullDegrees
;
1008 newresults
.mm
= fullMinutes
;
1009 newresults
.mmm
= decimalSeconds
;
1010 newresults
.ss
= fullSeconds
;
1011 newresults
.sss
= decimalSeconds
;
1021 //------------ CUSTOM PATTERN BUILDER--------------
1023 public class CustomPatternIn
{
1024 public String north
;
1025 public String south
;
1029 public String degreeSymbol
;
1030 public String minuteSymbol
;
1031 public String secondSymbol
;
1033 public boolean caseInsensitive
;
1034 public boolean allowWhiteSpace
;
1035 public boolean priorityOverDefaultPatterns
;
1036 public boolean disableDefaultPatterns
;
1041 private class CustomPattern
{
1043 public List
<CustomHemisphereIndicator
> hemisphereIndicators
;
1045 public String degreeSymbol
;
1046 public String minuteSymbol
;
1047 public String secondSymbol
;
1049 public boolean caseInsensitive
;
1053 //global variable to be used if a custom pattern is used
1054 private CustomPattern customPtrn
;
1056 //escape some of the chars
1057 private String
escapeChars(String str
){
1058 // backslash - first so it is not messed when other escape chars are corrected for being used in a string
1059 str
= str
.replace("\\", "\\\\");
1062 str
= str
.replace(".", "\\.");
1063 str
= str
.replace(",", "\\,");
1066 str
= str
.replace("(", "\\(");
1067 str
= str
.replace(")", "\\)");
1068 str
= str
.replace("[", "\\[");
1069 str
= str
.replace("]", "\\]");
1070 str
= str
.replace("{", "\\{");
1071 str
= str
.replace("}", "\\}");
1073 //other replacements
1074 str
= str
.replace("^", "\\^");
1075 str
= str
.replace("$", "\\$");
1076 str
= str
.replace("+", "\\+");
1077 str
= str
.replace("*", "\\*");
1078 str
= str
.replace("?", "\\?");
1079 str
= str
.replace("|", "\\|");
1085 //this implements sorting by using system.Icomparable - sorting is needed later when replacing
1086 private class CustomHemisphereIndicator
implements Comparable
<CustomHemisphereIndicator
> {
1088 private int m_length
;
1089 private String m_name
;
1090 private String m_indicator
;
1091 private boolean m_positive
;
1094 public CustomHemisphereIndicator(String name
, String indicator
, int length
, boolean positive
){
1096 this.m_indicator
= indicator
;
1097 this.m_length
= length
;
1098 this.m_positive
= positive
;
1103 public String
getName(){
1106 public void setName(String value
){
1107 this.m_name
= value
;
1110 public String
getIndicator(){
1111 return this.m_indicator
;
1113 public void setIndicator(String value
){
1114 this.m_indicator
= value
;
1117 public int getLength(){
1118 return this.m_length
;
1120 public void setLength(int value
){
1121 this.m_length
= value
;
1125 public boolean getPositive(){
1126 return this.m_positive
;
1128 public void setPositive(boolean value
){
1129 this.m_positive
= value
;
1132 /* Less than zero if this instance is less than obj.
1133 * Zero if this instance is equal to obj.
1134 * Greater than zero if this instance is greater than obj.
1136 * This method uses the predefined method Int32.CompareTo
1140 public int compareTo(CustomHemisphereIndicator ind
){
1142 //no need to rewrite the code again, we have Integer.compareTo ready to use
1143 return Integer
.valueOf(this.getLength()).compareTo(Integer
.valueOf(ind
.getLength()));
1149 //This adds custom pattern to a list of already predefined patterns
1150 //useful for batch conversions - allows for totally mixed input data (predefined & custom)
1151 public void addCustomPattern(CustomPatternIn patternIn
){
1153 //new custom pattern object - to pass the needed data farther
1154 CustomPattern pattern
= new CustomPattern();
1156 //keep indicators for parsing
1157 List
<CustomHemisphereIndicator
> indicators
= new ArrayList
<CustomHemisphereIndicator
>();
1160 CustomHemisphereIndicator ind
= new CustomHemisphereIndicator("North", patternIn
.north
, patternIn
.north
.length() ,true);
1161 indicators
.add(ind
);
1164 ind
= new CustomHemisphereIndicator("South", patternIn
.south
, patternIn
.south
.length(), false);
1165 indicators
.add(ind
);
1168 ind
= new CustomHemisphereIndicator("East", patternIn
.east
, patternIn
.east
.length(), true);
1169 indicators
.add(ind
);
1172 ind
= new CustomHemisphereIndicator("West", patternIn
.west
, patternIn
.west
.length(), false);
1173 indicators
.add(ind
);
1175 //sort the arraylist
1176 Collections
.sort(indicators
, lengthComparator
);
1179 //add it to the pattern object
1180 pattern
.hemisphereIndicators
= indicators
;
1183 pattern
.caseInsensitive
= patternIn
.caseInsensitive
;
1185 //keep symbols for parsing
1186 pattern
.degreeSymbol
= patternIn
.degreeSymbol
;
1187 pattern
.minuteSymbol
= patternIn
.minuteSymbol
;
1188 pattern
.secondSymbol
= patternIn
.secondSymbol
;
1192 customPtrn
= pattern
;
1195 //----------------build custom patterns----------------
1197 //prepare hemisphere indicators
1198 String north
= escapeChars(patternIn
.north
);
1199 String south
= escapeChars(patternIn
.south
);
1200 String east
= escapeChars(patternIn
.east
);
1201 String west
= escapeChars(patternIn
.west
);
1204 String degreesymbol
= "";
1205 if (patternIn
.degreeSymbol
!= ""){
1206 degreesymbol
= "(" + escapeChars(patternIn
.degreeSymbol
) + ")?";
1209 String minutesymbol
= "";
1210 if (patternIn
.minuteSymbol
!= ""){
1211 minutesymbol
= "(" + escapeChars(patternIn
.minuteSymbol
) + ")?";
1214 String secondsymbol
= "";
1215 if (escapeChars(patternIn
.secondSymbol
) != ""){
1216 secondsymbol
= "(" + escapeChars(patternIn
.secondSymbol
) + ")?";
1220 //is the pattern to be case insensitive?
1221 String CaseInsensitive
= "";
1222 if (patternIn
.caseInsensitive
){
1223 CaseInsensitive
= "(?i)";
1227 String WhiteSpace
= "";
1228 if (patternIn
.allowWhiteSpace
== true){
1229 WhiteSpace
= "(\\s)*";
1232 //hemisphere indicator
1233 String HemisphereIndicator
= "";
1235 //add north if present
1237 HemisphereIndicator
+= south
;
1239 HemisphereIndicator
+= north
;
1241 HemisphereIndicator
+= "|" + south
;
1246 if (north
== "" & south
== ""){
1247 HemisphereIndicator
+= east
;
1250 HemisphereIndicator
+= "|" + east
;
1255 if (north
== "" & south
== "" & east
== ""){
1256 HemisphereIndicator
+= west
;
1259 HemisphereIndicator
+= "|" + west
;
1263 //add remaining bits if not empty
1264 if (HemisphereIndicator
!= "") {
1265 HemisphereIndicator
= "(" + HemisphereIndicator
+ ")?";
1268 List
<CoordinatePattern
> customPatterns
= new ArrayList
<CoordinatePattern
>();
1270 //create custom patterns based on the specified user's input
1271 CoordinatePattern ptrn
;
1273 //Custom variation of DD.DDD
1274 ptrn
= new CoordinatePattern();
1275 ptrn
.description
= "Custom variation of DD.DDD";
1277 CaseInsensitive
+ "(^" +
1278 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1280 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)" +
1282 "|(^" + WhiteSpace
+
1284 "(\\d{1,3}(\\.|\\,)?" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")|(\\d{1,3}(\\.|\\,)\\d+" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")" +
1286 HemisphereIndicator
+ WhiteSpace
+ "$" +
1289 customPatterns
.add(ptrn
);
1291 //Custom variation of DD:MM.MMM
1292 ptrn
= new CoordinatePattern();
1293 ptrn
.description
= "Custom variation of DD:MM.MMM";
1295 CaseInsensitive
+ "(^" +
1296 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1298 "(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}(\\.|\\,)?" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ "$)|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}(\\.|\\,)\\d+" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ "$)" +
1300 "|(^" + WhiteSpace
+
1302 "(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}(\\.|\\,)?" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ ")|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}(\\.|\\,)\\d+" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ ")" +
1304 HemisphereIndicator
+ WhiteSpace
+ "$" +
1307 customPatterns
.add(ptrn
);
1309 //Custom variation of DD:MM:SS.SSS
1310 ptrn
= new CoordinatePattern();
1311 ptrn
.description
= "Custom variation of DD:MM:SS.SSS";
1313 CaseInsensitive
+ "(^" +
1314 WhiteSpace
+ HemisphereIndicator
+ WhiteSpace
+
1316 "(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "$)|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ "$)|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ "\\d{1,2}(\\.|\\,)?" + WhiteSpace
+ secondsymbol
+ WhiteSpace
+ "$)|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ "\\d{1,2}(\\.|\\,)\\d+" + WhiteSpace
+ secondsymbol
+ WhiteSpace
+ "$)" +
1318 "|(^" + WhiteSpace
+
1320 "(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ ")|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ ")|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ "\\d{1,2}(\\.|\\,)?" + WhiteSpace
+ secondsymbol
+ WhiteSpace
+ ")|(\\d{1,3}" + WhiteSpace
+ degreesymbol
+ WhiteSpace
+ "\\d{1,2}" + WhiteSpace
+ minutesymbol
+ WhiteSpace
+ "\\d{1,2}(\\.|\\,)\\d+" + WhiteSpace
+ secondsymbol
+ WhiteSpace
+ ")" +
1322 HemisphereIndicator
+ WhiteSpace
+ "$" +
1325 customPatterns
.add(ptrn
);
1327 //check if the default patterns are to be used
1328 if (patternIn
.disableDefaultPatterns
) {
1329 patterns
= customPatterns
;
1330 } else { //if all patterns are to be used check which set has the matching priority
1332 //check if the custom patterns are to have priority over the default ones
1333 if (patternIn
.priorityOverDefaultPatterns
){
1335 //add default patterns to the custom patterns
1336 for (int i
= 0; i
< patterns
.size(); i
++){
1337 customPatterns
.add(patterns
.get(i
));
1341 patterns
= customPatterns
;
1344 //add custom patterns to the default patterns
1345 for (int i
= 0; i
< customPatterns
.size(); i
++){
1346 patterns
.add(customPatterns
.get(i
));