001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.api.model.occurrence.geo; 015 016import java.util.Objects; 017import java.util.StringJoiner; 018 019import org.apache.commons.lang3.StringUtils; 020 021import com.fasterxml.jackson.annotation.JsonCreator; 022import com.fasterxml.jackson.annotation.JsonProperty; 023 024/** 025 * The DistanceUnit enumerates several units for measuring distances. These units 026 * provide methods for converting strings and methods to convert units among each 027 * others.The default unit used within this project is <code>METERS</code> 028 * which is defined by <code>DEFAULT</code> 029 */ 030public enum DistanceUnit { 031 INCH(0.0254, "in", "inch"), 032 YARD(0.9144, "yd", "yards"), 033 FEET(0.3048, "ft", "feet"), 034 KILOMETERS(1000.0, "km", "kilometers"), 035 NAUTICALMILES(1852.0, "NM", "nmi", "nauticalmiles"), 036 MILLIMETERS(0.001, "mm", "millimeters"), 037 CENTIMETERS(0.01, "cm", "centimeters"), 038 039 // 'm' is a suffix of 'nmi' so it must follow 'nmi' 040 MILES(1609.344, "mi", "miles"), 041 042 // since 'm' is suffix of other unit 043 // it must be the last entry of unit 044 // names ending with 'm'. otherwise 045 // parsing would fail 046 METERS(1, "m", "meters"); 047 048 public static final DistanceUnit DEFAULT = METERS; 049 050 private double meters; 051 private final String[] names; 052 053 DistanceUnit(double meters, String...names) { 054 this.meters = meters; 055 this.names = names; 056 } 057 058 public double getMeters() { 059 return meters; 060 } 061 062 public String[] getNames() { 063 return names; 064 } 065 066 /** 067 * Convert a value to a distance string 068 * 069 * @param distance value to convert 070 * @return String representation of the distance 071 */ 072 public String toString(double distance) { 073 return distance + toString(); 074 } 075 076 @Override 077 public String toString() { 078 return names[0]; 079 } 080 081 /** 082 * Parse a {@link Distance} from a given String 083 * 084 * @param distance String defining a {@link Distance} 085 * @return parsed {@link Distance} 086 */ 087 public static Distance parseDistance(String distance) { 088 for (DistanceUnit unit: DistanceUnit.values()) { 089 for (String name : unit.getNames()) { 090 if(distance.endsWith(name)) { 091 return new Distance(Double.parseDouble(distance.substring(0, distance.length() - name.length())), unit); 092 } 093 } 094 } 095 return new Distance(Double.parseDouble(distance), DEFAULT); 096 } 097 098 /** 099 * Converts the given distance from the given DistanceUnit, to the given DistanceUnit 100 * 101 * @param distance Distance to convert 102 * @param from Unit to convert the distance from 103 * @param to Unit of distance to convert to 104 * @return Given distance converted to the distance in the given unit 105 */ 106 public static double convert(double distance, DistanceUnit from, DistanceUnit to) { 107 if (from == to) { 108 return distance; 109 } else { 110 return distance * from.meters / to.meters; 111 } 112 } 113 114 public static class Distance implements Comparable<Distance> { 115 public final double value; 116 public final DistanceUnit unit; 117 118 @JsonCreator 119 public Distance(@JsonProperty("value") double value, 120 @JsonProperty("unit") DistanceUnit unit) { 121 this.value = value; 122 this.unit = unit; 123 } 124 125 public double getValue() { 126 return value; 127 } 128 129 public DistanceUnit getUnit() { 130 return unit; 131 } 132 133 @Override 134 public boolean equals(Object obj) { 135 if(obj == null) { 136 return false; 137 } else if (obj instanceof Distance) { 138 Distance other = (Distance) obj; 139 return DistanceUnit.convert(value, unit, other.unit) == other.value; 140 } else { 141 return false; 142 } 143 } 144 145 @Override 146 public int hashCode() { 147 return Double.valueOf(value * unit.meters).hashCode(); 148 } 149 150 @Override 151 public int compareTo(Distance o) { 152 return Double.compare(value, DistanceUnit.convert(o.value, o.unit, unit)); 153 } 154 155 @Override 156 public String toString() { 157 return unit.toString(value); 158 } 159 } 160 161 public static class GeoDistance { 162 163 private final double latitude; 164 165 private final double longitude; 166 167 private final Distance distance; 168 169 @JsonCreator 170 public GeoDistance(@JsonProperty("latitude") double latitude, 171 @JsonProperty("longitude") double longitude, 172 @JsonProperty("distance") Distance distance) { 173 this.latitude = latitude; 174 this.longitude = longitude; 175 this.distance = distance; 176 } 177 178 public double getLatitude() { 179 return latitude; 180 } 181 182 public double getLongitude() { 183 return longitude; 184 } 185 186 public Distance getDistance() { 187 return distance; 188 } 189 190 @Override 191 public boolean equals(Object o) { 192 if (this == o) return true; 193 if (o == null || getClass() != o.getClass()) return false; 194 GeoDistance that = (GeoDistance) o; 195 return Double.compare(that.latitude, latitude) == 0 196 && Double.compare(that.longitude, longitude) == 0 197 && Objects.equals(distance, that.distance); 198 } 199 200 @Override 201 public int hashCode() { 202 return Objects.hash(latitude, longitude, distance); 203 } 204 205 @Override 206 public String toString() { 207 return new StringJoiner(", ", GeoDistance.class.getSimpleName() + "[", "]") 208 .add("latitude=" + latitude) 209 .add("longitude=" + longitude) 210 .add("distance=" + distance) 211 .toString(); 212 } 213 214 public static GeoDistance parseGeoDistance(String geoDistance) { 215 if (StringUtils.isEmpty(geoDistance)) { 216 throw new IllegalArgumentException("GeoDistance cannot be null or empty"); 217 } 218 String[] geoDistanceTokens = geoDistance.split(","); 219 if (geoDistanceTokens.length != 3) { 220 throw new IllegalArgumentException("GeoDistance must follow the format lat,lng,distance"); 221 } 222 return parseGeoDistance(geoDistanceTokens[0].trim(), geoDistanceTokens[1].trim(), geoDistanceTokens[2].trim()); 223 } 224 225 public String toGeoDistanceString() { 226 return new StringJoiner(", ") 227 .add(Double.toString(latitude)) 228 .add(Double.toString(longitude)) 229 .add(distance.toString()) 230 .toString(); 231 } 232 233 public static GeoDistance parseGeoDistance(String latitude, String longitude, String distance) { 234 return new GeoDistance(Double.parseDouble(latitude), 235 Double.parseDouble(longitude), 236 DistanceUnit.parseDistance(distance)); 237 } 238 } 239}