X-Git-Url: https://dev.e-taxonomy.eu/gitweb/cdmlib.git/blobdiff_plain/32abcc976b07a79cd832ae915d1198911db4fa91..aa2d500aca7ba1f8b5f24e1a42cc3e23d3370246:/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/location/Point.java diff --git a/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/location/Point.java b/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/location/Point.java index 9486f978ed..d6dfaedaaa 100644 --- a/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/location/Point.java +++ b/cdmlib-model/src/main/java/eu/etaxonomy/cdm/model/location/Point.java @@ -1,15 +1,26 @@ /** * Copyright (C) 2007 EDIT -* European Distributed Institute of Taxonomy +* European Distributed Institute of Taxonomy * http://www.e-taxonomy.eu -* +* * The contents of this file are subject to the Mozilla Public License Version 1.1 * See LICENSE.TXT at the top of this package for the full license terms. */ package eu.etaxonomy.cdm.model.location; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.text.ParseException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import javax.persistence.Embeddable; +import javax.persistence.FetchType; import javax.persistence.ManyToOne; +import javax.persistence.Transient; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -18,9 +29,19 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSchemaType; import javax.xml.bind.annotation.XmlType; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; +import org.hibernate.search.annotations.Field; +import org.hibernate.search.annotations.Latitude; +import org.hibernate.search.annotations.Longitude; +import org.hibernate.search.annotations.NumericField; +import org.hibernate.search.annotations.Spatial; +import org.hibernate.search.annotations.SpatialMode; -import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase; +import eu.etaxonomy.cdm.common.CdmUtils; +import eu.etaxonomy.cdm.model.occurrence.DerivedUnit; +import eu.etaxonomy.cdm.strategy.parser.location.CoordinateConverter; +import eu.etaxonomy.cdm.strategy.parser.location.CoordinateConverter.ConversionResults; /** * @author m.doering @@ -36,112 +57,607 @@ import eu.etaxonomy.cdm.model.occurrence.DerivedUnitBase; }) @XmlRootElement(name = "Point") @Embeddable -public class Point implements Cloneable { - private static final Logger logger = Logger.getLogger(Point.class); - - //TODO was Float but H2 threw errors - @XmlElement(name = "Longitude") - private Double longitude; - - @XmlElement(name = "Latitude") - private Double latitude; - - //in Meters - @XmlElement(name = "ErrorRadius") - private Integer errorRadius = 0; - - @XmlElement(name = "ReferenceSystem") - @XmlIDREF - @XmlSchemaType(name = "IDREF") - private ReferenceSystem referenceSystem; - - /** - * Factory method - * @return - */ - public static Point NewInstance(){ - return new Point(); - } - - /** - * Constructor - */ - public Point() { - } - - @ManyToOne - public ReferenceSystem getReferenceSystem(){ - return this.referenceSystem; - } - - /** - * - * @param referenceSystem referenceSystem - */ - public void setReferenceSystem(ReferenceSystem referenceSystem){ - this.referenceSystem = referenceSystem; - } - - public Double getLongitude(){ - return this.longitude; - } - - /** - * - * @param longitude longitude - */ - public void setLongitude(Double longitude){ - this.longitude = longitude; - } - - public Double getLatitude(){ - return this.latitude; - } - - /** - * - * @param latitude latitude - */ - public void setLatitude(Double latitude){ - this.latitude = latitude; - } - - public Integer getErrorRadius(){ - return this.errorRadius; - } - - /** - * - * @param errorRadius errorRadius - */ - public void setErrorRadius(Integer errorRadius){ - this.errorRadius = errorRadius; - } - - -//*********** CLONE **********************************/ - - /** - * Clones this point. This is a shortcut that enables to - * create a new instance that differs only slightly from this point - * by modifying only some of the attributes.
- * This method overrides the clone method from {@link DerivedUnitBase DerivedUnitBase}. - * - * @see java.lang.Object#clone() - */ - @Override - public Point clone(){ - try{ - Point result = (Point)super.clone(); - result.setReferenceSystem(this.referenceSystem); - //no changes to: errorRadius, latitude, longitude - return result; - } catch (CloneNotSupportedException e) { - logger.warn("Object does not implement cloneable"); - e.printStackTrace(); - return null; - } - } +@Spatial(spatialMode=SpatialMode.RANGE, name="point") +public class Point implements Cloneable, Serializable { + private static final long serialVersionUID = 531030660792800636L; + private static final Logger logger = Logger.getLogger(Point.class); + + //TODO was Float but H2 threw errors + @XmlElement(name = "Longitude") + @Longitude(of="point") + private Double longitude; + + @XmlElement(name = "Latitude") + @Latitude(of="point") + private Double latitude; + + /** + * Error radius in Meters + */ + @XmlElement(name = "ErrorRadius") + @Field + @NumericField + private Integer errorRadius = 0; + + @XmlElement(name = "ReferenceSystem") + @XmlIDREF + @XmlSchemaType(name = "IDREF") + @ManyToOne(fetch = FetchType.LAZY) + private ReferenceSystem referenceSystem; + + +//******************** FACTORY METHODS **************************** + + /** + * Factory method + * @return + */ + public static Point NewInstance(){ + return new Point(); + } + + /** + * Factory method + * @return + */ + public static Point NewInstance(Double longitude, Double latitude, ReferenceSystem referenceSystem, Integer errorRadius){ + Point result = new Point(); + result.setLongitude(longitude); + result.setLatitude(latitude); + result.setReferenceSystem(referenceSystem); + result.setErrorRadius(errorRadius); + return result; + } + +// ******************** CONSTRUCTOR *************************** + + /** + * Constructor + */ + public Point() { + } + +//************** Sexagesimal /decimal METHODS ******************* + + public enum Direction { + WEST { + + @Override + public String toString() { + return "W"; + } + }, + EAST { + + @Override + public String toString() { + return "E"; + } + }, + NORTH { + + @Override + public String toString() { + return "N"; + } + }, + SOUTH { + + @Override + public String toString() { + return "S"; + } + }; + } + + public static final class CoordinateParser { + + /** + * Pattern zum parsen von Sexagesimalen Grad: 145° + */ + private static final String DEGREE_REGEX = "([0-9]*)\u00B0"; + /** + * Pattern zum parsen von Sexagesimalen Minuten: 65' + */ + private static final String MINUTES_REGEX = "(?:([0-9]*)')?"; + /** + * Pattern zum parsen von Sexagesimalen Sekunden: 17" + */ + private static final String SECONDS_REGEX = "(?:([0-9]*)(?:''|\"))?"; + /** + * Himmelsrichtung Längengrad + */ + private static final String LONGITUDE_DIRECTION_REGEX = "([OEW])"; + /** + * Himmelsrichtung Breitengrad + */ + private static final String LATITUDE_DIRECTION_REGEX = "([NS])"; + + /** + * Pattern zum Parsen von Breitengraden. + */ + private static final Pattern LATITUDE_PATTERN = Pattern + .compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX + + LATITUDE_DIRECTION_REGEX); + + /** + * Pattern zum Parsen von Längengraden. + */ + private static final Pattern LONGITUDE_PATTERN = Pattern + .compile(DEGREE_REGEX + MINUTES_REGEX + SECONDS_REGEX + + LONGITUDE_DIRECTION_REGEX); + + private CoordinateParser() { + throw new AssertionError( ); + } + + /** + * Parst einen Breitengrad der Form
+ * G°M'S""(OEW)
+ * Die Formen
+ * G°(OEW)
+ * G°M'(OEW)
+ * sind ebenfalls erlaubt. + * + * @param strg + * @return Die geparsten Koordinaten + * @throws ParseException + * Wenn eine Fehler beim Parsen aufgetreten ist. + */ + public static Sexagesimal parseLatitude(final String strg) + throws ParseException { + return parseCoordinates(strg, LATITUDE_PATTERN); + } + + /** + * Parst einen Längengrad der Form
+ * G°M'S"(NS)
+ * Die Formen
+ * G°(NS)
+ * G°M'(NS)
+ * sind ebenfalls erlaubt. + * + * @param strg + * @return Die geparsten Koordinaten + * @throws ParseException + * Wenn eine Fehler beim Parsen aufgetreten ist. + */ + public static Sexagesimal parseLongitude(final String strg) + throws ParseException { + return parseCoordinates(strg, LONGITUDE_PATTERN); + } + + + /** + * Not used at the moment. Use CoordinateConverter instead. + * @param strg + * @param pattern + * @return + * @throws ParseException + */ + private static Sexagesimal parseCoordinates(final String strg, final Pattern pattern) throws ParseException { + if (strg == null) { + throw new java.text.ParseException("Keine Koordinaten gegeben.", -1); + } + final Matcher matcher = pattern.matcher(strg); + if (matcher.matches( )) { + if (matcher.groupCount( ) == 4) { + // Grad + String tmp = matcher.group(1); + int degree = Integer.parseInt(tmp); + + // Optional minutes + tmp = matcher.group(2); + int minutes = Sexagesimal.NONE; + if (tmp != null) { + minutes = Integer.parseInt(tmp); + } + + // Optional seconds + tmp = matcher.group(3); + int seconds = Sexagesimal.NONE; + if (tmp != null) { + seconds = Integer.parseInt(tmp); + } + + // directions + tmp = matcher.group(4); + final Direction direction; + if (tmp.equals("N")) { + direction = Direction.NORTH; + } + else if (tmp.equals("S")) { + direction = Direction.SOUTH; + } + else if (tmp.equals("E") || tmp.equals("O")) { + direction = Direction.EAST; + } + else if (tmp.equals("W")) { + direction = Direction.WEST; + } + else { + direction = null; + } + return Sexagesimal.NewInstance(degree, minutes, seconds, direction); + } + else { + throw new java.text.ParseException( + "Die Koordinaten-Darstellung ist fehlerhaft: " + strg, + -1); + } + } + else { + throw new java.text.ParseException( + "Die Koordinaten-Darstellung ist fehlerhaft: " + strg, -1); + } + } + + } + + + private static final BigDecimal SIXTY = BigDecimal.valueOf(60.0); + private static final MathContext MC = new MathContext(34, RoundingMode.HALF_UP); + private static final double HALF_SECOND = 1. / 7200.; + + //see http://www.tutorials.de/forum/archiv/348596-quiz-10-zeja-java.html + public static class Sexagesimal{ + public static Sexagesimal NewInstance(Integer degree, Integer minutes, Integer seconds, Direction direction){ + Sexagesimal result = new Sexagesimal(); + result.degree = degree; result.minutes = minutes; result.seconds = seconds; + return result; + } + + public static final int NONE = 0; + public Integer degree; + public Integer minutes; + public Integer seconds; + public Double tertiers; + + public Direction direction; + + + public boolean isLatitude(){ + return (direction == Direction.WEST) || (direction == Direction.EAST) ; + } + public boolean isLongitude(){ + return ! isLatitude(); + } + + + public static Sexagesimal valueOf(Double decimal, boolean isLatitude){ + return valueOf(decimal, isLatitude, false, false, true); + } + + public static Sexagesimal valueOf(Double decimal, boolean isLatitude, boolean nullSecondsToNull, boolean nullMinutesToNull, boolean allowTertiers){ + if(decimal == null){ + return null; + } + Sexagesimal sexagesimal = new Sexagesimal(); + Double decimalDegree = decimal; + if (isLatitude) { + if (decimalDegree < 0) { + sexagesimal.direction = Direction.SOUTH; + } + else { + sexagesimal.direction = Direction.NORTH; + } + } + else { + if (decimalDegree < 0) { + sexagesimal.direction = Direction.WEST; + } + else { + sexagesimal.direction = Direction.EAST; + } + } + + // Decimal in \u00B0'" umrechnen + double d = Math.abs(decimalDegree); + if (! allowTertiers){ + d += HALF_SECOND; // add half a second for rounding + }else{ + d += HALF_SECOND / 10000; //to avoid rounding errors + } + sexagesimal.degree = (int) Math.floor(d); + sexagesimal.minutes = (int) Math.floor((d - sexagesimal.degree) * 60.0); + sexagesimal.seconds = (int) Math.floor((d - sexagesimal.degree - sexagesimal.minutes / 60.0) * 3600.0); + sexagesimal.tertiers = (d - sexagesimal.degree - sexagesimal.minutes / 60.0 - sexagesimal.seconds / 3600.0) * 3600.0; + + if (sexagesimal.seconds == 0 && nullSecondsToNull){ + sexagesimal.seconds = null; + } + if (sexagesimal.seconds == null && sexagesimal.minutes == 0 && nullMinutesToNull){ + sexagesimal.minutes = null; + } + + // sexagesimal.decimalRadian = Math.toRadians(this.decimalDegree); + return sexagesimal; + } + + + + private Double toDecimal(){ + BigDecimal value = BigDecimal.valueOf(CdmUtils.Nz(this.seconds)).divide(SIXTY, MC).add + (BigDecimal.valueOf(CdmUtils.Nz(this.minutes))).divide(SIXTY, MC).add + (BigDecimal.valueOf(CdmUtils.Nz(this.degree))); + + if (this.direction == Direction.WEST || this.direction == Direction.SOUTH) { + value = value.negate( ); + } + return value.doubleValue( ); + } + + @Override + public String toString(){ + return toString(false, false); + } + public String toString(boolean includeEmptySeconds){ + return toString(includeEmptySeconds, false); + } + + public String toString(boolean includeEmptySeconds, boolean removeTertiers){ + String result; + result = String.valueOf(CdmUtils.Nz(degree)) + "\u00B0"; + if (seconds != null || minutes != null){ + result += String.valueOf(CdmUtils.Nz(minutes)) + "'"; + } + if (seconds != null ){ + if (seconds != 0 || includeEmptySeconds){ + result += String.valueOf(CdmUtils.Nz(seconds)) + getTertiersString(tertiers, removeTertiers) + "\""; + } + } + result += direction; + return result; + } + private String getTertiersString(Double tertiers, boolean removeTertiers) { + if (tertiers == null || removeTertiers){ + return ""; + }else{ + if (tertiers >= 1.0 || tertiers < 0.0){ + throw new IllegalStateException("Tertiers should be 0.0 <= tertiers < 1.0 but are '" + tertiers + "'"); + } + String result = tertiers.toString(); + int pos = result.indexOf("E"); + if (pos > -1){ + int exp = - Integer.valueOf(result.substring(pos + 1)); + result = result.substring(0, pos).replace(".", ""); + result = "0." + StringUtils.leftPad("", exp - 1, "0") + result; + + } + + if (result.length() > 5){ + result = result.substring(0, 5); + } + while (result.endsWith("0")){ + result = result.substring(0, result.length() -1); + } + result = result.substring(1); + if (result.equals(".")){ + result = ""; + } + return result; + } + + } + + } + + + @Transient + public Sexagesimal getLongitudeSexagesimal (){ + boolean isLatitude = false; + return Sexagesimal.valueOf(longitude, isLatitude); + } + + @Transient + public Sexagesimal getLatitudeSexagesimal (){ + boolean isLatitude = true; + return Sexagesimal.valueOf(latitude, isLatitude); + } + + @Transient + public void setLatitudeSexagesimal(Sexagesimal sexagesimalLatitude){ + this.latitude = sexagesimalLatitude.toDecimal(); + } + @Transient + public void setLongitudeSexagesimal(Sexagesimal sexagesimalLongitude){ + this.longitude = sexagesimalLongitude.toDecimal(); + } + + @Transient + public void setLatitudeByParsing(String string) throws ParseException{ + this.setLatitude(parseLatitude(string)); + } + + @Transient + public void setLongitudeByParsing(String string) throws ParseException{ + this.setLongitude(parseLongitude(string)); + } + + + public static Double parseLatitude(String string) throws ParseException{ + try{ + if (string == null){ + return null; + } + string = setCurrentDoubleSeparator(string); + if (isDouble(string)){ + Double result = Double.valueOf(string); + if (Math.abs(result) > 90.0){ + throw new ParseException("Latitude could not be parsed", 0); + } + return result; + }else{ + CoordinateConverter converter = new CoordinateConverter(); + ConversionResults result = converter.tryConvert(string); + if (! result.conversionSuccessful || (result.isLongitude != null && result.isLongitude) ){ + throw new ParseException("Latitude could not be parsed", 0); + }else{ + return result.convertedCoord; + } + } + } catch (Exception e) { + String message = "Latitude %s could not be parsed"; + message = String.format(message, string); + throw new ParseException(message, 0); + } + } + + public static Double parseLongitude(String string) throws ParseException{ + try { + if (string == null){ + return null; + } + string = setCurrentDoubleSeparator(string); + if (isDouble(string)){ + Double result = Double.valueOf(string); + if (Math.abs(result) > 180.0){ + throw new ParseException("Longitude could not be parsed", 0); + } + return result; + }else{ + CoordinateConverter converter = new CoordinateConverter(); + ConversionResults result = converter.tryConvert(string); + if (! result.conversionSuccessful || (result.isLongitude != null && ! result.isLongitude)){ + throw new ParseException("Longitude could not be parsed", 0); + }else{ + return result.convertedCoord; + } + } + } catch (Exception e) { + String message = "Longitude %s could not be parsed"; + message = String.format(message, string); + throw new ParseException(message, 0); + } + } + + private static String setCurrentDoubleSeparator(String string) { + String regExReplaceComma = "(\\,|\\.)"; + string = string.replaceAll(regExReplaceComma,"."); + return string; + + } + + private static boolean isDouble(String string) { + try { + Double.valueOf(string); + return true; + + } catch (NumberFormatException e) { + return false; + } catch (Exception e) { + return false; + } + + } + +// ******************** GETTER / SETTER ******************************** + + public ReferenceSystem getReferenceSystem(){ + return this.referenceSystem; + } + + /** + * + * @param referenceSystem referenceSystem + */ + public void setReferenceSystem(ReferenceSystem referenceSystem){ + this.referenceSystem = referenceSystem; + } + + public Double getLongitude(){ + return this.longitude; + } + + /** + * + * @param longitude longitude + */ + public void setLongitude(Double longitude){ + this.longitude = longitude; + } + + public Double getLatitude(){ + return this.latitude; + } + + /** + * + * @param latitude latitude + */ + public void setLatitude(Double latitude){ + this.latitude = latitude; + } + + /** + * Error radius in Meters + */ + public Integer getErrorRadius(){ + return this.errorRadius; + } + + /** + * + * @param errorRadius errorRadius + */ + public void setErrorRadius(Integer errorRadius){ + this.errorRadius = errorRadius; + } + +// **************** toString *************************/ + + + /** + * Returns a string representation in sexagesimal coordinates. + * @return + */ + public String toSexagesimalString(boolean includeEmptySeconds, boolean includeReferenceSystem){ + String result = ""; + result += getLatitudeSexagesimal() == null ? "" : getLatitudeSexagesimal().toString(includeEmptySeconds); + result = CdmUtils.concat(", ", result, getLongitudeSexagesimal() == null ? "" : getLongitudeSexagesimal().toString(includeEmptySeconds)); + if (includeReferenceSystem && getReferenceSystem() != null){ + String refSys = CdmUtils.isBlank(getReferenceSystem().getLabel()) ? "" : "(" + getReferenceSystem().getLabel() + ")"; + result = CdmUtils.concat(" ", result, refSys); + } + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString(){ + String result = ""; + boolean includeEmptySeconds = true; + result += getLatitudeSexagesimal() == null ? "" : getLatitudeSexagesimal().toString(includeEmptySeconds); + result = CdmUtils.concat(", ", result, getLongitudeSexagesimal() == null ? "" : getLongitudeSexagesimal().toString(includeEmptySeconds)); + return result; + } + + +//*********** CLONE **********************************/ + + /** + * Clones this point. This is a shortcut that enables to + * create a new instance that differs only slightly from this point + * by modifying only some of the attributes.
+ * This method overrides the clone method from {@link DerivedUnit DerivedUnit}. + * + * @see java.lang.Object#clone() + */ + @Override + public Point clone(){ + try{ + Point result = (Point)super.clone(); + result.setReferenceSystem(this.referenceSystem); + //no changes to: errorRadius, latitude, longitude + return result; + } catch (CloneNotSupportedException e) { + logger.warn("Object does not implement cloneable"); + e.printStackTrace(); + return null; + } + } } \ No newline at end of file