From cc54c92d5bf23595c06c2a7106afa52b78713ff4 Mon Sep 17 00:00:00 2001 From: fjc40 Date: Thu, 18 May 2017 14:49:56 +1200 Subject: [PATCH] Added Mock.Angle, Mock.Azimith, Mock.Bearing. These encapsulate the relevant angle type, to allow for better type safety (e.g., to avoid passing a bearing into an azimuth function). Added more comments to Mock.Boat. Moved calculateAzimuth to GPSCoordinate, from Boat. --- mock/src/main/java/seng302/Model/Angle.java | 65 ++++++++ mock/src/main/java/seng302/Model/Azimuth.java | 66 +++++++++ mock/src/main/java/seng302/Model/Bearing.java | 64 ++++++++ mock/src/main/java/seng302/Model/Boat.java | 139 +++++++++++------- .../java/seng302/Model/GPSCoordinate.java | 19 +++ mock/src/main/java/seng302/Model/Race.java | 23 ++- .../src/test/java/seng302/Model/BoatTest.java | 8 +- .../src/test/java/seng302/Model/RaceTest.java | 2 +- 8 files changed, 311 insertions(+), 75 deletions(-) create mode 100644 mock/src/main/java/seng302/Model/Angle.java create mode 100644 mock/src/main/java/seng302/Model/Azimuth.java create mode 100644 mock/src/main/java/seng302/Model/Bearing.java diff --git a/mock/src/main/java/seng302/Model/Angle.java b/mock/src/main/java/seng302/Model/Angle.java new file mode 100644 index 00000000..4dc027f8 --- /dev/null +++ b/mock/src/main/java/seng302/Model/Angle.java @@ -0,0 +1,65 @@ +package seng302.Model; + +/** + * This represents an angle. + * Has functions to return angle as either degrees or radians. + */ +public class Angle { + + /** + * The angle stored in this object. + * Degrees. + */ + private double degrees; + + + /** + * Ctor. + * Don't use this. + * This is protected because you need to use the static helper functions {@link #fromDegrees(double)} and {@link #fromRadians(double)} to construct an Angle object. + * + * @param degrees The value, in degrees, to initialize this Angle object with. + */ + protected Angle(double degrees) { + this.degrees = degrees; + } + + + /** + * Constructs an Angle object from an angle value in degrees. + * @param degrees Angle value in degrees. + * @return Angle object. + */ + public static Angle fromDegrees(double degrees) { + Angle angle = new Angle(degrees); + return angle; + } + + /** + * Constructs an Angle object from an angle value in radians. + * @param radians Angle value in radians. + * @return Angle object. + */ + public static Angle fromRadians(double radians) { + return Angle.fromDegrees(Math.toDegrees(radians)); + } + + + + /** + * Returns the value of this Angle object, in degrees. + * @return The value of this Angle object, in degrees. + */ + public double degrees() { + return this.degrees; + } + + + /** + * Returns the value of this Angle object, in radians. + * @return The value of this Angle object, in radians. + */ + public double radians() { + return Math.toRadians(this.degrees); + } +} diff --git a/mock/src/main/java/seng302/Model/Azimuth.java b/mock/src/main/java/seng302/Model/Azimuth.java new file mode 100644 index 00000000..0f252ebc --- /dev/null +++ b/mock/src/main/java/seng302/Model/Azimuth.java @@ -0,0 +1,66 @@ +package seng302.Model; + + + +/** + * Represents an azimuth. + * This is the angle between north and a target point. + * It has the interval [-180, 180), and clockwise from north is positive. + */ +public class Azimuth extends Angle{ + + + /** + * Ctor. + * This is protected because you need to use the static helper functions {@link #fromDegrees(double)} and {@link #fromRadians(double)} to construct an Azimuth object. + * + * @param degrees The value, in degrees, to initialize this Azimuth object with. + */ + protected Azimuth(double degrees) { + super(degrees); + } + + + /** + * Converts an angle in degrees into an angle in degrees in the correct interval for an azimuth - [-180, 180). + * E.g., converts -183 to 177, or converts 250 to -110, or converts 180 to -180. + * @param degrees Degree value to convert. + * @return Degree value in interval [-180, 180). + */ + public static double toAzimuthInterval(double degrees) { + + if (degrees >= 180) { + //Too large. + degrees -= 360; + } else if (degrees < -180) { + //Too small. + degrees += 360; + } + + return degrees; + } + + /** + * Constructs an Azimuth object from an angle value in degrees. + * @param degrees Azimuth value in degrees. + * @return Azimuth object. + */ + public static Azimuth fromDegrees(double degrees) { + //Ensure the angle is in the correct interval. + double degreesInInterval = Azimuth.toAzimuthInterval(degrees); + + Azimuth azimuth = new Azimuth(degreesInInterval); + return azimuth; + } + + /** + * Constructs an Azimuth object from an angle value in radians. + * @param radians Azimuth value in radians. + * @return Azimuth object. + */ + public static Azimuth fromRadians(double radians) { + return Azimuth.fromDegrees(Math.toDegrees(radians)); + } + + +} diff --git a/mock/src/main/java/seng302/Model/Bearing.java b/mock/src/main/java/seng302/Model/Bearing.java new file mode 100644 index 00000000..0f25b747 --- /dev/null +++ b/mock/src/main/java/seng302/Model/Bearing.java @@ -0,0 +1,64 @@ +package seng302.Model; + +/** + * Represents a bearing. Also known as a heading. + * This is the angle between north and a target point. + * Has the interval [0, 360), and clockwise from north is positive. + */ +public class Bearing extends Angle { + + + /** + * Ctor. + * This is protected because you need to use the static helper functions {@link #fromDegrees(double)} and {@link #fromRadians(double)} to construct a Bearing object. + * + * @param degrees The value, in degrees, to initialize this Bearing object with. + */ + protected Bearing(double degrees) { + super(degrees); + } + + + /** + * Converts an angle in degrees into an angle in degrees in the correct interval for a bearing - [0, 360). + * E.g., converts -183 to 177, or converts 425 to 65. + * @param degrees Degree value to convert. + * @return Degree value in interval [0, 360). + */ + public static double toBearingInterval(double degrees) { + + if (degrees >= 360) { + //Too large. + degrees -= 360; + } else if (degrees < 0) { + //Too small. + degrees += 360; + } + + return degrees; + } + + /** + * Constructs a Bearing object from an angle value in degrees. + * @param degrees Bearing value in degrees. + * @return Bearing object. + */ + public static Bearing fromDegrees(double degrees) { + //Ensure the angle is in the correct interval. + double degreesInInterval = Bearing.toBearingInterval(degrees); + + Bearing bearing = new Bearing(degreesInInterval); + return bearing; + } + + /** + * Constructs a Bearing object from an angle value in radians. + * @param radians Bearing value in radians. + * @return Bearing object. + */ + public static Bearing fromRadians(double radians) { + return Bearing.fromDegrees(Math.toDegrees(radians)); + } + + +} diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 09d2491e..c3a13878 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -5,34 +5,91 @@ import seng302.Constants; /** - * Created by esa46 on 1/05/17. + * This class represents a boat during a race. */ public class Boat { + /** + * The name of the boat/team. + */ private String name; - private double velocity; - private double scaledVelocity; + + /** + * The current speed of the boat, in knots. + * TODO knots + */ + private double currentSpeed; + + /** + * The current heading of the boat. + * TODO bearing + */ + private double heading; + + /** + * The current position of the boat. + */ + private GPSCoordinate currentPosition; + + /** + * The country or team abbreviation of the boat. + */ private String country; + + /** + * The source ID of the boat. + * This uniquely identifies an entity during a race. + */ private int sourceID; + + /** + * The leg of the race that the boat is currently on. + */ private Leg currentLeg; + + /** + * The distance, in meters, that the boat has travelled in the current leg. + * TODO meters + */ private double distanceTravelledInLeg; - private double distanceTravelledInTack; - private double distanceForTack; - private GPSCoordinate currentPosition; + + /** + * The time, in milliseconds, that has elapsed during the current leg. + * TODO milliseconds + */ + private double timeElapsedInCurrentLeg; + + /** + * The timestamp, in milliseconds, of when the boat finished the race. + * Is -1 if it hasn't finished. + * TODO milliseconds + */ private long timeFinished = -1; - private boolean started = false; - private double heading; + + /** + * Whether or not the boat has finished the race. + */ + private boolean hasFinishedRace = false; + /** - * This stores a boat's polars table. Can be used to calculate VMG. + * Whether or not the boat has started the race. + */ + private boolean hasStartedRace = false; + + /** + * This stores a boat's polars table. + * Can be used to calculate VMG. */ private Polars polars; /** * This stores the milliseconds since the boat has changed its tack, to allow for only updating the tack every X milliseconds. + * TODO milliseconds */ private long timeSinceTackChange = 0; + /** - * Boat initialiser which keeps all of the information of the boat. + * Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table. * * @param sourceID The id of the boat * @param name The name of the Boat. @@ -46,34 +103,24 @@ public class Boat { this.polars = polars; } - /** - * Calculates the azimuth of the travel via map coordinates of the raceMarkers - * - * @return the direction that the boat is heading towards in degrees (-180 to 180). - */ - public double calculateAzimuth(GPSCoordinate start, GPSCoordinate end) { - GeodeticCalculator calc = new GeodeticCalculator(); - calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude()); - calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude()); - return calc.getAzimuth(); - } /** - * Calculate the heding depending on the calculated azimuth value + * Calculate the heading depending on the calculated azimuth value * @return The azimuth value which is greater than 0 */ public double calculateHeading() { - double azimuth = this.calculateAzimuth(currentLeg.getStartCompoundMark().getAverageGPSCoordinate(), + double azimuth = GPSCoordinate.calculateAzimuth(currentLeg.getStartCompoundMark().getAverageGPSCoordinate(), currentLeg.getEndCompoundMark().getAverageGPSCoordinate()); - if (azimuth >= 0) { - return azimuth; - } else { + //Azimuth is in the interval (-180, 180), but we need a heading in the interval [0, 360). + if (azimuth < 0) { return azimuth + 360; } + + return azimuth; } /** @@ -81,7 +128,7 @@ public class Boat { * @return The azimuth value which is greater than 0 */ public double calculateBearingToDestination() { - double azimuth = this.calculateAzimuth(this.currentPosition, + double azimuth = GPSCoordinate.calculateAzimuth(this.currentPosition, currentLeg.getEndCompoundMark().getAverageGPSCoordinate()); if (azimuth >= 0) { @@ -125,21 +172,14 @@ public class Boat { this.name = name; } - public double getVelocity() { - return velocity; - } - - public void setVelocity(double velocity) { - this.velocity = velocity; + public double getCurrentSpeed() { + return currentSpeed; } - public double getScaledVelocity() { - return scaledVelocity; + public void setCurrentSpeed(double currentSpeed) { + this.currentSpeed = currentSpeed; } - public void setScaledVelocity(double scaledVelocity) { - this.scaledVelocity = scaledVelocity; - } public String getCountry() { return country; @@ -173,21 +213,6 @@ public class Boat { this.distanceTravelledInLeg = distanceTravelledInLeg; } - public double getDistanceTravelledInTack() { - return distanceTravelledInTack; - } - - public void setDistanceTravelledInTack(double distanceTravelledInTack) { - this.distanceTravelledInTack = distanceTravelledInTack; - } - - public double getDistanceForTack() { - return distanceForTack; - } - - public void setDistanceForTack(double distanceForTack) { - this.distanceForTack = distanceForTack; - } public GPSCoordinate getCurrentPosition() { return currentPosition; @@ -205,12 +230,12 @@ public class Boat { this.timeFinished = timeFinished; } - public boolean isStarted() { - return started; + public boolean isHasStartedRace() { + return hasStartedRace; } - public void setStarted(boolean started) { - this.started = started; + public void setHasStartedRace(boolean hasStartedRace) { + this.hasStartedRace = hasStartedRace; } public double getHeading() { diff --git a/mock/src/main/java/seng302/Model/GPSCoordinate.java b/mock/src/main/java/seng302/Model/GPSCoordinate.java index ee93bb19..da8fbd3c 100644 --- a/mock/src/main/java/seng302/Model/GPSCoordinate.java +++ b/mock/src/main/java/seng302/Model/GPSCoordinate.java @@ -1,5 +1,7 @@ package seng302.Model; +import org.geotools.referencing.GeodeticCalculator; + import java.util.Comparator; import java.util.List; @@ -152,5 +154,22 @@ public class GPSCoordinate { return aRatio >= bRatio; } + + + /** + * Calculates the azimuth between two points. + * This is an angle in the interval (-180, 180), with + * + * @return the direction that the boat is heading towards in degrees (-180 to 180). + */ + public static double calculateAzimuth(GPSCoordinate start, GPSCoordinate end) { + + GeodeticCalculator calc = new GeodeticCalculator(); + + calc.setStartingGeographicPoint(start.getLongitude(), start.getLatitude()); + calc.setDestinationGeographicPoint(end.getLongitude(), end.getLatitude()); + + return calc.getAzimuth(); + } } diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 460c1386..55b37605 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -143,10 +143,10 @@ public class Race implements Runnable { checkPosition(boat, totalTimeElapsed); } if (boat.getTimeFinished() > 0) { - mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getVelocity()); + mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getCurrentSpeed()); boatStatuses.add(new BoatStatus(boat.getSourceID(), BoatStatusEnum.FINISHED, boat.getCurrentLeg().getLegNumber())); } else { - mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getVelocity()); + mockOutput.parseBoatLocation(boat.getSourceID(), boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getHeading(), boat.getCurrentSpeed()); boatStatuses.add(new BoatStatus(boat.getSourceID(), boat.getCurrentLeg().getLegNumber() >= 0 ? BoatStatusEnum.RACING : BoatStatusEnum.DNF, boat.getCurrentLeg().getLegNumber())); } @@ -174,8 +174,7 @@ public class Race implements Runnable { if (boat != null) { Leg newLeg = new Leg(name, new Marker(startingPositions.get(i)), endMark, 0); boat.setCurrentLeg(newLeg); - boat.setVelocity(Constants.TEST_VELOCITIES[i]); - boat.setScaledVelocity(boat.getVelocity() * scaleFactor); + boat.setCurrentSpeed(Constants.TEST_VELOCITIES[i]);//TODO we should get rid of TEST_VELOCITIES since speed is based off of wind speed/angle. boat.setCurrentPosition(startingPositions.get(i)); boat.setHeading(boat.calculateHeading()); boat.setTimeSinceTackChange(999999);//We set a large time since tack change so that it calculates a new VMG when the simulation starts. @@ -248,14 +247,14 @@ public class Race implements Runnable { protected void updatePosition(Boat boat, int millisecondsElapsed) { //distanceTravelled = velocity (nm p hr) * time taken to update loop - double distanceTravelled = (boat.getVelocity() * this.scaleFactor * millisecondsElapsed) / 3600000; + double distanceTravelled = (boat.getCurrentSpeed() * this.scaleFactor * millisecondsElapsed) / 3600000; double totalDistanceTravelled = distanceTravelled + boat.getDistanceTravelledInLeg(); boolean finish = boat.getCurrentLeg().getName().equals("Finish"); if (!finish) { - double totalDistanceTravelledInTack = distanceTravelled + boat.getDistanceTravelledInTack(); + double totalDistanceTravelledInTack = distanceTravelled;//TODO FIX// + boat.getDistanceTravelledInTack(); double bound1 = (boat.calculateBearingToDestination() - 90) % 360; double bound2 = (boat.calculateBearingToDestination() + 90) % 360; @@ -295,15 +294,15 @@ public class Race implements Runnable { double angleBetweenDestAndHeading = boat.getHeading() - boat.calculateBearingToDestination(); double angleBetweenDestAndNewVMG = newHeading.getBearing() - boat.calculateBearingToDestination(); - double currentVelocity = cos(Math.toRadians(angleBetweenDestAndHeading)) * boat.getVelocity(); + double currentVelocity = cos(Math.toRadians(angleBetweenDestAndHeading)) * boat.getCurrentSpeed(); double vmgVelocity = cos(Math.toRadians(angleBetweenDestAndNewVMG)) * newHeading.getSpeed(); //System.out.println("boat " + boat.getAbbrev() + " current velocity is " + currentVelocity + " knots, possible VMG is " + vmgVelocity + " knots.");//TEMP DEBUG REMOVE if (vmgVelocity > currentVelocity) { boat.setHeading(newHeading.getBearing()); - boat.setVelocity(newHeading.getSpeed()); + boat.setCurrentSpeed(newHeading.getSpeed()); boat.setTimeSinceTackChange(0); - //System.out.println("boat " + boat.getAbbrev() + " has a new bearing " + boat.getHeading() + " degrees, and is " + boat.calculateDistanceToNextMarker() + " nautical miles to the next marker. Velocity to next marker is " + boat.getVelocity() + " knots.");//TEMP DEBUG REMOVE + //System.out.println("boat " + boat.getAbbrev() + " has a new bearing " + boat.getHeading() + " degrees, and is " + boat.calculateDistanceToNextMarker() + " nautical miles to the next marker. Velocity to next marker is " + boat.getCurrentSpeed() + " knots.");//TEMP DEBUG REMOVE } @@ -346,14 +345,12 @@ public class Race implements Runnable { boatsFinished++; boat.setTimeFinished(timeElapsed); boat.setTimeFinished(timeElapsed); - boat.setVelocity(0); - boat.setScaledVelocity(0); + boat.setCurrentSpeed(0); } else if (doNotFinish()) { boatsFinished++; boat.setTimeFinished(timeElapsed); boat.setCurrentLeg(new Leg("DNF", -1)); - boat.setVelocity(0); - boat.setScaledVelocity(0); + boat.setCurrentSpeed(0); } else { //Calculate how much the boat overshot the marker by boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg() - boat.getCurrentLeg().getDistance()); diff --git a/mock/src/test/java/seng302/Model/BoatTest.java b/mock/src/test/java/seng302/Model/BoatTest.java index b2a40914..64e78d27 100644 --- a/mock/src/test/java/seng302/Model/BoatTest.java +++ b/mock/src/test/java/seng302/Model/BoatTest.java @@ -22,7 +22,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(50, 0)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 0, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 0, 1e-8); } @Test @@ -31,7 +31,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(-50, 0)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 180, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 180, 1e-8); } @@ -42,7 +42,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(0, 50)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 90, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), 90, 1e-8); } @@ -52,7 +52,7 @@ public class BoatTest { Marker endMarker = new Marker(new GPSCoordinate(0, -50)); Leg start = new Leg("Start", startMarker, endMarker, 0); TEST_BOAT.setCurrentLeg(start); - assertEquals(TEST_BOAT.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), -90, 1e-8); + assertEquals(GPSCoordinate.calculateAzimuth(startMarker.getAverageGPSCoordinate(), endMarker.getAverageGPSCoordinate()), -90, 1e-8); } diff --git a/mock/src/test/java/seng302/Model/RaceTest.java b/mock/src/test/java/seng302/Model/RaceTest.java index da20a51f..e86473fd 100644 --- a/mock/src/test/java/seng302/Model/RaceTest.java +++ b/mock/src/test/java/seng302/Model/RaceTest.java @@ -119,7 +119,7 @@ public class RaceTest{ testBoat.setDistanceTravelledInLeg(1); testRace.checkPosition(testBoat, 1); - assertEquals(testBoat.getVelocity(), 0, 1e-8); + assertEquals(testBoat.getCurrentSpeed(), 0, 1e-8); } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { e.printStackTrace();