MapModel3D.java 14 KB

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