source: josm/trunk/src/org/openstreetmap/josm/data/coor/LatLon.java @ 5241

Revision 5235, 11.3 KB checked in by bastiK, 9 days ago (diff)

no rounding for projection bounds, to avoid 42 being dispayed as 41.99999999999

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.data.coor;
3
4import static java.lang.Math.PI;
5import static java.lang.Math.asin;
6import static java.lang.Math.atan2;
7import static java.lang.Math.cos;
8import static java.lang.Math.sin;
9import static java.lang.Math.sqrt;
10import static java.lang.Math.toRadians;
11import static org.openstreetmap.josm.tools.I18n.trc;
12
13import java.math.BigDecimal;
14import java.math.MathContext;
15import java.text.DecimalFormat;
16import java.text.NumberFormat;
17import java.util.Locale;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.data.Bounds;
21
22/**
23 * LatLon are unprojected latitude / longitude coordinates.
24 *
25 * This class is immutable.
26 *
27 * @author Imi
28 */
29public class LatLon extends Coordinate {
30
31
32    /**
33     * Minimum difference in location to not be represented as the same position.
34     * The API returns 7 decimals.
35     */
36    public static final double MAX_SERVER_PRECISION = 1e-7;
37    public static final int    MAX_SERVER_DIGITS = 7;
38
39    private static DecimalFormat cDmsMinuteFormatter = new DecimalFormat("00");
40    private static DecimalFormat cDmsSecondFormatter = new DecimalFormat("00.0");
41    private static DecimalFormat cDmMinuteFormatter = new DecimalFormat("00.000");
42    public static final DecimalFormat cDdFormatter;
43    static {
44        // Don't use the localized decimal separator. This way we can present
45        // a comma separated list of coordinates.
46        cDdFormatter = (DecimalFormat) NumberFormat.getInstance(Locale.UK);
47        cDdFormatter.applyPattern("###0.0######");
48    }
49
50    /**
51     * Replies true if lat is in the range [-90,90]
52     *
53     * @param lat the latitude
54     * @return true if lat is in the range [-90,90]
55     */
56    public static boolean isValidLat(double lat) {
57        return lat >= -90d && lat <= 90d;
58    }
59
60    /**
61     * Replies true if lon is in the range [-180,180]
62     *
63     * @param lon the longitude
64     * @return true if lon is in the range [-180,180]
65     */
66    public static boolean isValidLon(double lon) {
67        return lon >= -180d && lon <= 180d;
68    }
69
70    /**
71     * Replies true if lat is in the range [-90,90] and lon is in the range [-180,180]
72     *
73     * @return true if lat is in the range [-90,90] and lon is in the range [-180,180]
74     */
75    public boolean isValid() {
76        return isValidLat(lat()) && isValidLon(lon());
77    }
78
79    public static double toIntervalLat(double value) {
80        if (value < -90)
81            return -90;
82        if (value > 90)
83            return 90;
84        return value;
85    }
86
87    /**
88     * Returns a valid OSM longitude [-180,+180] for the given extended longitude value.
89     * For example, a value of -181 will return +179, a value of +181 will return -179.
90     * @param lon A longitude value not restricted to the [-180,+180] range.
91     */
92    public static double toIntervalLon(double value) {
93        if (isValidLon(value))
94            return value;
95        else {
96            int n = (int) (value + Math.signum(value)*180.0) / 360;
97            return value - n*360.0;
98        }
99    }
100
101    /**
102     * Replies the coordinate in degrees/minutes/seconds format
103     */
104    public static String dms(double pCoordinate) {
105
106        double tAbsCoord = Math.abs(pCoordinate);
107        int tDegree = (int) tAbsCoord;
108        double tTmpMinutes = (tAbsCoord - tDegree) * 60;
109        int tMinutes = (int) tTmpMinutes;
110        double tSeconds = (tTmpMinutes - tMinutes) * 60;
111
112        return tDegree + "\u00B0" + cDmsMinuteFormatter.format(tMinutes) + "\'"
113        + cDmsSecondFormatter.format(tSeconds) + "\"";
114    }
115
116    public static String dm(double pCoordinate) {
117
118        double tAbsCoord = Math.abs(pCoordinate);
119        int tDegree = (int) tAbsCoord;
120        double tMinutes = (tAbsCoord - tDegree) * 60;
121        return tDegree + "\u00B0" + cDmMinuteFormatter.format(tMinutes) + "\'";
122    }
123
124    public LatLon(double lat, double lon) {
125        super(lon, lat);
126    }
127
128    public LatLon(LatLon coor) {
129        super(coor.lon(), coor.lat());
130    }
131
132    public double lat() {
133        return y;
134    }
135
136    public final static String SOUTH = trc("compass", "S");
137    public final static String NORTH = trc("compass", "N");
138    public String latToString(CoordinateFormat d) {
139        switch(d) {
140        case DECIMAL_DEGREES: return cDdFormatter.format(y);
141        case DEGREES_MINUTES_SECONDS: return dms(y) + ((y < 0) ? SOUTH : NORTH);
142        case NAUTICAL: return dm(y) + ((y < 0) ? SOUTH : NORTH);
143        case EAST_NORTH: return cDdFormatter.format(Main.getProjection().latlon2eastNorth(this).north());
144        default: return "ERR";
145        }
146    }
147
148    public double lon() {
149        return x;
150    }
151
152    public final static String WEST = trc("compass", "W");
153    public final static String EAST = trc("compass", "E");
154    public String lonToString(CoordinateFormat d) {
155        switch(d) {
156        case DECIMAL_DEGREES: return cDdFormatter.format(x);
157        case DEGREES_MINUTES_SECONDS: return dms(x) + ((x < 0) ? WEST : EAST);
158        case NAUTICAL: return dm(x) + ((x < 0) ? WEST : EAST);
159        case EAST_NORTH: return cDdFormatter.format(Main.getProjection().latlon2eastNorth(this).east());
160        default: return "ERR";
161        }
162    }
163
164    /**
165     * @return <code>true</code> if the other point has almost the same lat/lon
166     * values, only differing by no more than
167     * 1 / {@link #MAX_SERVER_PRECISION MAX_SERVER_PRECISION}.
168     */
169    public boolean equalsEpsilon(LatLon other) {
170        double p = MAX_SERVER_PRECISION / 2;
171        return Math.abs(lat()-other.lat()) <= p && Math.abs(lon()-other.lon()) <= p;
172    }
173
174    /**
175     * @return <code>true</code>, if the coordinate is outside the world, compared
176     * by using lat/lon.
177     */
178    public boolean isOutSideWorld() {
179        Bounds b = Main.getProjection().getWorldBoundsLatLon();
180        return lat() < b.getMin().lat() || lat() > b.getMax().lat() ||
181                lon() < b.getMin().lon() || lon() > b.getMax().lon();
182    }
183
184    /**
185     * @return <code>true</code> if this is within the given bounding box.
186     */
187    public boolean isWithin(Bounds b) {
188        return b.contains(this);
189    }
190
191    /**
192     * Computes the distance between this lat/lon and another point on the earth.
193     * Uses Haversine formular.
194     * @param other the other point.
195     * @return distance in metres.
196     */
197    public double greatCircleDistance(LatLon other) {
198        double R = 6378135;
199        double sinHalfLat = sin(toRadians(other.lat() - this.lat()) / 2);
200        double sinHalfLon = sin(toRadians(other.lon() - this.lon()) / 2);
201        double d = 2 * R * asin(
202                sqrt(sinHalfLat*sinHalfLat +
203                        cos(toRadians(this.lat()))*cos(toRadians(other.lat()))*sinHalfLon*sinHalfLon));
204        // For points opposite to each other on the sphere,
205        // rounding errors could make the argument of asin greater than 1
206        // (This should almost never happen.)
207        if (java.lang.Double.isNaN(d)) {
208            System.err.println("Error: NaN in greatCircleDistance");
209            d = PI * R;
210        }
211        return d;
212    }
213
214    /**
215     * Returns the heading, in radians, that you have to use to get from
216     * this lat/lon to another.
217     *
218     * (I don't know the original source of this formula, but see
219     * http://math.stackexchange.com/questions/720/how-to-calculate-a-heading-on-the-earths-surface
220     * for some hints how it is derived.)
221     *
222     * @param other the "destination" position
223     * @return heading in the range 0 <= hd < 2*PI
224     */
225    public double heading(LatLon other) {
226        double hd = atan2(sin(toRadians(this.lon() - other.lon())) * cos(toRadians(other.lat())),
227                cos(toRadians(this.lat())) * sin(toRadians(other.lat())) -
228                sin(toRadians(this.lat())) * cos(toRadians(other.lat())) * cos(toRadians(this.lon() - other.lon())));
229        hd %= 2 * PI;
230        if (hd < 0) {
231            hd += 2 * PI;
232        }
233        return hd;
234    }
235
236    /**
237     * Returns this lat/lon pair in human-readable format.
238     *
239     * @return String in the format "lat=1.23456 deg, lon=2.34567 deg"
240     */
241    public String toDisplayString() {
242        NumberFormat nf = NumberFormat.getInstance();
243        nf.setMaximumFractionDigits(5);
244        return "lat=" + nf.format(lat()) + "\u00B0, lon=" + nf.format(lon()) + "\u00B0";
245    }
246
247    public LatLon interpolate(LatLon ll2, double proportion) {
248        return new LatLon(this.lat() + proportion * (ll2.lat() - this.lat()),
249                this.lon() + proportion * (ll2.lon() - this.lon()));
250    }
251
252    public LatLon getCenter(LatLon ll2) {
253        return new LatLon((this.lat() + ll2.lat())/2.0, (this.lon() + ll2.lon())/2.0);
254    }
255
256    @Override public String toString() {
257        return "LatLon[lat="+lat()+",lon="+lon()+"]";
258    }
259
260    /**
261     * Returns the value rounded to OSM precisions, i.e. to
262     * LatLon.MAX_SERVER_PRECISION
263     *
264     * @return rounded value
265     */
266    public static double roundToOsmPrecision(double value) {
267        return Math.round(value / MAX_SERVER_PRECISION) * MAX_SERVER_PRECISION; // causes tiny rounding errors (see LatLonTest)
268    }
269
270    /**
271     * Returns the value rounded to OSM precisions, i.e. to
272     * LatLon.MAX_SERVER_PRECISION. The result is guaranteed to be exact, but at a great cost.
273     * This function is about 1000 times slower than roundToOsmPrecision(), use it with caution.
274     *
275     * @return rounded value
276     */
277    public static double roundToOsmPrecisionStrict(double value) {
278        double absV = Math.abs(value);
279        int numOfDigits = MAX_SERVER_DIGITS + (absV < 1 ? 0 : (absV < 10 ? 1 : (absV < 100 ? 2 : 3)));
280        return BigDecimal.valueOf(value).round(new MathContext(numOfDigits)).doubleValue();
281    }
282
283    /**
284     * Replies a clone of this lat LatLon, rounded to OSM precisions, i.e. to
285     * MAX_SERVER_PRECISION
286     *
287     * @return a clone of this lat LatLon
288     */
289    public LatLon getRoundedToOsmPrecision() {
290        return new LatLon(
291                roundToOsmPrecision(lat()),
292                roundToOsmPrecision(lon())
293                );
294    }
295
296    /**
297     * Replies a clone of this lat LatLon, rounded to OSM precisions, i.e. to
298     * MAX_SERVER_PRECISION
299     *
300     * @return a clone of this lat LatLon
301     */
302    public LatLon getRoundedToOsmPrecisionStrict() {
303        return new LatLon(
304                roundToOsmPrecisionStrict(lat()),
305                roundToOsmPrecisionStrict(lon())
306                );
307    }
308
309    @Override
310    public int hashCode() {
311        final int prime = 31;
312        int result = super.hashCode();
313        long temp;
314        temp = java.lang.Double.doubleToLongBits(x);
315        result = prime * result + (int) (temp ^ (temp >>> 32));
316        temp = java.lang.Double.doubleToLongBits(y);
317        result = prime * result + (int) (temp ^ (temp >>> 32));
318        return result;
319    }
320
321    @Override
322    public boolean equals(Object obj) {
323        if (this == obj)
324            return true;
325        if (!super.equals(obj))
326            return false;
327        if (getClass() != obj.getClass())
328            return false;
329        Coordinate other = (Coordinate) obj;
330        if (java.lang.Double.doubleToLongBits(x) != java.lang.Double.doubleToLongBits(other.x))
331            return false;
332        if (java.lang.Double.doubleToLongBits(y) != java.lang.Double.doubleToLongBits(other.y))
333            return false;
334        return true;
335    }
336}
Note: See TracBrowser for help on using the repository browser.