autoinitialization of termbase.references and point.exactlocation (indirectly)
[cdmlib.git] / cdmlib-model / src / main / java / eu / etaxonomy / cdm / model / location / Point.java
1 /**
2 * Copyright (C) 2007 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9
10 package eu.etaxonomy.cdm.model.location;
11
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;
19
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;
31
32 import org.apache.commons.lang.StringUtils;
33 import org.apache.log4j.Logger;
34
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;
39
40 /**
41 * @author m.doering
42 * @version 1.0
43 * @created 08-Nov-2007 13:06:44
44 */
45 @XmlAccessorType(XmlAccessType.FIELD)
46 @XmlType(name = "Point", propOrder = {
47 "longitude",
48 "latitude",
49 "errorRadius",
50 "referenceSystem"
51 })
52 @XmlRootElement(name = "Point")
53 @Embeddable
54 public class Point implements Cloneable, Serializable {
55 private static final long serialVersionUID = 531030660792800636L;
56 private static final Logger logger = Logger.getLogger(Point.class);
57
58 //TODO was Float but H2 threw errors
59 @XmlElement(name = "Longitude")
60 private Double longitude;
61
62 @XmlElement(name = "Latitude")
63 private Double latitude;
64
65 /**
66 * Error radius in Meters
67 */
68 @XmlElement(name = "ErrorRadius")
69 private Integer errorRadius = 0;
70
71 @XmlElement(name = "ReferenceSystem")
72 @XmlIDREF
73 @XmlSchemaType(name = "IDREF")
74 @ManyToOne(fetch = FetchType.LAZY)
75 private ReferenceSystem referenceSystem;
76
77
78 //******************** FACTORY METHODS ****************************
79
80 /**
81 * Factory method
82 * @return
83 */
84 public static Point NewInstance(){
85 return new Point();
86 }
87
88 /**
89 * Factory method
90 * @return
91 */
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);
98 return result;
99 }
100
101 // ******************** CONSTRUCTOR ***************************
102
103 /**
104 * Constructor
105 */
106 public Point() {
107 }
108
109 //************** Sexagesimal /decimal METHODS *******************
110
111 public enum Direction {
112 WEST {
113
114 @Override
115 public String toString() {
116 return "W";
117 }
118 },
119 EAST {
120
121 @Override
122 public String toString() {
123 return "E";
124 }
125 },
126 NORTH {
127
128 @Override
129 public String toString() {
130 return "N";
131 }
132 },
133 SOUTH {
134
135 @Override
136 public String toString() {
137 return "S";
138 }
139 };
140 }
141
142 public static final class CoordinateParser {
143
144 /**
145 * Pattern zum parsen von Sexagesimalen Grad: 145°
146 */
147 private static final String DEGREE_REGEX = "([0-9]*)\u00B0";
148 /**
149 * Pattern zum parsen von Sexagesimalen Minuten: 65'
150 */
151 private static final String MINUTES_REGEX = "(?:([0-9]*)')?";
152 /**
153 * Pattern zum parsen von Sexagesimalen Sekunden: 17"
154 */
155 private static final String SECONDS_REGEX = "(?:([0-9]*)(?:''|\"))?";
156 /**
157 * Himmelsrichtung Längengrad
158 */
159 private static final String LONGITUDE_DIRECTION_REGEX = "([OEW])";
160 /**
161 * Himmelsrichtung Breitengrad
162 */
163 private static final String LATITUDE_DIRECTION_REGEX = "([NS])";
164
165 /**
166 * Pattern zum Parsen von Breitengraden.
167 */
168 private static final Pattern LATITUDE_PATTERN = Pattern
169 .compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX
170 + LATITUDE_DIRECTION_REGEX);
171
172 /**
173 * Pattern zum Parsen von Längengraden.
174 */
175 private static final Pattern LONGITUDE_PATTERN = Pattern
176 .compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX
177 + LONGITUDE_DIRECTION_REGEX);
178
179 private CoordinateParser() {
180 throw new AssertionError( );
181 }
182
183 /**
184 * Parst einen Breitengrad der Form<br>
185 * G°M'S""(OEW)<br>
186 * Die Formen<br>
187 * G°(OEW)<br>
188 * G°M'(OEW)<br>
189 * sind ebenfalls erlaubt.
190 *
191 * @param strg
192 * @return Die geparsten Koordinaten
193 * @throws ParseException
194 * Wenn eine Fehler beim Parsen aufgetreten ist.
195 */
196 public static Sexagesimal parseLatitude(final String strg)
197 throws ParseException {
198 return parseCoordinates(strg, LATITUDE_PATTERN);
199 }
200
201 /**
202 * Parst einen Längengrad der Form<br>
203 * G°M'S"(NS)<br>
204 * Die Formen<br>
205 * G°(NS)<br>
206 * G°M'(NS)<br>
207 * sind ebenfalls erlaubt.
208 *
209 * @param strg
210 * @return Die geparsten Koordinaten
211 * @throws ParseException
212 * Wenn eine Fehler beim Parsen aufgetreten ist.
213 */
214 public static Sexagesimal parseLongitude(final String strg)
215 throws ParseException {
216 return parseCoordinates(strg, LONGITUDE_PATTERN);
217 }
218
219
220 /**
221 * Not used at the moment. Use CoordinateConverter instead.
222 * @param strg
223 * @param pattern
224 * @return
225 * @throws ParseException
226 */
227 private static Sexagesimal parseCoordinates(final String strg, final Pattern pattern) throws ParseException {
228 if (strg == null) {
229 throw new java.text.ParseException("Keine Koordinaten gegeben.", -1);
230 }
231 final Matcher matcher = pattern.matcher(strg);
232 if (matcher.matches( )) {
233 if (matcher.groupCount( ) == 4) {
234 // Grad
235 String tmp = matcher.group(1);
236 int degree = Integer.parseInt(tmp);
237
238 // Optional minutes
239 tmp = matcher.group(2);
240 int minutes = Sexagesimal.NONE;
241 if (tmp != null) {
242 minutes = Integer.parseInt(tmp);
243 }
244
245 // Optional seconds
246 tmp = matcher.group(3);
247 int seconds = Sexagesimal.NONE;
248 if (tmp != null) {
249 seconds = Integer.parseInt(tmp);
250 }
251
252 // directions
253 tmp = matcher.group(4);
254 final Direction direction;
255 if (tmp.equals("N")) {
256 direction = Direction.NORTH;
257 }
258 else if (tmp.equals("S")) {
259 direction = Direction.SOUTH;
260 }
261 else if (tmp.equals("E") || tmp.equals("O")) {
262 direction = Direction.EAST;
263 }
264 else if (tmp.equals("W")) {
265 direction = Direction.WEST;
266 }
267 else {
268 direction = null;
269 }
270 return Sexagesimal.NewInstance(degree, minutes, seconds, direction);
271 }
272 else {
273 throw new java.text.ParseException(
274 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg,
275 -1);
276 }
277 }
278 else {
279 throw new java.text.ParseException(
280 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg, -1);
281 }
282 }
283
284 }
285
286
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.;
290
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;
296 return result;
297 }
298
299 public static final int NONE = 0;
300 public Integer degree;
301 public Integer minutes;
302 public Integer seconds;
303 public Double tertiers;
304
305 public Direction direction;
306
307
308 public boolean isLatitude(){
309 return (direction == Direction.WEST) || (direction == Direction.EAST) ;
310 }
311 public boolean isLongitude(){
312 return ! isLatitude();
313 }
314
315
316 public static Sexagesimal valueOf(Double decimal, boolean isLatitude){
317 return valueOf(decimal, isLatitude, false, false, true);
318 }
319
320 public static Sexagesimal valueOf(Double decimal, boolean isLatitude, boolean nullSecondsToNull, boolean nullMinutesToNull, boolean allowTertiers){
321 if(decimal == null){
322 return null;
323 }
324 Sexagesimal sexagesimal = new Sexagesimal();
325 Double decimalDegree = decimal;
326 if (isLatitude) {
327 if (decimalDegree < 0) {
328 sexagesimal.direction = Direction.SOUTH;
329 }
330 else {
331 sexagesimal.direction = Direction.NORTH;
332 }
333 }
334 else {
335 if (decimalDegree < 0) {
336 sexagesimal.direction = Direction.WEST;
337 }
338 else {
339 sexagesimal.direction = Direction.EAST;
340 }
341 }
342
343 // Decimal in \u00B0'" umrechnen
344 double d = Math.abs(decimalDegree);
345 if (! allowTertiers){
346 d += HALF_SECOND; // add half a second for rounding
347 }else{
348 d += HALF_SECOND / 10000; //to avoid rounding errors
349 }
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;
354
355 if (sexagesimal.seconds == 0 && nullSecondsToNull){
356 sexagesimal.seconds = null;
357 }
358 if (sexagesimal.seconds == null && sexagesimal.minutes == 0 && nullMinutesToNull){
359 sexagesimal.minutes = null;
360 }
361
362 // sexagesimal.decimalRadian = Math.toRadians(this.decimalDegree);
363 return sexagesimal;
364 }
365
366
367
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)));
372
373 if (this.direction == Direction.WEST || this.direction == Direction.SOUTH) {
374 value = value.negate( );
375 }
376 return value.doubleValue( );
377 }
378
379 @Override
380 public String toString(){
381 return toString(false, false);
382 }
383 public String toString(boolean includeEmptySeconds){
384 return toString(includeEmptySeconds, false);
385 }
386
387 public String toString(boolean includeEmptySeconds, boolean removeTertiers){
388 String result;
389 result = String.valueOf(CdmUtils.Nz(degree)) + "\u00B0";
390 if (seconds != null || minutes != null){
391 result += String.valueOf(CdmUtils.Nz(minutes)) + "'";
392 }
393 if (seconds != null ){
394 if (seconds != 0 || includeEmptySeconds){
395 result += String.valueOf(CdmUtils.Nz(seconds)) + getTertiersString(tertiers, removeTertiers) + "\"";
396 }
397 }
398 result += direction;
399 return result;
400 }
401 private String getTertiersString(Double tertiers, boolean removeTertiers) {
402 if (tertiers == null || removeTertiers){
403 return "";
404 }else{
405 if (tertiers >= 1.0 || tertiers < 0.0){
406 throw new IllegalStateException("Tertiers should be 0.0 <= tertiers < 1.0 but are '" + tertiers + "'");
407 }
408 String result = tertiers.toString();
409 int pos = result.indexOf("E");
410 if (pos > -1){
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;
414
415 }
416
417 if (result.length() > 5){
418 result = result.substring(0, 5);
419 }
420 while (result.endsWith("0")){
421 result = result.substring(0, result.length() -1);
422 }
423 result = result.substring(1);
424 if (result.equals(".")){
425 result = "";
426 }
427 return result;
428 }
429
430 }
431
432 }
433
434
435 @Transient
436 public Sexagesimal getLongitudeSexagesimal (){
437 boolean isLatitude = false;
438 return Sexagesimal.valueOf(longitude, isLatitude);
439 }
440
441 @Transient
442 public Sexagesimal getLatitudeSexagesimal (){
443 boolean isLatitude = true;
444 return Sexagesimal.valueOf(latitude, isLatitude);
445 }
446
447 @Transient
448 public void setLatitudeSexagesimal(Sexagesimal sexagesimalLatitude){
449 this.latitude = sexagesimalLatitude.toDecimal();
450 }
451 @Transient
452 public void setLongitudeSexagesimal(Sexagesimal sexagesimalLongitude){
453 this.longitude = sexagesimalLongitude.toDecimal();
454 }
455
456 @Transient
457 public void setLatitudeByParsing(String string) throws ParseException{
458 this.setLatitude(parseLatitude(string));
459 }
460
461 @Transient
462 public void setLongitudeByParsing(String string) throws ParseException{
463 this.setLongitude(parseLongitude(string));
464 }
465
466
467 public static Double parseLatitude(String string) throws ParseException{
468 try{
469 if (string == null){
470 return null;
471 }
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);
477 }
478 return result;
479 }else{
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);
484 }else{
485 return result.convertedCoord;
486 }
487 }
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);
492 }
493 }
494
495 public static Double parseLongitude(String string) throws ParseException{
496 try {
497 if (string == null){
498 return null;
499 }
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);
505 }
506 return result;
507 }else{
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);
512 }else{
513 return result.convertedCoord;
514 }
515 }
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);
520 }
521 }
522
523 private static String setCurrentDoubleSeparator(String string) {
524 String regExReplaceComma = "(\\,|\\.)";
525 string = string.replaceAll(regExReplaceComma,".");
526 return string;
527
528 }
529
530 private static boolean isDouble(String string) {
531 try {
532 Double.valueOf(string);
533 return true;
534
535 } catch (NumberFormatException e) {
536 return false;
537 } catch (Exception e) {
538 return false;
539 }
540
541 }
542
543 // ******************** GETTER / SETTER ********************************
544
545 public ReferenceSystem getReferenceSystem(){
546 return this.referenceSystem;
547 }
548
549 /**
550 *
551 * @param referenceSystem referenceSystem
552 */
553 public void setReferenceSystem(ReferenceSystem referenceSystem){
554 this.referenceSystem = referenceSystem;
555 }
556
557 public Double getLongitude(){
558 return this.longitude;
559 }
560
561 /**
562 *
563 * @param longitude longitude
564 */
565 public void setLongitude(Double longitude){
566 this.longitude = longitude;
567 }
568
569 public Double getLatitude(){
570 return this.latitude;
571 }
572
573 /**
574 *
575 * @param latitude latitude
576 */
577 public void setLatitude(Double latitude){
578 this.latitude = latitude;
579 }
580
581 /**
582 * Error radius in Meters
583 */
584 public Integer getErrorRadius(){
585 return this.errorRadius;
586 }
587
588 /**
589 *
590 * @param errorRadius errorRadius
591 */
592 public void setErrorRadius(Integer errorRadius){
593 this.errorRadius = errorRadius;
594 }
595
596 // **************** toString *************************/
597
598
599 /**
600 * Returns a string representation in sexagesimal coordinates.
601 * @return
602 */
603 public String toSexagesimalString(boolean includeEmptySeconds, boolean includeReferenceSystem){
604 String result = "";
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);
610 }
611 return result;
612 }
613
614 /* (non-Javadoc)
615 * @see java.lang.Object#toString()
616 */
617 @Override
618 public String toString(){
619 String result = "";
620 boolean includeEmptySeconds = true;
621 result += getLatitudeSexagesimal() == null ? "" : getLatitudeSexagesimal().toString(includeEmptySeconds);
622 result = CdmUtils.concat(", ", result, getLongitudeSexagesimal() == null ? "" : getLongitudeSexagesimal().toString(includeEmptySeconds));
623 return result;
624 }
625
626
627 //*********** CLONE **********************************/
628
629 /**
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}.
634 *
635 * @see java.lang.Object#clone()
636 */
637 @Override
638 public Point clone(){
639 try{
640 Point result = (Point)super.clone();
641 result.setReferenceSystem(this.referenceSystem);
642 //no changes to: errorRadius, latitude, longitude
643 return result;
644 } catch (CloneNotSupportedException e) {
645 logger.warn("Object does not implement cloneable");
646 e.printStackTrace();
647 return null;
648 }
649 }
650
651
652 }