double
+ * with the maximum precision of five digits (one meter).
+ *
+ * @param latitude the latitude double
.
+ * @param longitude the longitude double
.
+ * @return the corresponding MGRSCoord
.
+ * @throws IllegalArgumentException if latitude
or longitude
is null,
+ * or the conversion to MGRS coordinates fails.
+ */
+ public static MGRSCoord fromLatLon(double latitude, double longitude) {
+ return fromLatLon(latitude, longitude, 5);
+ }
+
+ /**
+ * Create a MGRS coordinate from a pair of latitude and longitude double
+ * with the given precision or number of digits (1 to 5).
+ *
+ * @param latitude the latitude double
.
+ * @param longitude the longitude double
.
+ * @param precision the number of digits used for easting and northing (1 to 5).
+ * @return the corresponding MGRSCoord
.
+ * @throws IllegalArgumentException if latitude
or longitude
is null,
+ * or the conversion to MGRS coordinates fails.
+ */
+ public static MGRSCoord fromLatLon(double latitude, double longitude, int precision) {
+ final MGRSCoordConverter converter = new MGRSCoordConverter();
+ long err = converter.convertGeodeticToMGRS(Math.toRadians(latitude), Math.toRadians(longitude), precision);
+
+ if (err != MGRSCoordConverter.MGRS_NO_ERROR) {
+ throw new IllegalArgumentException("MGRS Conversion Error");
+ }
+
+ return new MGRSCoord(latitude, longitude, converter.getMGRSString());
+ }
+
+ /**
+ * Create a MGRS coordinate from a standard MGRS coordinate text string.
+ * + * The string will be converted to uppercase and stripped of all spaces before being evaluated. + *
+ *Valid examples:
+ * 32TLP5626635418
+ * 32 T LP 56266 35418
+ * 11S KU 528 111
+ *
MGRSCoord
.
+ * @throws IllegalArgumentException if the MGRSString
is null or empty,
+ * the globe
is null, or the conversion to geodetic coordinates fails (invalid coordinate string).
+ */
+ public static MGRSCoord fromString(String MGRSString) {
+ MGRSString = MGRSString.toUpperCase().replaceAll(" ", "");
+
+ final MGRSCoordConverter converter = new MGRSCoordConverter();
+ long err = converter.convertMGRSToGeodetic(MGRSString);
+
+ if (err != MGRSCoordConverter.MGRS_NO_ERROR) {
+ throw new IllegalArgumentException("MGRS Conversion Error");
+ }
+
+ return new MGRSCoord(Math.toDegrees(converter.getLatitude()), Math.toDegrees(converter.getLongitude()), MGRSString);
+ }
+
+ /**
+ * Create an arbitrary MGRS coordinate from a pair of latitude-longitude double
+ * and the corresponding MGRS coordinate string.
+ *
+ * @param latitude the latitude double
.
+ * @param longitude the longitude double
.
+ * @param MGRSString the corresponding MGRS coordinate string.
+ * @throws IllegalArgumentException if latitude
or longitude
is null,
+ * or the MGRSString is null or empty.
+ */
+ public MGRSCoord(double latitude, double longitude, String MGRSString) {
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.MGRSString = MGRSString;
+ }
+
+ public double getLatitude() {
+ return this.latitude;
+ }
+
+ public double getLongitude() {
+ return this.longitude;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return this.MGRSString;
+ }
+
+}
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/MGRSCoordConverter.java b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/MGRSCoordConverter.java
new file mode 100644
index 000000000..97ce5ce88
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/MGRSCoordConverter.java
@@ -0,0 +1,937 @@
+/*
+ * Copyright (C) 2011 United States Government as represented by the Administrator of the
+ * National Aeronautics and Space Administration.
+ * All Rights Reserved.
+ */
+package gov.nasa.worldwind.geom.coords;
+
+/**
+ * Converter used to translate MGRS coordinate strings to and from geodetic latitude and longitude.
+ *
+ * @author Patrick Murris
+ * @version $Id$
+ * @see MGRSCoord
+ */
+
+import android.support.annotation.NonNull;
+
+/**
+ * Ported to Java from the NGA GeoTrans mgrs.c and mgrs.h code. Contains routines to convert from Geodetic to MGRS and
+ * the other direction.
+ *
+ * @author Garrett Headley, Patrick Murris
+ */
+class MGRSCoordConverter {
+
+ public static final int MGRS_NO_ERROR = 0;
+ private static final int MGRS_LAT_ERROR = 0x0001;
+ private static final int MGRS_LON_ERROR = 0x0002;
+ public static final int MGRS_STRING_ERROR = 0x0004;
+ private static final int MGRS_PRECISION_ERROR = 0x0008;
+ private static final int MGRS_EASTING_ERROR = 0x0040;
+ private static final int MGRS_NORTHING_ERROR = 0x0080;
+ private static final int MGRS_ZONE_ERROR = 0x0100;
+ private static final int MGRS_HEMISPHERE_ERROR = 0x0200;
+ private static final int MGRS_LAT_WARNING = 0x0400;
+ private static final int MGRS_UTM_ERROR = 0x1000;
+ private static final int MGRS_UPS_ERROR = 0x2000;
+
+ private static final double PI = 3.14159265358979323;
+ private static final double PI_OVER_2 = (PI / 2.0e0);
+ private static final int MAX_PRECISION = 5;
+ private static final double MIN_UTM_LAT = (-80 * PI) / 180.0; // -80 degrees in radians
+ private static final double MAX_UTM_LAT = (84 * PI) / 180.0; // 84 degrees in radians
+ public static final double DEG_TO_RAD = 0.017453292519943295; // PI/180
+ private static final double RAD_TO_DEG = 57.29577951308232087; // 180/PI
+
+ private static final double MIN_EAST_NORTH = 0;
+ private static final double MAX_EAST_NORTH = 4000000;
+ private static final double TWOMIL = 2000000;
+ private static final double ONEHT = 100000;
+
+ private static final String CLARKE_1866 = "CC";
+ private static final String CLARKE_1880 = "CD";
+ private static final String BESSEL_1841 = "BR";
+ private static final String BESSEL_1841_NAMIBIA = "BN";
+
+ private String MGRS_Ellipsoid_Code = "WE";
+
+ private String MGRSString = "";
+ private long ltr2_low_value;
+ private long ltr2_high_value; // this is only used for doing MGRS to xxx conversions.
+ private double false_northing;
+ private long lastLetter;
+ private long last_error = MGRS_NO_ERROR;
+ private double north, south, min_northing, northing_offset; //smithjl added north_offset
+ private double latitude;
+ private double longitude;
+
+ private static final int LETTER_A = 0; /* ARRAY INDEX FOR LETTER A */
+ private static final int LETTER_B = 1; /* ARRAY INDEX FOR LETTER B */
+ private static final int LETTER_C = 2; /* ARRAY INDEX FOR LETTER C */
+ private static final int LETTER_D = 3; /* ARRAY INDEX FOR LETTER D */
+ private static final int LETTER_E = 4; /* ARRAY INDEX FOR LETTER E */
+ private static final int LETTER_F = 5; /* ARRAY INDEX FOR LETTER E */
+ private static final int LETTER_G = 6; /* ARRAY INDEX FOR LETTER H */
+ private static final int LETTER_H = 7; /* ARRAY INDEX FOR LETTER H */
+ private static final int LETTER_I = 8; /* ARRAY INDEX FOR LETTER I */
+ private static final int LETTER_J = 9; /* ARRAY INDEX FOR LETTER J */
+ private static final int LETTER_K = 10; /* ARRAY INDEX FOR LETTER J */
+ private static final int LETTER_L = 11; /* ARRAY INDEX FOR LETTER L */
+ private static final int LETTER_M = 12; /* ARRAY INDEX FOR LETTER M */
+ private static final int LETTER_N = 13; /* ARRAY INDEX FOR LETTER N */
+ private static final int LETTER_O = 14; /* ARRAY INDEX FOR LETTER O */
+ private static final int LETTER_P = 15; /* ARRAY INDEX FOR LETTER P */
+ private static final int LETTER_Q = 16; /* ARRAY INDEX FOR LETTER Q */
+ private static final int LETTER_R = 17; /* ARRAY INDEX FOR LETTER R */
+ private static final int LETTER_S = 18; /* ARRAY INDEX FOR LETTER S */
+ private static final int LETTER_T = 19; /* ARRAY INDEX FOR LETTER S */
+ private static final int LETTER_U = 20; /* ARRAY INDEX FOR LETTER U */
+ private static final int LETTER_V = 21; /* ARRAY INDEX FOR LETTER V */
+ private static final int LETTER_W = 22; /* ARRAY INDEX FOR LETTER W */
+ private static final int LETTER_X = 23; /* ARRAY INDEX FOR LETTER X */
+ private static final int LETTER_Y = 24; /* ARRAY INDEX FOR LETTER Y */
+ private static final int LETTER_Z = 25; /* ARRAY INDEX FOR LETTER Z */
+ private static final int MGRS_LETTERS = 3; /* NUMBER OF LETTERS IN MGRS */
+
+ private static final String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ // UPS Constants are in the following order:
+ // long letter; /* letter representing latitude band */
+ // long ltr2_low_value; /* 2nd letter range - high number */
+ // long ltr2_high_value; /* 2nd letter range - low number */
+ // long ltr3_high_value; /* 3rd letter range - high number (UPS) */
+ // double false_easting; /* False easting based on 2nd letter */
+ // double false_northing; /* False northing based on 3rd letter */
+ private static final long[][] upsConstants = {
+ {LETTER_A, LETTER_J, LETTER_Z, LETTER_Z, 800000, 800000},
+ {LETTER_B, LETTER_A, LETTER_R, LETTER_Z, 2000000, 800000},
+ {LETTER_Y, LETTER_J, LETTER_Z, LETTER_P, 800000, 1300000},
+ {LETTER_Z, LETTER_A, LETTER_J, LETTER_P, 2000000, 1300000}};
+
+ // Latitude Band Constants are in the following order:
+ // long letter; /* letter representing latitude band */
+ // double min_northing; /* minimum northing for latitude band */
+ // double north; /* upper latitude for latitude band */
+ // double south; /* lower latitude for latitude band */
+ private static final double[][] latitudeBandConstants = {
+ {LETTER_C, 1100000.0, -72.0, -80.5, 0.0},
+ {LETTER_D, 2000000.0, -64.0, -72.0, 2000000.0},
+ {LETTER_E, 2800000.0, -56.0, -64.0, 2000000.0},
+ {LETTER_F, 3700000.0, -48.0, -56.0, 2000000.0},
+ {LETTER_G, 4600000.0, -40.0, -48.0, 4000000.0},
+ {LETTER_H, 5500000.0, -32.0, -40.0, 4000000.0}, //smithjl last column to table
+ {LETTER_J, 6400000.0, -24.0, -32.0, 6000000.0},
+ {LETTER_K, 7300000.0, -16.0, -24.0, 6000000.0},
+ {LETTER_L, 8200000.0, -8.0, -16.0, 8000000.0},
+ {LETTER_M, 9100000.0, 0.0, -8.0, 8000000.0},
+ {LETTER_N, 0.0, 8.0, 0.0, 0.0},
+ {LETTER_P, 800000.0, 16.0, 8.0, 0.0},
+ {LETTER_Q, 1700000.0, 24.0, 16.0, 0.0},
+ {LETTER_R, 2600000.0, 32.0, 24.0, 2000000.0},
+ {LETTER_S, 3500000.0, 40.0, 32.0, 2000000.0},
+ {LETTER_T, 4400000.0, 48.0, 40.0, 4000000.0},
+ {LETTER_U, 5300000.0, 56.0, 48.0, 4000000.0},
+ {LETTER_V, 6200000.0, 64.0, 56.0, 6000000.0},
+ {LETTER_W, 7000000.0, 72.0, 64.0, 6000000.0},
+ {LETTER_X, 7900000.0, 84.5, 72.0, 6000000.0}};
+
+ private class MGRSComponents {
+ private final int zone;
+ private final int latitudeBand;
+ private final int squareLetter1;
+ private final int squareLetter2;
+ private final double easting;
+ private final double northing;
+ private final int precision;
+
+ public MGRSComponents(int zone, int latitudeBand, int squareLetter1, int squareLetter2,
+ double easting, double northing, int precision) {
+ this.zone = zone;
+ this.latitudeBand = latitudeBand;
+ this.squareLetter1 = squareLetter1;
+ this.squareLetter2 = squareLetter2;
+ this.easting = easting;
+ this.northing = northing;
+ this.precision = precision;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "MGRS: " + zone + " " +
+ alphabet.charAt(latitudeBand) + " " +
+ alphabet.charAt(squareLetter1) + alphabet.charAt(squareLetter2) + " " +
+ easting + " " +
+ northing + " " +
+ "(" + precision + ")";
+ }
+ }
+
+ MGRSCoordConverter(){}
+
+ /** @return Latitude band letter */
+ private long getLastLetter() {
+ return lastLetter;
+ }
+
+ /**
+ * The function ConvertMGRSToGeodetic converts an MGRS coordinate string to Geodetic (latitude and longitude)
+ * coordinates according to the current ellipsoid parameters. If any errors occur, the error code(s) are returned
+ * by the function, otherwise UTM_NO_ERROR is returned.
+ *
+ * @param MGRSString MGRS coordinate string.
+ *
+ * @return the error code.
+ */
+ public long convertMGRSToGeodetic(String MGRSString) {
+ latitude = 0;
+ longitude = 0;
+ MGRSComponents mgrs = breakMGRSString(MGRSString);
+ if (mgrs == null) {
+ return last_error;
+ }
+ long error_code = MGRS_NO_ERROR;
+ if (mgrs.zone != 0) {
+ UTMCoord UTM = convertMGRSToUTM(MGRSString);
+ if (UTM != null) {
+ latitude = Math.toRadians(UTM.getLatitude());
+ longitude = Math.toRadians(UTM.getLongitude());
+ } else
+ error_code = MGRS_UTM_ERROR;
+ } else {
+ UPSCoord UPS = convertMGRSToUPS(MGRSString);
+ if (UPS != null) {
+ latitude = Math.toRadians(UPS.getLatitude());
+ longitude = Math.toRadians(UPS.getLongitude());
+ } else
+ error_code = MGRS_UPS_ERROR;
+ }
+ return (error_code);
+ }
+
+ public double getLatitude() {
+ return latitude;
+ }
+
+ public double getLongitude() {
+ return longitude;
+ }
+
+ /**
+ * The function Break_MGRS_String breaks down an MGRS coordinate string into its component parts. Updates
+ * last_error.
+ *
+ * @param MGRSString the MGRS coordinate string
+ *
+ * @return the corresponding MGRSComponents
or null
.
+ */
+ private MGRSComponents breakMGRSString(String MGRSString) {
+ int num_digits;
+ int num_letters;
+ int i = 0;
+ int j = 0;
+ long error_code = MGRS_NO_ERROR;
+
+ int zone = 0;
+ int[] letters = new int[3];
+ long easting = 0;
+ long northing = 0;
+ int precision = 0;
+
+ MGRSString = MGRSString.toUpperCase().replaceAll("\\s", "");
+ j = i;
+ while (i < MGRSString.length() && Character.isDigit(MGRSString.charAt(i))) {
+ i++;
+ }
+ num_digits = i - j;
+ if (num_digits <= 2) {
+ if (num_digits > 0) {
+ /* get zone */
+ zone = Integer.parseInt(MGRSString.substring(j, i));
+ if ((zone < 1) || (zone > 60)) {
+ error_code |= MGRS_STRING_ERROR;
+ }
+ } else {
+ zone = 0;
+ }
+ }
+ j = i;
+
+ while (i < MGRSString.length() && Character.isLetter(MGRSString.charAt(i))) {
+ i++;
+ }
+ num_letters = i - j;
+ if (num_letters == 3) {
+ /* get letters */
+ letters[0] = alphabet.indexOf(Character.toUpperCase(MGRSString.charAt(j)));
+ if ((letters[0] == LETTER_I) || (letters[0] == LETTER_O))
+ error_code |= MGRS_STRING_ERROR;
+ letters[1] = alphabet.indexOf(Character.toUpperCase(MGRSString.charAt(j + 1)));
+ if ((letters[1] == LETTER_I) || (letters[1] == LETTER_O))
+ error_code |= MGRS_STRING_ERROR;
+ letters[2] = alphabet.indexOf(Character.toUpperCase(MGRSString.charAt(j + 2)));
+ if ((letters[2] == LETTER_I) || (letters[2] == LETTER_O))
+ error_code |= MGRS_STRING_ERROR;
+ } else
+ error_code |= MGRS_STRING_ERROR;
+ j = i;
+ while (i < MGRSString.length() && Character.isDigit(MGRSString.charAt(i))) {
+ i++;
+ }
+ num_digits = i - j;
+ if ((num_digits <= 10) && (num_digits % 2 == 0)) {
+ /* get easting, northing and precision */
+ int n;
+ double multiplier;
+ /* get easting & northing */
+ n = num_digits / 2;
+ precision = n;
+ if (n > 0) {
+ easting = Integer.parseInt(MGRSString.substring(j, j + n));
+ northing = Integer.parseInt(MGRSString.substring(j + n, j + n + n));
+ multiplier = Math.pow(10.0, 5 - n);
+ easting *= multiplier;
+ northing *= multiplier;
+ } else {
+ easting = 0;
+ northing = 0;
+ }
+ } else
+ error_code |= MGRS_STRING_ERROR;
+
+ last_error = error_code;
+ if (error_code == MGRS_NO_ERROR)
+ return new MGRSComponents(zone, letters[0], letters[1], letters[2], easting, northing, precision);
+
+ return null;
+ }
+
+ /**
+ * The function Get_Latitude_Band_Min_Northing receives a latitude band letter and uses the Latitude_Band_Table to
+ * determine the minimum northing for that latitude band letter. Updates min_northing.
+ *
+ * @param letter Latitude band letter.
+ *
+ * @return the error code.
+ */
+ private long getLatitudeBandMinNorthing(int letter) {
+ long error_code = MGRS_NO_ERROR;
+
+ if ((letter >= LETTER_C) && (letter <= LETTER_H)) {
+ min_northing = latitudeBandConstants[letter - 2][1];
+ northing_offset = latitudeBandConstants[letter - 2][4]; //smithjl
+ } else if ((letter >= LETTER_J) && (letter <= LETTER_N)) {
+ min_northing = latitudeBandConstants[letter - 3][1];
+ northing_offset = latitudeBandConstants[letter - 3][4]; //smithjl
+ } else if ((letter >= LETTER_P) && (letter <= LETTER_X)) {
+ min_northing = latitudeBandConstants[letter - 4][1];
+ northing_offset = latitudeBandConstants[letter - 4][4]; //smithjl
+ } else
+ error_code |= MGRS_STRING_ERROR;
+ return error_code;
+ }
+
+ /**
+ * The function Get_Latitude_Range receives a latitude band letter and uses the Latitude_Band_Table to determine the
+ * latitude band boundaries for that latitude band letter. Updates north and south.
+ *
+ * @param letter the Latitude band letter
+ *
+ * @return the error code.
+ */
+ private long getLatitudeRange(int letter) {
+ long error_code = MGRS_NO_ERROR;
+
+ if ((letter >= LETTER_C) && (letter <= LETTER_H)) {
+ north = latitudeBandConstants[letter - 2][2] * DEG_TO_RAD;
+ south = latitudeBandConstants[letter - 2][3] * DEG_TO_RAD;
+ } else if ((letter >= LETTER_J) && (letter <= LETTER_N)) {
+ north = latitudeBandConstants[letter - 3][2] * DEG_TO_RAD;
+ south = latitudeBandConstants[letter - 3][3] * DEG_TO_RAD;
+ } else if ((letter >= LETTER_P) && (letter <= LETTER_X)) {
+ north = latitudeBandConstants[letter - 4][2] * DEG_TO_RAD;
+ south = latitudeBandConstants[letter - 4][3] * DEG_TO_RAD;
+ } else
+ error_code |= MGRS_STRING_ERROR;
+
+ return error_code;
+ }
+
+ /**
+ * The function convertMGRSToUTM converts an MGRS coordinate string to UTM projection (zone, hemisphere, easting and
+ * northing) coordinates according to the current ellipsoid parameters. Updates last_error if any errors occured.
+ *
+ * @param MGRSString the MGRS coordinate string
+ *
+ * @return the corresponding UTMComponents
or null
.
+ */
+ private UTMCoord convertMGRSToUTM(String MGRSString) {
+ double grid_easting; /* Easting for 100,000 meter grid square */
+ double grid_northing; /* Northing for 100,000 meter grid square */
+ double latitude;
+ double divisor;
+ long error_code = MGRS_NO_ERROR;
+
+ Hemisphere hemisphere;
+ double easting;
+ double northing;
+ UTMCoord UTM = null;
+
+ MGRSComponents MGRS = breakMGRSString(MGRSString);
+ if (MGRS == null)
+ error_code |= MGRS_STRING_ERROR;
+ else {
+ if ((MGRS.latitudeBand == LETTER_X) && ((MGRS.zone == 32) || (MGRS.zone == 34) || (MGRS.zone == 36)))
+ error_code |= MGRS_STRING_ERROR;
+ else {
+ if (MGRS.latitudeBand < LETTER_N)
+ hemisphere = Hemisphere.S;
+ else
+ hemisphere = Hemisphere.N;
+
+ getGridValues(MGRS.zone);
+
+ // Check that the second letter of the MGRS string is within
+ // the range of valid second letter values
+ // Also check that the third letter is valid
+ if ((MGRS.squareLetter1 < ltr2_low_value) || (MGRS.squareLetter1 > ltr2_high_value) ||
+ (MGRS.squareLetter2 > LETTER_V))
+ error_code |= MGRS_STRING_ERROR;
+
+ if (error_code == MGRS_NO_ERROR) {
+ grid_northing = (MGRS.squareLetter2) * ONEHT; // smithjl commented out + false_northing;
+ grid_easting = ((MGRS.squareLetter1) - ltr2_low_value + 1) * ONEHT;
+ if ((ltr2_low_value == LETTER_J) && (MGRS.squareLetter1 > LETTER_O))
+ grid_easting = grid_easting - ONEHT;
+
+ if (MGRS.squareLetter2 > LETTER_O)
+ grid_northing = grid_northing - ONEHT;
+
+ if (MGRS.squareLetter2 > LETTER_I)
+ grid_northing = grid_northing - ONEHT;
+
+ if (grid_northing >= TWOMIL)
+ grid_northing = grid_northing - TWOMIL;
+
+ error_code = getLatitudeBandMinNorthing(MGRS.latitudeBand);
+ if (error_code == MGRS_NO_ERROR) {
+ /*smithjl Deleted code here and added this*/
+ grid_northing = grid_northing - false_northing;
+
+ if (grid_northing < 0.0)
+ grid_northing += TWOMIL;
+
+ grid_northing += northing_offset;
+
+ if (grid_northing < min_northing)
+ grid_northing += TWOMIL;
+
+ /* smithjl End of added code */
+
+ easting = grid_easting + MGRS.easting;
+ northing = grid_northing + MGRS.northing;
+
+ try {
+ UTM = UTMCoord.fromUTM(MGRS.zone, hemisphere, easting, northing);
+ latitude = Math.toRadians(UTM.getLatitude());
+ divisor = Math.pow(10.0, MGRS.precision);
+ error_code = getLatitudeRange(MGRS.latitudeBand);
+ if (error_code == MGRS_NO_ERROR) {
+ if (!(((south - DEG_TO_RAD / divisor) <= latitude)
+ && (latitude <= (north + DEG_TO_RAD / divisor))))
+ error_code |= MGRS_LAT_WARNING;
+ }
+ } catch (Exception e) {
+ error_code = MGRS_UTM_ERROR;
+ }
+ }
+ }
+ }
+ }
+
+ last_error = error_code;
+ if (error_code == MGRS_NO_ERROR || error_code == MGRS_LAT_WARNING)
+ return UTM;
+
+ return null;
+ } /* Convert_MGRS_To_UTM */
+
+ /**
+ * The function convertGeodeticToMGRS converts Geodetic (latitude and longitude) coordinates to an MGRS coordinate
+ * string, according to the current ellipsoid parameters. If any errors occur, the error code(s) are returned by
+ * the function, otherwise MGRS_NO_ERROR is returned.
+ *
+ * @param latitude Latitude in radians
+ * @param longitude Longitude in radian
+ * @param precision Precision level of MGRS string
+ *
+ * @return error code
+ */
+ public long convertGeodeticToMGRS(double latitude, double longitude, int precision) {
+ MGRSString = "";
+
+ long error_code = MGRS_NO_ERROR;
+ if ((latitude < -PI_OVER_2) || (latitude > PI_OVER_2)) { /* Latitude out of range */
+ error_code = MGRS_LAT_ERROR;
+ }
+
+ if ((longitude < -PI) || (longitude > (2 * PI))) { /* Longitude out of range */
+ error_code = MGRS_LON_ERROR;
+ }
+
+ if ((precision < 0) || (precision > MAX_PRECISION))
+ error_code = MGRS_PRECISION_ERROR;
+
+ if (error_code == MGRS_NO_ERROR) {
+ if ((latitude < MIN_UTM_LAT) || (latitude > MAX_UTM_LAT)) {
+ try {
+ UPSCoord UPS =
+ UPSCoord.fromLatLon(Math.toDegrees(latitude), Math.toDegrees(longitude));
+ error_code |= convertUPSToMGRS(UPS.getHemisphere(), UPS.getEasting(),
+ UPS.getNorthing(), precision);
+ } catch (Exception e) {
+ error_code = MGRS_UPS_ERROR;
+ }
+ } else {
+ try {
+ UTMCoord UTM =
+ UTMCoord.fromLatLon(Math.toDegrees(latitude), Math.toDegrees(longitude));
+ error_code |= convertUTMToMGRS(UTM.getZone(), latitude, UTM.getEasting(),
+ UTM.getNorthing(), precision);
+ } catch (Exception e) {
+ error_code = MGRS_UTM_ERROR;
+ }
+ }
+ }
+
+ return error_code;
+ }
+
+ /** @return converted MGRS string */
+ public String getMGRSString() {
+ return MGRSString;
+ }
+
+ /**
+ * The function Convert_UPS_To_MGRS converts UPS (hemisphere, easting, and northing) coordinates to an MGRS
+ * coordinate string according to the current ellipsoid parameters. If any errors occur, the error code(s) are
+ * returned by the function, otherwise MGRS_NO_ERROR is returned.
+ *
+ * @param hemisphere hemisphere either {@link Hemisphere#N} of {@link Hemisphere#S}.
+ * @param easting easting/X in meters
+ * @param northing northing/Y in meters
+ * @param precision precision level of MGRS string
+ *
+ * @return error value
+ */
+ private long convertUPSToMGRS(Hemisphere hemisphere, Double easting, Double northing, long precision) {
+ double false_easting; /* False easting for 2nd letter */
+ double false_northing; /* False northing for 3rd letter */
+ double grid_easting; /* easting used to derive 2nd letter of MGRS */
+ double grid_northing; /* northing used to derive 3rd letter of MGRS */
+ int ltr2_low_value; /* 2nd letter range - low number */
+ long[] letters = new long[MGRS_LETTERS]; /* Number location of 3 letters in alphabet */
+ double divisor;
+ int index;
+ long error_code = MGRS_NO_ERROR;
+
+ if (!Hemisphere.N.equals(hemisphere) && !Hemisphere.S.equals(hemisphere))
+ error_code |= MGRS_HEMISPHERE_ERROR;
+ if ((easting < MIN_EAST_NORTH) || (easting > MAX_EAST_NORTH))
+ error_code |= MGRS_EASTING_ERROR;
+ if ((northing < MIN_EAST_NORTH) || (northing > MAX_EAST_NORTH))
+ error_code |= MGRS_NORTHING_ERROR;
+ if ((precision < 0) || (precision > MAX_PRECISION))
+ error_code |= MGRS_PRECISION_ERROR;
+
+ if (error_code == MGRS_NO_ERROR) {
+ divisor = Math.pow(10.0, (5 - precision));
+ easting = roundMGRS(easting / divisor) * divisor;
+ northing = roundMGRS(northing / divisor) * divisor;
+
+ if (Hemisphere.N.equals(hemisphere)) {
+ if (easting >= TWOMIL)
+ letters[0] = LETTER_Z;
+ else
+ letters[0] = LETTER_Y;
+
+ index = (int) letters[0] - 22;
+// ltr2_low_value = UPS_Constant_Table.get(index).ltr2_low_value;
+// false_easting = UPS_Constant_Table.get(index).false_easting;
+// false_northing = UPS_Constant_Table.get(index).false_northing;
+ ltr2_low_value = (int) upsConstants[index][1];
+ false_easting = upsConstants[index][4];
+ false_northing = upsConstants[index][5];
+ } else {
+ if (easting >= TWOMIL)
+ letters[0] = LETTER_B;
+ else
+ letters[0] = LETTER_A;
+
+// ltr2_low_value = UPS_Constant_Table.get((int) letters[0]).ltr2_low_value;
+// false_easting = UPS_Constant_Table.get((int) letters[0]).false_easting;
+// false_northing = UPS_Constant_Table.get((int) letters[0]).false_northing;
+ ltr2_low_value = (int) upsConstants[(int) letters[0]][1];
+ false_easting = upsConstants[(int) letters[0]][4];
+ false_northing = upsConstants[(int) letters[0]][5];
+ }
+
+ grid_northing = northing;
+ grid_northing = grid_northing - false_northing;
+ letters[2] = (int) (grid_northing / ONEHT);
+
+ if (letters[2] > LETTER_H)
+ letters[2] = letters[2] + 1;
+
+ if (letters[2] > LETTER_N)
+ letters[2] = letters[2] + 1;
+
+ grid_easting = easting;
+ grid_easting = grid_easting - false_easting;
+ letters[1] = ltr2_low_value + ((int) (grid_easting / ONEHT));
+
+ if (easting < TWOMIL) {
+ if (letters[1] > LETTER_L)
+ letters[1] = letters[1] + 3;
+
+ if (letters[1] > LETTER_U)
+ letters[1] = letters[1] + 2;
+ } else {
+ if (letters[1] > LETTER_C)
+ letters[1] = letters[1] + 2;
+
+ if (letters[1] > LETTER_H)
+ letters[1] = letters[1] + 1;
+
+ if (letters[1] > LETTER_L)
+ letters[1] = letters[1] + 3;
+ }
+
+ makeMGRSString(0, letters, easting, northing, precision);
+ }
+ return (error_code);
+ }
+
+ /**
+ * The function UTM_To_MGRS calculates an MGRS coordinate string based on the zone, latitude, easting and northing.
+ *
+ * @param Zone Zone number
+ * @param Latitude Latitude in radians
+ * @param Easting Easting
+ * @param Northing Northing
+ * @param Precision Precision
+ *
+ * @return error code
+ */
+ private long convertUTMToMGRS(long Zone, double Latitude, double Easting, double Northing, long Precision) {
+ double grid_easting; /* Easting used to derive 2nd letter of MGRS */
+ double grid_northing; /* Northing used to derive 3rd letter of MGRS */
+ long[] letters = new long[MGRS_LETTERS]; /* Number location of 3 letters in alphabet */
+ double divisor;
+ long error_code;
+
+ /* Round easting and northing values */
+ divisor = Math.pow(10.0, (5 - Precision));
+ Easting = roundMGRS(Easting / divisor) * divisor;
+ Northing = roundMGRS(Northing / divisor) * divisor;
+
+ getGridValues(Zone);
+
+ error_code = getLatitudeLetter(Latitude);
+ letters[0] = getLastLetter();
+
+ if (error_code == MGRS_NO_ERROR) {
+ grid_northing = Northing;
+ if (grid_northing == 1.e7)
+ grid_northing = grid_northing - 1.0;
+
+ while (grid_northing >= TWOMIL) {
+ grid_northing = grid_northing - TWOMIL;
+ }
+ grid_northing = grid_northing + false_northing; //smithjl
+
+ if (grid_northing >= TWOMIL) //smithjl
+ grid_northing = grid_northing - TWOMIL; //smithjl
+
+ letters[2] = (long) (grid_northing / ONEHT);
+ if (letters[2] > LETTER_H)
+ letters[2] = letters[2] + 1;
+
+ if (letters[2] > LETTER_N)
+ letters[2] = letters[2] + 1;
+
+ grid_easting = Easting;
+ if (((letters[0] == LETTER_V) && (Zone == 31)) && (grid_easting == 500000.0))
+ grid_easting = grid_easting - 1.0; /* SUBTRACT 1 METER */
+
+ letters[1] = ltr2_low_value + ((long) (grid_easting / ONEHT) - 1);
+ if ((ltr2_low_value == LETTER_J) && (letters[1] > LETTER_N))
+ letters[1] = letters[1] + 1;
+
+ makeMGRSString(Zone, letters, Easting, Northing, Precision);
+ }
+ return error_code;
+ }
+
+ /**
+ * The function Get_Grid_Values sets the letter range used for the 2nd letter in the MGRS coordinate string, based
+ * on the set number of the utm zone. It also sets the false northing using a value of A for the second letter of
+ * the grid square, based on the grid pattern and set number of the utm zone.
+ *
+ * Key values that are set in this function include: ltr2_low_value, ltr2_high_value, and false_northing.
+ *
+ * @param zone Zone number
+ */
+ private void getGridValues(long zone) {
+ long set_number; /* Set number (1-6) based on UTM zone number */
+ long aa_pattern; /* Pattern based on ellipsoid code */
+
+ set_number = zone % 6;
+
+ if (set_number == 0)
+ set_number = 6;
+
+ if (MGRS_Ellipsoid_Code.compareTo(CLARKE_1866) == 0 || MGRS_Ellipsoid_Code.compareTo(CLARKE_1880) == 0 ||
+ MGRS_Ellipsoid_Code.compareTo(BESSEL_1841) == 0 || MGRS_Ellipsoid_Code.compareTo(BESSEL_1841_NAMIBIA) == 0)
+ aa_pattern = 0L;
+ else
+ aa_pattern = 1L;
+
+ if ((set_number == 1) || (set_number == 4)) {
+ ltr2_low_value = LETTER_A;
+ ltr2_high_value = LETTER_H;
+ } else if ((set_number == 2) || (set_number == 5)) {
+ ltr2_low_value = LETTER_J;
+ ltr2_high_value = LETTER_R;
+ } else if ((set_number == 3) || (set_number == 6)) {
+ ltr2_low_value = LETTER_S;
+ ltr2_high_value = LETTER_Z;
+ }
+
+ /* False northing at A for second letter of grid square */
+ if (aa_pattern == 1L) {
+ if ((set_number % 2) == 0)
+ false_northing = 500000.0; //smithjl was 1500000
+ else
+ false_northing = 0.0;
+ } else {
+ if ((set_number % 2) == 0)
+ false_northing = 1500000.0; //smithjl was 500000
+ else
+ false_northing = 1000000.00;
+ }
+ }
+
+ /**
+ * The function Get_Latitude_Letter receives a latitude value and uses the Latitude_Band_Table to determine the
+ * latitude band letter for that latitude.
+ *
+ * @param latitude latitude to turn into code
+ *
+ * @return error code
+ */
+ private long getLatitudeLetter(double latitude) {
+ double temp;
+ long error_code = MGRS_NO_ERROR;
+ double lat_deg = latitude * RAD_TO_DEG;
+
+ if (lat_deg >= 72 && lat_deg < 84.5)
+ lastLetter = LETTER_X;
+ else if (lat_deg > -80.5 && lat_deg < 72) {
+ temp = ((latitude + (80.0 * DEG_TO_RAD)) / (8.0 * DEG_TO_RAD)) + 1.0e-12;
+ // lastLetter = Latitude_Band_Table.get((int) temp).letter;
+ lastLetter = (long) latitudeBandConstants[(int) temp][0];
+ } else
+ error_code |= MGRS_LAT_ERROR;
+
+ return error_code;
+ }
+
+ /**
+ * The function Round_MGRS rounds the input value to the nearest integer, using the standard engineering rule. The
+ * rounded integer value is then returned.
+ *
+ * @param value Value to be rounded
+ *
+ * @return rounded double value
+ */
+ private double roundMGRS(double value) {
+ double ivalue = Math.floor(value);
+ long ival;
+ double fraction = value - ivalue;
+ // double fraction = modf (value, &ivalue);
+
+ ival = (long) (ivalue);
+ if ((fraction > 0.5) || ((fraction == 0.5) && (ival % 2 == 1)))
+ ival++;
+ return ival;
+ }
+
+ /**
+ * The function Make_MGRS_String constructs an MGRS string from its component parts.
+ *
+ * @param Zone UTM Zone
+ * @param Letters MGRS coordinate string letters
+ * @param Easting Easting value
+ * @param Northing Northing value
+ * @param Precision Precision level of MGRS string
+ */
+ private void makeMGRSString(long Zone, long[] Letters, double Easting, double Northing, long Precision) {
+ int j;
+ double divisor;
+ long east;
+ long north;
+
+ if (Zone != 0)
+ MGRSString = String.format("%02d", Zone);
+ else
+ MGRSString = " ";
+
+ for (j = 0; j < 3; j++) {
+ if (Letters[j] < 0 || Letters[j] > 26)
+ return;
+ MGRSString = MGRSString + alphabet.charAt((int) Letters[j]);
+ }
+
+ divisor = Math.pow(10.0, (5 - Precision));
+ Easting = Easting % 100000.0;
+ if (Easting >= 99999.5)
+ Easting = 99999.0;
+ east = (long) (Easting / divisor);
+
+ // Here we need to only use the number requesting in the precision
+ Integer iEast = (int) east;
+ StringBuilder sEast = new StringBuilder(iEast.toString());
+ if (sEast.length() > Precision)
+ sEast = new StringBuilder(sEast.substring(0, (int) Precision - 1));
+ else {
+ int i;
+ int length = sEast.length();
+ for (i = 0; i < Precision - length; i++) {
+ sEast.insert(0, "0");
+ }
+ }
+ MGRSString = MGRSString + " " + sEast;
+
+ Northing = Northing % 100000.0;
+ if (Northing >= 99999.5)
+ Northing = 99999.0;
+ north = (long) (Northing / divisor);
+
+ Integer iNorth = (int) north;
+ StringBuilder sNorth = new StringBuilder(iNorth.toString());
+ if (sNorth.length() > Precision)
+ sNorth = new StringBuilder(sNorth.substring(0, (int) Precision - 1));
+ else
+ {
+ int i;
+ int length = sNorth.length();
+ for (i = 0; i < Precision - length; i++)
+ {
+ sNorth.insert(0, "0");
+ }
+ }
+ MGRSString = MGRSString + " " + sNorth;
+ }
+
+ /**
+ * The function Convert_MGRS_To_UPS converts an MGRS coordinate string to UPS (hemisphere, easting, and northing)
+ * coordinates, according to the current ellipsoid parameters. If any errors occur, the error code(s) are returned
+ * by the function, otherwise UPS_NO_ERROR is returned.
+ *
+ * @param MGRS the MGRS coordinate string.
+ *
+ * @return a corresponding {@link UPSCoord} instance.
+ */
+ private UPSCoord convertMGRSToUPS(String MGRS) {
+ long ltr2_high_value; /* 2nd letter range - high number */
+ long ltr3_high_value; /* 3rd letter range - high number (UPS) */
+ long ltr2_low_value; /* 2nd letter range - low number */
+ double false_easting; /* False easting for 2nd letter */
+ double false_northing; /* False northing for 3rd letter */
+ double grid_easting; /* easting for 100,000 meter grid square */
+ double grid_northing; /* northing for 100,000 meter grid square */
+ int index = 0;
+ long error_code = MGRS_NO_ERROR;
+
+ Hemisphere hemisphere;
+ double easting, northing;
+
+ MGRSComponents mgrs = breakMGRSString(MGRS);
+ if (mgrs == null) {
+ error_code = this.last_error;
+ } else {
+ if (mgrs.zone > 0) {
+ error_code |= MGRS_STRING_ERROR;
+ }
+
+ if (error_code == MGRS_NO_ERROR) {
+ easting = mgrs.easting;
+ northing = mgrs.northing;
+
+ if (mgrs.latitudeBand >= LETTER_Y) {
+ hemisphere = Hemisphere.N;
+
+ index = mgrs.latitudeBand - 22;
+ ltr2_low_value = upsConstants[index][1]; //.ltr2_low_value;
+ ltr2_high_value = upsConstants[index][2]; //.ltr2_high_value;
+ ltr3_high_value = upsConstants[index][3]; //.ltr3_high_value;
+ false_easting = upsConstants[index][4]; //.false_easting;
+ false_northing = upsConstants[index][5]; //.false_northing;
+ } else {
+ hemisphere = Hemisphere.S;
+
+ ltr2_low_value = upsConstants[mgrs.latitudeBand][1]; //.ltr2_low_value;
+ ltr2_high_value = upsConstants[mgrs.latitudeBand][2]; //.ltr2_high_value;
+ ltr3_high_value = upsConstants[mgrs.latitudeBand][3]; //.ltr3_high_value;
+ false_easting = upsConstants[mgrs.latitudeBand][4]; //.false_easting;
+ false_northing = upsConstants[mgrs.latitudeBand][5]; //.false_northing;
+ }
+
+ // Check that the second letter of the MGRS string is within
+ // the range of valid second letter values
+ // Also check that the third letter is valid
+ if ((mgrs.squareLetter1 < ltr2_low_value) || (mgrs.squareLetter1 > ltr2_high_value) ||
+ ((mgrs.squareLetter1 == LETTER_D) || (mgrs.squareLetter1 == LETTER_E) ||
+ (mgrs.squareLetter1 == LETTER_M) || (mgrs.squareLetter1 == LETTER_N) ||
+ (mgrs.squareLetter1 == LETTER_V) || (mgrs.squareLetter1 == LETTER_W)) ||
+ (mgrs.squareLetter2 > ltr3_high_value))
+ error_code = MGRS_STRING_ERROR;
+
+ if (error_code == MGRS_NO_ERROR) {
+ grid_northing = mgrs.squareLetter2 * ONEHT + false_northing;
+ if (mgrs.squareLetter2 > LETTER_I)
+ grid_northing = grid_northing - ONEHT;
+
+ if (mgrs.squareLetter2 > LETTER_O)
+ grid_northing = grid_northing - ONEHT;
+
+ grid_easting = ((mgrs.squareLetter1) - ltr2_low_value) * ONEHT + false_easting;
+ if (ltr2_low_value != LETTER_A) {
+ if (mgrs.squareLetter1 > LETTER_L)
+ grid_easting = grid_easting - 300000.0;
+
+ if (mgrs.squareLetter1 > LETTER_U)
+ grid_easting = grid_easting - 200000.0;
+ } else {
+ if (mgrs.squareLetter1 > LETTER_C)
+ grid_easting = grid_easting - 200000.0;
+
+ if (mgrs.squareLetter1 > LETTER_I)
+ grid_easting = grid_easting - ONEHT;
+
+ if (mgrs.squareLetter1 > LETTER_L)
+ grid_easting = grid_easting - 300000.0;
+ }
+
+ easting = grid_easting + easting;
+ northing = grid_northing + northing;
+ return UPSCoord.fromUPS(hemisphere, easting, northing);
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/PolarCoordConverter.java b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/PolarCoordConverter.java
new file mode 100644
index 000000000..4cc47f92e
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/PolarCoordConverter.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2011 United States Government as represented by the Administrator of the
+ * National Aeronautics and Space Administration.
+ * All Rights Reserved.
+ */
+
+/* RSC IDENTIFIER: POLAR STEREOGRAPHIC
+ *
+ *
+ * ABSTRACT
+ *
+ * This component provides conversions between geodetic (latitude and
+ * longitude) coordinates and Polar Stereographic (easting and northing)
+ * coordinates.
+ *
+ * ERROR HANDLING
+ *
+ * This component checks parameters for valid values. If an invalid
+ * value is found the error code is combined with the current error code
+ * using the bitwise or. This combining allows multiple error codes to
+ * be returned. The possible error codes are:
+ *
+ * POLAR_NO_ERROR : No errors occurred in function
+ * POLAR_LAT_ERROR : Latitude outside of valid range
+ * (-90 to 90 degrees)
+ * POLAR_LON_ERROR : Longitude outside of valid range
+ * (-180 to 360 degrees)
+ * POLAR_ORIGIN_LAT_ERROR : Latitude of true scale outside of valid
+ * range (-90 to 90 degrees)
+ * POLAR_ORIGIN_LON_ERROR : Longitude down from pole outside of valid
+ * range (-180 to 360 degrees)
+ * POLAR_EASTING_ERROR : Easting outside of valid range,
+ * depending on ellipsoid and
+ * projection parameters
+ * POLAR_NORTHING_ERROR : Northing outside of valid range,
+ * depending on ellipsoid and
+ * projection parameters
+ * POLAR_RADIUS_ERROR : Coordinates too far from pole,
+ * depending on ellipsoid and
+ * projection parameters
+ * POLAR_A_ERROR : Semi-major axis less than or equal to zero
+ * POLAR_INV_F_ERROR : Inverse flattening outside of valid range
+ * (250 to 350)
+ *
+ *
+ * REUSE NOTES
+ *
+ * POLAR STEREOGRAPHIC is intended for reuse by any application that
+ * performs a Polar Stereographic projection.
+ *
+ *
+ * REFERENCES
+ *
+ * Further information on POLAR STEREOGRAPHIC can be found in the
+ * Reuse Manual.
+ *
+ *
+ * POLAR STEREOGRAPHIC originated from :
+ * U.S. Army Topographic Engineering Center
+ * Geospatial Information Division
+ * 7701 Telegraph Road
+ * Alexandria, VA 22310-3864
+ *
+ *
+ * LICENSES
+ *
+ * None apply to this component.
+ *
+ *
+ * RESTRICTIONS
+ *
+ * POLAR STEREOGRAPHIC has no restrictions.
+ *
+ *
+ * ENVIRONMENT
+ *
+ * POLAR STEREOGRAPHIC was tested and certified in the following
+ * environments:
+ *
+ * 1. Solaris 2.5 with GCC, version 2.8.1
+ * 2. Window 95 with MS Visual C++, version 6
+ *
+ *
+ * MODIFICATIONS
+ *
+ * Date Description
+ * ---- -----------
+ * 06-11-95 Original Code
+ * 03-01-97 Original Code
+ *
+ *
+ */
+
+package gov.nasa.worldwind.geom.coords;
+
+/**
+ * Ported to Java from the NGA GeoTrans polarst.c and polarst.h code.
+ *
+ * @author Garrett Headley - Feb 12, 2007 4:48:11 PM
+ * @version $Id$
+ */
+public class PolarCoordConverter {
+
+ private static final long POLAR_NO_ERROR = 0x0000;
+ private static final long POLAR_LAT_ERROR = 0x0001;
+ private static final long POLAR_LON_ERROR = 0x0002;
+ private static final long POLAR_ORIGIN_LAT_ERROR = 0x0004;
+ private static final long POLAR_ORIGIN_LON_ERROR = 0x0008;
+ public static final long POLAR_EASTING_ERROR = 0x0010;
+ public static final long POLAR_NORTHING_ERROR = 0x0020;
+ private static final long POLAR_A_ERROR = 0x0040;
+ private static final long POLAR_INV_F_ERROR = 0x0080;
+ public static final long POLAR_RADIUS_ERROR = 0x0100;
+
+ private static final double PI = 3.14159265358979323;
+ private static final double PI_OVER_2 = PI / 2.0;
+ private static final double PI_Over_4 = PI / 4.0;
+ private static final double TWO_PI = 2.0 * PI;
+
+ /* Ellipsoid Parameters, default to WGS 84 */
+ private double Polar_a = 6378137.0; /* Semi-major axis of ellipsoid in meters */
+ private double Polar_f = 1 / 298.257223563; /* Flattening of ellipsoid */
+ private double es = 0.08181919084262188000; /* Eccentricity of ellipsoid */
+ private double es_OVER_2 = .040909595421311; /* es / 2.0 */
+ private double Southern_Hemisphere = 0; /* Flag variable */
+ private double mc = 1.0;
+ private double tc = 1.0;
+ private double e4 = 1.0033565552493;
+ private double Polar_a_mc = 6378137.0; /* Polar_a * mc */
+ private double two_Polar_a = 12756274.0; /* 2.0 * Polar_a */
+
+ /* Polar Stereographic projection Parameters */
+ private double Polar_Origin_Lat = ((PI * 90) / 180); /* Latitude of origin in radians */
+ private double Polar_Origin_Long = 0.0; /* Longitude of origin in radians */
+ private double Polar_False_Easting = 0.0; /* False easting in meters */
+ private double Polar_False_Northing = 0.0; /* False northing in meters */
+
+ /* Maximum variance for easting and northing values for WGS 84. */
+ private double Polar_Delta_Easting = 12713601.0;
+ private double Polar_Delta_Northing = 12713601.0;
+
+ private double Easting;
+ private double Northing;
+ private double Latitude;
+ private double Longitude;
+
+ PolarCoordConverter()
+ {
+ }
+
+ /**
+ * The function setPolarStereographicParameters receives the ellipsoid parameters and Polar Stereograpic projection
+ * parameters as inputs, and sets the corresponding state variables. If any errors occur, error code(s) are
+ * returned by the function, otherwise POLAR_NO_ERROR is returned.
+ *
+ * @param a Semi-major axis of ellipsoid, in meters
+ * @param f Flattening of ellipsoid
+ * @param Latitude_of_True_Scale Latitude of true scale, in radians
+ * @param Longitude_Down_from_Pole Longitude down from pole, in radians
+ * @param False_Easting Easting (X) at center of projection, in meters
+ * @param False_Northing Northing (Y) at center of projection, in meters
+ * @return error code
+ */
+ public long setPolarStereographicParameters(double a, double f, double Latitude_of_True_Scale,
+ double Longitude_Down_from_Pole, double False_Easting, double False_Northing) {
+ double es2;
+ double slat, clat;
+ double essin;
+ double one_PLUS_es, one_MINUS_es;
+ double pow_es;
+ double inv_f = 1 / f;
+ final double epsilon = 1.0e-2;
+ long Error_Code = POLAR_NO_ERROR;
+
+ if (a <= 0.0) { /* Semi-major axis must be greater than zero */
+ Error_Code |= POLAR_A_ERROR;
+ }
+ if ((inv_f < 250) || (inv_f > 350)) { /* Inverse flattening must be between 250 and 350 */
+ Error_Code |= POLAR_INV_F_ERROR;
+ }
+ if ((Latitude_of_True_Scale < -PI_OVER_2) || (Latitude_of_True_Scale > PI_OVER_2)) { /* Origin Latitude out of range */
+ Error_Code |= POLAR_ORIGIN_LAT_ERROR;
+ }
+ if ((Longitude_Down_from_Pole < -PI) || (Longitude_Down_from_Pole > TWO_PI)) { /* Origin Longitude out of range */
+ Error_Code |= POLAR_ORIGIN_LON_ERROR;
+ }
+
+ if (Error_Code == POLAR_NO_ERROR) { /* no errors */
+ Polar_a = a;
+ two_Polar_a = 2.0 * Polar_a;
+ Polar_f = f;
+
+ if (Longitude_Down_from_Pole > PI)
+ Longitude_Down_from_Pole -= TWO_PI;
+ if (Latitude_of_True_Scale < 0) {
+ Southern_Hemisphere = 1;
+ Polar_Origin_Lat = -Latitude_of_True_Scale;
+ Polar_Origin_Long = -Longitude_Down_from_Pole;
+ } else {
+ Southern_Hemisphere = 0;
+ Polar_Origin_Lat = Latitude_of_True_Scale;
+ Polar_Origin_Long = Longitude_Down_from_Pole;
+ }
+ Polar_False_Easting = False_Easting;
+ Polar_False_Northing = False_Northing;
+
+ es2 = 2 * Polar_f - Polar_f * Polar_f;
+ es = Math.sqrt(es2);
+ es_OVER_2 = es / 2.0;
+
+ if (Math.abs(Math.abs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10) {
+ slat = Math.sin(Polar_Origin_Lat);
+ essin = es * slat;
+ pow_es = Math.pow((1.0 - essin) / (1.0 + essin), es_OVER_2);
+ clat = Math.cos(Polar_Origin_Lat);
+ mc = clat / Math.sqrt(1.0 - essin * essin);
+ Polar_a_mc = Polar_a * mc;
+ tc = Math.tan(PI_Over_4 - Polar_Origin_Lat / 2.0) / pow_es;
+ } else {
+ one_PLUS_es = 1.0 + es;
+ one_MINUS_es = 1.0 - es;
+ e4 = Math.sqrt(Math.pow(one_PLUS_es, one_PLUS_es) * Math.pow(one_MINUS_es, one_MINUS_es));
+ }
+ }
+
+ /* Calculate Radius */
+ convertGeodeticToPolarStereographic(0, Polar_Origin_Long);
+
+ Polar_Delta_Northing = Northing * 2; // Increased range for accepted easting and northing values
+ Polar_Delta_Northing = Math.abs(Polar_Delta_Northing) + epsilon;
+ Polar_Delta_Easting = Polar_Delta_Northing;
+
+ return (Error_Code);
+ }
+
+ /**
+ * The function Convert_Geodetic_To_Polar_Stereographic converts geodetic coordinates (latitude and longitude) to
+ * Polar Stereographic coordinates (easting and northing), according to the current ellipsoid and Polar
+ * Stereographic projection parameters. If any errors occur, error code(s) are returned by the function, otherwise
+ * POLAR_NO_ERROR is returned.
+ *
+ * @param Latitude latitude, in radians
+ * @param Longitude Longitude, in radians
+ * @return error code
+ */
+ public long convertGeodeticToPolarStereographic(double Latitude, double Longitude) {
+ double dlam;
+ double slat;
+ double essin;
+ double t;
+ double rho;
+ double pow_es;
+ long Error_Code = POLAR_NO_ERROR;
+
+ if ((Latitude < -PI_OVER_2) || (Latitude > PI_OVER_2)) { /* Latitude out of range */
+ Error_Code |= POLAR_LAT_ERROR;
+ }
+ if ((Latitude < 0) && (Southern_Hemisphere == 0)) { /* Latitude and Origin Latitude in different hemispheres */
+ Error_Code |= POLAR_LAT_ERROR;
+ }
+ if ((Latitude > 0) && (Southern_Hemisphere == 1)) { /* Latitude and Origin Latitude in different hemispheres */
+ Error_Code |= POLAR_LAT_ERROR;
+ }
+ if ((Longitude < -PI) || (Longitude > TWO_PI)) { /* Longitude out of range */
+ Error_Code |= POLAR_LON_ERROR;
+ }
+
+ if (Error_Code == POLAR_NO_ERROR) { /* no errors */
+
+ if (Math.abs(Math.abs(Latitude) - PI_OVER_2) < 1.0e-10) {
+ Easting = 0.0;
+ Northing = 0.0;
+ } else {
+ if (Southern_Hemisphere != 0) {
+ Longitude *= -1.0;
+ Latitude *= -1.0;
+ }
+ dlam = Longitude - Polar_Origin_Long;
+ if (dlam > PI) {
+ dlam -= TWO_PI;
+ }
+ if (dlam < -PI) {
+ dlam += TWO_PI;
+ }
+ slat = Math.sin(Latitude);
+ essin = es * slat;
+ pow_es = Math.pow((1.0 - essin) / (1.0 + essin), es_OVER_2);
+ t = Math.tan(PI_Over_4 - Latitude / 2.0) / pow_es;
+
+ if (Math.abs(Math.abs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10)
+ rho = Polar_a_mc * t / tc;
+ else
+ rho = two_Polar_a * t / e4;
+
+
+ if (Southern_Hemisphere != 0) {
+ Easting = -(rho * Math.sin(dlam) - Polar_False_Easting);
+ //Easting *= -1.0;
+ Northing = rho * Math.cos(dlam) + Polar_False_Northing;
+ } else
+ Easting = rho * Math.sin(dlam) + Polar_False_Easting;
+ Northing = -rho * Math.cos(dlam) + Polar_False_Northing;
+ }
+ }
+ return (Error_Code);
+ }
+
+ public double getEasting() {
+ return Easting;
+ }
+
+ public double getNorthing() {
+ return Northing;
+ }
+
+ /**
+ * The function Convert_Polar_Stereographic_To_Geodetic converts Polar
+ * Stereographic coordinates (easting and northing) to geodetic
+ * coordinates (latitude and longitude) according to the current ellipsoid
+ * and Polar Stereographic projection Parameters. If any errors occur, the
+ * code(s) are returned by the function, otherwise POLAR_NO_ERROR
+ * is returned.
+ *
+ * @param Easting Easting (X), in meters
+ * @param Northing Northing (Y), in meters
+ * @return error code
+ */
+ public long convertPolarStereographicToGeodetic (double Easting, double Northing) {
+ double dy = 0, dx = 0;
+ double rho = 0;
+ double t;
+ double PHI, sin_PHI;
+ double tempPHI = 0.0;
+ double essin;
+ double pow_es;
+ double delta_radius;
+ long Error_Code = POLAR_NO_ERROR;
+ double min_easting = Polar_False_Easting - Polar_Delta_Easting;
+ double max_easting = Polar_False_Easting + Polar_Delta_Easting;
+ double min_northing = Polar_False_Northing - Polar_Delta_Northing;
+ double max_northing = Polar_False_Northing + Polar_Delta_Northing;
+
+ if (Easting > max_easting || Easting < min_easting) { /* Easting out of range */
+ Error_Code |= POLAR_EASTING_ERROR;
+ }
+ if (Northing > max_northing || Northing < min_northing) { /* Northing out of range */
+ Error_Code |= POLAR_NORTHING_ERROR;
+ }
+
+ if (Error_Code == POLAR_NO_ERROR) {
+ dy = Northing - Polar_False_Northing;
+ dx = Easting - Polar_False_Easting;
+
+ /* Radius of point with origin of false easting, false northing */
+ rho = Math.sqrt(dx * dx + dy * dy);
+
+ delta_radius = Math.sqrt(Polar_Delta_Easting * Polar_Delta_Easting + Polar_Delta_Northing * Polar_Delta_Northing);
+
+ if(rho > delta_radius)
+ { /* Point is outside of projection area */
+ Error_Code |= POLAR_RADIUS_ERROR;
+ }
+ }
+
+ if (Error_Code == POLAR_NO_ERROR) { /* no errors */
+ if ((dy == 0.0) && (dx == 0.0)) {
+ Latitude = PI_OVER_2;
+ Longitude = Polar_Origin_Long;
+
+ } else {
+ if (Southern_Hemisphere != 0) {
+ dy *= -1.0;
+ dx *= -1.0;
+ }
+
+ if (Math.abs(Math.abs(Polar_Origin_Lat) - PI_OVER_2) > 1.0e-10)
+ t = rho * tc / (Polar_a_mc);
+ else
+ t = rho * e4 / (two_Polar_a);
+ PHI = PI_OVER_2 - 2.0 * Math.atan(t);
+ while (Math.abs(PHI - tempPHI) > 1.0e-10) {
+ tempPHI = PHI;
+ sin_PHI = Math.sin(PHI);
+ essin = es * sin_PHI;
+ pow_es = Math.pow((1.0 - essin) / (1.0 + essin), es_OVER_2);
+ PHI = PI_OVER_2 - 2.0 * Math.atan(t * pow_es);
+ }
+ Latitude = PHI;
+ Longitude = Polar_Origin_Long + Math.atan2(dx, -dy);
+
+ if (Longitude > PI)
+ Longitude -= TWO_PI;
+ else if (Longitude < -PI)
+ Longitude += TWO_PI;
+
+
+ if (Latitude > PI_OVER_2) /* force distorted values to 90, -90 degrees */
+ Latitude = PI_OVER_2;
+ else if (Latitude < -PI_OVER_2)
+ Latitude = -PI_OVER_2;
+
+ if (Longitude > PI) /* force distorted values to 180, -180 degrees */
+ Longitude = PI;
+ else if (Longitude < -PI)
+ Longitude = -PI;
+
+ }
+ if (Southern_Hemisphere != 0) {
+ Latitude *= -1.0;
+ Longitude *= -1.0;
+ }
+
+ }
+ return (Error_Code);
+ }
+
+ /**
+ * @return Latitude in radians.
+ */
+ public double getLatitude() {
+ return Latitude;
+ }
+
+ /**
+ * @return Longitude in radians.
+ */
+ public double getLongitude() {
+ return Longitude;
+ }
+
+}
+
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/TMCoord.java b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/TMCoord.java
new file mode 100644
index 000000000..863396dde
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/TMCoord.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2011 United States Government as represented by the Administrator of the
+ * National Aeronautics and Space Administration.
+ * All Rights Reserved.
+ */
+package gov.nasa.worldwind.geom.coords;
+
+/**
+ * This class holds a set of Transverse Mercator coordinates along with the
+ * corresponding latitude and longitude.
+ *
+ * @author Patrick Murris
+ * @version $Id$
+ * @see TMCoordConverter
+ */
+public class TMCoord {
+
+ private final double latitude;
+ private final double longitude;
+ private final double easting;
+ private final double northing;
+
+ /**
+ * Create a set of Transverse Mercator coordinates from a pair of latitude and longitude,
+ * for the given Globe
and projection parameters.
+ *
+ * @param latitude the latitude double
.
+ * @param longitude the longitude double
.
+ * @param a semi-major ellipsoid radius. If this and argument f are non-null and globe is null, will use the specfied a and f.
+ * @param f ellipsoid flattening. If this and argument a are non-null and globe is null, will use the specfied a and f.
+ * @param originLatitude the origin latitude double
.
+ * @param centralMeridian the central meridian longitude double
.
+ * @param falseEasting easting value at the center of the projection in meters.
+ * @param falseNorthing northing value at the center of the projection in meters.
+ * @param scale scaling factor.
+ * @return the corresponding TMCoord
.
+ * @throws IllegalArgumentException if latitude
or longitude
is null,
+ * or the conversion to TM coordinates fails. If the globe is null conversion will default
+ * to using WGS84.
+ */
+ public static TMCoord fromLatLon(double latitude, double longitude, Double a, Double f,
+ double originLatitude, double centralMeridian,
+ double falseEasting, double falseNorthing,
+ double scale) {
+
+ final TMCoordConverter converter = new TMCoordConverter();
+ if (a == null || f == null) {
+ a = converter.getA();
+ f = converter.getF();
+ }
+ long err = converter.setTransverseMercatorParameters(a, f, Math.toRadians(originLatitude), Math.toRadians(centralMeridian),
+ falseEasting, falseNorthing, scale);
+ if (err == TMCoordConverter.TRANMERC_NO_ERROR)
+ err = converter.convertGeodeticToTransverseMercator(Math.toRadians(latitude), Math.toRadians(longitude));
+
+ if (err != TMCoordConverter.TRANMERC_NO_ERROR && err != TMCoordConverter.TRANMERC_LON_WARNING) {
+ throw new IllegalArgumentException("TM Conversion Error");
+ }
+
+ return new TMCoord(latitude, longitude, converter.getEasting(), converter.getNorthing(),
+ originLatitude, centralMeridian);
+ }
+
+ /**
+ * Create a set of Transverse Mercator coordinates for the given Globe
,
+ * easting, northing and projection parameters.
+ *
+ * @param easting the easting distance value in meters.
+ * @param northing the northing distance value in meters.
+ * @param originLatitude the origin latitude double
.
+ * @param centralMeridian the central meridian longitude double
.
+ * @param falseEasting easting value at the center of the projection in meters.
+ * @param falseNorthing northing value at the center of the projection in meters.
+ * @param scale scaling factor.
+ * @return the corresponding TMCoord
.
+ * @throws IllegalArgumentException if originLatitude
or centralMeridian
+ * is null, or the conversion to geodetic coordinates fails. If the globe is null conversion will default
+ * to using WGS84.
+ */
+ public static TMCoord fromTM(double easting, double northing,
+ double originLatitude, double centralMeridian,
+ double falseEasting, double falseNorthing,
+ double scale) {
+
+ final TMCoordConverter converter = new TMCoordConverter();
+
+ double a = converter.getA();
+ double f = converter.getF();
+ long err = converter.setTransverseMercatorParameters(a, f, Math.toRadians(originLatitude), Math.toRadians(centralMeridian),
+ falseEasting, falseNorthing, scale);
+ if (err == TMCoordConverter.TRANMERC_NO_ERROR)
+ err = converter.convertTransverseMercatorToGeodetic(easting, northing);
+
+ if (err != TMCoordConverter.TRANMERC_NO_ERROR && err != TMCoordConverter.TRANMERC_LON_WARNING) {
+ throw new IllegalArgumentException("TM Conversion Error");
+ }
+
+ return new TMCoord(Math.toDegrees(converter.getLatitude()), Math.toDegrees(converter.getLongitude()),
+ easting, northing, originLatitude, centralMeridian);
+ }
+
+ /**
+ * Create an arbitrary set of Transverse Mercator coordinates with the given values.
+ *
+ * @param latitude the latitude double
.
+ * @param longitude the longitude double
.
+ * @param easting the easting distance value in meters.
+ * @param northing the northing distance value in meters.
+ * @param originLatitude the origin latitude double
.
+ * @param centralMeridian the central meridian longitude double
.
+ * @throws IllegalArgumentException if latitude
, longitude
, originLatitude
+ * or centralMeridian
is null.
+ */
+ public TMCoord(double latitude, double longitude, double easting, double northing,
+ double originLatitude, double centralMeridian) {
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.easting = easting;
+ this.northing = northing;
+ }
+
+ public double getLatitude() {
+ return this.latitude;
+ }
+
+ public double getLongitude() {
+ return this.longitude;
+ }
+
+ public double getEasting() {
+ return this.easting;
+ }
+
+ public double getNorthing() {
+ return this.northing;
+ }
+
+}
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/TMCoordConverter.java b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/TMCoordConverter.java
new file mode 100644
index 000000000..60259b031
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/TMCoordConverter.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2011 United States Government as represented by the Administrator of the
+ * National Aeronautics and Space Administration.
+ * All Rights Reserved.
+ */
+package gov.nasa.worldwind.geom.coords;
+
+/*
+ * Converter used to translate Transverse Mercator coordinates to and from geodetic latitude and longitude.
+ *
+ * @author Patrick Murris
+ * @version $Id$
+ * @see TMCoord, UTMCoordConverter, MGRSCoordConverter
+ */
+
+/*
+ * Ported to Java from the NGA GeoTrans code tranmerc.c and tranmerc.h
+ *
+ * @author Garrett Headley, Patrick Murris
+ */
+class TMCoordConverter {
+
+ public final static int TRANMERC_NO_ERROR = 0x0000;
+ private final static int TRANMERC_LAT_ERROR = 0x0001;
+ private final static int TRANMERC_LON_ERROR = 0x0002;
+ public final static int TRANMERC_EASTING_ERROR = 0x0004;
+ public final static int TRANMERC_NORTHING_ERROR = 0x0008;
+ private final static int TRANMERC_ORIGIN_LAT_ERROR = 0x0010;
+ private final static int TRANMERC_CENT_MER_ERROR = 0x0020;
+ private final static int TRANMERC_A_ERROR = 0x0040;
+ private final static int TRANMERC_INV_F_ERROR = 0x0080;
+ private final static int TRANMERC_SCALE_FACTOR_ERROR = 0x0100;
+ public final static int TRANMERC_LON_WARNING = 0x0200;
+
+ private final static double PI = 3.14159265358979323; /* PI */
+ private final static double MAX_LAT = ((PI * 89.99) / 180.0); /* 90 degrees in radians */
+ private final static double MAX_DELTA_LONG = ((PI * 90) / 180.0); /* 90 degrees in radians */
+ private final static double MIN_SCALE_FACTOR = 0.3;
+ private final static double MAX_SCALE_FACTOR = 3.0;
+
+ /* Ellipsoid Parameters, default to WGS 84 */
+ private double TranMerc_a = 6378137.0; /* Semi-major axis of ellipsoid i meters */
+ private double TranMerc_f = 1 / 298.257223563; /* Flattening of ellipsoid */
+ private double TranMerc_es = 0.0066943799901413800; /* Eccentricity (0.08181919084262188000) squared */
+ private double TranMerc_ebs = 0.0067394967565869; /* Second Eccentricity squared */
+
+ /* Transverse_Mercator projection Parameters */
+ private double TranMerc_Origin_Lat = 0.0; /* Latitude of origin in radians */
+ private double TranMerc_Origin_Long = 0.0; /* Longitude of origin in radians */
+ private double TranMerc_False_Northing = 0.0; /* False northing in meters */
+ private double TranMerc_False_Easting = 0.0; /* False easting in meters */
+ private double TranMerc_Scale_Factor = 1.0; /* Scale factor */
+
+ /* Isometeric to geodetic latitude parameters, default to WGS 84 */
+ private double TranMerc_ap = 6367449.1458008;
+ private double TranMerc_bp = 16038.508696861;
+ private double TranMerc_cp = 16.832613334334;
+ private double TranMerc_dp = 0.021984404273757;
+ private double TranMerc_ep = 3.1148371319283e-005;
+
+ /* Maximum variance for easting and northing values for WGS 84. */
+ private double TranMerc_Delta_Easting = 40000000.0;
+ private double TranMerc_Delta_Northing = 40000000.0;
+
+ private double Easting;
+ private double Northing;
+ private double Longitude;
+ private double Latitude;
+
+ TMCoordConverter() { }
+
+ public double getA() {
+ return TranMerc_a;
+ }
+
+ public double getF() {
+ return TranMerc_f;
+ }
+
+ /**
+ * The function Set_Tranverse_Mercator_Parameters receives the ellipsoid parameters and Tranverse Mercator
+ * projection parameters as inputs, and sets the corresponding state variables. If any errors occur, the error
+ * code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is returned.
+ *
+ * @param a Semi-major axis of ellipsoid, in meters
+ * @param f Flattening of ellipsoid
+ * @param Origin_Latitude Latitude in radians at the origin of the projection
+ * @param Central_Meridian Longitude in radians at the center of the projection
+ * @param False_Easting Easting/X at the center of the projection
+ * @param False_Northing Northing/Y at the center of the projection
+ * @param Scale_Factor Projection scale factor
+ *
+ * @return error code
+ */
+ public long setTransverseMercatorParameters(double a, double f, double Origin_Latitude,
+ double Central_Meridian, double False_Easting, double False_Northing, double Scale_Factor) {
+
+ double tn; /* True Meridianal distance constant */
+ double tn2;
+ double tn3;
+ double tn4;
+ double tn5;
+ double TranMerc_b; /* Semi-minor axis of ellipsoid, in meters */
+ double inv_f = 1 / f;
+ long Error_Code = TRANMERC_NO_ERROR;
+
+ if (a <= 0.0) { /* Semi-major axis must be greater than zero */
+ Error_Code |= TRANMERC_A_ERROR;
+ }
+ if ((inv_f < 250) || (inv_f > 350)) { /* Inverse flattening must be between 250 and 350 */
+ Error_Code |= TRANMERC_INV_F_ERROR;
+ }
+ if ((Origin_Latitude < -MAX_LAT) || (Origin_Latitude > MAX_LAT)) { /* origin latitude out of range */
+ Error_Code |= TRANMERC_ORIGIN_LAT_ERROR;
+ }
+ if ((Central_Meridian < -PI) || (Central_Meridian > (2 * PI))) { /* origin longitude out of range */
+ Error_Code |= TRANMERC_CENT_MER_ERROR;
+ }
+ if ((Scale_Factor < MIN_SCALE_FACTOR) || (Scale_Factor > MAX_SCALE_FACTOR)) {
+ Error_Code |= TRANMERC_SCALE_FACTOR_ERROR;
+ }
+ if (Error_Code == TRANMERC_NO_ERROR) { /* no errors */
+ TranMerc_a = a;
+ TranMerc_f = f;
+ TranMerc_Origin_Lat = 0;
+ TranMerc_Origin_Long = 0;
+ TranMerc_False_Northing = 0;
+ TranMerc_False_Easting = 0;
+ TranMerc_Scale_Factor = 1;
+
+ /* Eccentricity Squared */
+ TranMerc_es = 2 * TranMerc_f - TranMerc_f * TranMerc_f;
+ /* Second Eccentricity Squared */
+ TranMerc_ebs = (1 / (1 - TranMerc_es)) - 1;
+
+ TranMerc_b = TranMerc_a * (1 - TranMerc_f);
+ /*True meridianal constants */
+ tn = (TranMerc_a - TranMerc_b) / (TranMerc_a + TranMerc_b);
+ tn2 = tn * tn;
+ tn3 = tn2 * tn;
+ tn4 = tn3 * tn;
+ tn5 = tn4 * tn;
+
+ TranMerc_ap = TranMerc_a * (1.e0 - tn + 5.e0 * (tn2 - tn3) / 4.e0
+ + 81.e0 * (tn4 - tn5) / 64.e0);
+ TranMerc_bp = 3.e0 * TranMerc_a * (tn - tn2 + 7.e0 * (tn3 - tn4)
+ / 8.e0 + 55.e0 * tn5 / 64.e0) / 2.e0;
+ TranMerc_cp = 15.e0 * TranMerc_a * (tn2 - tn3 + 3.e0 * (tn4 - tn5) / 4.e0) / 16.0;
+ TranMerc_dp = 35.e0 * TranMerc_a * (tn3 - tn4 + 11.e0 * tn5 / 16.e0) / 48.e0;
+ TranMerc_ep = 315.e0 * TranMerc_a * (tn4 - tn5) / 512.e0;
+
+ convertGeodeticToTransverseMercator(MAX_LAT, MAX_DELTA_LONG);
+
+ TranMerc_Delta_Easting = getEasting();
+ TranMerc_Delta_Northing = getNorthing();
+
+ convertGeodeticToTransverseMercator(0, MAX_DELTA_LONG);
+ TranMerc_Delta_Easting = getEasting();
+
+ TranMerc_Origin_Lat = Origin_Latitude;
+ if (Central_Meridian > PI)
+ Central_Meridian -= (2 * PI);
+ TranMerc_Origin_Long = Central_Meridian;
+ TranMerc_False_Northing = False_Northing;
+ TranMerc_False_Easting = False_Easting;
+ TranMerc_Scale_Factor = Scale_Factor;
+ }
+ return (Error_Code);
+ }
+
+ /**
+ * The function Convert_Geodetic_To_Transverse_Mercator converts geodetic (latitude and longitude) coordinates to
+ * Transverse Mercator projection (easting and northing) coordinates, according to the current ellipsoid and
+ * Transverse Mercator projection coordinates. If any errors occur, the error code(s) are returned by the function,
+ * otherwise TRANMERC_NO_ERROR is returned.
+ *
+ * @param Latitude Latitude in radians
+ * @param Longitude Longitude in radians
+ *
+ * @return error code
+ */
+ public long convertGeodeticToTransverseMercator(double Latitude, double Longitude) {
+
+ double c; /* Cosine of latitude */
+ double c2;
+ double c3;
+ double c5;
+ double c7;
+ double dlam; /* Delta longitude - Difference in Longitude */
+ double eta; /* constant - TranMerc_ebs *c *c */
+ double eta2;
+ double eta3;
+ double eta4;
+ double s; /* Sine of latitude */
+ double sn; /* Radius of curvature in the prime vertical */
+ double t; /* Tangent of latitude */
+ double tan2;
+ double tan3;
+ double tan4;
+ double tan5;
+ double tan6;
+ double t1; /* Term in coordinate conversion formula - GP to Y */
+ double t2; /* Term in coordinate conversion formula - GP to Y */
+ double t3; /* Term in coordinate conversion formula - GP to Y */
+ double t4; /* Term in coordinate conversion formula - GP to Y */
+ double t5; /* Term in coordinate conversion formula - GP to Y */
+ double t6; /* Term in coordinate conversion formula - GP to Y */
+ double t7; /* Term in coordinate conversion formula - GP to Y */
+ double t8; /* Term in coordinate conversion formula - GP to Y */
+ double t9; /* Term in coordinate conversion formula - GP to Y */
+ double tmd; /* True Meridional distance */
+ double tmdo; /* True Meridional distance for latitude of origin */
+ long Error_Code = TRANMERC_NO_ERROR;
+ double temp_Origin;
+ double temp_Long;
+
+ if ((Latitude < -MAX_LAT) || (Latitude > MAX_LAT)) { /* Latitude out of range */
+ Error_Code |= TRANMERC_LAT_ERROR;
+ }
+ if (Longitude > PI)
+ Longitude -= (2 * PI);
+ if ((Longitude < (TranMerc_Origin_Long - MAX_DELTA_LONG))
+ || (Longitude > (TranMerc_Origin_Long + MAX_DELTA_LONG))) {
+ if (Longitude < 0)
+ temp_Long = Longitude + 2 * PI;
+ else
+ temp_Long = Longitude;
+ if (TranMerc_Origin_Long < 0)
+ temp_Origin = TranMerc_Origin_Long + 2 * PI;
+ else
+ temp_Origin = TranMerc_Origin_Long;
+ if ((temp_Long < (temp_Origin - MAX_DELTA_LONG))
+ || (temp_Long > (temp_Origin + MAX_DELTA_LONG)))
+ Error_Code |= TRANMERC_LON_ERROR;
+ }
+ if (Error_Code == TRANMERC_NO_ERROR) { /* no errors */
+ /*
+ * Delta Longitude
+ */
+ dlam = Longitude - TranMerc_Origin_Long;
+
+ if (Math.abs(dlam) > (9.0 * PI / 180)) { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian */
+ Error_Code |= TRANMERC_LON_WARNING;
+ }
+
+ if (dlam > PI)
+ dlam -= (2 * PI);
+ if (dlam < -PI)
+ dlam += (2 * PI);
+ if (Math.abs(dlam) < 2.e-10)
+ dlam = 0.0;
+
+ s = Math.sin(Latitude);
+ c = Math.cos(Latitude);
+ c2 = c * c;
+ c3 = c2 * c;
+ c5 = c3 * c2;
+ c7 = c5 * c2;
+ t = Math.tan(Latitude);
+ tan2 = t * t;
+ tan3 = tan2 * t;
+ tan4 = tan3 * t;
+ tan5 = tan4 * t;
+ tan6 = tan5 * t;
+ eta = TranMerc_ebs * c2;
+ eta2 = eta * eta;
+ eta3 = eta2 * eta;
+ eta4 = eta3 * eta;
+
+ /* radius of curvature in prime vertical */
+ // sn = SPHSN(Latitude);
+ sn = TranMerc_a / Math.sqrt(1 - TranMerc_es * Math.pow(Math.sin(Latitude), 2));
+
+ /* True Meridianal Distances */
+ // tmd = SPHTMD(Latitude);
+ tmd = TranMerc_ap * Latitude
+ - TranMerc_bp * Math.sin(2.0 * Latitude)
+ + TranMerc_cp * Math.sin(4.0 * Latitude)
+ - TranMerc_dp * Math.sin(6.0 * Latitude)
+ + TranMerc_ep * Math.sin(8.0 * Latitude);
+ /* Origin */
+
+ // tmdo = SPHTMD (TranMerc_Origin_Lat);
+ tmdo = TranMerc_ap * TranMerc_Origin_Lat
+ - TranMerc_bp * Math.sin(2.0 * TranMerc_Origin_Lat)
+ + TranMerc_cp * Math.sin(4.0 * TranMerc_Origin_Lat)
+ - TranMerc_dp * Math.sin(6.0 * TranMerc_Origin_Lat)
+ + TranMerc_ep * Math.sin(8.0 * TranMerc_Origin_Lat);
+
+ /* northing */
+ t1 = (tmd - tmdo) * TranMerc_Scale_Factor;
+ t2 = sn * s * c * TranMerc_Scale_Factor / 2.e0;
+ t3 = sn * s * c3 * TranMerc_Scale_Factor * (5.e0 - tan2 + 9.e0 * eta
+ + 4.e0 * eta2) / 24.e0;
+
+ t4 = sn * s * c5 * TranMerc_Scale_Factor * (61.e0 - 58.e0 * tan2
+ + tan4 + 270.e0 * eta - 330.e0 * tan2 * eta + 445.e0 * eta2
+ + 324.e0 * eta3 - 680.e0 * tan2 * eta2 + 88.e0 * eta4
+ - 600.e0 * tan2 * eta3 - 192.e0 * tan2 * eta4) / 720.e0;
+
+ t5 = sn * s * c7 * TranMerc_Scale_Factor * (1385.e0 - 3111.e0 *
+ tan2 + 543.e0 * tan4 - tan6) / 40320.e0;
+
+ Northing = TranMerc_False_Northing + t1 + Math.pow(dlam, 2.e0) * t2
+ + Math.pow(dlam, 4.e0) * t3 + Math.pow(dlam, 6.e0) * t4
+ + Math.pow(dlam, 8.e0) * t5;
+
+ /* Easting */
+ t6 = sn * c * TranMerc_Scale_Factor;
+ t7 = sn * c3 * TranMerc_Scale_Factor * (1.e0 - tan2 + eta) / 6.e0;
+ t8 = sn * c5 * TranMerc_Scale_Factor * (5.e0 - 18.e0 * tan2 + tan4
+ + 14.e0 * eta - 58.e0 * tan2 * eta + 13.e0 * eta2 + 4.e0 * eta3
+ - 64.e0 * tan2 * eta2 - 24.e0 * tan2 * eta3) / 120.e0;
+ t9 = sn * c7 * TranMerc_Scale_Factor * (61.e0 - 479.e0 * tan2
+ + 179.e0 * tan4 - tan6) / 5040.e0;
+
+ Easting = TranMerc_False_Easting + dlam * t6 + Math.pow(dlam, 3.e0) * t7
+ + Math.pow(dlam, 5.e0) * t8 + Math.pow(dlam, 7.e0) * t9;
+ }
+ return (Error_Code);
+ }
+
+ /** @return Easting/X at the center of the projection */
+ public double getEasting() {
+ return Easting;
+ }
+
+ /** @return Northing/Y at the center of the projection */
+ public double getNorthing() {
+ return Northing;
+ }
+
+ /**
+ * The function Convert_Transverse_Mercator_To_Geodetic converts Transverse Mercator projection (easting and
+ * northing) coordinates to geodetic (latitude and longitude) coordinates, according to the current ellipsoid and
+ * Transverse Mercator projection parameters. If any errors occur, the error code(s) are returned by the function,
+ * otherwise TRANMERC_NO_ERROR is returned.
+ *
+ * @param Easting Easting/X in meters
+ * @param Northing Northing/Y in meters
+ *
+ * @return error code
+ */
+ public long convertTransverseMercatorToGeodetic(double Easting, double Northing) {
+ double c; /* Cosine of latitude */
+ double de; /* Delta easting - Difference in Easting (Easting-Fe) */
+ double dlam; /* Delta longitude - Difference in Longitude */
+ double eta; /* constant - TranMerc_ebs *c *c */
+ double eta2;
+ double eta3;
+ double eta4;
+ double ftphi; /* Footpoint latitude */
+ int i; /* Loop iterator */
+ //double s; /* Sine of latitude */
+ double sn; /* Radius of curvature in the prime vertical */
+ double sr; /* Radius of curvature in the meridian */
+ double t; /* Tangent of latitude */
+ double tan2;
+ double tan4;
+ double t10; /* Term in coordinate conversion formula - GP to Y */
+ double t11; /* Term in coordinate conversion formula - GP to Y */
+ double t12; /* Term in coordinate conversion formula - GP to Y */
+ double t13; /* Term in coordinate conversion formula - GP to Y */
+ double t14; /* Term in coordinate conversion formula - GP to Y */
+ double t15; /* Term in coordinate conversion formula - GP to Y */
+ double t16; /* Term in coordinate conversion formula - GP to Y */
+ double t17; /* Term in coordinate conversion formula - GP to Y */
+ double tmd; /* True Meridional distance */
+ double tmdo; /* True Meridional distance for latitude of origin */
+ long Error_Code = TRANMERC_NO_ERROR;
+
+ if ((Easting < (TranMerc_False_Easting - TranMerc_Delta_Easting))
+ || (Easting > (TranMerc_False_Easting + TranMerc_Delta_Easting))) { /* Easting out of range */
+ Error_Code |= TRANMERC_EASTING_ERROR;
+ }
+ if ((Northing < (TranMerc_False_Northing - TranMerc_Delta_Northing))
+ || (Northing > (TranMerc_False_Northing + TranMerc_Delta_Northing))) { /* Northing out of range */
+ Error_Code |= TRANMERC_NORTHING_ERROR;
+ }
+
+ if (Error_Code == TRANMERC_NO_ERROR) {
+ /* True Meridional Distances for latitude of origin */
+ // tmdo = SPHTMD(TranMerc_Origin_Lat);
+ tmdo = TranMerc_ap * TranMerc_Origin_Lat
+ - TranMerc_bp * Math.sin(2.0 * TranMerc_Origin_Lat)
+ + TranMerc_cp * Math.sin(4.0 * TranMerc_Origin_Lat)
+ - TranMerc_dp * Math.sin(6.0 * TranMerc_Origin_Lat)
+ + TranMerc_ep * Math.sin(8.0 * TranMerc_Origin_Lat);
+
+ /* Origin */
+ tmd = tmdo + (Northing - TranMerc_False_Northing) / TranMerc_Scale_Factor;
+
+ /* First Estimate */
+ //sr = SPHSR(0.e0);
+ sr = TranMerc_a * (1.e0 - TranMerc_es) /
+ Math.pow(Math.sqrt(1.e0 - TranMerc_es * Math.pow(Math.sin(0.e0), 2)), 3);
+
+ ftphi = tmd / sr;
+
+ for (i = 0; i < 5; i++) {
+ // t10 = SPHTMD (ftphi);
+ t10 = TranMerc_ap * ftphi
+ - TranMerc_bp * Math.sin(2.0 * ftphi)
+ + TranMerc_cp * Math.sin(4.0 * ftphi)
+ - TranMerc_dp * Math.sin(6.0 * ftphi)
+ + TranMerc_ep * Math.sin(8.0 * ftphi);
+ // sr = SPHSR(ftphi);
+ sr = TranMerc_a * (1.e0 - TranMerc_es) /
+ Math.pow(Math.sqrt(1.e0 - TranMerc_es * Math.pow(Math.sin(ftphi), 2)), 3);
+ ftphi = ftphi + (tmd - t10) / sr;
+ }
+
+ /* Radius of Curvature in the meridian */
+ // sr = SPHSR(ftphi);
+ sr = TranMerc_a * (1.e0 - TranMerc_es) /
+ Math.pow(Math.sqrt(1.e0 - TranMerc_es * Math.pow(Math.sin(ftphi), 2)), 3);
+
+ /* Radius of Curvature in the meridian */
+ // sn = SPHSN(ftphi);
+ sn = TranMerc_a / Math.sqrt(1.e0 - TranMerc_es * Math.pow(Math.sin(ftphi), 2));
+
+ /* Sine Cosine terms */
+ //s = Math.sin(ftphi);
+ c = Math.cos(ftphi);
+
+ /* Tangent Value */
+ t = Math.tan(ftphi);
+ tan2 = t * t;
+ tan4 = tan2 * tan2;
+ eta = TranMerc_ebs * Math.pow(c, 2);
+ eta2 = eta * eta;
+ eta3 = eta2 * eta;
+ eta4 = eta3 * eta;
+ de = Easting - TranMerc_False_Easting;
+ if (Math.abs(de) < 0.0001)
+ de = 0.0;
+
+ /* Latitude */
+ t10 = t / (2.e0 * sr * sn * Math.pow(TranMerc_Scale_Factor, 2));
+ t11 = t * (5.e0 + 3.e0 * tan2 + eta - 4.e0 * Math.pow(eta, 2)
+ - 9.e0 * tan2 * eta) / (24.e0 * sr * Math.pow(sn, 3)
+ * Math.pow(TranMerc_Scale_Factor, 4));
+ t12 = t * (61.e0 + 90.e0 * tan2 + 46.e0 * eta + 45.E0 * tan4
+ - 252.e0 * tan2 * eta - 3.e0 * eta2 + 100.e0
+ * eta3 - 66.e0 * tan2 * eta2 - 90.e0 * tan4
+ * eta + 88.e0 * eta4 + 225.e0 * tan4 * eta2
+ + 84.e0 * tan2 * eta3 - 192.e0 * tan2 * eta4)
+ / (720.e0 * sr * Math.pow(sn, 5) * Math.pow(TranMerc_Scale_Factor, 6));
+ t13 = t * (1385.e0 + 3633.e0 * tan2 + 4095.e0 * tan4 + 1575.e0
+ * Math.pow(t, 6)) / (40320.e0 * sr * Math.pow(sn, 7) * Math.pow(TranMerc_Scale_Factor, 8));
+ Latitude = ftphi - Math.pow(de, 2) * t10 + Math.pow(de, 4) * t11 - Math.pow(de, 6) * t12
+ + Math.pow(de, 8) * t13;
+
+ t14 = 1.e0 / (sn * c * TranMerc_Scale_Factor);
+
+ t15 = (1.e0 + 2.e0 * tan2 + eta) / (6.e0 * Math.pow(sn, 3) * c *
+ Math.pow(TranMerc_Scale_Factor, 3));
+
+ t16 = (5.e0 + 6.e0 * eta + 28.e0 * tan2 - 3.e0 * eta2
+ + 8.e0 * tan2 * eta + 24.e0 * tan4 - 4.e0
+ * eta3 + 4.e0 * tan2 * eta2 + 24.e0
+ * tan2 * eta3) / (120.e0 * Math.pow(sn, 5) * c
+ * Math.pow(TranMerc_Scale_Factor, 5));
+
+ t17 = (61.e0 + 662.e0 * tan2 + 1320.e0 * tan4 + 720.e0
+ * Math.pow(t, 6)) / (5040.e0 * Math.pow(sn, 7) * c
+ * Math.pow(TranMerc_Scale_Factor, 7));
+
+ /* Difference in Longitude */
+ dlam = de * t14 - Math.pow(de, 3) * t15 + Math.pow(de, 5) * t16 - Math.pow(de, 7) * t17;
+
+ /* Longitude */
+ Longitude = TranMerc_Origin_Long + dlam;
+
+ if (Math.abs(Latitude) > (90.0 * PI / 180.0))
+ Error_Code |= TRANMERC_NORTHING_ERROR;
+
+ if ((Longitude) > (PI)) {
+ Longitude -= (2 * PI);
+ if (Math.abs(Longitude) > PI)
+ Error_Code |= TRANMERC_EASTING_ERROR;
+ }
+
+ if (Math.abs(dlam) > (9.0 * PI / 180) * Math.cos(Latitude)) { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian at the equator */
+ /* and decreases to 0 degrees at the poles */
+ /* As you move towards the poles, distortion will become more significant */
+ Error_Code |= TRANMERC_LON_WARNING;
+ }
+
+ if (Latitude > 1.0e10)
+ Error_Code |= TRANMERC_LON_WARNING;
+ }
+ return (Error_Code);
+ }
+
+ /** @return Latitude in radians. */
+ public double getLatitude() {
+ return Latitude;
+ }
+
+ /** @return Longitude in radians. */
+ public double getLongitude() {
+ return Longitude;
+ }
+} // end TMConverter class
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UPSCoord.java b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UPSCoord.java
new file mode 100644
index 000000000..881391be7
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UPSCoord.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2011 United States Government as represented by the Administrator of the
+ * National Aeronautics and Space Administration.
+ * All Rights Reserved.
+ */
+package gov.nasa.worldwind.geom.coords;
+
+import android.support.annotation.NonNull;
+
+/**
+ * This immutable class holds a set of UPS coordinates along with it's corresponding latitude and longitude.
+ *
+ * @author Patrick Murris
+ * @version $Id$
+ */
+
+public class UPSCoord {
+ private final double latitude;
+ private final double longitude;
+ private final Hemisphere hemisphere;
+ private final double easting;
+ private final double northing;
+
+ /**
+ * Create a set of UPS coordinates from a pair of latitude and longitude for the given Globe
.
+ *
+ * @param latitude the latitude double
.
+ * @param longitude the longitude double
.
+ *
+ * @return the corresponding UPSCoord
.
+ *
+ * @throws IllegalArgumentException if latitude
or longitude
is null, or the conversion to
+ * UPS coordinates fails.
+ */
+ public static UPSCoord fromLatLon(double latitude, double longitude) {
+ final UPSCoordConverter converter = new UPSCoordConverter();
+ long err = converter.convertGeodeticToUPS(Math.toRadians(latitude), Math.toRadians(longitude));
+
+ if (err != UPSCoordConverter.UPS_NO_ERROR) {
+ throw new IllegalArgumentException("UPS Conversion Error");
+ }
+
+ return new UPSCoord(latitude, longitude, converter.getHemisphere(),
+ converter.getEasting(), converter.getNorthing());
+ }
+
+ /**
+ * Create a set of UPS coordinates for the given Globe
.
+ *
+ * @param hemisphere the hemisphere, either {@link Hemisphere#N} of {@link Hemisphere#S}.
+ * @param easting the easting distance in meters
+ * @param northing the northing distance in meters.
+ *
+ * @return the corresponding UPSCoord
.
+ *
+ * @throws IllegalArgumentException if the conversion to UPS coordinates fails.
+ */
+ public static UPSCoord fromUPS(Hemisphere hemisphere, double easting, double northing) {
+ final UPSCoordConverter converter = new UPSCoordConverter();
+ long err = converter.convertUPSToGeodetic(hemisphere, easting, northing);
+
+ if (err != UTMCoordConverter.UTM_NO_ERROR) {
+ throw new IllegalArgumentException("UTM Conversion Error");
+ }
+
+ return new UPSCoord(Math.toDegrees(converter.getLatitude()),
+ Math.toDegrees(converter.getLongitude()),
+ hemisphere, easting, northing);
+ }
+
+ /**
+ * Create an arbitrary set of UPS coordinates with the given values.
+ *
+ * @param latitude the latitude double
.
+ * @param longitude the longitude double
.
+ * @param hemisphere the hemisphere, either {@link Hemisphere#N} of {@link Hemisphere#S}.
+ * @param easting the easting distance in meters
+ * @param northing the northing distance in meters.
+ *
+ * @throws IllegalArgumentException if latitude
, longitude
, or hemisphere
is
+ * null.
+ */
+ public UPSCoord(double latitude, double longitude, Hemisphere hemisphere, double easting, double northing) {
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.hemisphere = hemisphere;
+ this.easting = easting;
+ this.northing = northing;
+ }
+
+ public double getLatitude() {
+ return this.latitude;
+ }
+
+ public double getLongitude() {
+ return this.longitude;
+ }
+
+ public Hemisphere getHemisphere() {
+ return this.hemisphere;
+ }
+
+ public double getEasting() {
+ return this.easting;
+ }
+
+ public double getNorthing() {
+ return this.northing;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return hemisphere + " " + easting + "E" + " " + northing + "N";
+ }
+}
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UPSCoordConverter.java b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UPSCoordConverter.java
new file mode 100644
index 000000000..4e5f66024
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UPSCoordConverter.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2011 United States Government as represented by the Administrator of the
+ * National Aeronautics and Space Administration.
+ * All Rights Reserved.
+ */
+
+/********************************************************************/
+/* RSC IDENTIFIER: UPS
+ *
+ *
+ * ABSTRACT
+ *
+ * This component provides conversions between geodetic (latitude
+ * and longitude) coordinates and Universal Polar Stereographic (UPS)
+ * projection (hemisphere, easting, and northing) coordinates.
+ *
+ *
+ * ERROR HANDLING
+ *
+ * This component checks parameters for valid values. If an
+ * invalid value is found the error code is combined with the
+ * current error code using the bitwise or. This combining allows
+ * multiple error codes to be returned. The possible error codes
+ * are:
+ *
+ * UPS_NO_ERROR : No errors occurred in function
+ * UPS_LAT_ERROR : latitude outside of valid range
+ * (North Pole: 83.5 to 90,
+ * South Pole: -79.5 to -90)
+ * UPS_LON_ERROR : longitude outside of valid range
+ * (-180 to 360 degrees)
+ * UPS_HEMISPHERE_ERROR : Invalid hemisphere ('N' or 'S')
+ * UPS_EASTING_ERROR : easting outside of valid range,
+ * (0 to 4,000,000m)
+ * UPS_NORTHING_ERROR : northing outside of valid range,
+ * (0 to 4,000,000m)
+ * UPS_A_ERROR : Semi-major axis less than or equal to zero
+ * UPS_INV_F_ERROR : Inverse flattening outside of valid range
+ * (250 to 350)
+ *
+ *
+ * REUSE NOTES
+ *
+ * UPS is intended for reuse by any application that performs a Universal
+ * Polar Stereographic (UPS) projection.
+ *
+ *
+ * REFERENCES
+ *
+ * Further information on UPS can be found in the Reuse Manual.
+ *
+ * UPS originated from : U.S. Army Topographic Engineering Center
+ * Geospatial Information Division
+ * 7701 Telegraph Road
+ * Alexandria, VA 22310-3864
+ *
+ *
+ * LICENSES
+ *
+ * None apply to this component.
+ *
+ *
+ * RESTRICTIONS
+ *
+ * UPS has no restrictions.
+ *
+ *
+ * ENVIRONMENT
+ *
+ * UPS was tested and certified in the following environments:
+ *
+ * 1. Solaris 2.5 with GCC version 2.8.1
+ * 2. Windows 95 with MS Visual C++ version 6
+ *
+ *
+ * MODIFICATIONS
+ *
+ * Date Description
+ * ---- -----------
+ * 06-11-95 Original Code
+ * 03-01-97 Original Code
+ *
+ *
+ */
+
+package gov.nasa.worldwind.geom.coords;
+
+/**
+ * Ported to Java from the NGA GeoTrans ups.c and ups.h code - Feb 12, 2007 4:52:59 PM
+ *
+ * @author Garrett Headley, Patrick Murris
+ * @version $Id$
+ */
+public class UPSCoordConverter {
+
+ public static final int UPS_NO_ERROR = 0x0000;
+ private static final int UPS_LAT_ERROR = 0x0001;
+ private static final int UPS_LON_ERROR = 0x0002;
+ public static final int UPS_HEMISPHERE_ERROR = 0x0004;
+ public static final int UPS_EASTING_ERROR = 0x0008;
+ public static final int UPS_NORTHING_ERROR = 0x0010;
+
+ private static final double PI = 3.14159265358979323;
+ private static final double MAX_LAT = (PI * 90) / 180.0; // 90 degrees in radians
+ // Min and max latitude values accepted
+ private static final double MIN_NORTH_LAT = 72 * PI / 180.0; // 83.5
+ private static final double MIN_SOUTH_LAT = -72 * PI / 180.0; // -79.5
+
+ private static final double MAX_ORIGIN_LAT = (81.114528 * PI) / 180.0;
+ private static final double MIN_EAST_NORTH = 0;
+ private static final double MAX_EAST_NORTH = 4000000;
+
+ private double UPS_Origin_Latitude = MAX_ORIGIN_LAT; /*set default = North hemisphere */
+ private double UPS_Origin_Longitude = 0.0;
+
+ /* Ellipsoid Parameters, default to WGS 84 */
+ private double UPS_a = 6378137.0; /* Semi-major axis of ellipsoid in meters */
+ private double UPS_f = 1 / 298.257223563; /* Flattening of ellipsoid */
+ private double UPS_False_Easting = 2000000.0;
+ private double UPS_False_Northing = 2000000.0;
+ private double false_easting = 0.0;
+ private double false_northing = 0.0;
+ private double UPS_Easting = 0.0;
+ private double UPS_Northing = 0.0;
+
+ private double easting = 0.0;
+ private double northing = 0.0;
+ private Hemisphere hemisphere = Hemisphere.N;
+ private double latitude = 0.0;
+ private double longitude = 0.0;
+
+ private PolarCoordConverter polarConverter = new PolarCoordConverter();
+
+ UPSCoordConverter(){}
+
+ /**
+ * The function convertGeodeticToUPS converts geodetic (latitude and longitude) coordinates to UPS (hemisphere,
+ * easting, and northing) coordinates, according to the current ellipsoid parameters. If any errors occur, the error
+ * code(s) are returned by the function, otherwide UPS_NO_ERROR is returned.
+ *
+ * @param latitude latitude in radians
+ * @param longitude longitude in radians
+ *
+ * @return error code
+ */
+ public long convertGeodeticToUPS(double latitude, double longitude) {
+ if ((latitude < -MAX_LAT) || (latitude > MAX_LAT)) { /* latitude out of range */
+ return UPS_LAT_ERROR;
+ }
+ if ((latitude < 0) && (latitude > MIN_SOUTH_LAT))
+ return UPS_LAT_ERROR;
+ if ((latitude >= 0) && (latitude < MIN_NORTH_LAT))
+ return UPS_LAT_ERROR;
+
+ if ((longitude < -PI) || (longitude > (2 * PI))) { /* slam out of range */
+ return UPS_LON_ERROR;
+ }
+
+ if (latitude < 0) {
+ UPS_Origin_Latitude = -MAX_ORIGIN_LAT;
+ hemisphere = Hemisphere.S;
+ } else {
+ UPS_Origin_Latitude = MAX_ORIGIN_LAT;
+ hemisphere = Hemisphere.N;
+ }
+
+ polarConverter.setPolarStereographicParameters(UPS_a, UPS_f,
+ UPS_Origin_Latitude, UPS_Origin_Longitude,
+ false_easting, false_northing);
+
+ polarConverter.convertGeodeticToPolarStereographic(latitude, longitude);
+
+ UPS_Easting = UPS_False_Easting + polarConverter.getEasting();
+ UPS_Northing = UPS_False_Northing + polarConverter.getNorthing();
+ if (Hemisphere.S.equals(hemisphere))
+ UPS_Northing = UPS_False_Northing - polarConverter.getNorthing();
+
+ easting = UPS_Easting;
+ northing = UPS_Northing;
+
+ return UPS_NO_ERROR;
+ }
+
+ /** @return easting/X in meters */
+ public double getEasting() {
+ return easting;
+ }
+
+ /** @return northing/Y in meters */
+ public double getNorthing() {
+ return northing;
+ }
+
+ /**
+ * @return hemisphere, either {@link Hemisphere#N} of {@link Hemisphere#S}.
+ */
+ public Hemisphere getHemisphere() {
+ return hemisphere;
+ }
+
+ /**
+ * The function Convert_UPS_To_Geodetic converts UPS (hemisphere, easting, and northing) coordinates to geodetic
+ * (latitude and longitude) coordinates according to the current ellipsoid parameters. If any errors occur, the
+ * error code(s) are returned by the function, otherwise UPS_NO_ERROR is returned.
+ *
+ * @param hemisphere hemisphere, either {@link Hemisphere#N} of {@link Hemisphere#S}.
+ * @param easting easting/X in meters
+ * @param northing northing/Y in meters
+ *
+ * @return error code
+ */
+ public long convertUPSToGeodetic(Hemisphere hemisphere, double easting, double northing) {
+ long Error_Code = UPS_NO_ERROR;
+
+ if (!Hemisphere.N.equals(hemisphere) && !Hemisphere.S.equals(hemisphere))
+ Error_Code |= UPS_HEMISPHERE_ERROR;
+ if ((easting < MIN_EAST_NORTH) || (easting > MAX_EAST_NORTH))
+ Error_Code |= UPS_EASTING_ERROR;
+ if ((northing < MIN_EAST_NORTH) || (northing > MAX_EAST_NORTH))
+ Error_Code |= UPS_NORTHING_ERROR;
+
+ if (Hemisphere.N.equals(hemisphere))
+ UPS_Origin_Latitude = MAX_ORIGIN_LAT;
+ if (Hemisphere.S.equals(hemisphere))
+ UPS_Origin_Latitude = -MAX_ORIGIN_LAT;
+
+ if (Error_Code == UPS_NO_ERROR) { /* no errors */
+ polarConverter.setPolarStereographicParameters(UPS_a,
+ UPS_f,
+ UPS_Origin_Latitude,
+ UPS_Origin_Longitude,
+ UPS_False_Easting,
+ UPS_False_Northing);
+
+ polarConverter.convertPolarStereographicToGeodetic(easting, northing);
+ latitude = polarConverter.getLatitude();
+ longitude = polarConverter.getLongitude();
+
+ if ((latitude < 0) && (latitude > MIN_SOUTH_LAT))
+ Error_Code |= UPS_LAT_ERROR;
+ if ((latitude >= 0) && (latitude < MIN_NORTH_LAT))
+ Error_Code |= UPS_LAT_ERROR;
+ }
+ return Error_Code;
+ }
+
+ /** @return latitude in radians. */
+ public double getLatitude() {
+ return latitude;
+ }
+
+ /** @return longitude in radians. */
+ public double getLongitude() {
+ return longitude;
+ }
+}
+
+
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UTMCoord.java b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UTMCoord.java
new file mode 100644
index 000000000..06989c8b6
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UTMCoord.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2011 United States Government as represented by the Administrator of the
+ * National Aeronautics and Space Administration.
+ * All Rights Reserved.
+ */
+package gov.nasa.worldwind.geom.coords;
+
+import android.support.annotation.NonNull;
+
+/**
+ * This immutable class holds a set of UTM coordinates along with it's corresponding latitude and longitude.
+ *
+ * @author Patrick Murris
+ * @version $Id$
+ */
+
+public class UTMCoord {
+
+ private final double latitude;
+ private final double longitude;
+ private final Hemisphere hemisphere;
+ private final int zone;
+ private final double easting;
+ private final double northing;
+
+ /**
+ * Create a set of UTM coordinates from a pair of latitude and longitude for the given Globe
.
+ *
+ * @param latitude the latitude double
.
+ * @param longitude the longitude double
.
+ *
+ * @return the corresponding UTMCoord
.
+ *
+ * @throws IllegalArgumentException if latitude
or longitude
is null, or the conversion to
+ * UTM coordinates fails.
+ */
+ public static UTMCoord fromLatLon(double latitude, double longitude) {
+ final UTMCoordConverter converter = new UTMCoordConverter();
+ long err = converter.convertGeodeticToUTM(Math.toRadians(latitude), Math.toRadians(longitude));
+
+ if (err != UTMCoordConverter.UTM_NO_ERROR) {
+ throw new IllegalArgumentException("UTM Conversion Error");
+ }
+
+ return new UTMCoord(latitude, longitude, converter.getZone(), converter.getHemisphere(),
+ converter.getEasting(), converter.getNorthing());
+ }
+
+ /**
+ * Create a set of UTM coordinates for the given Globe
.
+ *
+ * @param zone the UTM zone - 1 to 60.
+ * @param hemisphere the hemisphere, either {@link Hemisphere#N} of {@link Hemisphere#S}.
+ * @param easting the easting distance in meters
+ * @param northing the northing distance in meters.
+ *
+ * @return the corresponding UTMCoord
.
+ *
+ * @throws IllegalArgumentException if the conversion to UTM coordinates fails.
+ */
+ public static UTMCoord fromUTM(int zone, Hemisphere hemisphere, double easting, double northing) {
+ final UTMCoordConverter converter = new UTMCoordConverter();
+ long err = converter.convertUTMToGeodetic(zone, hemisphere, easting, northing);
+
+ if (err != UTMCoordConverter.UTM_NO_ERROR) {
+ throw new IllegalArgumentException("UTM Conversion Error");
+ }
+
+ return new UTMCoord(Math.toDegrees(converter.getLatitude()),
+ Math.toDegrees(converter.getLongitude()),
+ zone, hemisphere, easting, northing);
+ }
+
+ /**
+ * Create an arbitrary set of UTM coordinates with the given values.
+ *
+ * @param latitude the latitude double
.
+ * @param longitude the longitude double
.
+ * @param zone the UTM zone - 1 to 60.
+ * @param hemisphere the hemisphere, either {@link Hemisphere#N} of {@link Hemisphere#S}.
+ * @param easting the easting distance in meters
+ * @param northing the northing distance in meters.
+ * @throws IllegalArgumentException if latitude
or longitude
is null.
+ */
+ public UTMCoord(double latitude, double longitude, int zone, Hemisphere hemisphere, double easting, double northing) {
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.hemisphere = hemisphere;
+ this.zone = zone;
+ this.easting = easting;
+ this.northing = northing;
+ }
+
+ public double getLatitude() {
+ return this.latitude;
+ }
+
+ public double getLongitude() {
+ return this.longitude;
+ }
+
+ public int getZone() {
+ return this.zone;
+ }
+
+ public Hemisphere getHemisphere() {
+ return this.hemisphere;
+ }
+
+ public double getEasting() {
+ return this.easting;
+ }
+
+ public double getNorthing() {
+ return this.northing;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return String.valueOf(zone) + " " + hemisphere + " " + easting + "E" + " " + northing + "N";
+ }
+}
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UTMCoordConverter.java b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UTMCoordConverter.java
new file mode 100644
index 000000000..1008ced1f
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/geom/coords/UTMCoordConverter.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2011 United States Government as represented by the Administrator of the
+ * National Aeronautics and Space Administration.
+ * All Rights Reserved.
+ */
+package gov.nasa.worldwind.geom.coords;
+
+/**
+ * Converter used to translate UTM coordinates to and from geodetic latitude and longitude.
+ *
+ * @author Patrick Murris
+ * @version $Id$
+ * @see UTMCoord, TMCoordConverter
+ */
+
+/**
+ * Ported to Java from the NGA GeoTrans utm.c and utm.h
+ *
+ * @author Garrett Headley, Patrick Murris
+ */
+class UTMCoordConverter {
+
+ public final static int UTM_NO_ERROR = 0x0000;
+ public final static int UTM_LAT_ERROR = 0x0001;
+ public final static int UTM_LON_ERROR = 0x0002;
+ public final static int UTM_EASTING_ERROR = 0x0004;
+ public final static int UTM_NORTHING_ERROR = 0x0008;
+ public final static int UTM_ZONE_ERROR = 0x0010;
+ public final static int UTM_HEMISPHERE_ERROR = 0x0020;
+ public final static int UTM_ZONE_OVERRIDE_ERROR = 0x0040;
+ public final static int UTM_TM_ERROR = 0x0200;
+
+ private final static double PI = 3.14159265358979323;
+ //private final static double MIN_LAT = ((-80.5 * PI) / 180.0); /* -80.5 degrees in radians */
+ //private final static double MAX_LAT = ((84.5 * PI) / 180.0); /* 84.5 degrees in radians */
+ private final static double MIN_LAT = ((-82 * PI) / 180.0); /* -82 degrees in radians */
+ private final static double MAX_LAT = ((86 * PI) / 180.0); /* 86 degrees in radians */
+
+ private final static int MIN_EASTING = 100000;
+ private final static int MAX_EASTING = 900000;
+ private final static int MIN_NORTHING = 0;
+ private final static int MAX_NORTHING = 10000000;
+
+ private double UTM_a = 6378137.0; /* Semi-major axis of ellipsoid in meters */
+ private double UTM_f = 1 / 298.257223563; /* Flattening of ellipsoid */
+ private long UTM_Override = 0; /* Zone override flag */
+
+ private double Easting;
+ private double Northing;
+ private Hemisphere hemisphere;
+ private int Zone;
+ private double Latitude;
+ private double Longitude;
+ private double Central_Meridian;
+
+ UTMCoordConverter(){}
+
+ /**
+ * The function Convert_Geodetic_To_UTM converts geodetic (latitude and longitude) coordinates to UTM projection
+ * (zone, hemisphere, easting and northing) coordinates according to the current ellipsoid and UTM zone override
+ * parameters. If any errors occur, the error code(s) are returned by the function, otherwise UTM_NO_ERROR is
+ * returned.
+ *
+ * @param Latitude Latitude in radians
+ * @param Longitude Longitude in radians
+ *
+ * @return error code
+ */
+ public long convertGeodeticToUTM(double Latitude, double Longitude) {
+ long Lat_Degrees;
+ long Long_Degrees;
+ long temp_zone;
+ long Error_Code = UTM_NO_ERROR;
+ double Origin_Latitude = 0;
+ double False_Easting = 500000;
+ double False_Northing = 0;
+ double Scale = 0.9996;
+
+ if ((Latitude < MIN_LAT) || (Latitude > MAX_LAT)) { /* Latitude out of range */
+ Error_Code |= UTM_LAT_ERROR;
+ }
+ if ((Longitude < -PI) || (Longitude > (2 * PI))) { /* Longitude out of range */
+ Error_Code |= UTM_LON_ERROR;
+ }
+ if (Error_Code == UTM_NO_ERROR) { /* no errors */
+ if (Longitude < 0)
+ Longitude += (2 * PI) + 1.0e-10;
+ Lat_Degrees = (long) (Latitude * 180.0 / PI);
+ Long_Degrees = (long) (Longitude * 180.0 / PI);
+
+ if (Longitude < PI)
+ temp_zone = (long) (31 + ((Longitude * 180.0 / PI) / 6.0));
+ else
+ temp_zone = (long) (((Longitude * 180.0 / PI) / 6.0) - 29);
+ if (temp_zone > 60)
+ temp_zone = 1;
+ /* UTM special cases */
+ if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > -1) && (Long_Degrees < 3))
+ temp_zone = 31;
+ if ((Lat_Degrees > 55) && (Lat_Degrees < 64) && (Long_Degrees > 2) && (Long_Degrees < 12))
+ temp_zone = 32;
+ if ((Lat_Degrees > 71) && (Long_Degrees > -1) && (Long_Degrees < 9))
+ temp_zone = 31;
+ if ((Lat_Degrees > 71) && (Long_Degrees > 8) && (Long_Degrees < 21))
+ temp_zone = 33;
+ if ((Lat_Degrees > 71) && (Long_Degrees > 20) && (Long_Degrees < 33))
+ temp_zone = 35;
+ if ((Lat_Degrees > 71) && (Long_Degrees > 32) && (Long_Degrees < 42))
+ temp_zone = 37;
+
+ if (UTM_Override != 0) {
+ if ((temp_zone == 1) && (UTM_Override == 60))
+ temp_zone = UTM_Override;
+ else if ((temp_zone == 60) && (UTM_Override == 1))
+ temp_zone = UTM_Override;
+ else if (((temp_zone - 1) <= UTM_Override) && (UTM_Override <= (temp_zone + 1)))
+ temp_zone = UTM_Override;
+ else
+ Error_Code = UTM_ZONE_OVERRIDE_ERROR;
+ }
+ if (Error_Code == UTM_NO_ERROR) {
+ if (temp_zone >= 31)
+ Central_Meridian = (6 * temp_zone - 183) * PI / 180.0;
+ else
+ Central_Meridian = (6 * temp_zone + 177) * PI / 180.0;
+ Zone = (int) temp_zone;
+ if (Latitude < 0) {
+ False_Northing = 10000000;
+ hemisphere = Hemisphere.S;
+ }
+ else
+ hemisphere = Hemisphere.N;
+
+ try {
+ TMCoord TM = TMCoord.fromLatLon(Math.toDegrees(Latitude), Math.toDegrees(Longitude),
+ this.UTM_a, this.UTM_f, Math.toDegrees(Origin_Latitude),
+ Math.toDegrees(Central_Meridian), False_Easting, False_Northing, Scale);
+ Easting = TM.getEasting();
+ Northing = TM.getNorthing();
+
+ if ((Easting < MIN_EASTING) || (Easting > MAX_EASTING))
+ Error_Code = UTM_EASTING_ERROR;
+ if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING))
+ Error_Code |= UTM_NORTHING_ERROR;
+ } catch (Exception e) {
+ Error_Code = UTM_TM_ERROR;
+ }
+ }
+ }
+ return (Error_Code);
+ }
+
+ /** @return Easting (X) in meters */
+ public double getEasting() {
+ return Easting;
+ }
+
+ /** @return Northing (Y) in meters */
+ public double getNorthing() {
+ return Northing;
+ }
+
+ /**
+ * @return The coordinate hemisphere, either {@link Hemisphere#N} of {@link Hemisphere#S}.
+ */
+ public Hemisphere getHemisphere() {
+ return hemisphere;
+ }
+
+ /** @return UTM zone */
+ public int getZone() {
+ return Zone;
+ }
+
+ /**
+ * The function Convert_UTM_To_Geodetic converts UTM projection (zone, hemisphere, easting and northing) coordinates
+ * to geodetic(latitude and longitude) coordinates, according to the current ellipsoid parameters. If any errors
+ * occur, the error code(s) are returned by the function, otherwise UTM_NO_ERROR is returned.
+ *
+ * @param zone UTM zone.
+ * @param hemisphere The coordinate hemisphere, either {@link Hemisphere#N} of {@link Hemisphere#S}.
+ * @param easting easting (X) in meters.
+ * @param Northing Northing (Y) in meters.
+ *
+ * @return error code.
+ */
+ public long convertUTMToGeodetic(long zone, Hemisphere hemisphere, double easting, double Northing) {
+ // TODO: arg checking
+ long Error_Code = UTM_NO_ERROR;
+ double Origin_Latitude = 0;
+ double False_Easting = 500000;
+ double False_Northing = 0;
+ double Scale = 0.9996;
+
+ if ((zone < 1) || (zone > 60))
+ Error_Code |= UTM_ZONE_ERROR;
+ if (!hemisphere.equals(Hemisphere.S) && !hemisphere.equals(Hemisphere.N))
+ Error_Code |= UTM_HEMISPHERE_ERROR;
+// if ((easting < MIN_EASTING) || (easting > MAX_EASTING)) //removed check to enable reprojecting images
+// Error_Code |= UTM_EASTING_ERROR; //that extend into another zone
+ if ((Northing < MIN_NORTHING) || (Northing > MAX_NORTHING))
+ Error_Code |= UTM_NORTHING_ERROR;
+
+ if (Error_Code == UTM_NO_ERROR) { /* no errors */
+ if (zone >= 31)
+ Central_Meridian = ((6 * zone - 183) * PI / 180.0 /*+ 0.00000005*/);
+ else
+ Central_Meridian = ((6 * zone + 177) * PI / 180.0 /*+ 0.00000005*/);
+ if (hemisphere.equals(Hemisphere.S))
+ False_Northing = 10000000;
+ try {
+ TMCoord TM = TMCoord.fromTM(easting, Northing,
+ Math.toDegrees(Origin_Latitude), Math.toDegrees(Central_Meridian),
+ False_Easting, False_Northing, Scale);
+ Latitude = Math.toRadians(TM.getLatitude());
+ Longitude = Math.toRadians(TM.getLongitude());
+
+ if ((Latitude < MIN_LAT) || (Latitude > MAX_LAT)) { /* Latitude out of range */
+ Error_Code |= UTM_NORTHING_ERROR;
+ }
+ } catch (Exception e) {
+ Error_Code = UTM_TM_ERROR;
+ }
+ }
+ return (Error_Code);
+ }
+
+ /** @return Latitude in radians. */
+ public double getLatitude() {
+ return Latitude;
+ }
+
+ /** @return Longitude in radians. */
+ public double getLongitude() {
+ return Longitude;
+ }
+
+}
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/globe/Globe.java b/worldwind/src/main/java/gov/nasa/worldwind/globe/Globe.java
index 958409124..0bd48b459 100644
--- a/worldwind/src/main/java/gov/nasa/worldwind/globe/Globe.java
+++ b/worldwind/src/main/java/gov/nasa/worldwind/globe/Globe.java
@@ -32,6 +32,10 @@ public class Globe {
*/
protected GeographicProjection projection;
+ private final float[] scratchHeights = new float[1];
+
+ private final Sector scratchSector = new Sector();
+
/**
* Constructs a globe with a specified reference ellipsoid and projection.
*
@@ -320,4 +324,19 @@ public boolean intersect(Line line, Vec3 result) {
return this.projection.intersect(this, line, result);
}
+
+ /**
+ * Determine terrain altitude in specified geographic point from elevation model
+ *
+ * @param latitude location latitude
+ * @param longitude location longitude
+ *
+ * @return Elevation in meters in specified location
+ */
+ public double getElevationAtLocation(double latitude, double longitude) {
+ // Use 1E-15 below because sector can not have zero deltas
+ this.scratchSector.set(latitude, longitude, 1E-15, 1E-15);
+ this.getElevationModel().getHeightGrid(this.scratchSector, 1, 1, this.scratchHeights);
+ return this.scratchHeights[0];
+ }
}
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/layer/graticule/AbstractGraticuleLayer.java b/worldwind/src/main/java/gov/nasa/worldwind/layer/graticule/AbstractGraticuleLayer.java
new file mode 100644
index 000000000..637cafc63
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/layer/graticule/AbstractGraticuleLayer.java
@@ -0,0 +1,610 @@
+/*
+ * Copyright (C) 2012 United States Government as represented by the Administrator of the
+ * National Aeronautics and Space Administration.
+ * All Rights Reserved.
+ */
+package gov.nasa.worldwind.layer.graticule;
+
+import android.graphics.Typeface;
+
+import java.util.List;
+
+import gov.nasa.worldwind.WorldWind;
+import gov.nasa.worldwind.geom.Line;
+import gov.nasa.worldwind.geom.Location;
+import gov.nasa.worldwind.geom.Position;
+import gov.nasa.worldwind.geom.Sector;
+import gov.nasa.worldwind.geom.Vec3;
+import gov.nasa.worldwind.layer.AbstractLayer;
+import gov.nasa.worldwind.render.Color;
+import gov.nasa.worldwind.render.RenderContext;
+import gov.nasa.worldwind.render.Renderable;
+import gov.nasa.worldwind.shape.Label;
+import gov.nasa.worldwind.shape.Path;
+import gov.nasa.worldwind.util.WWMath;
+
+/**
+ * Displays a graticule.
+ *
+ * @author Patrick Murris
+ * @version $Id: AbstractGraticuleLayer.java 2153 2014-07-17 17:33:13Z tgaskins $
+ */
+public abstract class AbstractGraticuleLayer extends AbstractLayer {
+
+// /**
+// * Solid line rendering style. This style specifies that a line will be drawn without any breaks. _________
+// * - - - - -
+// * . . . . .
+// * is an example of a dotted line.
+// */
+// public static final String LINE_STYLE_DOTTED = GraticuleRenderingParams.VALUE_LINE_STYLE_DOTTED;
+
+ private static final String LOOK_AT_LATITUDE_PROPERTY = "look_at_latitude";
+ private static final String LOOK_AT_LONGITUDE_PROPERTY = "look_at_longitude";
+ private static final String GRATICULE_PIXEL_SIZE_PROPERTY = "graticule_pixel_size";
+ private static final String GRATICULE_LABEL_OFFSET_PROPERTY = "graticule_label_offset";
+
+ // Helper variables to avoid memory leaks
+ private final Vec3 surfacePoint = new Vec3();
+ private final Line forwardRay = new Line();
+ private final Vec3 lookAtPoint = new Vec3();
+ private final Position lookAtPos = new Position();
+
+ private final GraticuleSupport graticuleSupport = new GraticuleSupport();
+
+ // Update reference states
+ private final Vec3 lastCameraPoint = new Vec3();
+ private double lastCameraHeading;
+ private double lastCameraTilt;
+ private double lastFOV;
+ private double lastVerticalExaggeration;
+// private Globe lastGlobe;
+// private GeographicProjection lastProjection;
+// private long frameTimeStamp; // used only for 2D continuous globes to determine whether render is in same frame
+// private double terrainConformance = 50;
+
+ AbstractGraticuleLayer(String name) {
+ this.setDisplayName(name);
+ this.setPickEnabled(false);
+ this.initRenderingParams();
+ }
+
+ protected abstract void initRenderingParams();
+
+ /**
+ * Returns whether or not graticule lines will be rendered.
+ *
+ * @param key the rendering parameters key.
+ *
+ * @return true if graticule lines will be rendered; false otherwise.
+ */
+ public boolean isDrawGraticule(String key) {
+ return this.getRenderingParams(key).isDrawLines();
+ }
+
+ /**
+ * Sets whether or not graticule lines will be rendered.
+ *
+ * @param drawGraticule true to render graticule lines; false to disable rendering.
+ * @param key the rendering parameters key.
+ */
+ public void setDrawGraticule(boolean drawGraticule, String key) {
+ this.getRenderingParams(key).setDrawLines(drawGraticule);
+ }
+
+ /**
+ * Returns the graticule line Color.
+ *
+ * @param key the rendering parameters key.
+ *
+ * @return Color used to render graticule lines.
+ */
+ public Color getGraticuleLineColor(String key) {
+ return this.getRenderingParams(key).getLineColor();
+ }
+
+ /**
+ * Sets the graticule line Color.
+ *
+ * @param color Color that will be used to render graticule lines.
+ * @param key the rendering parameters key.
+ */
+ public void setGraticuleLineColor(Color color, String key) {
+ this.getRenderingParams(key).setLineColor(color);
+ }
+
+ /**
+ * Returns the graticule line width.
+ *
+ * @param key the rendering parameters key.
+ *
+ * @return width of the graticule lines.
+ */
+ public double getGraticuleLineWidth(String key) {
+ return this.getRenderingParams(key).getLineWidth();
+ }
+
+ /**
+ * Sets the graticule line width.
+ *
+ * @param lineWidth width of the graticule lines.
+ * @param key the rendering parameters key.
+ */
+ public void setGraticuleLineWidth(double lineWidth, String key) {
+ this.getRenderingParams(key).setLineWidth(lineWidth);
+ }
+
+// /**
+// * Returns the graticule line rendering style.
+// *
+// * @param key the rendering parameters key.
+// *
+// * @return rendering style of the graticule lines.
+// */
+// public String getGraticuleLineStyle(String key) {
+// return this.getRenderingParams(key).getLineStyle();
+// }
+//
+// /**
+// * Sets the graticule line rendering style.
+// *
+// * @param lineStyle rendering style of the graticule lines. One of LINE_STYLE_SOLID, LINE_STYLE_DASHED, or
+// * LINE_STYLE_DOTTED.
+// * @param key the rendering parameters key.
+// */
+// public void setGraticuleLineStyle(String lineStyle, String key) {
+// this.getRenderingParams(key).setLineStyle(lineStyle);
+// }
+
+ /**
+ * Returns whether or not graticule labels will be rendered.
+ *
+ * @param key the rendering parameters key.
+ *
+ * @return true if graticule labels will be rendered; false otherwise.
+ */
+ public boolean isDrawLabels(String key) {
+ return this.getRenderingParams(key).isDrawLabels();
+ }
+
+ /**
+ * Sets whether or not graticule labels will be rendered.
+ *
+ * @param drawLabels true to render graticule labels; false to disable rendering.
+ * @param key the rendering parameters key.
+ */
+ public void setDrawLabels(boolean drawLabels, String key) {
+ this.getRenderingParams(key).setDrawLabels(drawLabels);
+ }
+
+ /**
+ * Returns the graticule label Color.
+ *
+ * @param key the rendering parameters key.
+ *
+ * @return Color used to render graticule labels.
+ */
+ public Color getLabelColor(String key) {
+ return this.getRenderingParams(key).getLabelColor();
+ }
+
+ /**
+ * Sets the graticule label Color.
+ *
+ * @param color Color that will be used to render graticule labels.
+ * @param key the rendering parameters key.
+ */
+ public void setLabelColor(Color color, String key) {
+ this.getRenderingParams(key).setLabelColor(color);
+ }
+
+ /**
+ * Returns the Typeface used for graticule labels.
+ *
+ * @param key the rendering parameters key.
+ *
+ * @return Typeface used to render graticule labels.
+ */
+ public Typeface getLabelTypeface(String key) {
+ return this.getRenderingParams(key).getLabelTypeface();
+ }
+
+ /**
+ * Sets the Typeface used for graticule labels.
+ *
+ * @param typeface Typeface that will be used to render graticule labels.
+ * @param key the rendering parameters key.
+ */
+ public void setLabelTypeface(Typeface typeface, String key) {
+ this.getRenderingParams(key).setLabelTypeface(typeface);
+ }
+
+ /**
+ * Returns the Size used for graticule labels.
+ *
+ * @param key the rendering parameters key.
+ *
+ * @return Size used to render graticule labels.
+ */
+ public Float getLabelSize(String key) {
+ return this.getRenderingParams(key).getLabelSize();
+ }
+
+ /**
+ * Sets the Size used for graticule labels.
+ *
+ * @param size Size that will be used to render graticule labels.
+ * @param key the rendering parameters key.
+ */
+ public void setLabelSize(Float size, String key) {
+ this.getRenderingParams(key).setLabelSize(size);
+ }
+
+ GraticuleRenderingParams getRenderingParams(String key) {
+ return this.graticuleSupport.getRenderingParams(key);
+ }
+
+ void setRenderingParams(String key, GraticuleRenderingParams renderingParams) {
+ this.graticuleSupport.setRenderingParams(key, renderingParams);
+ }
+
+ void addRenderable(Renderable renderable, String paramsKey) {
+ this.graticuleSupport.addRenderable(renderable, paramsKey);
+ }
+
+ private void removeAllRenderables() {
+ this.graticuleSupport.removeAllRenderables();
+ }
+
+ @Override
+ public void doRender(RenderContext rc) {
+// if (rc.isContinuous2DGlobe()) {
+// if (this.needsToUpdate(rc)) {
+// this.clear(rc);
+// this.selectRenderables(rc);
+// }
+//
+// // If the frame time stamp is the same, then this is the second or third pass of the same frame. We continue
+// // selecting renderables in these passes.
+// if (rc.getFrameTimeStamp() == this.frameTimeStamp)
+// this.selectRenderables(rc);
+//
+// this.frameTimeStamp = rc.getFrameTimeStamp();
+// } else {
+ if (this.needsToUpdate(rc)) {
+ this.clear(rc);
+ this.selectRenderables(rc);
+ }
+// }
+
+ // Render
+ this.graticuleSupport.render(rc, this.getOpacity());
+ }
+
+ /**
+ * Select the visible grid elements
+ *
+ * @param rc the current RenderContext
.
+ */
+ protected abstract void selectRenderables(RenderContext rc);
+
+ protected abstract ListAngle
+ *
+ * @return the intersection Position
or null if there was no intersection found.
+ */
+ private Location greatCircleIntersectionAtLongitude(Location p1, Location p2, double longitude) {
+ if (p1.longitude == longitude)
+ return p1;
+ if (p2.longitude == longitude)
+ return p2;
+ Location pos = null;
+ double deltaLon = this.getDeltaLongitude(p1, p2.longitude);
+ if (this.getDeltaLongitude(p1, longitude) < deltaLon && this.getDeltaLongitude(p2, longitude) < deltaLon) {
+ int count = 0;
+ double precision = 1d / 6378137d; // 1m angle in radians
+ Location a = p1;
+ Location b = p2;
+ Location midPoint = this.greatCircleMidPoint(a, b);
+ while (Math.toRadians(this.getDeltaLongitude(midPoint, longitude)) > precision && count <= 20) {
+ count++;
+ if (this.getDeltaLongitude(a, longitude) < this.getDeltaLongitude(b, longitude))
+ b = midPoint;
+ else
+ a = midPoint;
+ midPoint = this.greatCircleMidPoint(a, b);
+ }
+ pos = midPoint;
+ }
+ // Adjust final longitude for an exact match
+ if (pos != null)
+ pos = new Location(pos.latitude, longitude);
+ return pos;
+ }
+
+ /**
+ * Computes the intersection point position between a great circle segment and a parallel.
+ *
+ * @param p1 the great circle segment start position.
+ * @param p2 the great circle segment end position.
+ * @param latitude the parallel latitude Angle
+ *
+ * @return the intersection Position
or null if there was no intersection found.
+ */
+ private Location greatCircleIntersectionAtLatitude(Location p1, Location p2, double latitude) {
+ Location pos = null;
+ if (Math.signum(p1.latitude - latitude) != Math.signum(p2.latitude - latitude)) {
+ int count = 0;
+ double precision = 1d / 6378137d; // 1m angle in radians
+ Location a = p1;
+ Location b = p2;
+ Location midPoint = this.greatCircleMidPoint(a, b);
+ while (Math.abs(Math.toRadians(midPoint.latitude) - Math.toRadians(latitude)) > precision && count <= 20) {
+ count++;
+ if (Math.signum(a.latitude - latitude)
+ != Math.signum(midPoint.latitude - latitude))
+ b = midPoint;
+ else
+ a = midPoint;
+ midPoint = this.greatCircleMidPoint(a, b);
+ }
+ pos = midPoint;
+ }
+ // Adjust final latitude for an exact match
+ if (pos != null)
+ pos = new Location(latitude, pos.longitude);
+ return pos;
+ }
+
+ private Location greatCircleMidPoint(Location p1, Location p2) {
+ double azimuth = p1.greatCircleAzimuth(p2);
+ double distance = p1.greatCircleDistance(p2);
+ return p1.greatCircleLocation(azimuth, distance / 2, new Location());
+ }
+
+ private double getDeltaLongitude(Location p1, double longitude) {
+ double deltaLon = Math.abs(p1.longitude - longitude);
+ return deltaLon < 180 ? deltaLon : 360 - deltaLon;
+ }
+
+ private void calculateLookAtProperties(RenderContext rc) {
+ if (!rc.hasUserProperty(LOOK_AT_LATITUDE_PROPERTY) || !rc.hasUserProperty(LOOK_AT_LONGITUDE_PROPERTY)) {
+ //rc.modelview.extractEyePoint(forwardRay.origin);
+ forwardRay.origin.set(rc.cameraPoint);
+ rc.modelview.extractForwardVector(forwardRay.direction);
+
+ double range;
+ if (rc.globe.intersect(forwardRay, lookAtPoint)) {
+ rc.globe.cartesianToGeographic(lookAtPoint.x, lookAtPoint.y, lookAtPoint.z, lookAtPos);
+ rc.putUserProperty(LOOK_AT_LATITUDE_PROPERTY, lookAtPos.latitude);
+ rc.putUserProperty(LOOK_AT_LONGITUDE_PROPERTY, lookAtPos.longitude);
+ range = lookAtPoint.distanceTo(rc.cameraPoint);
+ } else {
+ rc.putUserProperty(LOOK_AT_LATITUDE_PROPERTY, null);
+ rc.putUserProperty(LOOK_AT_LONGITUDE_PROPERTY, null);
+ range = rc.horizonDistance;
+ }
+
+ double pixelSizeMeters = rc.pixelSizeAtDistance(range);
+ rc.putUserProperty(GRATICULE_PIXEL_SIZE_PROPERTY, pixelSizeMeters);
+
+ double pixelSizeDegrees = Math.toDegrees(pixelSizeMeters / rc.globe.getEquatorialRadius());
+ rc.putUserProperty(GRATICULE_LABEL_OFFSET_PROPERTY, pixelSizeDegrees * rc.viewport.width / 4);
+ }
+ }
+
+}
diff --git a/worldwind/src/main/java/gov/nasa/worldwind/layer/graticule/AbstractGraticuleTile.java b/worldwind/src/main/java/gov/nasa/worldwind/layer/graticule/AbstractGraticuleTile.java
new file mode 100644
index 000000000..774737d8b
--- /dev/null
+++ b/worldwind/src/main/java/gov/nasa/worldwind/layer/graticule/AbstractGraticuleTile.java
@@ -0,0 +1,119 @@
+package gov.nasa.worldwind.layer.graticule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import gov.nasa.worldwind.geom.BoundingBox;
+import gov.nasa.worldwind.geom.Sector;
+import gov.nasa.worldwind.geom.Vec3;
+import gov.nasa.worldwind.render.RenderContext;
+
+abstract class AbstractGraticuleTile {
+
+ private final AbstractGraticuleLayer layer;
+ private final Sector sector;
+
+ private ListRenderContext
.
+ */
+ void selectRenderables(RenderContext rc) {
+ ListMGRSGraticuleLayer
, with default graticule attributes. */
+ public MGRSGraticuleLayer() {
+ super("MGRS graticule", (int) 100e3, 1e5);
+ }
+
+ /**
+ * Returns the maxiumum resolution graticule that will be rendered, or null if no graticules will be rendered. By
+ * default, all graticules are rendered, and this will return GRATICULE_1M.
+ *
+ * @return maximum resolution rendered.
+ */
+ public String getMaximumGraticuleResolution() {
+ String maxTypeDrawn = null;
+ for (String type : getOrderedTypes()) {
+ GraticuleRenderingParams params = getRenderingParams(type);
+ if (params.isDrawLines()) {
+ maxTypeDrawn = type;
+ }
+ }
+ return maxTypeDrawn;
+ }
+
+ /**
+ * Sets the maxiumum resolution graticule that will be rendered.
+ *
+ * @param graticuleType one of GRATICULE_MGRS_OVERVIEW, GRATICULE_MGRS_GRID_ZONE, GRATICULE_100000M, GRATICULE_10000M,
+ * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M.
+ */
+ public void setMaximumGraticuleResolution(String graticuleType) {
+ boolean pastTarget = false;
+ for (String type : getOrderedTypes()) {
+ // Enable all graticulte BEFORE and INCLUDING the target.
+ // Disable all graticules AFTER the target.
+ GraticuleRenderingParams params = getRenderingParams(type);
+ params.setDrawLines(!pastTarget);
+ params.setDrawLabels(!pastTarget);
+ if (!pastTarget && type.equals(graticuleType)) {
+ pastTarget = true;
+ }
+ }
+ }
+
+ @Override
+ protected void initRenderingParams() {
+ super.initRenderingParams();
+
+ GraticuleRenderingParams params;
+ // MGRS Overview graticule
+ params = new GraticuleRenderingParams();
+ params.put(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(.8f, .8f, .8f, .5f));
+ params.put(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(1f, 1f, 1f, .8f));
+ params.put(GraticuleRenderingParams.KEY_LABEL_TYPEFACE, Typeface.create("arial", Typeface.BOLD));
+ params.put(GraticuleRenderingParams.KEY_LABEL_SIZE, 14f * Resources.getSystem().getDisplayMetrics().scaledDensity);
+ params.put(GraticuleRenderingParams.KEY_DRAW_LABELS, Boolean.TRUE);
+ setRenderingParams(GRATICULE_MGRS_OVERVIEW, params);
+ // MGRS GridZone graticule
+ params = new GraticuleRenderingParams();
+ params.put(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(android.graphics.Color.YELLOW));
+ params.put(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(android.graphics.Color.YELLOW));
+ params.put(GraticuleRenderingParams.KEY_LABEL_TYPEFACE, Typeface.create("arial", Typeface.BOLD));
+ params.put(GraticuleRenderingParams.KEY_LABEL_SIZE, 16f * Resources.getSystem().getDisplayMetrics().scaledDensity);
+ setRenderingParams(GRATICULE_MGRS_GRID_ZONE, params);
+ }
+
+ @Override
+ protected List