diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index f6630e7c..dc6de280 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -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); } } diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index ad4e8c36..6ee934b1 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -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 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. */ diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java index a788054a..d5d662b2 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -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 data = FXCollections.observableArrayList(); + teamList.setItems(data); + boatNameCol.setCellValueFactory( + new PropertyValueFactory("boatName") + ); + shortNameCol.setCellValueFactory( + new PropertyValueFactory("shortName") + ); + countryCol.setCellValueFactory( + new PropertyValueFactory("country") + ); + for (Boat boat : StreamParser.getBoats()) { + data.add(boat); + } } } diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index c064d225..43509252 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -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 startingBoats = new ArrayList<>(); - private boolean displayAnnotations; private boolean displayFps; private Timeline timerTimeline; private Map 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() { - @Override - public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { - displayAnnotations = !displayAnnotations; - } - }); toggleFps.selectedProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { displayFps = !displayFps; } }); + + //SLIFER STUFF BELOW + annotationSlider.setLabelFormatter(new StringConverter() { + @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; } } + } \ No newline at end of file diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java index 04ef14ab..dc3aa7b7 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -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; + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 8171bdeb..618a3b59 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -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); diff --git a/src/main/java/seng302/models/RaceObject.java b/src/main/java/seng302/models/RaceObject.java index 30ca28f3..af29cb5c 100644 --- a/src/main/java/seng302/models/RaceObject.java +++ b/src/main/java/seng302/models/RaceObject.java @@ -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 (); } diff --git a/src/main/java/seng302/models/Wake.java b/src/main/java/seng302/models/Wake.java index 3c79d3a9..d389e8e7 100644 --- a/src/main/java/seng302/models/Wake.java +++ b/src/main/java/seng302/models/Wake.java @@ -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); } } diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index 2c086b80..a32ba20f 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -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) { diff --git a/src/main/java/seng302/models/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index ab189034..b0ef4768 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -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; } - } diff --git a/src/main/java/seng302/models/mark/SingleMark.java b/src/main/java/seng302/models/mark/SingleMark.java index 81f6f0b4..d4b4f3f2 100644 --- a/src/main/java/seng302/models/mark/SingleMark.java +++ b/src/main/java/seng302/models/mark/SingleMark.java @@ -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() { diff --git a/src/main/java/seng302/models/parsers/BoatsParser.java b/src/main/java/seng302/models/parsers/BoatsParser.java new file mode 100644 index 00000000..8180bde8 --- /dev/null +++ b/src/main/java/seng302/models/parsers/BoatsParser.java @@ -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 getBoats() { + ArrayList 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; + } + } +} diff --git a/src/main/java/seng302/models/parsers/CourseParser.java b/src/main/java/seng302/models/parsers/CourseParser.java index 04a40e0a..ae7f7856 100644 --- a/src/main/java/seng302/models/parsers/CourseParser.java +++ b/src/main/java/seng302/models/parsers/CourseParser.java @@ -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."); diff --git a/src/main/java/seng302/models/parsers/FileParser.java b/src/main/java/seng302/models/parsers/FileParser.java index b3d66b05..be162b9e 100644 --- a/src/main/java/seng302/models/parsers/FileParser.java +++ b/src/main/java/seng302/models/parsers/FileParser.java @@ -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; } } diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index 1b0d7f94..5c2c0706 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -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); // } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 4a2ed055..34b76b80 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -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 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 getBoats() { + return boats; + } } diff --git a/src/main/java/seng302/models/parsers/StreamReceiver.java b/src/main/java/seng302/models/parsers/StreamReceiver.java index 0d50315a..4c16991e 100644 --- a/src/main/java/seng302/models/parsers/StreamReceiver.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -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 packetBuffer; @@ -31,24 +33,31 @@ public class StreamReceiver extends Thread { } public void run(){ - packetBuffer = new PriorityBlockingQueue<>(256, new Comparator() { + PriorityBlockingQueue pq = new PriorityBlockingQueue<>(256, new Comparator() { @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(); + + } } diff --git a/src/main/java/seng302/models/parsers/XMLParser.java b/src/main/java/seng302/models/parsers/XMLParser.java new file mode 100644 index 00000000..ef65ea38 --- /dev/null +++ b/src/main/java/seng302/models/parsers/XMLParser.java @@ -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 participants; + private ArrayList course; + private ArrayList compoundMarkSequence; + private ArrayList 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 getParticipants() { return participants; } + public ArrayList getCompoundMarks() { return course; } + public ArrayList getCompoundMarkSequence() { return compoundMarkSequence; } + public ArrayList 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 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 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 zoneLimits;// will only contain 5 elements. Limits 1-5 + + //Boats + ArrayList 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 getZoneLimits() { return zoneLimits; } + public ArrayList 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; } + + } + + } + +} \ No newline at end of file diff --git a/src/main/resources/config/course.xml b/src/main/resources/config/course.xml index e11a24cf..6e1a72fb 100644 --- a/src/main/resources/config/course.xml +++ b/src/main/resources/config/course.xml @@ -8,17 +8,20 @@ Start1 57.6703330 11.8278330 + 122 Start2 57.6706330 11.8281330 + 123 Mid Mark 57.6675700 11.8359880 + 131 Leeward Gate @@ -26,11 +29,13 @@ Leeward Gate1 57.6708220 11.8433900 + 124 Leeward Gate2 57.6711220 11.8436900 + 125 @@ -39,11 +44,13 @@ Windward Gate1 57.6650170 11.8279170 + 126 Windward Gate2 57.6653170 11.8282170 + 127 @@ -52,11 +59,13 @@ Finish1 57.6715240 11.8444950 + 128 Finish2 57.6718240 11.8447950 + 129 diff --git a/src/main/resources/views/MainView.fxml b/src/main/resources/views/MainView.fxml index 4a3d7298..ebea61a2 100644 --- a/src/main/resources/views/MainView.fxml +++ b/src/main/resources/views/MainView.fxml @@ -1,11 +1,62 @@ + + + - + + + + + + + + + + + + + + + + + + +