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
;
34 import org
.hibernate
.search
.annotations
.Field
;
35 import org
.hibernate
.search
.annotations
.Latitude
;
36 import org
.hibernate
.search
.annotations
.Longitude
;
37 import org
.hibernate
.search
.annotations
.NumericField
;
38 import org
.hibernate
.search
.annotations
.Spatial
;
39 import org
.hibernate
.search
.annotations
.SpatialMode
;
41 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
42 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnitBase
;
43 import eu
.etaxonomy
.cdm
.strategy
.parser
.location
.CoordinateConverter
;
44 import eu
.etaxonomy
.cdm
.strategy
.parser
.location
.CoordinateConverter
.ConversionResults
;
49 * @created 08-Nov-2007 13:06:44
51 @XmlAccessorType(XmlAccessType
.FIELD
)
52 @XmlType(name
= "Point", propOrder
= {
58 @XmlRootElement(name
= "Point")
60 @Spatial(spatialMode
=SpatialMode
.RANGE
, name
="point")
61 public class Point
implements Cloneable
, Serializable
{
62 private static final long serialVersionUID
= 531030660792800636L;
63 private static final Logger logger
= Logger
.getLogger(Point
.class);
65 //TODO was Float but H2 threw errors
66 @XmlElement(name
= "Longitude")
67 @Longitude(of
="point")
68 private Double longitude
;
70 @XmlElement(name
= "Latitude")
72 private Double latitude
;
75 * Error radius in Meters
77 @XmlElement(name
= "ErrorRadius")
80 private Integer errorRadius
= 0;
82 @XmlElement(name
= "ReferenceSystem")
84 @XmlSchemaType(name
= "IDREF")
85 @ManyToOne(fetch
= FetchType
.LAZY
)
86 private ReferenceSystem referenceSystem
;
89 //******************** FACTORY METHODS ****************************
95 public static Point
NewInstance(){
103 public static Point
NewInstance(Double longitude
, Double latitude
, ReferenceSystem referenceSystem
, Integer errorRadius
){
104 Point result
= new Point();
105 result
.setLongitude(longitude
);
106 result
.setLatitude(latitude
);
107 result
.setReferenceSystem(referenceSystem
);
108 result
.setErrorRadius(errorRadius
);
112 // ******************** CONSTRUCTOR ***************************
120 //************** Sexagesimal /decimal METHODS *******************
122 public enum Direction
{
126 public String
toString() {
133 public String
toString() {
140 public String
toString() {
147 public String
toString() {
153 public static final class CoordinateParser
{
156 * Pattern zum parsen von Sexagesimalen Grad: 145°
158 private static final String DEGREE_REGEX
= "([0-9]*)\u00B0";
160 * Pattern zum parsen von Sexagesimalen Minuten: 65'
162 private static final String MINUTES_REGEX
= "(?:([0-9]*)')?";
164 * Pattern zum parsen von Sexagesimalen Sekunden: 17"
166 private static final String SECONDS_REGEX
= "(?:([0-9]*)(?:''|\"))?";
168 * Himmelsrichtung Längengrad
170 private static final String LONGITUDE_DIRECTION_REGEX
= "([OEW])";
172 * Himmelsrichtung Breitengrad
174 private static final String LATITUDE_DIRECTION_REGEX
= "([NS])";
177 * Pattern zum Parsen von Breitengraden.
179 private static final Pattern LATITUDE_PATTERN
= Pattern
180 .compile(DEGREE_REGEX
+ MINUTES_REGEX
+ SECONDS_REGEX
181 + LATITUDE_DIRECTION_REGEX
);
184 * Pattern zum Parsen von Längengraden.
186 private static final Pattern LONGITUDE_PATTERN
= Pattern
187 .compile(DEGREE_REGEX
+ MINUTES_REGEX
+ SECONDS_REGEX
188 + LONGITUDE_DIRECTION_REGEX
);
190 private CoordinateParser() {
191 throw new AssertionError( );
195 * Parst einen Breitengrad der Form<br>
200 * sind ebenfalls erlaubt.
203 * @return Die geparsten Koordinaten
204 * @throws ParseException
205 * Wenn eine Fehler beim Parsen aufgetreten ist.
207 public static Sexagesimal
parseLatitude(final String strg
)
208 throws ParseException
{
209 return parseCoordinates(strg
, LATITUDE_PATTERN
);
213 * Parst einen Längengrad der Form<br>
218 * sind ebenfalls erlaubt.
221 * @return Die geparsten Koordinaten
222 * @throws ParseException
223 * Wenn eine Fehler beim Parsen aufgetreten ist.
225 public static Sexagesimal
parseLongitude(final String strg
)
226 throws ParseException
{
227 return parseCoordinates(strg
, LONGITUDE_PATTERN
);
232 * Not used at the moment. Use CoordinateConverter instead.
236 * @throws ParseException
238 private static Sexagesimal
parseCoordinates(final String strg
, final Pattern pattern
) throws ParseException
{
240 throw new java
.text
.ParseException("Keine Koordinaten gegeben.", -1);
242 final Matcher matcher
= pattern
.matcher(strg
);
243 if (matcher
.matches( )) {
244 if (matcher
.groupCount( ) == 4) {
246 String tmp
= matcher
.group(1);
247 int degree
= Integer
.parseInt(tmp
);
250 tmp
= matcher
.group(2);
251 int minutes
= Sexagesimal
.NONE
;
253 minutes
= Integer
.parseInt(tmp
);
257 tmp
= matcher
.group(3);
258 int seconds
= Sexagesimal
.NONE
;
260 seconds
= Integer
.parseInt(tmp
);
264 tmp
= matcher
.group(4);
265 final Direction direction
;
266 if (tmp
.equals("N")) {
267 direction
= Direction
.NORTH
;
269 else if (tmp
.equals("S")) {
270 direction
= Direction
.SOUTH
;
272 else if (tmp
.equals("E") || tmp
.equals("O")) {
273 direction
= Direction
.EAST
;
275 else if (tmp
.equals("W")) {
276 direction
= Direction
.WEST
;
281 return Sexagesimal
.NewInstance(degree
, minutes
, seconds
, direction
);
284 throw new java
.text
.ParseException(
285 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg
,
290 throw new java
.text
.ParseException(
291 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg
, -1);
298 private static final BigDecimal SIXTY
= BigDecimal
.valueOf(60.0);
299 private static final MathContext MC
= new MathContext(34, RoundingMode
.HALF_UP
);
300 private static final double HALF_SECOND
= 1. / 7200.;
302 //see http://www.tutorials.de/forum/archiv/348596-quiz-10-zeja-java.html
303 public static class Sexagesimal
{
304 public static Sexagesimal
NewInstance(Integer degree
, Integer minutes
, Integer seconds
, Direction direction
){
305 Sexagesimal result
= new Sexagesimal();
306 result
.degree
= degree
; result
.minutes
= minutes
; result
.seconds
= seconds
;
310 public static final int NONE
= 0;
311 public Integer degree
;
312 public Integer minutes
;
313 public Integer seconds
;
314 public Double tertiers
;
316 public Direction direction
;
319 public boolean isLatitude(){
320 return (direction
== Direction
.WEST
) || (direction
== Direction
.EAST
) ;
322 public boolean isLongitude(){
323 return ! isLatitude();
327 public static Sexagesimal
valueOf(Double decimal
, boolean isLatitude
){
328 return valueOf(decimal
, isLatitude
, false, false, true);
331 public static Sexagesimal
valueOf(Double decimal
, boolean isLatitude
, boolean nullSecondsToNull
, boolean nullMinutesToNull
, boolean allowTertiers
){
335 Sexagesimal sexagesimal
= new Sexagesimal();
336 Double decimalDegree
= decimal
;
338 if (decimalDegree
< 0) {
339 sexagesimal
.direction
= Direction
.SOUTH
;
342 sexagesimal
.direction
= Direction
.NORTH
;
346 if (decimalDegree
< 0) {
347 sexagesimal
.direction
= Direction
.WEST
;
350 sexagesimal
.direction
= Direction
.EAST
;
354 // Decimal in \u00B0'" umrechnen
355 double d
= Math
.abs(decimalDegree
);
356 if (! allowTertiers
){
357 d
+= HALF_SECOND
; // add half a second for rounding
359 d
+= HALF_SECOND
/ 10000; //to avoid rounding errors
361 sexagesimal
.degree
= (int) Math
.floor(d
);
362 sexagesimal
.minutes
= (int) Math
.floor((d
- sexagesimal
.degree
) * 60.0);
363 sexagesimal
.seconds
= (int) Math
.floor((d
- sexagesimal
.degree
- sexagesimal
.minutes
/ 60.0) * 3600.0);
364 sexagesimal
.tertiers
= (d
- sexagesimal
.degree
- sexagesimal
.minutes
/ 60.0 - sexagesimal
.seconds
/ 3600.0) * 3600.0;
366 if (sexagesimal
.seconds
== 0 && nullSecondsToNull
){
367 sexagesimal
.seconds
= null;
369 if (sexagesimal
.seconds
== null && sexagesimal
.minutes
== 0 && nullMinutesToNull
){
370 sexagesimal
.minutes
= null;
373 // sexagesimal.decimalRadian = Math.toRadians(this.decimalDegree);
379 private Double
toDecimal(){
380 BigDecimal value
= BigDecimal
.valueOf(CdmUtils
.Nz(this.seconds
)).divide(SIXTY
, MC
).add
381 (BigDecimal
.valueOf(CdmUtils
.Nz(this.minutes
))).divide(SIXTY
, MC
).add
382 (BigDecimal
.valueOf(CdmUtils
.Nz(this.degree
)));
384 if (this.direction
== Direction
.WEST
|| this.direction
== Direction
.SOUTH
) {
385 value
= value
.negate( );
387 return value
.doubleValue( );
391 public String
toString(){
392 return toString(false, false);
394 public String
toString(boolean includeEmptySeconds
){
395 return toString(includeEmptySeconds
, false);
398 public String
toString(boolean includeEmptySeconds
, boolean removeTertiers
){
400 result
= String
.valueOf(CdmUtils
.Nz(degree
)) + "\u00B0";
401 if (seconds
!= null || minutes
!= null){
402 result
+= String
.valueOf(CdmUtils
.Nz(minutes
)) + "'";
404 if (seconds
!= null ){
405 if (seconds
!= 0 || includeEmptySeconds
){
406 result
+= String
.valueOf(CdmUtils
.Nz(seconds
)) + getTertiersString(tertiers
, removeTertiers
) + "\"";
412 private String
getTertiersString(Double tertiers
, boolean removeTertiers
) {
413 if (tertiers
== null || removeTertiers
){
416 if (tertiers
>= 1.0 || tertiers
< 0.0){
417 throw new IllegalStateException("Tertiers should be 0.0 <= tertiers < 1.0 but are '" + tertiers
+ "'");
419 String result
= tertiers
.toString();
420 int pos
= result
.indexOf("E");
422 int exp
= - Integer
.valueOf(result
.substring(pos
+ 1));
423 result
= result
.substring(0, pos
).replace(".", "");
424 result
= "0." + StringUtils
.leftPad("", exp
- 1, "0") + result
;
428 if (result
.length() > 5){
429 result
= result
.substring(0, 5);
431 while (result
.endsWith("0")){
432 result
= result
.substring(0, result
.length() -1);
434 result
= result
.substring(1);
435 if (result
.equals(".")){
447 public Sexagesimal
getLongitudeSexagesimal (){
448 boolean isLatitude
= false;
449 return Sexagesimal
.valueOf(longitude
, isLatitude
);
453 public Sexagesimal
getLatitudeSexagesimal (){
454 boolean isLatitude
= true;
455 return Sexagesimal
.valueOf(latitude
, isLatitude
);
459 public void setLatitudeSexagesimal(Sexagesimal sexagesimalLatitude
){
460 this.latitude
= sexagesimalLatitude
.toDecimal();
463 public void setLongitudeSexagesimal(Sexagesimal sexagesimalLongitude
){
464 this.longitude
= sexagesimalLongitude
.toDecimal();
468 public void setLatitudeByParsing(String string
) throws ParseException
{
469 this.setLatitude(parseLatitude(string
));
473 public void setLongitudeByParsing(String string
) throws ParseException
{
474 this.setLongitude(parseLongitude(string
));
478 public static Double
parseLatitude(String string
) throws ParseException
{
483 string
= setCurrentDoubleSeparator(string
);
484 if (isDouble(string
)){
485 Double result
= Double
.valueOf(string
);
486 if (Math
.abs(result
) > 90.0){
487 throw new ParseException("Latitude could not be parsed", 0);
491 CoordinateConverter converter
= new CoordinateConverter();
492 ConversionResults result
= converter
.tryConvert(string
);
493 if (! result
.conversionSuccessful
|| (result
.isLongitude
!= null && result
.isLongitude
) ){
494 throw new ParseException("Latitude could not be parsed", 0);
496 return result
.convertedCoord
;
499 } catch (Exception e
) {
500 String message
= "Latitude %s could not be parsed";
501 message
= String
.format(message
, string
);
502 throw new ParseException(message
, 0);
506 public static Double
parseLongitude(String string
) throws ParseException
{
511 string
= setCurrentDoubleSeparator(string
);
512 if (isDouble(string
)){
513 Double result
= Double
.valueOf(string
);
514 if (Math
.abs(result
) > 180.0){
515 throw new ParseException("Longitude could not be parsed", 0);
519 CoordinateConverter converter
= new CoordinateConverter();
520 ConversionResults result
= converter
.tryConvert(string
);
521 if (! result
.conversionSuccessful
|| (result
.isLongitude
!= null && ! result
.isLongitude
)){
522 throw new ParseException("Longitude could not be parsed", 0);
524 return result
.convertedCoord
;
527 } catch (Exception e
) {
528 String message
= "Longitude %s could not be parsed";
529 message
= String
.format(message
, string
);
530 throw new ParseException(message
, 0);
534 private static String
setCurrentDoubleSeparator(String string
) {
535 String regExReplaceComma
= "(\\,|\\.)";
536 string
= string
.replaceAll(regExReplaceComma
,".");
541 private static boolean isDouble(String string
) {
543 Double
.valueOf(string
);
546 } catch (NumberFormatException e
) {
548 } catch (Exception e
) {
554 // ******************** GETTER / SETTER ********************************
556 public ReferenceSystem
getReferenceSystem(){
557 return this.referenceSystem
;
562 * @param referenceSystem referenceSystem
564 public void setReferenceSystem(ReferenceSystem referenceSystem
){
565 this.referenceSystem
= referenceSystem
;
568 public Double
getLongitude(){
569 return this.longitude
;
574 * @param longitude longitude
576 public void setLongitude(Double longitude
){
577 this.longitude
= longitude
;
580 public Double
getLatitude(){
581 return this.latitude
;
586 * @param latitude latitude
588 public void setLatitude(Double latitude
){
589 this.latitude
= latitude
;
593 * Error radius in Meters
595 public Integer
getErrorRadius(){
596 return this.errorRadius
;
601 * @param errorRadius errorRadius
603 public void setErrorRadius(Integer errorRadius
){
604 this.errorRadius
= errorRadius
;
607 // **************** toString *************************/
611 * Returns a string representation in sexagesimal coordinates.
614 public String
toSexagesimalString(boolean includeEmptySeconds
, boolean includeReferenceSystem
){
616 result
+= getLatitudeSexagesimal() == null ?
"" : getLatitudeSexagesimal().toString(includeEmptySeconds
);
617 result
= CdmUtils
.concat(", ", result
, getLongitudeSexagesimal() == null ?
"" : getLongitudeSexagesimal().toString(includeEmptySeconds
));
618 if (includeReferenceSystem
&& getReferenceSystem() != null){
619 String refSys
= CdmUtils
.isBlank(getReferenceSystem().getLabel()) ?
"" : "(" + getReferenceSystem().getLabel() + ")";
620 result
= CdmUtils
.concat(" ", result
, refSys
);
626 * @see java.lang.Object#toString()
629 public String
toString(){
631 boolean includeEmptySeconds
= true;
632 result
+= getLatitudeSexagesimal() == null ?
"" : getLatitudeSexagesimal().toString(includeEmptySeconds
);
633 result
= CdmUtils
.concat(", ", result
, getLongitudeSexagesimal() == null ?
"" : getLongitudeSexagesimal().toString(includeEmptySeconds
));
638 //*********** CLONE **********************************/
641 * Clones <i>this</i> point. This is a shortcut that enables to
642 * create a new instance that differs only slightly from <i>this</i> point
643 * by modifying only some of the attributes.<BR>
644 * This method overrides the clone method from {@link DerivedUnitBase DerivedUnitBase}.
646 * @see java.lang.Object#clone()
649 public Point
clone(){
651 Point result
= (Point
)super.clone();
652 result
.setReferenceSystem(this.referenceSystem
);
653 //no changes to: errorRadius, latitude, longitude
655 } catch (CloneNotSupportedException e
) {
656 logger
.warn("Object does not implement cloneable");