Merge branch 'wake_remake' into Story30b_correcting_boat_movement

# Conflicts:
#	src/main/java/seng302/controllers/CanvasController.java
#	src/main/java/seng302/models/BoatGroup.java
#	src/main/java/seng302/models/parsers/StreamParser.java
This commit is contained in:
Peter Galloway
2017-05-01 19:09:09 +12:00
25 changed files with 1403 additions and 280 deletions
+13 -6
View File
@@ -12,21 +12,28 @@ public class App extends Application
{
@Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
primaryStage.setTitle("RaceVision");
primaryStage.setScene(new Scene(root));
primaryStage.show();
// StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1");
StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread1");
}
public static void main(String[] args) {
StreamReceiver sr;
if (args.length > 1){
sr = new StreamReceiver("localhost", 8085, "TestThread1");
}
else{
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1");
sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread1");
}
sr.start();
StreamParser streamParser = new StreamParser("TestThread2");
streamParser.start();
}
public static void main(String[] args) {
launch(args);
}
}
@@ -22,6 +22,7 @@ import seng302.models.parsers.StreamPacket;
import seng302.models.parsers.StreamParser;
import seng302.models.parsers.packets.BoatPositionPacket;
import java.sql.Time;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.PriorityBlockingQueue;
@@ -102,9 +103,6 @@ public class CanvasController {
fitMarksToCanvas();
drawBoats();
timer = new AnimationTimer() {
private int countdown = 60;
private int[] currentRaceMarker = {1, 1, 1, 1, 1, 1};
List<Mark> marks = raceViewController.getRace().getCourse();
@Override
public void handle(long now) {
@@ -116,19 +114,22 @@ public class CanvasController {
if (frameTimeIndex == 0) {
arrayFilled = true ;
}
long elapsedNanos;
if (arrayFilled) {
long elapsedNanos = now - oldFrameTime ;
elapsedNanos = now - oldFrameTime ;
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
drawFps(frameRate.intValue());
}
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
elapsedNanos = 1000 / 60;
updateRaceObjects();
}
};
for (Mark m : raceViewController.getRace().getCourse()) {
// System.out.println(m.getName());
System.out.println(m.getName());
}
//timer.start();
}
@@ -175,7 +176,7 @@ public class CanvasController {
class ResizableCanvas extends Canvas {
public ResizableCanvas() {
ResizableCanvas() {
// Redraw canvas when size changes.
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
@@ -232,39 +233,14 @@ public class CanvasController {
for (Boat boat : boats) {
BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor());
boatGroup.moveTo(startingX, startingY, 0d);
// boatGroup.setDestination(firstMarkX, firstMarkY);
boatGroup.forceRotation();
//group.getChildren().add(boatGroup);
raceObjects.add(boatGroup);
boatAnnotations.getChildren().add(boatGroup.getLowPriorityAnnotations());
// drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading());
}
group.getChildren().add(boatAnnotations);
group.getChildren().addAll(raceObjects);
}
/**
* Inner class for creating point so that you can rotate it around origin point.
*/
class Point {
double x, y;
Point (double x, double y) {
this.x = x;
this.y = y;
}
void rotate(double angle) {
double oldX = x;
double oldY = y;
this.x = oldX * Math.cos(angle) - oldY * Math.sin(angle);
this.y = oldX * Math.sin(angle) + oldY * Math.cos(angle);
}
}
/**
* Calculates x and y location for every marker that fits it to the canvas the race will be drawn on.
*/
@@ -1,14 +1,29 @@
package seng302.controllers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
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.Boat;
import seng302.models.parsers.StreamParser;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by michaelrausch on 21/03/17.
@@ -16,6 +31,20 @@ import java.util.ResourceBundle;
public class Controller implements Initializable {
@FXML
private AnchorPane contentPane;
@FXML
private Label timeTillLive;
@FXML
private Button streamButton;
@FXML
private Button switchToRaceViewButton;
@FXML
private TableView teamList;
@FXML
private TableColumn boatNameCol;
@FXML
private TableColumn shortNameCol;
@FXML
private TableColumn countryCol;
private void setContentPane(String jfxUrl){
try{
@@ -33,5 +62,71 @@ public class Controller implements Initializable {
@Override
public void initialize(URL location, ResourceBundle resources) {
}
/**
* Running a timer to update the livestream status on welcome screen. Update interval is 500 miliseconds.
*/
public void startStream() {
if (StreamParser.isStreamStatus()) {
streamButton.setVisible(false);
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.isRaceFinished()) {
timeTillLive.setTextFill(Color.RED);
timeTillLive.setText("Race finished! Waiting for new race...");
switchToRaceViewButton.setDisable(true);
} else if (StreamParser.getTimeSinceStart() > 0 && StreamParser.getTimeSinceStart() % 10 == 0) {
updateTeamList();
timeTillLive.setTextFill(Color.RED);
switchToRaceViewButton.setDisable(false);
Long timerMinute = StreamParser.getTimeSinceStart() / 60;
Long timerSecond = StreamParser.getTimeSinceStart() % 60;
String timerString = "-" + timerMinute + "." + timerSecond + " minutes";
timeTillLive.setText(timerString);
} else if (StreamParser.getTimeSinceStart() % 10 == 0) {
updateTeamList();
timeTillLive.setTextFill(Color.BLACK);
switchToRaceViewButton.setDisable(false);
Long timerMinute = -1 * StreamParser.getTimeSinceStart() / 60;
Long timerSecond = -1 * StreamParser.getTimeSinceStart() % 60;
String timerString = timerMinute + "." + timerSecond + " minutes";
timeTillLive.setText(timerString);
}
});
}
}, 0, 500);
} else {
timeTillLive.setText("Stream not available.");
timeTillLive.setTextFill(Color.RED);
}
}
public void switchToRaceView() {
setContentPane("/views/RaceView.fxml");
}
private void updateTeamList() {
ObservableList<Boat> data = FXCollections.observableArrayList();
teamList.setItems(data);
boatNameCol.setCellValueFactory(
new PropertyValueFactory<Boat,String>("boatName")
);
shortNameCol.setCellValueFactory(
new PropertyValueFactory<Boat,String>("shortName")
);
countryCol.setCellValueFactory(
new PropertyValueFactory<Boat,String>("country")
);
for (Boat boat : StreamParser.getBoats()) {
data.add(boat);
}
}
}
@@ -9,13 +9,17 @@ import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.CheckBox;
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.text.Text;
import javafx.util.Duration;
import javafx.util.StringConverter;
import seng302.models.*;
import seng302.models.parsers.ConfigParser;
import seng302.models.parsers.StreamParser;
import java.io.IOException;
import java.util.*;
@@ -27,7 +31,7 @@ public class RaceViewController extends Thread{
@FXML
private VBox positionVbox;
@FXML
private CheckBox toggleAnnotation, toggleFps;
private CheckBox toggleFps;
@FXML
private Text timerLabel;
@FXML
@@ -35,10 +39,11 @@ public class RaceViewController extends Thread{
@FXML
private Text windArrowText, windDirectionText;
@FXML
private Slider annotationSlider;
@FXML
private CanvasController includedCanvasController;
private ArrayList<Boat> startingBoats = new ArrayList<>();
private boolean displayAnnotations;
private boolean displayFps;
private Timeline timerTimeline;
private Map<Boat, TimelineInfo> timelineInfos = new HashMap<>();
@@ -62,7 +67,7 @@ public class RaceViewController extends Thread{
includedCanvasController.setup(this);
includedCanvasController.initializeCanvas();
//initializeTimer();
initializeTimer();
initializeSettings();
//set wind direction!!!!!!! can't find another place to put my code --haoming
@@ -74,22 +79,50 @@ public class RaceViewController extends Thread{
private void initializeSettings(){
displayAnnotations = true;
private void initializeSettings() {
displayFps = true;
toggleAnnotation.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
displayAnnotations = !displayAnnotations;
}
});
toggleFps.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
displayFps = !displayFps;
}
});
//SLIFER STUFF BELOW
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";
return "All";
}
@Override
public Double fromString(String s) {
switch (s) {
case "None":
return 0d;
case "Low":
return 1d;
case "Medium":
return 2d;
case "All":
return 3d;
default:
return 3d;
}
}
});
annotationSlider.valueProperty().addListener((obs, oldval, newVal) ->
setAnnotations((int)annotationSlider.getValue()));
annotationSlider.setValue(3);
}
private void initializeTimer(){
@@ -99,12 +132,11 @@ public class RaceViewController extends Thread{
timerTimeline.getKeyFrames().add(
new KeyFrame(Duration.seconds(1),
event -> {
// Stop timer if race is finished
if (this.race.isRaceFinished()) {
this.timerTimeline.stop();
if (StreamParser.isRaceFinished()) {
timerLabel.setFill(Color.RED);
timerLabel.setText("Race Finished!");
} else {
timerLabel.setText(convertTimeToMinutesSeconds(race.getRaceTime()));
this.race.incrementRaceTime();
timerLabel.setText(currentTimer());
}
})
);
@@ -259,6 +291,20 @@ public class RaceViewController extends Thread{
return String.format("%02d:%02d", time / 60, time % 60);
}
private String currentTimer() {
String timerString = "0:00 minutes";
if (StreamParser.getTimeSinceStart() > 0 && StreamParser.getTimeSinceStart() % 10 == 0) {
Long timerMinute = StreamParser.getTimeSinceStart() / 60;
Long timerSecond = StreamParser.getTimeSinceStart() % 60;
timerString = "-" + timerMinute + "." + timerSecond + " minutes";
} else if (StreamParser.getTimeSinceStart() % 10 == 0) {
Long timerMinute = -1 * StreamParser.getTimeSinceStart() / 60;
Long timerSecond = -1 * StreamParser.getTimeSinceStart() % 60;
timerString = timerMinute + "." + timerSecond + " minutes";
}
return timerString;
}
public void stopTimer() {
timerTimeline.stop();
}
@@ -270,10 +316,6 @@ public class RaceViewController extends Thread{
return displayFps;
}
public boolean isDisplayAnnotations() {
return displayAnnotations;
}
public Race getRace() {
return race;
}
@@ -286,10 +328,54 @@ public class RaceViewController extends Thread{
return startingBoats;
}
@FXML
private void toggleAnnotations () {
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
ro.toggleAnnotations();
private void setAnnotations(Integer annotationLevel) {
switch (annotationLevel) {
case 0:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(false);
bg.setVelocityObjectVisible(false);
bg.setLineGroupVisible(false);
bg.setWakeVisible(false);
}
}
break;
case 1:
for (RaceObject ro : includedCanvasController.getRaceObjects()) {
if(ro instanceof BoatGroup) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(false);
bg.setLineGroupVisible(false);
bg.setWakeVisible(false);
}
}
break;
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) {
BoatGroup bg = (BoatGroup) ro;
bg.setTeamNameObjectVisible(true);
bg.setVelocityObjectVisible(true);
bg.setLineGroupVisible(true);
bg.setWakeVisible(true);
}
}
break;
}
}
}
+30
View File
@@ -21,6 +21,10 @@ public class Boat {
private int markLastPast;
private String shortName;
private int id;
// new attributes to boat
private int sourceID;
private String boatName;
private String country;
public Boat(String teamName) {
this.teamName = teamName;
@@ -45,6 +49,21 @@ public class Boat {
this.id = id;
}
/**
* New instance created by BoatsParser.
*
* @param sourceID source ID of the boat
* @param boatName full name of the boat
* @param shortName short name of the boat
* @param country country of the boat
*/
public Boat(int sourceID, String boatName, String shortName, String country) {
this.sourceID = sourceID;
this.boatName = boatName;
this.shortName = shortName;
this.country = country;
}
/**
* Returns the name of the team sailing the boat
*
@@ -141,4 +160,15 @@ public class Boat {
return id;
}
public int getSourceID() {
return sourceID;
}
public String getBoatName() {
return boatName;
}
public String getCountry() {
return country;
}
}
+132 -61
View File
@@ -10,7 +10,9 @@ import javafx.scene.transform.Rotate;
import seng302.models.parsers.StreamParser;
/**
* Created by CJIRWIN on 25/04/2017.
* 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.
*/
public class BoatGroup extends RaceObject{
@@ -18,37 +20,50 @@ public class BoatGroup extends RaceObject{
private static final double TEAMNAME_Y_OFFSET = -15d;
private static final double VELOCITY_X_OFFSET = 10d;
private static final double VELOCITY_Y_OFFSET = -5d;
private static final double VELOCITY_WAKE_RATIO = 2d;
private static final double BOAT_HEIGHT = 15d;
private static final double BOAT_WIDTH = 10d;
private static final int LINE_INTERVAL = 180;
private static double expectedUpdateInterval = 200;
private static int WAKE_FRAME_INTERVAL = 30;
private double framesForNewLine = 0;
private boolean destinationSet;
private Point2D lastPoint;
private int wakeGenerationDelay;
private int wakeGenerationDelay = 10;
private double distanceTravelled;
private Boat boat;
private int wakeCounter = WAKE_FRAME_INTERVAL;
private Group lineGroup = new Group();
private Group wakeGroup = new Group();
private Polygon boatPoly;
private Polygon wakePoly;
private Text teamNameObject;
private Text velocityObject;
private Wake wake;
/**
* 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 color The colour of the boat polygon and the trailing line.
*/
public BoatGroup (Boat 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.
*/
public BoatGroup (Boat 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.
*/
private void initChildren (Color color, double... points) {
boatPoly = new Polygon(points);
boatPoly.setFill(color);
@@ -65,16 +80,19 @@ public class BoatGroup extends RaceObject{
velocityObject.relocate(velocityObject.getX(), velocityObject.getY());
destinationSet = false;
wake = new Wake(0, 0);
wakeGenerationDelay = wake.numWakes;
wake = new Wake(0, -BOAT_HEIGHT);
super.getChildren().addAll(teamNameObject, velocityObject, boatPoly);
}
/**
* 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) {
initChildren(color,
-BOAT_WIDTH / 2, BOAT_HEIGHT,
0.0, 0.0,
BOAT_WIDTH / 2, BOAT_HEIGHT);
initChildren(color,
-BOAT_WIDTH / 2, BOAT_HEIGHT / 2,
0.0, -BOAT_HEIGHT / 2,
BOAT_WIDTH / 2, BOAT_HEIGHT / 2);
}
/**
@@ -98,12 +116,18 @@ 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 rotation The heading in degrees from north the boat should rotate to.
*/
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) {
boatPoly.setLayoutX(x);
boatPoly.setLayoutY(y);
@@ -116,15 +140,20 @@ public class BoatGroup extends RaceObject{
wake.rotate(currentRotation);
}
/**
* 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.
*/
public void updatePosition (long timeInterval) {
//Calculate the movement of the boat.
double dx = pixelVelocityX * timeInterval;
double dy = pixelVelocityY * timeInterval;
double rotation = 0d;
double rotation = rotationalVelocity * timeInterval;
distanceTravelled += Math.abs(dx) + Math.abs(dy);
moveGroupBy(dx, dy, rotation);
if (framesForNewLine-- == 0) {
framesForNewLine = LINE_INTERVAL;
//Draw a new section of the trail every 20 pixels of movement.
if (distanceTravelled > 20) {
distanceTravelled = 0;
if (lastPoint != null) {
Line l = new Line(
lastPoint.getX(),
@@ -132,37 +161,69 @@ public class BoatGroup extends RaceObject{
boatPoly.getLayoutX(),
boatPoly.getLayoutY()
);
l.getStrokeDashArray().setAll(4d, 6d);
l.getStrokeDashArray().setAll(3d, 7d);
l.setStroke(boatPoly.getFill());
lineGroup.getChildren().add(l);
}
if (destinationSet){
if (destinationSet){ //Only begin drawing after the first destination is set
lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY());
}
if (lineGroup.getChildren().size() > 100)
lineGroup.getChildren().remove(0);
}
wake.updatePosition(timeInterval);
}
/**
* Sets the destination of the boat and the headng it should have once it reaches
* @param newXValue
* @param newYValue
* @param rotation Rotation to move graphics to.
* @param raceIds RaceID of the object to move.
*/
public void setDestination (double newXValue, double newYValue, double rotation, int... raceIds) {
moveGroupBy(newXValue - boatPoly.getLayoutX(), newYValue - boatPoly.getLayoutY(), rotation);
// destinationSet = true;
// boat.setVelocity(StreamParser.boatSpeeds.get((long)boat.getId()));
// if (hasRaceId(raceIds)) {
// this.pixelVelocityX = (newXValue - boatPoly.getLayoutX()) / expectedUpdateInterval;
// this.pixelVelocityY = (newYValue - boatPoly.getLayoutY()) / expectedUpdateInterval;
// this.rotationalGoal = rotation;
// calculateRotationalVelocity();
// rotateTo(rotation);
// if (wakeGenerationDelay > 0) {
// wake.rotate(rotationalGoal);
// wakeGenerationDelay--;
if (hasRaceId(raceIds)) {
destinationSet = true;
boat.setVelocity(StreamParser.boatSpeeds.get((long)boat.getId()));
if (currentRotation < 0)
currentRotation = 360 - currentRotation;
double dx = newXValue - boatPoly.getLayoutX();
if ((dx > 0 && pixelVelocityX < 0) || (dx < 0 && pixelVelocityX > 0)) {
pixelVelocityX = 0;
} else {
pixelVelocityX = dx / expectedUpdateInterval;
}
double dy = newYValue - boatPoly.getLayoutY();
//Check movement is reasonable. Assumes a 1000 * 1000 canvas
if (Math.abs(dx) > 50 || Math.abs(dy) > 50) {
// System.out.println("dx = " + dx);
// System.out.println("dy = " + dy);
dx = 0;
dy = 0;
moveTo(newXValue, newYValue);
}
//Slight delay on changing X/Y direction that could help jitter. Disabled since there was an issue with
//packets that might be causing it.
// if ((dx > 0 && pixelVelocityX < 0) || (dx < 0 && pixelVelocityX > 0)) {
// pixelVelocityX = 0;
// } else {
// wake.setRotationalVelocity(rotationalVelocity, rotationalGoal, pixelVelocityX, pixelVelocityY);
// pixelVelocityX = dx / expectedUpdateInterval;
// }
// }
// if ((dy > 0 && pixelVelocityY < 0) || (dy < 0 && pixelVelocityY > 0)) {
// pixelVelocityY = 0;
// } else {
// pixelVelocityY = dy / expectedUpdateInterval;
// }
pixelVelocityX = dx / expectedUpdateInterval;
pixelVelocityY = dy / expectedUpdateInterval;
rotationalGoal = rotation;
calculateRotationalVelocity();
if (wakeGenerationDelay > 0) {
wake.rotate(rotationalGoal);
wakeGenerationDelay--;
} else {
wake.setRotationalVelocity(rotationalVelocity, currentRotation, boat.getVelocity());
}
velocityObject.setText(String.format("%.2f m/s", boat.getVelocity()));
}
}
public void setDestination (double newXValue, double newYValue, int... raceIDs) {
@@ -180,45 +241,43 @@ public class BoatGroup extends RaceObject{
}
}
void resizeWake(){
velocityObject.setText(String.valueOf(boat.getVelocity()));
super.getChildren().remove(wakePoly);
wakePoly = new Polygon(
5.0,0.0,
10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO,
0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO
);
wakePoly.setLayoutX(boatPoly.getLayoutX());
wakePoly.setLayoutY(boatPoly.getLayoutY());
wakePoly.setFill(Color.DARKBLUE);
super.getChildren().add(wakePoly);
}
public void rotateTo (double rotation) {
currentRotation = rotation;
boatPoly.getTransforms().clear();
boatPoly.getTransforms().add(new Rotate(rotation));
}
public void forceRotation () {
rotateTo (rotationalGoal);
wake.rotate(rotationalGoal);
}
public void toggleAnnotations () {
teamNameObject.setVisible(!teamNameObject.isVisible());
velocityObject.setVisible(!velocityObject.isVisible());
lineGroup.setVisible(!lineGroup.isVisible());
wake.setVisible(!wake.isVisible());
public void setTeamNameObjectVisible(Boolean visible) {
teamNameObject.setVisible(visible);
}
public void setVelocityObjectVisible(Boolean visible) {
velocityObject.setVisible(visible);
}
public void setLineGroupVisible(Boolean visible) {
lineGroup.setVisible(visible);
}
public void setWakeVisible(Boolean visible) {
wake.setVisible(visible);
}
public Boat getBoat() {
return boat;
}
/**
* 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) {
for (int id : raceIds) {
if (id == boat.getId())
@@ -227,10 +286,22 @@ public class BoatGroup extends RaceObject{
return false;
}
/**
* Returns all raceIds associated with this group. For BoatGroups the ID's are for the boat.
*
* @return An array containing all ID's associated with this RaceObject.
*/
public int[] getRaceIds () {
return new int[] {boat.getId()};
}
/**
* 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 () {
Group group = new Group();
group.getChildren().addAll(wake, lineGroup);
+22 -7
View File
@@ -26,10 +26,16 @@ public abstract class RaceObject extends Group {
return expectedUpdateInterval;
}
/**
*
*/
public static void setExpectedUpdateInterval(double expectedUpdateInterval) {
RaceObject.expectedUpdateInterval = expectedUpdateInterval;
}
/**
* Calculates the rotational velocity required to reach the rotationalGoal from the currentRotation.
*/
protected void calculateRotationalVelocity () {
if (Math.abs(rotationalGoal - currentRotation) > 180) {
if (rotationalGoal - currentRotation >= 0) {
@@ -40,18 +46,29 @@ public abstract class RaceObject extends Group {
} else {
this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval;
}
//Sometimes the rotation is too large to be realistic. In that case just do it instantly.
if (Math.abs(rotationalVelocity) > 1) {
rotationalVelocity = 0;
rotateTo(rotationalGoal);
}
}
/**
* Sets the destination of everything within the RaceObject that has an ID in the array raceIds. The destination is
* set to the co-ordinates (x, y) with the given rotation.
* @param x
* @param y
* @param rotation
* @param raceIds
* @param x X co-ordinate to move the graphics to.
* @param y Y co-ordinate to move the graphics to.
* @param rotation Rotation to move graphics to.
* @param raceIds RaceID of the object to move.
*/
public abstract void setDestination (double x, double y, double rotation, int... raceIds);
/**
* Sets the destination of everything within the RaceObject that has an ID in the array raceIds. The destination is
* set to the co-ordinates (x, y).
* @param x X co-ordinate to move the graphic to.
* @param y Y co-ordinate to move the graphic to.
* @param raceIds RaceID to the object to move.
*/
public abstract void setDestination (double x, double y, int... raceIds);
public abstract void updatePosition (long timeInterval);
@@ -67,6 +84,4 @@ public abstract class RaceObject extends Group {
public abstract boolean hasRaceId (int... raceIds);
public abstract int[] getRaceIds ();
public abstract void toggleAnnotations ();
}
+18 -9
View File
@@ -15,12 +15,13 @@ import javafx.scene.transform.Rotate;
*/
class Wake extends Group {
final int numWakes = 5;
private int numWakes = 5;
private double[] velocities = new double[13];
private Arc[] arcs = new Arc[numWakes];
private double[] rotations = new double[numWakes];
private int[] velocityIndices = new int[numWakes];
private double sum = 0;
private static double max;
/**
* Create a wake at the given location.
@@ -46,11 +47,19 @@ class Wake extends Group {
* Sets the rotationalVelocity of each arc. Each arc is 3 velocities behind the next smallest arc. The smallest uses
* the latest given velocity.
* @param rotationalVelocity The rotationalVelocity the wake should move at.
* @param rotationGoal Where the wake will rotate to if the wake is calculated to be on a straight section. This is
* used to prevent desynchronisation with the Boat polygon.
* @param velocity The real world velocity of the boat in m/s.
*/
void setRotationalVelocity (double rotationalVelocity, double rotationGoal, double velocityX, double velocityY) {
sum -= Math.abs(velocities[velocityIndices[0]]);
void setRotationalVelocity (double rotationalVelocity, double rotationGoal, double velocity) {
// if (Math.abs(rotationalVelocity) > 0.5) {
// rotationalVelocity = 0;
// }
sum -= Math.abs(velocities[(velocityIndices[0] + 10) % 13]);
sum += Math.abs(rotationalVelocity);
if (sum < 0.0001)
// System.out.println("sum = " + sum);
max = Math.max(max, rotationalVelocity);
if (sum < max)
rotate (rotationGoal); //In relatively straight segments the wake snaps to match the boats current position.
//This stops the wake from eventually becoming out of sync with the boat.
@@ -61,14 +70,14 @@ class Wake extends Group {
for (int i = 1; i < numWakes; i++)
velocityIndices[i] = (velocityIndices[0] + 3 * i) % 13;
//Scale wakes based on velocity. Assumes boats are always moving at a decent pace.
double scaleFactor = Math.abs(Math.log10(Math.abs(velocityX) + Math.abs(velocityY)));
double baseRad = 30;
//Scale wakes based on velocity.
double baseRad = 20;
double rad;
for (Arc arc :arcs) {
double rad = Math.min(baseRad + 4 * scaleFactor, baseRad + 12);
rad = baseRad + velocity;
arc.setRadiusX(rad);
arc.setRadiusY(rad);
baseRad += 10;
baseRad += 5 + (velocity / 2);
}
}
+2 -2
View File
@@ -17,10 +17,10 @@ public abstract class Mark {
* @param name the name of the mark
* @param markType the type of mark. either GATE_MARK or SINGLE_MARK.
*/
public Mark (String name, MarkType markType) {
public Mark (String name, MarkType markType, int id) {
this.name = name;
this.markType = markType;
id = 0;
this.id = id;
}
public Mark(String name, MarkType markType, double latitude, double longitude) {
+129 -98
View File
@@ -28,6 +28,9 @@ public class MarkGroup extends RaceObject {
private Point2D[] nodeDestinations;
public MarkGroup (Mark mark, Point2D... points) {
nodePixelVelocitiesX = new double[points.length];
nodePixelVelocitiesY = new double[points.length];
nodeDestinations = new Point2D[points.length];
marks.add(mark);
mainMark = mark;
Color color = Color.BLACK;
@@ -36,33 +39,58 @@ public class MarkGroup extends RaceObject {
} else if (mark.getName().equals("Finish")){
color = Color.RED;
}
System.out.println("HERE ARE THE CHILDREN LOL");
Circle markCircle;
if (mark.getMarkType() == MarkType.SINGLE_MARK) {
super.getChildren().add(new Circle(0, 0, MARK_RADIUS, color));
markCircle = new Circle(
points[0].getX(),
points[0].getY(),
MARK_RADIUS,
color
);
nodeDestinations = new Point2D[]{
new Point2D(markCircle.getCenterX(), markCircle.getCenterY()
)
};
super.getChildren().add(markCircle);
} else {
marks.add(((GateMark) mark).getSingleMark1());
marks.add(((GateMark) mark).getSingleMark2());
super.getChildren().add(
new Circle(
(points[1].getX() - points[0].getX()) / 2d,
(points[1].getY() - points[0].getY()) / 2d,
MARK_RADIUS,
color
)
nodePixelVelocitiesX = new double[]{0d,0d};
nodePixelVelocitiesY = new double[]{0d,0d};
nodeDestinations = new Point2D[2];
// markCircle = new Circle(
// (points[1].getX() - points[0].getX()) / 2d,
// (points[1].getY() - points[0].getY()) / 2d,
// MARK_RADIUS,
// color
//
markCircle = new Circle(
points[0].getX(),
points[0].getY(),
MARK_RADIUS,
color
);
super.getChildren().add(
new Circle(
-(points[1].getX() - points[0].getX()) / 2d,
-(points[1].getY() - points[0].getY()) / 2d,
MARK_RADIUS,
color
)
nodeDestinations[0] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
super.getChildren().add(markCircle);
// markCircle = new Circle(
// -(points[1].getX() - points[0].getX()) / 2d,
// -(points[1].getY() - points[0].getY()) / 2d,
// MARK_RADIUS,
// color
// );
markCircle = new Circle(
points[1].getX(),
points[1].getY(),
MARK_RADIUS,
color
);
nodeDestinations[1] = new Point2D(markCircle.getCenterX(), markCircle.getCenterY());
super.getChildren().add(markCircle);
Line line = new Line(
(points[1].getX() - points[0].getX()) / 2d,
(points[1].getY() - points[0].getY()) / 2d,
-(points[1].getX() - points[0].getX()) / 2d,
-(points[1].getY() - points[0].getY()) / 2d
points[0].getX(),
points[0].getY(),
points[1].getX(),
points[1].getY()
);
line.setStrokeWidth(LINE_THICKNESS);
line.setStroke(color);
@@ -70,14 +98,8 @@ public class MarkGroup extends RaceObject {
line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN);
}
super.getChildren().add(line);
nodePixelVelocitiesX = new double[]{0d,0d};
nodePixelVelocitiesY = new double[]{0d,0d};
nodeDestinations = new Point2D[]{
new Point2D(super.getChildren().get(0).getLayoutX(), super.getChildren().get(0).getLayoutY()),
new Point2D(super.getChildren().get(1).getLayoutX(), super.getChildren().get(1).getLayoutY())
};
}
moveTo(points[0].getX(), points[0].getY());
//moveTo(points[0].getX(), points[0].getY());
}
public void setDestination (double x, double y, double rotation, int... raceIds) {
@@ -87,88 +109,90 @@ public class MarkGroup extends RaceObject {
}
public void setDestination (double x, double y, int... raceIds) {
int childrenIndex = -1;
for (Mark mark : marks) {
for (int i = 0; i < marks.size(); i++)
for (int id : raceIds)
if (id == mark.getId() && childrenIndex != -1)
setDestinationChild(x, y, childrenIndex);
else if (id == mark.getId())
setDestinationGroup(x, y);
childrenIndex++;
}
if (id == marks.get(i).getId())
setDestinationChild(x, y, Math.max(0, i-1));
}
private void setDestinationChild (double x, double y, int childIndex) {
double relativeX = x - super.getLayoutX();
double relativeY = y - super.getLayoutY();
this.nodeDestinations[childIndex] = new Point2D(relativeX, relativeY);
this.nodePixelVelocitiesX[childIndex] = (relativeX - super.getChildren().get(childIndex).getLayoutX()) / expectedUpdateInterval;
this.nodePixelVelocitiesY[childIndex] = (relativeY - super.getChildren().get(childIndex).getLayoutY()) / expectedUpdateInterval;
//double relativeX = x - super.getLayoutX();
//double relativeY = y - super.getLayoutY();
Circle markCircle = (Circle) super.getChildren().get(childIndex);
this.nodeDestinations[childIndex] = new Point2D(x, y);
//if (Math.abs(relativeX - markCircle.getCenterX()) > 30 && Math.abs(relativeY - markCircle.getCenterY()) > 30) {
this.nodePixelVelocitiesX[childIndex] = (x - markCircle.getCenterX()) / expectedUpdateInterval;
this.nodePixelVelocitiesY[childIndex] = (y - markCircle.getCenterY()) / expectedUpdateInterval;
//}
}
private void setDestinationGroup (double x, double y) {
pixelVelocityX = (x - super.getLayoutX()) / expectedUpdateInterval;
pixelVelocityY = (y - super.getLayoutY()) / expectedUpdateInterval;
}
public void rotateTo (double rotation) {
super.getTransforms().clear();
super.getTransforms().add(new Rotate(rotation));
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
Line line = (Line) super.getChildren().get(2);
double xCenter = Math.abs(line.getEndX() - line.getStartX());
double yCenter = Math.abs(line.getEndY() - line.getStartY());
super.getTransforms().setAll(new Rotate(rotation, xCenter, yCenter));
}
}
public void updatePosition (long timeInterval) {
double x = pixelVelocityX * timeInterval;
double y = pixelVelocityY * timeInterval;
double rotation = rotationalVelocity * timeInterval;
moveGroupBy(x, y, rotation);
updateChildren(timeInterval);
Circle markCircle = (Circle) super.getChildren().get(0);
if (nodePixelVelocitiesX[0] > 0 && markCircle.getCenterX() > nodeDestinations[0].getX() ||
nodePixelVelocitiesX[0] < 0 && markCircle.getCenterX() < nodeDestinations[0].getY())
nodePixelVelocitiesX[0] = 0;
else if (nodePixelVelocitiesX[0] != 0)
markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[0] * timeInterval);
if (nodePixelVelocitiesY[0] > 0 && markCircle.getCenterY() > nodeDestinations[0].getY() ||
nodePixelVelocitiesY[0] < 0 && markCircle.getCenterY() < nodeDestinations[0].getY())
nodePixelVelocitiesY[0] = 0;
else if (nodePixelVelocitiesY[0] != 0)
markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[0] * timeInterval);
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
Line line = (Line) super.getChildren().get(2);
line.setStartX(markCircle.getCenterX());
line.setStartY(markCircle.getCenterY());
markCircle = (Circle) super.getChildren().get(1);
if (nodePixelVelocitiesX[1] > 0 && markCircle.getCenterX() >= nodeDestinations[1].getX() ||
nodePixelVelocitiesX[1] < 0 && markCircle.getCenterX() <= nodeDestinations[1].getX())
nodePixelVelocitiesX[1] = 0;
else if (nodePixelVelocitiesX[1] != 0)
markCircle.setCenterX(markCircle.getCenterX() + nodePixelVelocitiesX[1] * timeInterval);
if (nodePixelVelocitiesY[1] > 0 && markCircle.getCenterY() > nodeDestinations[1].getY() ||
nodePixelVelocitiesY[1] < 0 && markCircle.getCenterY() < nodeDestinations[1].getY())
nodePixelVelocitiesY[1] = 0;
else if (nodePixelVelocitiesY[1] != 0)
markCircle.setCenterY(markCircle.getCenterY() + nodePixelVelocitiesY[1] * timeInterval);
line.setEndX(markCircle.getCenterX());
line.setEndY(markCircle.getCenterY());
}
}
public void moveGroupBy (double x, double y, double rotation) {
super.setLayoutX(super.getLayoutX() + x);
super.setLayoutY(super.getLayoutY() + y);
rotateTo(rotation);
}
private void updateChildren (double timeInterval) {
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
Line line = (Line) super.getChildren().get(2);
for (int childIndex = 0; childIndex < 2; childIndex++){
Circle mark = (Circle) super.getChildren().get(childIndex);
mark.setCenterY(mark.getCenterY() + y);
mark.setCenterX(mark.getCenterX() + x);
}
line.setStartX(line.getStartX() + x);
line.setStartY(line.getStartY() + y);
line.setEndX(line.getEndX() + x);
line.setEndY(line.getEndY() + y);
} else {
Circle mark = (Circle) super.getChildren().get(0);
if (nodePixelVelocitiesX[0] > 0 && mark.getLayoutX() >= nodeDestinations[0].getX()) {
nodePixelVelocitiesX[0] = 0;
} else if (nodePixelVelocitiesX[0] < 0 && mark.getLayoutX() <= nodeDestinations[0].getX()) {
nodePixelVelocitiesX[0] = 0;
} else {
mark.setLayoutX(mark.getLayoutX() + nodePixelVelocitiesX[0] * timeInterval);
mark.setLayoutY(mark.getLayoutY() + nodePixelVelocitiesY[0] * timeInterval);
}
if (nodePixelVelocitiesY[0] >= 0 && mark.getLayoutY() > nodeDestinations[0].getY()) {
nodePixelVelocitiesY[0] = 0;
} else if (nodePixelVelocitiesY[0] < 0 && mark.getLayoutY() <= nodeDestinations[0].getY()) {
nodePixelVelocitiesY[0] = 0;
} else {
mark.setLayoutX(mark.getLayoutX() + nodePixelVelocitiesX[0] * timeInterval);
mark.setLayoutY(mark.getLayoutY() + nodePixelVelocitiesY[0] * timeInterval);
}
mark = (Circle) super.getChildren().get(1);
if (nodePixelVelocitiesX[1] > 0 && mark.getLayoutX() >= nodeDestinations[1].getX()) {
nodePixelVelocitiesX[1] = 0;
} else if (nodePixelVelocitiesX[1] < 0 && mark.getLayoutX() <= nodeDestinations[1].getX()) {
nodePixelVelocitiesX[1] = 0;
} else {
mark.setLayoutX(mark.getLayoutX() + nodePixelVelocitiesX[1] * timeInterval);
mark.setLayoutY(mark.getLayoutY() + nodePixelVelocitiesY[1] * timeInterval);
}
if (nodePixelVelocitiesY[1] >= 0 && mark.getLayoutY() > nodeDestinations[1].getY()) {
nodePixelVelocitiesY[1] = 0;
} else if (nodePixelVelocitiesY[1] < 0 && mark.getLayoutY() <= nodeDestinations[1].getY()) {
nodePixelVelocitiesY[1] = 0;
} else {
mark.setLayoutX(mark.getLayoutX() + nodePixelVelocitiesX[1] * timeInterval);
mark.setLayoutY(mark.getLayoutY() + nodePixelVelocitiesY[1] * timeInterval);
}
mark.setCenterY(mark.getCenterY() + y);
mark.setCenterX(mark.getCenterX() + x);
}
rotateTo(currentRotation + rotation);
}
public void moveTo (double x, double y, double rotation) {
@@ -177,8 +201,19 @@ public class MarkGroup extends RaceObject {
}
public void moveTo (double x, double y) {
super.setLayoutX(x);
super.setLayoutY(y);
Circle markCircle = (Circle) super.getChildren().get(0);
markCircle.setCenterX(x);
markCircle.setCenterY(y);
if (mainMark.getMarkType() != MarkType.SINGLE_MARK) {
markCircle = (Circle) super.getChildren().get(1);
markCircle.setCenterX(x);
markCircle.setCenterY(y);
Line line = (Line) super.getChildren().get(2);
line.setStartX(x);
line.setStartY(y);
line.setEndX(x);
line.setEndY(y);
}
}
public boolean hasRaceId (int... raceIds) {
@@ -188,9 +223,6 @@ public class MarkGroup extends RaceObject {
return true;
return false;
}
public void toggleAnnotations () {
}
public static int getMarkRadius() {
return MARK_RADIUS;
@@ -207,5 +239,4 @@ public class MarkGroup extends RaceObject {
idArray[i++] = mark.getId();
return idArray;
}
}
@@ -9,6 +9,7 @@ public class SingleMark extends Mark {
private double lat;
private double lon;
private String name;
private int id;
/**
@@ -18,10 +19,11 @@ public class SingleMark extends Mark {
* @param lat, the latitude of the marker
* @param lon, the longitude of the marker
*/
public SingleMark(String name, double lat, double lon) {
super(name, MarkType.SINGLE_MARK);
public SingleMark(String name, double lat, double lon, int id) {
super(name, MarkType.SINGLE_MARK, id);
this.lat = lat;
this.lon = lon;
this.id = id;
}
/**
@@ -30,9 +32,10 @@ public class SingleMark extends Mark {
* @param name, the name of the marker
*/
public SingleMark(String name) {
super(name, MarkType.SINGLE_MARK);
super(name, MarkType.SINGLE_MARK, 0);
this.lat = 0;
this.lon = 0;
this.id = 0;
}
public double getLatitude() {
@@ -0,0 +1,77 @@
package seng302.models.parsers;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import seng302.models.Boat;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.io.StringBufferInputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
/**
* Created by ryan_ on 30/04/2017.
*/
public class BoatsParser extends FileParser {
private Document doc;
public BoatsParser(String xmlString) {
this.doc = this.parseFile(xmlString);
}
/**
* Create a boat instance from a given node if 'Type' is 'Yacht'
*
* @param node a boat node
* @return an instance of Boat
*/
private Boat parseBoat(Node node) {
try {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
if (element.getAttribute("Type").equals("Yacht")) {
String sourceID = element.getAttribute("SourceID");
String boatName = element.getAttribute("BoatName");
String shortName = element.getAttribute("ShortName");
String stoweName = element.getAttribute("StoweName");
String country = element.getAttribute("Country");
Boat boat = new Boat(Integer.parseInt(sourceID), boatName, shortName, country);
return boat;
}
} else {
throw new NoSuchElementException("Cannot generate a boat by given node");
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Returns a list of boats from the xml.
*
* @return a list of boats
*/
public List<Boat> getBoats() {
ArrayList<Boat> boats = new ArrayList<>();
try {
NodeList nodes = this.doc.getElementsByTagName("Boat");
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
Boat boat = parseBoat(node);
if (!(boat == null)) {
boats.add(boat);
}
}
return boats;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
@@ -35,7 +35,8 @@ public class CourseParser extends FileParser {
String name = element.getElementsByTagName("name").item(0).getTextContent();
double lat = Double.valueOf(element.getElementsByTagName("latitude").item(0).getTextContent());
double lon = Double.valueOf(element.getElementsByTagName("longitude").item(0).getTextContent());
SingleMark singleMark = new SingleMark(name, lat, lon);
int id = Integer.valueOf(element.getElementsByTagName("id").item(0).getTextContent());
SingleMark singleMark = new SingleMark(name, lat, lon, id);
return singleMark;
} else {
throw new NoSuchElementException("Cannot generate a mark by given node.");
@@ -1,12 +1,14 @@
package seng302.models.parsers;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
/**
* Created by Haoming Yin (hyi25) on 16/3/2017
@@ -15,6 +17,8 @@ public abstract class FileParser {
private String filePath;
public FileParser() {}
public FileParser(String path) {
this.filePath = path;
}
@@ -32,6 +36,19 @@ public abstract class FileParser {
e.printStackTrace();
return null;
}
}
protected Document parseFile(String xmlString) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xmlString)));
// optional, in order to recover info from broken line.
doc.getDocumentElement().normalize();
return doc;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
@@ -22,7 +22,7 @@ public class StreamPacket {
// if (this.type == PacketType.XML_MESSAGE){
// //System.out.println("--------");
// System.out.println(new String(payload));
// StreamParser.parsePacket(this);
// //StreamParser.parsePacket(this);
// }
}
@@ -32,10 +32,10 @@ public class StreamParser extends Thread{
private String threadName;
private Thread t;
private static boolean raceStarted = false;
private static boolean raceFinished = false;
private static boolean streamStatus = false;
private static long timeSinceStart = -1;
private static List<Boat> boats = new ArrayList<>();
public StreamParser(String threadName){
this.threadName = threadName;
@@ -48,6 +48,7 @@ public class StreamParser extends Thread{
public void run(){
try {
System.out.println("START OF STREAM");
streamStatus = true;
while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) {
Thread.sleep(1);
}
@@ -158,16 +159,21 @@ public class StreamParser extends Thread{
format.setTimeZone(TimeZone.getTimeZone("UTC"));
long timeTillStart = ((new Date (expectedStartTime)).getTime() - (new Date (currentTime)).getTime())/1000;
if (timeTillStart > 0 && timeTillStart % 10 == 0) {
timeSinceStart = timeTillStart;
System.out.println("Time till start: " + timeTillStart + " Seconds");
} else {
if (raceStatus == 4 || raceStatus == 8){
raceFinished = true;
raceStarted = false;
System.out.println("RACE HAS FINISHED");
} else if (!raceStarted){
raceStarted = true;
raceFinished = false;
System.out.println("RACE HAS STARTED");
}
if (timeTillStart % 10 == 0){
//System.out.println("Time since start: " + -1 * timeTillStart + " Seconds");
System.out.println("Time since start: " + -1 * timeTillStart + " Seconds");
timeSinceStart = timeTillStart;
}
}
long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20));
@@ -184,7 +190,6 @@ public class StreamParser extends Thread{
boatStatus += "\nEstTimeAtNextMark: " + extractTimeStamp(Arrays.copyOfRange(payload,31 + (i * 20),37+ (i * 20)), 6);
boatStatus += "\nEstTimeAtFinish: " + extractTimeStamp(Arrays.copyOfRange(payload,37 + (i * 20),43+ (i * 20)), 6);
boatStatuses.add(boatStatus);
// System.out.println("boatStatus = " + boatStatus);
}
}
@@ -228,19 +233,25 @@ public class StreamParser extends Thread{
//Converts XML message to string to be parsed
int currentChar;
while (payloadStream.available() > 0 && (currentChar = payloadStream.read()) != 0) {
xmlMessage += (char)currentChar;
xmlMessage += (char)currentChar;
}
// Parse boat xml from server
if (xmlMessageSubType == 7) {
BoatsParser boatsParser = new BoatsParser(xmlMessage);
boats = boatsParser.getBoats();
}
//Create XML document Object
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = null;
try {
db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xmlMessage)));
// TODO: 25/04/17 ajm412: Check that the object matches expected structure and return Document object.
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
// DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// DocumentBuilder db = null;
// try {
// db = dbf.newDocumentBuilder();
// Document doc = db.parse(new InputSource(new StringReader(xmlMessage)));
// // TODO: 25/04/17 ajm412: Check that the object matches expected structure and return Document object.
// } catch (ParserConfigurationException | IOException | SAXException e) {
// e.printStackTrace();
// }
}
@@ -417,5 +428,50 @@ public class StreamParser extends Thread{
}
return partialLong;
}
/**
* returns false if race not started, true otherwise
*
* @return race started status
*/
public static boolean isRaceStarted() {
return raceStarted;
}
/**
* returns false if stream not connected, true otherwise
*
* @return stream started status
*/
public static boolean isStreamStatus() {
return streamStatus;
}
/**
* returns race timer
*
* @return race timer in long
*/
public static long getTimeSinceStart() {
return timeSinceStart;
}
/**
* return false if race not finished, true otherwise
*
* @return race finished status
*/
public static boolean isRaceFinished() {
return raceFinished;
}
/**
* return list of boats from the server
*
* @return list of boats
*/
public static List<Boat> getBoats() {
return boats;
}
}
@@ -6,6 +6,8 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.zip.CRC32;
@@ -15,8 +17,8 @@ import java.util.zip.Checksum;
public class StreamReceiver extends Thread {
private InputStream stream;
private Socket host;
private ByteArrayOutputStream crcBuffer;
private Thread thread;
private ByteArrayOutputStream crcBuffer;
private Thread t;
private String threadName;
public static PriorityBlockingQueue<StreamPacket> packetBuffer;
@@ -31,24 +33,31 @@ public class StreamReceiver extends Thread {
}
public void run(){
packetBuffer = new PriorityBlockingQueue<>(256, new Comparator<StreamPacket>() {
PriorityBlockingQueue<StreamPacket> pq = new PriorityBlockingQueue<>(256, new Comparator<StreamPacket>() {
@Override
public int compare(StreamPacket s1, StreamPacket s2) {
return (int) (s1.getTimeStamp() - s2.getTimeStamp());
}
});
packetBuffer = pq;
connect();
}
public void start () {
System.out.println("Starting " + threadName );
if (thread == null) {
thread = new Thread (this, threadName);
thread.start ();
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
public StreamReceiver(Socket host, PriorityBlockingQueue packetBuffer){
this.host=host;
this.packetBuffer = packetBuffer;
}
public void connect(){
try {
stream = host.getInputStream();
@@ -120,8 +129,8 @@ public class StreamReceiver extends Thread {
}
/**
* takes an array of up to 7 bytes and returns a positive
* long constructed from the input bytes
* takes an array of up to 7 bytes in little endian format and
* returns a positive long constructed from the input bytes
*
* @return a positive long if there is less than 8 bytes -1 otherwise
*/
@@ -137,4 +146,13 @@ public class StreamReceiver extends Thread {
}
return partialLong;
}
public static void main(String[] args) {
StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1");
//StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread2");
sr.start();
}
}
@@ -0,0 +1,461 @@
package seng302.models.parsers;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
/**
* Class to create an XML object from the XML Packet Messages.
*/
class XMLParser {
/**
* Creates a Regatta XML Object from the data in a Regatta XML Message
* @param doc XML Document Object
* @return A new RegattaXMLObject from the input Document.
*/
RegattaXMLObject createRegattaXML(Document doc) {
return new RegattaXMLObject(doc);
}
/**
* Creates a Race XML Object from the data in a Regatta XML Message
* @param doc XML Document Object
* @return A new RaceXMLObject from the input Document.
*/
RaceXMLObject createRaceXML(Document doc) {
return new RaceXMLObject(doc);
}
/**
* Creates a Boat XML Object from the data in a Regatta XML Message
* @param doc XML Document Object
* @return A new BoatXMLObject from the input Document.
*/
BoatXMLObject createBoatXML(Document doc) {
return new BoatXMLObject(doc);
}
/**
* Returns the text content of a given child element tag, assuming it exists, as an Integer.
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static Integer getElementInt(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Integer.parseInt(tagList.item(0).getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of a given child element tag, assuming it exists, as an String.
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static String getElementString(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return tagList.item(0).getTextContent();
} else {
return null;
}
}
/**
* Returns the text content of a given child element tag, assuming it exists, as a Double.
* @param ele Document Element with child elements.
* @param tag Tag to find in document elements child elements.
* @return Text content from tag if found, null otherwise.
*/
private static Double getElementDouble(Element ele, String tag) {
NodeList tagList = ele.getElementsByTagName(tag);
if (tagList.getLength() > 0) {
return Double.parseDouble(tagList.item(0).getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as a String.
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The String representation of the text content of an attribute in the given node, else returns null.
*/
private static String getNodeAttributeString(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return attrItem.getTextContent();
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as an Integer.
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The Integer representation of the text content of an attribute in the given node, else returns null.
*/
private static Integer getNodeAttributeInt(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return Integer.parseInt(attrItem.getTextContent());
} else {
return null;
}
}
/**
* Returns the text content of an attribute of a given Node, assuming it exists, as a Double.
* @param n A node object that should have some attributes
* @param attr The attribute you want to get from the given node.
* @return The Double representation of the text content of an attribute in the given node, else returns null.
*/
private static Double getNodeAttributeDouble(Node n, String attr) {
Node attrItem = n.getAttributes().getNamedItem(attr);
if (attrItem != null) {
return Double.parseDouble(attrItem.getTextContent());
} else {
return null;
}
}
class RegattaXMLObject {
//Regatta Info
private Integer regattaID;
private String regattaName;
private String courseName;
private Double centralLat;
private Double centralLng;
private Integer utcOffset;
/**
* Constructor for a RegattaXMLObject.
* Takes the information from a Document object and creates a more usable format.
* @param doc XML Document Object
*/
RegattaXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
this.regattaID = getElementInt(docEle, "RegattaID");
this.regattaName = getElementString(docEle, "RegattaName");
this.courseName = getElementString(docEle, "CourseName");
this.centralLat = getElementDouble(docEle, "CentralLatitude");
this.centralLng = getElementDouble(docEle, "CentralLongitude");
this.utcOffset = getElementInt(docEle, "UtcOffset");
}
public Integer getRegattaID() { return regattaID; }
public String getRegattaName() { return regattaName; }
public String getCourseName() { return courseName; }
public Double getCentralLat() { return centralLat; }
public Double getCentralLng() { return centralLng; }
public Integer getUtcOffset() { return utcOffset; }
}
class RaceXMLObject {
// Race Info
private Integer raceID;
private String raceType;
private String creationTimeDate; // XML Creation Time
//Race Start Details
private String raceStartTime;
private Boolean postponeStatus;
//Non atomic race attributes
private ArrayList<Participant> participants;
private ArrayList<CompoundMark> course;
private ArrayList<Corner> compoundMarkSequence;
private ArrayList<Limit> courseLimit;
/**
* Constructor for a RaceXMLObject.
* Takes the information from a Document object and creates a more usable format.
* @param doc XML Document Object
*/
RaceXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
//Atomic and Semi-Atomic Elements
this.raceID = getElementInt(docEle, "RaceID");
this.raceType = getElementString(docEle, "RaceType");
this.creationTimeDate = getElementString(docEle, "CreationTimeDate");
Node raceStart = docEle.getElementsByTagName("RaceStartTime").item(0);
this.raceStartTime = getNodeAttributeString(raceStart, "Start") ;
this.postponeStatus = Boolean.parseBoolean(getNodeAttributeString(raceStart, "Postpone"));
//Participants
participants = new ArrayList<>();
NodeList pList = docEle.getElementsByTagName("Participants").item(0).getChildNodes();
for (int i = 0; i < pList.getLength(); i++) {
Node pNode = pList.item(i);
String entry;
if (pNode.getNodeName().equals("Yacht")) {
Integer sourceID = getNodeAttributeInt(pNode, "SourceID");
if (pNode.getAttributes().getLength() == 2) {
entry = getNodeAttributeString(pNode, "Entry");
} else {
entry = null;
}
Participant pa = new Participant(sourceID, entry);
participants.add(pa);
}
}
//Course
course = new ArrayList<>();
NodeList cMarkList = docEle.getElementsByTagName("Course").item(0).getChildNodes();
for (int i = 0; i < cMarkList.getLength(); i++) {
Node cMarkNode = cMarkList.item(i);
if (cMarkNode.getNodeName().equals("CompoundMark")) {
CompoundMark cMark = new CompoundMark(cMarkNode);
course.add(cMark);
}
}
//Course Mark Sequence
compoundMarkSequence = new ArrayList<>();
NodeList cornerList = docEle.getElementsByTagName("CompoundMarkSequence").item(0).getChildNodes();
for (int i = 0; i < cornerList.getLength(); i++) {
Node cornerNode = cornerList.item(i);
if (cornerNode.getNodeName().equals("Corner")) {
Corner corner = new Corner(cornerNode);
compoundMarkSequence.add(corner);
}
}
//Course Limits
courseLimit = new ArrayList<>();
NodeList limitList = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes();
for (int i = 0; i < limitList.getLength(); i++) {
Node limitNode = limitList.item(i);
if (limitNode.getNodeName().equals("Limit")) {
Limit limit = new Limit(limitNode);
courseLimit.add(limit);
}
}
}
public Integer getRaceID() { return raceID; }
public String getRaceType() { return raceType; }
public String getCreationTimeDate() { return creationTimeDate; }
public String getRaceStartTime() { return raceStartTime; }
public Boolean getPostponeStatus() { return postponeStatus; }
public ArrayList<Participant> getParticipants() { return participants; }
public ArrayList<CompoundMark> getCompoundMarks() { return course; }
public ArrayList<Corner> getCompoundMarkSequence() { return compoundMarkSequence; }
public ArrayList<Limit> getCourseLimit() { return courseLimit; }
class Participant {
Integer sourceID;
String entry;
Participant(Integer sourceID, String entry) {
this.sourceID = sourceID;
this.entry = entry;
}
public Integer getsourceID() { return sourceID; }
public String getEntry() { return entry; }
}
class CompoundMark {
private Integer markID;
private String cMarkName;
private ArrayList<Mark> marks;
CompoundMark(Node compoundMark) {
marks = new ArrayList<>();
this.markID = getNodeAttributeInt(compoundMark, "CompoundMarkID");
this.cMarkName = getNodeAttributeString(compoundMark, "Name");
NodeList childMarks = compoundMark.getChildNodes();
for (int i = 0; i < childMarks.getLength(); i++) {
Node markNode = childMarks.item(i);
if (markNode.getNodeName().equals("Mark")) {
Mark mark = new Mark(markNode);
marks.add(mark);
}
}
}
public Integer getMarkID() { return markID; }
public String getcMarkName() { return cMarkName; }
public ArrayList<Mark> getMarks() { return marks; }
class Mark {
private Integer seqID;
private Integer sourceID;
private String markName;
private Double targetLat;
private Double targetLng;
Mark(Node markNode) {
this.seqID = getNodeAttributeInt(markNode, "SeqID");
this.sourceID = getNodeAttributeInt(markNode, "SourceID");
this.markName = getNodeAttributeString(markNode, "Name");
this.targetLat = getNodeAttributeDouble(markNode, "TargetLat");
this.targetLng = getNodeAttributeDouble(markNode, "TargetLng");
}
public Integer getSeqID() { return seqID; }
public Integer getSourceID() { return sourceID; }
public String getMarkName() { return markName; }
public Double getTargetLat() { return targetLat; }
public Double getTargetLng() { return targetLng; }
}
}
class Corner {
private Integer seqID;
private Integer compoundMarkID;
private String rounding;
private Integer zoneSize;
Corner(Node cornerNode) {
this.seqID = getNodeAttributeInt(cornerNode, "SeqID");
this.compoundMarkID = getNodeAttributeInt(cornerNode, "CompoundMarkID");
this.rounding = getNodeAttributeString(cornerNode, "Rounding");
this.zoneSize = getNodeAttributeInt(cornerNode, "ZoneSize");
}
public Integer getSeqID() { return seqID; }
public Integer getCompoundMarkID() { return compoundMarkID; }
public String getRounding() { return rounding; }
public Integer getZoneSize() { return zoneSize; }
}
class Limit {
private Integer seqID;
private Double lat;
private Double lng;
Limit(Node limitNode) {
this.seqID = getNodeAttributeInt(limitNode, "SeqID");
this.lat = getNodeAttributeDouble(limitNode, "Lat");
this.lng = getNodeAttributeDouble(limitNode, "Lon");
}
public Integer getSeqID() { return seqID; }
public Double getLat() { return lat; }
public Double getLng() { return lng; }
}
}
class BoatXMLObject {
private String lastModified;
private Integer version;
//Settings for the boat type in the race. This may end up having to be reworked if multiple boat types compete.
private String boatType;
private Double boatLength;
private Double hullLength;
private Double markZoneSize;
private Double courseZoneSize;
private ArrayList<Double> zoneLimits;// will only contain 5 elements. Limits 1-5
//Boats
ArrayList<Boat> boats;
/**
* Constructor for a BoatXMLObject.
* Takes the information from a Document object and creates a more usable format.
* @param doc XML Document Object
*/
BoatXMLObject(Document doc) {
Element docEle = doc.getDocumentElement();
this.lastModified = getElementString(docEle, "Modified");
this.version = getElementInt(docEle, "Version");
NodeList settingsList = docEle.getElementsByTagName("Settings").item(0).getChildNodes();
this.boatType = getNodeAttributeString(settingsList.item(1), "Type");
this.boatLength = getNodeAttributeDouble(settingsList.item(3), "BoatLength");
this.hullLength = getNodeAttributeDouble(settingsList.item(3), "HullLength");
this.markZoneSize = getNodeAttributeDouble(settingsList.item(5), "MarkZoneSize");
this.courseZoneSize = getNodeAttributeDouble(settingsList.item(5), "CourseZoneSize");
Node zoneLimitsList = settingsList.item(7);
this.zoneLimits = new ArrayList<>();
for (int i = 0; i < zoneLimitsList.getAttributes().getLength(); i++) {
String tag = String.format("Limit%d", i+1);
this.zoneLimits.add(getNodeAttributeDouble(zoneLimitsList, tag));
}
this.boats = new ArrayList<>();
NodeList boatsList = docEle.getElementsByTagName("Boats").item(0).getChildNodes();
for (int i = 0; i < boatsList.getLength(); i++) {
Node currentBoat = boatsList.item(i);
if (currentBoat.getNodeName().equals("Boat")) {
Boat boat = new Boat(currentBoat);
this.boats.add(boat);
}
//System.out.println(this.getBoats());
}
}
public String getLastModified() { return lastModified; }
public Integer getVersion() { return version; }
public String getBoatType() { return boatType; }
public Double getBoatLength() { return boatLength; }
public Double getHullLength() { return hullLength; }
public Double getMarkZoneSize() { return markZoneSize; }
public Double getCourseZoneSize() { return courseZoneSize; }
public ArrayList<Double> getZoneLimits() { return zoneLimits; }
public ArrayList<Boat> getBoats() { return boats; }
class Boat {
private String boatType;
private Integer sourceID;
private String hullID; //matches HullNum in the XML spec.
private String shortName;
private String boatName;
private String country;
Boat(Node boatNode) {
this.boatType = getNodeAttributeString(boatNode, "Type");
this.sourceID = getNodeAttributeInt(boatNode, "SourceID");
this.hullID = getNodeAttributeString(boatNode, "HullNum");
this.shortName = getNodeAttributeString(boatNode, "ShortName");
this.boatName = getNodeAttributeString(boatNode, "BoatName");
this.country = getNodeAttributeString(boatNode, "Country");
}
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; }
}
}
}