Project

General

Profile

Download (21.9 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
 * @since 08-Nov-2007 13:06:44
51
 */
52
@XmlAccessorType(XmlAccessType.FIELD)
53
@XmlType(name = "Point", propOrder = {
54
    "longitude",
55
    "latitude",
56
    "errorRadius",
57
    "referenceSystem"
58
})
59
@XmlRootElement(name = "Point")
60
@Embeddable
61
@Spatial(spatialMode=SpatialMode.RANGE, name="point")
62
public class Point implements Cloneable, Serializable {
63
    private static final long serialVersionUID = 531030660792800636L;
64
    private static final Logger logger = Logger.getLogger(Point.class);
65

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

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

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

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

    
91

    
92
//******************** FACTORY METHODS ****************************
93

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

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

    
115
// ******************** CONSTRUCTOR ***************************
116

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

    
123
//************** Sexagesimal /decimal METHODS *******************
124

    
125
    public enum Direction {
126
        WEST {
127

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

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

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

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

    
156
    public static final class CoordinateParser {
157

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

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

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

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

    
197
        /**
198
         * Parst einen Breitengrad der Form<br>
199
         * G°M'S""(OEW)<br>
200
         * Die Formen<br>
201
         * G°(OEW)<br>
202
         * G°M'(OEW)<br>
203
         * sind ebenfalls erlaubt.
204
         *
205
         * @param strg
206
         * @return Die geparsten Koordinaten
207
         * @throws ParseException
208
         *             Wenn eine Fehler beim Parsen aufgetreten ist.
209
         */
210
        public static Sexagesimal parseLatitude(final String strg)
211
                throws ParseException {
212
            return parseCoordinates(strg, LATITUDE_PATTERN);
213
        }
214

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

    
233

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

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

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

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

    
298
    }
299

    
300

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

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

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

    
319
        public Direction direction;
320

    
321

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

    
329

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

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

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

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

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

    
380

    
381

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

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

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

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

    
429
                }
430

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

    
444
        }
445

    
446
    }
447

    
448

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

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

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

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

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

    
480

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

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

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

    
542
    }
543

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

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

    
555
    }
556

    
557
    /**
558
     * <code>true</code>, if none of the attributes (lat, long, errRadius, refSys) is set.
559
     */
560
    @Transient
561
    public boolean isEmpty(){
562
        if (errorRadius == null && latitude == null && longitude == null
563
                && referenceSystem == null){
564
            return true;
565
        }else{
566
            return false;
567
        }
568
    }
569

    
570
// ******************** GETTER / SETTER ********************************
571

    
572
    public ReferenceSystem getReferenceSystem(){
573
        return this.referenceSystem;
574
    }
575

    
576
    /**
577
     *
578
     * @param referenceSystem    referenceSystem
579
     */
580
    public void setReferenceSystem(ReferenceSystem referenceSystem){
581
        this.referenceSystem = referenceSystem;
582
    }
583

    
584
    public Double getLongitude(){
585
        return this.longitude;
586
    }
587

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

    
596
    public Double getLatitude(){
597
        return this.latitude;
598
    }
599

    
600
    /**
601
     *
602
     * @param latitude    latitude
603
     */
604
    public void setLatitude(Double latitude){
605
        this.latitude = latitude;
606
    }
607

    
608
    /**
609
     * Error radius in Meters
610
     */
611
    public Integer getErrorRadius(){
612
        return this.errorRadius;
613
    }
614

    
615
    /**
616
     *
617
     * @param errorRadius    errorRadius
618
     */
619
    public void setErrorRadius(Integer errorRadius){
620
        this.errorRadius = errorRadius;
621
    }
622

    
623
// **************** toString *************************/
624

    
625

    
626
    /**
627
     * Returns a string representation in sexagesimal coordinates.
628
     * @return
629
     */
630
    public String toSexagesimalString(boolean includeEmptySeconds, boolean includeReferenceSystem){
631
        String result = "";
632
        result += getLatitudeSexagesimal() == null ? "" : getLatitudeSexagesimal().toString(includeEmptySeconds);
633
        result = CdmUtils.concat(", ", result, getLongitudeSexagesimal() == null ? "" : getLongitudeSexagesimal().toString(includeEmptySeconds));
634
        if (includeReferenceSystem && getReferenceSystem() != null){
635
            String refSys = CdmUtils.isBlank(getReferenceSystem().getLabel()) ? "" : "(" + getReferenceSystem().getLabel() + ")";
636
            result = CdmUtils.concat(" ", result, refSys);
637
        }
638
        return result;
639
    }
640

    
641
    @Override
642
    public String toString(){
643
        String result = "";
644
        boolean includeEmptySeconds = true;
645
        result += getLatitudeSexagesimal() == null ? "" : getLatitudeSexagesimal().toString(includeEmptySeconds);
646
        result = CdmUtils.concat(", ", result, getLongitudeSexagesimal() == null ? "" : getLongitudeSexagesimal().toString(includeEmptySeconds));
647
        return result;
648
    }
649

    
650

    
651
//*********** CLONE **********************************/
652

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

    
675

    
676
}
(5-5/8)