Project

General

Profile

Download (21.6 KB) Statistics
| Branch: | Tag: | Revision:
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.validation.constraints.NotNull;
25
import javax.xml.bind.annotation.XmlAccessType;
26
import javax.xml.bind.annotation.XmlAccessorType;
27
import javax.xml.bind.annotation.XmlElement;
28
import javax.xml.bind.annotation.XmlIDREF;
29
import javax.xml.bind.annotation.XmlRootElement;
30
import javax.xml.bind.annotation.XmlSchemaType;
31
import javax.xml.bind.annotation.XmlType;
32

    
33
import org.apache.commons.lang.StringUtils;
34
import org.apache.log4j.Logger;
35
import org.hibernate.search.annotations.Field;
36
import org.hibernate.search.annotations.Latitude;
37
import org.hibernate.search.annotations.Longitude;
38
import org.hibernate.search.annotations.NumericField;
39
import org.hibernate.search.annotations.Spatial;
40
import org.hibernate.search.annotations.SpatialMode;
41

    
42
import eu.etaxonomy.cdm.common.CdmUtils;
43
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
44
import eu.etaxonomy.cdm.strategy.parser.location.CoordinateConverter;
45
import eu.etaxonomy.cdm.strategy.parser.location.CoordinateConverter.ConversionResults;
46
import eu.etaxonomy.cdm.validation.Level2;
47

    
48
/**
49
 * @author m.doering
50
 * @version 1.0
51
 * @created 08-Nov-2007 13:06:44
52
 */
53
@XmlAccessorType(XmlAccessType.FIELD)
54
@XmlType(name = "Point", propOrder = {
55
    "longitude",
56
    "latitude",
57
    "errorRadius",
58
    "referenceSystem"
59
})
60
@XmlRootElement(name = "Point")
61
@Embeddable
62
@Spatial(spatialMode=SpatialMode.RANGE, name="point")
63
public class Point implements Cloneable, Serializable {
64
    private static final long serialVersionUID = 531030660792800636L;
65
    private static final Logger logger = Logger.getLogger(Point.class);
66

    
67
    //TODO was Float but H2 threw errors
68
    @XmlElement(name = "Longitude")
69
    @Longitude(of="point")
70
    @NotNull(groups = Level2.class)
71
    private Double longitude;
72

    
73
    @XmlElement(name = "Latitude")
74
    @Latitude(of="point")
75
    @NotNull(groups = Level2.class)
76
    private Double latitude;
77

    
78
    /**
79
     * Error radius in Meters
80
     */
81
    @XmlElement(name = "ErrorRadius")
82
    @Field
83
    @NumericField
84
    private Integer errorRadius = 0;
85

    
86
    @XmlElement(name = "ReferenceSystem")
87
    @XmlIDREF
88
    @XmlSchemaType(name = "IDREF")
89
    @ManyToOne(fetch = FetchType.LAZY)
90
    private ReferenceSystem referenceSystem;
91

    
92

    
93
//******************** FACTORY METHODS ****************************
94

    
95
    /**
96
     * Factory method
97
     * @return
98
     */
99
    public static Point NewInstance(){
100
        return new Point();
101
    }
102

    
103
    /**
104
     * Factory method
105
     * @return
106
     */
107
    public static Point NewInstance(Double longitude, Double latitude, ReferenceSystem referenceSystem, Integer errorRadius){
108
        Point result = new Point();
109
        result.setLongitude(longitude);
110
        result.setLatitude(latitude);
111
        result.setReferenceSystem(referenceSystem);
112
        result.setErrorRadius(errorRadius);
113
        return result;
114
    }
115

    
116
// ******************** CONSTRUCTOR ***************************
117

    
118
    /**
119
     * Constructor
120
     */
121
    public Point() {
122
    }
123

    
124
//************** Sexagesimal /decimal METHODS *******************
125

    
126
    public enum Direction {
127
        WEST {
128

    
129
            @Override
130
            public String toString() {
131
                return "W";
132
            }
133
        },
134
        EAST {
135

    
136
            @Override
137
            public String toString() {
138
                return "E";
139
            }
140
        },
141
        NORTH {
142

    
143
            @Override
144
            public String toString() {
145
                return "N";
146
            }
147
        },
148
        SOUTH {
149

    
150
            @Override
151
            public String toString() {
152
                return "S";
153
            }
154
        };
155
    }
156

    
157
    public static final class CoordinateParser {
158

    
159
        /**
160
         * Pattern zum parsen von Sexagesimalen Grad: 145°
161
         */
162
        private static final String DEGREE_REGEX = "([0-9]*)\u00B0";
163
        /**
164
         * Pattern zum parsen von Sexagesimalen Minuten: 65'
165
         */
166
        private static final String MINUTES_REGEX = "(?:([0-9]*)')?";
167
        /**
168
         * Pattern zum parsen von Sexagesimalen Sekunden: 17"
169
         */
170
        private static final String SECONDS_REGEX = "(?:([0-9]*)(?:''|\"))?";
171
        /**
172
         * Himmelsrichtung Längengrad
173
         */
174
        private static final String LONGITUDE_DIRECTION_REGEX = "([OEW])";
175
        /**
176
         * Himmelsrichtung Breitengrad
177
         */
178
        private static final String LATITUDE_DIRECTION_REGEX = "([NS])";
179

    
180
        /**
181
         * Pattern zum Parsen von Breitengraden.
182
         */
183
        private static final Pattern LATITUDE_PATTERN = Pattern
184
                .compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX
185
                        + LATITUDE_DIRECTION_REGEX);
186

    
187
        /**
188
         * Pattern zum Parsen von Längengraden.
189
         */
190
        private static final Pattern LONGITUDE_PATTERN = Pattern
191
                .compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX
192
                        + LONGITUDE_DIRECTION_REGEX);
