Home | History | Annotate | Download | only in navigation
      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      */
     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