root/trunk/cdmlib/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/location/Point.java

Revision 13258, 17.9 kB (checked in by a.mueller, 7 months ago)

bugfix for Coordinate Parser with whitespaces, Dateparsing, etc.

  • Property svn:keywords set to Id
Line 
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
10package eu.etaxonomy.cdm.model.location;
11
12import java.io.Serializable;
13import java.math.BigDecimal;
14import java.math.MathContext;
15import java.math.RoundingMode;
16import java.text.ParseException;
17import java.util.regex.Matcher;
18import java.util.regex.Pattern;
19
20import javax.persistence.Embeddable;
21import javax.persistence.FetchType;
22import javax.persistence.ManyToOne;
23import javax.persistence.Transient;
24import javax.xml.bind.annotation.XmlAccessType;
25import javax.xml.bind.annotation.XmlAccessorType;
26import javax.xml.bind.annotation.XmlElement;
27import javax.xml.bind.annotation.XmlIDREF;
28import javax.xml.bind.annotation.XmlRootElement;
29import javax.xml.bind.annotation.XmlSchemaType;
30import javax.xml.bind.annotation.XmlType;
31
32import org.apache.log4j.Logger;
33
34import eu.etaxonomy.cdm.common.CdmUtils;
35import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase;
36import eu.etaxonomy.cdm.strategy.parser.location.CoordinateConverter;
37import 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
53public 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]*)\u00B0";
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, true);
315                }
316               
317                public static Sexagesimal valueOf(Double decimal, boolean isLatitude, boolean nullSecondsToNull, boolean nullMinutesToNull, boolean allowTertiers){
318                        if(decimal == null){
319                                return null;
320                        }
321                        Sexagesimal sexagesimal = new Sexagesimal(); 
322                        Double decimalDegree = decimal;
323                        if (isLatitude) {
324                                if (decimalDegree < 0) {
325                                sexagesimal.direction = Direction.SOUTH;
326                            }
327                            else {
328                                sexagesimal.direction = Direction.NORTH;
329                            }
330                        }
331                        else {
332                                if (decimalDegree < 0) {
333                                       sexagesimal.direction = Direction.WEST;
334                                    }
335                                    else {
336                                        sexagesimal.direction = Direction.EAST;
337                                    }
338                        }
339                 
340                        // Decimal in \u00B0'" umrechnen
341                        double d = Math.abs(decimalDegree);
342                        if (! allowTertiers){
343                                d += HALF_SECOND; // add half a second for rounding
344                        }else{
345                                d += HALF_SECOND / 10000;  //to avoid rounding errors
346                        }
347                        sexagesimal.degree = (int) Math.floor(d);
348                        sexagesimal.minutes = (int) Math.floor((d - sexagesimal.degree) * 60.0);
349                        sexagesimal.seconds = (int) Math.floor((d - sexagesimal.degree - sexagesimal.minutes / 60.0) * 3600.0);
350                        sexagesimal.tertiers = (d - sexagesimal.degree - sexagesimal.minutes / 60.0 - sexagesimal.seconds / 3600.0) * 3600.0;
351                       
352                        if (sexagesimal.seconds == 0 && nullSecondsToNull){
353                                sexagesimal.seconds = null;
354                        }
355                        if (sexagesimal.seconds == null && sexagesimal.minutes == 0 && nullMinutesToNull){
356                                sexagesimal.minutes = null;
357                        }
358                       
359                       // sexagesimal.decimalRadian = Math.toRadians(this.decimalDegree);
360                        return sexagesimal;
361                }
362
363               
364               
365                private Double toDecimal(){
366                        BigDecimal value = BigDecimal.valueOf(CdmUtils.Nz(this.seconds)).divide(SIXTY, MC).add
367                                (BigDecimal.valueOf(CdmUtils.Nz(this.minutes))).divide(SIXTY, MC).add
368                                (BigDecimal.valueOf(CdmUtils.Nz(this.degree)));
369
370                if (this.direction == Direction.WEST || this.direction == Direction.SOUTH) {
371                    value = value.negate( );
372                }
373                return value.doubleValue( );
374                }
375
376                @Override
377                public String toString(){
378                        return toString(false, false);
379                }
380                public String toString(boolean includeEmptySeconds){
381                        return toString(includeEmptySeconds, false);
382                }
383               
384                public String toString(boolean includeEmptySeconds, boolean removeTertiers){
385                        String result;
386                        result = String.valueOf(CdmUtils.Nz(degree)) + "\u00B0";
387                        if (seconds != null || minutes != null){
388                                result += String.valueOf(CdmUtils.Nz(minutes)) + "'";
389                        }
390                        if (seconds != null ){
391                                if (seconds != 0 || includeEmptySeconds){
392                                        result += String.valueOf(CdmUtils.Nz(seconds)) + getTertiersString(tertiers, removeTertiers) + "\"";
393                                }
394                        }
395                        result += direction; 
396                        return result;
397                }
398                private String getTertiersString(Double tertiers, boolean removeTertiers) {
399                        if (tertiers == null || removeTertiers){
400                                return "";
401                        }else{
402                                String result = String.valueOf(tertiers);
403                                if (result.length() > 5){
404                                        result = result.substring(0, 5);
405                                }
406                                while (result.endsWith("0")){
407                                        result = result.substring(0, result.length() -1);
408                                }
409                                return result.substring(1);
410                        }
411                       
412                }
413               
414        }
415       
416       
417        @Transient
418        public Sexagesimal getLongitudeSexagesimal (){
419                boolean isLatitude = false;
420                return Sexagesimal.valueOf(longitude, isLatitude);
421        }
422
423        @Transient
424        public Sexagesimal getLatitudeSexagesimal (){
425                boolean isLatitude = true;
426                return Sexagesimal.valueOf(latitude, isLatitude);
427        }
428       
429        @Transient
430        public void setLatitudeSexagesimal(Sexagesimal sexagesimalLatitude){
431                this.latitude = sexagesimalLatitude.toDecimal();
432        }
433        @Transient
434        public void setLongitudeSexagesimal(Sexagesimal sexagesimalLongitude){
435                this.longitude = sexagesimalLongitude.toDecimal();
436        }
437       
438        @Transient
439        public void setLatitudeByParsing(String string) throws ParseException{
440                this.setLatitude(parseLatitude(string));
441        }
442       
443        @Transient
444        public void setLongitudeByParsing(String string) throws ParseException{
445                this.setLongitude(parseLongitude(string));
446        }
447       
448       
449        public static Double parseLatitude(String string) throws ParseException{
450                try{
451                        if (string == null){
452                                return null;
453                        }
454                        string = setCurrentDoubleSeparator(string);
455                        if (isDouble(string)){
456                                Double result = Double.valueOf(string);
457                                if (Math.abs(result) > 90.0){
458                                        throw new ParseException("Latitude could not be parsed", 0);
459                                }
460                                return result;
461                        }else{
462                                CoordinateConverter converter = new CoordinateConverter();
463                                ConversionResults result = converter.tryConvert(string);
464                                if (! result.conversionSuccessful || (result.isLongitude != null  && result.isLongitude)  ){
465                                        throw new ParseException("Latitude could not be parsed", 0);
466                                }else{
467                                        return result.convertedCoord;
468                                }
469                        }
470                } catch (Exception e) {
471                        String message = "Latitude %s could not be parsed";
472                        message = String.format(message, string);
473                        throw new ParseException(message, 0);
474                }
475        }
476       
477        public static Double parseLongitude(String string) throws ParseException{
478                try {
479                        if (string == null){
480                                return null;
481                        }
482                        string = setCurrentDoubleSeparator(string);
483                        if (isDouble(string)){
484                                Double result = Double.valueOf(string);
485                                if (Math.abs(result) > 180.0){
486                                        throw new ParseException("Longitude could not be parsed", 0);
487                                }
488                                return result;
489                        }else{
490                                CoordinateConverter converter = new CoordinateConverter();
491                                ConversionResults result = converter.tryConvert(string);
492                                if (! result.conversionSuccessful || (result.isLongitude != null  && ! result.isLongitude)){
493                                        throw new ParseException("Longitude could not be parsed", 0);
494                                }else{
495                                        return result.convertedCoord;
496                                }
497                        }
498                } catch (Exception e) {
499                        String message = "Longitude %s could not be parsed";
500                        message = String.format(message, string);
501                        throw new ParseException(message, 0);
502                }
503        }
504
505        private static String setCurrentDoubleSeparator(String string) {
506        String regExReplaceComma = "(\\,|\\.)";
507        string = string.replaceAll(regExReplaceComma,".");
508        return string;
509 
510        }
511
512        private static boolean isDouble(String string) {
513                try {
514                        Double.valueOf(string);
515                        return true;
516
517                } catch (NumberFormatException e) {
518                        return false;
519                } catch (Exception e) {
520                        return false;
521                }
522
523        }
524
525// ******************** GETTER / SETTER ********************************       
526       
527        public ReferenceSystem getReferenceSystem(){
528                return this.referenceSystem;
529        }
530
531        /**
532         *
533         * @param referenceSystem    referenceSystem
534         */
535        public void setReferenceSystem(ReferenceSystem referenceSystem){
536                this.referenceSystem = referenceSystem;
537        }
538
539        public Double getLongitude(){
540                return this.longitude;
541        }
542
543        /**
544         *
545         * @param longitude    longitude
546         */
547        public void setLongitude(Double longitude){
548                this.longitude = longitude;
549        }
550
551        public Double getLatitude(){
552                return this.latitude;
553        }
554
555        /**
556         *
557         * @param latitude    latitude
558         */
559        public void setLatitude(Double latitude){
560                this.latitude = latitude;
561        }
562
563        public Integer getErrorRadius(){
564                return this.errorRadius;
565        }
566
567        /**
568         *
569         * @param errorRadius    errorRadius
570         */
571        public void setErrorRadius(Integer errorRadius){
572                this.errorRadius = errorRadius;
573        }
574       
575// **************** toString *************************/
576       
577       
578        /**
579         * Returns a string representation in sexagesimal coordinates.
580         * @return
581         */
582        public String toSexagesimalString(boolean includeEmptySeconds, boolean includeReferenceSystem){
583                String result = "";
584                result += getLatitudeSexagesimal() == null ? "" : getLatitudeSexagesimal().toString(includeEmptySeconds);
585                result = CdmUtils.concat(", ", result, getLongitudeSexagesimal() == null ? "" : getLongitudeSexagesimal().toString(includeEmptySeconds));
586                if (includeReferenceSystem && getReferenceSystem() != null){
587                        String refSys = CdmUtils.isEmpty(getReferenceSystem().getLabel()) ? "" : "(" + getReferenceSystem().getLabel() + ")";
588                        result = CdmUtils.concat(" ", result, refSys);
589                }
590                return result;
591        }
592       
593        /* (non-Javadoc)
594         * @see java.lang.Object#toString()
595         */
596        @Override
597        public String toString(){
598                String result = "";
599                boolean includeEmptySeconds = true;
600                result += getLatitudeSexagesimal() == null ? "" : getLatitudeSexagesimal().toString(includeEmptySeconds);
601                result = CdmUtils.concat(", ", result, getLongitudeSexagesimal() == null ? "" : getLongitudeSexagesimal().toString(includeEmptySeconds));
602                return result;
603        }
604       
605       
606//*********** CLONE **********************************/
607       
608        /**
609         * Clones <i>this</i> point. This is a shortcut that enables to
610         * create a new instance that differs only slightly from <i>this</i> point
611         * by modifying only some of the attributes.<BR>
612         * This method overrides the clone method from {@link DerivedUnitBase DerivedUnitBase}.
613         *
614         * @see java.lang.Object#clone()
615         */
616        @Override
617        public Point clone(){
618                try{
619                        Point result = (Point)super.clone();
620                        result.setReferenceSystem(this.referenceSystem);
621                        //no changes to: errorRadius, latitude, longitude
622                        return result;
623                } catch (CloneNotSupportedException e) {
624                        logger.warn("Object does not implement cloneable");
625                        e.printStackTrace();
626                        return null;
627                }
628        }
629
630
631}
Note: See TracBrowser for help on using the browser.