2 * Copyright (C) 2007 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.
10 package eu
.etaxonomy
.cdm
.model
.location
;
12 import java
.io
.Serializable
;
13 import java
.math
.BigDecimal
;
14 import java
.math
.MathContext
;
15 import java
.math
.RoundingMode
;
16 import java
.text
.ParseException
;
17 import java
.util
.regex
.Matcher
;
18 import java
.util
.regex
.Pattern
;
20 import javax
.persistence
.Embeddable
;
21 import javax
.persistence
.FetchType
;
22 import javax
.persistence
.ManyToOne
;
23 import javax
.persistence
.Transient
;
24 import javax
.xml
.bind
.annotation
.XmlAccessType
;
25 import javax
.xml
.bind
.annotation
.XmlAccessorType
;
26 import javax
.xml
.bind
.annotation
.XmlElement
;
27 import javax
.xml
.bind
.annotation
.XmlIDREF
;
28 import javax
.xml
.bind
.annotation
.XmlRootElement
;
29 import javax
.xml
.bind
.annotation
.XmlSchemaType
;
30 import javax
.xml
.bind
.annotation
.XmlType
;
32 import org
.apache
.commons
.lang
.StringUtils
;
33 import org
.apache
.log4j
.Logger
;
35 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
36 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnitBase
;
37 import eu
.etaxonomy
.cdm
.strategy
.parser
.location
.CoordinateConverter
;
38 import eu
.etaxonomy
.cdm
.strategy
.parser
.location
.CoordinateConverter
.ConversionResults
;
43 * @created 08-Nov-2007 13:06:44
45 @XmlAccessorType(XmlAccessType
.FIELD
)
46 @XmlType(name
= "Point", propOrder
= {
52 @XmlRootElement(name
= "Point")
54 public class Point
implements Cloneable
, Serializable
{
55 private static final long serialVersionUID
= 531030660792800636L;
56 private static final Logger logger
= Logger
.getLogger(Point
.class);
58 //TODO was Float but H2 threw errors
59 @XmlElement(name
= "Longitude")
60 private Double longitude
;
62 @XmlElement(name
= "Latitude")
63 private Double latitude
;
66 * Error radius in Meters
68 @XmlElement(name
= "ErrorRadius")
69 private Integer errorRadius
= 0;
71 @XmlElement(name
= "ReferenceSystem")
73 @XmlSchemaType(name
= "IDREF")
74 @ManyToOne(fetch
= FetchType
.LAZY
)
75 private ReferenceSystem referenceSystem
;
78 //******************** FACTORY METHODS ****************************
84 public static Point
NewInstance(){
92 public static Point
NewInstance(Double longitude
, Double latitude
, ReferenceSystem referenceSystem
, Integer errorRadius
){
93 Point result
= new Point();
94 result
.setLongitude(longitude
);
95 result
.setLatitude(latitude
);
96 result
.setReferenceSystem(referenceSystem
);
97 result
.setErrorRadius(errorRadius
);
101 // ******************** CONSTRUCTOR ***************************
109 //************** Sexagesimal /decimal METHODS *******************
111 public enum Direction
{
115 public String
toString() {
122 public String
toString() {
129 public String
toString() {
136 public String
toString() {
142 public static final class CoordinateParser
{
145 * Pattern zum parsen von Sexagesimalen Grad: 145°
147 private static final String DEGREE_REGEX
= "([0-9]*)\u00B0";
149 * Pattern zum parsen von Sexagesimalen Minuten: 65'
151 private static final String MINUTES_REGEX
= "(?:([0-9]*)')?";
153 * Pattern zum parsen von Sexagesimalen Sekunden: 17"
155 private static final String SECONDS_REGEX
= "(?:([0-9]*)(?:''|\"))?";
157 * Himmelsrichtung Längengrad
159 private static final String LONGITUDE_DIRECTION_REGEX
= "([OEW])";
161 * Himmelsrichtung Breitengrad
163 private static final String LATITUDE_DIRECTION_REGEX
= "([NS])";
166 * Pattern zum Parsen von Breitengraden.
168 private static final Pattern LATITUDE_PATTERN
= Pattern
169 .compile(DEGREE_REGEX
+ MINUTES_REGEX
+ SECONDS_REGEX
170 + LATITUDE_DIRECTION_REGEX
);
173 * Pattern zum Parsen von Längengraden.
175 private static final Pattern LONGITUDE_PATTERN
= Pattern
176 .compile(DEGREE_REGEX
+ MINUTES_REGEX
+ SECONDS_REGEX
177 + LONGITUDE_DIRECTION_REGEX
);
179 private CoordinateParser() {
180 throw new AssertionError( );
184 * Parst einen Breitengrad der Form<br>
189 * sind ebenfalls erlaubt.
192 * @return Die geparsten Koordinaten
193 * @throws ParseException
194 * Wenn eine Fehler beim Parsen aufgetreten ist.
196 public static Sexagesimal
parseLatitude(final String strg
)
197 throws ParseException
{
198 return parseCoordinates(strg
, LATITUDE_PATTERN
);
202 * Parst einen Längengrad der Form<br>
207 * sind ebenfalls erlaubt.
210 * @return Die geparsten Koordinaten
211 * @throws ParseException
212 * Wenn eine Fehler beim Parsen aufgetreten ist.
214 public static Sexagesimal
parseLongitude(final String strg
)
215 throws ParseException
{
216 return parseCoordinates(strg
, LONGITUDE_PATTERN
);
221 * Not used at the moment. Use CoordinateConverter instead.
225 * @throws ParseException
227 private static Sexagesimal
parseCoordinates(final String strg
, final Pattern pattern
) throws ParseException
{
229 throw new java
.text
.ParseException("Keine Koordinaten gegeben.", -1);
231 final Matcher matcher
= pattern
.matcher(strg
);
232 if (matcher
.matches( )) {
233 if (matcher
.groupCount( ) == 4) {
235 String tmp
= matcher
.group(1);
236 int degree
= Integer
.parseInt(tmp
);
239 tmp
= matcher
.group(2);
240 int minutes
= Sexagesimal
.NONE
;
242 minutes
= Integer
.parseInt(tmp
);
246 tmp
= matcher
.group(3);
247 int seconds
= Sexagesimal
.NONE
;
249 seconds
= Integer
.parseInt(tmp
);
253 tmp
= matcher
.group(4);
254 final Direction direction
;
255 if (tmp
.equals("N")) {
256 direction
= Direction
.NORTH
;
258 else if (tmp
.equals("S")) {
259 direction
= Direction
.SOUTH
;
261 else if (tmp
.equals("E") || tmp
.equals("O")) {
262 direction
= Direction
.EAST
;
264 else if (tmp
.equals("W")) {
265 direction
= Direction
.WEST
;
270 return Sexagesimal
.NewInstance(degree
, minutes
, seconds
, direction
);
273 throw new java
.text
.ParseException(
274 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg
,
279 throw new java
.text
.ParseException(
280 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg
, -1);
287 private static final BigDecimal SIXTY
= BigDecimal
.valueOf(60.0);
288 private static final MathContext MC
= new MathContext(34, RoundingMode
.HALF_UP
);
289 private static final double HALF_SECOND
= 1. / 7200.;
291 //see http://www.tutorials.de/forum/archiv/348596-quiz-10-zeja-java.html
292 public static class Sexagesimal
{
293 public static Sexagesimal
NewInstance(Integer degree
, Integer minutes
, Integer seconds
, Direction direction
){
294 Sexagesimal result
= new Sexagesimal();
295 result
.degree
= degree
; result
.minutes
= minutes
; result
.seconds
= seconds
;
299 public static final int NONE
= 0;
300 public Integer degree
;
301 public Integer minutes
;
302 public Integer seconds
;
303 public Double tertiers
;
305 public Direction direction
;
308 public boolean isLatitude(){
309 return (direction
== Direction
.WEST
) || (direction
== Direction
.EAST
) ;
311 public boolean isLongitude(){
312 return ! isLatitude();
316 public static Sexagesimal
valueOf(Double decimal
, boolean isLatitude
){
317 return valueOf(decimal
, isLatitude
, false, false, true);
320 public static Sexagesimal
valueOf(Double decimal
, boolean isLatitude
, boolean nullSecondsToNull
, boolean nullMinutesToNull
, boolean allowTertiers
){
324 Sexagesimal sexagesimal
= new Sexagesimal();
325 Double decimalDegree
= decimal
;
327 if (decimalDegree
< 0) {
328 sexagesimal
.direction
= Direction
.SOUTH
;
331 sexagesimal
.direction
= Direction
.NORTH
;
335 if (decimalDegree
< 0) {
336 sexagesimal
.direction
= Direction
.WEST
;
339 sexagesimal
.direction
= Direction
.EAST
;
343 // Decimal in \u00B0'" umrechnen
344 double d
= Math
.abs(decimalDegree
);
345 if (! allowTertiers
){
346 d
+= HALF_SECOND
; // add half a second for rounding
348 d
+= HALF_SECOND
/ 10000; //to avoid rounding errors
350 sexagesimal
.degree
= (int) Math
.floor(d
);
351 sexagesimal
.minutes
= (int) Math
.floor((d
- sexagesimal
.degree
) * 60.0);
352 sexagesimal
.seconds
= (int) Math
.floor((d
- sexagesimal
.degree
- sexagesimal
.minutes
/ 60.0) * 3600.0);
353 sexagesimal
.tertiers
= (d
- sexagesimal
.degree
- sexagesimal
.minutes
/ 60.0 - sexagesimal
.seconds
/ 3600.0) * 3600.0;
355 if (sexagesimal
.seconds
== 0 && nullSecondsToNull
){
356 sexagesimal
.seconds
= null;
358 if (sexagesimal
.seconds
== null && sexagesimal
.minutes
== 0 && nullMinutesToNull
){
359 sexagesimal
.minutes
= null;
362 // sexagesimal.decimalRadian = Math.toRadians(this.decimalDegree);
368 private Double
toDecimal(){
369 BigDecimal value
= BigDecimal
.valueOf(CdmUtils
.Nz(this.seconds
)).divide(SIXTY
, MC
).add
370 (BigDecimal
.valueOf(CdmUtils
.Nz(this.minutes
))).divide(SIXTY
, MC
).add
371 (BigDecimal
.valueOf(CdmUtils
.Nz(this.degree
)));
373 if (this.direction
== Direction
.WEST
|| this.direction
== Direction
.SOUTH
) {
374 value
= value
.negate( );
376 return value
.doubleValue( );
380 public String
toString(){
381 return toString(false, false);
383 public String
toString(boolean includeEmptySeconds
){
384 return toString(includeEmptySeconds
, false);
387 public String
toString(boolean includeEmptySeconds
, boolean removeTertiers
){
389 result
= String
.valueOf(CdmUtils
.Nz(degree
)) + "\u00B0";
390 if (seconds
!= null || minutes
!= null){
391 result
+= String
.valueOf(CdmUtils
.Nz(minutes
)) + "'";
393 if (seconds
!= null ){
394 if (seconds
!= 0 || includeEmptySeconds
){
395 result
+= String
.valueOf(CdmUtils
.Nz(seconds
)) + getTertiersString(tertiers
, removeTertiers
) + "\"";
401 private String
getTertiersString(Double tertiers
, boolean removeTertiers
) {
402 if (tertiers
== null || removeTertiers
){
405 if (tertiers
>= 1.0 || tertiers
< 0.0){
406 throw new IllegalStateException("Tertiers should be 0.0 <= tertiers < 1.0 but are '" + tertiers
+ "'");
408 String result
= tertiers
.toString();
409 int pos
= result
.indexOf("E");
411 int exp
= - Integer
.valueOf(result
.substring(pos
+ 1));
412 result
= result
.substring(0, pos
).replace(".", "");
413 result
= "0." + StringUtils
.leftPad("", exp
- 1, "0") + result
;
417 if (result
.length() > 5){
418 result
= result
.substring(0, 5);
420 while (result
.endsWith("0")){
421 result
= result
.substring(0, result
.length() -1);
423 result
= result
.substring(1);
424 if (result
.equals(".")){
436 public Sexagesimal
getLongitudeSexagesimal (){
437 boolean isLatitude
= false;
438 return Sexagesimal
.valueOf(longitude
, isLatitude
);
442 public Sexagesimal
getLatitudeSexagesimal (){
443 boolean isLatitude
= true;
444 return Sexagesimal
.valueOf(latitude
, isLatitude
);
448 public void setLatitudeSexagesimal(Sexagesimal sexagesimalLatitude
){
449 this.latitude
= sexagesimalLatitude
.toDecimal();
452 public void setLongitudeSexagesimal(Sexagesimal sexagesimalLongitude
){
453 this.longitude
= sexagesimalLongitude
.toDecimal();
457 public void setLatitudeByParsing(String string
) throws ParseException
{
458 this.setLatitude(parseLatitude(string
));
462 public void setLongitudeByParsing(String string
) throws ParseException
{
463 this.setLongitude(parseLongitude(string
));
467 public static Double
parseLatitude(String string
) throws ParseException
{
472 string
= setCurrentDoubleSeparator(string
);
473 if (isDouble(string
)){
474 Double result
= Double
.valueOf(string
);
475 if (Math
.abs(result
) > 90.0){
476 throw new ParseException("Latitude could not be parsed", 0);
480 CoordinateConverter converter
= new CoordinateConverter();
481 ConversionResults result
= converter
.tryConvert(string
);
482 if (! result
.conversionSuccessful
|| (result
.isLongitude
!= null && result
.isLongitude
) ){
483 throw new ParseException("Latitude could not be parsed", 0);
485 return result
.convertedCoord
;
488 } catch (Exception e
) {
489 String message
= "Latitude %s could not be parsed";
490 message
= String
.format(message
, string
);
491 throw new ParseException(message
, 0);
495 public static Double
parseLongitude(String string
) throws ParseException
{
500 string
= setCurrentDoubleSeparator(string
);
501 if (isDouble(string
)){
502 Double result
= Double
.valueOf(string
);
503 if (Math
.abs(result
) > 180.0){
504 throw new ParseException("Longitude could not be parsed", 0);
508 CoordinateConverter converter
= new CoordinateConverter();
509 ConversionResults result
= converter
.tryConvert(string
);
510 if (! result
.conversionSuccessful
|| (result
.isLongitude
!= null && ! result
.isLongitude
)){
511 throw new ParseException("Longitude could not be parsed", 0);
513 return result
.convertedCoord
;
516 } catch (Exception e
) {
517 String message
= "Longitude %s could not be parsed";
518 message
= String
.format(message
, string
);
519 throw new ParseException(message
, 0);
523 private static String
setCurrentDoubleSeparator(String string
) {
524 String regExReplaceComma
= "(\\,|\\.)";
525 string
= string
.replaceAll(regExReplaceComma
,".");
530 private static boolean isDouble(String string
) {
532 Double
.valueOf(string
);
535 } catch (NumberFormatException e
) {
537 } catch (Exception e
) {
543 // ******************** GETTER / SETTER ********************************
545 public ReferenceSystem
getReferenceSystem(){
546 return this.referenceSystem
;
551 * @param referenceSystem referenceSystem
553 public void setReferenceSystem(ReferenceSystem referenceSystem
){
554 this.referenceSystem
= referenceSystem
;
557 public Double
getLongitude(){
558 return this.longitude
;
563 * @param longitude longitude
565 public void setLongitude(Double longitude
){
566 this.longitude
= longitude
;
569 public Double
getLatitude(){
570 return this.latitude
;
575 * @param latitude latitude
577 public void setLatitude(Double latitude
){
578 this.latitude
= latitude
;
582 * Error radius in Meters
584 public Integer
getErrorRadius(){
585 return this.errorRadius
;
590 * @param errorRadius errorRadius
592 public void setErrorRadius(Integer errorRadius
){
593 this.errorRadius
= errorRadius
;
596 // **************** toString *************************/
600 * Returns a string representation in sexagesimal coordinates.
603 public String
toSexagesimalString(boolean includeEmptySeconds
, boolean includeReferenceSystem
){
605 result
+= getLatitudeSexagesimal() == null ?
"" : getLatitudeSexagesimal().toString(includeEmptySeconds
);
606 result
= CdmUtils
.concat(", ", result
, getLongitudeSexagesimal() == null ?
"" : getLongitudeSexagesimal().toString(includeEmptySeconds
));
607 if (includeReferenceSystem
&& getReferenceSystem() != null){
608 String refSys
= CdmUtils
.isBlank(getReferenceSystem().getLabel()) ?
"" : "(" + getReferenceSystem().getLabel() + ")";
609 result
= CdmUtils
.concat(" ", result
, refSys
);
615 * @see java.lang.Object#toString()
618 public String
toString(){
620 boolean includeEmptySeconds
= true;
621 result
+= getLatitudeSexagesimal() == null ?
"" : getLatitudeSexagesimal().toString(includeEmptySeconds
);
622 result
= CdmUtils
.concat(", ", result
, getLongitudeSexagesimal() == null ?
"" : getLongitudeSexagesimal().toString(includeEmptySeconds
));
627 //*********** CLONE **********************************/
630 * Clones <i>this</i> point. This is a shortcut that enables to
631 * create a new instance that differs only slightly from <i>this</i> point
632 * by modifying only some of the attributes.<BR>
633 * This method overrides the clone method from {@link DerivedUnitBase DerivedUnitBase}.
635 * @see java.lang.Object#clone()
638 public Point
clone(){
640 Point result
= (Point
)super.clone();
641 result
.setReferenceSystem(this.referenceSystem
);
642 //no changes to: errorRadius, latitude, longitude
644 } catch (CloneNotSupportedException e
) {
645 logger
.warn("Object does not implement cloneable");