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