• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * To change this template, choose Tools | Templates
3  * and open the template in the editor.
4  */
5 package jme3tools.navigation;
6 
7 import com.jme3.math.Vector3f;
8 import java.text.DecimalFormat;
9 
10 
11 /**
12  * A representation of the actual map in terms of lat/long and x,y,z co-ordinates.
13  * The Map class contains various helper methods such as methods for determining
14  * the world unit positions for lat/long coordinates and vice versa. This map projection
15  * does not handle screen/pixel coordinates.
16  *
17  * @author Benjamin Jakobus (thanks to Cormac Gebruers)
18  * @version 1.0
19  * @since 1.0
20  */
21 public class MapModel3D {
22 
23     /* The number of radians per degree */
24     private final static double RADIANS_PER_DEGREE = 57.2957;
25 
26     /* The number of degrees per radian */
27     private final static double DEGREES_PER_RADIAN = 0.0174532925;
28 
29     /* The map's width in longitude */
30     public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360;
31 
32     /* The top right hand corner of the map */
33     private Position centre;
34 
35     /* The x and y co-ordinates for the viewport's centre */
36     private int xCentre;
37     private int zCentre;
38 
39     /* The width (in world units (wu)) of the viewport holding the map */
40     private int worldWidth;
41 
42     /* The viewport height in pixels */
43     private int worldHeight;
44 
45     /* The number of minutes that one pixel represents */
46     private double minutesPerWorldUnit;
47 
48     /**
49      * Constructor.
50      *
51      * @param worldWidth         The world unit width the map's area
52      * @since 1.0
53      */
MapModel3D(int worldWidth)54     public MapModel3D(int worldWidth) {
55         try {
56             this.centre = new Position(0, 0);
57         } catch (InvalidPositionException e) {
58             e.printStackTrace();
59         }
60 
61         this.worldWidth = worldWidth;
62 
63         // Calculate the number of minutes that one pixel represents along the longitude
64         calculateMinutesPerWorldUnit(DEFAULT_MAP_WIDTH_LONGITUDE);
65 
66         // Calculate the viewport height based on its width and the number of degrees (85)
67         // in our map
68         worldHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerWorldUnit) * 2;
69 
70         // Determine the map's x,y centre
71         xCentre = 0;
72         zCentre = 0;
73 //        xCentre = worldWidth / 2;
74 //        zCentre = worldHeight / 2;
75     }
76 
77     /**
78      * Returns the height of the viewport in pixels.
79      *
80      * @return          The height of the viewport in pixels.
81      * @since 1.0
82      */
getWorldHeight()83     public int getWorldHeight() {
84         return worldHeight;
85     }
86 
87     /**
88      * Calculates the number of minutes per pixels using a given
89      * map width in longitude.
90      *
91      * @param mapWidthInLongitude               The map's with in degrees of longitude.
92      * @since 1.0
93      */
calculateMinutesPerWorldUnit(double mapWidthInLongitude)94     public void calculateMinutesPerWorldUnit(double mapWidthInLongitude) {
95         // Multiply mapWidthInLongitude by 60 to convert it to minutes.
96         minutesPerWorldUnit = (mapWidthInLongitude * 60) / (double) worldWidth;
97     }
98 
99     /**
100      * Returns the width of the viewport in pixels.
101      *
102      * @return              The width of the viewport in pixels.
103      * @since 1.0
104      */
getWorldWidth()105     public int getWorldWidth() {
106         return worldWidth;
107     }
108 
109     /**
110      * Sets the world's desired width.
111      *
112      * @param viewportWidth     The world's desired width in WU.
113      * @since 1.0
114      */
setWorldWidth(int viewportWidth)115     public void setWorldWidth(int viewportWidth) {
116         this.worldWidth = viewportWidth;
117     }
118 
119      /**
120      * Sets the world's desired height.
121      *
122      * @param viewportHeight     The world's desired height in WU.
123      * @since 1.0
124      */
setWorldHeight(int viewportHeight)125     public void setWorldHeight(int viewportHeight) {
126         this.worldHeight = viewportHeight;
127     }
128 
129     /**
130      * Sets the map's centre.
131      *
132      * @param centre            The <code>Position</code> denoting the map's
133      *                          desired centre.
134      * @since 1.0
135      */
setCentre(Position centre)136     public void setCentre(Position centre) {
137         this.centre = centre;
138     }
139 
140     /**
141      * Returns the number of minutes there are per WU.
142      *
143      * @return                  The number of minutes per WU.
144      * @since 1.0
145      */
getMinutesPerWu()146     public double getMinutesPerWu() {
147         return minutesPerWorldUnit;
148     }
149 
150     /**
151      * Returns the meters per WU.
152      *
153      * @return                  The meters per WU.
154      * @since 1.0
155      */
getMetersPerWu()156     public double getMetersPerWu() {
157         return 1853 * minutesPerWorldUnit;
158     }
159 
160     /**
161      * Converts a latitude/longitude position into a WU coordinate.
162      *
163      * @param position          The <code>Position</code> to convert.
164      * @return                  The <code>Point</code> a pixel coordinate.
165      * @since 1.0
166      */
toWorldUnit(Position position)167     public Vector3f toWorldUnit(Position position) {
168         // Get the difference between position and the centre for calculating
169         // the position's longitude translation
170         double distance = NavCalculator.computeLongDiff(centre.getLongitude(),
171                 position.getLongitude());
172 
173         // Use the difference from the centre to calculate the pixel x co-ordinate
174         double distanceInPixels = (distance / minutesPerWorldUnit);
175 
176         // Use the difference in meridional parts to calculate the pixel y co-ordinate
177         double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(),
178                 position.getLatitude());
179 
180         int x = 0;
181         int z = 0;
182 
183         if (centre.getLatitude() == position.getLatitude()) {
184             z = zCentre;
185         }
186         if (centre.getLongitude() == position.getLongitude()) {
187             x = xCentre;
188         }
189 
190         // Distinguish between northern and southern hemisphere for latitude calculations
191         if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) {
192             // Centre is north. Position is north of centre
193             z = zCentre - (int) ((dmp) / minutesPerWorldUnit);
194         } else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) {
195             // Centre is north. Position is south of centre
196             z = zCentre + (int) ((dmp) / minutesPerWorldUnit);
197         } else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) {
198             // Centre is south. Position is north of centre
199             z = zCentre - (int) ((dmp) / minutesPerWorldUnit);
200         } else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) {
201             // Centre is south. Position is south of centre
202             z = zCentre + (int) ((dmp) / minutesPerWorldUnit);
203         } else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) {
204             // Centre is at the equator. Position is north of the equator
205             z = zCentre - (int) ((dmp) / minutesPerWorldUnit);
206         } else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) {
207             // Centre is at the equator. Position is south of the equator
208             z = zCentre + (int) ((dmp) / minutesPerWorldUnit);
209         }
210 
211         // Distinguish between western and eastern hemisphere for longitude calculations
212         if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) {
213             // Centre is west. Position is west of centre
214             x = xCentre - (int) distanceInPixels;
215         } else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) {
216             // Centre is west. Position is south of centre
217             x = xCentre + (int) distanceInPixels;
218         } else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) {
219             // Centre is east. Position is west of centre
220             x = xCentre - (int) distanceInPixels;
221         } else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) {
222             // Centre is east. Position is east of centre
223             x = xCentre + (int) distanceInPixels;
224         } else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) {
225             // Centre is at the equator. Position is east of centre
226             x = xCentre + (int) distanceInPixels;
227         } else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) {
228             // Centre is at the equator. Position is west of centre
229             x = xCentre - (int) distanceInPixels;
230         }
231 
232         // Distinguish between northern and southern hemisphere for longitude calculations
233         return new Vector3f(x, 0, z);
234     }
235 
236     /**
237      * Converts a world position into a Mercator position.
238      *
239      * @param posVec                     <code>Vector</code> containing the world unit
240      *                              coordinates that are to be converted into
241      *                              longitude / latitude coordinates.
242      * @return                      The resulting <code>Position</code> in degrees of
243      *                              latitude and longitude.
244      * @since 1.0
245      */
toPosition(Vector3f posVec)246     public Position toPosition(Vector3f posVec) {
247         double lat, lon;
248         Position pos = null;
249         try {
250             Vector3f worldCentre = toWorldUnit(new Position(0, 0));
251 
252             // Get the difference between position and the centre
253             double xDistance = difference(xCentre, posVec.getX());
254             double yDistance = difference(worldCentre.getZ(), posVec.getZ());
255             double lonDistanceInDegrees = (xDistance * minutesPerWorldUnit) / 60;
256             double mp = (yDistance * minutesPerWorldUnit);
257             // If we are zoomed in past a certain point, then use linear search.
258             // Otherwise use binary search
259             if (getMinutesPerWu() < 0.05) {
260                 lat = findLat(mp, getCentre().getLatitude());
261                 if (lat == -1000) {
262                     System.out.println("lat: " + lat);
263                 }
264             } else {
265                 lat = findLat(mp, 0.0, 85.0);
266             }
267             lon = (posVec.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees
268                     : centre.getLongitude() + lonDistanceInDegrees);
269 
270             if (posVec.getZ() > worldCentre.getZ()) {
271                 lat = -1 * lat;
272             }
273             if (lat == -1000 || lon == -1000) {
274                 return pos;
275             }
276             pos = new Position(lat, lon);
277         } catch (InvalidPositionException ipe) {
278             ipe.printStackTrace();
279         }
280         return pos;
281     }
282 
283     /**
284      * Calculates difference between two points on the map in WU.
285      *
286      * @param a
287      * @param b
288      * @return difference           The difference between a and b in WU.
289      * @since 1.0
290      */
difference(double a, double b)291     private double difference(double a, double b) {
292         return Math.abs(a - b);
293     }
294 
295     /**
296      * Defines the centre of the map in pixels.
297      *
298      * @param posVec             <code>Vector3f</code> object denoting the map's new centre.
299      * @since 1.0
300      */
setCentre(Vector3f posVec)301     public void setCentre(Vector3f posVec) {
302         try {
303             Position newCentre = toPosition(posVec);
304             if (newCentre != null) {
305                 centre = newCentre;
306             }
307         } catch (Exception e) {
308             e.printStackTrace();
309         }
310     }
311 
312     /**
313      * Returns the WU (x,y,z) centre of the map.
314      *
315      * @return              <code>Vector3f</code> object marking the map's (x,y) centre.
316      * @since 1.0
317      */
getCentreWu()318     public Vector3f getCentreWu() {
319         return new Vector3f(xCentre, 0, zCentre);
320     }
321 
322     /**
323      * Returns the <code>Position</code> centre of the map.
324      *
325      * @return              <code>Position</code> object marking the map's (lat, long)
326      *                      centre.
327      * @since 1.0
328      */
getCentre()329     public Position getCentre() {
330         return centre;
331     }
332 
333     /**
334      * Uses binary search to find the latitude of a given MP.
335      *
336      * @param mp                Maridian part whose latitude to determine.
337      * @param low               Minimum latitude bounds.
338      * @param high              Maximum latitude bounds.
339      * @return                  The latitude of the MP value
340      * @since 1.0
341      */
findLat(double mp, double low, double high)342     private double findLat(double mp, double low, double high) {
343         DecimalFormat form = new DecimalFormat("#.####");
344         mp = Math.round(mp);
345         double midLat = (low + high) / 2.0;
346         // ctr is used to make sure that with some
347         // numbers which can't be represented exactly don't inifitely repeat
348         double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
349 
350         while (low <= high) {
351             if (guessMP == mp) {
352                 return midLat;
353             } else {
354                 if (guessMP > mp) {
355                     high = midLat - 0.0001;
356                 } else {
357                     low = midLat + 0.0001;
358                 }
359             }
360 
361             midLat = Double.valueOf(form.format(((low + high) / 2.0)));
362             guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
363             guessMP = Math.round(guessMP);
364         }
365         return -1000;
366     }
367 
368     /**
369      * Uses linear search to find the latitude of a given MP.
370      *
371      * @param mp                The meridian part for which to find the latitude.
372      * @param previousLat       The previous latitude. Used as a upper / lower bound.
373      * @return                  The latitude of the MP value.
374      * @since 1.0
375      */
findLat(double mp, double previousLat)376     private double findLat(double mp, double previousLat) {
377         DecimalFormat form = new DecimalFormat("#.#####");
378         mp = Double.parseDouble(form.format(mp));
379         double guessMP;
380         for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) {
381             guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat);
382             guessMP = Double.parseDouble(form.format(guessMP));
383             if (guessMP == mp || Math.abs(guessMP - mp) < 0.05) {
384                 return lat;
385             }
386         }
387         return -1000;
388     }
389 }
390