From 6a6ed3ed44d531511b2551b2d2da7287643ecd64 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Thu, 18 May 2017 13:32:24 +1200 Subject: [PATCH 1/7] Server sends mark locations to test - Added a timer to send boat location messages containing the mark locations to test the receiver #story[891] --- .../java/seng302/server/ServerThread.java | 50 +++++++++++++++++++ .../seng302/server/simulator/Simulator.java | 17 +++++++ 2 files changed, 67 insertions(+) diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index 094845ab..4270b633 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -1,5 +1,7 @@ package seng302.server; +import seng302.server.simulator.mark.CompoundMark; +import seng302.server.simulator.mark.Mark; import seng302.server.messages.*; import seng302.server.simulator.Boat; import seng302.server.simulator.Simulator; @@ -257,6 +259,53 @@ public class ServerThread implements Runnable, Observer { //Delays the new course xml data for 25 seconds so the boats are able to pass the starting line } + /** + * Starts sending boat location messages containing the mark positions + * Marks are flipped by 90 degrees from their original position + */ + private void startUpdatingMarkPositions(){ + Timer t = new Timer(); + t.schedule(new TimerTask() { + + /** + * Send the mark location message + * @param m The mark to send + * @param offset How far to move the marks from their original position + */ + private void sendMark(Mark m, Double offset){ + Message markLocation = new BoatLocationMessage(m.getSourceID(), server.getSequenceNumber(), + m.getLat()-offset, m.getLng()+offset*2, 0, 0); + + try { + server.send(markLocation); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void run() { + for (CompoundMark m : raceSimulator.getMarks()){ + if (m == null){ + continue; + } + + Mark mark1 = m.getMark1(); + Mark mark2 = m.getMark2(); + + if (mark1 != null){ + sendMark(mark1, 0.0002); + } + + if (mark2 != null){ + sendMark(mark2, 0.0005); + } + + } + } + }, 21000, 1000); + } + public void run() { try{ server = new StreamingServerSocket(PORT_NUMBER); @@ -275,6 +324,7 @@ public class ServerThread implements Runnable, Observer { startSendingRaceStartStatusMessages(); startSendingRaceStatusMessages(); sendPostStartCourseXml(); + startUpdatingMarkPositions(); } /** diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/server/simulator/Simulator.java index 72d2717a..c279fea5 100644 --- a/src/main/java/seng302/server/simulator/Simulator.java +++ b/src/main/java/seng302/server/simulator/Simulator.java @@ -1,12 +1,15 @@ package seng302.server.simulator; +import seng302.server.simulator.mark.CompoundMark; import seng302.server.simulator.mark.Corner; import seng302.server.simulator.mark.Mark; import seng302.server.simulator.mark.Position; import seng302.server.simulator.parsers.RaceParser; +import java.util.HashSet; import java.util.List; import java.util.Observable; +import java.util.Set; import java.util.concurrent.ThreadLocalRandom; public class Simulator extends Observable implements Runnable { @@ -138,4 +141,18 @@ public class Simulator extends Observable implements Runnable { public void setRaceStarted(boolean raceStarted) { isRaceStarted = raceStarted; } + + /** + * @return A list of marks in the race + */ + public Set getMarks(){ + Set marks = new HashSet<>(); + + for (Corner c : course){ + marks.add(c.getCompoundMark()); + marks.add(c.getCompoundMark()); + } + + return marks; + } } From 7885b3fae21e34e79718b2769094294edd7d7bc4 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Wed, 2 Aug 2017 22:03:10 +1200 Subject: [PATCH 2/7] Loading course mark order from RaceXML - Mark order is read from the generated RaceXML and stored - Added .getNextMark() to get the next mark in the race - Added .equals() and .hashCode() for Marks Tags: #story[1124] (Task 1) --- .../java/seng302/gameServer/GameState.java | 10 +- .../java/seng302/models/map/CanvasMap.java | 2 - src/main/java/seng302/models/mark/Mark.java | 38 ++++++ .../java/seng302/models/mark/MarkOrder.java | 116 ++++++++++++++++++ .../java/seng302/server/messages/Message.java | 2 - .../java/seng302/models/MarkOrderTest.java | 95 ++++++++++++++ 6 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 src/main/java/seng302/models/mark/MarkOrder.java create mode 100644 src/test/java/seng302/models/MarkOrderTest.java diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 16eb8050..918cd210 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -1,13 +1,14 @@ package seng302.gameServer; +import seng302.client.ClientPacketParser; +import seng302.models.Player; +import seng302.models.Yacht; +import seng302.server.messages.BoatActionType; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import seng302.client.ClientPacketParser; -import seng302.models.Player; -import seng302.models.Yacht; -import seng302.server.messages.BoatActionType; /** * A Static class to hold information about the current state of the game (model) @@ -43,7 +44,6 @@ public class GameState implements Runnable { yachts = new HashMap<>(); new Thread(this).start(); - } public static String getHostIpAddress() { diff --git a/src/main/java/seng302/models/map/CanvasMap.java b/src/main/java/seng302/models/map/CanvasMap.java index e6a00cfa..de6403c7 100644 --- a/src/main/java/seng302/models/map/CanvasMap.java +++ b/src/main/java/seng302/models/map/CanvasMap.java @@ -7,8 +7,6 @@ import seng302.utilities.GeoPoint; import javax.net.ssl.HttpsURLConnection; import java.net.URL; -import java.lang.Math; - /** * CanvasMap retrieves a map image with given geo boundary from Google Map server. * By passing a rectangle like geo boundary, it returns a map image with the diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index 027bf6d3..362c77fd 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -145,4 +145,42 @@ public abstract class Mark { public int getCompoundMarkID() { return compoundMarkID; } + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + + if (!(other instanceof Mark)){ + return false; + } + + Mark otherMark = (Mark) other; + + if (otherMark.getLatitude() != getLatitude() || 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() { + return getName().hashCode() + getMarkType().hashCode() + + Integer.hashCode(getCompoundMarkID()) + Double.hashCode(getLatitude()) + + Double.hashCode(getLongitude()); + } } diff --git a/src/main/java/seng302/models/mark/MarkOrder.java b/src/main/java/seng302/models/mark/MarkOrder.java new file mode 100644 index 00000000..bf6f5f40 --- /dev/null +++ b/src/main/java/seng302/models/mark/MarkOrder.java @@ -0,0 +1,116 @@ +package seng302.models.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.models.stream.XMLParser; +import seng302.models.xml.Race; +import seng302.models.xml.XMLGenerator; +import seng302.server.messages.XMLMessageSubType; + +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.Collections; +import java.util.List; + +/** + * 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 previous The previous mark + * @return the next mark + * OR null if there is no next mark + */ + public Mark getNextMark(Mark previous){ + for (int i = 0; i < raceMarkOrder.size(); i++){ + Mark mark = raceMarkOrder.get(i); + + if (i + 1 >= raceMarkOrder.size()){ + return null; + } + + if (mark.equals(previous)){ + return raceMarkOrder.get(i+1); + } + } + + return null; + } + + /** + * 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){ + XMLParser xmlParser = new XMLParser(); + XMLParser.RaceXMLObject raceXMLObject; + + 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; + } + + xmlParser.constructXML(doc , XMLMessageSubType.RACE.getType()); + raceXMLObject = xmlParser.getRaceXML(); + + if (raceXMLObject != null){ + logger.debug("Loaded RaceXML for mark order"); + return raceXMLObject.getNonDupCompoundMarks(); + } + + 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/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index 398628ab..10afb8e5 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -1,7 +1,5 @@ package seng302.server.messages; -import java.io.IOException; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java new file mode 100644 index 00000000..97db018c --- /dev/null +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -0,0 +1,95 @@ +package seng302.models; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import seng302.models.mark.Mark; +import seng302.models.mark.MarkOrder; +import seng302.models.mark.SingleMark; + +import static junit.framework.TestCase.*; +import static org.junit.Assert.assertNotEquals; + +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 .equals() method on returns true on two marks that are equal + */ + @Test + public void testMarkEqualsTrue(){ + Mark mark1 = new SingleMark("asd", 1.1, 2.2, 1, 2); + Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); + + assertEquals(mark1, mark2); + } + + /** + * Test if .equals() method on returns false on two marks that are NOT equal + */ + @Test + public void testMarkNotEquals(){ + Mark mark1 = new SingleMark("asf", 1.1, 2.2, 2, 2); + Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); + + assertNotEquals(mark1, mark2); + } + + /** + * 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); + + assertEquals(null, markOrder.getNextMark(lastMark)); + } + + /** + * Test if .getNextMark() method on returns null if the mark does not exist in the race + */ + @Test + public void testNextMarkNotExists(){ + Mark someMark = new SingleMark("0-0-0-0-0-0-0", 0.0, 0.1, 2, 1); + + assertEquals(null, markOrder.getNextMark(someMark)); + } + + /** + * 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; + } + + Mark firstMark = markOrder.getMarkOrder().get(0); + + assertEquals(markOrder.getMarkOrder().get(1), markOrder.getNextMark(firstMark)); + } + + @AfterClass + public static void tearDown(){ + markOrder = null; + } +} From f9e6df46c145afddbc0a222586613f5ae768be16 Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 3 Aug 2017 13:23:43 +1200 Subject: [PATCH 3/7] Fixed issues caused by merge. #bug --- src/main/java/seng302/App.java | 6 +- .../java/seng302/gameServer/GameState.java | 20 +-- .../server/messages/BoatLocationMessage.java | 39 +++-- .../server/simulator/Simulator.java | 159 ------------------ .../java/seng302/model/mark/CompoundMark.java | 42 +++++ .../{models => model}/mark/MarkOrder.java | 52 +++--- .../seng302/visualiser/map/CanvasMap.java | 3 +- src/main/resources/server_config/race.xml | 2 +- .../java/seng302/models/MarkOrderTest.java | 59 ++++--- 9 files changed, 130 insertions(+), 252 deletions(-) delete mode 100644 src/main/java/seng302/gameServer/server/simulator/Simulator.java rename src/main/java/seng302/{models => model}/mark/MarkOrder.java (76%) diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index ab3ec79a..3b914350 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -7,7 +7,11 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; -import org.apache.commons.cli.*; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import seng302.model.PolarTable; diff --git a/src/main/java/seng302/gameServer/GameState.java b/src/main/java/seng302/gameServer/GameState.java index 8ab071c3..b49283c3 100644 --- a/src/main/java/seng302/gameServer/GameState.java +++ b/src/main/java/seng302/gameServer/GameState.java @@ -4,15 +4,9 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import seng302.client.ClientPacketParser; -import seng302.models.Player; -import seng302.models.Yacht; -import seng302.server.messages.BoatActionType; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import seng302.gameServer.server.messages.BoatActionType; +import seng302.model.Player; +import seng302.model.Yacht; /** * A Static class to hold information about the current state of the game (model) @@ -33,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 @@ -73,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 9f638beb..00000000 --- a/src/main/java/seng302/gameServer/server/simulator/Simulator.java +++ /dev/null @@ -1,159 +0,0 @@ -package seng302.server.simulator; - -import seng302.server.simulator.mark.Corner; -import seng302.server.simulator.mark.Mark; -import seng302.server.simulator.parsers.RaceParser; -import seng302.utilities.GeoPoint; -import seng302.utilities.GeoUtility; - -import java.util.HashSet; -import java.util.List; -import java.util.Observable; -import java.util.Set; -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; - } - - /** - * @return A list of marks in the race - */ - public Set getMarks(){ - Set marks = new HashSet<>(); - - for (Corner c : course){ - marks.add(c.getCompoundMark()); - marks.add(c.getCompoundMark()); - } - - return marks; - } -} 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/models/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java similarity index 76% rename from src/main/java/seng302/models/mark/MarkOrder.java rename to src/main/java/seng302/model/mark/MarkOrder.java index bf6f5f40..d3270c8f 100644 --- a/src/main/java/seng302/models/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -1,28 +1,29 @@ -package seng302.models.mark; +package seng302.model.mark; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; 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.models.stream.XMLParser; -import seng302.models.xml.Race; -import seng302.models.xml.XMLGenerator; -import seng302.server.messages.XMLMessageSubType; - -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.Collections; -import java.util.List; +import seng302.model.stream.xml.generator.Race; +import seng302.model.stream.xml.parser.RaceXMLData; +import seng302.utilities.XMLGenerator; +import seng302.utilities.XMLParser; /** * Class to hold the order of the marks in the race. */ public class MarkOrder { - private List raceMarkOrder; + private List raceMarkOrder; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); public MarkOrder(){ @@ -33,7 +34,7 @@ public class MarkOrder { * @return An ordered list of marks in the race * OR null if the mark order could not be loaded */ - public List getMarkOrder(){ + public List getMarkOrder(){ if (raceMarkOrder == null){ logger.warn("Race order accessed but not instantiated"); return null; @@ -48,9 +49,9 @@ public class MarkOrder { * @return the next mark * OR null if there is no next mark */ - public Mark getNextMark(Mark previous){ + public CompoundMark getNextMark(CompoundMark previous){ for (int i = 0; i < raceMarkOrder.size(); i++){ - Mark mark = raceMarkOrder.get(i); + CompoundMark mark = raceMarkOrder.get(i); if (i + 1 >= raceMarkOrder.size()){ return null; @@ -60,7 +61,6 @@ public class MarkOrder { return raceMarkOrder.get(i+1); } } - return null; } @@ -69,9 +69,7 @@ public class MarkOrder { * @param xml An AC35 RaceXML * @return An ordered list of marks in the race */ - private List loadRaceOrderFromXML(String xml){ - XMLParser xmlParser = new XMLParser(); - XMLParser.RaceXMLObject raceXMLObject; + private List loadRaceOrderFromXML(String xml){ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; @@ -84,13 +82,14 @@ public class MarkOrder { logger.error("Failed to read generated race XML"); return null; } + + RaceXMLData data = XMLParser.parseRace(doc); - xmlParser.constructXML(doc , XMLMessageSubType.RACE.getType()); - raceXMLObject = xmlParser.getRaceXML(); - - if (raceXMLObject != null){ + if (data != null){ logger.debug("Loaded RaceXML for mark order"); - return raceXMLObject.getNonDupCompoundMarks(); + List course = new ArrayList<>(data.getCompoundMarks().values()); + course.sort(Comparator.comparingInt(CompoundMark::getId)); + return course; } return null; @@ -110,7 +109,6 @@ public class MarkOrder { logger.error("Failed to generate raceXML (for race properties)"); return; } - raceMarkOrder = loadRaceOrderFromXML(raceXML); } } diff --git a/src/main/java/seng302/visualiser/map/CanvasMap.java b/src/main/java/seng302/visualiser/map/CanvasMap.java index 140a0b50..e79805e4 100644 --- a/src/main/java/seng302/visualiser/map/CanvasMap.java +++ b/src/main/java/seng302/visualiser/map/CanvasMap.java @@ -4,8 +4,7 @@ import java.net.URL; import javafx.geometry.Point2D; import javafx.scene.image.Image; import javax.net.ssl.HttpsURLConnection; -import java.net.URL; -import java.lang.Math; +import seng302.model.GeoPoint; /** * CanvasMap retrieves a map image with given geo boundary from Google Map server. diff --git a/src/main/resources/server_config/race.xml b/src/main/resources/server_config/race.xml index 5798aeb7..b2379fca 100644 --- a/src/main/resources/server_config/race.xml +++ b/src/main/resources/server_config/race.xml @@ -28,7 +28,7 @@ - + diff --git a/src/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java index 97db018c..ad9b700e 100644 --- a/src/test/java/seng302/models/MarkOrderTest.java +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -1,14 +1,13 @@ package seng302.models; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import seng302.models.mark.Mark; -import seng302.models.mark.MarkOrder; -import seng302.models.mark.SingleMark; - -import static junit.framework.TestCase.*; -import static org.junit.Assert.assertNotEquals; +import seng302.model.mark.CompoundMark; +import seng302.model.mark.MarkOrder; public class MarkOrderTest { private static MarkOrder markOrder; @@ -26,27 +25,27 @@ public class MarkOrderTest { assertTrue(markOrder != null); } - /** - * Test if .equals() method on returns true on two marks that are equal - */ - @Test - public void testMarkEqualsTrue(){ - Mark mark1 = new SingleMark("asd", 1.1, 2.2, 1, 2); - Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); - - assertEquals(mark1, mark2); - } - - /** - * Test if .equals() method on returns false on two marks that are NOT equal - */ - @Test - public void testMarkNotEquals(){ - Mark mark1 = new SingleMark("asf", 1.1, 2.2, 2, 2); - Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); - - assertNotEquals(mark1, mark2); - } +// /** +// * Test if .equals() method on returns true on two marks that are equal +// */ +// @Test +// public void testMarkEqualsTrue(){ +// M mark1 = new SingleMark("asd", 1.1, 2.2, 1, 2); +// Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); +// +// assertEquals(mark1, mark2); +// } +// +// /** +// * Test if .equals() method on returns false on two marks that are NOT equal +// */ +// @Test +// public void testMarkNotEquals(){ +// Mark mark1 = new SingleMark("asf", 1.1, 2.2, 2, 2); +// Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); +// +// assertNotEquals(mark1, mark2); +// } /** * Test if .getNextMark() returns null if it is called with the final mark in the race @@ -58,7 +57,7 @@ public class MarkOrderTest { return; } - Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); + CompoundMark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); assertEquals(null, markOrder.getNextMark(lastMark)); } @@ -68,7 +67,7 @@ public class MarkOrderTest { */ @Test public void testNextMarkNotExists(){ - Mark someMark = new SingleMark("0-0-0-0-0-0-0", 0.0, 0.1, 2, 1); + CompoundMark someMark = new CompoundMark(1, "something"); assertEquals(null, markOrder.getNextMark(someMark)); } @@ -83,7 +82,7 @@ public class MarkOrderTest { return; } - Mark firstMark = markOrder.getMarkOrder().get(0); + CompoundMark firstMark = markOrder.getMarkOrder().get(0); assertEquals(markOrder.getMarkOrder().get(1), markOrder.getNextMark(firstMark)); } From 454e9ac9f17839d4cf4fd30db093bffd3a7e0b29 Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 3 Aug 2017 16:29:12 +1200 Subject: [PATCH 4/7] Added an attribute to each yacht: 'DistanceToNextMark' This attribute is calculated at each update of the boat as prompted by the game state regularly Removed the lat and lng attribute from the Yacht class and replaced its usage with the GeoPoint object instead Removed redundant test files and merged GeoUtility and testGeoUtil test classes into one tags: #story[1124] #pair[hyi25, wmu16] --- src/main/java/seng302/model/Yacht.java | 46 +++++-- src/test/java/seng302/BoatTest.java | 35 ----- src/test/java/seng302/TestGeoUtils.java | 67 --------- .../server/simulator/GeoUtilityTest.java | 76 ----------- src/test/java/seng302/model/YachtTest.java | 55 ++++++++ .../java/seng302/model/mark/MarkTest.java | 45 ------ .../seng302/utilities/GeoUtilityTest.java | 128 ++++++++++++++++++ 7 files changed, 218 insertions(+), 234 deletions(-) delete mode 100644 src/test/java/seng302/BoatTest.java delete mode 100644 src/test/java/seng302/TestGeoUtils.java delete mode 100644 src/test/java/seng302/gameServer/server/simulator/GeoUtilityTest.java create mode 100644 src/test/java/seng302/model/YachtTest.java delete mode 100644 src/test/java/seng302/model/mark/MarkTest.java create mode 100644 src/test/java/seng302/utilities/GeoUtilityTest.java diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index aba80d1a..38104e9e 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. @@ -38,11 +38,10 @@ public class Yacht { private Long estimateTimeAtFinish; private Long timeTillNext; + private Double distanceToNextMark; private Long markRoundTime; private CompoundMark nextMark; private Double heading; - private Double lat; - private Double lon; private Integer legNumber = 0; //SERVER SIDE @@ -110,7 +109,28 @@ public class Yacht { } Double metersCovered = velocity * secondsElapsed; - location = getGeoCoordinate(location, heading, metersCovered); + location = GeoUtility.getGeoCoordinate(location, heading, metersCovered); + distanceToNextMark = calcDistanceToNextMark(); + } + + + /** + * 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) { @@ -326,19 +346,19 @@ public class Yacht { } public Double getLat() { - return lat; + return location.getLat(); } public void setLat(Double lat) { - this.lat = lat; + location.setLat(lat); } public Double getLon() { - return lon; + return location.getLng(); } public void setLon(Double lon) { - this.lon = lon; + location.setLng(lon); } public Double getHeading() { @@ -392,9 +412,13 @@ public class Yacht { this.velocity = velocity; } + public Double getDistanceToNextMark() { + return distanceToNextMark; + } + public void updateLocation (double lat, double lon, double heading, double velocity) { - this.lat = lat; - this.lon = lon; + location.setLat(lat); + location.setLng(lon); this.heading = heading; this.velocity = velocity; updateVelocityProperty(velocity); 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..e3144a55 --- /dev/null +++ b/src/test/java/seng302/model/YachtTest.java @@ -0,0 +1,55 @@ +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.setLat(57.670333); + yacht.setLon(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/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java new file mode 100644 index 00000000..b7ad8f85 --- /dev/null +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -0,0 +1,128 @@ +package seng302.utilities; + +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.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 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); + } + +} \ No newline at end of file From 874cdec654d242bc9561fa58cec64b5591a9f9af Mon Sep 17 00:00:00 2001 From: William Muir Date: Thu, 3 Aug 2017 17:39:07 +1200 Subject: [PATCH 5/7] Added booleans: has entered rounding zone, has crossed first line, has crossed second line All for purposes of checking mark rounding. Currently not yet finished tags: #story[1124] #pair[hyi25, wmu16] --- src/main/java/seng302/model/Yacht.java | 62 ++++++++++++------- .../java/seng302/utilities/GeoUtility.java | 5 +- src/test/java/seng302/model/YachtTest.java | 3 +- 3 files changed, 43 insertions(+), 27 deletions(-) diff --git a/src/main/java/seng302/model/Yacht.java b/src/main/java/seng302/model/Yacht.java index 38104e9e..d7cd1f9e 100644 --- a/src/main/java/seng302/model/Yacht.java +++ b/src/main/java/seng302/model/Yacht.java @@ -28,6 +28,8 @@ public class Yacht { void notifyLocation(Yacht yacht, double lat, double lon, double heading, double velocity); } + 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; @@ -37,9 +39,10 @@ public class Yacht { private String country; private Long estimateTimeAtFinish; - private Long timeTillNext; - private Double distanceToNextMark; + private Long lastMark; private Long markRoundTime; + private Double distanceToNextMark; + private Long timeTillNext; private CompoundMark nextMark; private Double heading; private Integer legNumber = 0; @@ -51,6 +54,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<>(); @@ -71,8 +79,13 @@ public class Yacht { this.country = country; this.sailIn = false; 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; } /** @@ -108,9 +121,16 @@ public class Yacht { } } - Double metersCovered = velocity * secondsElapsed; - location = GeoUtility.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 } @@ -345,20 +365,21 @@ public class Yacht { return nextMark; } - public Double getLat() { - return location.getLat(); + public GeoPoint getLocation() { + return location; } - public void setLat(Double lat) { + /** + * 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); - } - - public Double getLon() { - return location.getLng(); - } - - public void setLon(Double lon) { - location.setLng(lon); + location.setLng(lng); } public Double getHeading() { @@ -378,10 +399,6 @@ public class Yacht { return boatName; } - public GeoPoint getLocation() { - return location; - } - public void updateTimeSinceLastMarkProperty(long timeSinceLastMark) { this.timeSinceLastMarkProperty.set(timeSinceLastMark); } @@ -416,14 +433,13 @@ public class Yacht { return distanceToNextMark; } - public void updateLocation (double lat, double lon, double heading, double velocity) { - location.setLat(lat); - location.setLng(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); + yll.notifyLocation(this, lat, lng, heading, velocity); } } diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 54b0484a..0a49c776 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -96,8 +96,9 @@ public class GeoUtility { 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 + + /** + * 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 diff --git a/src/test/java/seng302/model/YachtTest.java b/src/test/java/seng302/model/YachtTest.java index e3144a55..7937b43c 100644 --- a/src/test/java/seng302/model/YachtTest.java +++ b/src/test/java/seng302/model/YachtTest.java @@ -31,8 +31,7 @@ public class YachtTest { "HaomingIsOk", "NZL"); - yacht.setLat(57.670333); - yacht.setLon(11.827833); + yacht.setLocation(57.670333, 11.827833); compoundMark = new CompoundMark(0, "HaomingsMark"); Mark subMark1 = new Mark("H", 57.671524, 11.844495, 0); From 281ce2d842442a89b081e0b5fa4d6d43e067a732 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Fri, 4 Aug 2017 13:20:50 +1200 Subject: [PATCH 6/7] Loading course mark order from RaceXML - Re-engineered code to work with the new marks - Fixed bug where race order wasn't correct (added RacePosition class to fix) - Rewrote tests to work with new RacePosition class Tags: #story[1124] (Task 1) --- .../java/seng302/model/mark/MarkOrder.java | 75 ++++++++++++------- .../java/seng302/model/mark/RacePosition.java | 55 ++++++++++++++ .../java/seng302/models/MarkOrderTest.java | 67 +++++++---------- 3 files changed, 131 insertions(+), 66 deletions(-) create mode 100644 src/main/java/seng302/model/mark/RacePosition.java diff --git a/src/main/java/seng302/model/mark/MarkOrder.java b/src/main/java/seng302/model/mark/MarkOrder.java index d3270c8f..ca2b4356 100644 --- a/src/main/java/seng302/model/mark/MarkOrder.java +++ b/src/main/java/seng302/model/mark/MarkOrder.java @@ -1,14 +1,5 @@ package seng302.model.mark; -import java.io.IOException; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -19,11 +10,21 @@ 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 List raceMarkOrder; private Logger logger = LoggerFactory.getLogger(MarkOrder.class); public MarkOrder(){ @@ -34,7 +35,7 @@ public class MarkOrder { * @return An ordered list of marks in the race * OR null if the mark order could not be loaded */ - public List getMarkOrder(){ + public List getMarkOrder(){ if (raceMarkOrder == null){ logger.warn("Race order accessed but not instantiated"); return null; @@ -45,23 +46,25 @@ public class MarkOrder { /** * Returns the mark in the race after the previous mark - * @param previous The previous mark - * @return the next mark - * OR null if there is no next mark + * @param position The current race position + * @return the next race position + * OR null if there is no position */ - public CompoundMark getNextMark(CompoundMark previous){ - for (int i = 0; i < raceMarkOrder.size(); i++){ - CompoundMark mark = raceMarkOrder.get(i); + public RacePosition getNextPosition(RacePosition position){ + Mark previousMark = position.getNextMark(); + Mark nextMark; - if (i + 1 >= raceMarkOrder.size()){ - return null; - } + if (position.getPositionIndex() + 1 >= raceMarkOrder.size() - 1){ + RacePosition nextRacePosition = new RacePosition(raceMarkOrder.size() - 1, null, previousMark); + nextRacePosition.setFinishingLeg(); - if (mark.equals(previous)){ - return raceMarkOrder.get(i+1); - } + return nextRacePosition; } - return null; + + Integer nextPositionIndex = position.getPositionIndex() + 1; + RacePosition nextRacePosition = new RacePosition(nextPositionIndex, raceMarkOrder.get(nextPositionIndex), previousMark); + + return nextRacePosition; } /** @@ -69,7 +72,7 @@ public class MarkOrder { * @param xml An AC35 RaceXML * @return An ordered list of marks in the race */ - private List loadRaceOrderFromXML(String xml){ + private List loadRaceOrderFromXML(String xml){ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; @@ -87,14 +90,32 @@ public class MarkOrder { if (data != null){ logger.debug("Loaded RaceXML for mark order"); - List course = new ArrayList<>(data.getCompoundMarks().values()); - course.sort(Comparator.comparingInt(CompoundMark::getId)); + 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 */ 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/test/java/seng302/models/MarkOrderTest.java b/src/test/java/seng302/models/MarkOrderTest.java index ad9b700e..8db06ec8 100644 --- a/src/test/java/seng302/models/MarkOrderTest.java +++ b/src/test/java/seng302/models/MarkOrderTest.java @@ -1,13 +1,14 @@ package seng302.models; -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertTrue; - import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import seng302.model.mark.CompoundMark; +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; @@ -25,28 +26,6 @@ public class MarkOrderTest { assertTrue(markOrder != null); } -// /** -// * Test if .equals() method on returns true on two marks that are equal -// */ -// @Test -// public void testMarkEqualsTrue(){ -// M mark1 = new SingleMark("asd", 1.1, 2.2, 1, 2); -// Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); -// -// assertEquals(mark1, mark2); -// } -// -// /** -// * Test if .equals() method on returns false on two marks that are NOT equal -// */ -// @Test -// public void testMarkNotEquals(){ -// Mark mark1 = new SingleMark("asf", 1.1, 2.2, 2, 2); -// Mark mark2 = new SingleMark("asd", 1.1, 2.2, 1, 2); -// -// assertNotEquals(mark1, mark2); -// } - /** * Test if .getNextMark() returns null if it is called with the final mark in the race */ @@ -57,19 +36,12 @@ public class MarkOrderTest { return; } - CompoundMark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); + Mark lastMark = markOrder.getMarkOrder().get(markOrder.getMarkOrder().size() - 1); + Integer lastIndex = markOrder.getMarkOrder().size() - 1; - assertEquals(null, markOrder.getNextMark(lastMark)); - } + RacePosition lastRacePosition = new RacePosition(lastIndex, lastMark, null); - /** - * Test if .getNextMark() method on returns null if the mark does not exist in the race - */ - @Test - public void testNextMarkNotExists(){ - CompoundMark someMark = new CompoundMark(1, "something"); - - assertEquals(null, markOrder.getNextMark(someMark)); + assertEquals(null, markOrder.getNextPosition(lastRacePosition).getNextMark()); } /** @@ -82,9 +54,26 @@ public class MarkOrderTest { return; } - CompoundMark firstMark = markOrder.getMarkOrder().get(0); + RacePosition firstRacePos = new RacePosition(0, markOrder.getMarkOrder().get(0), null); - assertEquals(markOrder.getMarkOrder().get(1), markOrder.getNextMark(firstMark)); + 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 From 43788bd15396c08d2fe8dcac2de978036ab9d67c Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Sun, 6 Aug 2017 12:36:17 +1200 Subject: [PATCH 7/7] Added a method to test if a geo point is located in a triangle which is formed by other three geo points. The method helps to check the mark rounding. Also unit tests have been done for this method. tags: #story[1124] --- .../java/seng302/utilities/GeoUtility.java | 272 ++++++++++-------- .../seng302/utilities/GeoUtilityTest.java | 25 ++ 2 files changed, 179 insertions(+), 118 deletions(-) diff --git a/src/main/java/seng302/utilities/GeoUtility.java b/src/main/java/seng302/utilities/GeoUtility.java index 0a49c776..9aa61b9d 100644 --- a/src/main/java/seng302/utilities/GeoUtility.java +++ b/src/main/java/seng302/utilities/GeoUtility.java @@ -5,146 +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)); - } + 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) { + * 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 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 + 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; - } - } + 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) { + /** + * 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)); + 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); + 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/utilities/GeoUtilityTest.java b/src/test/java/seng302/utilities/GeoUtilityTest.java index b7ad8f85..8382bb8c 100644 --- a/src/test/java/seng302/utilities/GeoUtilityTest.java +++ b/src/test/java/seng302/utilities/GeoUtilityTest.java @@ -1,7 +1,9 @@ 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; @@ -30,6 +32,7 @@ public class GeoUtilityTest { 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; @@ -125,4 +128,26 @@ public class GeoUtilityTest { 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