/* * Copyright (C) 2014 Alec Dhuse * * 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, either version 3 of the License, or * (at your option) any later version. * * 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, see <http://www.gnu.org/licenses/>. */ package co.foldingmap.map.vector; import co.foldingmap.map.MapObject; import co.foldingmap.map.NumericValueOutOfRangeException; import java.awt.geom.Point2D; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.SimpleTimeZone; import java.util.StringTokenizer; /** * The class that represents a Latitude, Longitude, Altitude, Time Coordinate * * @author Alec */ public class Coordinate { protected ArrayList<MapObject> parentObjects; protected byte pullCount; protected long id, timestamp; protected float altitude, latitude, longitude; protected Point2D.Float centerPoint, leftPoint, rightPoint; public static final Coordinate UNKNOWN_COORDINATE = new Coordinate(0, -999, -999); public static final float RAD_CONVERSION = 0.01745329251994f; public static final float DEC_CONVERSION = 57.2957795130823f; /** * Creates a coordinate from a string formated longitude,latitude,altitude * Example: 10.5,4.5,1300 * * @param coordinate */ public Coordinate(String coordinate) { try { StringTokenizer st; if (coordinate.indexOf(",") > 0) { //read comma delimited coordinate data st = new StringTokenizer(coordinate, ","); } else { //read space delimited coordinate data st = new StringTokenizer(coordinate); } this.setLongitude(Float.parseFloat(st.nextToken())); this.setLatitude(Float.parseFloat(st.nextToken())); if (st.hasMoreTokens()) altitude = Float.parseFloat(st.nextToken()); if (st.hasMoreTokens()) { setTimestamp(st.nextToken()); } else { this.timestamp = System.currentTimeMillis(); } this.pullCount = 0; this.id = 0; } catch (Exception e) { System.err.println("Error in Coordinate(String = " + coordinate + ") - " + e); } } /** * Creates a coordinate from altitude, latitude and longitude values * * @param altitude * @param latitude * @param longitude */ public Coordinate(float altitude, double latitude, double longitude) { this.setLongitude((float) longitude); this.setLatitude((float) latitude); this.setAltitude((float) altitude); this.timestamp = System.currentTimeMillis(); this.pullCount = 0; this.id = 0; } /** * Creates a coordinate from altitude, latitude and longitude values. * Sets the coordinate id to the id value provided. * * @param altitude * @param latitude * @param longitude * @param id */ public Coordinate(float altitude, double latitude, double longitude, long id) { this.setLongitude((float) longitude); this.setLatitude((float) latitude); this.setAltitude((float) altitude); this.timestamp = System.currentTimeMillis(); this.pullCount = 0; this.id = id; } /** * Creates a coordinate from altitude, latitude and longitude values * Does not perform bounds checking. * * @param altitude * @param latitude * @param longitude * @param noChecke */ public Coordinate(float altitude, double latitude, double longitude, boolean noCheck) { this.longitude = (float) longitude; this.latitude = (float) latitude; this.altitude = (float) altitude; this.timestamp = System.currentTimeMillis(); this.pullCount = 0; this.id = 0; } /** * Creates a coordinate from altitude, latitude, longitude and timestamp values * * @param altitude * @param latitude * @param longitude * @param timestamp in the format: yyyy-MM-dd'T'HH:mm:ssZ */ public Coordinate(float altitude, double latitude, double longitude, String timestamp) { this.setLongitude((float) longitude); this.setLatitude((float) latitude); this.setAltitude((float) altitude); this.pullCount = 0; if (timestamp.length() > 0) { this.setTimestamp(timestamp); } else { this.timestamp = System.currentTimeMillis(); } this.id = 0; } /** * Adds a reference for an object that uses this Coordinate. This will * provide a quick way for the program to check which object are using this * coordinate. * * @param parent */ public void addParent(MapObject parent) { if (parentObjects == null) { //Average parent use is 3 parentObjects = new ArrayList<MapObject>(3); } parentObjects.add(parent); } /** * Creates a new coordinate that is a copy of this one. * * @return The new copy Coordinate */ public Coordinate copy() { Coordinate newCopy; newCopy = new Coordinate(this.altitude, this.latitude, this.longitude, getTimestamp()); newCopy.setPullCount(pullCount); return newCopy; } /** * Test to see if this coordinate equals another coordinate. * It Compares altitude, latitude and longitude; it does not check the timestamp. * @param coordinateToCompare * @return */ @Override public boolean equals(Object o) { if (o instanceof Coordinate) { Coordinate c = (Coordinate) o; if (this.getAltitude() == c.getAltitude() && this.getLatitude() == c.getLatitude() && this.getLongitude() == c.getLongitude()) { return true; } else { return false; } } else { return false; } } @Override public int hashCode() { int hash = 5; hash = 53 * hash + Float.floatToIntBits(this.altitude); hash = 85 * hash + Float.floatToIntBits(this.latitude); hash = 32 * hash + Float.floatToIntBits(this.longitude); return hash; } /** * Returns this coordinates Altitude. * * @return */ public float getAltitude() { return altitude; } /** * Creates a Point2D with x and y equals to longitude and latitude respectively. * * @return */ public Point2D getAsPoint2D() { return new Point2D.Float(this.longitude, this.latitude); } /** * Returns the center on screen point for this Coordinate. */ public Point2D.Float getCenterPoint() { if (centerPoint == null) parentObjects.get(0).getParentLayer().getParentMap().addCoordinateNode(this); return centerPoint; } /** * Returns the left on screen point for this Coordinate. */ public Point2D.Float getLeftPoint() { return leftPoint; } /** * Returns the right on screen point for this Coordinate. */ public Point2D.Float getRightPoint() { return rightPoint; } /** * Returns the timestamp as milliseconds since the epoch. * * @return */ public long getDate() { return timestamp; } /** * Returns the ID of this coordinate, if no ID is set 0 is returned. * * @return */ public long getID() { return this.id; } /** * Returns the Coordinates Latitude. * * @return The Latitude in Decimal. */ public double getLatitude() { return latitude; } /** * Calculates the Decimal Latitude given a Radian Latitude. * * @param latitude The Latitude in Radians. * @return The Latitude in Decimal. */ public static double getLatitudeInDecimal(double latitude) { return (float) DEC_CONVERSION * latitude; } /** * Calculates the Radian Latitude given a Decimal Latitude. * * @param latitude The Latitude in Decimal. * @return The Latitude in Radians. */ public static double getLatitudeInRadians(double latitude) { return (float) RAD_CONVERSION * latitude; } /** * Returns the Latitude in Radians of this Coordinate. * * @return The Latitude in Radians. */ public double getLatitudeInRadians() { return getLatitudeInRadians(this.latitude); } /** * Returns the Coordinates Longitude. * * @return The Longitude in Decimal */ public double getLongitude() { return longitude; } /** * Calculates a Decimal Longitude given a Longitude in Radians. * * @param longitude The Longitude in Radians * @return The Longitude in Decimal */ public static double getLongitudeInDecimal(double longitude) { return (float) DEC_CONVERSION * longitude; } /** * Returns this Coordinates Longitude in Radians. * * @return The Longitude in Radians */ public double getLongitudeInRadians() { return getLongitudeInRadians(this.longitude); } /** * Calculates a Radian Longitude given a Longitude in Decimal. * * @param longitude The Longitude in Decimal * @return The Longitude in Radians */ public static double getLongitudeInRadians(double longitude) { return (float) RAD_CONVERSION * longitude; } /** * Returns a list of all parent VectorObject for this coordinate. * * @return */ public ArrayList<VectorObject> getParentVectorObjects() { ArrayList<VectorObject> parents = new ArrayList<VectorObject>(); try { if (parentObjects != null) { for (MapObject object: parentObjects) { if (object instanceof VectorObject) parents.add((VectorObject) object); } } } catch (Exception e) { System.out.println("Error in Coordinate.getParentVectorObjects() - " + e); } return parents; } /** * Returns this coordinates timestamp. * * @return The timestamp in yyyy-mm-ddThh:mm:ssZ format */ public String getTimestamp() { SimpleDateFormat timestampDateFormat; timestampDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'kk:mm:ss"); timestampDateFormat.setTimeZone(new SimpleTimeZone(0, "Z")); return timestampDateFormat.format(timestamp) + "Z"; } /** * Returns the timestamp value as a long data type. * * @return */ public long getTimestampValue() { return this.timestamp; } /** * Increments the pull count, the number of times this coordinate has been * pulled from a NodeMap. */ public void incrementPullCount() { this.pullCount++; } /** * Returns if this coordinate is east of another. * * @param c * @return */ public boolean isEastOf(Coordinate c) { boolean eastOf = false; float diff; if (longitude >= c.getLongitude()) eastOf = true; if (longitude > 0 && c.getLongitude() < 0) { diff = (float) ((180 - longitude) + Math.abs(-180 - c.getLongitude())); if (diff < 90) { eastOf = true; } } return eastOf; } /** * Returns whether or not a given Latitude is valid. * * @param latitude as a String. * @return */ public static boolean isLatitudeValid(String latitude) { boolean valid = false; try { valid = isLatitudeValid(Float.parseFloat(latitude)); } catch (Exception e) { } return valid; } /** * Returns whether or not a given Latitude is valid. * * @param latitude as a float * @return */ public static boolean isLatitudeValid(double latitude) { boolean valid = false; if ((latitude <= 90) && (latitude >= -90)) valid = true; return valid; } /** * Returns whether or not a given Longitude is valid. * * @param Longitude as a String * @return */ public static boolean isLongitudeValid(String Longitude) { boolean valid = false; try { valid = isLongitudeValid(Float.parseFloat(Longitude)); } catch (Exception e) { } return valid; } /** * Returns whether or not a given Longitude is valid. * * @param longitude as a float * @return */ public static boolean isLongitudeValid(double longitude) { boolean valid = false; if ((longitude <= 180) && (longitude >= -180)) valid = true; return valid; } /** * Returns if this coordinate is north of another. * * @param c * @return */ public boolean isNorthOf(Coordinate c) { boolean northOf = false; if (latitude >= c.getLatitude()) { northOf = true; } return northOf; } /** * Returns if this coordinate is shared between objects. * Sharing is detected by the pull count. The pull count is the number of * times an object has been pulled from the NodeMap. * * @return */ public boolean isShared() { if (pullCount > 1) { return true; } else { if (parentObjects != null) { if (parentObjects.size() > 1) { return true; } else { return false; } } else { return false; } } } /** * Returns if this coordinate is South of another. * * @param c * @return */ public boolean isSouthOf(Coordinate c) { boolean southOf = false; if (latitude <= c.getLatitude()) { southOf = true; } return southOf; } /** * Returns if this coordinate is west of another. * * @param c The coordinate to compare. * @param maxDifference The maximum longitude to detect being west of. * @return If this coordinate is west of the given coordinate with the * maximum longitude difference. */ public boolean isWestOf(Coordinate c, float maxDifference) { boolean westOf = false; float diff; if (longitude <= c.getLongitude()) westOf = true; if (longitude > 0 && c.getLongitude() < 0) { diff = (float) ((180 - longitude) + Math.abs(180 + c.getLongitude())); if (diff < maxDifference) { westOf = true; } } return westOf; } /** * Returns a new Coordinate the given Distance in KM and an Azimuth in Decimal degrees. * * @param distance Distance in KM. * @param azimuth Degree in decimal 0-360. * @return */ public final Coordinate reckonCoordinate(double distance, double azimuth) { float earthRadius = 6371.0f; //In KM double latitude1, longitude1, latitude2, longitude2; //convert input to radians azimuth = Math.toRadians(azimuth); distance = distance / earthRadius; latitude1 = getLatitudeInRadians(); longitude1 = getLongitudeInRadians(); // Taken from "Map Projections - A Working Manual", page 31, equation 5-5 and 5-6. latitude2 = (float) (Math.asin(Math.sin(latitude1) * Math.cos(distance) + Math.cos(latitude1) * Math.sin(distance) * Math.cos(azimuth))); longitude2 = (float) (longitude1 + Math.atan2( Math.sin(distance) * Math.sin(azimuth), Math.cos(latitude1) * Math.cos(distance) - Math.sin(latitude1) * Math.sin(distance) * Math.cos(azimuth))); //convert back to degrees latitude2 = (float) (DEC_CONVERSION * latitude2); longitude2 = (float) (DEC_CONVERSION * longitude2); return new Coordinate(altitude, latitude2, longitude2, false); } /** * Removes a MapObject as a Parent object of this Coordinate. * * @param parent * @return True if the object was removed, false if it was not found. */ public final boolean removeParent(MapObject parent) { if (parentObjects != null) { return parentObjects.remove(parent); } else { return false; } } /** * Sets this Coordinates Altitude * * @param newAltitude */ public final void setAltitude(float newAltitude) { altitude = newAltitude; } /** * Sets the center on screen point of this Coordinate. * * @param x * @param y */ public final void setCenterPoint(float x, float y) { if (this.centerPoint != null) { this.centerPoint.setLocation(x, y); } else { this.centerPoint = new Point2D.Float(x, y); } } /** * Sets the left on screen point of this Coordinate. * * @param x * @param y */ public final void setLeftPoint(float x, float y) { if (this.leftPoint != null) { this.leftPoint.setLocation(x, y); } else { this.leftPoint = new Point2D.Float(x, y); } } /** * Sets the right on screen point of this Coordinate. * * @param x * @param y */ public final void setRightPoint(float x, float y) { if (this.rightPoint != null) { this.rightPoint.setLocation(x, y); } else { this.rightPoint = new Point2D.Float(x, y); } } /** * Sets the Id of this coordinate. The Id is a reference that can be used * to identify a coordinate by a single long value instead of lon, lat, alt. * * @param id */ public final void setId(long id) { this.id = id; } /** * Sets this Coordinates Latitude * * @param newLatitude * @throws NumericValueOutOfRangeException */ public final void setLatitude(double newLatitude) throws NumericValueOutOfRangeException { if (((newLatitude <= 90) && (newLatitude >= -90)) || newLatitude == -999) { this.latitude = (float) newLatitude; } else { throw (new NumericValueOutOfRangeException(newLatitude)); } } /** * Sets this Coordinates Longitude * * @param newLongitude * @throws NumericValueOutOfRangeException */ public final void setLongitude(double newLongitude) throws NumericValueOutOfRangeException { //if (((newLongitude <= 180) && (newLongitude >= -180)) || newLongitude == -999) { this.longitude = (float) newLongitude; //} else { // throw (new NumericValueOutOfRangeException(newLongitude)); //} } /** * Sets the number of times this Coordinate has been pulled from a NodeMap. * * @param count */ public void setPullCount(byte count) { this.pullCount = count; } public void setShared(boolean shared) { if (shared) { if (pullCount > 1) { pullCount++; } else { pullCount = 2; } } else { pullCount = 1; } } /** * Sets this Coordinates TimeStamp * * @param newTimestamp as a String in the format: yyyy-MM-dd'T'HH:mm:ssZ */ public final void setTimestamp(String newTimestamp) { GregorianCalendar calendar; int dateEndIndex; int year, month, day; int hour, minute, second; String date, time, timeZone; try { timestamp = 0; dateEndIndex = newTimestamp.indexOf("T"); date = newTimestamp.substring(0, dateEndIndex); time = newTimestamp.substring(dateEndIndex + 1, newTimestamp.length() - 1); year = Integer.parseInt(date.substring(0,4)); month = Integer.parseInt(date.substring(5,7)); day = Integer.parseInt(date.substring(8,10)); hour = Integer.parseInt(time.substring(0,2)); minute = Integer.parseInt(time.substring(3,5)); second = Integer.parseInt(time.substring(6,8)); timeZone = newTimestamp.substring(newTimestamp.length() - 1); /* there is a problem somewhere that causes the month to increment by one, * this code combats it until the reson can be found */ if (month > 1) { month--; } else { month = 12; year--; } //end fix code calendar = new GregorianCalendar(); calendar.setTimeZone(new SimpleTimeZone(0, timeZone)); calendar.set(year, month, day, hour, minute, second); timestamp = calendar.getTimeInMillis(); } catch (Exception e) { System.out.println("Error in Coordinate.setTimestamp - " + e); } } /** * Returns this Coordinate as a String in the format longitude,latitude,altitude * @return */ @Override public String toString() { String lon, lat, alt, out; lon = Float.toString(longitude); lat = Float.toString(latitude); alt = Float.toString(altitude); if (lon.endsWith(".0")) lon = lon.substring(0, lon.length() - 2); if (lat.endsWith(".0")) lat = lat.substring(0, lat.length() - 2); if (alt.endsWith(".0")) alt = alt.substring(0, alt.length() - 2); if (this.timestamp > 0) { out = lon + "," +lat + "," + alt + "," + getTimestamp(); } else { out = lon + "," +lat + "," + alt; } return out; } /** * Updates this coordinate with new values. * * @param altitude * @param latitude * @param longitude */ public void update(float altitude, double latitude, double longitude, long timestamp) { this.setLongitude((float) longitude); this.setLatitude((float) latitude); this.setAltitude(altitude); this.timestamp = timestamp; } }