159b2e6871c65f58fdad78cd7229c292f6a177578Scott Bartapackage jme3tools.navigation;
259b2e6871c65f58fdad78cd7229c292f6a177578Scott Bartaimport java.awt.Point;
359b2e6871c65f58fdad78cd7229c292f6a177578Scott Bartaimport java.text.DecimalFormat;
459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta/*
659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * To change this template, choose Tools | Templates
759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * and open the template in the editor.
859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta */
959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
1059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
1159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta/**
1259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * A representation of the actual map in terms of lat/long and x,y co-ordinates.
1359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * The Map class contains various helper methods such as methods for determining
1459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * the pixel positions for lat/long co-ordinates and vice versa.
1559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta *
1659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * @author Cormac Gebruers
1759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * @author Benjamin Jakobus
1859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * @version 1.0
1959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * @since 1.0
2059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta */
2159b2e6871c65f58fdad78cd7229c292f6a177578Scott Bartapublic class MapModel2D {
2259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
2359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The number of radians per degree */
2459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private final static double RADIANS_PER_DEGREE = 57.2957;
2559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
2659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The number of degrees per radian */
2759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private final static double DEGREES_PER_RADIAN = 0.0174532925;
2859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
2959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The map's width in longitude */
3059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360;
3159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
3259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The top right hand corner of the map */
3359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private Position centre;
3459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
3559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The x and y co-ordinates for the viewport's centre */
3659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private int xCentre;
3759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private int yCentre;
3859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
3959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The width (in pixels) of the viewport holding the map */
4059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private int viewportWidth;
4159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
4259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The viewport height in pixels */
4359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private int viewportHeight;
4459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
4559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The number of minutes that one pixel represents */
4659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private double minutesPerPixel;
4759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
4859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
4959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Constructor
5059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param viewportWidth the pixel width of the viewport (component) in which
5159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *        the map is displayed
5259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
5359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
5459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public MapModel2D(int viewportWidth) {
5559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        try {
5659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            this.centre = new Position(0, 0);
5759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } catch (InvalidPositionException e) {
5859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            e.printStackTrace();
5959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
6059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
6159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.viewportWidth = viewportWidth;
6259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
6359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Calculate the number of minutes that one pixel represents along the longitude
6459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        calculateMinutesPerPixel(DEFAULT_MAP_WIDTH_LONGITUDE);
6559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
6659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Calculate the viewport height based on its width and the number of degrees (85)
6759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // in our map
6859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        viewportHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerPixel) * 2;
6959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta//        viewportHeight = viewportWidth; // REMOVE!!!
7059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Determine the map's x,y centre
7159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        xCentre = viewportWidth / 2;
7259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        yCentre = viewportHeight / 2;
7359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
7459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
7559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
7659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the height of the viewport in pixels
7759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return the height of the viewport in pixels
7859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 0.1
7959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
8059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public int getViewportPixelHeight() {
8159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return viewportHeight;
8259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
8359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
8459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
8559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Calculates the number of minutes per pixels using a given
8659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * map width in longitude
8759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param mapWidthInLongitude
8859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
8959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
9059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void calculateMinutesPerPixel(double mapWidthInLongitude) {
9159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        minutesPerPixel = (mapWidthInLongitude * 60) / (double) viewportWidth;
9259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
9359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
9459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
9559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the width of the viewport in pixels
9659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return the width of the viewport in pixels
9759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 0.1
9859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
9959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public int getViewportPixelWidth() {
10059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return viewportWidth;
10159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
10259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
10359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setViewportWidth(int viewportWidth) {
10459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.viewportWidth = viewportWidth;
10559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
10659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
10759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setViewportHeight(int viewportHeight) {
10859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.viewportHeight = viewportHeight;
10959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
11059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
11159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setCentre(Position centre) {
11259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.centre = centre;
11359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
11459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
11559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
11659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the number of minutes there are per pixel
11759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return the number of minutes per pixel
11859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
11959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
12059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public double getMinutesPerPixel() {
12159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return minutesPerPixel;
12259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
12359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
12459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public double getMetersPerPixel() {
12559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return 1853 * minutesPerPixel;
12659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
12759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
12859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setMinutesPerPixel(double minutesPerPixel) {
12959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.minutesPerPixel = minutesPerPixel;
13059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
13159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
13259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
13359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Converts a latitude/longitude position into a pixel co-ordinate
13459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param position the position to convert
13559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return {@code Point} a pixel co-ordinate
13659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
13759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
13859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public Point toPixel(Position position) {
13959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Get the distance between position and the centre for calculating
14059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // the position's longitude translation
14159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double distance = NavCalculator.computeLongDiff(centre.getLongitude(),
14259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                position.getLongitude());
14359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
14459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Use the distance from the centre to calculate the pixel x co-ordinate
14559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double distanceInPixels = (distance / minutesPerPixel);
14659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
14759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Use the difference in meridional parts to calculate the pixel y co-ordinate
14859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(),
14959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                position.getLatitude());
15059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
15159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        int x = 0;
15259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        int y = 0;
15359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
15459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        if (centre.getLatitude() == position.getLatitude()) {
15559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            y = yCentre;
15659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
15759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        if (centre.getLongitude() == position.getLongitude()) {
15859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre;
15959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
16059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
16159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Distinguish between northern and southern hemisphere for latitude calculations
16259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) {
16359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is north. Position is north of centre
16459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            y = yCentre + (int) ((dmp) / minutesPerPixel);
16559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) {
16659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is north. Position is south of centre
16759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            y = yCentre - (int) ((dmp) / minutesPerPixel);
16859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) {
16959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is south. Position is north of centre
17059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            y = yCentre + (int) ((dmp) / minutesPerPixel);
17159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) {
17259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is south. Position is south of centre
17359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            y = yCentre - (int) ((dmp) / minutesPerPixel);
17459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) {
17559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is at the equator. Position is north of the equator
17659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            y = yCentre + (int) ((dmp) / minutesPerPixel);
17759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) {
17859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is at the equator. Position is south of the equator
17959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            y = yCentre - (int) ((dmp) / minutesPerPixel);
18059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
18159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
18259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Distinguish between western and eastern hemisphere for longitude calculations
18359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) {
18459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is west. Position is west of centre
18559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre - (int) distanceInPixels;
18659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) {
18759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is west. Position is south of centre
18859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre + (int) distanceInPixels;
18959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) {
19059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is east. Position is west of centre
19159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre - (int) distanceInPixels;
19259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) {
19359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is east. Position is east of centre
19459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre + (int) distanceInPixels;
19559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) {
19659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is at the equator. Position is east of centre
19759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre + (int) distanceInPixels;
19859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) {
19959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is at the equator. Position is west of centre
20059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre - (int) distanceInPixels;
20159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
20259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
20359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Distinguish between northern and souterhn hemisphere for longitude calculations
20459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return new Point(x, y);
20559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
20659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
20759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
20859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Converts a pixel position into a mercator position
20959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param p {@link Point} object that you wish to convert into
21059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *        longitude / latiude
21159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return the converted {@code Position} object
21259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
21359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
21459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public Position toPosition(Point p) {
21559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double lat, lon;
21659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        Position pos = null;
21759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        try {
21859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            Point pixelCentre = toPixel(new Position(0, 0));
21959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
22059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Get the distance between position and the centre
22159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            double xDistance = distance(xCentre, p.getX());
22259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            double yDistance = distance(pixelCentre.getY(), p.getY());
22359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            double lonDistanceInDegrees = (xDistance * minutesPerPixel) / 60;
22459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            double mp = (yDistance * minutesPerPixel);
22559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // If we are zoomed in past a certain point, then use linear search.
22659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Otherwise use binary search
22759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (getMinutesPerPixel() < 0.05) {
22859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                lat = findLat(mp, getCentre().getLatitude());
22959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                if (lat == -1000) {
23059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                    System.out.println("lat: " + lat);
23159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                }
23259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            } else {
23359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                lat = findLat(mp, 0.0, 85.0);
23459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
23559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            lon = (p.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees
23659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                    : centre.getLongitude() + lonDistanceInDegrees);
23759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
23859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (p.getY() > pixelCentre.getY()) {
23959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                lat = -1 * lat;
24059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
24159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (lat == -1000 || lon == -1000) {
24259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                return pos;
24359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
24459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            pos = new Position(lat, lon);
24559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } catch (InvalidPositionException ipe) {
24659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            ipe.printStackTrace();
24759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
24859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return pos;
24959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
25059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
25159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
25259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Calculates distance between two points on the map in pixels
25359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param a
25459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param b
25559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return distance the distance between a and b in pixels
25659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
25759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
25859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private double distance(double a, double b) {
25959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return Math.abs(a - b);
26059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
26159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
26259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
26359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Defines the centre of the map in pixels
26459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param p <code>Point</code> object denoting the map's new centre
26559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
26659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
26759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setCentre(Point p) {
26859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        try {
26959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            Position newCentre = toPosition(p);
27059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (newCentre != null) {
27159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                centre = newCentre;
27259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
27359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } catch (Exception e) {
27459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            e.printStackTrace();
27559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
27659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
27759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
27859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
27959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Sets the map's xCentre
28059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param xCentre
28159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
28259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
28359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setXCentre(int xCentre) {
28459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.xCentre = xCentre;
28559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
28659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
28759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
28859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Sets the map's yCentre
28959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param yCentre
29059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
29159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
29259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setYCentre(int yCentre) {
29359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.yCentre = yCentre;
29459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
29559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
29659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
29759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the pixel (x,y) centre of the map
29859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return {@link Point) object marking the map's (x,y) centre
29959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
30059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
30159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public Point getPixelCentre() {
30259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return new Point(xCentre, yCentre);
30359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
30459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
30559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
30659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the {@code Position} centre of the map
30759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return {@code Position} object marking the map's (lat, long) centre
30859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
30959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
31059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public Position getCentre() {
31159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return centre;
31259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
31359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
31459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
31559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Uses binary search to find the latitude of a given MP.
31659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
31759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param mp maridian part
31859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param low
31959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param high
32059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return the latitude of the MP value
32159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
32259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
32359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private double findLat(double mp, double low, double high) {
32459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        DecimalFormat form = new DecimalFormat("#.####");
32559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        mp = Math.round(mp);
32659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double midLat = (low + high) / 2.0;
32759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // ctr is used to make sure that with some
32859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // numbers which can't be represented exactly don't inifitely repeat
32959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
33059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
33159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        while (low <= high) {
33259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (guessMP == mp) {
33359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                return midLat;
33459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            } else {
33559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                if (guessMP > mp) {
33659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                    high = midLat - 0.0001;
33759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                } else {
33859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                    low = midLat + 0.0001;
33959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                }
34059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
34159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
34259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            midLat = Double.valueOf(form.format(((low + high) / 2.0)));
34359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
34459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            guessMP = Math.round(guessMP);
34559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
34659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return -1000;
34759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
34859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
34959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
35059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Uses linear search to find the latitude of a given MP
35159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param mp the meridian part for which to find the latitude
35259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param previousLat the previous latitude. Used as a upper / lower bound
35359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return the latitude of the MP value
35459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
35559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private double findLat(double mp, double previousLat) {
35659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        DecimalFormat form = new DecimalFormat("#.#####");
35759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        mp = Double.parseDouble(form.format(mp));
35859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double guessMP;
35959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) {
36059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat);
36159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            guessMP = Double.parseDouble(form.format(guessMP));
36259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (guessMP == mp || Math.abs(guessMP - mp) < 0.001) {
36359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                return lat;
36459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
36559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
36659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return -1000;
36759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
36859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta}
369