159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta/*
259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * To change this template, choose Tools | Templates
359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * and open the template in the editor.
459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta */
559b2e6871c65f58fdad78cd7229c292f6a177578Scott Bartapackage jme3tools.navigation;
659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
759b2e6871c65f58fdad78cd7229c292f6a177578Scott Bartaimport com.jme3.math.Vector3f;
859b2e6871c65f58fdad78cd7229c292f6a177578Scott Bartaimport java.text.DecimalFormat;
959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
1059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
1159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta/**
1259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * A representation of the actual map in terms of lat/long and x,y,z co-ordinates.
1359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * The Map class contains various helper methods such as methods for determining
1459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * the world unit positions for lat/long coordinates and vice versa. This map projection
1559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * does not handle screen/pixel coordinates.
1659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta *
1759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * @author Benjamin Jakobus (thanks to Cormac Gebruers)
1859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * @version 1.0
1959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta * @since 1.0
2059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta */
2159b2e6871c65f58fdad78cd7229c292f6a177578Scott Bartapublic class MapModel3D {
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 zCentre;
3859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
3959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The width (in world units (wu)) of the viewport holding the map */
4059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private int worldWidth;
4159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
4259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The viewport height in pixels */
4359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private int worldHeight;
4459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
4559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /* The number of minutes that one pixel represents */
4659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private double minutesPerWorldUnit;
4759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
4859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
4959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Constructor.
5059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
5159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param worldWidth         The world unit width the map's area
5259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
5359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
5459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public MapModel3D(int worldWidth) {
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.worldWidth = worldWidth;
6259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
6359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Calculate the number of minutes that one pixel represents along the longitude
6459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        calculateMinutesPerWorldUnit(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        worldHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerWorldUnit) * 2;
6959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
7059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Determine the map's x,y centre
7159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        xCentre = 0;
7259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        zCentre = 0;
7359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta//        xCentre = worldWidth / 2;
7459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta//        zCentre = worldHeight / 2;
7559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
7659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
7759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
7859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the height of the viewport in pixels.
7959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
8059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return          The height of the viewport in pixels.
8159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
8259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
8359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public int getWorldHeight() {
8459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return worldHeight;
8559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
8659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
8759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
8859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Calculates the number of minutes per pixels using a given
8959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * map width in longitude.
9059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
9159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param mapWidthInLongitude               The map's with in degrees of longitude.
9259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
9359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
9459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void calculateMinutesPerWorldUnit(double mapWidthInLongitude) {
9559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Multiply mapWidthInLongitude by 60 to convert it to minutes.
9659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        minutesPerWorldUnit = (mapWidthInLongitude * 60) / (double) worldWidth;
9759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
9859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
9959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
10059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the width of the viewport in pixels.
10159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
10259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return              The width of the viewport in pixels.
10359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
10459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
10559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public int getWorldWidth() {
10659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return worldWidth;
10759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
10859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
10959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
11059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Sets the world's desired width.
11159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
11259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param viewportWidth     The world's desired width in WU.
11359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
11459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
11559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setWorldWidth(int viewportWidth) {
11659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.worldWidth = viewportWidth;
11759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
11859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
11959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     /**
12059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Sets the world's desired height.
12159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
12259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param viewportHeight     The world's desired height in WU.
12359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
12459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
12559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setWorldHeight(int viewportHeight) {
12659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.worldHeight = viewportHeight;
12759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
12859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
12959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
13059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Sets the map's centre.
13159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
13259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param centre            The <code>Position</code> denoting the map's
13359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *                          desired centre.
13459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
13559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
13659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setCentre(Position centre) {
13759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        this.centre = centre;
13859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
13959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
14059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
14159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the number of minutes there are per WU.
14259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
14359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return                  The number of minutes per WU.
14459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
14559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
14659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public double getMinutesPerWu() {
14759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return minutesPerWorldUnit;
14859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
14959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
15059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
15159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the meters per WU.
15259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
15359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return                  The meters per WU.
15459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
15559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
15659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public double getMetersPerWu() {
15759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return 1853 * minutesPerWorldUnit;
15859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
15959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
16059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
16159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Converts a latitude/longitude position into a WU coordinate.
16259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
16359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param position          The <code>Position</code> to convert.
16459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return                  The <code>Point</code> a pixel coordinate.
16559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
16659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
16759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public Vector3f toWorldUnit(Position position) {
16859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Get the difference between position and the centre for calculating
16959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // the position's longitude translation
17059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double distance = NavCalculator.computeLongDiff(centre.getLongitude(),
17159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                position.getLongitude());
17259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
17359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Use the difference from the centre to calculate the pixel x co-ordinate
17459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double distanceInPixels = (distance / minutesPerWorldUnit);
17559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
17659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Use the difference in meridional parts to calculate the pixel y co-ordinate
17759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(),
17859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                position.getLatitude());
17959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
18059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        int x = 0;
18159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        int z = 0;
18259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
18359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        if (centre.getLatitude() == position.getLatitude()) {
18459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            z = zCentre;
18559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
18659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        if (centre.getLongitude() == position.getLongitude()) {
18759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre;
18859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
18959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
19059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Distinguish between northern and southern hemisphere for latitude calculations
19159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) {
19259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is north. Position is north of centre
19359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            z = zCentre - (int) ((dmp) / minutesPerWorldUnit);
19459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) {
19559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is north. Position is south of centre
19659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            z = zCentre + (int) ((dmp) / minutesPerWorldUnit);
19759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) {
19859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is south. Position is north of centre
19959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            z = zCentre - (int) ((dmp) / minutesPerWorldUnit);
20059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) {
20159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is south. Position is south of centre
20259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            z = zCentre + (int) ((dmp) / minutesPerWorldUnit);
20359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) {
20459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is at the equator. Position is north of the equator
20559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            z = zCentre - (int) ((dmp) / minutesPerWorldUnit);
20659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) {
20759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is at the equator. Position is south of the equator
20859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            z = zCentre + (int) ((dmp) / minutesPerWorldUnit);
20959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
21059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
21159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Distinguish between western and eastern hemisphere for longitude calculations
21259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) {
21359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is west. Position is west of centre
21459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre - (int) distanceInPixels;
21559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) {
21659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is west. Position is south of centre
21759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre + (int) distanceInPixels;
21859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) {
21959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is east. Position is west of centre
22059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre - (int) distanceInPixels;
22159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) {
22259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is east. Position is east of centre
22359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre + (int) distanceInPixels;
22459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) {
22559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is at the equator. Position is east of centre
22659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre + (int) distanceInPixels;
22759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) {
22859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Centre is at the equator. Position is west of centre
22959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            x = xCentre - (int) distanceInPixels;
23059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
23159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
23259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // Distinguish between northern and southern hemisphere for longitude calculations
23359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return new Vector3f(x, 0, z);
23459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
23559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
23659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
23759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Converts a world position into a Mercator position.
23859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
23959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param posVec                     <code>Vector</code> containing the world unit
24059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *                              coordinates that are to be converted into
24159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *                              longitude / latitude coordinates.
24259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return                      The resulting <code>Position</code> in degrees of
24359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *                              latitude and longitude.
24459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
24559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
24659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public Position toPosition(Vector3f posVec) {
24759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double lat, lon;
24859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        Position pos = null;
24959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        try {
25059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            Vector3f worldCentre = toWorldUnit(new Position(0, 0));
25159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
25259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Get the difference between position and the centre
25359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            double xDistance = difference(xCentre, posVec.getX());
25459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            double yDistance = difference(worldCentre.getZ(), posVec.getZ());
25559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            double lonDistanceInDegrees = (xDistance * minutesPerWorldUnit) / 60;
25659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            double mp = (yDistance * minutesPerWorldUnit);
25759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // If we are zoomed in past a certain point, then use linear search.
25859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            // Otherwise use binary search
25959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (getMinutesPerWu() < 0.05) {
26059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                lat = findLat(mp, getCentre().getLatitude());
26159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                if (lat == -1000) {
26259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                    System.out.println("lat: " + lat);
26359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                }
26459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            } else {
26559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                lat = findLat(mp, 0.0, 85.0);
26659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
26759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            lon = (posVec.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees
26859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                    : centre.getLongitude() + lonDistanceInDegrees);
26959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
27059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (posVec.getZ() > worldCentre.getZ()) {
27159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                lat = -1 * lat;
27259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
27359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (lat == -1000 || lon == -1000) {
27459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                return pos;
27559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
27659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            pos = new Position(lat, lon);
27759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } catch (InvalidPositionException ipe) {
27859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            ipe.printStackTrace();
27959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
28059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return pos;
28159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
28259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
28359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
28459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Calculates difference between two points on the map in WU.
28559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
28659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param a
28759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param b
28859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return difference           The difference between a and b in WU.
28959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
29059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
29159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private double difference(double a, double b) {
29259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return Math.abs(a - b);
29359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
29459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
29559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
29659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Defines the centre of the map in pixels.
29759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
29859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param posVec             <code>Vector3f</code> object denoting the map's new centre.
29959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
30059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
30159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public void setCentre(Vector3f posVec) {
30259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        try {
30359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            Position newCentre = toPosition(posVec);
30459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (newCentre != null) {
30559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                centre = newCentre;
30659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
30759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        } catch (Exception e) {
30859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            e.printStackTrace();
30959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
31059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
31159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
31259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
31359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the WU (x,y,z) centre of the map.
31459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
31559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return              <code>Vector3f</code> object marking the map's (x,y) centre.
31659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
31759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
31859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public Vector3f getCentreWu() {
31959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return new Vector3f(xCentre, 0, zCentre);
32059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
32159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
32259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
32359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Returns the <code>Position</code> centre of the map.
32459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
32559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return              <code>Position</code> object marking the map's (lat, long)
32659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *                      centre.
32759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
32859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
32959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    public Position getCentre() {
33059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return centre;
33159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
33259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
33359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
33459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Uses binary search to find the latitude of a given MP.
33559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
33659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param mp                Maridian part whose latitude to determine.
33759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param low               Minimum latitude bounds.
33859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param high              Maximum latitude bounds.
33959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return                  The latitude of the MP value
34059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
34159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
34259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private double findLat(double mp, double low, double high) {
34359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        DecimalFormat form = new DecimalFormat("#.####");
34459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        mp = Math.round(mp);
34559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double midLat = (low + high) / 2.0;
34659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // ctr is used to make sure that with some
34759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        // numbers which can't be represented exactly don't inifitely repeat
34859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
34959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
35059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        while (low <= high) {
35159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (guessMP == mp) {
35259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                return midLat;
35359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            } else {
35459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                if (guessMP > mp) {
35559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                    high = midLat - 0.0001;
35659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                } else {
35759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                    low = midLat + 0.0001;
35859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                }
35959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
36059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
36159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            midLat = Double.valueOf(form.format(((low + high) / 2.0)));
36259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
36359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            guessMP = Math.round(guessMP);
36459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
36559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return -1000;
36659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
36759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta
36859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    /**
36959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * Uses linear search to find the latitude of a given MP.
37059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     *
37159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param mp                The meridian part for which to find the latitude.
37259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @param previousLat       The previous latitude. Used as a upper / lower bound.
37359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @return                  The latitude of the MP value.
37459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     * @since 1.0
37559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta     */
37659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    private double findLat(double mp, double previousLat) {
37759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        DecimalFormat form = new DecimalFormat("#.#####");
37859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        mp = Double.parseDouble(form.format(mp));
37959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        double guessMP;
38059b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) {
38159b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat);
38259b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            guessMP = Double.parseDouble(form.format(guessMP));
38359b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            if (guessMP == mp || Math.abs(guessMP - mp) < 0.05) {
38459b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta                return lat;
38559b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta            }
38659b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        }
38759b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta        return -1000;
38859b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta    }
38959b2e6871c65f58fdad78cd7229c292f6a177578Scott Barta}
390