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