Merge remote-tracking branch 'origin/develop' into issue#8_create_finish_screen

# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
This commit is contained in:
Zhi You Tan
2017-05-25 12:32:10 +12:00
22 changed files with 489 additions and 530 deletions
+1 -2
View File
@@ -24,7 +24,6 @@ public class App extends Application {
primaryStage.setOnCloseRequest(e -> {
StreamParser.appClose();
StreamReceiver.noMoreBytes();
System.out.println("[CLIENT] Exiting program");
System.exit(0);
});
@@ -65,8 +64,8 @@ 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("livedata.americascup.com", 4941, "RaceStream");
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
}
sr.start();
@@ -34,6 +34,12 @@ import seng302.models.stream.packets.BoatPositionPacket;
import seng302.server.simulator.GeoUtility;
import seng302.server.simulator.mark.Position;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.PriorityBlockingQueue;
/**
* Created by ptg19 on 15/03/17.
* Modified by Haoming Yin (hyi25) on 20/3/2017.
@@ -49,16 +55,11 @@ public class CanvasController {
private GraphicsContext gc;
private ImageView mapImage;
private final int MARK_SIZE = 10;
private final int BUFFER_SIZE = 50;
private final int PANEL_WIDTH = 1260; // it should be 1280 but, minors 40 to cancel the bias.
private final int PANEL_HEIGHT = 960;
private final int CANVAS_WIDTH = 720;
private final int CANVAS_HEIGHT = 720;
private final int LHS_BUFFER = BUFFER_SIZE;
private final int RHS_BUFFER = BUFFER_SIZE;
private final int TOP_BUFFER = BUFFER_SIZE;
private final int BOT_BUFFER = TOP_BUFFER;
private boolean horizontalInversion = false;
private double distanceScaleFactor;
@@ -122,6 +123,9 @@ public class CanvasController {
initializeMarks();
timer = new AnimationTimer() {
private int UPDATE_FPM_PERIOD = 50; // update FPM label every 50 frames
private int updateFPMCounter = 100;
@Override
public void handle(long now) {
@@ -137,11 +141,12 @@ public class CanvasController {
elapsedNanos = now - oldFrameTime ;
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
if (updateFPMCounter++ > UPDATE_FPM_PERIOD) {
updateFPMCounter = 0;
drawFps(frameRate.intValue());
}
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
elapsedNanos = 1000 / 60;
raceViewController.updateSparkLine();
}
updateGroups();
if (StreamParser.isRaceFinished()) {
this.stop();
@@ -390,17 +395,14 @@ public class CanvasController {
private void drawFps(int fps){
if (raceViewController.isDisplayFps()){
gc.clearRect(5, 5, 50, 20);
gc.setFill(Color.SKYBLUE);
gc.fillRect(4, 4, 51, 21);
gc.setFill(Color.BLACK);
gc.setFont(new Font(14));
gc.setLineWidth(3);
gc.clearRect(5, 5, 60, 30);
gc.setFont(new Font(16));
gc.setLineWidth(4);
gc.setGlobalAlpha(0.75);
gc.fillText(fps + " FPS", 5, 20);
gc.setGlobalAlpha(0.5);
} else {
gc.clearRect(5, 5, 50, 20);
gc.setFill(Color.SKYBLUE);
gc.fillRect(4, 4, 51, 21);
gc.clearRect(5,5,60,30);
}
}
@@ -463,29 +465,24 @@ public class CanvasController {
if (scaleDirection == ScaleDirection.HORIZONTAL) {
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
referencePointX = LHS_BUFFER + distanceScaleFactor * Math.sin(referenceAngle) * Mark
.calculateDistance(referencePoint, minLonPoint);
referencePointX = BUFFER_SIZE + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint));
referencePointY = CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark
.calculateDistance(referencePoint, maxLatPoint);
referencePointY = CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE);
referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
referencePointY = referencePointY / 2;
referencePointY += TOP_BUFFER;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark
.calculateDistance(referencePoint, maxLatPoint);
referencePointY += BUFFER_SIZE;
referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint);
} else {
referencePointY = CANVAS_HEIGHT - BOT_BUFFER;
referencePointY = CANVAS_HEIGHT - BUFFER_SIZE;
referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint));
referencePointX = LHS_BUFFER;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark
.calculateDistance(referencePoint, minLonPoint);
referencePointX += ((CANVAS_WIDTH - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon
* distanceScaleFactor)) / 2;
referencePointX = BUFFER_SIZE;
referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint);
referencePointX += ((CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) - (minLonToMaxLon * distanceScaleFactor)) / 2;
}
if(horizontalInversion) {
referencePointX = CANVAS_WIDTH - RHS_BUFFER - (referencePointX - LHS_BUFFER);
referencePointX = CANVAS_WIDTH - BUFFER_SIZE - (referencePointX - BUFFER_SIZE);
}
}
@@ -509,10 +506,10 @@ public class CanvasController {
double horiDistance =
Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint);
double vertScale = (CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER)) / vertDistance;
double vertScale = (CANVAS_HEIGHT - (BUFFER_SIZE + BUFFER_SIZE)) / vertDistance;
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER))) {
distanceScaleFactor = (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER)) / horiDistance;
if ((horiDistance * vertScale) > (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE))) {
distanceScaleFactor = (CANVAS_WIDTH - (BUFFER_SIZE + BUFFER_SIZE)) / horiDistance;
scaleDirection = ScaleDirection.HORIZONTAL;
} else {
distanceScaleFactor = vertScale;
@@ -562,7 +559,7 @@ public class CanvasController {
.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference);
}
if(horizontalInversion) {
xAxisLocation = CANVAS_WIDTH - RHS_BUFFER - (xAxisLocation - LHS_BUFFER);
xAxisLocation = CANVAS_WIDTH - BUFFER_SIZE - (xAxisLocation - BUFFER_SIZE);
}
return new Point2D(xAxisLocation, yAxisLocation);
}
@@ -6,7 +6,12 @@ import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Side;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
@@ -30,12 +35,19 @@ import seng302.models.stream.StreamParser;
import java.io.IOException;
import java.util.*;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
import java.util.stream.Collectors;
/**
*
* Created by ptg19 on 29/03/17.
*/
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
@FXML
private LineChart raceSparkLine;
@FXML
private NumberAxis sparklineYAxis;
@FXML
private VBox positionVbox;
@FXML
@@ -59,7 +71,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
private boolean displayFps;
private Timeline timerTimeline;
private Stage stage;
private static HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
private static ArrayList<Yacht> racingBoats = new ArrayList<>();
private ImportantAnnotationsState importantAnnotations;
private Yacht selectedBoat;
@@ -67,6 +80,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
// Load a default important annotation state
importantAnnotations = new ImportantAnnotationsState();
//Formatting the y axis of the sparkline
raceSparkLine.getYAxis().setRotate(180);
raceSparkLine.getYAxis().setTickLabelRotation(180);
raceSparkLine.getYAxis().setTranslateX(15);
raceSparkLine.getYAxis().setAutoRanging(false);
startingBoats = new ArrayList<>(StreamParser.getBoats().values());
includedCanvasController.setup(this);
includedCanvasController.initializeCanvas();
initializeUpdateTimer();
@@ -74,14 +95,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
initialiseAnnotationSlider();
initialiseBoatSelectionComboBox();
includedCanvasController.timer.start();
selectAnnotationBtn.setOnAction(event -> {
loadSelectAnnotationView();
});
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) {
@@ -89,6 +109,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
}
/**
* Loads the "select annotations" view in a new window
*/
@@ -126,6 +147,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
(observable, oldValue, newValue) -> displayFps = !displayFps);
}
private void initialiseAnnotationSlider() {
annotationSlider.setLabelFormatter(new StringConverter<Double>() {
@Override
@@ -166,6 +188,79 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
/**
* Used to add any new boats into the race that may have started late or not have had data received yet
*/
void updateSparkLine(){
// Collect the racing boats that aren't already in the chart
ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID())
&& yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
// Obtain the qualifying boats to set the max on the Y axis
racingBoats = startingBoats.stream().filter(yacht ->
yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
sparklineYAxis.setUpperBound(racingBoats.size() + 1);
// Create a new data series for new boats
sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
Series<String, Double> yachtData = new Series<>();
yachtData.setName(yacht.getBoatName());
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
sparkLineData.put(yacht.getSourceID(), yachtData);
});
// Lambda function to sort the series in order of leg (later legs shown more to the right)
List<XYChart.Series<String, Double>> positions = new ArrayList<>(sparkLineData.values());
Collections.sort(positions, (o1, o2) -> {
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
if (leg2 < leg1){
return 1;
} else {
return -1;
}
});
// Adds the new data series to the sparkline (and set the colour of the series)
raceSparkLine.setCreateSymbols(false);
positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> {
raceSparkLine.getData().add(spark);
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
});
}
/**
* Updates the yachts sparkline of the desired boat and using the new leg number
* @param yacht The yacht to be updated on the sparkline
* @param legNumber the leg number that the position will be assigned to
*/
public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
}
/**
* gets the rgb string of the boats colour to use for the chart via css
* @param boatName boat passed in to get the boats colour
* @return the colour as an rgb string
*/
private String getBoatColorAsRGB(String boatName){
Color color = Color.WHITE;
for (Yacht yacht: startingBoats){
if (Objects.equals(yacht.getBoatName(), boatName)){
color = yacht.getColour();
}
}
return String.format( "#%02X%02X%02X",
(int)( color.getRed() * 255 ),
(int)( color.getGreen() * 255 ),
(int)( color.getBlue() * 255 ) );
}
/**
* 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.
@@ -182,7 +277,6 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
updateWindDirection();
updateOrder();
updateBoatSelectionComboBox();
})
);
@@ -233,7 +327,17 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
positionVbox.getChildren().removeAll();
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
// list of racing boat id
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
}
if (StreamParser.isRaceStarted()) {
for (Yacht boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
if (boat.getBoatStatus() == 3) { // 3 is finish status
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " (Finished)");
@@ -247,7 +351,18 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
textToAdd.setStyle("");
positionVbox.getChildren().add(textToAdd);
}
}
}
} else {
for (Yacht boat : StreamParser.getBoats().values()) {
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
Text textToAdd = new Text(boat.getPosition() + ". " +
boat.getShortName() + " ");
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
textToAdd.setStyle("");
positionVbox.getChildren().add(textToAdd);
}
}
}
}
@@ -322,12 +437,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
}
public boolean isDisplayFps() {
boolean isDisplayFps() {
return displayFps;
}
/**
* Display the important annotations for a specific BoatGroup
*
* @param bg The boat group to set the annotations for
*/
private void setBoatGroupImportantAnnotations(BoatGroup bg) {
@@ -427,4 +543,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
Stage getStage() {
return stage;
}
/**
* Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it)
* @param yachtId
* @return
*/
public static boolean sparkLineStatus(Integer yachtId) {
return sparkLineData.containsKey(yachtId);
}
}
@@ -2,6 +2,7 @@ package seng302.controllers;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
@@ -22,6 +23,7 @@ import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import seng302.models.Yacht;
import seng302.models.stream.StreamParser;
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
public class StartScreenController implements Initializable {
@@ -162,7 +164,28 @@ public class StartScreenController implements Initializable {
new PropertyValueFactory<>("position")
);
data.addAll(StreamParser.getBoatsPos().values());
// check if the boat is racing
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
.getParticipants();
ArrayList<Integer> participantIDs = new ArrayList<>();
for (Participant p : participants) {
participantIDs.add(p.getsourceID());
}
// add boats to the start screen list
if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos()
for (Yacht boat : StreamParser.getBoatsPos().values()) {
if (participantIDs.contains(boat.getSourceID())) {
data.add(boat);
}
}
} else { // else use StreamParser.getBoats()
for (Yacht boat : StreamParser.getBoats().values()) {
if (participantIDs.contains(boat.getSourceID())) {
data.add(boat);
}
}
}
teamList.refresh();
}
}
@@ -1,13 +1,9 @@
package seng302.models;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.input.MouseDragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Line;
import javafx.scene.shape.Polygon;
import javafx.scene.text.Text;
+1 -1
View File
@@ -3,7 +3,7 @@ package seng302.models;
import javafx.scene.paint.Color;
/**
* Created by ryan_ on 16/03/2017.
* Enum for randomly generating colours.
*/
public enum Colors {
RED, PERU, SEAGREEN, GREEN, BLUE, PURPLE;
-1
View File
@@ -92,7 +92,6 @@ public class Event {
if (this.isFinishingEvent) {
return (this.getTimeString() + ", " + this.getBoat().getBoatName() + " finished the race");
}
// System.out.println(this.getDistanceBetweenMarks());
return (this.getTimeString() + ", " + this.getBoat().getBoatName() + " passed " + this.mark1.getName() + " going heading " + this.getBoatHeading() + "°");
}
-116
View File
@@ -1,116 +0,0 @@
package seng302.models;
import seng302.models.mark.SingleMark;
/**
* Represents the leg of a race.
*/
public class Leg {
private int heading;
private int distance;
private boolean isFinishingLeg;
private SingleMark startingSingleMark;
/**
* Create a new leg
*
* @param heading, the magnetic heading of this leg
* @param distance, the total distance of this leg in meters
* @param singleMark, the singleMark this leg starts on
*/
public Leg(int heading, int distance, SingleMark singleMark) {
this.heading = heading;
this.distance = distance;
this.startingSingleMark = singleMark;
this.isFinishingLeg = false;
}
/**
* Create a new leg
*
* @param heading, the magnetic heading of this leg
* @param distance, the total distance of this leg in meters
* @param markerName, the name of the marker this leg starts on
*/
public Leg(int heading, int distance, String markerName) {
this.heading = heading;
this.distance = distance;
this.startingSingleMark = new SingleMark(markerName);
this.isFinishingLeg = false;
}
/**
* Get the heading of this leg
* @return int
*/
public int getHeading() {
return this.heading;
}
/**
* Set the heading for this leg
* @param heading
*/
public void setHeading(int heading) {
this.heading = heading;
}
/**
* Get the total distance of this leg in meters
* @return int
*/
public int getDistance() {
return this.distance;
}
/**
* Set the distance of this leg in meters
* @param distance
*/
public void setDistance(int distance) {
this.distance = distance;
}
/**
* Returns the marker this leg started on
* @return SingleMark
*/
public SingleMark getMarker() {
return this.startingSingleMark;
}
/**
* Set the singleMark this leg starts on
* @param singleMark
*/
public void setMarker(SingleMark singleMark) {
this.startingSingleMark = singleMark;
}
/**
* Returns the name of the marker this leg started on
* @return String
*/
public String getMarkerLabel() {
return this.startingSingleMark.getName();
}
/**
* Specify whether or not the race finishes on this leg
*
* @param isFinishingLeg whether or not the race finishes on this leg
*/
public void setFinishingLeg(boolean isFinishingLeg) {
this.isFinishingLeg = isFinishingLeg;
}
/**
* Returns whether or not the race finishes after this leg
* @return true if this the race finishes after this leg
*/
public boolean getIsFinishingLeg() {
return this.isFinishingLeg;
}
}
@@ -1,31 +0,0 @@
package seng302.models;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
/**
* Created by zyt10 on 17/03/17.
* this class is literally just to associate a timeline with a DoubleProperty x and y
*/
public class TimelineInfo {
private Timeline timeline;
private DoubleProperty x;
private DoubleProperty y;
public TimelineInfo(Timeline timeline, DoubleProperty x, DoubleProperty y) {
this.timeline = timeline;
this.x = x;
this.y = y;
}
public Timeline getTimeline() {
return timeline;
}
public DoubleProperty getX() {
return x;
}
public DoubleProperty getY() {
return y;
}
}
+15 -1
View File
@@ -1,6 +1,8 @@
package seng302.models;
import javafx.scene.paint.Color;
import seng302.controllers.RaceViewController;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@@ -12,6 +14,7 @@ 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;
@@ -33,6 +36,7 @@ public class Yacht {
// Mark rounding
private Long markRoundingTime;
/**
* Used in EventTest and RaceTest.
*
@@ -56,30 +60,37 @@ public class Yacht {
this.sourceID = id;
}
public Yacht(String boatType, Integer sourceID, String hullID, String shortName, String boatName, String country) {
public Yacht(String boatType, Integer sourceID, String hullID, String shortName,
String boatName, String country) {
this.boatType = boatType;
this.sourceID = sourceID;
this.hullID = hullID;
this.shortName = shortName;
this.boatName = boatName;
this.country = country;
this.position = "-";
}
public String getBoatType() {
return boatType;
}
public Integer getSourceID() {
return sourceID;
}
public String getHullID() {
return hullID;
}
public String getShortName() {
return shortName;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
return country;
}
@@ -97,6 +108,9 @@ public class Yacht {
}
public void setLegNumber(Integer legNumber) {
if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(sourceID)) {
RaceViewController.updateYachtPositionSparkline(this, legNumber);
}
this.legNumber = legNumber;
}
+15 -8
View File
@@ -14,6 +14,7 @@ public abstract class Mark {
/**
* Create a mark instance by passing its name and type
*
* @param name the name of the mark
* @param markType the type of mark. either GATE_MARK or SINGLE_MARK.
*/
@@ -47,20 +48,24 @@ public abstract class Mark {
}
/**
* Calculate the heading in radians from geographical location with latitude1, longitude 1 to geographical
* latitude2, longitude 2
* Calculate the heading in radians from geographical location with latitude1, longitude 1 to
* geographical latitude2, longitude 2
*
* @param longitude1 Longitude of first point in degrees
* @param longitude2 Longitude of second point in degrees
* @param latitude1 Latitude of first point in degrees
* @param latitude2 Latitude of first point in degrees
* @return Heading in radians
*/
public static double calculateHeadingRad (Double latitude1, Double longitude1, Double latitude2, Double longitude2) {
public static double calculateHeadingRad(Double latitude1, Double longitude1, Double latitude2,
Double longitude2) {
latitude1 = Math.toRadians(latitude1);
latitude2 = Math.toRadians(latitude2);
Double longDiff = Math.toRadians(longitude2 - longitude1);
Double y = Math.sin(longDiff) * Math.cos(latitude2);
Double x = Math.cos(latitude1)*Math.sin(latitude2)-Math.sin(latitude1)*Math.cos(latitude2)*Math.cos(longDiff);
Double x =
Math.cos(latitude1) * Math.sin(latitude2) - Math.sin(latitude1) * Math.cos(latitude2)
* Math.cos(longDiff);
return Math.atan2(y, x);
}
@@ -80,8 +85,8 @@ public abstract class Mark {
}
/**
* Calculate the distance in meters from geographical location with latitude1, longitude 1 to geographical
* latitude2, longitude 2
* Calculate the distance in meters from geographical location with latitude1, longitude 1 to
* geographical latitude2, longitude 2
*
* @param longitude1 Longitude of first point in degrees
* @param longitude2 Longitude of second point in degrees
@@ -89,14 +94,16 @@ public abstract class Mark {
* @param latitude2 Latitude of first point in degrees
* @return Distance in meters
*/
public static Double calculateDistance (Double latitude1, Double longitude1, Double latitude2, Double longitude2) {
public static Double calculateDistance(Double latitude1, Double longitude1, Double latitude2,
Double longitude2) {
Double theta = longitude1 - longitude2;
Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) +
Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) *
Math.cos(Math.toRadians(theta));
dist = Math.acos(dist);
dist = Math.toDegrees(dist);
dist = dist * 60 * 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute)
dist = dist * 60
* 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute)
dist = dist * 1609.344; //ratio of miles to metres
return dist;
}
@@ -9,7 +9,7 @@ import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
/**
* Created by CJIRWIN on 26/04/2017.
* Grouping of javaFX objects needed to represent a Mark on screen.
*/
public class MarkGroup extends Group {
@@ -5,5 +5,5 @@ package seng302.models.mark;
* Created by Haoming Yin (hyi25) on 17/3/17.
*/
public enum MarkType {
SINGLE_MARK, OPEN_GATE, CLOSED_GATE
SINGLE_MARK, OPEN_GATE
}
@@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
@@ -60,14 +61,12 @@ public class StreamParser extends Thread{
}
/**
* Used to within threading so when the stream parser thread runs, it will keep looking for a packet to
* process until it is unable to find anymore packets
*
* Used to within threading so when the stream parser thread runs, it will keep looking for a
* packet to process until it is unable to find anymore packets
*/
public void run() {
appRunning = true;
try {
System.out.println("[CLIENT] Start of stream");
streamStatus = true;
xmlObject = new XMLParser();
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
@@ -87,10 +86,8 @@ public class StreamParser extends Thread{
/**
* Used to start the stream parser thread when multithreading
*
*/
public void start() {
System.out.println("[CLIENT] Starting " + threadName );
if (t == null) {
t = new Thread(this, threadName);
t.start();
@@ -145,10 +142,8 @@ public class StreamParser extends Thread{
break;
default:
break;
//System.out.println(packet.getType().toString());
}
}
catch (NullPointerException e){
} catch (NullPointerException e) {
System.out.println("Error parsing packet");
}
}
@@ -179,9 +174,10 @@ public class StreamParser extends Thread{
}
/**
* Extracts the useful race status data from race status type packets. This method will also print to the
* console the current state of the race (if it has started/finished or is about to start), along side
* this it'll also display the amount of time since the race has started or time till it starts
* Extracts the useful race status data from race status type packets. This method will also
* print to the console the current state of the race (if it has started/finished or is about to
* start), along side this it'll also display the amount of time since the race has started or
* time till it starts
*
* @param packet Packet parsed in to use the payload
*/
@@ -201,20 +197,18 @@ public class StreamParser extends Thread{
format.setTimeZone(TimeZone.getTimeZone(getTimeZoneString()));
currentTimeString = format.format((new Date(currentTime)).getTime());
}
long timeTillStart = ((new Date (expectedStartTime)).getTime() - (new Date (currentTime)).getTime())/1000;
long timeTillStart =
((new Date(expectedStartTime)).getTime() - (new Date(currentTime)).getTime()) / 1000;
if (timeTillStart > 0) {
timeSinceStart = timeTillStart;
//System.out.println("Time till start: " + timeTillStart + " Seconds");
} else {
if (raceStatus == 4 || raceStatus == 8) {
raceFinished = true;
raceStarted = false;
System.out.println("[CLIENT] Race has finished");
} else if (!raceStarted) {
raceStarted = true;
raceFinished = false;
System.out.println("[CLIENT] Race has started");
}
timeSinceStart = timeTillStart;
}
@@ -226,17 +220,19 @@ public class StreamParser extends Thread{
int raceType = payload[23];
boatsPos = new TreeMap<>();
for (int i = 0; i < noBoats; i++) {
long boatStatusSourceID = bytesToLong(Arrays.copyOfRange(payload,24 + (i * 20),28+ (i * 20)));
long boatStatusSourceID = bytesToLong(
Arrays.copyOfRange(payload, 24 + (i * 20), 28 + (i * 20)));
Yacht boat = boats.get((int) boatStatusSourceID);
boat.setBoatStatus((int) payload[28 + (i * 20)]);
boat.setLegNumber((int) payload[29 + (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)));
Long estTimeAtNextMark = bytesToLong(
Arrays.copyOfRange(payload, 32 + (i * 20), 38 + (i * 20)));
boat.setEstimateTimeAtNextMark(estTimeAtNextMark);
Long estTimeAtFinish = bytesToLong(Arrays.copyOfRange(payload,38 + (i * 20),44+ (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;
// boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)];
// boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)];
@@ -268,7 +264,8 @@ public class StreamParser extends Thread{
for (int i = 0; i < numOfLines; i++) {
int lineNum = payload[4 + totalLen];
int textLength = payload[5 + totalLen];
byte[] messageTextBytes = Arrays.copyOfRange(payload,6 + totalLen,6 + textLength + totalLen);
byte[] messageTextBytes = Arrays
.copyOfRange(payload, 6 + totalLen, 6 + textLength + totalLen);
String messageText = new String(messageTextBytes);
totalLen += 2 + textLength;
}
@@ -285,7 +282,8 @@ public class StreamParser extends Thread{
int messageType = payload[9];
long messageLength = bytesToLong(Arrays.copyOfRange(payload, 12, 14));
String xmlMessage = new String((Arrays.copyOfRange(payload,14,(int) (14 + messageLength)))).trim();
String xmlMessage = new String(
(Arrays.copyOfRange(payload, 14, (int) (14 + messageLength)))).trim();
//Create XML document Object
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
@@ -340,8 +338,8 @@ public class StreamParser extends Thread{
}
/**
* When a yacht action occurs this will parse the parse the byte array to retrieve the necessary info,
* currently unused
* When a yacht action occurs this will parse the parse the byte array to retrieve the necessary
* info, currently unused
*
* @param packet Packet parsed in to use the payload
*/
@@ -368,8 +366,8 @@ public class StreamParser extends Thread{
}
/**
* Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are all used
* All the other extra data is still being read and translated however is unused.
* Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are
* all used All the other extra data is still being read and translated however is unused.
*
* @param packet Packet parsed in to use the payload
*/
@@ -390,11 +388,13 @@ public class StreamParser extends Thread{
//type 1 is a racing yacht and type 3 is a mark, needed for updating positions of the mark and boat
if (deviceType == 1) {
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed);
BoatPositionPacket boatPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
heading, groundSpeed);
//add a new priority que to the boatPositions HashMap
if (!boatPositions.containsKey(boatId)) {
boatPositions.put(boatId, new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
boatPositions.put(boatId,
new PriorityBlockingQueue<>(256, new Comparator<BoatPositionPacket>() {
@Override
public int compare(BoatPositionPacket p1, BoatPositionPacket p2) {
return (int) (p1.getTimeValid() - p2.getTimeValid());
@@ -403,7 +403,8 @@ public class StreamParser extends Thread{
}
boatPositions.get(boatId).put(boatPacket);
} else if (deviceType == 3) {
BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon, heading, groundSpeed);
BoatPositionPacket markPacket = new BoatPositionPacket(boatId, timeValid, lat, lon,
heading, groundSpeed);
//add a new priority que to the boatPositions HashMap
if (!markPositions.containsKey(boatId)) {
@@ -452,13 +453,21 @@ public class StreamParser extends Thread{
ArrayList<String> windInfo = new ArrayList<>();
for (int i = 0; i < loopCount; i++) {
String wind = "WindId: " + payload[3 + (20 * i)];
wind += "\nTime: " + bytesToLong(Arrays.copyOfRange(payload,4 + (20 * i),10 + (20 * i)));
wind += "\nRaceId: " + bytesToLong(Arrays.copyOfRange(payload,10 + (20 * i),14 + (20 * i)));
wind += "\nWindDirection: " + bytesToLong(Arrays.copyOfRange(payload,14 + (20 * i),16 + (20 * i)));
wind += "\nWindSpeed: " + bytesToLong(Arrays.copyOfRange(payload,16 + (20 * i),18 + (20 * i)));
wind += "\nBestUpWindAngle: " + bytesToLong(Arrays.copyOfRange(payload,18 + (20 * i),20 + (20 * i)));
wind += "\nBestDownWindAngle: " + bytesToLong(Arrays.copyOfRange(payload,20 + (20 * i),22 + (20 * i)));
wind += "\nFlags: " + String.format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF)).replace(' ', '0');
wind +=
"\nTime: " + bytesToLong(Arrays.copyOfRange(payload, 4 + (20 * i), 10 + (20 * i)));
wind += "\nRaceId: " + bytesToLong(
Arrays.copyOfRange(payload, 10 + (20 * i), 14 + (20 * i)));
wind += "\nWindDirection: " + bytesToLong(
Arrays.copyOfRange(payload, 14 + (20 * i), 16 + (20 * i)));
wind += "\nWindSpeed: " + bytesToLong(
Arrays.copyOfRange(payload, 16 + (20 * i), 18 + (20 * i)));
wind += "\nBestUpWindAngle: " + bytesToLong(
Arrays.copyOfRange(payload, 18 + (20 * i), 20 + (20 * i)));
wind += "\nBestDownWindAngle: " + bytesToLong(
Arrays.copyOfRange(payload, 20 + (20 * i), 22 + (20 * i)));
wind += "\nFlags: " + String
.format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF))
.replace(' ', '0');
windInfo.add(wind);
}
}
@@ -594,7 +603,6 @@ public class StreamParser extends Thread{
public static void appClose() {
appRunning = false;
System.out.println("[CLIENT] Shutting down stream parser");
}
/**
@@ -44,7 +44,6 @@ public class StreamReceiver extends Thread {
}
public void start () {
System.out.println("[CLIENT] Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
@@ -155,6 +154,5 @@ public class StreamReceiver extends Thread {
public static void noMoreBytes(){
moreBytes = false;
System.out.println("[CLIENT] Shutting down stream receiver");
}
}
+4 -18
View File
@@ -29,8 +29,6 @@ public class ServerThread implements Runnable, Observer {
Thread runner = new Thread(this, threadName);
runner.setDaemon(true);
serverLog("Spawning Server", 0);
raceSimulator = new Simulator(BOAT_LOCATION_PERIOD);
raceSimulator.addObserver(this);
// run race simulator, so it can send boats' static location.
@@ -134,7 +132,6 @@ public class ServerThread implements Runnable, Observer {
* Starts an instance of the race simulator
*/
private void startRaceSim(){
serverLog("Starting Running Race Simulator", 0);
// set race started to true, so the simulator will start moving boats
raceSimulator.setRaceStarted(true);
}
@@ -143,7 +140,6 @@ public class ServerThread implements Runnable, Observer {
* Starts sending heartbeat messages to the client
*/
private void startSendingHeartbeats() {
serverLog("Sending Heartbeats", 0);
Timer t = new Timer();
t.schedule(new TimerTask() {
@@ -154,7 +150,7 @@ public class ServerThread implements Runnable, Observer {
try {
server.send(heartbeat);
} catch (IOException e) {
System.out.print("");
e.printStackTrace();
}
}
}, 0, HEARTBEAT_PERIOD);
@@ -174,14 +170,13 @@ public class ServerThread implements Runnable, Observer {
if (startTime < System.currentTimeMillis() && !raceStarted){
startRaceSim();
raceStarted = true;
serverLog("Race Started", 0);
}
else{
server.send(raceStartStatusMessage);
}
} catch (IOException e) {
System.out.print("");
e.printStackTrace();
}
}
}, 0, RACE_START_STATUS_PERIOD);
@@ -191,7 +186,6 @@ public class ServerThread implements Runnable, Observer {
* Start sending race start status messages until race starts
*/
private void startSendingRaceStatusMessages(){
serverLog("Sending race status messages", 0);
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
@@ -199,9 +193,8 @@ public class ServerThread implements Runnable, Observer {
Message raceStatusMessage = getRaceStatusMessage();
try {
server.send(raceStatusMessage);
} catch (IOException e) {
System.out.print("");
e.printStackTrace();
}
}
}, 0, RACE_STATUS_PERIOD);
@@ -218,17 +211,12 @@ public class ServerThread implements Runnable, Observer {
if (raceData != null){
server.send(raceData);
serverLog("Sending race data", 0);
}
if (boatData != null){
server.send(boatData);
serverLog("Sending boat data", 0);
}
if (regatta != null){
server.send(regatta);
serverLog("Sending regatta data", 0);
}
} catch (IOException e) {
serverLog("Couldn't send an XML Message: " + e.getMessage(), 0);
@@ -247,7 +235,6 @@ public class ServerThread implements Runnable, Observer {
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);
@@ -295,7 +282,7 @@ public class ServerThread implements Runnable, Observer {
}
} catch (IOException e) {
System.out.print("");
e.printStackTrace();
}
}
}, 0, BOAT_LOCATION_PERIOD);
@@ -322,7 +309,6 @@ public class ServerThread implements Runnable, Observer {
numOfBoatsFinished ++;
if (!boatsFinished.get(boat.getSourceID())) {
boatsFinished.put(boat.getSourceID(), true);
serverLog("Boat " + boat.getSourceID() + " finished the race", 1);
}
}
Message m = new BoatLocationMessage(boat.getSourceID(), 1, boat.getLat(),
@@ -28,7 +28,6 @@ class StreamingServerSocket {
}
void start(){
ServerThread.serverLog("Listening For Connections",0);
try {
client = socket.accept();
} catch (IOException e) {
@@ -39,7 +38,6 @@ class StreamingServerSocket {
}
else{
isServerStarted = true;
ServerThread.serverLog("client connected from " + client.socket().getInetAddress(),0);
}
}
@@ -54,7 +54,6 @@ public class Simulator extends Observable implements Runnable {
numOfFinishedBoats += moveBoat(boat, lapse);
}
}
//System.out.println(boats.get(0));
setChanged();
notifyObservers(boats);
@@ -65,8 +64,6 @@ public class Simulator extends Observable implements Runnable {
e.printStackTrace();
}
}
System.out.println("[SERVER] Race simulator has been terminated");
}
/**
+4
View File
@@ -182,3 +182,7 @@ Remove scroll bars
-fx-background-insets: 0;
-fx-padding: 0;
}
.chart{
-fx-background-color: #ffffff;
}
+9
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import javafx.scene.chart.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.shape.*?>
@@ -50,6 +51,14 @@
<Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
<Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" />
<ComboBox fx:id="boatSelectionComboBox" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
<LineChart fx:id="raceSparkLine" layoutX="-15.0" layoutY="719.0" legendVisible="false" prefHeight="277.0" prefWidth="260.0" title="Boat Positions">
<xAxis>
<CategoryAxis label="Leg Number" side="BOTTOM" styleClass="spark-line-xaxis" />
</xAxis>
<yAxis>
<NumberAxis fx:id="sparklineYAxis" minorTickCount="1" minorTickLength="1.0" side="LEFT" styleClass="spark-line-yaxis" tickLabelGap="1.0" tickUnit="1.0" upperBound="7.0" />
</yAxis>
</LineChart>
</children>
</AnchorPane>
<AnchorPane fx:id="contentAnchorPane" maxHeight="960.0" maxWidth="1280.0" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
-54
View File
@@ -1,54 +0,0 @@
package seng302;
import org.junit.Test;
import seng302.models.Leg;
import seng302.models.mark.SingleMark;
import static org.junit.Assert.assertEquals;
/**
* Unit test for the Leg class.
*/
public class LegTest {
/**
* Test creation of the leg by specifying a string
* for the marker label
*/
@Test
public void testLegCreationUsingMarkerLabel() {
Leg leg = new Leg(010, 100, "SingleMark");
assertEquals(leg.getHeading(), 010);
assertEquals(leg.getDistance(), 100);
assertEquals(leg.getMarkerLabel(), "SingleMark");
assertEquals(leg.getIsFinishingLeg(), false);
}
/**
* Test creation of the leg by providing a
* SingleMark object
*/
@Test
public void testLegCreation() {
Leg leg = new Leg(010, 100, new SingleMark("SingleMark"));
assertEquals(leg.getHeading(), 010);
assertEquals(leg.getDistance(), 100);
assertEquals(leg.getMarkerLabel(), "SingleMark");
assertEquals(leg.getIsFinishingLeg(), false);
}
/**
* Test changing whether or not a
* leg is the finishing leg
*/
@Test
public void testSetFinishLeg() {
Leg leg = new Leg(010, 100, "SingleMark");
leg.setFinishingLeg(true);
assertEquals(leg.getIsFinishingLeg(), true);
}
}
@@ -99,7 +99,7 @@ public class StreamReceiverTest {
byte[] emptyArray = {};
assert bytesToLong.invoke(streamReceiver, emptyArray).equals(0L);
} catch (Exception e){
System.out.println("");
e.printStackTrace();
}
}