Merge branch 'master' of https://eng-git.canterbury.ac.nz/seng302-2017/team-7 into changeStartingPositions2

main
Erika Savell 9 years ago
commit fb409af23b

@ -32,7 +32,8 @@ public class App extends Application {
@Override @Override
public void start(Stage primaryStage) throws Exception { public void start(Stage primaryStage) throws Exception {
this.primaryStage = primaryStage; this.primaryStage = primaryStage;
primaryStage.minHeightProperty().setValue(600);
primaryStage.minWidthProperty().setValue(780);
//load the first container //load the first container
try { try {
FXMLLoader loader = new FXMLLoader(); FXMLLoader loader = new FXMLLoader();

@ -21,12 +21,14 @@ public class Constants {
public static final GPSCoordinate finishLineMarker1 = new GPSCoordinate(32.317379, -64.839291); public static final GPSCoordinate finishLineMarker1 = new GPSCoordinate(32.317379, -64.839291);
public static final GPSCoordinate finishLineMarker2 = new GPSCoordinate(32.317257, -64.836260); public static final GPSCoordinate finishLineMarker2 = new GPSCoordinate(32.317257, -64.836260);
public static final double wakeScale = 10;
public static final BoatInRace[] OFFICIAL_AC35_COMPETITORS = new BoatInRace[] public static final BoatInRace[] OFFICIAL_AC35_COMPETITORS = new BoatInRace[]
{new BoatInRace("Oracle Team USA", 30.0, Color.BLUEVIOLET, "Oracle"), {new BoatInRace("Oracle Team USA", 30.0, Color.BLUEVIOLET, "Oracle"),
new BoatInRace("Land Rover BAR", 23.0, Color.BLACK, "BAR"), new BoatInRace("Land Rover BAR", 23.0, Color.BLACK, "BGR"),
new BoatInRace("SoftBank Team Japan", 27.0, Color.RED, "JAP"), new BoatInRace("SoftBank Team Japan", 27.0, Color.RED, "JPN"),
new BoatInRace("Groupama Team France", 25.0, Color.ORANGE, "FRN"), new BoatInRace("Groupama Team France", 25.0, Color.ORANGE, "FRA"),
new BoatInRace("Artemis Racing", 22.5, Color.DARKOLIVEGREEN, "ART"), new BoatInRace("Artemis Racing", 22.5, Color.DARKOLIVEGREEN, "SWE"),
new BoatInRace("Emirates Team New Zealand", 62, Color.LIMEGREEN, "ETNZ")}; new BoatInRace("Emirates Team New Zealand", 62, Color.LIMEGREEN, "ETNZ")};
} }

@ -12,6 +12,7 @@ import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane; import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.util.Callback; import javafx.util.Callback;
@ -34,7 +35,7 @@ import java.util.ResourceBundle;
*/ */
public class RaceController extends Controller{ public class RaceController extends Controller{
@FXML @FXML
AnchorPane canvasBase; GridPane canvasBase;
ResizableRaceCanvas raceMap; ResizableRaceCanvas raceMap;
@ -55,7 +56,6 @@ public class RaceController extends Controller{
@FXML @FXML
Label FPS; Label FPS;
@FXML @FXML
TableView<BoatInRace> boatInfoTable; TableView<BoatInRace> boatInfoTable;
@FXML @FXML
@ -76,7 +76,7 @@ public class RaceController extends Controller{
public void updateMap(ObservableList<BoatInRace> boats) { public void updateMap(ObservableList<BoatInRace> boats) {
BoatInRace[] boatInRaces = new BoatInRace[boats.size()]; BoatInRace[] boatInRaces = new BoatInRace[boats.size()];
raceMap.setBoats(boats.toArray(boatInRaces)); raceMap.setBoats(boats.toArray(boatInRaces));
raceMap.drawRaceMap(); raceMap.update();
} }
/** /**
@ -122,15 +122,38 @@ public class RaceController extends Controller{
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
//listener for fps
/*Tooltip tp = new Tooltip("");
tp.install(timer, tp);*/
showFPS.selectedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> ov,
Boolean old_val, Boolean new_val) {
if (showFPS.isSelected()){
FPS.setVisible(true);
} else {
FPS.setVisible(false);
}
}
});
/*timer.setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
tp.show(timer, timer.getLayoutX()+timer.getWidth()+10, timer.getLayoutY()+timer.getHeight()-10);
}
});
timer.setOnMouseExited(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
tp.hide();
}
});*/
} }
/** /**
* Initializes and runs the race, based on the user's chosen scale factor * Initializes and runs the race, based on the user's chosen scale factor
* Currently uses an example racecourse * Currently uses an example racecourse
* *
* @param scaleFactor * @param scaleFactor scale value of race
*/ */
private void startRace(int scaleFactor) { private void startRace(int scaleFactor) {
RaceXMLReader raceXMLReader = null; RaceXMLReader raceXMLReader = null;
@ -145,12 +168,18 @@ public class RaceController extends Controller{
} }
BoatInRace[] boats = new BoatInRace[raceXMLReader.getBoats().size()]; BoatInRace[] boats = new BoatInRace[raceXMLReader.getBoats().size()];
boats = raceXMLReader.getBoats().toArray(boats); boats = raceXMLReader.getBoats().toArray(boats);
//BoatInRace[] boats = generateAC35Competitors();
raceMap = new ResizableRaceCanvas(); double lat1 = raceXMLReader.getMapTopLeft().getLatitude();
double long1 = raceXMLReader.getMapTopLeft().getLongitude();
double lat2 = raceXMLReader.getMapBottomRight().getLatitude();
double long2 = raceXMLReader.getMapBottomRight().getLongitude();
raceMap = new ResizableRaceCanvas(lat1, long1, lat2, long2);
raceMap.setMouseTransparent(true); raceMap.setMouseTransparent(true);
raceMap.widthProperty().bind(canvasBase.widthProperty()); raceMap.widthProperty().bind(canvasBase.widthProperty());
raceMap.heightProperty().bind(canvasBase.heightProperty()); raceMap.heightProperty().bind(canvasBase.heightProperty());
raceMap.setBoats(boats); raceMap.setBoats(boats);
raceMap.setRaceBoundaries(raceXMLReader.getBoundary());
raceMap.drawBoats();
raceMap.drawRaceMap(); raceMap.drawRaceMap();
raceMap.setVisible(true); raceMap.setVisible(true);
@ -161,9 +190,7 @@ public class RaceController extends Controller{
ArrayList<Leg> legs = raceXMLReader.getLegs(); ArrayList<Leg> legs = raceXMLReader.getLegs();
ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, this, scaleFactor); ConstantVelocityRace race = new ConstantVelocityRace(boats, legs, this, scaleFactor);
new Thread((race)).start(); showFPS.setVisible(true);
//listener for fps
showFPS.selectedProperty().addListener(new ChangeListener<Boolean>() { showFPS.selectedProperty().addListener(new ChangeListener<Boolean>() {
public void changed(ObservableValue<? extends Boolean> ov, public void changed(ObservableValue<? extends Boolean> ov,
Boolean old_val, Boolean new_val) { Boolean old_val, Boolean new_val) {
@ -180,11 +207,14 @@ public class RaceController extends Controller{
public void changed(ObservableValue<? extends Boolean> ov, public void changed(ObservableValue<? extends Boolean> ov,
Boolean old_val, Boolean new_val) { Boolean old_val, Boolean new_val) {
raceMap.toggleAnno(); raceMap.toggleAnno();
raceMap.drawRaceMap(); raceMap.update();
} }
}); });
new Thread((race)).start();
} }
/** /**
* Set the value for the race clock label * Set the value for the race clock label
* @param time time that the label will be updated to * @param time time that the label will be updated to
@ -199,5 +229,4 @@ public class RaceController extends Controller{
*/ */
public void setFrames(String fps) { FPS.setText((fps)); } public void setFrames(String fps) { FPS.setText((fps)); }
} }

@ -21,7 +21,7 @@ public class Boat {
*/ */
public Boat(String name, double velocity, String abbrev) { public Boat(String name, double velocity, String abbrev) {
this.velocity = velocity; this.velocity = velocity;
this.velocityProp = new SimpleStringProperty(String.valueOf(velocity)); this.velocityProp = new SimpleStringProperty(String.valueOf(Math.round(velocity)));
this.abbrev = abbrev; this.abbrev = abbrev;
this.name = new SimpleStringProperty(name); this.name = new SimpleStringProperty(name);
} }
@ -48,6 +48,14 @@ public class Boat {
return velocity; return velocity;
} }
/**
* Sets the speed of the boat in knots.
* @param velocity speed in knots
*/
public void setVelocity(double velocity) {
this.velocity = velocity;
this.velocityProp.setValue(String.valueOf(Math.round(velocity)));
}
/** /**
* Print method prints the name of the boat * Print method prints the name of the boat

@ -4,8 +4,10 @@ import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.geotools.referencing.GeodeticCalculator; import org.geotools.referencing.GeodeticCalculator;
import seng302.Constants;
import seng302.GPSCoordinate; import seng302.GPSCoordinate;
import java.awt.geom.Point2D;
/** /**
* Boat in the Race extends Boat. * Boat in the Race extends Boat.
@ -21,6 +23,7 @@ public class BoatInRace extends Boat {
private Color colour; private Color colour;
private boolean finished = false; private boolean finished = false;
private StringProperty currentLegName; private StringProperty currentLegName;
private boolean started = false;
/** /**
* Constructor method. * Constructor method.
@ -76,6 +79,25 @@ public class BoatInRace extends Boat {
return calculateHeading(azimuth); return calculateHeading(azimuth);
} }
/**
* Returns the position of the end of the boat's wake, which is 180 degrees
* from the boat's heading, and whose length is proportional to the boat's
* speed.
* @return GPSCoordinate of wake endpoint.
*/
public GPSCoordinate getWake() {
double reverseHeading = calculateHeading() - 180;
double distance = Constants.wakeScale * getVelocity();
GeodeticCalculator calc = new GeodeticCalculator();
calc.setStartingGeographicPoint(
new Point2D.Double(getCurrentPosition().getLongitude(), getCurrentPosition().getLatitude())
);
calc.setDirection(reverseHeading, distance);
Point2D endpoint = calc.getDestinationGeographicPoint();
return new GPSCoordinate(endpoint.getY(), endpoint.getX());
}
/** /**
* @return Scaled velocity of the boat * @return Scaled velocity of the boat
*/ */
@ -203,4 +225,11 @@ public class BoatInRace extends Boat {
this.finished = bool; this.finished = bool;
} }
public boolean isStarted() {
return started;
}
public void setStarted(boolean started) {
this.started = started;
}
} }