193

    
194
        private CoordinateParser() {
195
            throw new AssertionError( );
196
        }
197

    
198
        /**
199
         * Parst einen Breitengrad der Form<br>
200
         * G°M'S""(OEW)<br>
201
         * Die Formen<br>
202
         * G°(OEW)<br>
203
         * G°M'(OEW)<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 parseLatitude(final String strg)
212
                throws ParseException {
213
            return parseCoordinates(strg, LATITUDE_PATTERN);
214
        }
215

    
216
        /**
217
         * Parst einen Längengrad der Form<br>
218
         * G°M'S"(NS)<br>
219
         * Die Formen<br>
220
         * G°(NS)<br>
221
         * G°M'(NS)<br>
222
         * sind ebenfalls erlaubt.
223
         *
224
         * @param strg
225
         * @return Die geparsten Koordinaten
226
         * @throws ParseException
227
         *             Wenn eine Fehler beim Parsen aufgetreten ist.
228
         */
229
        public static Sexagesimal parseLongitude(final String strg)
230
                throws ParseException {
231
            return parseCoordinates(strg, LONGITUDE_PATTERN);
232
        }
233

    
234

    
235
        /**
236
         * Not used at the moment. Use CoordinateConverter instead.
237
         * @param strg
238
         * @param pattern
239
         * @return
240
         * @throws ParseException
241
         */
242
        private static Sexagesimal parseCoordinates(final String strg, final Pattern pattern) throws ParseException {
243
            if (strg == null) {
244
                throw new java.text.ParseException("Keine Koordinaten gegeben.", -1);
245
            }
246
            final Matcher matcher = pattern.matcher(strg);
247
            if (matcher.matches( )) {
248
                if (matcher.groupCount( ) == 4) {
249
                    // Grad
250
                    String tmp = matcher.group(1);
251
                    int degree = Integer.parseInt(tmp);
252

    
253
                    // Optional minutes
254
                    tmp = matcher.group(2);
255
                    int minutes = Sexagesimal.NONE;
256
                    if (tmp != null) {
257
                        minutes = Integer.parseInt(tmp);
258
                    }
259

    
260
                    // Optional seconds
261
                    tmp = matcher.group(3);
262
                    int seconds = Sexagesimal.NONE;
263
                    if (tmp != null) {
264
                        seconds = Integer.parseInt(tmp);
265
                    }
266

    
267
                    // directions
268
                    tmp = matcher.group(4);
269
                    final Direction direction;
270
                    if (tmp.equals("N")) {
271
                        direction = Direction.NORTH;
272
                    }
273
                    else if (tmp.equals("S")) {
274
                        direction = Direction.SOUTH;
275
                    }
276
                    else if (tmp.equals("E") || tmp.equals("O")) {
277
                        direction = Direction.EAST;
278
                    }
279
                    else if (tmp.equals("W")) {
280
                        direction = Direction.WEST;
281
                    }
282
                    else {
283
                        direction = null;
284
                    }
285
                    return Sexagesimal.NewInstance(degree, minutes, seconds, direction);
286
                }
287
                else {
288
                    throw new java.text.ParseException(
289
                            "Die Koordinaten-Darstellung ist fehlerhaft: " + strg,
290
                            -1);
291
                }
292
            }
293
            else {
294
                throw new java.text.ParseException(
295
                        "Die Koordinaten-Darstellung ist fehlerhaft: " + strg, -1);
296
            }
297
        }
