/* GNU General Public License CacheWolf is a software for PocketPC, Win and Linux that enables paperless caching. It supports the sites geocaching.com and opencaching.de Copyright (C) 2006 CacheWolf development team See http://www.cachewolf.de/ for more information. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package CacheWolf.database; import CacheWolf.navi.GeodeticCalculator; import CacheWolf.navi.ParseLatLon; import CacheWolf.navi.ProjectedPoint; import CacheWolf.navi.TransformCoordinates; import CacheWolf.utils.Common; import CacheWolf.utils.MyLocale; import CacheWolf.utils.STRreplace; import com.stevesoft.ewe_pat.Regex; /** * Class for getting an setting coords in different formats * and for doing projection and calculation of bearing and * distance * */ public class CWPoint extends CoordinatePoint { /** Degrees/Radians conversion constant. */ static private final double PiOver180 = Math.PI / 180.0; /** * Create CWPoint by using lat and lon * * @param lat * Latitude as decimal * @param lon * Longitude as decimal */ public CWPoint(double lat, double lon) { super(lat, lon); } /** * Creates an inValid CWPoint, use set methods for filling */ public CWPoint() { super(); } /** * Create CWPoint by using a CoordinatePoint * * @param coordinatePoint */ public CWPoint(CoordinatePoint coordinatePoint) { super(coordinatePoint.latDec, coordinatePoint.lonDec); } /** * Create CWPoint by using coordinates in "CacheWolf" format * * @param coord * String of type N 49� 33.167 E 011� 21.608 * @param format * only CWPoint.CW or CWPoint.REGEX is supported */ public CWPoint(String coord, int format) { super(-361, -361); set(coord, format); } /** * set lat and lon by parsing coordinates with Regex * * @param coord * String like N 49� 33.167 E 011� 21.608 */ public CWPoint(String coord) { set(coord); } /** * Set lat and lon * * @param lat * Latitude as decimal * @param lon * Longitude as decimal */ public void set(double lat, double lon) { this.latDec = lat; this.lonDec = lon; } /** * Set CWPoint by using a CWPoint * * @param CWPoint * cwPoint */ public void set(CoordinatePoint cwPoint) { this.latDec = cwPoint.latDec; this.lonDec = cwPoint.lonDec; } /** * set lat and lon by using coordinates in "CacheWolf" format * * @param coord * String of type N 49� 33.167 E 011� 21.608 * @param format * only CWPoint.CW is supported */ public void set(String coord, int format) { if (coord != null) { switch (format) { case TransformCoordinates.DMM: ParseLatLon pll = new ParseLatLon(coord); try { pll.parse(); this.latDec = pll.lat2; this.lonDec = pll.lon2; } catch (Exception e) { this.latDec = 91; this.lonDec = 361; break; } case TransformCoordinates.REGEX: set(coord); break; default: this.latDec = 91; this.lonDec = 361; } } else { this.latDec = 91; this.lonDec = 361; } } /** * set lat and lon by parsing coordinates with regular expression * * @param coord * String of type N 49� 33.167 E 011� 21.608 * or -12.3456 23.4567 * or 32U 2345234 8902345 */ public void set(String coord) { //replace non-breaking-spaces by normal spaces coord = coord.replace((char) 0xA0, ' '); /* (?: ([NSns])\s*([0-9]{1,2})[\s�]+([0-9]{1,2})(?:\s+([0-9]{1,2}))?[,.]([0-9]{1,8})\s* ([EWewOo])\s*([0-9]{1,3})[\s�]+([0-9]{1,2})(?:\s+([0-9]{1,2}))?[,.]([0-9]{1,8}) )|(?: ([+-NnSs]?[0-9]{1,2})[,.]([0-9]{1,8})(?:(?=\+)|(?=-)|\s+|\s*�\s*)([+-WwEeOo]?[0-9]{1,3})[,.]([0-9]{1,8})\s*[�]? )|(?: ([0-9]{1,2}[C-HJ-PQ-X])\s*[EeOo]?\s*([0-9]{1,7})\s+[Nn]?\s*([0-9]{1,7}) ) */ String crsid = null; if ((coord.length() >= 2) && (coord.charAt(2) == '.') && (coord.indexOf(' ') >= 0)) { // first 2 letters = Internet domain of projected area (ex: "dk utm <koords>" wird zu "utm <koords>", Implemented since se and dk EPSGs) // removed from List, since there is already utm in the List. Why this additional prefix?) crsid = coord.substring(0, coord.indexOf(' ')); if (TransformCoordinates.getLocalSystemCode(crsid) != -1) { coord = coord.substring(coord.indexOf(' ') + 1, coord.length()); } } Regex rex = new Regex("(?:" + "([NSns])\\s*([0-9]{1,2})(?:[�\uC2B0]\\s*|\\s+[�\uC2B0]?\\s*)([0-9]{1,2})(?:(?:['�]\\s*|\\s+['�]?\\s*)([0-9]{1,2}))?(?:[,.]([0-9]{1,8}))?\\s*['�\"]?\\s*" + "[,./_;+:-]*\\s*" + // allow N xx xx.xxx / E xxx xx.xxx "([EWewOo])\\s*([0-9]{1,3})(?:[�\uC2B0]\\s*|\\s+[�\uC2B0]?\\s*)([0-9]{1,2})(?:(?:['�]\\s*|\\s+['�]?\\s*)([0-9]{1,2}))?(?:[,.]([0-9]{1,8}))?\\s*['�\"]?" + ")|(?:" + "(?:([NnSs])\\s*(?![+-]))?" + "([+-]?[0-9]{1,2})[,.]([0-9]{1,8})(?:(?=[+-EeWwOo])|\\s+|\\s*[�\uC2B0]\\s*)" + "[,./_;:]*\\s*" + "(?:([EeWwOo])\\s*(?![+-]))?" + "([+-]?[0-9]{1,3})[,.]([0-9]{1,8})\\s*[�\uC2B0]?" + ")|(?:" + "([0-9]{1,2}[C-HJ-PQ-X])\\s*[EeOo]?\\s*([0-9]{1,7})\\s+[Nn]?\\s*([0-9]{1,7})" + ")|(?:" + "[Rr]:?\\s*([+-]?[0-9]{1,7})\\s+[Hh]:?\\s*([+-]?[0-9]{1,7})" + ")|(?:" + "([\\-]{0,1}[0-9]{1,8})\\s+([\\-]{0,1}[0-9]{1,8})" + // projected easting northing ")"); this.makeInvalid(); //return unset / unvalid values if parsing was not successfull rex.search(coord); if (rex.stringMatched(1) != null) { // Std format // Handle "E" or "O" for longitiude String strEW = rex.stringMatched(6).toUpperCase(); if (!strEW.equals("W")) strEW = "E"; if (rex.stringMatched(4) != null) { //Seconds available set(rex.stringMatched(1).toUpperCase(), rex.stringMatched(2), rex.stringMatched(3), rex.stringMatched(4) + "." + rex.stringMatched(5), strEW, rex.stringMatched(7), rex.stringMatched(8), rex.stringMatched(9) + "." + rex.stringMatched(10), TransformCoordinates.DMS); } else { set(rex.stringMatched(1).toUpperCase(), rex.stringMatched(2), rex.stringMatched(3) + "." + rex.stringMatched(5), null, strEW, rex.stringMatched(7), rex.stringMatched(8) + "." + rex.stringMatched(10), null, TransformCoordinates.DMM); } } else if (rex.stringMatched(12) != null) { // Decimal set(rex.stringMatched(11) == null ? "N" : rex.stringMatched(11).toUpperCase(), rex.stringMatched(12) + "." + rex.stringMatched(13), null, null, rex.stringMatched(14) == null ? "E" : rex.stringMatched(14).toUpperCase(), rex.stringMatched(15) + "." + rex.stringMatched(16), null, null, TransformCoordinates.DD); } else if (rex.stringMatched(17) != null) { // UTM set(rex.stringMatched(19), rex.stringMatched(18), rex.stringMatched(17)); //parse sequence is E N, but set needs N E } else if (rex.stringMatched(20) != null) { // GK set(rex.stringMatched(21), rex.stringMatched(20), TransformCoordinates.LOCALSYSTEM_DEFAULT); } else if (rex.stringMatched(22) != null) { // general projected coordinate reference system if (crsid != null) { int ls = TransformCoordinates.getLocalSystemCode(crsid); if (ls == -1) makeInvalid(); else set(rex.stringMatched(23), rex.stringMatched(22), ls); } } } /** * set lat and lon * * @param strLatNS * "N" or "S" * @param strLatDeg * Degrees of Latitude * @param strLatMin * Minutes of Latitude * @param strLatSec * Seconds of Latitude * @param strLonEW * "E" or "W" * @param strLonDeg * Degrees of Longitude * @param strLonMin * Minutes of Longitude * @param strLonSec * Seconds of Longitude * @param format * Format: DD, DMM, DMS */ public void set(String strLatNS, String strLatDeg, String strLatMin, String strLatSec, String strLonEW, String strLonDeg, String strLonMin, String strLonSec, int format) { switch (format) { case TransformCoordinates.DD: this.latDec = Common.parseDouble(strLatDeg); this.lonDec = Common.parseDouble(strLonDeg); break; case TransformCoordinates.DMM: this.latDec = Math.abs(Common.parseDouble(strLatDeg)) + Math.abs((Common.parseDouble(strLatMin) / 60)); this.lonDec = Math.abs(Common.parseDouble(strLonDeg)) + Math.abs((Common.parseDouble(strLonMin) / 60)); break; case TransformCoordinates.DMS: this.latDec = Math.abs(Common.parseDouble(strLatDeg)) + Math.abs((Common.parseDouble(strLatMin) / 60)) + Math.abs((Common.parseDouble(strLatSec) / 3600)); this.lonDec = Math.abs(Common.parseDouble(strLonDeg)) + Math.abs((Common.parseDouble(strLonMin) / 60)) + Math.abs((Common.parseDouble(strLonSec) / 3600)); break; default: this.latDec = 91; this.lonDec = 361; } //makeValid(); // To avoid changing sign twice if we have something like W -34.2345 if (strLatNS.trim().equals("S") && this.latDec > 0) this.latDec *= -1; if (strLonEW.trim().equals("W") && this.lonDec > 0) this.lonDec *= -1; } /** * sets by UTM with respect to WGS84 * * @param UTMNorthing * @param UTMEasting * @param utmzone */ public void set(String UTMNorthing, String UTMEasting, String utmzone) { try { ProjectedPoint utm = new ProjectedPoint(new CWPoint(Common.parseDouble(UTMNorthing), Common.parseDouble(UTMEasting)), utmzone, TransformCoordinates.LOCALSYSTEM_UTM_WGS84, true); set(TransformCoordinates.ProjectedToWgs84(utm, TransformCoordinates.LOCALSYSTEM_UTM_WGS84, true)); } catch (Exception e) { makeInvalid(); } } /** * shift the point * * @param meters * positiv to north (east), negativ to south (west) * @param direction * 0 north(meters negative=south), 1 east(meters negative=west) 2 south(meters negative=north) 3 west(meters negative=east) */ public void shift(double meters, int direction) { double meters2deglon = 1 / (1000 * (new CWPoint(0, 0)).getDistance(new CWPoint(1, 0))); switch (direction) { // TODO use ellipsoid distance calculations for better accuracy case 0: latDec += meters * meters2deglon; return; case 1: lonDec += meters * (meters2deglon / Math.cos(latDec / 180 * Math.PI)); return; case 2: latDec += -meters * meters2deglon; return; case 3: lonDec += -meters * (meters2deglon / Math.cos(latDec / 180 * Math.PI)); return; } } /** * mark the Point as invalid * */ public void makeInvalid() { latDec = -361; lonDec = 91; } /** * set lat and lon by using a local coordinates system * * @param strEasting * Easting component * @param strNorthing * Northing component * @param localCooSystem * one of TransformCoordinates.LOCALSYSTEM_XXX */ public void set(String strNorthing, String strEasting, int localCooSystem) { try { CWPoint pp = new CWPoint(Common.parseDouble(strNorthing), Common.parseDouble(strEasting)); ProjectedPoint gk = new ProjectedPoint(pp, localCooSystem, true, true); set(TransformCoordinates.ProjectedToWgs84(gk, localCooSystem, true)); } catch (Exception e) { makeInvalid(); } } /** * set lat and lon by using UTM coordinates * * @param strEasting * Easting component * @param strNorthing * Northing component * @param localCooSystem * one of TransformCoordinates.LOCALSYSTEM_XXX which requires an explicit zone */ public void set(String strNorthing, String strEasting, String zone, int localCooSystem) { try { CWPoint pp = new CWPoint(Common.parseDouble(strNorthing), Common.parseDouble(strEasting)); ProjectedPoint gk = new ProjectedPoint(pp, zone, localCooSystem, true); set(TransformCoordinates.ProjectedToWgs84(gk, localCooSystem, true)); } catch (Exception e) { makeInvalid(); } } /** * Get degrees of latitude in different formats * * @param format * Format: DD, DMM, DMS, */ public String getLatDeg(int format) { switch (format) { case TransformCoordinates.DD: return MyLocale.formatDouble(this.latDec, "00.00000").replace(',', '.'); case TransformCoordinates.DMM: case TransformCoordinates.DMS: return getDMS(latDec, 0, format); default: return ""; } } /** * Get degrees of longitude in different formats * * @param format * Format: DD, DMM, DMS, */ public String getLonDeg(int format) { switch (format) { case TransformCoordinates.DD: return MyLocale.formatDouble(this.lonDec, "000.00000").replace(',', '.'); case TransformCoordinates.DMM: case TransformCoordinates.DMS: return (((lonDec < 100.0) && (lonDec > -100.0)) ? "0" : "") + getDMS(lonDec, 0, format); default: return ""; } } /** * Get minutes of latitude in different formats * * @param format * Format: DD, DMM, DMS, */ public String getLatMin(int format) { return getDMS(latDec, 1, format); } /** * Get minutes of longitude in different formats * * @param format * Format: DD, DMM, DMS, */ public String getLonMin(int format) { return getDMS(lonDec, 1, format); } /** * Get seconds of latitude in different formats * * @param format * Format: DD, DMM, DMS, */ public String getLatSec(int format) { return getDMS(latDec, 2, format); } /** * Get seconds of longitude in different formats * * @param format * Format: DD, DMM, DMS, */ public String getLonSec(int format) { return getDMS(lonDec, 2, format); } /** * Returns the degrees or minutes or seconds (depending on parameter what) formatted as a string * To determine the degrees, we need to calculate the minutes (and seconds) just in case rounding errors * propagate. Equally we need to know the seconds to determine the minutes value. * * @param deg * The coordinate in degrees * @param what * 0=deg, 1=min, 2=sec * @param format * DD,CW,DMM,DMS * @return */ private String getDMS(double deg, int what, int format) { deg = Math.abs(deg); long iDeg = (int) deg; double tmpMin, tmpSec; tmpMin = (deg - iDeg) * 60.0; switch (format) { case TransformCoordinates.DD: return ""; case TransformCoordinates.DMM: // Need to check if minutes would round up to 60 if (java.lang.Math.round(tmpMin * 1000.0) == 60000) { tmpMin = 0; iDeg++; } switch (what) { case 0: return MyLocale.formatLong(iDeg, "00"); case 1: return MyLocale.formatDouble(tmpMin, "00.000").replace(',', '.'); case 2: return ""; } case TransformCoordinates.DMS: tmpSec = (tmpMin - (int) tmpMin) * 60.0; tmpMin = (int) tmpMin; // Check if seconds round up to 60 if (java.lang.Math.round(tmpSec * 10.0) == 600) { tmpSec = 0; tmpMin = tmpMin + 1.0; } // Check if minutes round up to 60 if (java.lang.Math.round(tmpMin) == 60) { tmpMin = 0; iDeg++; } switch (what) { case 0: return MyLocale.formatLong(iDeg, "00"); case 1: return MyLocale.formatDouble(tmpMin, "00"); case 2: return MyLocale.formatDouble(tmpSec, "00.00").replace(',', '.'); } } return ""; // Dummy to keep compiler happy } /** * Get "N" or "S" letter for latitude */ public String getNSLetter() { String result = "N"; if (this.latDec >= -90 && this.latDec < 0) { result = "S"; } return result; } /** * Get "E" or "W" letter for latitude */ public String getEWLetter() { String result = "E"; if (this.lonDec >= -180 && this.lonDec < 0) { result = "W"; } return result; } /** * Method to calculate a projected waypoint * * @param degrees * Bearing * @param distance * Distance in km * @return projected waypoint */ public CWPoint project(double degrees, double distance) { return new CWPoint(GeodeticCalculator.calculateEndingGlobalCoordinates(TransformCoordinates.WGS84, this, degrees, distance * 1000.0)); } /** * Method to calculate the bearing of a waypoint * * @param dest * waypoint * @return bearing of waypoint 361 if this or dest is not valid */ public double getBearing(CWPoint dest) { if (!this.isValid() || dest == null || !dest.isValid()) return 361; return GeodeticCalculator.calculateBearing(TransformCoordinates.WGS84, this, dest); } /** * Method to identify one of 16 compass directions based * on the bearing. * * @param degrees * bearing * @return direction */ public static String getDirection(double degrees) { return getDirectionFromBearing(degrees); } /** * Method to identify one of 16 compass directions based * on the bearing of the destination waypoint * * @param dest * waypoint * @return direction */ public String getDirection(CWPoint dest) { return getDirectionFromBearing(getBearing(dest)); } /** * Method to calculate the distance to a waypoint * * @param dest * waypoint * @return distance to waypoint in KM */ public double getDistance(CWPoint dest) { return GeodeticCalculator.calculateDistance(TransformCoordinates.WGS84, this, dest) / 1000.0; } /** * Method to calculate the distance to a waypoint * * @param dest * lat, lon * @return distance to waypoint in KM */ public double getDistance(double latDecD, double lonDecD) { return getDistance(new CWPoint(latDecD, lonDecD)); } /** * Method to calculate the distance to a waypoint * * @param dest * lat, lon * @return distance to waypoint in Rad */ public double getDistanceRad(double latDecD, double lonDecD) { double phi1 = this.latDec * PiOver180; double lambda0 = this.lonDec * PiOver180; double phi = latDecD * PiOver180; double lambda = lonDecD * PiOver180; double pdiff = Math.sin(((phi - phi1) / 2.0)); double ldiff = Math.sin((lambda - lambda0) / 2.0); double rval = Math.sqrt((pdiff * pdiff) + Math.cos(phi1) * Math.cos(phi) * (ldiff * ldiff)); return 2.0 * Math.asin(rval); } public double getDistanceRad(CWPoint ll) { return getDistanceRad(ll.latDec, ll.lonDec); } /** * Returns the string reprenstation of the CWPoint * Format ist CacheWolf (N 49� 33.167 E 011� 21.608), which can be used * with parseLatLon * * @return string like N 49� 33.167 E 011� 21.608 */ public String toString() { return toString(TransformCoordinates.DMM); } /** * Returns the string representation of the CWPoint * Formats DD, DMM (same as CW), DMS, UTM * * @return string representation of CWPoint */ public String toString(int format) { if (!isValid()) return MyLocale.getMsg(999, "not set"); switch (format) { case TransformCoordinates.DD: return getNSLetter() + " " + STRreplace.replace(getLatDeg(format), "-", "") + "� " + getEWLetter() + " " + STRreplace.replace(getLonDeg(format), "-", "") + "�"; case TransformCoordinates.DMM: return getNSLetter() + " " + getLatDeg(format) + "� " + getLatMin(format) + " " + getEWLetter() + " " + getLonDeg(format) + "� " + getLonMin(format); case TransformCoordinates.DMS: return getNSLetter() + " " + getLatDeg(format) + "� " + getLatMin(format) + "\' " + getLatSec(format) + "\" " + getEWLetter() + " " + getLonDeg(format) + "� " + getLonMin(format) + "\' " + getLonSec(format) + "\""; case TransformCoordinates.LAT_LON: return getLatDeg(TransformCoordinates.DD) + "," + getLonDeg(TransformCoordinates.DD); case TransformCoordinates.LON_LAT: return getLonDeg(TransformCoordinates.DD) + "," + getLatDeg(TransformCoordinates.DD); //case TransformCoordinates.CUSTOM: return getGermanGkCoordinates(); default: return TransformCoordinates.getLocalSystem(format).id + " " + TransformCoordinates.wgs84ToLocalsystem(this, format).toHumanReadableString(); //return "Unknown Format: " + format; } } /** * Method to identify one of 16 compass directions based * on the bearing. */ private static String getDirectionFromBearing(double wert) { //System.out.println(wert); String strBear = ""; double stVal = -11.25; if (wert >= stVal) strBear = "N"; stVal += 22.5; if (wert >= stVal) strBear = "NNE"; stVal += 22.5; if (wert >= stVal) strBear = "NE"; stVal += 22.5; if (wert >= stVal) strBear = "ENE"; stVal += 22.5; if (wert >= stVal) strBear = "E"; stVal += 22.5; if (wert >= stVal) strBear = "ESE"; stVal += 22.5; if (wert >= stVal) strBear = "SE"; stVal += 22.5; if (wert >= stVal) strBear = "SSE"; stVal += 22.5; if (wert >= stVal) strBear = "S"; stVal += 22.5; if (wert >= stVal) strBear = "SSW"; stVal += 22.5; if (wert >= stVal) strBear = "SW"; stVal += 22.5; if (wert >= stVal) strBear = "WSW"; stVal += 22.5; if (wert >= stVal) strBear = "W"; stVal += 22.5; if (wert >= stVal) strBear = "WNW"; stVal += 22.5; if (wert >= stVal) strBear = "NW"; stVal += 22.5; if (wert >= stVal) strBear = "NNW"; stVal += 22.5; if (wert >= stVal) strBear = "N"; stVal += 22.5; return strBear; } //getBearing }