diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index bb6f0c16..ea8c1004 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -4,12 +4,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import javafx.application.Platform; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; +import seng302.gameServer.server.messages.BoatActionType; import seng302.model.Player; import seng302.model.Yacht; -import seng302.gameServer.server.messages.BoatActionType; /** * A Static class to hold information about the current state of the game (model) @@ -30,8 +27,6 @@ public class GameState implements Runnable { private static GameStages currentStage; private static long startTime; - // TODO: 26/07/17 cir27 - Super hackish fix until something more permanent can be made. - private static ObservableList observablePlayers = FXCollections.observableArrayList(); private static Map playerStringMap = new HashMap<>(); /* Ideally I would like to make this class an object instantiated by the server and given to @@ -60,7 +55,6 @@ public class GameState implements Runnable { yachts = new HashMap<>(); new Thread(this).start(); - } public static String getHostIpAddress() { @@ -71,20 +65,14 @@ public class GameState implements Runnable { return players; } - public static ObservableList getObservablePlayers () { - return observablePlayers; - } - public static void addPlayer(Player player) { players.add(player); String playerText = player.getYacht().getSourceId() + " " + player.getYacht().getBoatName() + " " + player.getYacht().getCountry(); - Platform.runLater(() -> observablePlayers.add(playerText)); //Had to add this to handle javaFX window using array playerStringMap.put(player, playerText); } public static void removePlayer(Player player) { players.remove(player); - observablePlayers.remove(playerStringMap.get(player)); playerStringMap.remove(player); } diff --git a/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java b/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java index 6e85f4e1..72548c12 100644 --- a/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/gameServer/server/messages/BoatLocationMessage.java @@ -1,6 +1,7 @@ package seng302.gameServer.server.messages; public class BoatLocationMessage extends Message { + private final int MESSAGE_SIZE = 56; private long messageVersionNumber; @@ -28,6 +29,7 @@ public class BoatLocationMessage extends Message { /** * Describes the location, altitude and sensor data from the boat. + * * @param sourceId ID of the boat * @param sequenceNum Sequence number of the message * @param latitude The boats latitude @@ -35,7 +37,8 @@ public class BoatLocationMessage extends Message { * @param heading The boats heading * @param boatSpeed The boats speed */ - public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){ + public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, + double heading, long boatSpeed) { messageVersionNumber = 1; time = System.currentTimeMillis(); this.sourceId = sourceId; @@ -49,7 +52,7 @@ public class BoatLocationMessage extends Message { this.roll = 0; this.boatSpeed = boatSpeed; this.COG = 2; - this.SOG = boatSpeed ; + this.SOG = boatSpeed; this.apparentWindSpeed = 0; this.apparentWindAngle = 0; this.trueWindSpeed = 0; @@ -63,7 +66,7 @@ public class BoatLocationMessage extends Message { allocateBuffer(); writeHeaderToBuffer(); - long headingToSend = (long)((heading/360.0) * 65535.0); + long headingToSend = (long) ((heading / 360.0) * 65535.0); putByte((byte) messageVersionNumber); putInt(time, 6); @@ -94,56 +97,62 @@ public class BoatLocationMessage extends Message { /** * Convert binary latitude or longitude to floating point number + * * @param binaryPackedLatLon Binary packed lat OR lon * @return Floating point lat/lon */ - public static double binaryPackedToLatLon(long binaryPackedLatLon){ - return (double)binaryPackedLatLon * 180.0 / 2147483648.0; + public static double binaryPackedToLatLon(long binaryPackedLatLon) { + return (double) binaryPackedLatLon * 180.0 / 2147483648.0; } /** * Convert binary packed heading to floating point number + * * @param binaryPackedHeading Binary packed heading * @return heading as a decimal */ - public static double binaryPackedHeadingToDouble(long binaryPackedHeading){ - return (double)binaryPackedHeading * 360.0 / 65536.0; + public static double binaryPackedHeadingToDouble(long binaryPackedHeading) { + return (double) binaryPackedHeading * 360.0 / 65536.0; } /** * Convert binary packed wind angle to floating point number + * * @param binaryPackedWindAngle Binary packed wind angle * @return wind angle as a decimal */ - public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle){ - return (double)binaryPackedWindAngle*180.0/32768.0; + public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle) { + return (double) binaryPackedWindAngle * 180.0 / 32768.0; } /** * Convert a latitude or longitude to a binary packed long + * * @param latLon A floating point latitude/longitude * @return A binary packed lat/lon */ - public static long latLonToBinaryPackedLong(double latLon){ - return (long)((536870912 * latLon) / 45); + public static long latLonToBinaryPackedLong(double latLon) { + return (long) ((536870912 * latLon) / 45); } /** * Convert a heading to a binary packed long + * * @param heading A floating point heading * @return A binary packed heading */ - public static long headingToBinaryPackedLong(double heading){ - return (long)((8192*heading)/45); + public static long headingToBinaryPackedLong(double heading) { + return (long) ((8192 * heading) / 45); } /** * Convert a wind angle to a binary packed long + * * @param windAngle Floating point wind angle * @return A binary packed wind angle */ - public static long windAngleToBinaryPackedLong(double windAngle){ - return (long)((8192*windAngle)/45); + public static long windAngleToBinaryPackedLong(double windAngle) { + return (long) ((8192 * windAngle) / 45); } @Override diff --git a/src/main/java/seng302/gameServer/server/simulator/Simulator.java b/src/main/java/seng302/gameServer/server/simulator/Simulator.java deleted file mode 100644 index 338f011a..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/Simulator.java +++ /dev/null @@ -1,137 +0,0 @@ -package seng302.gameServer.server.simulator; - -import java.util.List; -import java.util.Observable; -import java.util.concurrent.ThreadLocalRandom; -import seng302.model.mark.Mark; -import seng302.gameServer.server.simulator.parsers.RaceParser; -import seng302.model.GeoPoint; -import seng302.utilities.GeoUtility; - -public class Simulator extends Observable implements Runnable { - - private List course; - private List boats; - private long lapse; - private boolean isRaceStarted; - - /** - * Creates a simulator instance with given time lapse. - * @param lapse time duration in millisecond. - */ - public Simulator(long lapse) { - RaceParser rp = new RaceParser("/server_config/race.xml"); - course = rp.getCourse(); - boats = rp.getBoats(); - this.lapse = lapse; - isRaceStarted = false; - - setLegs(); - - // set start line's coordinate to boats - Double startLat = course.get(0).getCompoundMark().getSubMark(1).getLat(); - Double startLng = course.get(0).getCompoundMark().getSubMark(1).getLng(); - for (Boat boat : boats) { - boat.setLat(startLat); - boat.setLng(startLng); - boat.setLastPassedCorner(course.get(0)); - boat.setHeadingCorner(course.get(1)); - boat.setSpeed(ThreadLocalRandom.current().nextInt(40000, 60000 + 1)); - } - } - - @Override - public void run() { - - int numOfFinishedBoats = 0; - - while (numOfFinishedBoats < boats.size()) { - - // if race has started, then boat should start to move. - if (isRaceStarted) { - for (Boat boat : boats) { - numOfFinishedBoats += moveBoat(boat, lapse); - } - } - - setChanged(); - notifyObservers(boats); - - try { - Thread.sleep(lapse); - } catch (InterruptedException e) { - System.out.println("[Simulator] interrupted exception "); - } - } - } - - /** - * Moves a boat with given time duration. - * @param boat the boat to be moved - * @param duration the moving duration in milliseconds - * @return 1 if the boat has reached the final line, otherwise return 0 - */ - private int moveBoat(Boat boat, double duration) { - if (boat.getHeadingCorner() != null) { - - boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration); - - GeoPoint boatPos = new GeoPoint(boat.getLat(), boat.getLng()); - GeoPoint lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getSubMark(1); - - double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos); - // if a boat passes its heading mark - while (distanceFromLastMark >= boat.getLastPassedCorner().getDistanceToNextCorner()) { - double compensateDistance = distanceFromLastMark - boat.getLastPassedCorner().getDistanceToNextCorner(); - boat.setLastPassedCorner(boat.getHeadingCorner()); - boat.setHeadingCorner(boat.getLastPassedCorner().getNextCorner()); - - // heading corner == null means boat has reached the final mark - if (boat.getHeadingCorner() == null) { - boat.setFinished(true); - return 1; - } - - // move compensate distance for the mark just passed - GeoPoint pos = GeoUtility.getGeoCoordinate( - boat.getLastPassedCorner().getCompoundMark().getSubMark(1), - boat.getLastPassedCorner().getBearingToNextCorner(), - compensateDistance); - boat.setLat(pos.getLat()); - boat.setLng(pos.getLng()); - distanceFromLastMark = GeoUtility.getDistance(new GeoPoint(boat.getLat(), boat.getLng()), - boat.getLastPassedCorner().getCompoundMark().getSubMark(1)); - } - } - return 0; - } - - /** - * Link all the corners in the course list so that every corner knows its next - * corner, as well as the distance and bearing to its next corner. However, - * the last corner's heading is null, which means it is the final line. - */ - private void setLegs() { - // get the bearing from one mark to the next heading mark - for (int i = 0; i < course.size() - 1; i++) { - - Mark mark1 = course.get(i).getCompoundMark().getSubMark(1); - Mark mark2 = course.get(i + 1).getCompoundMark().getSubMark(1); - course.get(i).setDistanceToNextCorner(GeoUtility.getDistance(mark1, mark2)); - - course.get(i).setNextCorner(course.get(i + 1)); - - course.get(i).setBearingToNextCorner( - GeoUtility.getBearing(course.get(i).getCompoundMark().getSubMark(1), - course.get(i + 1).getCompoundMark().getSubMark(1))); - } - } - - public List getBoats(){ - return boats; - } - - public void setRaceStarted(boolean raceStarted) { - isRaceStarted = raceStarted; - } -} diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 16df9123..519df57c 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -1,7 +1,5 @@ package seng302.model; -import static seng302.utilities.GeoUtility.getGeoCoordinate; - import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -14,6 +12,8 @@ import javafx.beans.property.ReadOnlyLongWrapper; import javafx.scene.paint.Color; import seng302.gameServer.GameState; import seng302.model.mark.CompoundMark; +import seng302.model.mark.Mark; +import seng302.utilities.GeoUtility; /** * Yacht class for the racing boat. @@ -29,6 +29,8 @@ public class Yacht { void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity, boolean sailIn); } + private static final Double ROUNDING_DISTANCE = 15d; // TODO: 3/08/17 wmu16 - Look into this value further + //BOTH AFAIK private String boatType; private Integer sourceId; @@ -38,12 +40,12 @@ public class Yacht { private String country; private Long estimateTimeAtFinish; - private Long timeTillNext; + private Long lastMark; private Long markRoundTime; + private Double distanceToNextMark; + private Long timeTillNext; private CompoundMark nextMark; private Double heading; - private Double lat; - private Double lon; private Integer legNumber = 0; //SERVER SIDE @@ -53,6 +55,11 @@ public class Yacht { private GeoPoint location; private Integer boatStatus; private Double velocity; + //MARK ROUNDING INFO + private GeoPoint lastLocation; //For purposes of mark rounding calculations + private Boolean hasEnteredRoundingZone; //The distance that the boat must be from the mark to round + private Boolean hasPassedFirstLine; //The line extrapolated from the next mark to the current mark + private Boolean hasPassedSecondLine; //The line extrapolated from the last mark to the current mark //CLIENT SIDE private List locationListeners = new ArrayList<>(); @@ -73,8 +80,13 @@ public class Yacht { this.boatName = boatName; this.country = country; this.location = new GeoPoint(57.670341, 11.826856); + this.lastLocation = location; this.heading = 120.0; //In degrees this.velocity = 0d; //in mms-1 + + this.hasEnteredRoundingZone = false; + this.hasPassedFirstLine = false; + this.hasPassedSecondLine = false; } /** @@ -110,8 +122,36 @@ public class Yacht { } } - Double metersCovered = velocity * secondsElapsed; - location = getGeoCoordinate(location, heading, metersCovered); + //UPDATE BOAT LOCATION + location = GeoUtility.getGeoCoordinate(location, heading, velocity * secondsElapsed); + + //CHECK FOR MARK ROUNDING + distanceToNextMark = calcDistanceToNextMark(); + if (distanceToNextMark < ROUNDING_DISTANCE) { + hasEnteredRoundingZone = true; + } + + // TODO: 3/08/17 wmu16 - Implement line cross check here + } + + + /** + * Calculates the distance to the next mark (closest of the two if a gate mark). + * + * @return A distance in metres. Returns -1 if there is no next mark + */ + public Double calcDistanceToNextMark() { + if (nextMark == null) { + return -1d; + } else if (nextMark.isGate()) { + Mark sub1 = nextMark.getSubMark(1); + Mark sub2 = nextMark.getSubMark(2); + Double distance1 = GeoUtility.getDistance(location, sub1); + Double distance2 = GeoUtility.getDistance(location, sub2); + return (distance1 < distance2) ? distance1 : distance2; + } else { + return GeoUtility.getDistance(location, nextMark.getSubMark(1)); + } } public void adjustHeading(Double amount) { @@ -327,20 +367,21 @@ public class Yacht { return nextMark; } - public Double getLat() { - return lat; + public GeoPoint getLocation() { + return location; } - public void setLat(Double lat) { - this.lat = lat; - } - - public Double getLon() { - return lon; - } - - public void setLon(Double lon) { - this.lon = lon; + /** + * Sets the current location of the boat in lat and long whilst preserving the last location + * + * @param lat Latitude + * @param lng Longitude + */ + public void setLocation(Double lat, Double lng) { + lastLocation.setLat(location.getLat()); + lastLocation.setLng(location.getLng()); + location.setLat(lat); + location.setLng(lng); } public Double getHeading() { @@ -360,10 +401,6 @@ public class Yacht { return boatName; } - public GeoPoint getLocation() { - return location; - } - public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) { this.timeSinceLastMarkProperty.set(timeSinceLastMark); } @@ -397,18 +434,18 @@ public class Yacht { this.velocity = velocity; } - public Boolean getClientSailsIn(){ - return clientSailsIn; + + public Double getDistanceToNextMark() { + return distanceToNextMark; } - public void updateLocation (double lat, double lon, double heading, double velocity) { - this.lat = lat; - this.lon = lon; + public void updateLocation(double lat, double lng, double heading, double velocity) { + setLocation(lat, lng); this.heading = heading; this.velocity = velocity; updateVelocityProperty(velocity); for (YachtLocationListener yll : locationListeners) { - yll.notifyLocation(this, lat, lon, heading, velocity, clientSailsIn); + yll.notifyLocation(this, lat, lng, heading, velocity, clientSailsIn); } } diff --git a/src/main/java/seng302/model/mark/CompoundMark.java b/src/main/java/seng302/model/mark/CompoundMark.java index b1989845..af4cd8a8 100644 --- a/src/main/java/seng302/model/mark/CompoundMark.java +++ b/src/main/java/seng302/model/mark/CompoundMark.java @@ -86,4 +86,46 @@ public class CompoundMark { public List getMarks () { return marks; } + + +// @Override +// public boolean equals(Object other) { +// if (other == null) { +// return false; +// } +// +// if (!(other instanceof Mark)){ +// return false; +// } +// +// Mark otherMark = (Mark) other; +// +// if (otherMark.getLat() != getLat() || otherMark.getLongitude() != getLongitude()) { +// return false; +// } +// +// if (otherMark.getCompoundMarkID() != getCompoundMarkID()){ +// return false; +// } +// +// if (otherMark.getId() != getId()){ +// return false; +// } +// +// if (!otherMark.getName().equals(name)){ +// return false; +// } +// +// return true; +// } + + @Override + public int hashCode() { + int hash = 0; + for (Mark mark : marks) { + hash += Double.hashCode(mark.getSourceID()) + Double.hashCode(mark.getLat()) + + Double.hashCode(mark.getLng()) + mark.getName().hashCode(); + } + return hash + getName().hashCode() + Integer.hashCode(getId()); + } } diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java new file mode 100644 index 00000000..ca2b4356 --- /dev/null +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -0,0 +1,135 @@ +package seng302.model.mark; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import seng302.model.stream.xml.generator.Race; +import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.utilities.XMLGenerator; +import seng302.utilities.XMLParser; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Class to hold the order of the marks in the race. + */ +public class MarkOrder { + private List raceMarkOrder; + private Logger logger = LoggerFactory.getLogger(MarkOrder.class); + + public MarkOrder(){ + loadRaceProperties(); + } + + /** + * @return An ordered list of marks in the race + * OR null if the mark order could not be loaded + */ + public List getMarkOrder(){ + if (raceMarkOrder == null){ + logger.warn("Race order accessed but not instantiated"); + return null; + } + + return Collections.unmodifiableList(raceMarkOrder); + } + + /** + * Returns the mark in the race after the previous mark + * @param position The current race position + * @return the next race position + * OR null if there is no position + */ + public RacePosition getNextPosition(RacePosition position){ + Mark previousMark = position.getNextMark(); + Mark nextMark; + + if (position.getPositionIndex() + 1 >= raceMarkOrder.size() - 1){ + RacePosition nextRacePosition = new RacePosition(raceMarkOrder.size() - 1, null, previousMark); + nextRacePosition.setFinishingLeg(); + + return nextRacePosition; + } + + Integer nextPositionIndex = position.getPositionIndex() + 1; + RacePosition nextRacePosition = new RacePosition(nextPositionIndex, raceMarkOrder.get(nextPositionIndex), previousMark); + + return nextRacePosition; + } + + /** + * Loads the race order from an XML string + * @param xml An AC35 RaceXML + * @return An ordered list of marks in the race + */ + private List loadRaceOrderFromXML(String xml){ + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db; + Document doc; + + try { + db = dbf.newDocumentBuilder(); + doc = db.parse(new InputSource(new StringReader(xml))); + } catch (ParserConfigurationException | IOException | SAXException e) { + logger.error("Failed to read generated race XML"); + return null; + } + + RaceXMLData data = XMLParser.parseRace(doc); + + if (data != null){ + logger.debug("Loaded RaceXML for mark order"); + List corners = data.getMarkSequence(); + Map marks = data.getCompoundMarks(); + List course = new ArrayList<>(); + + for (Corner corner : corners){ + CompoundMark compoundMark = marks.get(corner.getCompoundMarkID()); + course.add(compoundMark.getMarks().get(0)); + } + + return course; + } + + return null; + } + + /** + * @return The first position in the race + */ + public RacePosition getFirstPosition(){ + if (raceMarkOrder.size() > 0){ + return new RacePosition(-1, raceMarkOrder.get(0), null); + } + + return null; + } + + /** + * Load the raceXML and mark order + */ + private void loadRaceProperties(){ + XMLGenerator generator = new XMLGenerator(); + + generator.setRace(new Race()); + + String raceXML = generator.getRaceAsXml(); + + if (raceXML == null){ + logger.error("Failed to generate raceXML (for race properties)"); + return; + } + raceMarkOrder = loadRaceOrderFromXML(raceXML); + } +} diff --git a/src/main/java/seng302/model/mark/RacePosition.java b/src/main/java/seng302/model/mark/RacePosition.java new file mode 100644 index 00000000..fc160b10 --- /dev/null +++ b/src/main/java/seng302/model/mark/RacePosition.java @@ -0,0 +1,55 @@ +package seng302.model.mark; + +/** + * Represents a boats position between two marks + */ +public class RacePosition { + private Integer positionIndex; + private Mark nextMark; + private Mark previousMark; + private Boolean isFinishingLeg; + + public RacePosition(Integer positionIndex, Mark nextMark, Mark previousMark){ + this.positionIndex = positionIndex; + this.nextMark = nextMark; + this.previousMark = previousMark; + isFinishingLeg = false; + } + + /** + * @return The position of the boat (0...number of marks in race - 1) + */ + public Integer getPositionIndex(){ + return positionIndex; + } + + /** + * @return The mark the boat is heading to + * will return NULL if this is the finishing legg + */ + public Mark getNextMark(){ + return nextMark; + } + + /** + * @return The mark the boat is heading away from, + * Will return NULL if this is the starting leg + */ + public Mark getPreviousMark(){ + return previousMark; + } + + /** + * Sets a flag that this is the last leg in the race + */ + public void setFinishingLeg(){ + isFinishingLeg = true; + } + + /** + * @return true if this is the last leg in the race + */ + public boolean getIsFinishingLeg() { + return isFinishingLeg; + } +} diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 54b0484a..9aa61b9d 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -5,145 +5,182 @@ import seng302.model.GeoPoint; public class GeoUtility { - private static double EARTH_RADIUS = 6378.137; + private static double EARTH_RADIUS = 6378.137; - /** - * Calculates the euclidean distance between two markers on the canvas using xy coordinates - * - * @param p1 first geographical position - * @param p2 second geographical position - * @return the distance in meter between two points in meters - */ - public static Double getDistance(GeoPoint p1, GeoPoint p2) { + /** + * Calculates the euclidean distance between two markers on the canvas using xy coordinates + * + * @param p1 first geographical position + * @param p2 second geographical position + * @return the distance in meter between two points in meters + */ + public static Double getDistance(GeoPoint p1, GeoPoint p2) { - double dLat = Math.toRadians(p2.getLat() - p1.getLat()); - double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + double dLat = Math.toRadians(p2.getLat() - p1.getLat()); + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); - double a = Math.pow(Math.sin(dLat / 2), 2.0) - + Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) - * Math.pow(Math.sin(dLon / 2), 2.0); + double a = Math.pow(Math.sin(dLat / 2), 2.0) + + Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) + * Math.pow(Math.sin(dLon / 2), 2.0); - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - double d = EARTH_RADIUS * c; + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + double d = EARTH_RADIUS * c; - return d * 1000; // distance from km to meter - } + return d * 1000; // distance from km to meter + } - /** - * Calculates the angle between to angular co-ordinates on a sphere. - * - * @param p1 the first geographical position, start point - * @param p2 the second geographical position, end point - * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). - * vertical up is 0 deg. horizontal right is 90 deg. - * - * NOTE: - * The final bearing will differ from the initial bearing by varying degrees - * according to distance and latitude (if you were to go from say 35°N,45°E - * (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60° - * and end up on a heading of 120° - */ - public static Double getBearing(GeoPoint p1, GeoPoint p2) { - return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; - } + /** + * Calculates the angle between to angular co-ordinates on a sphere. + * + * @param p1 the first geographical position, start point + * @param p2 the second geographical position, end point + * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). vertical up + * is 0 deg. horizontal right is 90 deg. + * + * NOTE: The final bearing will differ from the initial bearing by varying degrees according to + * distance and latitude (if you were to go from say 35°N,45°E (≈ Baghdad) to 35°N,135°E (≈ + * Osaka), you would start on a heading of 60° and end up on a heading of 120° + */ + public static Double getBearing(GeoPoint p1, GeoPoint p2) { + return (Math.toDegrees(getBearingRad(p1, p2)) + 360.0) % 360.0; + } - /** - * Calculates the angle between to angular co-ordinates on a sphere in radians. - * - * @param p1 the first geographical position, start point - * @param p2 the second geographical position, end point - * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). - * vertical up is 0 deg. horizontal right is 90 deg. - * - * NOTE: - * The final bearing will differ from the initial bearing by varying degrees - * according to distance and latitude (if you were to go from say 35°N,45°E - * (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60° - * and end up on a heading of 120° - */ - public static Double getBearingRad(GeoPoint p1, GeoPoint p2) { - double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + /** + * Calculates the angle between to angular co-ordinates on a sphere in radians. + * + * @param p1 the first geographical position, start point + * @param p2 the second geographical position, end point + * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). vertical up + * is 0 deg. horizontal right is 90 deg. + * + * NOTE: The final bearing will differ from the initial bearing by varying degrees according to + * distance and latitude (if you were to go from say 35°N,45°E (≈ Baghdad) to 35°N,135°E (≈ + * Osaka), you would start on a heading of 60° and end up on a heading of 120° + */ + public static Double getBearingRad(GeoPoint p1, GeoPoint p2) { + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); - double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat())); - double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat())) - - Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math.cos(dLon); + double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat())); + double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat())) + - Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math + .cos(dLon); - return Math.atan2(y, x); - } + return Math.atan2(y, x); + } - /** - * Given an existing point in lat/lng, distance in (in meter) and bearing - * (in degrees), calculates the new lat/lng. - * - * @param origin the original position within lat / lng - * @param bearing the bearing in degree, from original position to the new position - * @param distance the distance in meter, from original position to the new position - * @return the new position - */ - public static GeoPoint getGeoCoordinate(GeoPoint origin, Double bearing, Double distance) { - double b = Math.toRadians(bearing); // bearing to radians - double d = distance / 1000.0; // distance to km + /** + * Given an existing point in lat/lng, distance in (in meter) and bearing (in degrees), + * calculates the new lat/lng. + * + * @param origin the original position within lat / lng + * @param bearing the bearing in degree, from original position to the new position + * @param distance the distance in meter, from original position to the new position + * @return the new position + */ + public static GeoPoint getGeoCoordinate(GeoPoint origin, Double bearing, Double distance) { + double b = Math.toRadians(bearing); // bearing to radians + double d = distance / 1000.0; // distance to km - double originLat = Math.toRadians(origin.getLat()); - double originLng = Math.toRadians(origin.getLng()); + double originLat = Math.toRadians(origin.getLat()); + double originLng = Math.toRadians(origin.getLng()); - double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS) - + Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b)); - double endLng = originLng - + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), - Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); + double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS) + + Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b)); + double endLng = originLng + + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), + Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); - return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); - } - - /** - * Performs the line function on two points of a line and a test point to test which side of the line that point is - * on. If the return value is - * return 1, then the point is on one side of the line, - * return -1 then the point is on the other side of the line - * return 0 then the point is exactly on the line. - * @param linePoint1 One point of the line - * @param linePoint2 Second point of the line - * @param testPoint The point to test with this line - * @return A return value indicating which side of the line the point is on - */ - public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { - - Double x = testPoint.getX(); - Double y = testPoint.getY(); - Double x1 = linePoint1.getX(); - Double y1 = linePoint1.getY(); - Double x2 = linePoint2.getX(); - Double y2 = linePoint2.getY(); - - Double result = (x - x1)*(y2 - y1) - (y - y1)*(x2 - x1); //Line function - - if (result > 0) { - return 1; - } - else if (result < 0) { - return -1; - } - else { - return 0; - } - } + return new GeoPoint(Math.toDegrees(endLat), Math.toDegrees(endLng)); + } - /** - * Given a point and a vector (angle and vector length) Will create a new point, that vector away from the origin - * point - * @param originPoint The point with which to use as the base for our vector addition - * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) - * @param vectorLength The length out on this angle from the origin point to create the new point - * @return a Point2D - */ - public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, Double vectorLength) { + /** + * Performs the line function on two points of a line and a test point to test which side of the + * line that point is on. If the return value is return 1, then the point is on one side of the + * line, return -1 then the point is on the other side of the line return 0 then the point is + * exactly on the line. + * + * @param linePoint1 One point of the line + * @param linePoint2 Second point of the line + * @param testPoint The point to test with this line + * @return A return value indicating which side of the line the point is on + */ + public static Integer lineFunction(Point2D linePoint1, Point2D linePoint2, Point2D testPoint) { - Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); - Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); + Double x = testPoint.getX(); + Double y = testPoint.getY(); + Double x1 = linePoint1.getX(); + Double y1 = linePoint1.getY(); + Double x2 = linePoint2.getX(); + Double y2 = linePoint2.getY(); - return new Point2D(endPointX, endPointY); + Double result = (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1); //Line function - } + if (result > 0) { + return 1; + } else if (result < 0) { + return -1; + } else { + return 0; + } + } + + + /** + * Given a point and a vector (angle and vector length) Will create a new point, that vector + * away from the origin point + * + * @param originPoint The point with which to use as the base for our vector addition + * @param angleInDeg (DEGREES) The angle at which our new point is being created (in degrees!) + * @param vectorLength The length out on this angle from the origin point to create the new + * point + * @return a Point2D + */ + public static Point2D makeArbitraryVectorPoint(Point2D originPoint, Double angleInDeg, + Double vectorLength) { + + Double endPointX = originPoint.getX() + vectorLength * Math.cos(Math.toRadians(angleInDeg)); + Double endPointY = originPoint.getY() + vectorLength * Math.sin(Math.toRadians(angleInDeg)); + + return new Point2D(endPointX, endPointY); + + } + + /** + * Define vector v1 = p1 - p0 to v2 = p2- p0. This function returns the difference of bearing + * from v1 to v2. For example, if bearing of v1 is 30 deg and bearing of v2 is 90 deg, then the + * difference is 60 deg. + * + * @param bearing1 the bearing of v1 + * @param bearing2 the bearing of v2 + * @return the difference of bearing from v1 to v2 + */ + private static double getBearingDiff(double bearing1, double bearing2) { + return ((360 - bearing1) + bearing2) % 360; + } + + /** + * Given three geo points to form a triangle, the method returns true if the fourth point is + * inside the triangle + * + * @param v1 the vertex of the triangle + * @param v2 the vertex of the triangle + * @param v3 the vertex of the triangle + * @param point the point to be tested + * @return true if the fourth point is inside the triangle + */ + public static boolean isPointInTriangle(GeoPoint v1, GeoPoint v2, GeoPoint v3, GeoPoint point) { + // true, if diff of bearing from (v1->v2) to (v1->p) is less than 180 deg + boolean sideFlag = getBearingDiff(getBearing(v1, v2), getBearing(v1, point)) < 180; + + if ((getBearingDiff(getBearing(v2, v3), getBearing(v2, point)) < 180) != sideFlag) { + return false; + } + + if ((getBearingDiff(getBearing(v3, v1), getBearing(v3, point)) < 180) != sideFlag) { + return false; + } + + return true; + } } diff --git a/src/test/java/seng302/BoatTest.java b/src/test/java/seng302/BoatTest.java deleted file mode 100644 index 9ab5b66d..00000000 --- a/src/test/java/seng302/BoatTest.java +++ /dev/null @@ -1,35 +0,0 @@ -//package seng302; -// -//import org.junit.Test; -//import seng302.model.Boat; -// -//import static org.junit.Assert.assertEquals; -// -///** -// * Unit test for the Team class. -// */ -//public class BoatTest { -// -// @Test -// public void testBoatCreation() { -// Boat boat1 = new Boat("Team 1"); -// assertEquals(boat1.getTeamName(), "Team 1"); -// assertEquals(boat1.getVelocity(), (double) 10.0, 1e-15); -// } -// -// @Test -// public void testChangeTeamName() { -// Boat boat1 = new Boat("Team 1"); -// boat1.setTeamName("Team 2"); -// assertEquals(boat1.getTeamName(), "Team 2"); -// } -// -// @Test -// public void testSetVelocity() { -// Boat boat1 = new Boat("Team 1", 29.0, "", 100); -// assertEquals(boat1.getVelocity(), (double) 29.0, 1e-15); -// -// boat1.setVelocity(12.0); -// assertEquals(boat1.getVelocity(), (double)12.0, 1e-15); -// } -//} diff --git a/src/test/java/seng302/TestGeoUtils.java b/src/test/java/seng302/TestGeoUtils.java deleted file mode 100644 index 09232f55..00000000 --- a/src/test/java/seng302/TestGeoUtils.java +++ /dev/null @@ -1,67 +0,0 @@ -package seng302; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; - -import javafx.geometry.Point2D; -import org.junit.Before; -import org.junit.Test; -import seng302.utilities.GeoUtility; - -/** - * Test Class for the GeometryUtils class - * Created by wmu16 on 24/05/17. - */ -public class TestGeoUtils { - - //Line in x = y - private Point2D linePoint1 = new Point2D(0, 0); - private Point2D linePoint2 = new Point2D(1, 1); - - //Point below x = y - private Point2D arbitraryPoint1 = new Point2D(1, 0); - - //Point above x = y - private Point2D arbitraryPoint2 = new Point2D(0, 1); - - //Point on x = y - private Point2D arbitraryPoint3 = new Point2D(2, 2); - - @Before - public void setUp() throws Exception { - - } - - - @Test - public void testLineFunction() { - - Integer lineFunctionResult1 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint1); - Integer lineFunctionResult2 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint2); - Integer lineFunctionResult3 = GeoUtility.lineFunction(linePoint1, linePoint2, arbitraryPoint3); - - //Point1 and Point2 are on opposite sides - assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2)); - assertNotEquals(lineFunctionResult1, lineFunctionResult2); - - //Point3 is on the line - assertEquals((long) lineFunctionResult3, 0L); - } - - @Test - public void testMakeArbitraryVectorPoint() { - - //Make a point (1,0) from point (0,0) - Point2D newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 0d, 1d); - Point2D expected = new Point2D(1,0); - - assertEquals(expected.getX(), newPoint.getX(), 1E-6); - assertEquals(expected.getY(), newPoint.getY(), 1E-6); - - newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 90d, 1d); - expected = new Point2D(0, 1); - - assertEquals(expected.getX(), newPoint.getX(), 1E-6); - assertEquals(expected.getY(), newPoint.getY(), 1E-6); - } -} diff --git a/src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java b/src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java deleted file mode 100644 index 7078eace..00000000 --- a/src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package seng302.gameServer.server.simulator; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import seng302.model.GeoPoint; -import seng302.utilities.GeoUtility; - -/** - * To test methods in GeoUtility. - * Created by Haoming on 28/04/17. - */ -public class GeoUtilityTest { - - private GeoPoint p1 = new GeoPoint(57.670333, 11.827833); - private GeoPoint p2 = new GeoPoint(57.671524, 11.844495); - private GeoPoint p3 = new GeoPoint(57.670822, 11.843392); - private GeoPoint p4 = new GeoPoint(25.694829, 98.392049); - - private double toleranceRate = 0.01; - - @Test - public void getDistance() throws Exception { - double expected, actual; - - actual = GeoUtility.getDistance(p1, p2); - expected = 1000; - assertEquals(expected, actual, expected * toleranceRate); - - actual = GeoUtility.getDistance(p1, p3); - expected = 927; - assertEquals(expected, actual, expected * toleranceRate); - - actual = GeoUtility.getDistance(p2, p4); - expected = 7430180; - assertEquals(expected, actual, expected * toleranceRate); - } - - @Test - public void getBearing() throws Exception { - double expected, actual; - - actual = GeoUtility.getBearing(p1, p2); - expected = 82; - assertEquals(expected, actual, expected * toleranceRate); - - actual = GeoUtility.getBearing(p1, p3); - expected = 86; - assertEquals(expected, actual, expected * toleranceRate); - - actual = GeoUtility.getBearing(p2, p4); - expected = 78; - assertEquals(expected, actual, expected * toleranceRate); - } - - @Test - public void getGeoCoordinate() throws Exception { - GeoPoint expected, actual; - - actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0); - expected = p2; - assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); - assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); - - actual = GeoUtility.getGeoCoordinate(p1, 86.0, 927.0); - expected = p3; - assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); - assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); - - actual = GeoUtility.getGeoCoordinate(p2, 78.0, 7430180.0); - expected = p4; - assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); - assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); - } - -} \ No newline at end of file diff --git a/src/test/java/seng302/model/YachtTest.java b/src/test/java/seng302/model/YachtTest.java new file mode 100644 index 00000000..7937b43c --- /dev/null +++ b/src/test/java/seng302/model/YachtTest.java @@ -0,0 +1,54 @@ +package seng302.model; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.Mark; + +/** + * Use this link to test geo distances + * http://www.csgnetwork.com/gpsdistcalc.html + * Created by wmu16 on 3/08/17. + */ +public class YachtTest { + + private Yacht yacht; + private CompoundMark compoundMark; + private Double toleranceRatio = 0.01; + private GeoPoint p1 = new GeoPoint(57.670333, 11.827833); + private GeoPoint p2 = new GeoPoint(57.671524, 11.844495); + private GeoPoint p3 = new GeoPoint(57.670822, 11.843392); + private GeoPoint p4 = new GeoPoint(25.694829, 98.392049); + + @Before + public void setup() { + yacht = new Yacht("Yacht", + 0, + "0", + "WillIsCool", + "HaomingIsOk", + "NZL"); + + yacht.setLocation(57.670333, 11.827833); + + compoundMark = new CompoundMark(0, "HaomingsMark"); + Mark subMark1 = new Mark("H", 57.671524, 11.844495, 0); + Mark subMark2 = new Mark("H", 57.670822, 11.843392, 0); + compoundMark.addSubMarks(subMark1, subMark2); + + yacht.setNextMark(compoundMark); + } + + + @Test + public void testDistanceToNextMark() { + Double actual, expected; + actual = yacht.calcDistanceToNextMark(); + expected = 927d; + assertEquals(expected, actual, expected * toleranceRatio); + } + + +} \ No newline at end of file diff --git a/src/test/java/seng302/model/mark/MarkTest.java b/src/test/java/seng302/model/mark/MarkTest.java deleted file mode 100644 index 125f20cc..00000000 --- a/src/test/java/seng302/model/mark/MarkTest.java +++ /dev/null @@ -1,45 +0,0 @@ -//package seng302.model.mark; -// -//import static org.junit.Assert.assertEquals; -//import static org.junit.Assert.assertTrue; -// -//import org.junit.Before; -//import org.junit.Test; -// -///** -// * Created by Haoming on 17/3/17. -// */ -//public class MarkTest { -// -// private SingleMark singleMark1; -// private SingleMark singleMark2; -// private GateMark gateMark; -// -// @Before -// public void setUp() throws Exception { -// this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342, 1, 0); -// this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352, 2, 1); -// this.gateMark = new GateMark("testMark_GM", MarkType.OPEN_GATE, singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude(), 2); -// } -// -// @Test -// public void getName() throws Exception { -// assertEquals("testMark_SM1", this.singleMark1.getName()); -// assertEquals("testMark_GM", this.gateMark.getName()); -// } -// -// @Test -// public void getMarkType() throws Exception { -// assertTrue(this.singleMark2.getMarkType() == MarkType.SINGLE_MARK); -// assertTrue(this.gateMark.getMarkType() == MarkType.OPEN_GATE); -// } -// -// @Test -// public void getMarkContent() throws Exception { -// assertEquals(12.23234, this.singleMark1.getLatitude(), 1e-10); -// assertEquals(-34.342, this.singleMark1.getLongitude(), 1e-10); -// assertEquals("testMark_SM1", this.gateMark.getSingleMark1().getName()); -// assertEquals(-34.352, this.gateMark.getSingleMark2().getLongitude(), 1e-10); -// } -// -//} \ No newline at end of file diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java new file mode 100644 index 00000000..8db06ec8 --- /dev/null +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -0,0 +1,83 @@ +package seng302.models; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import seng302.model.mark.Mark; +import seng302.model.mark.MarkOrder; +import seng302.model.mark.RacePosition; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + +public class MarkOrderTest { + private static MarkOrder markOrder; + + @BeforeClass + public static void setup(){ + markOrder = new MarkOrder(); + } + + /** + * Test to ensure marks are loaded from XML + */ + @Test + public void testMarkOrderLoadedFromXML(){ + assertTrue(markOrder != null); + } + + /** + * Test if .getNextMark() returns null if it is called with the final mark in the race + */ + @Test + public void testNextMarkAtEnd(){ + // There are no marks in the XML, therefore this can't be tested + if (markOrder.getMarkOrder().size() == 0){ + return; + } + + Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); + Integer lastIndex = markOrder.getMarkOrder().size() - 1; + + RacePosition lastRacePosition = new RacePosition(lastIndex, lastMark, null); + + assertEquals(null, markOrder.getNextPosition(lastRacePosition).getNextMark()); + } + + /** + * Test if .getNextMark() method returns the next mark in the race + */ + @Test + public void testNextMark(){ + // There are not enough marks for this to be tested + if (markOrder.getMarkOrder().size() < 2){ + return; + } + + RacePosition firstRacePos = new RacePosition(0, markOrder.getMarkOrder().get(0), null); + + assertEquals(markOrder.getMarkOrder().get(1).getName(), markOrder.getNextPosition(firstRacePos).getNextMark().getName()); + } + + /** + * Test if a whole race can be completed + */ + @Test + public void testMarkSequence(){ + RacePosition current = markOrder.getFirstPosition(); + + while (!current.getIsFinishingLeg()){ + + current = markOrder.getNextPosition(current); + + if (current.getIsFinishingLeg()){ + assertEquals(null, current.getNextMark()); + } + } + } + + @AfterClass + public static void tearDown(){ + markOrder = null; + } +} diff --git a/src/test/java/seng302/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java new file mode 100644 index 00000000..8382bb8c --- /dev/null +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -0,0 +1,153 @@ +package seng302.utilities; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import javafx.geometry.Point2D; +import org.junit.Before; +import org.junit.Test; +import seng302.model.GeoPoint; +import seng302.utilities.GeoUtility; + +/** + * To test methods in GeoUtility. + * Use this site to calculate distances + * https://rechneronline.de/geo-coordinates/#distance + * Created by Haoming on 28/04/17. + */ +public class GeoUtilityTest { + + + //Line in x = y + private Point2D linePoint1 = new Point2D(0, 0); + private Point2D linePoint2 = new Point2D(1, 1); + + private Point2D arbitraryPoint1 = new Point2D(1, 0); //Point below x = y + private Point2D arbitraryPoint2 = new Point2D(0, 1); //Point above x = y + private Point2D arbitraryPoint3 = new Point2D(2, 2); //Point on x = y + + private GeoPoint p1 = new GeoPoint(57.670333, 11.827833); + private GeoPoint p2 = new GeoPoint(57.671524, 11.844495); + private GeoPoint p3 = new GeoPoint(57.670822, 11.843392); + private GeoPoint p4 = new GeoPoint(25.694829, 98.392049); + private GeoPoint p5 = new GeoPoint(57.671829, 11.842049); + + private double toleranceRate = 0.01; + + + @Test + public void getBearing() throws Exception { + double expected, actual; + + actual = GeoUtility.getBearing(p1, p2); + expected = 82; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getBearing(p1, p3); + expected = 86; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getBearing(p2, p4); + expected = 78; + assertEquals(expected, actual, expected * toleranceRate); + } + + @Test + public void getGeoCoordinate() throws Exception { + GeoPoint expected, actual; + + actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0); + expected = p2; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + + actual = GeoUtility.getGeoCoordinate(p1, 86.0, 927.0); + expected = p3; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + + actual = GeoUtility.getGeoCoordinate(p2, 78.0, 7430180.0); + expected = p4; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + } + + + @Test + public void testGetDistance() throws Exception { + double expected, actual; + + actual = GeoUtility.getDistance(p1, p2); + expected = 1000; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getDistance(p1, p3); + expected = 927; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getDistance(p2, p4); + expected = 7430180; + assertEquals(expected, actual, expected * toleranceRate); + + } + + @Test + public void testLineFunction() { + + Integer lineFunctionResult1 = GeoUtility + .lineFunction(linePoint1, linePoint2, arbitraryPoint1); + Integer lineFunctionResult2 = GeoUtility + .lineFunction(linePoint1, linePoint2, arbitraryPoint2); + Integer lineFunctionResult3 = GeoUtility + .lineFunction(linePoint1, linePoint2, arbitraryPoint3); + + //Point1 and Point2 are on opposite sides + assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2)); + assertNotEquals(lineFunctionResult1, lineFunctionResult2); + + //Point3 is on the line + assertEquals((long) lineFunctionResult3, 0L); + } + + @Test + public void testMakeArbitraryVectorPoint() { + + //Make a point (1,0) from point (0,0) + Point2D newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 0d, 1d); + Point2D expected = new Point2D(1, 0); + + assertEquals(expected.getX(), newPoint.getX(), 1E-6); + assertEquals(expected.getY(), newPoint.getY(), 1E-6); + + newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 90d, 1d); + expected = new Point2D(0, 1); + + assertEquals(expected.getX(), newPoint.getX(), 1E-6); + assertEquals(expected.getY(), newPoint.getY(), 1E-6); + } + + @Test + public void testIsPointInTriangle() { + GeoPoint v1 = new GeoPoint(57.670333, 11.842833); + GeoPoint v2 = new GeoPoint(57.671524, 11.844495); + GeoPoint v3 = new GeoPoint(57.671829, 11.842049); + GeoPoint p1 = new GeoPoint(57.670822, 11.843192); // inside triangle + GeoPoint p2 = new GeoPoint(57.670892, 11.843642); // outside triangle + + // benchmark test. 100,000 calculations for 0.336 seconds +// long startTime = System.nanoTime(); +// for (int i = 0; i < 100000; i++) { +// assertTrue(GeoUtility.isPointInTriangle(v1, v2, v3, p1)); +// } +// System.out.println((System.nanoTime() - startTime) / 1000000000.0); + + // test for different orders of vertices, which should not affect the result + assertTrue(GeoUtility.isPointInTriangle(v2, v1, v3, p1)); + assertTrue(GeoUtility.isPointInTriangle(v3, v1, v2, p1)); + + assertFalse(GeoUtility.isPointInTriangle(v1, v2, v3, p2)); + + } +} \ No newline at end of file