blob: 1190cf508606dbac3a432323b00067cc8f19449f [file] [log] [blame]
Michael E. Rodriguez86f57b02006-03-29 21:05:08 +00001/*
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 */
18package org.osgi.util.position;
19
20import 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 */
38public 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&gt; The algorithm is not very fast, but it handling the
195 * [&gt; 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}