Merge remote-tracking branch 'origin/develop' into issue#1_wakes_3.0

# Conflicts:
#	src/main/java/seng302/App.java
#	src/main/java/seng302/models/BoatGroup.java
This commit is contained in:
Calum
2017-05-17 17:27:34 +12:00
27 changed files with 1387 additions and 644 deletions
+6 -8
View File
@@ -9,8 +9,8 @@ import seng302.models.parsers.StreamParser;
import seng302.models.parsers.StreamReceiver;
import seng302.server.ServerThread;
public class App extends Application
{
public class App extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
@@ -39,15 +39,15 @@ public class App extends Application
e.printStackTrace();
}
if (args.length == 1 && args[0].equals("-standalone")){
if (args.length == 1 && args[0].equals("-standalone")) {
return;
}
if (args.length == 3 && args[0].equals("-server")){
if (args.length == 3 && args[0].equals("-server")) {
sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream");
} else if(args.length == 2 && args[0].equals("-server")){
} else if (args.length == 2 && args[0].equals("-server")) {
switch (args[1]) {
case "internal":
sr = new StreamReceiver("localhost", 4949, "RaceStream");
@@ -63,7 +63,7 @@ public class App extends Application
//Change the StreamReceiver in this else block to change the default data source.
else{
// sr = new StreamReceiver("localhost", 4949, "RaceStream");
sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
}
sr.start();
@@ -72,8 +72,6 @@ public class App extends Application
launch(args);
}
}
@@ -1,31 +1,31 @@
package seng302.controllers;
import javafx.animation.*;
import javafx.animation.AnimationTimer;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import seng302.models.*;
import seng302.models.BoatGroup;
import seng302.models.Colors;
import seng302.models.RaceObject;
import seng302.models.Yacht;
import seng302.models.mark.*;
import seng302.models.parsers.StreamParser;
import seng302.models.parsers.StreamReceiver;
import seng302.models.parsers.packets.BoatPositionPacket;
import seng302.models.parsers.XMLParser;
import seng302.models.parsers.XMLParser.RaceXMLObject.CompoundMark;
import seng302.models.parsers.XMLParser.RaceXMLObject.Limit;
import seng302.models.mark.Mark;
import seng302.models.parsers.packets.BoatPositionPacket;
import java.text.DecimalFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue;
/**
@@ -240,6 +240,17 @@ public class CanvasController {
}
}
}
checkForCourseChanges();
}
private void checkForCourseChanges() {
if (StreamParser.isNewRaceXmlReceived()){
gc.setFill(Color.SKYBLUE);
gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
gc.restore();
addRaceBorder();
canvas.toBack();
}
}
private void move(long id, RaceObject raceObject){
@@ -345,6 +356,8 @@ public class CanvasController {
* Calculates x and y location for every marker that fits it to the canvas the race will be drawn on.
*/
private void fitMarksToCanvas() {
//Check is called once to avoid unnecessarily change the course limits once the race is running
StreamParser.isNewRaceXmlReceived();
findMinMaxPoint();
double minLonToMaxLon = scaleRaceExtremities();
calculateReferencePointLocation(minLonToMaxLon);
+10 -141
View File
@@ -1,168 +1,37 @@
package seng302.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.models.Yacht;
import seng302.models.parsers.StreamParser;
import seng302.models.parsers.XMLParser;
import java.io.IOException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
public class Controller implements Initializable {
@FXML
private AnchorPane contentPane;
@FXML
private Label timeTillLive;
@FXML
private Button streamButton;
@FXML
private Button switchToRaceViewButton;
@FXML
private TableView<Yacht> teamList;
@FXML
private TableColumn<Yacht, String> boatNameCol;
@FXML
private TableColumn<Yacht, String> shortNameCol;
@FXML
private TableColumn<Yacht, String> countryCol;
@FXML
private TableColumn<Yacht, String> posCol;
@FXML
private Label realTime;
private XMLParser xmlParser;
private void setContentPane(String jfxUrl){
try{
private void setContentPane(String jfxUrl) {
try {
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
}
catch(javafx.fxml.LoadException e){
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren()
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
} catch (javafx.fxml.LoadException e) {
System.err.println(e.getCause());
}
catch(IOException e){
} catch (IOException e) {
System.err.println(e);
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
//DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
//format.setTimeZone(TimeZone.getTimeZone("GMT-8"));
//realTime.setText(format.format(new Date()));
}
/**
* Running a timer to update the livestream status on welcome screen. Update interval is 1 second.
*/
public void startStream() {
if (StreamParser.isStreamStatus()) {
xmlParser = StreamParser.getXmlObject();
streamButton.setVisible(false);
realTime.setVisible(true);
timeTillLive.setVisible(true);
timeTillLive.setTextFill(Color.GREEN);
timeTillLive.setText("Connecting...");
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> {
if (StreamParser.isRaceStarted()) {
switchToRaceView();
timer.cancel();
}
if (StreamParser.isRaceFinished()) {
realTime.setText(StreamParser.getCurrentTimeString());
timeTillLive.setTextFill(Color.RED);
timeTillLive.setText("Race finished! Waiting for new race...");
switchToRaceViewButton.setDisable(true);
} else if (StreamParser.getTimeSinceStart() > 0) {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.RED);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = "-" + timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
} else {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.BLACK);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
}
});
}
}, 0, 1000);
} else {
timeTillLive.setText("Stream not available.");
timeTillLive.setTextFill(Color.RED);
}
}
public void switchToRaceView() {
setContentPane("/views/RaceView.fxml");
}
private void updateTeamList() {
ObservableList<Yacht> data = FXCollections.observableArrayList();
teamList.setItems(data);
boatNameCol.setCellValueFactory(
new PropertyValueFactory<>("boatName")
);
shortNameCol.setCellValueFactory(
new PropertyValueFactory<>("shortName")
);
countryCol.setCellValueFactory(
new PropertyValueFactory<>("country")
);
posCol.setCellValueFactory(
new PropertyValueFactory<>("position")
);
// if (StreamParser.isRaceStarted()) {
data.addAll(StreamParser.getBoatsPos().values());
// } else {
// for (Yacht boat : StreamParser.getBoats().values()) {
// boat.setPosition("-");
// data.add(boat);
// }
// }
teamList.refresh();
// posCol.setSortType(TableColumn.SortType.ASCENDING);
// teamList.getSortOrder().add(posCol);
// posCol.setSortable(false);
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
setContentPane("/views/StartScreenView.fxml");
}
}
@@ -25,7 +25,7 @@ public class RaceController {
String teamsConfigFile = "/config/teams.xml";
try {
race = createRace(raceConfigFile, teamsConfigFile);
race = createRace(raceConfigFile, teamsConfigFile); //These config files arent actually used
} catch (Exception e) {
System.out.println("There was an error creating the race.");
}
@@ -1,26 +1,31 @@
package seng302.controllers;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Slider;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import javafx.util.StringConverter;
import seng302.controllers.annotations.Annotation;
import seng302.controllers.annotations.ImportantAnnotationController;
import seng302.controllers.annotations.ImportantAnnotationDelegate;
import seng302.controllers.annotations.ImportantAnnotationsState;
import seng302.models.*;
import seng302.models.parsers.ConfigParser;
import seng302.models.parsers.StreamParser;
import java.io.IOException;
@@ -29,7 +34,8 @@ import java.util.*;
/**
* Created by ptg19 on 29/03/17.
*/
public class RaceViewController extends Thread{
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
@FXML
private VBox positionVbox;
@FXML
@@ -43,64 +49,104 @@ public class RaceViewController extends Thread{
@FXML
private Slider annotationSlider;
@FXML
private Button selectAnnotationBtn;
@FXML
private ComboBox boatSelectionComboBox;
@FXML
private CanvasController includedCanvasController;
private ArrayList<Yacht> startingBoats = new ArrayList<>();
private boolean displayFps;
private Timeline timerTimeline;
private Map<Yacht, TimelineInfo> timelineInfos = new HashMap<>();
private ArrayList<Yacht> boatOrder = new ArrayList<>();
private Race race;
private Stage stage;
public void initialize() {
private ImportantAnnotationsState importantAnnotations;
private Yacht selectedBoat;
public void initialize() {
// Load a default important annotation state
importantAnnotations = new ImportantAnnotationsState();
//Initialise race controller
RaceController raceController = new RaceController();
raceController.initializeRace();
race = raceController.getRace();
for (Yacht boat : race.getBoats()) {
startingBoats.add(boat);
}
// try{
// initializeTimelines();
// }
// catch (Exception e){
// e.printStackTrace();
// }
startingBoats = new ArrayList<>(Arrays.asList(race.getBoats()));
includedCanvasController.setup(this);
includedCanvasController.initializeCanvas();
initializeTimer();
initializeSettings();
initialiseWindDirection();
initialisePositionVBox();
//set wind direction!!!!!!! can't find another place to put my code --haoming
// double windDirection = new ConfigParser("/config/config.xml").getWindDirection();
// windDirectionText.setText(String.format("%.1f°", windDirection));
// windArrowText.setRotate(windDirection);
initializeUpdateTimer();
initialiseFPSCheckBox();
initialiseAnnotationSlider();
initialiseBoatSelectionComboBox();
includedCanvasController.timer.start();
selectAnnotationBtn.setOnAction(event -> {
loadSelectAnnotationView();
});
}
/**
* The important annotations have been changed, update this view
* @param importantAnnotationsState The current state of the selected annotations
*/
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
this.importantAnnotations = importantAnnotationsState;
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
}
/**
* Loads the "select annotations" view in a new window
*/
private void loadSelectAnnotationView() {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
Stage stage = new Stage();
// Set controller
ImportantAnnotationController controller = new ImportantAnnotationController(this,
stage);
fxmlLoader.setController(controller);
// Load FXML and set CSS
fxmlLoader
.setLocation(getClass().getResource("/views/importantAnnotationSelectView.fxml"));
Scene scene = new Scene(fxmlLoader.load(), 469, 248);
scene.getStylesheets().add(getClass().getResource("/css/master.css").toString());
stage.initStyle(StageStyle.UNDECORATED);
stage.setScene(scene);
stage.show();
controller.loadState(importantAnnotations);
} catch (IOException e) {
e.printStackTrace();
}
}
private void initializeSettings() {
private void initialiseFPSCheckBox() {
displayFps = true;
toggleFps.selectedProperty().addListener(
(observable, oldValue, newValue) -> displayFps = !displayFps);
}
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
displayFps = !displayFps;
}
});
//SLIFER STUFF BELOW
private void initialiseAnnotationSlider() {
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
@Override
public String toString(Double n) {
if (n == 0) return "None";
if (n == 1) return "Low";
if (n == 2) return "Medium";
if (n == 3) return "All";
if (n == 0) {
return "None";
}
if (n == 1) {
return "Important";
}
if (n == 2) {
return "All";
}
return "All";
}
@@ -110,162 +156,126 @@ public class RaceViewController extends Thread{
switch (s) {
case "None":
return 0d;
case "Low":
case "Important":
return 1d;
case "Medium":
return 2d;
case "All":
return 3d;
return 2d;
default:
return 3d;
return 2d;
}
}
});
annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
setAnnotations((int)annotationSlider.getValue()));
setAnnotations((int) annotationSlider.getValue()));
annotationSlider.setValue(3);
annotationSlider.setValue(2);
}
private void initializeTimer(){
/**
* Initalises a timer which updates elements of the RaceView such as wind direction, boat
* orderings etc.. which are dependent on the info from the stream parser constantly.
* Updates of each of these attributes are called ONCE EACH SECOND
*/
private void initializeUpdateTimer() {
timerTimeline = new Timeline();
timerTimeline.setCycleCount(Timeline.INDEFINITE);
// Run timer update every second
timerTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
if (StreamParser.isRaceFinished()) {
timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!");
} else {
timerLabel.setText(currentTimer());
}
})
new KeyFrame(Duration.seconds(1),
event -> {
updateRaceTime();
updateWindDirection();
updateOrder();
updateBoatSelectionComboBox();
})
);
// Start the timer
timerTimeline.playFromStart();
}
private void initialiseWindDirection() {
Timeline windDirTimeline = new Timeline();
windDirTimeline.setCycleCount(Timeline.INDEFINITE);
windDirTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
windArrowText.setRotate(StreamParser.getWindDirection());
})
);
windDirTimeline.playFromStart();
}
private void initialisePositionVBox() {
Timeline posVBoxTimeline = new Timeline();
posVBoxTimeline.setCycleCount(Timeline.INDEFINITE);
posVBoxTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
showOrder();
})
);
posVBoxTimeline.playFromStart();
}
/**
* Generates time line for each boat, and stores time time into timelineInfos hash map
* Updates the wind direction arrow and text as from info from the StreamParser
*/
private void initializeTimelines() {
HashMap<Yacht, List> boat_events = race.getEvents();
for (Yacht boat : boat_events.keySet()) {
startingBoats.add(boat);
// // x, y are the real time coordinates
// DoubleProperty x = new SimpleDoubleProperty();
// DoubleProperty y = new SimpleDoubleProperty();
//
// List<KeyFrame> keyFrames = new ArrayList<>();
// List<Event> events = boat_events.get(boat);
//
// // iterates all events and convert each event to keyFrame, then add them into a list
// for (Event event : events) {
// if (event.getIsFinishingEvent()) {
// keyFrames.add(
// new KeyFrame(Duration.seconds(event.getTime()),
// onFinished -> {race.setBoatFinished(boat); handleEvent(event);},
// new KeyValue(x, event.getThisMark().getLatitude()),
// new KeyValue(y, event.getThisMark().getLongitude())
// )
// );
// } else {
// keyFrames.add(
// new KeyFrame(Duration.seconds(event.getTime()),
// onFinished ->{
// handleEvent(event);
// boat.setHeading(event.getBoatHeading());
// },
// new KeyValue(x, event.getThisMark().getLatitude()),
// new KeyValue(y, event.getThisMark().getLongitude())
// )
// );
// }
// }
// timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y));
}
setRaceDuration();
private void updateWindDirection() {
windDirectionText.setText(String.format("%.1f°", StreamParser.getWindDirection()));
windArrowText.setRotate(StreamParser.getWindDirection());
}
private void setRaceDuration(){
Double maxDuration = 0.0;
Timeline maxTimeline = null;
for (TimelineInfo timelineInfo : timelineInfos.values()) {
/**
* Updates the clock for the race
*/
private void updateRaceTime() {
if (StreamParser.isRaceFinished()) {
timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!");
} else {
timerLabel.setText(getTimeSinceStartOfRace());
}
}
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getTotalDuration().toMillis() >= maxDuration) {
maxDuration = timeline.getTotalDuration().toMillis();
maxTimeline = timeline;
/**
* Grabs the boats currently in the race as from the StreamParser and sets them to be selectable
* in the boat selection combo box
*/
private void updateBoatSelectionComboBox() {
ObservableList<Yacht> observableBoats = FXCollections
.observableArrayList(StreamParser.getBoatsPos().values());
boatSelectionComboBox.setItems(observableBoats);
}
/**
* Updates the order of the boats as from the StreamParser and sets them in the boat order
* section
*/
private void updateOrder() {
positionVbox.getChildren().clear();
positionVbox.getChildren().removeAll();
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
for (Yacht boat : StreamParser.getBoatsPos().values()) {
if (boat.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " (Finished)");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
positionVbox.getChildren().add(textToAdd);
} else {
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle("");
positionVbox.getChildren().add(textToAdd);
}
// Timelines are paused by default
timeline.play();
timeline.pause();
}
}
maxTimeline.setOnFinished(event -> {
race.setRaceFinished();
loadRaceResultView();
/**
* Initialised the combo box with any boats currently in the race and adds the required listener
* for the combobox to take action upon selection
*/
private void initialiseBoatSelectionComboBox() {
updateBoatSelectionComboBox();
boatSelectionComboBox.valueProperty().addListener((observable, oldValue, newValue) -> {
//This listener is fired whenever the combo box changes. This means when the values are updated
//We dont want to set the selected value if the values are updated but nothing clicked (null)
if (newValue != null && newValue != selectedBoat) {
Yacht thisYacht = (Yacht) newValue;
setSelectedBoat(thisYacht);
}
});
}
/**
* Play each boats timerTimeline
*/
public void playTimelines(){
for (TimelineInfo timelineInfo : timelineInfos.values()){
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getStatus() == Animation.Status.PAUSED){
timeline.play();
}
}
}
/**
* Pause each boats timerTimeline
*/
public void pauseTimelines(){
for (TimelineInfo timelineInfo : timelineInfos.values()){
Timeline timeline = timelineInfo.getTimeline();
if (timeline.getStatus() == Animation.Status.RUNNING){
timeline.pause();
}
}
}
/**
* Display the list of boats in the order they finished the race
@@ -286,39 +296,6 @@ public class RaceViewController extends Thread{
}
}
public void handleEvent(Event event) {
Yacht boat = event.getBoat();
boatOrder.remove(boat);
boat.setMarkLastPast(event.getMarkPosInRace());
boatOrder.add(boat);
boatOrder.sort(new Comparator<Yacht>() {
@Override
public int compare(Yacht b1, Yacht b2) {
return b2.getMarkLastPast() - b1.getMarkLastPast();
}
});
showOrder();
}
private void showOrder() {
positionVbox.getChildren().clear();
positionVbox.getChildren().removeAll();
// for (Boat boat : boatOrder) {
// positionVbox.getChildren().add(new Text(boat.getShortName() + " " + boat.getSpeedInKnots() + " Knots"));
// }
for (Yacht boat : StreamParser.getBoatsPos().values()) {
if (boat.getBoatStatus() == 3) { // 3 is finish status
positionVbox.getChildren().add(new Text(boat.getPosition() + ". " +
boat.getShortName() + " (Finished)"));
} else {
positionVbox.getChildren().add(new Text(boat.getPosition() + ". " +
boat.getShortName() + " "));
}
}
}
/**
* Convert seconds to a string of the format mm:ss
@@ -333,7 +310,7 @@ public class RaceViewController extends Thread{
return String.format("%02d:%02d", time / 60, time % 60);
}
private String currentTimer() {
private String getTimeSinceStartOfRace() {
String timerString = "0:00";
if (StreamParser.getTimeSinceStart() > 0) {
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
@@ -353,12 +330,6 @@ public class RaceViewController extends Thread{
return timerString;
}
public void stopTimer() {
timerTimeline.stop();
}
public void startTimer() {
timerTimeline.play();
}
public boolean isDisplayFps() {
return displayFps;
@@ -368,56 +339,83 @@ public class RaceViewController extends Thread{
return race;
}
public Map<Yacht, TimelineInfo> getTimelineInfos() {
return timelineInfos;
}
public ArrayList<Yacht> getStartingBoats(){
return startingBoats;
}
/**
* Display the important annotations for a specific BoatGroup
* @param bg The boat group to set the annotations for
*/
private void setBoatGroupImportantAnnotations(BoatGroup bg) {
if (importantAnnotations.getAnnotationState(Annotation.NAME)) {
bg.setTeamNameObjectVisible(true);
} else {
bg.setTeamNameObjectVisible(false);
}
if (importantAnnotations.getAnnotationState(Annotation.SPEED)) {
bg.setVelocityObjectVisible(true);
} else {
bg.setVelocityObjectVisible(false);
}
if (importantAnnotations.getAnnotationState(Annotation.TRACK)) {
bg.setLineGroupVisible(true);
} else {
bg.setLineGroupVisible(false);
}
if (importantAnnotations.getAnnotationState(Annotation.WAKE)) {
bg.setWakeVisible(true);
} else {
bg.setWakeVisible(false);
}
if (importantAnnotations.getAnnotationState(Annotation.ESTTIMETONEXTMARK)) {
bg.setEstTimeToNextMarkObjectVisible(true);
} else {
bg.setEstTimeToNextMarkObjectVisible(false);
}
if (importantAnnotations.getAnnotationState(Annotation.LEGTIME)) {
bg.setLegTimeObjectVisible(true);
} else {
bg.setLegTimeObjectVisible(false);
}
}
private void setAnnotations(Integer annotationLevel) {
switch (annotationLevel) {
// No Annotations
case 0:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(false);
bg.setVelocityObjectVisible(false);
bg.setEstTimeToNextMarkObjectVisible(false);
bg.setLegTimeObjectVisible(false);
bg.setLineGroupVisible(false);
bg.setWakeVisible(false);
}
}
break;
// Important Annotations
case 1:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(false);
bg.setLineGroupVisible(false);
bg.setWakeVisible(false);
setBoatGroupImportantAnnotations(bg);
}
}
break;
// All Annotations
case 2:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(false);
bg.setLineGroupVisible(true);
bg.setWakeVisible(false);
}
}
break;
case 3:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(true);
bg.setEstTimeToNextMarkObjectVisible(true);
bg.setLegTimeObjectVisible(true);
bg.setLineGroupVisible(true);
bg.setWakeVisible(true);
}
@@ -426,11 +424,33 @@ public class RaceViewController extends Thread{
}
}
void setStage (Stage stage) {
/**
* Sets all the annotations of the selected boat to be visible and all others to be hidden
*
* @param yacht The yacht for which we want to view all annotations
*/
private void setSelectedBoat(Yacht yacht) {
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if (ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
//We need to iterate over all race groups to get the matching boat group belonging to this boat if we
//are to toggle its annotations, there is no other backwards knowledge of a yacht to its boatgroup.
if (bg.getBoat().getHullID().equals(yacht.getHullID())) {
bg.setIsSelected(true);
selectedBoat = yacht;
} else {
bg.setIsSelected(false);
}
}
}
}
void setStage(Stage stage) {
this.stage = stage;
}
Stage getStage () {
Stage getStage() {
return stage;
}
}
@@ -0,0 +1,160 @@
package seng302.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.models.Yacht;
import seng302.models.parsers.StreamParser;
public class StartScreenController implements Initializable {
@FXML
private GridPane gridPane;
@FXML
private Label timeTillLive;
@FXML
private Button streamButton;
@FXML
private Button switchToRaceViewButton;
@FXML
private TableView<Yacht> teamList;
@FXML
private TableColumn<Yacht, String> boatNameCol;
@FXML
private TableColumn<Yacht, String> shortNameCol;
@FXML
private TableColumn<Yacht, String> countryCol;
@FXML
private TableColumn<Yacht, String> posCol;
@FXML
private Label realTime;
private boolean switchedToRaceView = false;
private void setContentPane(String jfxUrl){
try{
// get the main controller anchor pane (MainView.fxml)
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
contentPane.getChildren().removeAll();
contentPane.getChildren().clear();
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
}
catch(javafx.fxml.LoadException e){
System.err.println(e.getCause());
}
catch(IOException e){
System.err.println(e);
}
}
@Override
public void initialize(URL location, ResourceBundle resources) {
gridPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
teamList.getStylesheets().add(getClass().getResource("/css/master.css").toString());
}
/**
* Running a timer to update the livestream status on welcome screen. Update interval is 1 second.
*/
public void startStream() {
if (StreamParser.isStreamStatus()) {
streamButton.setVisible(false);
realTime.setVisible(true);
timeTillLive.setVisible(true);
timeTillLive.setTextFill(Color.GREEN);
timeTillLive.setText("Connecting...");
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> {
if (StreamParser.isRaceStarted()) {
if (!switchedToRaceView) {
switchToRaceView();
}
timer.cancel();
}
if (StreamParser.isRaceFinished()) {
realTime.setText(StreamParser.getCurrentTimeString());
timeTillLive.setTextFill(Color.RED);
timeTillLive.setText("Race finished! Waiting for new race...");
switchToRaceViewButton.setDisable(true);
} else if (StreamParser.getTimeSinceStart() > 0) {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.RED);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = "-" + timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
} else {
realTime.setText(StreamParser.getCurrentTimeString());
updateTeamList();
timeTillLive.setTextFill(Color.BLACK);
switchToRaceViewButton.setDisable(false);
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
if (timerSecond.length() == 1) {
timerSecond = "0" + timerSecond;
}
String timerString = timerMinute + ":" + timerSecond;
timeTillLive.setText(timerString);
}
});
}
}, 0, 1000);
} else {
timeTillLive.setText("Stream not available.");
timeTillLive.setTextFill(Color.RED);
}
}
public void switchToRaceView() {
switchedToRaceView = true;
setContentPane("/views/RaceView.fxml");
}
private void updateTeamList() {
ObservableList<Yacht> data = FXCollections.observableArrayList();
teamList.setItems(data);
boatNameCol.setCellValueFactory(
new PropertyValueFactory<>("boatName")
);
shortNameCol.setCellValueFactory(
new PropertyValueFactory<>("shortName")
);
countryCol.setCellValueFactory(
new PropertyValueFactory<>("country")
);
posCol.setCellValueFactory(
new PropertyValueFactory<>("position")
);
data.addAll(StreamParser.getBoatsPos().values());
teamList.refresh();
}
}
@@ -0,0 +1,13 @@
package seng302.controllers.annotations;
/**
* Annotations the user can select as important
*/
public enum Annotation {
SPEED,
WAKE,
TRACK,
NAME,
ESTTIMETONEXTMARK,
LEGTIME
}
@@ -0,0 +1,145 @@
package seng302.controllers.annotations;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;
import seng302.controllers.RaceViewController;
import seng302.controllers.annotations.Annotation;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
public class ImportantAnnotationController implements Initializable {
/*
* JavaFX Outlets
*/
@FXML
private CheckBox boatWakeSelect;
@FXML
private CheckBox boatSpeedSelect;
@FXML
private CheckBox boatTrackSelect;
@FXML
private CheckBox boatNameSelect;
@FXML
private CheckBox boatEstTimeToNextMarkSelect;
@FXML
private CheckBox boatElapsedTimeSelect;
@FXML
private AnchorPane annotationSelectWindow;
@FXML
private Button closeButton;
private ImportantAnnotationDelegate delegate;
private ImportantAnnotationsState importantAnnotationsState;
private Stage stage;
public ImportantAnnotationController(ImportantAnnotationDelegate delegate, Stage stage) {
this.delegate = delegate;
importantAnnotationsState = new ImportantAnnotationsState();
this.stage = stage;
}
/**
* Sets whether or not an annotation is considered important, then
* sends an update to the delegate
*
* @param annotation The annotation
* @param isSet True if annotation is important
*/
private void setAnnotation(Annotation annotation, Boolean isSet) {
importantAnnotationsState.setAnnotationState(annotation, isSet);
sendUpdate();
}
/**
* Sends an update to the delegate when the important
* annotations have changed
*/
private void sendUpdate() {
this.delegate.importantAnnotationsChanged(importantAnnotationsState);
}
/**
* Load the current state of the 'important annotations'
*
* @param currentState hashmap containing the states of each annotation
*/
public void loadState(ImportantAnnotationsState currentState) {
this.importantAnnotationsState = currentState;
// Initialise checkboxes
for (Annotation annotation : importantAnnotationsState.getAnnotations()) {
switch (annotation) {
case WAKE:
boatWakeSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case SPEED:
boatSpeedSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case TRACK:
boatTrackSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case NAME:
boatNameSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case ESTTIMETONEXTMARK:
boatEstTimeToNextMarkSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
break;
case LEGTIME:
boatElapsedTimeSelect
.setSelected(importantAnnotationsState.getAnnotationState(annotation));
default:
break;
}
}
}
/**
* View did load
*
* @param location .
* @param resources .
*/
@Override
public void initialize(URL location, ResourceBundle resources) {
boatWakeSelect
.setOnAction(event -> setAnnotation(Annotation.WAKE, boatWakeSelect.isSelected()));
boatSpeedSelect
.setOnAction(event -> setAnnotation(Annotation.SPEED, boatSpeedSelect.isSelected()));
boatTrackSelect
.setOnAction(event -> setAnnotation(Annotation.TRACK, boatTrackSelect.isSelected()));
boatNameSelect
.setOnAction(event -> setAnnotation(Annotation.NAME, boatNameSelect.isSelected()));
boatEstTimeToNextMarkSelect.setOnAction(event -> setAnnotation(Annotation.ESTTIMETONEXTMARK,
boatEstTimeToNextMarkSelect.isSelected()));
boatElapsedTimeSelect.setOnAction(
event -> setAnnotation(Annotation.LEGTIME, boatElapsedTimeSelect.isSelected()));
closeButton.setOnAction(event -> stage.close());
}
}
@@ -0,0 +1,16 @@
package seng302.controllers.annotations;
/**
* An ImportantAnnotationDelegate handles updating the important annotations
* displayed to the user on behalf of the ImportantAnnotationController
*/
public interface ImportantAnnotationDelegate {
/**
* The important annotations have been changed, update the
* annotations displayed to the user
* @param importantAnnotationsState The current state of the selected annotations
*/
void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState);
}
@@ -0,0 +1,52 @@
package seng302.controllers.annotations;
import java.util.HashMap;
import java.util.Map;
public class ImportantAnnotationsState {
public static final Boolean DEFAULT_ANNOTATION_STATE = true;
private Map<Annotation, Boolean> currentState;
/**
* Stores the users preference for the annotations
* they consider to be important
*/
public ImportantAnnotationsState(){
this.currentState = new HashMap<>();
initialiseState();
}
/**
* Set each annotation to the default annotation state
*/
private void initialiseState(){
for (Annotation annotation : getAnnotations()){
currentState.put(annotation, DEFAULT_ANNOTATION_STATE);
}
}
/**
* Sets the state (visibility) of an annotation
* @param annotation The annotation to set
* @param visible Whether or not the annotation should be visible
*/
public void setAnnotationState(Annotation annotation, Boolean visible){
this.currentState.put(annotation, visible);
}
/**
* Returns the state (visibility) of a specific annotation
* @param annotation The annotation to check
* @return True if visible, else false
*/
public Boolean getAnnotationState(Annotation annotation){
return this.currentState.containsKey(annotation) && this.currentState.get(annotation);
}
/**
* @return Return an array containing all defined annotations
*/
public Annotation[] getAnnotations(){
return Annotation.class.getEnumConstants();
}
}
+176 -67
View File
@@ -9,24 +9,32 @@ import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import seng302.models.parsers.StreamParser;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
/**
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2 dimensional boat.
* It contains a single polygon for the boat, a group of lines to show it's path, a wake object and two text labels to
* annotate the boat teams name and the boats velocity. The boat will update it's position onscreen everytime
* UpdatePosition is called unless the window is minimized in which case it attempts to store animations and apply them
* when the window is maximised.
* BoatGroup is a javafx group that by default contains a graphical objects for representing a 2
* dimensional boat. It contains a single polygon for the boat, a group of lines to show it's path,
* a wake object and two text labels to annotate the boat teams name and the boats velocity. The
* boat will update it's position onscreen everytime UpdatePosition is called unless the window is
* minimized in which case it attempts to store animations and apply them when the window is
* maximised.
*/
public class BoatGroup extends RaceObject{
public class BoatGroup extends RaceObject {
//Constants for drawing
private static final double TEAMNAME_X_OFFSET = 10d;
private static final double TEAMNAME_Y_OFFSET = -15d;
private static final double TEAMNAME_Y_OFFSET = -29d;
private static final double VELOCITY_X_OFFSET = 10d;
private static final double VELOCITY_Y_OFFSET = -5d;
private static final double VELOCITY_Y_OFFSET = -17d;
private static final double ESTTIMETONEXTMARK_X_OFFSET = 10d;
private static final double ESTTIMETONEXTMARK_Y_OFFSET = -5d;
private static final double LEGTIME_X_OFFSET = 10d;
private static final double LEGTIME_Y_OFFSET = 7d;
private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d;
//Variables for boat logic.
@@ -39,48 +47,59 @@ public class BoatGroup extends RaceObject{
private Polygon boatPoly;
private Text teamNameObject;
private Text velocityObject;
private Text estTimeToNextMarkObject;
private Text legTimeObject;
private Wake wake;
private boolean isSelected = true; //Boats annotations are visible by default at the start
//Handles boat moving when connecting to a stream
private boolean setToInitialLocation = false;
private boolean destinationSet;
//Variables for handling minimization
private Stage stage;
private boolean isMaximized= true;
private boolean isMaximized = true;
private List<Line> lineStorage = new ArrayList<>();
private int setCallCount = 5;
/**
* Creates a BoatGroup with the default triangular boat polygon.
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which
* BoatGroup to update.
*
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
* to tell which BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line.
*/
public BoatGroup (Yacht boat, Color color){
public BoatGroup(Yacht boat, Color color) {
this.boat = boat;
initChildren(color);
}
/**
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be at point (0,0).
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used to tell which
* BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon.
* Creates a BoatGroup with the boat being the default polygon. The head of the boat should be
* at point (0,0).
*
* @param boat The boat that the BoatGroup will represent. Must contain an ID which will be used
* to tell which BoatGroup to update.
* @param color The colour of the boat polygon and the trailing line.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
public BoatGroup (Yacht boat, Color color, double... points)
{
public BoatGroup(Yacht boat, Color color, double... points) {
this.boat = boat;
initChildren(color, points);
}
/**
* Creates the javafx objects that will be the in the group by default.
* @param color The colour of the boat polygon and the trailing line.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat polygon.
*
* @param color The colour of the boat polygon and the trailing line.
* @param points An array of co-ordinates x1,y1,x2,y2,x3,y3... that will make up the boat
* polygon.
*/
private void initChildren (Color color, double... points) {
private void initChildren(Color color, double... points) {
boatPoly = new Polygon(points);
boatPoly.setFill(color);
boatPoly.setOnMouseEntered(event -> boatPoly.setFill(Color.FLORALWHITE));
boatPoly.setOnMouseExited(event -> boatPoly.setFill(color));
boatPoly.setOnMouseClicked(event -> setIsSelected(!isSelected));
boatPoly.setCache(true);
boatPoly.setCacheHint(CacheHint.SPEED);
@@ -89,6 +108,17 @@ public class BoatGroup extends RaceObject{
teamNameObject.setCache(true);
teamNameObject.setCacheHint(CacheHint.SPEED);
velocityObject = new Text(String.valueOf(boat.getVelocity()));
DateFormat format = new SimpleDateFormat("mm:ss");
String timeToNextMark = format
.format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong());
estTimeToNextMarkObject = new Text("Next mark: " + timeToNextMark);
if (boat.getMarkRoundingTime() != null) {
String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime());
legTimeObject = new Text("Last mark: " + elapsedTime);
} else {
legTimeObject = new Text("Last mark: -");
}
velocityObject.setCache(true);
velocityObject.setCacheHint(CacheHint.SPEED);
@@ -101,15 +131,27 @@ public class BoatGroup extends RaceObject{
velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
destinationSet = false;
estTimeToNextMarkObject.setX(ESTTIMETONEXTMARK_X_OFFSET);
estTimeToNextMarkObject.setY(ESTTIMETONEXTMARK_Y_OFFSET);
estTimeToNextMarkObject
.relocate(estTimeToNextMarkObject.getX(), estTimeToNextMarkObject.getY());
legTimeObject.setX(LEGTIME_X_OFFSET);
legTimeObject.setY(LEGTIME_Y_OFFSET);
legTimeObject.relocate(legTimeObject.getX(), legTimeObject.getY());
wake = new Wake(0, -BOAT_HEIGHT);
super.getChildren().addAll(teamNameObject, velocityObject, boatPoly);
super.getChildren()
.addAll(teamNameObject, velocityObject, boatPoly, estTimeToNextMarkObject,
legTimeObject);
}
/**
* Creates the javafx objects that will be the in the group by default.
*
* @param color The colour of the boat polygon and the trailing line.
*/
private void initChildren (Color color) {
private void initChildren(Color color) {
initChildren(color,
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
0.0, -BOAT_HEIGHT / 2,
@@ -117,7 +159,9 @@ public class BoatGroup extends RaceObject{
}
/**
* Moves the boat and its children annotations from its current coordinates by specified amounts.
* Moves the boat and its children annotations from its current coordinates by specified
* amounts.
*
* @param dx The amount to move the X coordinate by
* @param dy The amount to move the Y coordinate by
*/
@@ -128,6 +172,10 @@ public class BoatGroup extends RaceObject{
teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy);
velocityObject.setLayoutX(velocityObject.getLayoutX() + dx);
velocityObject.setLayoutY(velocityObject.getLayoutY() + dy);
estTimeToNextMarkObject.setLayoutX(estTimeToNextMarkObject.getLayoutX() + dx);
estTimeToNextMarkObject.setLayoutY(estTimeToNextMarkObject.getLayoutY() + dy);
legTimeObject.setLayoutX(legTimeObject.getLayoutX() + dx);
legTimeObject.setLayoutY(legTimeObject.getLayoutY() + dy);
wake.setLayoutX(wake.getLayoutX() + dx);
wake.setLayoutY(wake.getLayoutY() + dy);
rotateTo(rotation + currentRotation);
@@ -135,27 +183,33 @@ public class BoatGroup extends RaceObject{
/**
* Moves the boat and its children annotations to coordinates specified
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
*
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
* @param rotation The heading in degrees from north the boat should rotate to.
*/
public void moveTo (double x, double y, double rotation) {
public void moveTo(double x, double y, double rotation) {
rotateTo(rotation);
moveTo(x, y);
}
/**
* Moves the boat and its children annotations to coordinates specified
*
* @param x The X coordinate to move the boat to
* @param y The Y coordinate to move the boat to
*/
public void moveTo (double x, double y) {
public void moveTo(double x, double y) {
boatPoly.setLayoutX(x);
boatPoly.setLayoutY(y);
teamNameObject.setLayoutX(x);
teamNameObject.setLayoutY(y);
velocityObject.setLayoutX(x);
velocityObject.setLayoutY(y);
estTimeToNextMarkObject.setLayoutX(x);
estTimeToNextMarkObject.setLayoutY(y);
legTimeObject.setLayoutX(x);
legTimeObject.setLayoutY(y);
wake.setLayoutX(x);
wake.setLayoutY(y);
wake.rotate(currentRotation);
@@ -163,9 +217,11 @@ public class BoatGroup extends RaceObject{
/**
* Updates the position of all graphics in the BoatGroup based off of the given time interval.
* @param timeInterval The interval, in milliseconds, the boat should update it's position based on.
*
* @param timeInterval The interval, in milliseconds, the boat should update it's position based
* on.
*/
public void updatePosition (long timeInterval) {
public void updatePosition(long timeInterval) {
//Calculate the movement of the boat.
if (isMaximized) {
double dx = pixelVelocityX * timeInterval;
@@ -178,18 +234,13 @@ public class BoatGroup extends RaceObject{
distanceTravelled = 0;
if (lastPoint != null) {
Line l = new Line(
lastPoint.getX() * 3 - 1000,
lastPoint.getY() * 3 - 1000,
boatPoly.getLayoutX() * 3 - 1000,
boatPoly.getLayoutY() * 3 - 1000
lastPoint.getX(),
lastPoint.getY(),
boatPoly.getLayoutX(),
boatPoly.getLayoutY()
);
l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boatPoly.getFill());
if (lastPoint.getX() * 3 - 1000 < 0 || lastPoint.getY() * 3 - 1000 < 0 || boatPoly.getLayoutX() * 3 - 1000 < 0 || boatPoly.getLayoutY() * 3 - 1000 < 0)
l.setVisible(false);
else
l.setVisible(true);
l.setStroke(boat.getColour());
lineGroup.getChildren().add(l);
}
if (destinationSet) { //Only begin drawing after the first destination is set
@@ -202,18 +253,21 @@ public class BoatGroup extends RaceObject{
/**
* Sets the destination of the boat and the headng it should have once it reaches
*
* @param newXValue The X co-ordinate the boat needs to move to.
* @param newYValue The Y co-ordinate the boat needs to move to.
* @param rotation Rotation to move graphics to.
* @param raceIds RaceID of the object to move.
* @param rotation Rotation to move graphics to.
* @param raceIds RaceID of the object to move.
*/
public void setDestination (double newXValue, double newYValue, double rotation, double groundSpeed, int... raceIds) {
public void setDestination(double newXValue, double newYValue, double rotation,
double groundSpeed, int... raceIds) {
if (hasRaceId(raceIds)) {
if (setToInitialLocation) {
destinationSet = true;
boat.setVelocity(groundSpeed);
if (currentRotation < 0)
if (currentRotation < 0) {
currentRotation = 360 - currentRotation;
}
double dx = newXValue - boatPoly.getLayoutX();
double dy = newYValue - boatPoly.getLayoutY();
//Check movement is reasonable. Assumes a 1000 * 1000 canvas
@@ -230,11 +284,28 @@ public class BoatGroup extends RaceObject{
if (Math.abs(rotationalVelocity) > 0.075) {
System.out.println("rotationalVelocity = " + rotationalVelocity);
rotationalVelocity = 0;
wakeGenerationDelay--;
} else {
wake.setRotationalVelocity(rotationalVelocity, rotationalGoal,
boat.getVelocity());
rotateTo(rotationalGoal);
wake.rotate(rotationalGoal);
}
wake.setRotationalVelocity(rotationalVelocity, boat.getVelocity());
velocityObject.setText(String.format("%.2f m/s", boat.getVelocity()));
DateFormat format = new SimpleDateFormat("mm:ss");
// estimate time to next mark
String timeToNextMark = format
.format(boat.getEstimateTimeAtNextMark() - StreamParser.getCurrentTimeLong());
estTimeToNextMarkObject.setText("Next mark: " + timeToNextMark);
// elapsed time
if (boat.getMarkRoundingTime() != null) {
String elapsedTime = format
.format(StreamParser.getCurrentTimeLong() - boat.getMarkRoundingTime());
legTimeObject.setText("Last mark: " + elapsedTime);
} else {
legTimeObject.setText("Last mark: -");
}
} else {
setToInitialLocation = true;
rotationalGoal = rotation;
@@ -245,14 +316,14 @@ public class BoatGroup extends RaceObject{
if (!isMaximized) {
setToInitialLocation = false;
wakeGenerationDelay = 2;
if(setCallCount-- == 0) {
if (setCallCount-- == 0) {
setCallCount = 5;
if (lastPoint != null) {
Line l = new Line(
lastPoint.getX() * 1.5,
lastPoint.getY() * 1.5,
newXValue * 1.5,
newYValue * 1.5
lastPoint.getX(),
lastPoint.getY(),
newXValue,
newYValue
);
l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boatPoly.getFill());
@@ -265,7 +336,8 @@ public class BoatGroup extends RaceObject{
}
}
public void setDestination (double newXValue, double newYValue, double groundSpeed, int... raceIDs) {
public void setDestination(double newXValue, double newYValue, double groundSpeed,
int... raceIDs) {
destinationSet = true;
if (hasRaceId(raceIDs)) {
@@ -280,16 +352,20 @@ public class BoatGroup extends RaceObject{
}
}
public void rotateTo (double rotation) {
public void rotateTo(double rotation) {
currentRotation = rotation;
boatPoly.getTransforms().setAll(new Rotate(rotation));
}
public void forceRotation () {
rotateTo (rotationalGoal);
public void forceRotation() {
rotateTo(rotationalGoal);
wake.rotate(rotationalGoal);
}
public void paintBoat(Color color) {
boatPoly.setFill(color);
}
public void setTeamNameObjectVisible(Boolean visible) {
teamNameObject.setVisible(visible);
}
@@ -298,6 +374,14 @@ public class BoatGroup extends RaceObject{
velocityObject.setVisible(visible);
}
public void setEstTimeToNextMarkObjectVisible(Boolean visible) {
estTimeToNextMarkObject.setVisible(visible);
}
public void setLegTimeObjectVisible(Boolean visible) {
legTimeObject.setVisible(visible);
}
public void setLineGroupVisible(Boolean visible) {
lineGroup.setVisible(visible);
}
@@ -310,16 +394,35 @@ public class BoatGroup extends RaceObject{
return boat;
}
/**
* This function sets the boats isSelected property AS WELL as actually acting upon the value of
* that selection. (Painting or not painting annotations)
*
* @param isSelected A Boolean indicating whether or not the boat is selected
*/
public void setIsSelected(Boolean isSelected) {
this.isSelected = isSelected;
setTeamNameObjectVisible(isSelected);
setVelocityObjectVisible(isSelected);
setLineGroupVisible(isSelected);
setWakeVisible(isSelected);
setEstTimeToNextMarkObjectVisible(isSelected);
setLegTimeObjectVisible(isSelected);
// TODO: 17/05/17 wmu16 - this should iterate over some list of annotations which we should make to easily make extensible
// paintBoat((isSelected) ? Color.WHITE : boat.getColour());
}
/**
* Returns true if this BoatGroup contains at least one of the given IDs.
*
* @param raceIds The ID's to check the BoatGroup for.
* @return True if the BoatGroup contains at east one of the given IDs, false otherwise.
*/
public boolean hasRaceId (int... raceIds) {
public boolean hasRaceId(int... raceIds) {
for (int id : raceIds) {
if (id == boat.getSourceID())
if (id == boat.getSourceID()) {
return true;
}
}
return false;
}
@@ -329,36 +432,37 @@ public class BoatGroup extends RaceObject{
*
* @return An array containing all ID's associated with this RaceObject.
*/
public int[] getRaceIds () {
return new int[] {boat.getSourceID()};
public int[] getRaceIds() {
return new int[]{boat.getSourceID()};
}
/**
* Due to javaFX limitations annotations associated with a boat that you want to appear below all boats in the
* Z-axis need to be pulled out of the BoatGroup and added to the parent group of the BoatGroups. This function
* returns these annotations as a group.
* Due to javaFX limitations annotations associated with a boat that you want to appear below
* all boats in the Z-axis need to be pulled out of the BoatGroup and added to the parent group
* of the BoatGroups. This function returns these annotations as a group.
*
* @return A group containing low priority annotations.
*/
public Group getLowPriorityAnnotations () {
public Group getLowPriorityAnnotations() {
Group group = new Group();
group.getChildren().addAll(wake, lineGroup);
return group;
}
/**
* Use this function to let the BoatGroup know about the stage it is in. If it knows about it's stage then it will
* listen to the iconified property of that stage and change it's behaviour upon minimization. Without setting the
* Stage there is guarantee that the BoatGroup will draw properly when the stage is minimized.
* Use this function to let the BoatGroup know about the stage it is in. If it knows about it's
* stage then it will listen to the iconified property of that stage and change it's behaviour
* upon minimization. Without setting the Stage there is guarantee that the BoatGroup will draw
* properly when the stage is minimized.
*
* @param stage The stage that the BoatGroup is added to.
*/
public void setStage (Stage stage) {
public void setStage(Stage stage) {
/* TODO: 4/05/17 cir27 - Find a way to get the stage to this point. Need to pass it through multiple controllers.
App.start() -> Controller.setContentPane -> RaceViewController -> CanvasController
*/
this.stage = stage;
this.stage.iconifiedProperty().addListener( e -> {
this.stage.iconifiedProperty().addListener(e -> {
isMaximized = !stage.isIconified();
if (!lineStorage.isEmpty()) {
lineGroup.getChildren().addAll(lineStorage);
@@ -366,4 +470,9 @@ public class BoatGroup extends RaceObject{
}
});
}
@Override
public String toString() {
return boat.toString();
}
}
+12 -5
View File
@@ -12,9 +12,9 @@ import java.text.SimpleDateFormat;
* also done outside Boat class because some old variables are not used anymore.
*/
public class Yacht {
// Used in boat group
private Color colour;
private double velocity;
private Integer markLastPast;
private String boatType;
private Integer sourceID;
@@ -30,6 +30,8 @@ public class Yacht {
private Long estimateTimeAtNextMark;
private Long estimateTimeAtFinish;
private String position;
// Mark rounding
private Long markRoundingTime;
/**
* Used in EventTest and RaceTest.
@@ -157,11 +159,16 @@ public class Yacht {
this.velocity = velocity;
}
public Integer getMarkLastPast() {
return markLastPast;
public Long getMarkRoundingTime() {
return markRoundingTime;
}
public void setMarkLastPast(Integer markLastPast) {
this.markLastPast = markLastPast;
public void setMarkRoundingTime(Long markRoundingTime) {
this.markRoundingTime = markRoundingTime;
}
@Override
public String toString() {
return boatName;
}
}
@@ -1,6 +1,7 @@
package seng302.models.mark;
import javafx.geometry.Point2D;
import javafx.scene.CacheHint;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
@@ -1,53 +0,0 @@
package seng302.models.parsers;
/**
* Created by Kusal on 4/24/2017.
*/
public enum PacketType {
HEARTBEAT,
RACE_STATUS,
DISPLAY_TEXT_MESSAGE,
XML_MESSAGE,
RACE_START_STATUS,
YACHT_EVENT_CODE,
YACHT_ACTION_CODE,
CHATTER_TEXT,
BOAT_LOCATION,
MARK_ROUNDING,
COURSE_WIND,
AVG_WIND,
OTHER;
static PacketType assignPacketType(int packetType){
switch(packetType){
case 1:
return HEARTBEAT;
case 12:
return RACE_STATUS;
case 20:
return DISPLAY_TEXT_MESSAGE;
case 26:
return XML_MESSAGE;
case 27:
return RACE_START_STATUS;
case 29:
return YACHT_EVENT_CODE;
case 31:
return YACHT_ACTION_CODE;
case 36:
return CHATTER_TEXT;
case 37:
return BOAT_LOCATION;
case 38:
return MARK_ROUNDING;
case 44:
return COURSE_WIND;
case 47:
return AVG_WIND;
default:
}
return OTHER;
}
}
@@ -1,44 +0,0 @@
package seng302.models.parsers;
/**
* Created by kre39 on 23/04/17.
*/
public class StreamPacket {
//Change int to an ENUM for the type
private PacketType type;
private long messageLength;
private long timeStamp;
private byte[] payload;
StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) {
this.type = PacketType.assignPacketType(type);
this.messageLength = messageLength;
this.timeStamp = timeStamp;
this.payload = payload;
// System.out.println("type = " + this.type.toString());
//switch the packet type to deal with what ever specific packet you want to deal with
// if (this.type == PacketType.XML_MESSAGE){
// //System.out.println("--------");
// System.out.println(new String(payload));
// //StreamParser.parsePacket(this);
// }
}
PacketType getType() {
return type;
}
public long getMessageLength() {
return messageLength;
}
byte[] getPayload() {
return payload;
}
long getTimeStamp() {
return timeStamp;
}
}
@@ -25,13 +25,13 @@ import java.util.concurrent.PriorityBlockingQueue;
* and parsed in by turning the byte arrays into useful data. There are two public static hashmaps
* that are threadsafe so the visualiser can always access the latest speed and position available
* Created by kre39 on 23/04/17.
*
*/
public class StreamParser extends Thread{
public static ConcurrentHashMap<Long, PriorityBlockingQueue<BoatPositionPacket>> boatPositions = new ConcurrentHashMap<>();
private String threadName;
private Thread t;
private static boolean newRaceXmlReceived = false;
private static boolean raceStarted = false;
private static XMLParser xmlObject;
private static boolean raceFinished = false;
@@ -40,6 +40,7 @@ public class StreamParser extends Thread{
private static Map<Integer, Yacht> boats = new ConcurrentHashMap<>();
private static Map<Long, Yacht> boatsPos = new ConcurrentSkipListMap<>();
private static double windDirection = 0;
private static Long currentTimeLong;
private static String currentTimeString;
private static boolean appRunning;
@@ -123,6 +124,7 @@ public class StreamParser extends Thread{
extractDisplayMessage(packet);
break;
case XML_MESSAGE:
newRaceXmlReceived = true;
extractXmlMessage(packet);
break;
case RACE_START_STATUS:
@@ -197,9 +199,11 @@ public class StreamParser extends Thread{
long currentTime = bytesToLong(Arrays.copyOfRange(payload,1,7));
long raceId = bytesToLong(Arrays.copyOfRange(payload,7,11));
int raceStatus = payload[11];
// System.out.println("raceStatus = " + raceStatus);
long expectedStartTime = bytesToLong(Arrays.copyOfRange(payload,12,18));
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
currentTimeLong = currentTime;
DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
if (xmlObject.getRegattaXML() != null) {
format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString()));
@@ -207,7 +211,6 @@ public class StreamParser extends Thread{
}
long timeTillStart = ((new Date (expectedStartTime)).getTime() - (new Date (currentTime)).getTime())/1000;
if (timeTillStart > 0) {
timeSinceStart = timeTillStart;
//System.out.println("Time till start: " + timeTillStart + " Seconds");
@@ -224,10 +227,10 @@ public class StreamParser extends Thread{
//System.out.println("Time since start: " + -1 * timeTillStart + " Seconds");
timeSinceStart = timeTillStart;
}
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
double windDirFactor = 0x4000 / 90; //0x4000 is 90 degrees, 0x8000 is 180 degrees, etc...
windDirection = windDir / windDirFactor;
long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22));
int noBoats = payload[22];
int raceType = payload[23];
// ArrayList<String> boatStatuses = new ArrayList<>();
@@ -237,11 +240,11 @@ public class StreamParser extends Thread{
Yacht boat = boats.get((int)(long) boatStatusSourceID);
boat.setBoatStatus((int)payload[28 + (i * 20)]);
boat.setLegNumber((int)payload[29 + (i * 20)]);
boat.setPenaltiesAwarded((int)payload[29 + (i * 20)]);
boat.setPenaltiesServed((int)payload[30 + (i * 20)]);
Long estTimeAtNextMark = bytesToLong(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)));
boat.setPenaltiesAwarded((int)payload[30 + (i * 20)]);
boat.setPenaltiesServed((int)payload[31 + (i * 20)]);
Long estTimeAtNextMark = bytesToLong(Arrays.copyOfRange(payload,32 + (i * 20),38+ (i * 20)));
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
Long estTimeAtFinish = bytesToLong(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)));
Long estTimeAtFinish = bytesToLong(Arrays.copyOfRange(payload,38 + (i * 20),44+ (i * 20)));
boat.setEstimateTimeAtFinish(estTimeAtFinish);
boatsPos.put(estTimeAtFinish, boat);
// String boatStatus = "SourceID: " + boatStatusSourceID;
@@ -295,9 +298,8 @@ public class StreamParser extends Thread{
byte[] payload = packet.getPayload();
int messageType = payload[9];
long messagelength = bytesToLong(Arrays.copyOfRange(payload,12,14));
String xmlMessage = new String((Arrays.copyOfRange(payload,14,(int) (14 + messagelength)))).trim();
//System.out.println("xmlMessage2 = " + xmlMessage);
long messageLength = bytesToLong(Arrays.copyOfRange(payload,12,14));
String xmlMessage = new String((Arrays.copyOfRange(payload,14,(int) (14 + messageLength)))).trim();
//Create XML document Object
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
@@ -314,6 +316,9 @@ public class StreamParser extends Thread{
if (messageType == 7) { //7 is the boat XML
boats = xmlObject.getBoatXML().getCompetingBoats();
}
if (messageType == 6) { //6 is race info xml
newRaceXmlReceived = true;
}
}
/**
@@ -430,6 +435,9 @@ public class StreamParser extends Thread{
int roundingSide = payload[18];
int markType = payload[19];
int markId = payload[20];
// assign mark rounding time to boat
boats.get((int)subjectId).setMarkRoundingTime(timeStamp);
}
/**
@@ -576,9 +584,33 @@ public class StreamParser extends Thread{
return boatsPos;
}
/**
* returns current time in stream in long
*
* @return a long value of current time
*/
public static Long getCurrentTimeLong() {
return currentTimeLong;
}
public static void appClose(){
appRunning = false;
System.out.println("[CLIENT] Shutting down stream parser");
}
/**
* Used to check if a new un-processed xml has been found, if so will return true before
* toggling off so that the next check will return false.
*
* @return the status of if new xml has been received
*/
public static boolean isNewRaceXmlReceived(){
if (newRaceXmlReceived){
newRaceXmlReceived = false;
return true;
} else {
return false;
}
}
}
+25 -2
View File
@@ -235,6 +235,28 @@ public class ServerThread implements Runnable, Observer {
}
}
/**
* Send the post-start race course information
*/
private void sendPostStartCourseXml(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
try {
Message raceData = getXmlMessage("/server_config/courseLimits.xml", XMLMessageSubType.RACE);
if (raceData != null) {
server.send(raceData);
serverLog("Sending race data", 0);
}
}catch (IOException e) {
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
}
}
},25000);
//Delays the new course xml data for 25 seconds so the boats are able to pass the starting line
}
public void run() {
try{
server = new StreamingServerSocket(PORT_NUMBER);
@@ -252,12 +274,13 @@ public class ServerThread implements Runnable, Observer {
sendXml();
startSendingRaceStartStatusMessages();
startSendingRaceStatusMessages();
sendPostStartCourseXml();
}
/**
* Start sending static boat position updates when race has finished
*/
private void startSendingRaceFinishedBoatPostions(){
private void startSendingRaceFinishedBoatPositions(){
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
@@ -316,7 +339,7 @@ public class ServerThread implements Runnable, Observer {
}
if (numOfBoatsFinished == ((List<Boat>) arg).size()) {
startSendingRaceFinishedBoatPostions();
startSendingRaceFinishedBoatPositions();
}
}
@@ -1,10 +1,7 @@
package seng302.server.messages;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
public class BoatLocationMessage extends Message {
private final int MESSAGE_SIZE = 56;
@@ -44,7 +41,7 @@ public class BoatLocationMessage extends Message {
public BoatLocationMessage(int sourceId, int sequenceNum, double latitude, double longitude, double heading, long boatSpeed){
boatSpeed /= 10;
messageVersionNumber = 1;
time = System.currentTimeMillis() / 1000L;
time = System.currentTimeMillis();
this.sourceId = sourceId;
this.sequenceNum = sequenceNum;
this.deviceType = DeviceType.RACING_YACHT;