You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
394 lines
14 KiB
394 lines
14 KiB
/*
|
|
* Copyright (c) 2009-2020 jMonkeyEngine
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
package jme3tools.navigation;
|
|
|
|
import java.awt.Point;
|
|
import java.text.DecimalFormat;
|
|
|
|
/**
|
|
* A representation of the actual map in terms of lat/long and x,y co-ordinates.
|
|
* The Map class contains various helper methods such as methods for determining
|
|
* the pixel positions for lat/long co-ordinates and vice versa.
|
|
*
|
|
* @author Cormac Gebruers
|
|
* @author Benjamin Jakobus
|
|
* @version 1.0
|
|
* @since 1.0
|
|
*/
|
|
public class MapModel2D {
|
|
|
|
/* The number of radians per degree */
|
|
private final static double RADIANS_PER_DEGREE = 57.2957;
|
|
|
|
/* The number of degrees per radian */
|
|
private final static double DEGREES_PER_RADIAN = 0.0174532925;
|
|
|
|
/* The map's width in longitude */
|
|
public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360;
|
|
|
|
/* The top right hand corner of the map */
|
|
private Position centre;
|
|
|
|
/* The x and y co-ordinates for the viewport's centre */
|
|
private int xCentre;
|
|
private int yCentre;
|
|
|
|
/* The width (in pixels) of the viewport holding the map */
|
|
private int viewportWidth;
|
|
|
|
/* The viewport height in pixels */
|
|
private int viewportHeight;
|
|
|
|
/* The number of minutes that one pixel represents */
|
|
private double minutesPerPixel;
|
|
|
|
/**
|
|
* Constructor
|
|
* @param viewportWidth the pixel width of the viewport (component) in which
|
|
* the map is displayed
|
|
* @since 1.0
|
|
*/
|
|
public MapModel2D(int viewportWidth) {
|
|
try {
|
|
this.centre = new Position(0, 0);
|
|
} catch (InvalidPositionException e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
this.viewportWidth = viewportWidth;
|
|
|
|
// Calculate the number of minutes that one pixel represents along the longitude
|
|
calculateMinutesPerPixel(DEFAULT_MAP_WIDTH_LONGITUDE);
|
|
|
|
// Calculate the viewport height based on its width and the number of degrees (85)
|
|
// in our map
|
|
viewportHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerPixel) * 2;
|
|
// viewportHeight = viewportWidth; // REMOVE!!!
|
|
// Determine the map's x,y centre
|
|
xCentre = viewportWidth / 2;
|
|
yCentre = viewportHeight / 2;
|
|
}
|
|
|
|
/**
|
|
* Returns the height of the viewport in pixels
|
|
* @return the height of the viewport in pixels
|
|
* @since 0.1
|
|
*/
|
|
public int getViewportPixelHeight() {
|
|
return viewportHeight;
|
|
}
|
|
|
|
/**
|
|
* Calculates the number of minutes per pixels using a given
|
|
* map width in longitude
|
|
* @param mapWidthInLongitude
|
|
* @since 1.0
|
|
*/
|
|
public void calculateMinutesPerPixel(double mapWidthInLongitude) {
|
|
minutesPerPixel = (mapWidthInLongitude * 60) / viewportWidth;
|
|
}
|
|
|
|
/**
|
|
* Returns the width of the viewport in pixels
|
|
* @return the width of the viewport in pixels
|
|
* @since 0.1
|
|
*/
|
|
public int getViewportPixelWidth() {
|
|
return viewportWidth;
|
|
}
|
|
|
|
public void setViewportWidth(int viewportWidth) {
|
|
this.viewportWidth = viewportWidth;
|
|
}
|
|
|
|
public void setViewportHeight(int viewportHeight) {
|
|
this.viewportHeight = viewportHeight;
|
|
}
|
|
|
|
public void setCentre(Position centre) {
|
|
this.centre = centre;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of minutes there are per pixel
|
|
* @return the number of minutes per pixel
|
|
* @since 1.0
|
|
*/
|
|
public double getMinutesPerPixel() {
|
|
return minutesPerPixel;
|
|
}
|
|
|
|
public double getMetersPerPixel() {
|
|
return 1853 * minutesPerPixel;
|
|
}
|
|
|
|
public void setMinutesPerPixel(double minutesPerPixel) {
|
|
this.minutesPerPixel = minutesPerPixel;
|
|
}
|
|
|
|
/**
|
|
* Converts a latitude/longitude position into a pixel co-ordinate
|
|
* @param position the position to convert
|
|
* @return {@code Point} a pixel co-ordinate
|
|
* @since 1.0
|
|
*/
|
|
public Point toPixel(Position position) {
|
|
// Get the distance between position and the centre for calculating
|
|
// the position's longitude translation
|
|
double distance = NavCalculator.computeLongDiff(centre.getLongitude(),
|
|
position.getLongitude());
|
|
|
|
// Use the distance from the centre to calculate the pixel x co-ordinate
|
|
double distanceInPixels = (distance / minutesPerPixel);
|
|
|
|
// Use the difference in meridional parts to calculate the pixel y co-ordinate
|
|
double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(),
|
|
position.getLatitude());
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
if (centre.getLatitude() == position.getLatitude()) {
|
|
y = yCentre;
|
|
}
|
|
if (centre.getLongitude() == position.getLongitude()) {
|
|
x = xCentre;
|
|
}
|
|
|
|
// Distinguish between northern and southern hemisphere for latitude calculations
|
|
if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) {
|
|
// Centre is north. Position is north of centre
|
|
y = yCentre + (int) ((dmp) / minutesPerPixel);
|
|
} else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) {
|
|
// Centre is north. Position is south of centre
|
|
y = yCentre - (int) ((dmp) / minutesPerPixel);
|
|
} else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) {
|
|
// Centre is south. Position is north of centre
|
|
y = yCentre + (int) ((dmp) / minutesPerPixel);
|
|
} else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) {
|
|
// Centre is south. Position is south of centre
|
|
y = yCentre - (int) ((dmp) / minutesPerPixel);
|
|
} else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) {
|
|
// Centre is at the equator. Position is north of the equator
|
|
y = yCentre + (int) ((dmp) / minutesPerPixel);
|
|
} else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) {
|
|
// Centre is at the equator. Position is south of the equator
|
|
y = yCentre - (int) ((dmp) / minutesPerPixel);
|
|
}
|
|
|
|
// Distinguish between western and eastern hemisphere for longitude calculations
|
|
if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) {
|
|
// Centre is west. Position is west of centre
|
|
x = xCentre - (int) distanceInPixels;
|
|
} else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) {
|
|
// Centre is west. Position is south of centre
|
|
x = xCentre + (int) distanceInPixels;
|
|
} else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) {
|
|
// Centre is east. Position is west of centre
|
|
x = xCentre - (int) distanceInPixels;
|
|
} else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) {
|
|
// Centre is east. Position is east of centre
|
|
x = xCentre + (int) distanceInPixels;
|
|
} else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) {
|
|
// Centre is at the equator. Position is east of centre
|
|
x = xCentre + (int) distanceInPixels;
|
|
} else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) {
|
|
// Centre is at the equator. Position is west of centre
|
|
x = xCentre - (int) distanceInPixels;
|
|
}
|
|
|
|
// Distinguish between northern and souterhn hemisphere for longitude calculations
|
|
return new Point(x, y);
|
|
}
|
|
|
|
/**
|
|
* Converts a pixel position into a mercator position
|
|
* @param p {@link Point} object that you wish to convert into
|
|
* longitude / latiude
|
|
* @return the converted {@code Position} object
|
|
* @since 1.0
|
|
*/
|
|
public Position toPosition(Point p) {
|
|
double lat, lon;
|
|
Position pos = null;
|
|
try {
|
|
Point pixelCentre = toPixel(new Position(0, 0));
|
|
|
|
// Get the distance between position and the centre
|
|
double xDistance = distance(xCentre, p.getX());
|
|
double yDistance = distance(pixelCentre.getY(), p.getY());
|
|
double lonDistanceInDegrees = (xDistance * minutesPerPixel) / 60;
|
|
double mp = (yDistance * minutesPerPixel);
|
|
// If we are zoomed in past a certain point, then use linear search.
|
|
// Otherwise use binary search
|
|
if (getMinutesPerPixel() < 0.05) {
|
|
lat = findLat(mp, getCentre().getLatitude());
|
|
if (lat == -1000) {
|
|
System.out.println("lat: " + lat);
|
|
}
|
|
} else {
|
|
lat = findLat(mp, 0.0, 85.0);
|
|
}
|
|
lon = (p.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees
|
|
: centre.getLongitude() + lonDistanceInDegrees);
|
|
|
|
if (p.getY() > pixelCentre.getY()) {
|
|
lat = -1 * lat;
|
|
}
|
|
if (lat == -1000 || lon == -1000) {
|
|
return pos;
|
|
}
|
|
pos = new Position(lat, lon);
|
|
} catch (InvalidPositionException ipe) {
|
|
ipe.printStackTrace();
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
/**
|
|
* Calculates distance between two points on the map in pixels
|
|
* @param a
|
|
* @param b
|
|
* @return distance the distance between a and b in pixels
|
|
* @since 1.0
|
|
*/
|
|
private double distance(double a, double b) {
|
|
return Math.abs(a - b);
|
|
}
|
|
|
|
/**
|
|
* Defines the centre of the map in pixels
|
|
* @param p <code>Point</code> object denoting the map's new centre
|
|
* @since 1.0
|
|
*/
|
|
public void setCentre(Point p) {
|
|
try {
|
|
Position newCentre = toPosition(p);
|
|
if (newCentre != null) {
|
|
centre = newCentre;
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the map's xCentre
|
|
* @param xCentre
|
|
* @since 1.0
|
|
*/
|
|
public void setXCentre(int xCentre) {
|
|
this.xCentre = xCentre;
|
|
}
|
|
|
|
/**
|
|
* Sets the map's yCentre
|
|
* @param yCentre
|
|
* @since 1.0
|
|
*/
|
|
public void setYCentre(int yCentre) {
|
|
this.yCentre = yCentre;
|
|
}
|
|
|
|
/**
|
|
* Returns the pixel (x,y) centre of the map
|
|
* @return {@link Point} object marking the map's (x,y) centre
|
|
* @since 1.0
|
|
*/
|
|
public Point getPixelCentre() {
|
|
return new Point(xCentre, yCentre);
|
|
}
|
|
|
|
/**
|
|
* Returns the {@code Position} centre of the map
|
|
* @return {@code Position} object marking the map's (lat, long) centre
|
|
* @since 1.0
|
|
*/
|
|
public Position getCentre() {
|
|
return centre;
|
|
}
|
|
|
|
/**
|
|
* Uses binary search to find the latitude of a given MP.
|
|
*
|
|
* @param mp maridian part
|
|
* @param low
|
|
* @param high
|
|
* @return the latitude of the MP value
|
|
* @since 1.0
|
|
*/
|
|
private double findLat(double mp, double low, double high) {
|
|
DecimalFormat form = new DecimalFormat("#.####");
|
|
mp = Math.round(mp);
|
|
double midLat = (low + high) / 2.0;
|
|
// ctr is used to make sure that with some
|
|
// numbers which can't be represented exactly don't inifitely repeat
|
|
double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
|
|
|
|
while (low <= high) {
|
|
if (guessMP == mp) {
|
|
return midLat;
|
|
} else {
|
|
if (guessMP > mp) {
|
|
high = midLat - 0.0001;
|
|
} else {
|
|
low = midLat + 0.0001;
|
|
}
|
|
}
|
|
|
|
midLat = Double.valueOf(form.format(((low + high) / 2.0)));
|
|
guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat);
|
|
guessMP = Math.round(guessMP);
|
|
}
|
|
return -1000;
|
|
}
|
|
|
|
/**
|
|
* Uses linear search to find the latitude of a given MP
|
|
* @param mp the meridian part for which to find the latitude
|
|
* @param previousLat the previous latitude. Used as a upper / lower bound
|
|
* @return the latitude of the MP value
|
|
*/
|
|
private double findLat(double mp, double previousLat) {
|
|
DecimalFormat form = new DecimalFormat("#.#####");
|
|
mp = Double.parseDouble(form.format(mp));
|
|
double guessMP;
|
|
for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) {
|
|
guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat);
|
|
guessMP = Double.parseDouble(form.format(guessMP));
|
|
if (guessMP == mp || Math.abs(guessMP - mp) < 0.001) {
|
|
return lat;
|
|
}
|
|
}
|
|
return -1000;
|
|
}
|
|
}
|
|
|