1package jme3tools.navigation; 2import java.awt.Point; 3import 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 */ 21public 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 */ 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 */ 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 */ 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 */ 99 public int getViewportPixelWidth() { 100 return viewportWidth; 101 } 102 103 public void setViewportWidth(int viewportWidth) { 104 this.viewportWidth = viewportWidth; 105 } 106 107 public void setViewportHeight(int viewportHeight) { 108 this.viewportHeight = viewportHeight; 109 } 110 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 */ 120 public double getMinutesPerPixel() { 121 return minutesPerPixel; 122 } 123 124 public double getMetersPerPixel() { 125 return 1853 * minutesPerPixel; 126 } 127 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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