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
.log4j
.Logger
;
34 import com
.sun
.corba
.se
.spi
.orbutil
.fsm
.Guard
.Result
;
36 import eu
.etaxonomy
.cdm
.common
.CdmUtils
;
37 import eu
.etaxonomy
.cdm
.model
.occurrence
.DerivedUnitBase
;
38 import eu
.etaxonomy
.cdm
.strategy
.parser
.location
.CoordinateConverter
;
39 import eu
.etaxonomy
.cdm
.strategy
.parser
.location
.CoordinateConverter
.ConversionResults
;
44 * @created 08-Nov-2007 13:06:44
46 @XmlAccessorType(XmlAccessType
.FIELD
)
47 @XmlType(name
= "Point", propOrder
= {
53 @XmlRootElement(name
= "Point")
55 public class Point
implements Cloneable
, Serializable
{
56 private static final long serialVersionUID
= 531030660792800636L;
57 private static final Logger logger
= Logger
.getLogger(Point
.class);
59 //TODO was Float but H2 threw errors
60 @XmlElement(name
= "Longitude")
61 private Double longitude
;
63 @XmlElement(name
= "Latitude")
64 private Double latitude
;
67 @XmlElement(name
= "ErrorRadius")
68 private Integer errorRadius
= 0;
70 @XmlElement(name
= "ReferenceSystem")
72 @XmlSchemaType(name
= "IDREF")
73 @ManyToOne(fetch
= FetchType
.LAZY
)
74 private ReferenceSystem referenceSystem
;
77 //******************** FACTORY METHODS ****************************
83 public static Point
NewInstance(){
91 public static Point
NewInstance(Double longitude
, Double latitude
, ReferenceSystem referenceSystem
, Integer errorRadius
){
92 Point result
= new Point();
93 result
.setLongitude(longitude
);
94 result
.setLatitude(latitude
);
95 result
.setReferenceSystem(referenceSystem
);
96 result
.setErrorRadius(errorRadius
);
100 // ******************** CONSTRUCTOR ***************************
108 //************** Sexagesimal /decimal METHODS *******************
110 public enum Direction
{
114 public String
toString() {
121 public String
toString() {
128 public String
toString() {
135 public String
toString() {
141 public static final class CoordinateParser
{
144 * Pattern zum parsen von Sexagesimalen Grad: 145°
146 private static final String DEGREE_REGEX
= "([0-9]*)\u00B0";
148 * Pattern zum parsen von Sexagesimalen Minuten: 65'
150 private static final String MINUTES_REGEX
= "(?:([0-9]*)')?";
152 * Pattern zum parsen von Sexagesimalen Sekunden: 17"
154 private static final String SECONDS_REGEX
= "(?:([0-9]*)(?:''|\"))?";
156 * Himmelsrichtung Längengrad
158 private static final String LONGITUDE_DIRECTION_REGEX
= "([OEW])";
160 * Himmelsrichtung Breitengrad
162 private static final String LATITUDE_DIRECTION_REGEX
= "([NS])";
165 * Pattern zum Parsen von Breitengraden.
167 private static final Pattern LATITUDE_PATTERN
= Pattern
168 .compile(DEGREE_REGEX
+ MINUTES_REGEX
+ SECONDS_REGEX
169 + LATITUDE_DIRECTION_REGEX
);
172 * Pattern zum Parsen von Längengraden.
174 private static final Pattern LONGITUDE_PATTERN
= Pattern
175 .compile(DEGREE_REGEX
+ MINUTES_REGEX
+ SECONDS_REGEX
176 + LONGITUDE_DIRECTION_REGEX
);
178 private CoordinateParser() {
179 throw new AssertionError( );
183 * Parst einen Breitengrad der Form<br>
188 * sind ebenfalls erlaubt.
191 * @return Die geparsten Koordinaten
192 * @throws ParseException
193 * Wenn eine Fehler beim Parsen aufgetreten ist.
195 public static Sexagesimal
parseLatitude(final String strg
)
196 throws ParseException
{
197 return parseCoordinates(strg
, LATITUDE_PATTERN
);
201 * Parst einen Längengrad der Form<br>
206 * sind ebenfalls erlaubt.
209 * @return Die geparsten Koordinaten
210 * @throws ParseException
211 * Wenn eine Fehler beim Parsen aufgetreten ist.
213 public static Sexagesimal
parseLongitude(final String strg
)
214 throws ParseException
{
215 return parseCoordinates(strg
, LONGITUDE_PATTERN
);
220 * Not used at the moment. Use CoordinateConverter instead.
224 * @throws ParseException
226 private static Sexagesimal
parseCoordinates(final String strg
, final Pattern pattern
) throws ParseException
{
228 throw new java
.text
.ParseException("Keine Koordinaten gegeben.", -1);
230 final Matcher matcher
= pattern
.matcher(strg
);
231 if (matcher
.matches( )) {
232 if (matcher
.groupCount( ) == 4) {
234 String tmp
= matcher
.group(1);
235 int degree
= Integer
.parseInt(tmp
);
238 tmp
= matcher
.group(2);
239 int minutes
= Sexagesimal
.NONE
;
241 minutes
= Integer
.parseInt(tmp
);
245 tmp
= matcher
.group(3);
246 int seconds
= Sexagesimal
.NONE
;
248 seconds
= Integer
.parseInt(tmp
);
252 tmp
= matcher
.group(4);
253 final Direction direction
;
254 if (tmp
.equals("N")) {
255 direction
= Direction
.NORTH
;
257 else if (tmp
.equals("S")) {
258 direction
= Direction
.SOUTH
;
260 else if (tmp
.equals("E") || tmp
.equals("O")) {
261 direction
= Direction
.EAST
;
263 else if (tmp
.equals("W")) {
264 direction
= Direction
.WEST
;
269 return Sexagesimal
.NewInstance(degree
, minutes
, seconds
, direction
);
272 throw new java
.text
.ParseException(
273 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg
,
278 throw new java
.text
.ParseException(
279 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg
, -1);
286 private static final BigDecimal SIXTY
= BigDecimal
.valueOf(60.0);
287 private static final MathContext MC
= new MathContext(34, RoundingMode
.HALF_UP
);
288 private static final double HALF_SECOND
= 1. / 7200.;
290 //see http://www.tutorials.de/forum/archiv/348596-quiz-10-zeja-java.html
291 public static class Sexagesimal
{
292 public static Sexagesimal
NewInstance(Integer degree
, Integer minutes
, Integer seconds
, Direction direction
){
293 Sexagesimal result
= new Sexagesimal();
294 result
.degree
= degree
; result
.minutes
= minutes
; result
.seconds
= seconds
;
298 public static final int NONE
= 0;
299 public Integer degree
;
300 public Integer minutes
;
301 public Integer seconds
;
302 public Double tertiers
;
304 public Direction direction
;
307 public boolean isLatitude(){
308 return (direction
== Direction
.WEST
) || (direction
== Direction
.EAST
) ;
310 public boolean isLongitude(){
311 return ! isLatitude();
315 public static Sexagesimal
valueOf(Double decimal
, boolean isLatitude
){
316 return valueOf(decimal
, isLatitude
, false, false, true);
319 public static Sexagesimal
valueOf(Double decimal
, boolean isLatitude
, boolean nullSecondsToNull
, boolean nullMinutesToNull
, boolean allowTertiers
){
323 Sexagesimal sexagesimal
= new Sexagesimal();
324 Double decimalDegree
= decimal
;
326 if (decimalDegree
< 0) {
327 sexagesimal
.direction
= Direction
.SOUTH
;
330 sexagesimal
.direction
= Direction
.NORTH
;
334 if (decimalDegree
< 0) {
335 sexagesimal
.direction
= Direction
.WEST
;
338 sexagesimal
.direction
= Direction
.EAST
;
342 // Decimal in \u00B0'" umrechnen
343 double d
= Math
.abs(decimalDegree
);
344 if (! allowTertiers
){
345 d
+= HALF_SECOND
; // add half a second for rounding
347 d
+= HALF_SECOND
/ 10000; //to avoid rounding errors
349 sexagesimal
.degree
= (int) Math
.floor(d
);
350 sexagesimal
.minutes
= (int) Math
.floor((d
- sexagesimal
.degree
) * 60.0);
351 sexagesimal
.seconds
= (int) Math
.floor((d
- sexagesimal
.degree
- sexagesimal
.minutes
/ 60.0) * 3600.0);
352 sexagesimal
.tertiers
= (d
- sexagesimal
.degree
- sexagesimal
.minutes
/ 60.0 - sexagesimal
.seconds
/ 3600.0) * 3600.0;
354 if (sexagesimal
.seconds
== 0 && nullSecondsToNull
){
355 sexagesimal
.seconds
= null;
357 if (sexagesimal
.seconds
== null && sexagesimal
.minutes
== 0 && nullMinutesToNull
){
358 sexagesimal
.minutes
= null;
361 // sexagesimal.decimalRadian = Math.toRadians(this.decimalDegree);
367 private Double
toDecimal(){
368 BigDecimal value
= BigDecimal
.valueOf(CdmUtils
.Nz(this.seconds
)).divide(SIXTY
, MC
).add
369 (BigDecimal
.valueOf(CdmUtils
.Nz(this.minutes
))).divide(SIXTY
, MC
).add
370 (BigDecimal
.valueOf(CdmUtils
.Nz(this.degree
)));
372 if (this.direction
== Direction
.WEST
|| this.direction
== Direction
.SOUTH
) {
373 value
= value
.negate( );
375 return value
.doubleValue( );
379 public String
toString(){
380 return toString(false, false);
382 public String
toString(boolean includeEmptySeconds
){
383 return toString(includeEmptySeconds
, false);
386 public String
toString(boolean includeEmptySeconds
, boolean removeTertiers
){
388 result
= String
.valueOf(CdmUtils
.Nz(degree
)) + "\u00B0";
389 if (seconds
!= null || minutes
!= null){
390 result
+= String
.valueOf(CdmUtils
.Nz(minutes
)) + "'";
392 if (seconds
!= null ){
393 if (seconds
!= 0 || includeEmptySeconds
){
394 result
+= String
.valueOf(CdmUtils
.Nz(seconds
)) + getTertiersString(tertiers
, removeTertiers
) + "\"";
400 private String
getTertiersString(Double tertiers
, boolean removeTertiers
) {
401 if (tertiers
== null || removeTertiers
){
404 String result
= String
.valueOf(tertiers
);
405 if (result
.length() > 5){
406 result
= result
.substring(0, 5);
408 while (result
.endsWith("0")){
409 result
= result
.substring(0, result
.length() -1);
411 return result
.substring(1);
420 public Sexagesimal
getLongitudeSexagesimal (){
421 boolean isLatitude
= false;
422 return Sexagesimal
.valueOf(longitude
, isLatitude
);
426 public Sexagesimal
getLatitudeSexagesimal (){
427 boolean isLatitude
= true;
428 return Sexagesimal
.valueOf(latitude
, isLatitude
);
432 public void setLatitudeSexagesimal(Sexagesimal sexagesimalLatitude
){
433 this.latitude
= sexagesimalLatitude
.toDecimal();
436 public void setLongitudeSexagesimal(Sexagesimal sexagesimalLongitude
){
437 this.longitude
= sexagesimalLongitude
.toDecimal();
441 public void setLatitudeByParsing(String string
) throws ParseException
{
442 this.setLatitude(parseLatitude(string
));
446 public void setLongitudeByParsing(String string
) throws ParseException
{
447 this.setLongitude(parseLongitude(string
));
451 public static Double
parseLatitude(String string
) throws ParseException
{
452 string
= setCurrentDoubleSeparator(string
);
453 if (isDouble(string
)){
454 Double result
= Double
.valueOf(string
);
455 if (Math
.abs(result
) > 90.0){
456 throw new ParseException("Latitude could not be parsed", 0);
460 CoordinateConverter converter
= new CoordinateConverter();
461 ConversionResults result
= converter
.tryConvert(string
);
462 if (! result
.conversionSuccessful
|| (result
.isLongitude
!= null && result
.isLongitude
) ){
463 throw new ParseException("Latitude could not be parsed", 0);
465 return result
.convertedCoord
;
470 public static Double
parseLongitude(String string
) throws ParseException
{
471 string
= setCurrentDoubleSeparator(string
);
472 if (isDouble(string
)){
473 Double result
= Double
.valueOf(string
);
474 if (Math
.abs(result
) > 180.0){
475 throw new ParseException("Longitude could not be parsed", 0);
479 CoordinateConverter converter
= new CoordinateConverter();
480 ConversionResults result
= converter
.tryConvert(string
);
481 if (! result
.conversionSuccessful
|| (result
.isLongitude
!= null && ! result
.isLongitude
)){
482 throw new ParseException("Longitude could not be parsed", 0);
484 return result
.convertedCoord
;
489 private static String
setCurrentDoubleSeparator(String string
) {
490 String regExReplaceComma
= "(\\,|\\.)";
491 string
= string
.replaceAll(regExReplaceComma
,".");
496 private static boolean isDouble(String string
) {
498 Double
.valueOf(string
);
501 } catch (NumberFormatException e
) {
503 } catch (Exception e
) {
509 // ******************** GETTER / SETTER ********************************
511 public ReferenceSystem
getReferenceSystem(){
512 return this.referenceSystem
;
517 * @param referenceSystem referenceSystem
519 public void setReferenceSystem(ReferenceSystem referenceSystem
){
520 this.referenceSystem
= referenceSystem
;
523 public Double
getLongitude(){
524 return this.longitude
;
529 * @param longitude longitude
531 public void setLongitude(Double longitude
){
532 this.longitude
= longitude
;
535 public Double
getLatitude(){
536 return this.latitude
;
541 * @param latitude latitude
543 public void setLatitude(Double latitude
){
544 this.latitude
= latitude
;
547 public Integer
getErrorRadius(){
548 return this.errorRadius
;
553 * @param errorRadius errorRadius
555 public void setErrorRadius(Integer errorRadius
){
556 this.errorRadius
= errorRadius
;
559 // **************** toString *************************/
563 * Returns a string representation in sexagesimal coordinates.
566 public String
toSexagesimalString(boolean includeEmptySeconds
, boolean includeReferenceSystem
){
568 result
+= getLatitudeSexagesimal() == null ?
"" : getLatitudeSexagesimal().toString(includeEmptySeconds
);
569 result
= CdmUtils
.concat(", ", result
, getLongitudeSexagesimal() == null ?
"" : getLongitudeSexagesimal().toString(includeEmptySeconds
));
570 if (includeReferenceSystem
&& getReferenceSystem() != null){
571 String refSys
= CdmUtils
.isEmpty(getReferenceSystem().getLabel()) ?
"" : "(" + getReferenceSystem().getLabel() + ")";
572 result
= CdmUtils
.concat(" ", result
, refSys
);
578 //*********** CLONE **********************************/
581 * Clones <i>this</i> point. This is a shortcut that enables to
582 * create a new instance that differs only slightly from <i>this</i> point
583 * by modifying only some of the attributes.<BR>
584 * This method overrides the clone method from {@link DerivedUnitBase DerivedUnitBase}.
586 * @see java.lang.Object#clone()
589 public Point
clone(){
591 Point result
= (Point
)super.clone();
592 result
.setReferenceSystem(this.referenceSystem
);
593 //no changes to: errorRadius, latitude, longitude
595 } catch (CloneNotSupportedException e
) {
596 logger
.warn("Object does not implement cloneable");