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