removed abstract method getGatheringEvent which was not implemented in DerivedUnitBase
[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 eu.etaxonomy.cdm.common.CdmUtils;
35 import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;
36 import eu.etaxonomy.cdm.strategy.parser.location.CoordinateConverter;
37 import eu.etaxonomy.cdm.strategy.parser.location.CoordinateConverter.ConversionResults;
38
39 /**
40 * @author m.doering
41 * @version 1.0
42 * @created 08-Nov-2007 13:06:44
43 */
44 @XmlAccessorType(XmlAccessType.FIELD)
45 @XmlType(name = "Point", propOrder = {
46 "longitude",
47 "latitude",
48 "errorRadius",
49 "referenceSystem"
50 })
51 @XmlRootElement(name = "Point")
52 @Embeddable
53 public class Point implements Cloneable, Serializable {
54 private static final long serialVersionUID = 531030660792800636L;
55 private static final Logger logger = Logger.getLogger(Point.class);
56
57 //TODO was Float but H2 threw errors
58 @XmlElement(name = "Longitude")
59 private Double longitude;
60
61 @XmlElement(name = "Latitude")
62 private Double latitude;
63
64 //in Meters
65 @XmlElement(name = "ErrorRadius")
66 private Integer errorRadius = 0;
67
68 @XmlElement(name = "ReferenceSystem")
69 @XmlIDREF
70 @XmlSchemaType(name = "IDREF")
71 @ManyToOne(fetch = FetchType.LAZY)
72 private ReferenceSystem referenceSystem;
73
74
75 //******************** FACTORY METHODS ****************************
76
77 /**
78 * Factory method
79 * @return
80 */
81 public static Point NewInstance(){
82 return new Point();
83 }
84
85 /**
86 * Factory method
87 * @return
88 */
89 public static Point NewInstance(Double longitude, Double latitude, ReferenceSystem referenceSystem, Integer errorRadius){
90 Point result = new Point();
91 result.setLongitude(longitude);
92 result.setLatitude(latitude);
93 result.setReferenceSystem(referenceSystem);
94 result.setErrorRadius(errorRadius);
95 return result;
96 }
97
98 // ******************** CONSTRUCTOR ***************************
99
100 /**
101 * Constructor
102 */
103 public Point() {
104 }
105
106 //************** Sexagesimal /decimal METHODS *******************
107
108 public enum Direction {
109 WEST {
110
111 @Override
112 public String toString() {
113 return "W";
114 }
115 },
116 EAST {
117
118 @Override
119 public String toString() {
120 return "E";
121 }
122 },
123 NORTH {
124
125 @Override
126 public String toString() {
127 return "N";
128 }
129 },
130 SOUTH {
131
132 @Override
133 public String toString() {
134 return "S";
135 }
136 };
137 }
138
139 public static final class CoordinateParser {
140
141 /**
142 * Pattern zum parsen von Sexagesimalen Grad: 145°
143 */
144 private static final String DEGREE_REGEX = "([0-9]*)°";
145 /**
146 * Pattern zum parsen von Sexagesimalen Minuten: 65'
147 */
148 private static final String MINUTES_REGEX = "(?:([0-9]*)')?";
149 /**
150 * Pattern zum parsen von Sexagesimalen Sekunden: 17"
151 */
152 private static final String SECONDS_REGEX = "(?:([0-9]*)(?:''|\"))?";
153 /**
154 * Himmelsrichtung Längengrad
155 */
156 private static final String LONGITUDE_DIRECTION_REGEX = "([OEW])";
157 /**
158 * Himmelsrichtung Breitengrad
159 */
160 private static final String LATITUDE_DIRECTION_REGEX = "([NS])";
161
162 /**
163 * Pattern zum Parsen von Breitengraden.
164 */
165 private static final Pattern LATITUDE_PATTERN = Pattern
166 .compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX
167 + LATITUDE_DIRECTION_REGEX);
168
169 /**
170 * Pattern zum Parsen von Längengraden.
171 */
172 private static final Pattern LONGITUDE_PATTERN = Pattern
173 .compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX
174 + LONGITUDE_DIRECTION_REGEX);
175
176 private CoordinateParser() {
177 throw new AssertionError( );
178 }
179
180 /**
181 * Parst einen Breitengrad der Form<br>
182 * G°M'S""(OEW)<br>
183 * Die Formen<br>
184 * G°(OEW)<br>
185 * G°M'(OEW)<br>
186 * sind ebenfalls erlaubt.
187 *
188 * @param strg
189 * @return Die geparsten Koordinaten
190 * @throws ParseException
191 * Wenn eine Fehler beim Parsen aufgetreten ist.
192 */
193 public static Sexagesimal parseLatitude(final String strg)
194 throws ParseException {
195 return parseCoordinates(strg, LATITUDE_PATTERN);
196 }
197
198 /**
199 * Parst einen Längengrad der Form<br>
200 * G°M'S"(NS)<br>
201 * Die Formen<br>
202 * G°(NS)<br>
203 * G°M'(NS)<br>
204 * sind ebenfalls erlaubt.
205 *
206 * @param strg
207 * @return Die geparsten Koordinaten
208 * @throws ParseException
209 * Wenn eine Fehler beim Parsen aufgetreten ist.
210 */
211 public static Sexagesimal parseLongitude(final String strg)
212 throws ParseException {
213 return parseCoordinates(strg, LONGITUDE_PATTERN);
214 }
215
216
217 /**
218 * Not used at the moment. Use CoordinateConverter instead.
219 * @param strg
220 * @param pattern
221 * @return
222 * @throws ParseException
223 */
224 private static Sexagesimal parseCoordinates(final String strg, final Pattern pattern) throws ParseException {
225 if (strg == null) {
226 throw new java.text.ParseException("Keine Koordinaten gegeben.", -1);
227 }
228 final Matcher matcher = pattern.matcher(strg);
229 if (matcher.matches( )) {
230 if (matcher.groupCount( ) == 4) {
231 // Grad
232 String tmp = matcher.group(1);
233 int degree = Integer.parseInt(tmp);
234
235 // Optional minutes
236 tmp = matcher.group(2);
237 int minutes = Sexagesimal.NONE;
238 if (tmp != null) {
239 minutes = Integer.parseInt(tmp);
240 }
241
242 // Optional seconds
243 tmp = matcher.group(3);
244 int seconds = Sexagesimal.NONE;
245 if (tmp != null) {
246 seconds = Integer.parseInt(tmp);
247 }
248
249 // directions
250 tmp = matcher.group(4);
251 final Direction direction;
252 if (tmp.equals("N")) {
253 direction = Direction.NORTH;
254 }
255 else if (tmp.equals("S")) {
256 direction = Direction.SOUTH;
257 }
258 else if (tmp.equals("E") || tmp.equals("O")) {
259 direction = Direction.EAST;
260 }
261 else if (tmp.equals("W")) {
262 direction = Direction.WEST;
263 }
264 else {
265 direction = null;
266 }
267 return Sexagesimal.NewInstance(degree, minutes, seconds, direction);
268 }
269 else {
270 throw new java.text.ParseException(
271 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg,
272 -1);
273 }
274 }
275 else {
276 throw new java.text.ParseException(
277 "Die Koordinaten-Darstellung ist fehlerhaft: " + strg, -1);
278 }
279 }
280
281 }
282
283
284 private static final BigDecimal SIXTY = BigDecimal.valueOf(60.0);
285 private static final MathContext MC = new MathContext(34, RoundingMode.HALF_UP);
286 private static final double HALF_SECOND = 1. / 7200.;
287
288 //see http://www.tutorials.de/forum/archiv/348596-quiz-10-zeja-java.html
289 public static class Sexagesimal{
290 public static Sexagesimal NewInstance(Integer degree, Integer minutes, Integer seconds, Direction direction){
291 Sexagesimal result = new Sexagesimal();
292 result.degree = degree; result.minutes = minutes; result.seconds = seconds;
293 return result;
294 }
295
296 public static final int NONE = 0;
297 public Integer degree;
298 public Integer minutes;
299 public Integer seconds;
300 // public Double tertiers;
301
302 public Direction direction;
303
304
305 public boolean isLatitude(){
306 return (direction == Direction.WEST) || (direction == Direction.EAST) ;
307 }
308 public boolean isLongitude(){
309 return ! isLatitude();
310 }
311
312
313 public static Sexagesimal valueOf(Double decimal, boolean isLatitude){
314 return valueOf(decimal, isLatitude, false, false);
315 }
316
317 public static Sexagesimal valueOf(Double decimal, boolean isLatitude, boolean nullSecondsToNull, boolean nullMinutesToNull){
318 Sexagesimal sexagesimal = new Sexagesimal();
319 Double decimalDegree = decimal;
320 if (isLatitude) {
321 if (decimalDegree < 0) {
322 sexagesimal.direction = Direction.SOUTH;
323 }
324 else {
325 sexagesimal.direction = Direction.NORTH;
326 }
327 }
328 else {
329 if (decimalDegree < 0) {
330 sexagesimal.direction = Direction.WEST;
331 }
332 else {
333 sexagesimal.direction = Direction.EAST;
334 }
335 }
336
337 // Decimal in °'" umrechnen
338 double d = Math.abs(decimalDegree);
339 d += HALF_SECOND; // add a second for rounding
340 sexagesimal.degree = (int) Math.floor(d);
341 sexagesimal.minutes = (int) Math.floor((d - sexagesimal.degree) * 60.0);
342 sexagesimal.seconds = (int) Math.floor((d - sexagesimal.degree - sexagesimal.minutes / 60.0) * 3600.0);
343
344 if (sexagesimal.seconds == 0 && nullSecondsToNull){
345 sexagesimal.seconds = null;
346 }
347 if (sexagesimal.seconds == null && nullMinutesToNull){
348 sexagesimal.minutes = null;
349 }
350
351 // sexagesimal.decimalRadian = Math.toRadians(this.decimalDegree);
352 return sexagesimal;
353 }
354
355
356
357 private Double toDecimal(){
358 BigDecimal value = BigDecimal.valueOf(CdmUtils.Nz(this.seconds)).divide(SIXTY, MC).add
359 (BigDecimal.valueOf(CdmUtils.Nz(this.minutes))).divide(SIXTY, MC).add
360 (BigDecimal.valueOf(CdmUtils.Nz(this.degree)));
361
362 if (this.direction == Direction.WEST || this.direction == Direction.SOUTH) {
363 value = value.negate( );
364 }
365 return value.doubleValue( );
366 }
367
368
369 public String toString(){
370 String result;
371 result = String.valueOf(CdmUtils.Nz(degree)) + "°";
372 if (seconds != null || minutes != null){
373 result += String.valueOf(CdmUtils.Nz(minutes)) + "'";
374 }
375 if (seconds != null){
376 result += String.valueOf(CdmUtils.Nz(seconds)) + "\"";
377 }
378 result += direction;
379 return result;
380 }
381
382 }
383
384
385 @Transient
386 public Sexagesimal getLongitudeSexagesimal (){
387 boolean isLatitude = false;
388 return Sexagesimal.valueOf(longitude, isLatitude);
389 }
390
391 @Transient
392 public Sexagesimal getLatitudeSexagesimal (){
393 boolean isLatitude = true;
394 return Sexagesimal.valueOf(longitude, isLatitude);
395 }
396
397 @Transient
398 public void setLatitudeSexagesimal(Sexagesimal sexagesimalLatitude){
399 this.latitude = sexagesimalLatitude.toDecimal();
400 }
401 @Transient
402 public void setLongitudeSexagesimal(Sexagesimal sexagesimalLongitude){
403 this.longitude = sexagesimalLongitude.toDecimal();
404 }
405
406 @Transient
407 public void setLatitudeByParsing(String string) throws ParseException{
408 this.setLatitude(parseLatitude(string));
409 }
410
411 @Transient
412 public void setLongitudeByParsing(String string) throws ParseException{
413 this.setLongitude(parseLongitude(string));
414 }
415
416
417 public static Double parseLatitude(String string) throws ParseException{
418 CoordinateConverter converter = new CoordinateConverter();
419 ConversionResults result = converter.tryConvert(string);
420 if (! result.conversionSuccessful || result.isLongitude ){
421 throw new ParseException("Latitude could not be parsed", 0);
422 }else{
423 return result.convertedCoord;
424 }
425 }
426
427 public static Double parseLongitude(String string) throws ParseException{
428 CoordinateConverter converter = new CoordinateConverter();
429 ConversionResults result = converter.tryConvert(string);
430 if (! result.conversionSuccessful || ! result.isLongitude){
431 throw new ParseException("Longitude could not be parsed", 0);
432 }else{
433 return result.convertedCoord;
434 }
435 }
436
437 // ******************** GETTER / SETTER ********************************
438
439 public ReferenceSystem getReferenceSystem(){
440 return this.referenceSystem;
441 }
442
443 /**
444 *
445 * @param referenceSystem referenceSystem
446 */
447 public void setReferenceSystem(ReferenceSystem referenceSystem){
448 this.referenceSystem = referenceSystem;
449 }
450
451 public Double getLongitude(){
452 return this.longitude;
453 }
454
455 /**
456 *
457 * @param longitude longitude
458 */
459 public void setLongitude(Double longitude){
460 this.longitude = longitude;
461 }
462
463 public Double getLatitude(){
464 return this.latitude;
465 }
466
467 /**
468 *
469 * @param latitude latitude
470 */
471 public void setLatitude(Double latitude){
472 this.latitude = latitude;
473 }
474
475 public Integer getErrorRadius(){
476 return this.errorRadius;
477 }
478
479 /**
480 *
481 * @param errorRadius errorRadius
482 */
483 public void setErrorRadius(Integer errorRadius){
484 this.errorRadius = errorRadius;
485 }
486
487 //*********** CLONE **********************************/
488
489 /**
490 * Clones <i>this</i> point. This is a shortcut that enables to
491 * create a new instance that differs only slightly from <i>this</i> point
492 * by modifying only some of the attributes.<BR>
493 * This method overrides the clone method from {@link DerivedUnitBase DerivedUnitBase}.
494 *
495 * @see java.lang.Object#clone()
496 */
497 @Override
498 public Point clone(){
499 try{
500 Point result = (Point)super.clone();
501 result.setReferenceSystem(this.referenceSystem);
502 //no changes to: errorRadius, latitude, longitude
503 return result;
504 } catch (CloneNotSupportedException e) {
505 logger.warn("Object does not implement cloneable");
506 e.printStackTrace();
507 return null;
508 }
509 }
510
511
512 }