Michael E. Rodriguez | 86f57b0 | 2006-03-29 21:05:08 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * $Header: /cvshome/build/org.osgi.util.position/src/org/osgi/util/position/Position.java,v 1.7 2006/03/14 01:20:44 hargrave Exp $ |
| 3 | * |
| 4 | * Copyright (c) OSGi Alliance (2002, 2005). All Rights Reserved. |
| 5 | * |
| 6 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | * you may not use this file except in compliance with the License. |
| 8 | * You may obtain a copy of the License at |
| 9 | * |
| 10 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | * |
| 12 | * Unless required by applicable law or agreed to in writing, software |
| 13 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | * See the License for the specific language governing permissions and |
| 16 | * limitations under the License. |
| 17 | */ |
| 18 | package org.osgi.util.position; |
| 19 | |
| 20 | import org.osgi.util.measurement.*; |
| 21 | |
| 22 | /** |
| 23 | * Position represents a geographic location, based on the WGS84 System (World |
| 24 | * Geodetic System 1984). |
| 25 | * <p> |
| 26 | * The <code>org.osgi.util.measurement.Measurement</code> class is used to |
| 27 | * represent the values that make up a position. |
| 28 | * <p> |
| 29 | * <p> |
| 30 | * A given position object may lack any of it's components, i.e. the altitude |
| 31 | * may not be known. Such missing values will be represented by null. |
| 32 | * <p> |
| 33 | * Position does not override the implementation of either equals() or |
| 34 | * hashCode() because it is not clear how missing values should be handled. It |
| 35 | * is up to the user of a position to determine how best to compare two position |
| 36 | * objects. A <code>Position</code> object is immutable. |
| 37 | */ |
| 38 | public class Position { |
| 39 | private Measurement altitude; |
| 40 | private Measurement longitude; |
| 41 | private Measurement latitude; |
| 42 | private Measurement speed; |
| 43 | private Measurement track; |
| 44 | |
| 45 | /** |
| 46 | * Contructs a <code>Position</code> object with the given values. |
| 47 | * |
| 48 | * @param lat a <code>Measurement</code> object specifying the latitude in |
| 49 | * radians, or null |
| 50 | * @param lon a <code>Measurement</code> object specifying the longitude in |
| 51 | * radians, or null |
| 52 | * @param alt a <code>Measurement</code> object specifying the altitude in |
| 53 | * meters, or null |
| 54 | * @param speed a <code>Measurement</code> object specifying the speed in |
| 55 | * meters per second, or null |
| 56 | * @param track a <code>Measurement</code> object specifying the track in |
| 57 | * radians, or null |
| 58 | */ |
| 59 | public Position(Measurement lat, Measurement lon, Measurement alt, |
| 60 | Measurement speed, Measurement track) { |
| 61 | if (lat != null) { |
| 62 | if (!Unit.rad.equals(lat.getUnit())) { |
| 63 | throw new IllegalArgumentException("Invalid Latitude"); |
| 64 | } |
| 65 | this.latitude = lat; |
| 66 | } |
| 67 | if (lon != null) { |
| 68 | if (!Unit.rad.equals(lon.getUnit())) { |
| 69 | throw new IllegalArgumentException("Invalid Longitude"); |
| 70 | } |
| 71 | this.longitude = lon; |
| 72 | } |
| 73 | normalizeLatLon(); |
| 74 | if (alt != null) { |
| 75 | if (!Unit.m.equals(alt.getUnit())) { |
| 76 | throw new IllegalArgumentException("Invalid Altitude"); |
| 77 | } |
| 78 | this.altitude = alt; |
| 79 | } |
| 80 | if (speed != null) { |
| 81 | if (!Unit.m_s.equals(speed.getUnit())) { |
| 82 | throw new IllegalArgumentException("Invalid Speed"); |
| 83 | } |
| 84 | this.speed = speed; |
| 85 | } |
| 86 | if (track != null) { |
| 87 | if (!Unit.rad.equals(track.getUnit())) { |
| 88 | throw new IllegalArgumentException("Invalid Track"); |
| 89 | } |
| 90 | this.track = normalizeTrack(track); |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * Returns the altitude of this position in meters. |
| 96 | * |
| 97 | * @return a <code>Measurement</code> object in <code>Unit.m</code> representing |
| 98 | * the altitude in meters above the ellipsoid <code>null</code> if the |
| 99 | * altitude is not known. |
| 100 | */ |
| 101 | public Measurement getAltitude() { |
| 102 | return altitude; |
| 103 | } |
| 104 | |
| 105 | /** |
| 106 | * Returns the longitude of this position in radians. |
| 107 | * |
| 108 | * @return a <code>Measurement</code> object in <code>Unit.rad</code> |
| 109 | * representing the longitude, or <code>null</code> if the longitude |
| 110 | * is not known. |
| 111 | */ |
| 112 | public Measurement getLongitude() { |
| 113 | return longitude; |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Returns the latitude of this position in radians. |
| 118 | * |
| 119 | * @return a <code>Measurement</code> object in <code>Unit.rad</code> |
| 120 | * representing the latitude, or <code>null</code> if the latitude is |
| 121 | * not known.. |
| 122 | */ |
| 123 | public Measurement getLatitude() { |
| 124 | return latitude; |
| 125 | } |
| 126 | |
| 127 | /** |
| 128 | * Returns the ground speed of this position in meters per second. |
| 129 | * |
| 130 | * @return a <code>Measurement</code> object in <code>Unit.m_s</code> |
| 131 | * representing the speed, or <code>null</code> if the speed is not |
| 132 | * known.. |
| 133 | */ |
| 134 | public Measurement getSpeed() { |
| 135 | return speed; |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Returns the track of this position in radians as a compass heading. The |
| 140 | * track is the extrapolation of previous previously measured positions to a |
| 141 | * future position. |
| 142 | * |
| 143 | * @return a <code>Measurement</code> object in <code>Unit.rad</code> |
| 144 | * representing the track, or <code>null</code> if the track is not |
| 145 | * known.. |
| 146 | */ |
| 147 | public Measurement getTrack() { |
| 148 | return track; |
| 149 | } |
| 150 | |
| 151 | private static final double LON_RANGE = Math.PI; |
| 152 | private static final double LAT_RANGE = Math.PI / 2.0D; |
| 153 | |
| 154 | /** |
| 155 | * Verify the longitude and latitude parameters so they fit the normal |
| 156 | * coordinate system. A latitude is between -90 (south) and +90 (north). A A |
| 157 | * longitude is between -180 (Western hemisphere) and +180 (eastern |
| 158 | * hemisphere). This method first normalizes the latitude and longitude |
| 159 | * between +/- 180. If the |latitude| > 90, then the longitude is added 180 |
| 160 | * and the latitude is normalized to fit +/-90. (Example are with degrees |
| 161 | * though radians are used) <br> |
| 162 | * No normalization takes place when either lon or lat is null. |
| 163 | */ |
| 164 | private void normalizeLatLon() { |
| 165 | if (longitude == null || latitude == null) |
| 166 | return; |
| 167 | double dlon = longitude.getValue(); |
| 168 | double dlat = latitude.getValue(); |
| 169 | if (dlon >= -LON_RANGE && dlon < LON_RANGE && dlat >= -LAT_RANGE |
| 170 | && dlat <= LAT_RANGE) |
| 171 | return; |
| 172 | dlon = normalize(dlon, LON_RANGE); |
| 173 | dlat = normalize(dlat, LAT_RANGE * 2.0D); // First over 180 degree |
| 174 | // Check if we have to move to other side of the earth |
| 175 | if (dlat > LAT_RANGE || dlat < -LAT_RANGE) { |
| 176 | dlon = normalize(dlon - LON_RANGE, LON_RANGE); |
| 177 | dlat = normalize((LAT_RANGE * 2.0D) - dlat, LAT_RANGE); |
| 178 | } |
| 179 | longitude = new Measurement(dlon, longitude.getError(), longitude |
| 180 | .getUnit(), longitude.getTime()); |
| 181 | latitude = new Measurement(dlat, latitude.getError(), latitude |
| 182 | .getUnit(), latitude.getTime()); |
| 183 | } |
| 184 | |
| 185 | /** |
| 186 | * This function normalizes the a value according to a range. This is not |
| 187 | * simple modulo (as I thought when I started), but requires some special |
| 188 | * handling. For positive numbers we subtract 2*range from the number so |
| 189 | * that end up between -/+ range. For negative numbers we add this value. |
| 190 | * For example, if the value is 270 and the range is +/- 180. Then sign=1 so |
| 191 | * the (int) factor becomes 270+180/360 = 1. This means that 270-360=-90 is |
| 192 | * the result. (degrees are only used to make it easier to understand, this |
| 193 | * function is agnostic for radians/degrees). The result will be in |
| 194 | * [range,range> The algorithm is not very fast, but it handling the |
| 195 | * [> ranges made it very messy using integer arithmetic, and this is |
| 196 | * very readable. Note that it is highly unlikely that this method is called |
| 197 | * in normal situations. Normally input values to position are already |
| 198 | * normalized because they come from a GPS. And this is much more readable. |
| 199 | * |
| 200 | * @param value The value that needs adjusting |
| 201 | * @param range -range = < value < range |
| 202 | */ |
| 203 | private double normalize(double value, double range) { |
| 204 | double twiceRange = 2.0D * range; |
| 205 | while (value >= range) { |
| 206 | value -= twiceRange; |
| 207 | } |
| 208 | while (value < -range) { |
| 209 | value += twiceRange; |
| 210 | } |
| 211 | return value; |
| 212 | } |
| 213 | |
| 214 | private static final double TRACK_RANGE = Math.PI * 2.0D; |
| 215 | |
| 216 | /** |
| 217 | * Normalize track to be a value such that: 0 <= value < +2PI. This |
| 218 | * corresponds to 0 deg to +360 deg. 0 is North 0.5*PI is East PI is South |
| 219 | * 1.5*PI is West |
| 220 | * |
| 221 | * @param track Value to be normalized |
| 222 | * @return Normalized value |
| 223 | */ |
| 224 | private Measurement normalizeTrack(Measurement track) { |
| 225 | double value = track.getValue(); |
| 226 | if ((0.0D <= value) && (value < TRACK_RANGE)) { |
| 227 | return track; /* value is already normalized */ |
| 228 | } |
| 229 | value %= TRACK_RANGE; |
| 230 | if (value < 0.0D) { |
| 231 | value += TRACK_RANGE; |
| 232 | } |
| 233 | return new Measurement(value, track.getError(), track.getUnit(), track |
| 234 | .getTime()); |
| 235 | } |
| 236 | } |