298

    
299
    }
300

    
301

    
302
    private static final BigDecimal SIXTY = BigDecimal.valueOf(60.0);
303
    private static final MathContext MC = new MathContext(34, RoundingMode.HALF_UP);
304
    private static final double HALF_SECOND = 1. / 7200.;
305

    
306
    //see http://www.tutorials.de/forum/archiv/348596-quiz-10-zeja-java.html
307
    public static class Sexagesimal{
308
        public static Sexagesimal NewInstance(Integer degree, Integer minutes, Integer seconds, Direction direction){
309
            Sexagesimal result = new Sexagesimal();
310
            result.degree = degree; result.minutes = minutes; result.seconds = seconds;
311
            return result;
312
        }
313

    
314
        public static final int NONE = 0;
315
        public Integer degree;
316
        public Integer minutes;
317
        public Integer seconds;
318
        public Double tertiers;
319

    
320
        public Direction direction;
321

    
322

    
323
        public boolean isLatitude(){
324
            return (direction == Direction.WEST) || (direction == Direction.EAST) ;
325
        }
326
        public boolean isLongitude(){
327
            return ! isLatitude();
328
        }
329

    
330

    
331
        public static Sexagesimal valueOf(Double decimal, boolean isLatitude){
332
            return valueOf(decimal, isLatitude, false, false, true);
333
        }
334

    
335
        public static Sexagesimal valueOf(Double decimal, boolean isLatitude, boolean nullSecondsToNull, boolean nullMinutesToNull, boolean allowTertiers){
336
            if(decimal == null){
337
                return null;
338
            }
339
            Sexagesimal sexagesimal = new Sexagesimal();
340
            Double decimalDegree = decimal;
341
                if (isLatitude) {
342
                    if (decimalDegree < 0) {
343
                        sexagesimal.direction = Direction.SOUTH;
344
                    }
345
                    else {
346
                        sexagesimal.direction = Direction.NORTH;
347
                    }
348
                }
349
                else {
350
                    if (decimalDegree < 0) {
351
                           sexagesimal.direction = Direction.WEST;
352
                        }
353
                        else {
354
                            sexagesimal.direction = Direction.EAST;
355
                        }
356
                }
357

    
358
                // Decimal in \u00B0'" umrechnen
359
                double d = Math.abs(decimalDegree);
360
                if (! allowTertiers){
361
                    d += HALF_SECOND; // add half a second for rounding
362
                }else{
363
                    d += HALF_SECOND / 10000;  //to avoid rounding errors
364
                }
365
                sexagesimal.degree = (int) Math.floor(d);
366
                sexagesimal.minutes = (int) Math.floor((d - sexagesimal.degree) * 60.0);
367
                sexagesimal.seconds = (int) Math.floor((d - sexagesimal.degree - sexagesimal.minutes / 60.0) * 3600.0);
368
                sexagesimal.tertiers = (d - sexagesimal.degree - sexagesimal.minutes / 60.0 - sexagesimal.seconds / 3600.0) * 3600.0;
369

    
370
                if (sexagesimal.seconds == 0 && nullSecondsToNull){
371
                    sexagesimal.seconds = null;
372
                }
373
                if (sexagesimal.seconds == null && sexagesimal.minutes == 0 && nullMinutesToNull){
374
                    sexagesimal.minutes = null;
375
                }
376

    
377
               // sexagesimal.decimalRadian = Math.toRadians(this.decimalDegree);
378
                return sexagesimal;
379
        }
380

    
381

    
382

    
383
        private Double toDecimal(){
384
            BigDecimal value = BigDecimal.valueOf(CdmUtils.Nz(this.seconds)).divide(SIXTY, MC).add
385
                (BigDecimal.valueOf(CdmUtils.Nz(this.minutes))).divide(SIXTY, MC).add
386
                (BigDecimal.valueOf(CdmUtils.Nz(this.degree)));
387

    
388
            if (this.direction == Direction.WEST || this.direction == Direction.SOUTH) {
389
                value = value.negate( );
390
            }
391
            return value.doubleValue( );
392
        }
393

    
394
        @Override
395
        public String toString(){
396
            return toString(false, false);
397
        }
398
        public String toString(boolean includeEmptySeconds){
399
            return toString(includeEmptySeconds, false);
400
        }
401

    
402
        public String toString(boolean includeEmptySeconds, boolean removeTertiers){
403
            String result;
404
            result = String.valueOf(CdmUtils.Nz(degree)) + "\u00B0";
405
            if (seconds != null || minutes != null){
406
                result += String.valueOf(CdmUtils.Nz(minutes)) + "'";
407
            }
408
            if (seconds != null ){
409
                if (seconds != 0 || includeEmptySeconds){
410
                    result += String.valueOf(CdmUtils.Nz(seconds)) + getTertiersString(tertiers, removeTertiers) + "\"";
411
                }
412
            }
413
            result += direction;
414
            return result;
415
        }
416
        private String getTertiersString(Double tertiers, boolean removeTertiers) {
417
            if (tertiers == null || removeTertiers){
418
                return "";
419
            }else{
420
                if (tertiers >= 1.0 || tertiers < 0.0){
421
                    throw new IllegalStateException("Tertiers should be 0.0 <= tertiers < 1.0 but are '" + tertiers + "'");
422
                }
423
                String result = tertiers.toString();
424
                int pos = result.indexOf("E");
425
                if (pos > -1){
426
                    int exp = - Integer.valueOf(result.substring(pos + 1));
427
                    result = result.substring(0, pos).replace(".", "");
428
                    result = "0." + StringUtils.leftPad("", exp - 1, "0") +  result;
429

    
430
                }
431

    
432
                if (result.length() > 5){
433
                    result = result.substring(0, 5);
434
                }
435
                while (result.endsWith("0")){
436
                    result = result.substring(0, result.length() -1);
437
                }
438
                result = result.substring(1);
439
                if (result.equals(".")){
440
                    result = "";
441
                }
442
                return result;
443
            }
444

    
445
        }
446

    
447
    }