@ -13,6 +13,7 @@ import seng302.GPSCoordinate;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Random;
/** /**
* Parent class for races * Parent class for races
@ -30,8 +31,8 @@ public abstract class Race implements Runnable {
protected int scaleFactor; protected int scaleFactor;
private int SLEEP_TIME = 100; //time in milliseconds to pause in a paced loop private int SLEEP_TIME = 100; //time in milliseconds to pause in a paced loop
protected int PRERACE_TIME = 10000;//Integer.MAX_VALUE; //time in milliseconds to pause during pre-race protected int PRERACE_TIME = 5000; //time in milliseconds to pause during pre-race
private boolean timerEnabled = true; private boolean timerEnabled = true; //boolean to determine if timer is ran
/** /**
* Initailiser for Race * Initailiser for Race
@ -105,6 +106,21 @@ public abstract class Race implements Runnable {
timerEnabled = false; timerEnabled = false;
} }
/**
* Set up the state in waiting for the race starts.
*/
private void preRace() {
//show the boats participating.
System.out.println("Boats Participating:");
System.out.println("====================");
for (int i = 0; i < startingBoats.size(); i++) {
if (startingBoats.get(i) != null) {
System.out.println(i + 1 + ". " + startingBoats.get(i).toString() + ", Speed: "
+ Math.round(startingBoats.get(i).getVelocity()) + "kn");
startingBoats.get(i).setCurrentLeg(legs.get(0));
}
}
}
/** /**
* Countdown timer until race starts. Use PRERACE_TIME to set countdown duration. * Countdown timer until race starts. Use PRERACE_TIME to set countdown duration.
@ -120,15 +136,18 @@ public abstract class Race implements Runnable {
long timeLoopEnded; long timeLoopEnded;
while (currentTime <= startTime) { while (currentTime <= startTime) {
//if (controller != null) controller.updateMap(startingBoats);
timeLeft = startTime - currentTime; timeLeft = startTime - currentTime;
if (timeLeft == 0 && controller != null) {
updateTime("Race is starting...");
} else {
currentTimeInSeconds = timeLeft / 1000; currentTimeInSeconds = timeLeft / 1000;
minutes = currentTimeInSeconds / 60; minutes = currentTimeInSeconds / 60;
remainingSeconds = currentTimeInSeconds % 60; remainingSeconds = currentTimeInSeconds % 60;
hours = minutes / 60; hours = minutes / 60;
minutes = minutes % 60; minutes = minutes % 60;
if (controller != null) { if (controller != null) {
updateTime(String.format("Time until race starts: %02d:%02d:%02d", hours, minutes, remainingSeconds)); updateTime(String.format("Race clock: -%02d:%02d:%02d", hours, minutes, remainingSeconds));
}
} }
try { try {
timeLoopEnded = System.currentTimeMillis(); timeLoopEnded = System.currentTimeMillis();
@ -150,7 +169,7 @@ public abstract class Race implements Runnable {
long remainingSeconds; long remainingSeconds;
long hours; long hours;
currentTimeInSeconds = (totalTimeElapsed / 1000) * scaleFactor; currentTimeInSeconds = (totalTimeElapsed * scaleFactor)/ 1000;
minutes = currentTimeInSeconds / 60; minutes = currentTimeInSeconds / 60;
remainingSeconds = currentTimeInSeconds % 60; remainingSeconds = currentTimeInSeconds % 60;
hours = minutes / 60; hours = minutes / 60;
@ -174,6 +193,11 @@ public abstract class Race implements Runnable {
Platform.runLater(() -> {controller.setFrames("FPS: " + fps);}); Platform.runLater(() -> {controller.setFrames("FPS: " + fps);});
} }
private boolean doNotFinish() {
Random rand = new Random();
return rand.nextInt(100) < 1;
}
/** /**
* Starts the Race Simulation, playing the race start to finish with the timescale. * Starts the Race Simulation, playing the race start to finish with the timescale.
* This prints the boats participating, the order that the events occur in time order, and the respective information of the events. * This prints the boats participating, the order that the events occur in time order, and the respective information of the events.
@ -182,11 +206,15 @@ public abstract class Race implements Runnable {
System.setProperty("javafx.animation.fullspeed", "true"); System.setProperty("javafx.animation.fullspeed", "true");
for (BoatInRace boat: startingBoats){
boat.setStarted(true);
}
new AnimationTimer() { new AnimationTimer() {
long timeRaceStarted = System.currentTimeMillis(); long timeRaceStarted = System.currentTimeMillis(); //start time of loop
int fps = 0; int fps = 0; //init fps value
long timeCurrent = System.currentTimeMillis(); long timeCurrent = System.currentTimeMillis(); //current time
@Override @Override
public void handle(long arg0) { public void handle(long arg0) {
@ -208,6 +236,11 @@ public abstract class Race implements Runnable {
if (controller != null) controller.updateMap(startingBoats); if (controller != null) controller.updateMap(startingBoats);
if (timerEnabled) if (timerEnabled)
updateTime(calcTimer()); updateTime(calcTimer());
} else {
//Exit animation timer
updateTime(calcTimer());
updateFPS(0); //race ended so fps = 0
stop(); //exit animation timer
} }
fps++; fps++;
if ((System.currentTimeMillis()-timeCurrent) > 1000){ if ((System.currentTimeMillis()-timeCurrent) > 1000){
@ -236,21 +269,28 @@ public abstract class Race implements Runnable {
*/ */
protected void checkPosition(BoatInRace boat, long timeElapsed) { protected void checkPosition(BoatInRace boat, long timeElapsed) {
if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()){ if (boat.getDistanceTravelledInLeg() > boat.getCurrentLeg().getDistance()){
// updateController();
//boat has passed onto new leg //boat has passed onto new leg
if (boat.getCurrentLeg().getName().equals("Finish")) { if (boat.getCurrentLeg().getName().equals("Finish")) {
//boat has finished //boat has finished
boatsFinished++; boatsFinished++;
boat.setFinished(true); boat.setFinished(true);
boat.setTimeFinished(timeElapsed); boat.setTimeFinished(timeElapsed);
} else if(doNotFinish()) {
boatsFinished++;
boat.setFinished(true);
boat.setCurrentLeg(new Leg("DNF",-1));
} else { } else {
//Calculate how much the boat overshot the marker by
boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg() - boat.getCurrentLeg().getDistance()); boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg() - boat.getCurrentLeg().getDistance());
//Move boat on to next leg
Leg nextLeg = legs.get(boat.getCurrentLeg().getLegNumber() + 1); Leg nextLeg = legs.get(boat.getCurrentLeg().getLegNumber() + 1);
boat.setCurrentLeg(nextLeg); boat.setCurrentLeg(nextLeg);
//Add overshoot distance into the distance travelled for the next leg
boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg()); boat.setDistanceTravelledInLeg(boat.getDistanceTravelledInLeg());
} }
FXCollections.sort(startingBoats, (a,b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber()); //Update the boat display table in the GUI to reflect the leg change
FXCollections.sort(startingBoats, (a, b) -> b.getCurrentLeg().getLegNumber() - a.getCurrentLeg().getLegNumber());
} }
} }
@ -272,9 +312,9 @@ public abstract class Race implements Runnable {
} }
/** /**
* This function is a function that generates the Race and populates the events list. * Updates the boat's gps coordinates depending on time elapsed
* Is automatically called by the initialiser function, so that simulateRace() does not return an empty race. * @param boat
* @see Race#simulateRace() * @param millisecondsElapsed
*/ */
protected abstract void updatePosition(BoatInRace boat, int millisecondsElapsed); protected abstract void updatePosition(BoatInRace boat, int millisecondsElapsed);

