diff --git a/mock/src/main/java/seng302/DataInput/XMLReader.java b/mock/src/main/java/seng302/DataInput/XMLReader.java index c01891e3..a44b4183 100644 --- a/mock/src/main/java/seng302/DataInput/XMLReader.java +++ b/mock/src/main/java/seng302/DataInput/XMLReader.java @@ -50,8 +50,8 @@ public abstract class XMLReader { /** * Alternate constructor - * @param xmlFile - * @param isWholeFile + * @param xmlFile File to be read + * @param isWholeFile boolean value whether entire file is being passed */ public XMLReader(String xmlFile, Boolean isWholeFile) { @@ -91,9 +91,9 @@ public abstract class XMLReader { /** * Get the contents of the XML FILe. - * @param document - * @return - * @throws TransformerException + * @param document holds all xml information + * @return String representation of document + * @throws TransformerException when document is malformed, and cannot be turned into a string */ public static String getContents(Document document) throws TransformerException { DOMSource source = new DOMSource(document); diff --git a/mock/src/main/java/seng302/MockOutput.java b/mock/src/main/java/seng302/MockOutput.java index 4bb23af1..5ec96d2a 100644 --- a/mock/src/main/java/seng302/MockOutput.java +++ b/mock/src/main/java/seng302/MockOutput.java @@ -108,10 +108,11 @@ public class MockOutput implements Runnable * @param lon longitude of boat * @param heading heading of boat * @param speed speed of boat + * @param time historical time of race */ - public synchronized void parseBoatLocation(int sourceID, double lat, double lon, double heading, double speed){ + public synchronized void parseBoatLocation(int sourceID, double lat, double lon, double heading, double speed, long time){ - BoatLocation boatLocation = new BoatLocation(sourceID, lat, lon, boatLocationSequenceNumber, heading, speed); + BoatLocation boatLocation = new BoatLocation(sourceID, lat, lon, boatLocationSequenceNumber, heading, speed, time); //iterates the sequence number boatLocationSequenceNumber++; diff --git a/mock/src/main/java/seng302/Model/Boat.java b/mock/src/main/java/seng302/Model/Boat.java index 7e9fc9b6..1cc95a1e 100644 --- a/mock/src/main/java/seng302/Model/Boat.java +++ b/mock/src/main/java/seng302/Model/Boat.java @@ -81,6 +81,8 @@ public class Boat { */ private long timeSinceTackChange = 0; + private long estimatedTime = 0; + /** * Constructs a boat object with a given sourceID, name, country/team abbreviation, and polars table. @@ -442,4 +444,11 @@ public class Boat { return distanceTravelledMeters; } + public long getEstimatedTime() { + return estimatedTime; + } + + public void setEstimatedTime(long estimatedTime) { + this.estimatedTime = estimatedTime; + } } diff --git a/mock/src/main/java/seng302/Model/Race.java b/mock/src/main/java/seng302/Model/Race.java index 76698152..775091c7 100644 --- a/mock/src/main/java/seng302/Model/Race.java +++ b/mock/src/main/java/seng302/Model/Race.java @@ -18,6 +18,8 @@ import java.util.Iterator; import java.util.List; import java.util.Random; +import static java.lang.Math.cos; + /** * Represents a yacht race. @@ -181,7 +183,7 @@ public class Race implements Runnable { */ private void parseIndividualMark(Mark mark) { - this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0); + this.mockOutput.parseBoatLocation(mark.getSourceID(), mark.getPosition().getLatitude(), mark.getPosition().getLongitude(),0,0, totalTimeElapsed+startTime); } @@ -210,7 +212,8 @@ public class Race implements Runnable { boat.getCurrentPosition().getLatitude(), boat.getCurrentPosition().getLongitude(), boat.getBearing().degrees(), - boat.getCurrentSpeed() + boat.getCurrentSpeed(), + startTime + totalTimeElapsed ); } @@ -261,7 +264,7 @@ public class Race implements Runnable { //Add each boat status to the status list. for (Boat boat : boats) { - BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber()); + BoatStatus boatStatus = new BoatStatus(boat.getSourceID(), boat.getStatus(), boat.getCurrentLeg().getLegNumber(), boat.getEstimatedTime()); boatStatuses.add(boatStatus); } @@ -379,26 +382,29 @@ public class Race implements Runnable { } else { //Otherwise, the race is over! + System.out.println("test"); raceFinished.start(); setRaceStatusEnum(RaceStatusEnum.FINISHED); this.stop(); } - // Change wind direction - changeWindDir(); + if (getNumberOfActiveBoats() != 0) { + // Change wind direction + changeWindDir(); - //Parse the boat locations. - parseBoatLocations(); + //Parse the boat locations. + parseBoatLocations(); - //Parse the marks. - parseMarks(); + //Parse the marks. + parseMarks(); - //Parse the race status. - parseRaceStatus(); + //Parse the race status. + parseRaceStatus(); - //Update the last frame time. - this.lastFrameTime = currentTime; + //Update the last frame time. + this.lastFrameTime = currentTime; + } } }; @@ -409,7 +415,7 @@ public class Race implements Runnable { int iters = 0; @Override public void handle(long now) { - RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, null); + RaceStatus raceStatus = new RaceStatus(System.currentTimeMillis(), raceId, 4, startTime, 0, 2300, 2, new ArrayList<>()); mockOutput.parseRaceStatus(raceStatus); if (iters > 500){ mockOutput.stop(); @@ -625,10 +631,11 @@ public class Race implements Runnable { if (!willStayInsideCourse) { boat.setVMG(newVMG); } - } } - + + this.updateEstimatedTime(boat); + //Check the boats position (update leg and stuff). this.checkPosition(boat, totalTimeElapsed); @@ -757,7 +764,6 @@ public class Race implements Runnable { boat.setTimeFinished(timeElapsed); boat.setCurrentSpeed(0); boat.setStatus(BoatStatusEnum.FINISHED); - } else if (doNotFinish()) { //Boat has pulled out of race. boat.setTimeFinished(timeElapsed); @@ -909,4 +915,16 @@ public class Race implements Runnable { protected int getWind(){ return windDir; } + + /** + * Updates the boat's estimated time to next mark if positive + * @param boat to estimate time given its velocity + */ + private void updateEstimatedTime(Boat boat) { + double velocityToMark = boat.getCurrentSpeed() * cos(boat.getBearing().radians() - boat.calculateBearingToNextMarker().radians()) / Constants.KnotsToMMPerSecond; + if (velocityToMark > 0) { + long timeFromNow = (long)(1000*boat.calculateDistanceToNextMarker()/velocityToMark); + boat.setEstimatedTime(startTime + totalTimeElapsed + timeFromNow); + } + } } diff --git a/mock/src/test/java/seng302/Model/RaceTest.java b/mock/src/test/java/seng302/Model/RaceTest.java index 7365e320..f1bf440f 100644 --- a/mock/src/test/java/seng302/Model/RaceTest.java +++ b/mock/src/test/java/seng302/Model/RaceTest.java @@ -57,7 +57,7 @@ public class RaceTest{ Race testRace = new Race(raceDataSource, mockOutput); testRace.initialiseBoats(); testRace.countdownTimer.handle(1); - verify(mockOutput, atLeast(boatDataSource.getBoats().size())).parseBoatLocation(anyInt(), anyDouble(), anyDouble(), anyDouble(), anyDouble()); + verify(mockOutput, atLeast(boatDataSource.getBoats().size())).parseBoatLocation(anyInt(), anyDouble(), anyDouble(), anyDouble(), anyDouble(), anyLong()); } catch (ParserConfigurationException | IOException | SAXException | ParseException | StreamedCourseXMLException e) { e.printStackTrace(); diff --git a/network/src/main/java/seng302/Networking/Messages/BoatLocation.java b/network/src/main/java/seng302/Networking/Messages/BoatLocation.java index fe7f2161..9ebde2ef 100644 --- a/network/src/main/java/seng302/Networking/Messages/BoatLocation.java +++ b/network/src/main/java/seng302/Networking/Messages/BoatLocation.java @@ -152,11 +152,11 @@ public class BoatLocation extends AC35Data { this.rudderAngle = rudderAngle; } - public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed) { + public BoatLocation(int sourceID, double lat, double lon, long sequenceNumber, double heading, double boatSpeed, long time) { super(MessageType.BOATLOCATION); this.messageVersionNumber = (byte) 1; - this.time = System.currentTimeMillis(); + this.time = time; this.sourceID = sourceID; this.sequenceNumber = sequenceNumber; this.deviceType = 1; diff --git a/network/src/main/java/seng302/Networking/Messages/BoatStatus.java b/network/src/main/java/seng302/Networking/Messages/BoatStatus.java index dacb5d5e..291bc50b 100644 --- a/network/src/main/java/seng302/Networking/Messages/BoatStatus.java +++ b/network/src/main/java/seng302/Networking/Messages/BoatStatus.java @@ -27,14 +27,14 @@ public class BoatStatus { } - public BoatStatus(int sourceID, BoatStatusEnum boatStatusEnum, int legNum) { + public BoatStatus(int sourceID, BoatStatusEnum boatStatusEnum, int legNum, long estTimeAtNextMark) { this.sourceID = sourceID; this.boatStatus = boatStatusEnum.getValue(); this.legNumber = ByteConverter.intToBytes(legNum)[0]; this.numPenaltiesAwarded = 0; this.numPenaltiesServed = 0; this.estTimeAtFinish = 0; - this.estTimeAtNextMark = 0; + this.estTimeAtNextMark = estTimeAtNextMark; } diff --git a/visualiser/src/main/java/seng302/Controllers/RaceController.java b/visualiser/src/main/java/seng302/Controllers/RaceController.java index 3082b325..5dfcab13 100644 --- a/visualiser/src/main/java/seng302/Controllers/RaceController.java +++ b/visualiser/src/main/java/seng302/Controllers/RaceController.java @@ -37,6 +37,7 @@ public class RaceController extends Controller { private static String speedCheckAnno = "speed"; private static String pathCheckAnno = "path"; private static String timeCheckAnno = "time"; + private static String estTimeCheckAnno = "est time"; private static int noBtn = 0; private static int hideBtn = 1; @@ -65,6 +66,7 @@ public class RaceController extends Controller { @FXML CheckBox showAbbrev; @FXML CheckBox showSpeed; @FXML CheckBox showTime; + @FXML CheckBox showEstTime; @FXML Label timer; @FXML Label FPS; @FXML Label timeZone; @@ -356,6 +358,7 @@ public class RaceController extends Controller { annoShownBeforeHide.put(pathCheckAnno, showBoatPath.isSelected()); annoShownBeforeHide.put(speedCheckAnno, showSpeed.isSelected()); annoShownBeforeHide.put(timeCheckAnno, showTime.isSelected()); + annoShownBeforeHide.put(estTimeCheckAnno, showEstTime.isSelected()); } /** @@ -369,7 +372,8 @@ public class RaceController extends Controller { importantAnno.put(abbrevCheckAnno, false); importantAnno.put(pathCheckAnno, false); importantAnno.put(speedCheckAnno, false); - importantAnno.put(timeCheckAnno, true); + importantAnno.put(timeCheckAnno, false); + importantAnno.put(estTimeCheckAnno, false); annoShownBeforeHide = new HashMap<>(); annoShownBeforeHide.put(nameCheckAnno, true); @@ -377,6 +381,7 @@ public class RaceController extends Controller { annoShownBeforeHide.put(pathCheckAnno, true); annoShownBeforeHide.put(speedCheckAnno, true); annoShownBeforeHide.put(timeCheckAnno, true); + annoShownBeforeHide.put(estTimeCheckAnno, true); //listener for show name in annotation showName.selectedProperty().addListener((ov, old_val, new_val) -> { if (old_val != new_val) { @@ -468,6 +473,23 @@ public class RaceController extends Controller { prevBtnChecked = noBtn; raceMap.update(); }); + showEstTime.selectedProperty().addListener((ov, old_val, new_val) -> { + if (old_val != new_val) { + raceMap.toggleAnnoEstTime(); + } + if (buttonChecked != hideBtn) { + if (prevBtnChecked == hideBtn && buttonChecked != showBtn){ + storeCurrentAnnotationState(); + } else { + annoShownBeforeHide.put(estTimeCheckAnno, showEstTime.isSelected()); + } + if (buttonChecked == noBtn) { + annotationGroup.selectToggle(showAnnoRBTN); + } + } + prevBtnChecked = noBtn; + raceMap.update(); + }); //listener to save currently selected annotation saveAnno.setOnAction(event -> { presetAnno.clear(); @@ -476,6 +498,7 @@ public class RaceController extends Controller { presetAnno.add(showSpeed.isSelected()); presetAnno.add(showBoatPath.isSelected()); presetAnno.add(showTime.isSelected()); + presetAnno.add(showEstTime.isSelected()); }); //listener for hiding hideAnnoRBTN.selectedProperty().addListener((ov, old_val, new_val) ->{ @@ -486,6 +509,7 @@ public class RaceController extends Controller { showBoatPath.setSelected(false); showSpeed.setSelected(false); showTime.setSelected(false); + showEstTime.setSelected(false); annotationGroup.selectToggle(hideAnnoRBTN); raceMap.update(); buttonChecked = noBtn; @@ -499,6 +523,7 @@ public class RaceController extends Controller { showBoatPath.setSelected(annoShownBeforeHide.get(pathCheckAnno)); showSpeed.setSelected(annoShownBeforeHide.get(speedCheckAnno)); showTime.setSelected(annoShownBeforeHide.get(timeCheckAnno)); + showEstTime.setSelected(annoShownBeforeHide.get(estTimeCheckAnno)); raceMap.update(); buttonChecked = noBtn; prevBtnChecked = showBtn; @@ -511,6 +536,7 @@ public class RaceController extends Controller { showSpeed.setSelected(true); showBoatPath.setSelected(false); showTime.setSelected(false); + showEstTime.setSelected(false); annotationGroup.selectToggle(partialAnnoRBTN); raceMap.update(); buttonChecked = noBtn; @@ -525,6 +551,7 @@ public class RaceController extends Controller { showSpeed.setSelected(presetAnno.get(2)); showBoatPath.setSelected(presetAnno.get(3)); showTime.setSelected(presetAnno.get(4)); + showEstTime.setSelected(presetAnno.get(5)); annotationGroup.selectToggle(importantAnnoRBTN); raceMap.update(); } diff --git a/visualiser/src/main/java/seng302/Controllers/StartController.java b/visualiser/src/main/java/seng302/Controllers/StartController.java index 87996f76..db8091ff 100644 --- a/visualiser/src/main/java/seng302/Controllers/StartController.java +++ b/visualiser/src/main/java/seng302/Controllers/StartController.java @@ -98,7 +98,6 @@ public class StartController extends Controller implements Observer { raceStat = visualiserInput.getRaceStatus().getRaceStatus(); raceStatusLabel.setText("Race Status: " + visualiserInput.getRaceStatus().getRaceStatus()); if (raceStat==2 || raceStat == 3) { - System.out.println("countdown finished!");//TEMP DEBUG REMOVE stop(); startWrapper.setVisible(false); diff --git a/visualiser/src/main/java/seng302/Mock/StreamedRace.java b/visualiser/src/main/java/seng302/Mock/StreamedRace.java index 0b867f4d..71b9c68a 100644 --- a/visualiser/src/main/java/seng302/Mock/StreamedRace.java +++ b/visualiser/src/main/java/seng302/Mock/StreamedRace.java @@ -114,16 +114,17 @@ public class StreamedRace implements Runnable { * Updates the boat's gps coordinates * * @param boat to be updated - * @param millisecondsElapsed time since last update */ - private void updatePosition(Boat boat, int millisecondsElapsed) { + private void updatePosition(Boat boat) { int sourceID = boat.getSourceID(); BoatLocation boatLocation = visualiserInput.getBoatLocationMessage(sourceID); + BoatStatus boatStatus = visualiserInput.getBoatStatusMessage(sourceID); if(boatLocation != null) { double lat = boatLocation.getLatitudeDouble(); double lon = boatLocation.getLongitudeDouble(); boat.setCurrentPosition(new GPSCoordinate(lat, lon)); boat.setHeading(boatLocation.getHeadingDegrees()); + boat.setEstTime(convertEstTime(boatStatus.getEstTimeAtNextMark(), boatLocation.getTime())); double MMPS_TO_KN = 0.001944; boat.setVelocity(boatLocation.getBoatSOG() * MMPS_TO_KN); } @@ -198,19 +199,20 @@ public class StreamedRace implements Runnable { public void handle(long arg0) { totalTimeElapsed = System.currentTimeMillis() - timeRaceStarted; - for (Boat boat : startingBoats) { - if (boat != null && !boat.isFinished()) { - updatePosition(boat, Math.round(1000 / lastFPS) > 20 ? 15 : Math.round(1000 / lastFPS)); - checkPosition(boat, totalTimeElapsed); + for (Boat boat : startingBoats) { + if (boat != null && !boat.isFinished()) { + updatePosition(boat); + checkPosition(boat, totalTimeElapsed); + } + } - } - for (Marker mark: boatMarkers){ - if (mark != null){ - updateMarker(mark); + for (Marker mark: boatMarkers){ + if (mark != null){ + updateMarker(mark); + } } - } - //System.out.println(boatsFinished + ":" + startingBoats.size()); - if (visualiserInput.getRaceStatus().isFinished()){ + + if (visualiserInput.getRaceStatus().isFinished()) { controller.finishRace(startingBoats); stop(); } @@ -258,4 +260,18 @@ public class StreamedRace implements Runnable { return startingBoats; } + /** + * Takes an estimated time an event will occur, and converts it to the number of seconds before the event will occur. + * + * @param estTimeMillis + * @return int difference between time the race started and the estimated time + */ + private int convertEstTime(long estTimeMillis, long currentTime) { + + long estElapsedMillis = estTimeMillis - currentTime; + int estElapsedSecs = Math.round(estElapsedMillis/1000); + return estElapsedSecs; + + } + } diff --git a/visualiser/src/main/java/seng302/Model/Boat.java b/visualiser/src/main/java/seng302/Model/Boat.java index a99ed148..c9765107 100644 --- a/visualiser/src/main/java/seng302/Model/Boat.java +++ b/visualiser/src/main/java/seng302/Model/Boat.java @@ -29,6 +29,7 @@ public class Boat { private boolean started = false; private boolean dnf = false; private int sourceID; + private int estTime; private final Queue track = new ConcurrentLinkedQueue<>(); private long nextValidTime = 0; @@ -248,4 +249,21 @@ public class Boat { public void setTimeSinceLastMark(ZonedDateTime timeSinceLastMark) { this.timeSinceLastMark = timeSinceLastMark; } + + public void setEstTime(int estTime) { this.estTime = estTime; } + + public String getFormattedEstTime() { + if (estTime < 0) { + return " -"; + } + if (estTime <= 60) { + return " " + estTime + "s"; + } else { + int seconds = estTime % 60; + int minutes = (estTime - seconds) / 60; + return String.format(" %dm %ds", minutes, seconds); + } + + + } } diff --git a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java b/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java index 5c26b293..9dfab1a9 100644 --- a/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java +++ b/visualiser/src/main/java/seng302/Model/ResizableRaceCanvas.java @@ -31,6 +31,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { private boolean annoAbbrev = true; private boolean annoSpeed = true; private boolean annoPath = true; + private boolean annoEstTime = true; private boolean annoTimeSinceLastMark = true; private List colours; private final List markers; @@ -177,7 +178,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { * @param coordinate coordinate the text appears * @param timeSinceLastMark time since the last mark was passed */ - private void displayText(String name, String abbrev, double speed, GraphCoordinate coordinate, ZonedDateTime timeSinceLastMark) { + private void displayText(String name, String abbrev, double speed, GraphCoordinate coordinate, String estTime, ZonedDateTime timeSinceLastMark) { String text = ""; //Check name toggle value if (annoName){ @@ -191,10 +192,13 @@ public class ResizableRaceCanvas extends ResizableCanvas { if (annoSpeed){ text += String.format("%.2fkn ", speed); } + if (annoEstTime) { + text += estTime; + } //Check time since last mark toggle value if(annoTimeSinceLastMark){ Duration timeSince = Duration.between(timeSinceLastMark, raceClock.getTime()); - text += String.format("%d", timeSince.getSeconds()); + text += String.format(" %ds ", timeSince.getSeconds()); } //String text = String.format("%s, %2$.2fkn", name, speed); long xCoord = coordinate.getX() + 20; @@ -277,6 +281,10 @@ public class ResizableRaceCanvas extends ResizableCanvas { annoPath = !annoPath; } + public void toggleAnnoEstTime() { + annoEstTime = !annoEstTime; + } + /** * Toggle boat time display in annotation */ @@ -320,7 +328,7 @@ public class ResizableRaceCanvas extends ResizableCanvas { if (Duration.between(boat.getTimeSinceLastMark(), raceClock.getTime()).getSeconds() < 0) { boat.setTimeSinceLastMark(raceClock.getTime()); } - displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()), boat.getTimeSinceLastMark()); + displayText(boat.toString(), boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()), boat.getFormattedEstTime(), boat.getTimeSinceLastMark()); //TODO this needs to be fixed. drawTrack(boat, boatColours.get(sourceID)); } diff --git a/visualiser/src/main/java/seng302/VisualiserInput.java b/visualiser/src/main/java/seng302/VisualiserInput.java index 4c3df9d9..1c552904 100644 --- a/visualiser/src/main/java/seng302/VisualiserInput.java +++ b/visualiser/src/main/java/seng302/VisualiserInput.java @@ -88,6 +88,10 @@ public class VisualiserInput implements Runnable { return boatLocationMap.get(sourceID); } + public BoatStatus getBoatStatusMessage(int sourceID) { + return boatStatusMap.get(sourceID); + } + /** * Calculates the time since last heartbeat, in milliseconds. * @return Time since last heartbeat, in milliseconds.. diff --git a/visualiser/src/main/resources/scenes/race.fxml b/visualiser/src/main/resources/scenes/race.fxml index 900b731d..5f11b477 100644 --- a/visualiser/src/main/resources/scenes/race.fxml +++ b/visualiser/src/main/resources/scenes/race.fxml @@ -1,12 +1,24 @@ - - - - - - + + + + + + + + + + + + + + + + + + @@ -24,7 +36,7 @@ - + @@ -33,13 +45,14 @@ - -