448

    
449

    
450
    @Transient
451
    public Sexagesimal getLongitudeSexagesimal (){
452
        boolean isLatitude = false;
453
        return Sexagesimal.valueOf(longitude, isLatitude);
454
    }
455

    
456
    @Transient
457
    public Sexagesimal getLatitudeSexagesimal (){
458
        boolean isLatitude = true;
459
        return Sexagesimal.valueOf(latitude, isLatitude);
460
    }
461

    
462
    @Transient
463
    public void setLatitudeSexagesimal(Sexagesimal sexagesimalLatitude){
464
        this.latitude = sexagesimalLatitude.toDecimal();
465
    }
466
    @Transient
467
    public void setLongitudeSexagesimal(Sexagesimal sexagesimalLongitude){
468
        this.longitude = sexagesimalLongitude.toDecimal();
469
    }
470

    
471
    @Transient
472
    public void setLatitudeByParsing(String string) throws ParseException{
473
        this.setLatitude(parseLatitude(string));
474
    }
475

    
476
    @Transient
477
    public void setLongitudeByParsing(String string) throws ParseException{
478
        this.setLongitude(parseLongitude(string));
479
    }
480

    
481

    
482
    public static Double parseLatitude(String string) throws ParseException{
483
        try{
484
            if (string == null){
485
                return null;
486
            }
487
            string = setCurrentDoubleSeparator(string);
488
            if (isDouble(string)){
489
                Double result = Double.valueOf(string);
490
                if (Math.abs(result) > 90.0){
491
                    throw new ParseException("Latitude could not be parsed", 0);
492
                }
493
                return result;
494
            }else{
495
                CoordinateConverter converter = new CoordinateConverter();
496
                ConversionResults result = converter.tryConvert(string);
497
                if (! result.conversionSuccessful || (result.isLongitude != null  && result.isLongitude)  ){
498
                    throw new ParseException("Latitude could not be parsed", 0);
499
                }else{
500
                    return result.convertedCoord;
501
                }
502
            }
503
        } catch (Exception e) {
504
            String message = "Latitude %s could not be parsed";
505
            message = String.format(message, string);
506
            throw new ParseException(message, 0);
507
        }
508
    }
509

    
510
    public static Double parseLongitude(String string) throws ParseException{
511
        try {
512
            if (string == null){
513
                return null;
514
            }
515
            string = setCurrentDoubleSeparator(string);
516
            if (isDouble(string)){
517
                Double result = Double.valueOf(string);
518
                if (Math.abs(result) > 180.0){
519
                    throw new ParseException("Longitude could not be parsed", 0);
520
                }
521
                return result;
522
            }else{
523
                CoordinateConverter converter = new CoordinateConverter();
524
                ConversionResults result = converter.tryConvert(string);
525
                if (! result.conversionSuccessful || (result.isLongitude != null  && ! result.isLongitude)){
526
                    throw new ParseException("Longitude could not be parsed", 0);
527
                }else{
528
                    return result.convertedCoord;
529
                }
530
            }
531
        } catch (Exception e) {
532
            String message = "Longitude %s could not be parsed";
533
            message = String.format(message, string);
534
            throw new ParseException(message, 0);
535
        }
536
    }