@ -1,7 +1,9 @@
package seng302.Model; package seng302.Model;
import com.sun.corba.se.impl.orbutil.graph.Graph;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.StringProperty;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
@ -17,6 +19,7 @@ import java.awt.*;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Random; import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/** /**
* This creates a JavaFX Canvas that is fills it's parent. * This creates a JavaFX Canvas that is fills it's parent.
@ -27,7 +30,10 @@ public class ResizableRaceCanvas extends Canvas {
private GraphicsContext gc; private GraphicsContext gc;
private RaceMap map; private RaceMap map;
private BoatInRace[] boats; private BoatInRace[] boats;
private RaceController controller;
private boolean raceAnno = true; private boolean raceAnno = true;
private ArrayList<GPSCoordinate> raceBoundaries;
double[] xpoints = {}, ypoints = {};
/** /**
* Sets the boats that are to be displayed in this race. * Sets the boats that are to be displayed in this race.
@ -38,8 +44,8 @@ public class ResizableRaceCanvas extends Canvas {
this.boats = boats; this.boats = boats;
} }
public ResizableRaceCanvas(RaceMap map) { public ResizableRaceCanvas(RaceMap map) {
super();
this.map = map; this.map = map;
gc = this.getGraphicsContext2D(); gc = this.getGraphicsContext2D();
// Redraw canvas when size changes. // Redraw canvas when size changes.
@ -52,6 +58,12 @@ public class ResizableRaceCanvas extends Canvas {
*/ */
public ResizableRaceCanvas() { public ResizableRaceCanvas() {
this(null); this(null);
setMap(new RaceMap(32.278, -64.863, 32.320989, -64.821, (int) getWidth(), (int) getHeight()));
}
public ResizableRaceCanvas(double lat1, double long1, double lat2, double long2){
this(null);
setMap(new RaceMap(lat1, long1, lat2, long2, (int) getWidth(), (int) getHeight()));
} }
/** /**
@ -71,9 +83,24 @@ public class ResizableRaceCanvas extends Canvas {
* @see Color * @see Color
* @see Paint * @see Paint
*/ */
public void displayMark(GraphCoordinate graphCoordinate, Paint paint) { public void displayMark(GraphCoordinate graphCoordinate, Paint paint){
double d = 25;
gc.setFill(paint);
gc.fillOval(graphCoordinate.getX() - (d/2), graphCoordinate.getY() - (d/2), d, d);
}
public void displayBoat(BoatInRace boat, double angle) {
GraphCoordinate pos = this.map.convertGPS(boat.getCurrentPosition());
Paint paint = boat.getColour();
double[] x = {pos.getX() - 6, pos.getX(), pos.getX() + 6};
double[] y = {pos.getY() + 12, pos.getY() - 12, pos.getY() + 12};
gc.setFill(paint); gc.setFill(paint);
gc.fillOval(graphCoordinate.getX(), graphCoordinate.getY(), 25, 25);
gc.save();
rotate(angle, pos.getX(), pos.getY());
gc.fillPolygon(x, y, 3);
gc.restore();
} }
/** /**
@ -85,7 +112,7 @@ public class ResizableRaceCanvas extends Canvas {
* @see Color * @see Color
* @see Paint * @see Paint
*/ */
public void displayLine(GraphCoordinate graphCoordinateA, GraphCoordinate graphCoordinateB, Paint paint) { private void displayLine(GraphCoordinate graphCoordinateA, GraphCoordinate graphCoordinateB, Paint paint) {
gc.setStroke(paint); gc.setStroke(paint);
gc.setFill(paint); gc.setFill(paint);
gc.fillOval(graphCoordinateA.getX() - 3, graphCoordinateA.getY() - 3, 6, 6); gc.fillOval(graphCoordinateA.getX() - 3, graphCoordinateA.getY() - 3, 6, 6);
@ -101,23 +128,24 @@ public class ResizableRaceCanvas extends Canvas {
* @see Paint * @see Paint
* @see Color * @see Color
*/ */
public void displayPoint(GraphCoordinate graphCoordinate, Paint paint) { private void displayPoint(GraphCoordinate graphCoordinate, Paint paint) {
gc.setFill(paint); gc.setFill(paint);
gc.fillOval(graphCoordinate.getX(), graphCoordinate.getY(), 10, 10); gc.fillOval(graphCoordinate.getX(), graphCoordinate.getY(), 10, 10);
} }
/** /**
* Displays an arrow on the Canvas * Displays an arrow on the Canvas
* @param coordinate Coordinate that the arrow is to be displayed at. * @param coordinate Coordinate that the arrow is to be displayed at.
* @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up). * @param angle Angle that the arrow is to be facing in degrees 0 degrees = North (Up).
* @see GraphCoordinate * @see GraphCoordinate
*/ */
public void displayArrow(GraphCoordinate coordinate, int angle) { private void displayArrow(GraphCoordinate coordinate, int angle) {
gc.save(); gc.save();
rotate(angle, coordinate.getX(),coordinate.getY()); rotate(angle, coordinate.getX(), coordinate.getY());
gc.setFill(Color.BLACK); gc.setFill(Color.BLACK);
gc.fillPolygon(new double[]{coordinate.getX()-12, coordinate.getX()-6, coordinate.getX(), coordinate.getX()-4, coordinate.getX()-4, coordinate.getX()-8, coordinate.getX()-8}, gc.fillPolygon(new double[]{coordinate.getX() - 12, coordinate.getX() - 6, coordinate.getX(), coordinate.getX() - 4, coordinate.getX() - 4, coordinate.getX() - 8, coordinate.getX() - 8},
new double[]{coordinate.getY()-5, coordinate.getY()-20, coordinate.getY()-5, coordinate.getY()-5, coordinate.getY()+20, coordinate.getY()+20, coordinate.getY()-5}, new double[]{coordinate.getY() - 5, coordinate.getY() - 20, coordinate.getY() - 5, coordinate.getY() - 5, coordinate.getY() + 20, coordinate.getY() + 20, coordinate.getY() - 5},
7); 7);
gc.restore(); gc.restore();
} }
@ -139,8 +167,8 @@ public class ResizableRaceCanvas extends Canvas {
* @param speed speed of the boat * @param speed speed of the boat
* @param coordinate coordinate the text appears * @param coordinate coordinate the text appears
*/ */
public void displayText(String name, double speed, GraphCoordinate coordinate){ private void displayText(String name, double speed, GraphCoordinate coordinate){
String text = String.format("%s, %2$.2f knots", name, speed); String text = String.format("%s, %2$.2fkn", name, speed);
//System.out.println(text.length()*7); //System.out.println(text.length()*7);
long xCoord = coordinate.getX()+20; long xCoord = coordinate.getX()+20;
long yCoord = coordinate.getY(); long yCoord = coordinate.getY();
@ -153,6 +181,23 @@ public class ResizableRaceCanvas extends Canvas {
gc.fillText(text, xCoord, yCoord); gc.fillText(text, xCoord, yCoord);
} }
/**
* Draws race map with up to date data.
*/
public void update() {
this.drawRaceMap();
this.updateBoats();
}
public void drawBoundaries(){
if (this.raceBoundaries == null){
return;
}
gc.setFill(Color.AQUA);
setRaceBoundCoordinates();
gc.fillPolygon(xpoints, ypoints, xpoints.length);
}
/** /**
* Draws the Race Map * Draws the Race Map
*/ */
@ -163,13 +208,15 @@ public class ResizableRaceCanvas extends Canvas {
gc.clearRect(0, 0, width, height); gc.clearRect(0, 0, width, height);
//System.out.println("Race Map Canvas Width: "+ width + ", Height:" + height); //System.out.println("Race Map Canvas Width: "+ width + ", Height:" + height);
this.map = new RaceMap(32.278, -64.863, 32.320989, -64.821, (int) width, (int) height);
if (map == null) { if (map == null) {
return; return;//TODO this should return a exception in the future
} }
this.map.setHeight((int)height);
this.map.setWidth((int)width);
//finish line //finish line
gc.setLineWidth(2); gc.setLineWidth(2);
drawBoundaries();
GraphCoordinate finishLineCoord1 = this.map.convertGPS(Constants.finishLineMarker1); GraphCoordinate finishLineCoord1 = this.map.convertGPS(Constants.finishLineMarker1);
GraphCoordinate finishLineCoord2 = this.map.convertGPS(Constants.finishLineMarker2); GraphCoordinate finishLineCoord2 = this.map.convertGPS(Constants.finishLineMarker2);
displayLine(finishLineCoord1, finishLineCoord2, Color.DARKRED); displayLine(finishLineCoord1, finishLineCoord2, Color.DARKRED);
@ -188,20 +235,10 @@ public class ResizableRaceCanvas extends Canvas {
displayLine(startline1, startline2, Color.GREEN); displayLine(startline1, startline2, Color.GREEN);
updateBoats();
if (boats != null) { //display wind direction arrow - specify origin point and angle - angle now set to random angle
for (BoatInRace boat : boats) { displayArrow(new GraphCoordinate((int)getWidth()-40, 40), 150);
if (boat != null) {
// System.out.print("Drawing Boat At: " + boat.getCurrentPosition());
displayPoint(this.map.convertGPS(boat.getCurrentPosition()), boat.getColour());
if (raceAnno){
displayText(boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()));}
}
}
}
//display wind direction arrow - specify origin point and angle
displayArrow(new GraphCoordinate(500, 20), 100);
} }
/** /**
@ -228,6 +265,48 @@ public class ResizableRaceCanvas extends Canvas {
} }
} }
/**
* Draws boats while race in progress, when leg heading is set.
*/
public void updateBoats() {
if (boats != null) {
for (BoatInRace boat : boats) {
boolean finished = boat.getCurrentLeg().getName().equals("Finish") || boat.getCurrentLeg().getName().equals("DNF");
boolean isStart = boat.isStarted();
if (!finished && isStart) {
displayBoat(boat, boat.calculateHeading());
GraphCoordinate wakeFrom = this.map.convertGPS(boat.getCurrentPosition());
GraphCoordinate wakeTo = this.map.convertGPS(boat.getWake());
displayLine(wakeFrom, wakeTo, boat.getColour());
} else if (!isStart){
displayBoat(boat, boat.calculateHeading());
} else {
displayBoat(boat, 0);
}
if (raceAnno) displayText(boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()));
}
}
}
public void setRaceBoundaries(ArrayList<GPSCoordinate> boundaries) {
this.raceBoundaries = new ArrayList<>();
for (GPSCoordinate bound: boundaries){
raceBoundaries.add(bound);
}
setRaceBoundCoordinates();
}
public void setRaceBoundCoordinates(){
xpoints = new double[this.raceBoundaries.size()];
ypoints = new double[this.raceBoundaries.size()];
for (int i = 0; i < raceBoundaries.size(); i++){
GraphCoordinate coord = map.convertGPS(raceBoundaries.get(i));
xpoints[i] = coord.getX();
ypoints[i] = coord.getY();
}
}
/** /**
* Set the Canvas to resizable. * Set the Canvas to resizable.
* *
@ -260,4 +339,17 @@ public class ResizableRaceCanvas extends Canvas {
return getHeight(); return getHeight();
} }
/**
* Draws boats during race setup, when leg heading is not set.
*/
public void drawBoats() {
if (boats != null) {
for (BoatInRace boat : boats) {
if (boat != null) {
displayBoat(boat, 0);
if (raceAnno) displayText(boat.getAbbrev(), boat.getVelocity(), this.map.convertGPS(boat.getCurrentPosition()));
}
}
}
}
} }

@ -35,8 +35,16 @@ public class RaceMap {
* @see GraphCoordinate * @see GraphCoordinate
*/ */
public GraphCoordinate convertGPS(double lat, double lon) { public GraphCoordinate convertGPS(double lat, double lon) {
int difference = Math.abs(width - height);
int size = width;
if (width > height){
size = height;
return new GraphCoordinate((int) ((size * (lon - x1) / (x2 - x1)) + difference/2), (int) (size - (size * (lat - y1) / (y2 - y1))));
}else{
return new GraphCoordinate((int) (size * (lon - x1) / (x2 - x1)), (int) ((size - (size * (lat - y1) / (y2 - y1))) + difference/2));
}
return new GraphCoordinate((int) (width * (lon - x1) / (x2 - x1)), (int) (height - (height * (lat - y1) / (y2 - y1)))); //return new GraphCoordinate((int) (width * (lon - x1) / (x2 - x1)), (int) (height - (height * (lat - y1) / (y2 - y1))));
} }
/** /**
@ -50,4 +58,12 @@ public class RaceMap {
public GraphCoordinate convertGPS(GPSCoordinate coordinate) { public GraphCoordinate convertGPS(GPSCoordinate coordinate) {
return convertGPS(coordinate.getLatitude(), coordinate.getLongitude()); return convertGPS(coordinate.getLatitude(), coordinate.getLongitude());
} }
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
} }

@ -20,7 +20,9 @@ public class RaceXMLReader extends XMLReader{
private Color[] colors = {Color.BLUEVIOLET, Color.BLACK, Color.RED, Color.ORANGE, Color.DARKOLIVEGREEN, Color.LIMEGREEN};//TODO make this established in xml or come up with a better system. private Color[] colors = {Color.BLUEVIOLET, Color.BLACK, Color.RED, Color.ORANGE, Color.DARKOLIVEGREEN, Color.LIMEGREEN};//TODO make this established in xml or come up with a better system.
private ArrayList<Leg> legs = new ArrayList<>(); private ArrayList<Leg> legs = new ArrayList<>();
private GPSCoordinate mark, startPt1, startPt2, finishPt1, finishPt2, leewardPt1, leewardPt2, windwardPt1, windwardPt2; private GPSCoordinate mark, startPt1, startPt2, finishPt1, finishPt2, leewardPt1, leewardPt2, windwardPt1, windwardPt2;
private GPSCoordinate mapTopLeft, mapBottomRight;
private ArrayList<GPSCoordinate> boundary = new ArrayList<>(); private ArrayList<GPSCoordinate> boundary = new ArrayList<>();
private static double COORDINATEPADDING = 0.0005;
public RaceXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException { public RaceXMLReader(String filePath) throws IOException, SAXException, ParserConfigurationException {
@ -36,8 +38,8 @@ public class RaceXMLReader extends XMLReader{
private void read(){ private void read(){
readCourse(); readCourse();
readBoats();
readLegs(); readLegs();
readBoats();
} }
public void readBoats(){ public void readBoats(){
@ -51,6 +53,9 @@ public class RaceXMLReader extends XMLReader{
//System.out.println(String.format("%s, %s, %s",name, abbrev, velo)); //System.out.println(String.format("%s, %s, %s",name, abbrev, velo));
BoatInRace boat = new BoatInRace(name, velo, colors[i], abbrev); BoatInRace boat = new BoatInRace(name, velo, colors[i], abbrev);
boat.setCurrentPosition(startPt1); boat.setCurrentPosition(startPt1);
if (legs.size() > 0){
boat.setCurrentLeg(legs.get(0));
}
boats.add(boat); boats.add(boat);
} }
} }
@ -74,9 +79,61 @@ public class RaceXMLReader extends XMLReader{
NodeList nCourse = doc.getElementsByTagName("course"); NodeList nCourse = doc.getElementsByTagName("course");
NodeList nBounds = ((Element)nCourse.item(0)).getElementsByTagName("boundaries"); NodeList nBounds = ((Element)nCourse.item(0)).getElementsByTagName("boundaries");
nBounds = ((Element) nBounds.item(0)).getElementsByTagName("coordinate");
int maxLatitudeIndex = 0;
double maxLatitude = -Double.MIN_VALUE;
int maxLongitudeIndex = 0;
double maxLongitude = -180;
int minLatitudeIndex = 0;
double minLatitude = Double.MAX_VALUE;
int minLongitudeIndex = 0;
double minLongitude = Double.MAX_VALUE;
for (int i = 0; i < nBounds.getLength(); i++){ for (int i = 0; i < nBounds.getLength(); i++){
boundary.add(getCoordinates(nBounds, i)); boundary.add(getCoordinates((Element) nBounds.item(i)));
if (boundary.get(i).getLatitude() > maxLatitude){
maxLatitudeIndex = i;
maxLatitude = boundary.get(i).getLatitude();
}
if (boundary.get(i).getLatitude() < minLatitude){
minLatitudeIndex = i;
minLatitude = boundary.get(i).getLatitude();
}
if (boundary.get(i).getLongitude() > maxLongitude){
maxLongitudeIndex = i;
maxLongitude = boundary.get(i).getLongitude();
}
if (boundary.get(i).getLongitude() < minLongitude){
minLongitudeIndex = i;
minLongitude = boundary.get(i).getLongitude();
} }
}
/*System.out.println(nBounds.getLength());
System.out.println(maxLatitude);
System.out.println(minLatitude);
System.out.println(maxLongitude);
System.out.println(minLongitude);*/
double difference = 0;//this will hold the largest difference so we can make the map square.
double latitudeDiff = Math.abs(Math.abs(boundary.get(maxLatitudeIndex).getLatitude()) - Math.abs(boundary.get(minLatitudeIndex).getLatitude()));
double longitudeDiff = Math.abs(Math.abs(boundary.get(maxLongitudeIndex).getLongitude()) - Math.abs(boundary.get(minLongitudeIndex).getLongitude()));
if (latitudeDiff >= longitudeDiff){
difference = latitudeDiff - longitudeDiff;
maxLongitude += difference/2;
minLongitude -= difference/2;
}else{
difference = longitudeDiff - latitudeDiff;
maxLatitude += difference/2;
minLatitude -= difference/2;
}
maxLatitude += COORDINATEPADDING;
minLatitude -= COORDINATEPADDING;
maxLongitude += COORDINATEPADDING;
minLongitude -= COORDINATEPADDING;
//now create map boundaries
//top left canvas point is min logitude, max latitude
//bottom right of canvas point is min longitude, max latitude.
mapTopLeft = new GPSCoordinate(minLatitude, minLongitude);
mapBottomRight = new GPSCoordinate(maxLatitude, maxLongitude);
NodeList nMarks = ((Element)nCourse.item(0)).getElementsByTagName("marker"); NodeList nMarks = ((Element)nCourse.item(0)).getElementsByTagName("marker");
startPt1 = getCoordinates(nMarks, 0); startPt1 = getCoordinates(nMarks, 0);
@ -190,4 +247,12 @@ public class RaceXMLReader extends XMLReader{
public ArrayList<GPSCoordinate> getBoundary() { public ArrayList<GPSCoordinate> getBoundary() {
return boundary; return boundary;
} }
public GPSCoordinate getMapTopLeft() {
return mapTopLeft;
}
public GPSCoordinate getMapBottomRight() {
return mapBottomRight;
}
} }

@ -9,31 +9,31 @@
<boat> <boat>
<name>Land Rover BAR</name> <name>Land Rover BAR</name>
<speed>50</speed> <speed>50</speed>
<abbr>BAR</abbr> <abbr>GBR</abbr>
<colour>BLACK</colour> <colour>BLACK</colour>
</boat> </boat>
<boat> <boat>
<name>SoftBank Team Japan</name> <name>SoftBank Team Japan</name>
<speed>40</speed> <speed>40</speed>
<abbr>JAP</abbr> <abbr>JPN</abbr>
<colour>RED</colour> <colour>RED</colour>
</boat> </boat>
<boat> <boat>
<name>Groupama Team France</name> <name>Groupama Team France</name>
<speed>35</speed> <speed>35</speed>
<abbr>FRN</abbr> <abbr>FRA</abbr>
<colour>ORANGE</colour> <colour>ORANGE</colour>
</boat> </boat>
<boat> <boat>
<name>Artemis Racing</name> <name>Artemis Racing</name>
<speed>44</speed> <speed>44</speed>
<abbr>ART</abbr> <abbr>SWE</abbr>
<colour>DARKOLIVEGREEN</colour> <colour>DARKOLIVEGREEN</colour>
</boat> </boat>
<boat> <boat>
<name>Emirates Team New Zealand</name> <name>Emirates Team New Zealand</name>
<speed>62</speed> <speed>62</speed>
<abbr>ENZ</abbr> <abbr>NZL</abbr>
<colour>LIMEGREEN</colour> <colour>LIMEGREEN</colour>
</boat> </boat>
</boats> </boats>
@ -169,12 +169,48 @@
<course> <course>
<boundaries> <boundaries>
<coordinate> <coordinate>
<latitude>32.278</latitude> <latitude>32.313922</latitude>
<longitude>-64.863</longitude> <longitude>-64.837168</longitude>
</coordinate> </coordinate>
<coordinate> <coordinate>
<latitude>32.30989</latitude> <latitude>32.317403</latitude>
<longitude>-64.821</longitude> <longitude>-64.838627</longitude>
</coordinate>
<coordinate>
<latitude>32.317911</latitude>
<longitude>-64.836996</longitude>
</coordinate>
<coordinate>
<latitude>32.317548</latitude>
<longitude>-64.835022</longitude>
</coordinate>
<coordinate>
<latitude>32.304273</latitude>
<longitude>-64.822834</longitude>
</coordinate>
<coordinate>
<latitude>32.279097</latitude>
<longitude>-64.841545</longitude>
</coordinate>
<coordinate>
<latitude>32.279604</latitude>
<longitude>-64.849871</longitude>
</coordinate>
<coordinate>
<latitude>32.289545</latitude>
<longitude>-64.854162</longitude>
</coordinate>
<coordinate>
<latitude>32.290198</latitude>
<longitude>-64.858711</longitude>
</coordinate>
<coordinate>
<latitude>32.297164</latitude>
<longitude>-64.856394</longitude>
</coordinate>
<coordinate>
<latitude>32.296148</latitude>
<longitude>-64.849184</longitude>
</coordinate> </coordinate>
</boundaries> </boundaries>
<marker> <marker>

@ -61,22 +61,40 @@
</GridPane> </GridPane>
<SplitPane fx:id="ongoingRacePane" dividerPositions="0.7" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <SplitPane fx:id="ongoingRacePane" dividerPositions="0.7" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<items> <items>
<AnchorPane fx:id="canvasBase" prefHeight="581.0" prefWidth="537.0"> <GridPane fx:id="canvasBase">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Pane prefHeight="200.0" prefWidth="400.0" GridPane.halignment="LEFT" GridPane.valignment="TOP">
<children> <children>
<Label fx:id="FPS" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" />
<Label fx:id="timer" layoutX="45.0" layoutY="146.0" text="0:0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" />
<TitledPane fx:id="userControl" animated="false" text="User Control" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0"> <TitledPane fx:id="userControl" animated="false" text="User Control" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0">
<content> <content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"> <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="77.0" prefWidth="200.0">
<children> <children>
<CheckBox fx:id="showFPS" mnemonicParsing="false" selected="true" text="Show FPS" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" /> <CheckBox fx:id="showFPS" mnemonicParsing="false" selected="true" text="Show FPS" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
<CheckBox fx:id="showAnno" mnemonicParsing="false" selected="true" text="Show Annotations" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="25.0" /> <CheckBox fx:id="showAnno" mnemonicParsing="false" selected="true" text="Show Annotation" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="30.0" />
</children> </children>
</AnchorPane> </AnchorPane>
</content> </content>
</TitledPane> </TitledPane>
</children> </children>
</AnchorPane> </Pane>
<Label fx:id="timer" layoutX="45.0" layoutY="146.0" text="0:0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" GridPane.halignment="RIGHT" GridPane.valignment="BOTTOM">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
<Label fx:id="FPS" text="FPS: 0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" GridPane.halignment="LEFT" GridPane.valignment="BOTTOM">
<font>
<Font name="System Bold" size="15.0" />
</font>
</Label>
</children>
</GridPane>
<AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" GridPane.columnIndex="1"> <AnchorPane layoutX="450.0" minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="200.0" GridPane.columnIndex="1">
<children> <children>
<TableView fx:id="boatInfoTable" layoutX="-2.0" prefHeight="600.0" prefWidth="264.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-62.0" AnchorPane.topAnchor="0.0"> <TableView fx:id="boatInfoTable" layoutX="-2.0" prefHeight="600.0" prefWidth="264.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="-2.0" AnchorPane.rightAnchor="-62.0" AnchorPane.topAnchor="0.0">

@ -1,6 +1,8 @@
package seng302.Model; package seng302.Model;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import seng302.GPSCoordinate; import seng302.GPSCoordinate;
@ -113,4 +115,43 @@ public class BoatInRaceTest {
assertFalse(testBoat.isFinished()); assertFalse(testBoat.isFinished());
} }
@Test
public void getWakeAtProperHeading() throws Exception {
BoatInRace boat = new BoatInRace("Test", 1, Color.ALICEBLUE, "tt");
// Construct leg of 0 degrees
GPSCoordinate startPoint = new GPSCoordinate(0, 0);
GPSCoordinate endPoint = new GPSCoordinate(50, 0);
Leg leg0deg = new Leg("Start", startPoint, endPoint, 0);
boat.setCurrentLeg(leg0deg);
boat.setCurrentPosition(new GPSCoordinate(0,0));
assertEquals(0, boat.calculateHeading(), 1e-8);
// Construct leg from wake - heading should be 180 degrees
Leg leg180deg = new Leg("Start", startPoint, boat.getWake(), 0);
boat.setCurrentLeg(leg180deg);
assertEquals(180, boat.calculateHeading(), 1e-8);
}
@Test
public void getWakeProportionalToVelocity() throws Exception {
BoatInRace boat = new BoatInRace("Test", 10, Color.ALICEBLUE, "tt");
// Construct leg of 0 degrees at 0 N
GPSCoordinate startPoint = new GPSCoordinate(0, 0);
GPSCoordinate endPoint = new GPSCoordinate(50, 0);
Leg leg0deg = new Leg("Start", startPoint, endPoint, 0);
boat.setCurrentLeg(leg0deg);
boat.setCurrentPosition(new GPSCoordinate(0,0));
// Get latitude of endpoint of wake at 10 kn (longitude is 0)
double endpointAt10Kn = boat.getWake().getLatitude();
// Latitude of endpoint at 20 kn should be twice endpoint at 10 kn
boat.setVelocity(20);
assertEquals(2*endpointAt10Kn, boat.getWake().getLatitude(), 1e-8);
}
} }
Loading…
Cancel
Save