537

    
538
    private static String setCurrentDoubleSeparator(String string) {
539
        String regExReplaceComma = "(\\,|\\.)";
540
        string = string.replaceAll(regExReplaceComma,".");
541
        return string;
542

    
543
    }
544

    
545
    private static boolean isDouble(String string) {
546
        try {
547
            Double.valueOf(string);
548
            return true;
549

    
550
        } catch (NumberFormatException e) {
551
            return false;
552
        } catch (Exception e) {
553
            return false;
554
        }
555

    
556
    }
557

    
558
// ******************** GETTER / SETTER ********************************
559

    
560
    public ReferenceSystem getReferenceSystem(){
561
        return this.referenceSystem;
562
    }
563

    
564
    /**
565
     *
566
     * @param referenceSystem    referenceSystem
567
     */
568
    public void setReferenceSystem(ReferenceSystem referenceSystem){
569
        this.referenceSystem = referenceSystem;
570
    }
571

    
572
    public Double getLongitude(){
573
        return this.longitude;
574
    }
575

    
576
    /**
577
     *
578
     * @param longitude    longitude
579
     */
580
    public void setLongitude(Double longitude){
581
        this.longitude = longitude;
582
    }
583

    
584
    public Double getLatitude(){
585
        return this.latitude;
586
    }
587

    
588
    /**
589
     *
590
     * @param latitude    latitude
591
     */
592
    public void setLatitude(Double latitude){
593
        this.latitude = latitude;
594
    }
595

    
596
    /**
597
     * Error radius in Meters
598
     */
599
    public Integer getErrorRadius(){
600
        return this.errorRadius;
601
    }
602

    
603
    /**
604
     *
605
     * @param errorRadius    errorRadius
606
     */
607
    public void setErrorRadius(Integer errorRadius){
608
        this.errorRadius = errorRadius;
609
    }
610

    
611
// **************** toString *************************/
612

    
613

    
614
    /**
615
     * Returns a string representation in sexagesimal coordinates.
616
     * @return
617
     */
618
    public String toSexagesimalString(boolean includeEmptySeconds, boolean includeReferenceSystem){
619
        String result = "";
620
        result += getLatitudeSexagesimal() == null ? "" : getLatitudeSexagesimal().toString(includeEmptySeconds);
621
        result = CdmUtils.concat(", ", result, getLongitudeSexagesimal() == null ? "" : getLongitudeSexagesimal().toString(includeEmptySeconds));
622
        if (includeReferenceSystem && getReferenceSystem() != null){
623
            String refSys = CdmUtils.isBlank(getReferenceSystem().getLabel()) ? "" : "(" + getReferenceSystem().getLabel() + ")";
624
            result = CdmUtils.concat(" ", result, refSys);
625
        }
626
        return result;
627
    }
628

    
629
    /* (non-Javadoc)
630
     * @see java.lang.Object#toString()
631
     */
632
    @Override
633
    public String toString(){
634
        String result = "";
635
        boolean includeEmptySeconds = true;
636
        result += getLatitudeSexagesimal() == null ? "" : getLatitudeSexagesimal().toString(includeEmptySeconds);
637
        result = CdmUtils.concat(", ", result, getLongitudeSexagesimal() == null ? "" : getLongitudeSexagesimal().toString(includeEmptySeconds));
638
        return result;
639
    }
640

    
641

    
642
//*********** CLONE **********************************/
643

    
644
    /**
645
     * Clones <i>this</i> point. This is a shortcut that enables to
646
     * create a new instance that differs only slightly from <i>this</i> point
647
     * by modifying only some of the attributes.<BR>
648
     * This method overrides the clone method from {@link DerivedUnit DerivedUnit}.
649
     *
650
     * @see java.lang.Object#clone()
651
     */
652
    @Override
653
    public Point clone(){
654
        try{
655
            Point result = (Point)super.clone();
656
            result.setReferenceSystem(this.referenceSystem);
657
            //no changes to: errorRadius, latitude, longitude
658
            return result;
659
        } catch (CloneNotSupportedException e) {
660
            logger.warn("Object does not implement cloneable");
661
            e.printStackTrace();
662
            return null;
663
        }
664
    }
665

    
666

    
667
}
(5-5/8)