From 34872a822b30c0fb1a7853a89bff3e9c96fc580c Mon Sep 17 00:00:00 2001 From: William Muir Date: Sat, 8 Apr 2017 17:49:50 +1200 Subject: [PATCH 01/97] Stripped back codebase to make to create basic model for streaming data Removed many classes involved with visualisation such as controllers and multiple fxmls. Now there is just one for debugging Merged in Boat updating pattern from team 27 #story[828] --- .../seng302/controllers/CanvasController.java | 283 ------------------ .../java/seng302/controllers/Controller.java | 180 +++++++++-- .../seng302/controllers/RaceController.java | 80 ----- .../controllers/RaceResultController.java | 37 --- .../controllers/RaceViewController.java | 280 ----------------- src/main/java/seng302/models/Boat.java | 74 ++++- src/main/java/seng302/models/Course.java | 54 ++++ src/main/java/seng302/models/Event.java | 148 --------- src/main/java/seng302/models/Leg.java | 165 +++++----- src/main/java/seng302/models/Race.java | 219 ++++++++------ .../java/seng302/models/TimelineInfo.java | 31 -- src/main/resources/config/course.xml | 4 +- src/main/resources/views/CanvasView.fxml | 7 - src/main/resources/views/FinishView.fxml | 55 ---- src/main/resources/views/MainView.fxml | 48 ++- src/main/resources/views/RaceView.fxml | 59 ---- src/test/java/seng302/BoatTest.java | 35 --- src/test/java/seng302/ColorsTest.java | 45 --- src/test/java/seng302/EventTest.java | 38 --- src/test/java/seng302/LegTest.java | 54 ---- src/test/java/seng302/RaceTest.java | 41 --- src/test/java/seng302/TestRaceTimer.java | 25 -- .../java/seng302/models/mark/MarkTest.java | 44 --- .../models/parsers/ConfigParserTest.java | 42 --- .../models/parsers/CourseParserTest.java | 60 ---- .../models/parsers/TeamsParserTest.java | 35 --- 26 files changed, 518 insertions(+), 1625 deletions(-) delete mode 100644 src/main/java/seng302/controllers/CanvasController.java delete mode 100644 src/main/java/seng302/controllers/RaceController.java delete mode 100644 src/main/java/seng302/controllers/RaceResultController.java delete mode 100644 src/main/java/seng302/controllers/RaceViewController.java create mode 100644 src/main/java/seng302/models/Course.java delete mode 100644 src/main/java/seng302/models/Event.java delete mode 100644 src/main/java/seng302/models/TimelineInfo.java delete mode 100644 src/main/resources/views/CanvasView.fxml delete mode 100644 src/main/resources/views/FinishView.fxml delete mode 100644 src/main/resources/views/RaceView.fxml delete mode 100644 src/test/java/seng302/BoatTest.java delete mode 100644 src/test/java/seng302/ColorsTest.java delete mode 100644 src/test/java/seng302/EventTest.java delete mode 100644 src/test/java/seng302/LegTest.java delete mode 100644 src/test/java/seng302/RaceTest.java delete mode 100644 src/test/java/seng302/TestRaceTimer.java delete mode 100644 src/test/java/seng302/models/mark/MarkTest.java delete mode 100644 src/test/java/seng302/models/parsers/ConfigParserTest.java delete mode 100644 src/test/java/seng302/models/parsers/CourseParserTest.java delete mode 100644 src/test/java/seng302/models/parsers/TeamsParserTest.java diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java deleted file mode 100644 index c0b20e26..00000000 --- a/src/main/java/seng302/controllers/CanvasController.java +++ /dev/null @@ -1,283 +0,0 @@ -package seng302.controllers; - -import javafx.animation.*; -import javafx.fxml.FXML; -import javafx.scene.canvas.Canvas; -import javafx.scene.canvas.GraphicsContext; -import javafx.scene.layout.AnchorPane; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import seng302.models.Boat; -import seng302.models.TimelineInfo; -import seng302.models.mark.GateMark; -import seng302.models.mark.Mark; -import seng302.models.mark.MarkType; -import seng302.models.mark.SingleMark; - -import java.util.*; - -/** - * Created by ptg19 on 15/03/17. - * Modified by Haoming Yin (hyi25) on 20/3/2017. - */ -public class CanvasController { - - @FXML - private AnchorPane canvasPane; - - private RaceViewController raceViewController; - private ResizableCanvas canvas; - private GraphicsContext gc; - - private final double ORIGIN_LAT = 32.321504; - private final double ORIGIN_LON = -64.857063; - private final int SCALE = 16000; - - public void setup(RaceViewController raceViewController){ - this.raceViewController = raceViewController; - } - - public void initialize() { - canvas = new ResizableCanvas(); - canvasPane.getChildren().add(canvas); - // Bind canvas size to stack pane size. - canvas.widthProperty().bind(canvasPane.widthProperty()); - canvas.heightProperty().bind(canvasPane.heightProperty()); - gc = canvas.getGraphicsContext2D(); - - - // overriding the handle so that it can clean canvas and redraw boats and course marks - AnimationTimer timer = new AnimationTimer() { - private long lastUpdate = 0; - private long lastFpsUpdate = 0; - private int lastFpsCount = 0; - private int fpsCount = 0; - - @Override - public void handle(long now) { - if (true){ //if statement for limiting refresh rate if needed - gc.clearRect(0, 0, canvas.getWidth(),canvas.getHeight()); - gc.setFill(Color.SKYBLUE); - gc.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); - drawCourse(); - drawBoats(); - drawFps(lastFpsCount); - - // If race has started, draw the boats and play the timeline - if (raceViewController.getRace().getRaceTime() > 1){ - raceViewController.playTimelines(); - } - // Race has not started, pause the timelines - else { - raceViewController.pauseTimelines(); - } - lastUpdate = now; - fpsCount ++; - if (now - lastFpsUpdate >= 1000000000){ - lastFpsCount = fpsCount; - fpsCount = 0; - lastFpsUpdate = now; - } - } - } - }; - timer.start(); - } - - class ResizableCanvas extends Canvas { - - public ResizableCanvas() { - // Redraw canvas when size changes. - widthProperty().addListener(evt -> draw()); - heightProperty().addListener(evt -> draw()); - } - - private void draw() { - double width = getWidth(); - double height = getHeight(); - - GraphicsContext gc = getGraphicsContext2D(); - gc.clearRect(0, 0, width, height); - } - - @Override - public boolean isResizable() { - return true; - } - - @Override - public double prefWidth(double height) { - return getWidth(); - } - - @Override - public double prefHeight(double width) { - return getHeight(); - } - } - - private void drawFps(int fps){ - if (raceViewController.isDisplayFps()){ - gc.setFill(Color.BLACK); - gc.setFont(new Font(14)); - gc.setLineWidth(3); - gc.fillText(fps + " FPS", 5, 20); - } - } - - /** - * Draws all the boats. - */ - private void drawBoats() { - Map timelineInfos = raceViewController.getTimelineInfos(); - for (Boat boat : timelineInfos.keySet()) { - TimelineInfo timelineInfo = timelineInfos.get(boat); - - boat.setLocation(timelineInfo.getY().doubleValue(), timelineInfo.getX().doubleValue()); - - drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); - } - } - - /** - * Draw the wake line behind a boat - * @param gc The graphics context used for drawing the wake - * @param x the x position of the boat - * @param y the y position of the boat - * @param speed the speed of the boat - * @param color the color of the wake line - * @param heading the heading of the boat - */ - private void drawWake(GraphicsContext gc, double x, double y, double speed, Color color, double heading){ - double angle = Math.toRadians(heading); - speed = speed * 2; - Point newP = new Point(0, speed); - newP.rotate(angle); - - gc.setStroke(color); - gc.setLineWidth(1.0); - gc.strokeLine(x, y, newP.x + x, newP.y + y); - } - - /** - * Draws a boat with given (x, y) position in the given color - * - * @param lat - * @param lon - * @param color - * @param name - * @param speed - */ - private void drawBoat(double lat, double lon, Color color, String name, double speed, double heading) { - // Latitude - double x = (lon - ORIGIN_LON) * SCALE; - double y = (ORIGIN_LAT - lat) * SCALE; - - gc.setFill(color); - - if (raceViewController.isDisplayAnnotations()) { - // Set boat text - gc.setFont(new Font(14)); - gc.setLineWidth(3); - gc.fillText(name + ", " + speed + " knots", x + 15, y + 15); - } -// double diameter = 9; -// gc.fillOval(x, y, diameter, diameter); - double angle = Math.toRadians(heading); - - Point p1 = new Point(0, -15); // apex point - Point p2 = new Point(7, 4); // base point - Point p3 = new Point(-7, 4); // base point - p1.rotate(angle); - p2.rotate(angle); - p3.rotate(angle); - double[] xx = new double[] {p1.x + x, p2.x + x, x, p3.x + x}; - double[] yy = new double[] {p1.y + y, p2.y + y, y, p3.y + y}; - gc.fillPolygon(xx, yy, 4); - - if (raceViewController.isDisplayAnnotations()){ - drawWake(gc, x, y, speed, color, heading); - } - } - - /** - * 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); - - } - } - - /** - * Draws the course. - */ - private void drawCourse() { - for (Mark mark : raceViewController.getRace().getCourse()) { - if (mark.getMarkType() == MarkType.SINGLE_MARK) { - drawSingleMark((SingleMark) mark, Color.BLACK); - } else if (mark.getMarkType() == MarkType.GATE_MARK) { - drawGateMark((GateMark) mark); - } - } - } - - /** - * Draw a given mark on canvas - * - * @param singleMark - */ - private void drawSingleMark(SingleMark singleMark, Color color) { - double x = (singleMark.getLongitude() - ORIGIN_LON) * SCALE; - double y = (ORIGIN_LAT - singleMark.getLatitude()) * SCALE; - - gc.setFill(color); - gc.fillRect(x,y,5.5,5.5); - } - - /** - * Draw a gate mark which contains two single marks - * - * @param gateMark - */ - private void drawGateMark(GateMark gateMark) { - Color color = Color.BLUE; - - if (gateMark.getName().equals("Start")){ - color = Color.RED; - } - - if (gateMark.getName().equals("Finish")){ - color = Color.GREEN; - } - - drawSingleMark(gateMark.getSingleMark1(), color); - drawSingleMark(gateMark.getSingleMark2(), color); - - GraphicsContext gc = canvas.getGraphicsContext2D(); - - gc.setStroke(color); - - // Convert lat/lon to x,y - double x1 = (gateMark.getSingleMark1().getLongitude()- ORIGIN_LON) * SCALE; - double y1 = (ORIGIN_LAT - gateMark.getSingleMark1().getLatitude()) * SCALE; - - double x2 = (gateMark.getSingleMark2().getLongitude() - ORIGIN_LON) * SCALE; - double y2 = (ORIGIN_LAT - gateMark.getSingleMark2().getLatitude()) * SCALE; - - gc.setLineWidth(1); - gc.strokeLine(x1, y1, x2, y2); - } -} \ No newline at end of file diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java index 1892d472..080e06ab 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -1,38 +1,176 @@ package seng302.controllers; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; +import javafx.scene.Group; +import javafx.scene.canvas.Canvas; +import javafx.scene.canvas.GraphicsContext; +import javafx.scene.control.Button; +import javafx.scene.paint.Color; +import seng302.models.Boat; +import seng302.models.Course; +import seng302.models.Race; +import seng302.models.mark.Mark; +import seng302.models.mark.MarkType; +import seng302.models.parsers.ConfigParser; +import seng302.models.parsers.CourseParser; +import seng302.models.parsers.TeamsParser; -import java.io.IOException; import java.net.URL; +import java.util.ArrayList; import java.util.ResourceBundle; /** - * Created by michaelrausch on 21/03/17. + * Controller for main window for RaceVision + * Created by Kusal on 3/22/2017. */ public class Controller implements Initializable { - @FXML - private AnchorPane contentPane; - private void setContentPane(String jfxUrl){ - try{ - contentPane.getChildren().removeAll(); - contentPane.getChildren().clear(); - contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); - } - catch(javafx.fxml.LoadException e){ - System.err.println(e.getCause()); - } - catch(IOException e){ - System.err.println(e); - } - } + private static final int MARKER_WIDTH = 10; + private static final int MARKER_HEIGHT = 10; + private static final double ORIGINAL_LAT = 32.321504; + private static final double ORIGINAL_LON = -64.857063; + + + @FXML + private Canvas courseCanvas; + + @FXML + private Group boatGroup; + + @FXML + private Button playPauseButton; + + + private Course thisCourse; + private ArrayList startingBoats; + private int raceDuration; + private Race race; + private boolean raceRunning = false; + + private CourseParser cp; + private TeamsParser tp; + private ConfigParser cop; @Override public void initialize(URL location, ResourceBundle resources) { - setContentPane("/views/RaceView.fxml"); + cp = new CourseParser("/config/course.xml"); + tp = new TeamsParser("/config/teams.xml"); + cop = new ConfigParser("/config/config.xml"); + + thisCourse = new Course(cp.getCourse()); + startingBoats = tp.getBoats(); + race = new Race(thisCourse.getMarks(), startingBoats, cop.getTimeScale(), this); + init(); + } + + /** + * Initialises a race on the screen after it has been loaded + */ + private void init() { + initMap(); + initBoats(); + playPauseButton.setDisable(false); + } + + /** + * Initialise the map by drawing it onto courseCanvas. + */ + private void initMap() { + //Create the boundary of the course displayed on the map + drawCourse(); + } + + + /** + * Draw the markers and gates onto the courseCanvas. + */ + private void drawCourse() { + GraphicsContext gc = courseCanvas.getGraphicsContext2D(); + gc.save(); + + for(Mark rp : thisCourse.getMarks()) { + if (rp.getMarkType().equals(MarkType.SINGLE_MARK)) { + gc.setFill(Color.GRAY); + gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT); + } else if (rp.getMarkType().equals(MarkType.GATE_MARK)) { + gc.setFill(Color.GRAY); + gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT); +// gc.fillOval(((OpenGate) rp).getDrawX2(), ((OpenGate) rp).getDrawY2(), MARKER_WIDTH, MARKER_HEIGHT); + + gc.setLineWidth(2); + gc.setFill(Color.GREEN); + gc.setStroke(Color.GREEN); + } + gc.fillOval(convertLongToX(rp.getLongitude()), convertLatToY(rp.getLatitude()), MARKER_WIDTH, MARKER_HEIGHT); +// gc.fillOval(((ClosedGate) rp).getDrawX2(), ((ClosedGate) rp).getDrawY2(), MARKER_WIDTH, MARKER_HEIGHT); +// gc.strokeLine(convertLongToX(rp.getLongitude()) + 5, convertLatToY(rp.getLatitude()) + 5, ((ClosedGate) rp).getDrawX2() + 5, ((ClosedGate) rp).getDrawY2() + 5); + } + gc.restore(); + } + + /** + * Places boats at starting line + */ + private void initBoats() { + +// int startingX = (convertLongToX(thisCourse.getMarks().get(0).getLongitude())).intValue(); +// int startingY = (convertLatToY(thisCourse.getMarks().get(0).getLongitude())).intValue(); + + int startingX = 50; + int startingY = 100; + + for(Boat boat : startingBoats) { + boat.moveBoatTo(startingX, startingY); + boatGroup.getChildren().add(boat.getBoatObject()); + boat.setCurrentLeg(race.getRaceLegs().get(0)); + boat.setHeading(race.getRaceLegs().get(0).getHeading()); + boat.setLegDistance(0d); + } + } + + + /** + * Starts and stops the race depending on whether or not it is already running + */ + public void playPause() { + if (!raceRunning) { + play(); + } else { + pause(); + } + } + + /** + * Plays the race and updates the play / pause button + */ + private void play() { + race.run(); + raceRunning = true; + playPauseButton.setText("Pause"); + + } + + /** + * Pauses the race and updates the play / pause button + */ + private void pause() { + race.pause(); + raceRunning = false; + playPauseButton.setText("Play"); + } + + + + private Double convertLongToX(Double lon) { + return (lon - ORIGINAL_LON) * thisCourse.getDistanceScaleFactor(); + } + + private Double convertLatToY(Double lat) { + return (ORIGINAL_LAT - lat) * thisCourse.getDistanceScaleFactor(); + } + + public Button getPlayPauseButton() { + return playPauseButton; } } diff --git a/src/main/java/seng302/controllers/RaceController.java b/src/main/java/seng302/controllers/RaceController.java deleted file mode 100644 index b5fa2847..00000000 --- a/src/main/java/seng302/controllers/RaceController.java +++ /dev/null @@ -1,80 +0,0 @@ -package seng302.controllers; - -import seng302.models.Boat; -import seng302.models.Race; -import seng302.models.parsers.ConfigParser; -import seng302.models.parsers.CourseParser; -import seng302.models.parsers.TeamsParser; - -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Random; - -/** - * Created by zyt10 on 17/03/17. - * run before CanvasController to initialize race events - * the CanvasController then uses the event data to make the animations - */ -public class RaceController { - Race race = null; - - public void initializeRace() { - String raceConfigFile = "/config/config.xml"; - String teamsConfigFile = "/config/teams.xml"; - - try { - race = createRace(raceConfigFile, teamsConfigFile); - } catch (Exception e) { - System.out.println("There was an error creating the race."); - } - - if (race != null) { - race.startRace(); - } else { - System.out.println("There was an error creating the race. Exiting."); - } - } - - public Race createRace(String configFile, String teamsConfigFile) throws Exception { - Race race = new Race(); - - // Read team names from file - TeamsParser tp = new TeamsParser(teamsConfigFile); - - // Read course from file - ConfigParser config = new ConfigParser(configFile); - - ArrayList boatNames = new ArrayList<>(); - ArrayList teams = tp.getBoats(); - - //get race size - int numberOfBoats = teams.size(); - - //get time scale - double timeScale = config.getTimeScale(); - race.setTimeScale(timeScale); - - for (Boat boat : teams) { - boatNames.add(boat.getTeamName()); - race.addBoat(boat); - } - - // Shuffle team names - long seed = System.nanoTime(); - Collections.shuffle(boatNames, new Random(seed)); - - if (numberOfBoats > Array.getLength(boatNames.toArray())) { - return null; - } - - CourseParser course = new CourseParser("/config/course.xml"); - race.addCourse(course.getCourse()); - - return race; - } - - public Race getRace() { - return race; - } -} diff --git a/src/main/java/seng302/controllers/RaceResultController.java b/src/main/java/seng302/controllers/RaceResultController.java deleted file mode 100644 index 7378fa68..00000000 --- a/src/main/java/seng302/controllers/RaceResultController.java +++ /dev/null @@ -1,37 +0,0 @@ -package seng302.controllers; - -import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.VBox; -import javafx.scene.text.Text; -import seng302.models.Race; - -import java.net.URL; -import java.util.ResourceBundle; - -/** - * Created by ptg19 on 20/03/17. - */ -public class RaceResultController implements Initializable{ - @FXML private AnchorPane window; - @FXML private VBox resultsVBox; - private Race race; - - RaceResultController(Race race){ - this.race = race; - } - - @Override - public void initialize(URL location, ResourceBundle resources) { - int boatPosition = this.race.getFinishedBoats().length; - - for (int i = this.race.getFinishedBoats().length - 1; i >= 0; i--){ - resultsVBox.getChildren().add(0, new Text(boatPosition + ": " + this.race.getFinishedBoats()[i].getTeamName())); - boatPosition--; - } - - - - } -} diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java deleted file mode 100644 index 965fbc7d..00000000 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ /dev/null @@ -1,280 +0,0 @@ -package seng302.controllers; - -import javafx.animation.Animation; -import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; -import javafx.animation.Timeline; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.control.CheckBox; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.VBox; -import javafx.scene.text.Text; -import javafx.util.Duration; -import seng302.models.Boat; -import seng302.models.Event; -import seng302.models.Race; -import seng302.models.TimelineInfo; -import seng302.models.parsers.ConfigParser; - -import java.io.IOException; -import java.util.*; - -/** - * Created by ptg19 on 29/03/17. - */ -public class RaceViewController { - @FXML - private VBox positionVbox; - @FXML - private CheckBox toggleAnnotation, toggleFps; - @FXML - private Text timerLabel; - @FXML - private AnchorPane contentAnchorPane; - @FXML - private Text windArrowText, windDirectionText; - @FXML - private CanvasController includedCanvasController; - - private boolean displayAnnotations; - private boolean displayFps; - private Timeline timerTimeline; - private Map timelineInfos = new HashMap<>(); - private ArrayList boatOrder = new ArrayList<>(); - private Race race; - - public void initialize() { - includedCanvasController.setup(this); - RaceController raceController = new RaceController(); - raceController.initializeRace(); - race = raceController.getRace(); - - initializeTimer(); - initializeSettings(); - try{ - initializeTimelines(); - } - catch (Exception e){ - e.printStackTrace(); - } - - //set wind direction!!!!!!! can't find another place to put my code --haoming - double windDirection = new ConfigParser("/config/config.xml").getWindDirection(); - windDirectionText.setText(String.format("%.1f°", windDirection)); - windArrowText.setRotate(windDirection); - } - - private void initializeSettings(){ - displayAnnotations = true; - 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; - } - }); - } - - private void initializeTimer(){ - timerTimeline = new Timeline(); - timerTimeline.setCycleCount(Timeline.INDEFINITE); - // Run timer update every second - timerTimeline.getKeyFrames().add( - new KeyFrame(Duration.seconds(1), - event -> { - // Stop timer if race is finished - if (this.race.isRaceFinished()) { - this.timerTimeline.stop(); - } else { - timerLabel.setText(convertTimeToMinutesSeconds(race.getRaceTime())); - this.race.incrementRaceTime(); - } - }) - ); - - // Start the timer - timerTimeline.playFromStart(); - } - - /** - * Generates time line for each boat, and stores time time into timelineInfos hash map - */ - private void initializeTimelines() { - HashMap boat_events = race.getEvents(); - - for (Boat boat : boat_events.keySet()) { - // x, y are the real time coordinates - DoubleProperty x = new SimpleDoubleProperty(); - DoubleProperty y = new SimpleDoubleProperty(); - - List keyFrames = new ArrayList<>(); - List events = boat_events.get(boat); - - // iterates all events and convert each event to keyFrame, then add them into a list - for (Event event : events) { - if (event.getIsFinishingEvent()) { - keyFrames.add( - new KeyFrame(Duration.seconds(event.getTime()), - onFinished -> {race.setBoatFinished(boat); handleEvent(event);}, - new KeyValue(x, event.getThisMark().getLatitude()), - new KeyValue(y, event.getThisMark().getLongitude()) - ) - ); - } else { - keyFrames.add( - new KeyFrame(Duration.seconds(event.getTime()), - onFinished ->{ - handleEvent(event); - boat.setHeading(event.getBoatHeading()); - }, - new KeyValue(x, event.getThisMark().getLatitude()), - new KeyValue(y, event.getThisMark().getLongitude()) - ) - ); - } - } - timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y)); - } - setRaceDuration(); - } - - private void setRaceDuration(){ - Double maxDuration = 0.0; - Timeline maxTimeline = null; - - for (TimelineInfo timelineInfo : timelineInfos.values()) { - - Timeline timeline = timelineInfo.getTimeline(); - if (timeline.getTotalDuration().toMillis() >= maxDuration) { - maxDuration = timeline.getTotalDuration().toMillis(); - maxTimeline = timeline; - } - - // Timelines are paused by default - timeline.play(); - timeline.pause(); - } - - maxTimeline.setOnFinished(event -> { - race.setRaceFinished(); - loadRaceResultView(); - }); - } - - /** - * Play each boats timerTimeline - */ - public void playTimelines(){ - for (TimelineInfo timelineInfo : timelineInfos.values()){ - Timeline timeline = timelineInfo.getTimeline(); - - if (timeline.getStatus() == Animation.Status.PAUSED){ - timeline.play(); - } - } - } - - /** - * Pause each boats timerTimeline - */ - public void pauseTimelines(){ - for (TimelineInfo timelineInfo : timelineInfos.values()){ - Timeline timeline = timelineInfo.getTimeline(); - - if (timeline.getStatus() == Animation.Status.RUNNING){ - timeline.pause(); - } - } - } - - /** - * Display the list of boats in the order they finished the race - */ - private void loadRaceResultView() { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/views/FinishView.fxml")); - loader.setController(new RaceResultController(race)); - - try { - contentAnchorPane.getChildren().removeAll(); - contentAnchorPane.getChildren().clear(); - contentAnchorPane.getChildren().addAll((Pane) loader.load()); - - } catch (javafx.fxml.LoadException e) { - System.err.println(e.getCause()); - } catch (IOException e) { - System.err.println(e); - } - } - - public void handleEvent(Event event) { - Boat boat = event.getBoat(); - boatOrder.remove(boat); - boat.setMarkLastPast(event.getMarkPosInRace()); - boatOrder.add(boat); - boatOrder.sort(new Comparator() { - @Override - public int compare(Boat b1, Boat b2) { - return b2.getMarkLastPast() - b1.getMarkLastPast(); - } - }); - showOrder(); - } - - private void showOrder() { - positionVbox.getChildren().clear(); - positionVbox.getChildren().removeAll(); - - for (Boat boat : boatOrder) { - positionVbox.getChildren().add(new Text(boat.getShortName() + " " + boat.getSpeedInKnots() + " Knots")); - } - } - - /** - * Convert seconds to a string of the format mm:ss - * - * @param time the time in seconds - * @return a formatted string - */ - public String convertTimeToMinutesSeconds(int time) { - if (time < 0) { - return String.format("-%02d:%02d", (time * -1) / 60, (time * -1) % 60); - } - return String.format("%02d:%02d", time / 60, time % 60); - } - - public void stopTimer() { - timerTimeline.stop(); - } - public void startTimer() { - timerTimeline.play(); - } - - public boolean isDisplayFps() { - return displayFps; - } - - public boolean isDisplayAnnotations() { - return displayAnnotations; - } - - public Race getRace() { - return race; - } - - public Map getTimelineInfos() { - return timelineInfos; - } -} \ 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 0973ad38..e081efb9 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -1,28 +1,36 @@ package seng302.models; import javafx.scene.paint.Color; +import javafx.scene.shape.Polygon; +import javafx.scene.text.Text; /** * Represents a boat in the race. */ public class Boat { + private static final double BOAT_HEIGHT = 15d; + private static final double BOAT_WIDTH = 10d; + private static final double VELOCITY_WAKE_RATIO = 1/2d; //Ratio for deciding how long the wake will be wrt velocity + private String teamName; // The name of the team, this is also the name of the boat - private double velocity; // In meters/second - private double lat; // Boats position - private double lon; // - - private double distanceToNextMark; + private Double velocity; // In meters/second + private Double lat; // Boats position + private Double lon; // - + private Double legDistance; private Color color; - private int markLastPast; - private double heading; + private Leg currentLeg; + private Double heading; private String shortName; + private Polygon boatObject = new Polygon(); + public Boat(String teamName) { this.teamName = teamName; - this.velocity = 10; // Default velocity + this.velocity = 10d; // Default velocity this.lat = 0.0; this.lon = 0.0; - this.distanceToNextMark = 0.0; + this.legDistance = 0.0; this.shortName = ""; } @@ -36,9 +44,12 @@ public class Boat { public Boat(String teamName, double boatVelocity, String shortName) { this.teamName = teamName; this.velocity = boatVelocity; - this.distanceToNextMark = 0.0; + this.legDistance = 0.0; this.color = Colors.getColor(); this.shortName = shortName; + this.boatObject.getPoints().addAll(BOAT_WIDTH /2,0.0, + BOAT_WIDTH, BOAT_HEIGHT, + 0.0, BOAT_HEIGHT); } /** @@ -88,8 +99,12 @@ public class Boat { this.lon = lon; } - public void setDistanceToNextMark(double distance){ - this.distanceToNextMark = distance; + public Double getLegDistance() { + return legDistance; + } + + public void setLegDistance(double legDistance){ + this.legDistance = legDistance; } public double getLatitude(){ @@ -108,12 +123,12 @@ public class Boat { return Math.round((this.velocity * 1.94384) * 100d) / 100d; } - public void setMarkLastPast(int markLastPast) { - this.markLastPast = markLastPast; + public Leg getCurrentLeg() { + return currentLeg; } - public int getMarkLastPast() { - return markLastPast; + public void setCurrentLeg(Leg currentLeg) { + this.currentLeg = currentLeg; } public void setHeading(double heading){ @@ -127,4 +142,33 @@ public class Boat { public String getShortName(){ return this.shortName; } + + + /** + * Moves the boat and its children annotations from its current coordinates by specified amounts. + * @param x The amount to move the X coordinate by + * @param y The amount to move the Y coordinate by + */ + void moveBoatBy(Double x, Double y) { + boatObject.setLayoutX(boatObject.getLayoutX() + x); + boatObject.setLayoutY(boatObject.getLayoutY() + y); + boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); + + } + + /** + * 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 moveBoatTo(int x, int y) { + boatObject.setLayoutX(x); + boatObject.setLayoutY(y); + boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); + + } + + public Polygon getBoatObject() { + return boatObject; + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/Course.java b/src/main/java/seng302/models/Course.java new file mode 100644 index 00000000..d6647a02 --- /dev/null +++ b/src/main/java/seng302/models/Course.java @@ -0,0 +1,54 @@ +package seng302.models; + +/** + * Created by wmu16 on 4/8/17. + */ +import javafx.scene.canvas.Canvas; +import javafx.util.Pair; +import seng302.models.mark.Mark; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Class for describing the course information for the canvas + * Created by wmu16 on 22/03/17. + */ +public class Course { + + private ArrayList Legs; + private ArrayList marks; + public static final Integer SCALE = 160000; + + + public Course(ArrayList marks) { + this.marks = marks; + this.Legs = makeLegs(); + } + + /** + * Makes the race legs out of all the marker marks for the course + * @return ArrayList of Legs + */ + private ArrayList makeLegs() { + ArrayList Legs = new ArrayList<>(); + for (int i = 0; i < marks.size()-1; i++) { + Legs.add(new Leg(marks.get(i), marks.get(i+1))); + } + return Legs; + } + + + public ArrayList getMarks() { + return marks; + } + + public double getDistanceScaleFactor() { + return SCALE; + } + + public ArrayList getLegs() { + return Legs; + } +} \ No newline at end of file diff --git a/src/main/java/seng302/models/Event.java b/src/main/java/seng302/models/Event.java deleted file mode 100644 index e803845d..00000000 --- a/src/main/java/seng302/models/Event.java +++ /dev/null @@ -1,148 +0,0 @@ -package seng302.models; - -import seng302.models.mark.Mark; - -import java.text.SimpleDateFormat; -import java.util.Date; - -/** -* Event class containing the time of specific event, related team/boat, and -* event location such as leg. -*/ -public class Event { - private Double time; // Time the event occurs - private Boat boat; - private boolean isFinishingEvent = false; // This event occurs when a boat finishes the race - private Mark mark1; // This mark - private Mark mark2; // Next mark - private int markPosInRace; // the position of the current mark in the race course - - private final double ORIGIN_LAT = 32.320504; - private final double ORIGIN_LON = -64.857063; - private final double SCALE = 16000; - - - - /** - * Event class containing the time of specific event, related team/boat, and - * event location such as leg. - * - * @param eventTime, what time the event happens - * @param eventBoat, the boat that the event belongs to - */ - public Event(Double eventTime, Boat eventBoat, Mark mark1, Mark mark2, int markPosInRace) { - this.time = eventTime; - this.boat = eventBoat; - this.mark1 = mark1; - this.mark2 = mark2; - this.markPosInRace = markPosInRace; - } - - /** - * Event class containing the time of specific event, related team/boat, and - * event location such as leg. - * - * @param eventTime, what time the event happens - * @param eventBoat, the boat that the event belongs to - */ - public Event(Double eventTime, Boat eventBoat, Mark mark1, int markPosInRace) { - this.time = eventTime; - this.boat = eventBoat; - this.mark1 = mark1; - this.markPosInRace = markPosInRace; - this.isFinishingEvent = true; - } - - public double getTime() { - return this.time; - } - - public void setTime(double eventTime) { - this.time = eventTime; - } - - /** - * Gets the time in a formatted string - * - * @return the string of time - */ - public String getTimeString() { - return (new SimpleDateFormat("mm:ss:SSS")).format(new Date(time.longValue())); - } - - public Boat getBoat() { - return this.boat; - } - - public void setBoat(Boat eventBoat) { - this.boat = eventBoat; - } - - public boolean getIsFinishingEvent() { - return this.isFinishingEvent; - } - - /** - * Get a string that contains the timestamp and course information for this event - * - * @return A string that details what happened in this event - */ - public String getEventString() { - // This event is a boat finishing the race - if (this.isFinishingEvent) { - return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " finished the race"); - } - System.out.println(this.getDistanceBetweenMarks()); - return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " passed " + this.mark1.getName() + " going heading " + this.getBoatHeading() + "°"); - } - - /** - * @return the distance between the two marks - */ - public double getDistanceBetweenMarks() { - double earth_radius = 6378.137; - double dLat = this.mark2.getLatitude() * Math.PI / 180 - this.mark1.getLatitude() * Math.PI / 180; - double dLon = this.mark2.getLongitude() * Math.PI / 180 - this.mark1.getLongitude() * Math.PI / 180; - - double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.mark1.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); - - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - double d = earth_radius * c; - - return d * 1000; - } - - /** - * Calculates current boat heading direction. - * @return the boats heading as degree. vertical upward is 0 degree, and degree goes up clockwise. - */ - public double getBoatHeading() { - if (mark2 == null){ - return 0.0; - } - - double x1 = (mark1.getLongitude() - ORIGIN_LON) * SCALE; - double y1 = (ORIGIN_LAT - mark1.getLatitude()) * SCALE; - double x2 = (mark2.getLongitude() - ORIGIN_LON) * SCALE; - double y2 = (ORIGIN_LAT - mark2.getLatitude()) * SCALE; - - double headingRadians = Math.atan2(y2-y1, x2-x1); - - if (headingRadians < 0){ - headingRadians += 2 * Math.PI; - } - - // Convert back to degrees, and flip 180 degrees -// return ((headingRadians) * 180) / Math.PI; - return (Math.toDegrees(headingRadians) + 90) % 360; - - } - - public Mark getThisMark() { - return this.mark1; - } - - public int getMarkPosInRace() { - return markPosInRace; - } -} \ No newline at end of file diff --git a/src/main/java/seng302/models/Leg.java b/src/main/java/seng302/models/Leg.java index 8f21a6ea..7e03e195 100644 --- a/src/main/java/seng302/models/Leg.java +++ b/src/main/java/seng302/models/Leg.java @@ -1,116 +1,97 @@ package seng302.models; -import seng302.models.mark.SingleMark; + +import seng302.models.mark.Mark; /** -* Represents the leg of a race. -*/ + * Class for defining the leg of a race between two markers + * Created by cir27 on 3/03/17. + */ public class Leg { - private int heading; - private int distance; - private boolean isFinishingLeg; - private SingleMark startingSingleMark; + + private final double ORIGIN_LAT = 32.320504; + private final double ORIGIN_LON = -64.857063; + private final double SCALE = 16000; + + private Double distance; + private Double heading; + private Mark end; + private Mark start; + + Leg(Mark start, Mark end) { + this.distance = calculateMarkerDistance(start, end); + this.heading = angleFromCoordinate(start, end); + this.start = start; + this.end = end; + } /** - * Create a new leg + * Calculates the euclidian distance between two markers on the canvas using xy coordinates * - * @param heading, the magnetic heading of this leg - * @param distance, the total distance of this leg in meters - * @param singleMark, the singleMark this leg starts on + * @param geoPointOne first geographical point + * @param geoPointTwo second geographical point + * @return the distance between two points in meters */ - public Leg(int heading, int distance, SingleMark singleMark) { - this.heading = heading; - this.distance = distance; - this.startingSingleMark = singleMark; - this.isFinishingLeg = false; + private Double calculateMarkerDistance(Mark geoPointOne, Mark geoPointTwo) { + + double earth_radius = 6378.137; + double dLat = geoPointTwo.getLatitude() * Math.PI / 180 - geoPointOne.getLatitude() * Math.PI / 180; + double dLon = geoPointTwo.getLongitude() * Math.PI / 180 - geoPointOne.getLongitude() * Math.PI / 180; + + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(geoPointOne.getLatitude() * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); + + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + double d = earth_radius * c; + + return d * 1000; } /** - * Create a new leg + * Calculates the angle between to angular co-ordinates on a sphere. * - * @param heading, the magnetic heading of this leg - * @param distance, the total distance of this leg in meters - * @param markerName, the name of the marker this leg starts on + * @param geoPointOne first geographical location + * @param geoPointTwo second geographical location + * @return the angle from point one to point two */ - public Leg(int heading, int distance, String markerName) { - this.heading = heading; - this.distance = distance; - this.startingSingleMark = new SingleMark(markerName); - this.isFinishingLeg = false; + private Double angleFromCoordinate(Mark geoPointOne, Mark geoPointTwo) { + + if (geoPointTwo == null){ + return 0.0; + } + + double x1 = (geoPointOne.getLongitude() - ORIGIN_LON) * SCALE; + double y1 = (ORIGIN_LAT - geoPointOne.getLatitude()) * SCALE; + double x2 = (geoPointTwo.getLongitude() - ORIGIN_LON) * SCALE; + double y2 = (ORIGIN_LAT - geoPointTwo.getLatitude()) * SCALE; + + double headingRadians = Math.atan2(y2-y1, x2-x1); + + if (headingRadians < 0){ + headingRadians += 2 * Math.PI; + } + + // Convert back to degrees, and flip 180 degrees +// return ((headingRadians) * 180) / Math.PI; + return (Math.toDegrees(headingRadians) + 90) % 360; + } - /** - * Get the heading of this leg - * @return int - */ - public int getHeading() { - return this.heading; - } - - /** - * Set the heading for this leg - * @param heading - */ - public void setHeading(int heading) { - this.heading = heading; - } - - /** - * Get the total distance of this leg in meters - * @return int - */ - public int getDistance() { + Double getDistance() + { return this.distance; } - /** - * Set the distance of this leg in meters - * @param distance - */ - public void setDistance(int distance) { - this.distance = distance; + Mark getEnd() + { + return this.end; } - /** - * Returns the marker this leg started on - * @return SingleMark - */ - public SingleMark getMarker() { - return this.startingSingleMark; + public Mark getStart() { + return start; } - /** - * Set the singleMark this leg starts on - * @param singleMark - */ - public void setMarker(SingleMark singleMark) { - this.startingSingleMark = singleMark; + public Double getHeading() + { + return this.heading; } - - /** - * Returns the name of the marker this leg started on - * @return String - */ - public String getMarkerLabel() { - return this.startingSingleMark.getName(); - } - - - - /** - * Specify whether or not the race finishes on this leg - * - * @param isFinishingLeg whether or not the race finishes on this leg - */ - public void setFinishingLeg(boolean isFinishingLeg) { - this.isFinishingLeg = isFinishingLeg; - } - - /** - * Returns whether or not the race finishes after this leg - * @return true if this the race finishes after this leg - */ - public boolean getIsFinishingLeg() { - return this.isFinishingLeg; - } -} \ No newline at end of file +} diff --git a/src/main/java/seng302/models/Race.java b/src/main/java/seng302/models/Race.java index 165d9468..a396b508 100644 --- a/src/main/java/seng302/models/Race.java +++ b/src/main/java/seng302/models/Race.java @@ -1,5 +1,7 @@ package seng302.models; +import javafx.animation.AnimationTimer; +import seng302.controllers.Controller; import seng302.models.mark.Mark; import java.util.*; @@ -8,47 +10,137 @@ import java.util.*; * Race class containing the boats and legs in the race * Created by mra106 on 8/3/2017. */ -public class Race { +public class Race extends Thread { + + private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps + + private ArrayList boats; // The boats in the race private ArrayList finishingOrder; // The order in which the boats finish the race - private HashMap events = new HashMap<>(); // The events that occur in the race - private List course; // Marks in the race - private long startTime = 0; - private double timeScale = 1; + private List markers; // Marks in the race + private List raceLegs; private boolean raceFinished = false; // Race is finished private int raceTime = -2; // Current time in the race + private Double timeScale = null; + private boolean raceStarted = false; + private Controller controller; /** * Race class containing the boats and legs in the race */ - public Race() { - this.boats = new ArrayList<>(); + public Race(List markers, ArrayList boats, Double timescale, Controller controller) { + this.boats = boats; + this.markers = markers; + this.raceLegs = makeRaceLegs(); + this.controller = controller; + + this.timeScale = timescale; this.finishingOrder = new ArrayList<>(); - this.course = new ArrayList<>(); + } + + + /** + * Makes the race legs out of all the marker points for the course + * @return ArrayList of raceLegs + */ + private ArrayList makeRaceLegs() { + ArrayList raceLegs = new ArrayList<>(); + for (int i=0; i < markers.size()-1; i++) { + raceLegs.add(new Leg(markers.get(i), markers.get(i+1))); + } + return raceLegs; + } + + + /** + * A timer that is called every frame to call the action of moving the boats + */ + private AnimationTimer at = new AnimationTimer() { + @Override + public void handle(long now) { + + //Updating Boat positions + if(finishingOrder.size() != boats.size()) { + // update the time + boats.stream().filter(boat -> !finishingOrder.contains(boat)).forEach(boat -> { + updateBoatPosition(boat); + }); + } else { + at.stop(); + } + } + + }; + + + /** + * Begins the race simulation + */ + @Override + public void run() { + if (!raceStarted) { +// controller.getPlayPauseButton().setDisable(true); + at.start(); + } + } + + + /** + * Moves the coordinates of the boats. + * @param boat The boat to update the position of + */ + private void updateBoatPosition(Boat boat) { + Double thisHeading = boat.getHeading(); + // TODO: 4/8/17 wmu16 - Add a distance scale factor from lat long distance in Metres to xy equivalent + Double hypMove = boat.getVelocity() * UPDATE_TIME * timeScale; //* distanceScaleFactor + boat.setLegDistance(boat.getLegDistance() + hypMove); + moveBoat(boat, thisHeading, hypMove); } /** - * Add a boat to the race - * - * @param boat, the boat to add + * Moves a boat along coordinates by breaking down the distance moved along the hypotenuse into x and y components + * @param boat The Boat to move + * @param heading The heading the boat is moving at + * @param hypMove The distance moved along the hypotenuse (absolute distance) */ - public void addBoat(Boat boat) { - boats.add(boat); + private void moveBoat(Boat boat, Double heading, Double hypMove) { + //If the boat has not yet passed the marker at the end of the leg increase its (x,y) as per normal + // TODO: 4/8/17 wmu16 - Initialising boat positions and legs and headings etc. + if(boat.getLegDistance() <= boat.getCurrentLeg().getDistance()) { + Double xMove = hypMove * Math.sin(Math.toRadians(heading)); + Double yMove = - hypMove * Math.cos(Math.toRadians(heading)); + boat.moveBoatBy(xMove, yMove); + + //If the boat has finished... + } else if (getNextRaceLeg(boat.getCurrentLeg()).equals(boat.getCurrentLeg())) { + finishingOrder.add(boat); + //Otherwise apply the overflow distance of the leg to the next leg + } else { + Double overflowDistance = boat.getLegDistance() - boat.getCurrentLeg().getDistance(); + boat.setCurrentLeg(getNextRaceLeg(boat.getCurrentLeg())); + boat.setHeading(boat.getCurrentLeg().getHeading()); + boat.setLegDistance(overflowDistance); + moveBoat(boat, boat.getHeading(), overflowDistance); + } + } /** - * Returns a list of boats in a random order - * - * @return a list of boats + * Returns the next leg in the race + * @param currentRaceLeg The leg that you are currently on + * @return The next race leg or the same race leg if it has reached the end */ - public Boat[] getShuffledBoats() { - // Shuffle the list of boats - long seed = System.nanoTime(); - Collections.shuffle(this.boats, new Random(seed)); - - return boats.toArray(new Boat[boats.size()]); + private Leg getNextRaceLeg(Leg currentRaceLeg) { + Leg nextRaceLeg = currentRaceLeg; + for(int i = 0; i< raceLegs.size() - 1; i++) { + if (raceLegs.get(i).equals(currentRaceLeg)) { + nextRaceLeg = raceLegs.get(i + 1); + } + } + return nextRaceLeg; } + /** * Returns a list of boats in the order that they * finished the race (position 0 is first place) @@ -69,85 +161,28 @@ public class Race { return boats.toArray(new Boat[boats.size()]); } - /** - * Sets time scale - * - * @param timeScale - */ - public void setTimeScale(double timeScale) { - this.timeScale = timeScale; - } - - /** - * Generate all events that will happen during the race. - */ - private void generateEvents() { - - for (Boat boat : this.boats) { - double totalDistance = 0; - int numberOfMarks = this.course.size(); - - for (int i = 0; i < numberOfMarks; i++) { - Double time = (totalDistance / boat.getVelocity() / timeScale); - - // If there are singleMarks after this event - if (i < numberOfMarks - 1) { - Event event = new Event(time, boat, course.get(i), course.get(i + 1), i); - - try { - events.get(boat).add(event); - - } catch (NullPointerException e) { - events.put(boat, new ArrayList<>(Arrays.asList(event))); - } - totalDistance += event.getDistanceBetweenMarks(); - System.out.println(totalDistance); - System.out.println(boat.getVelocity()); - } - - // There are no more marks after this event - - else{ - Event event = new Event(time, boat, course.get(i), i); - events.get(boat).add(event); - } - } - } - } - - /** * Starts a race and generates all events for the race. */ public void startRace() { // record start time. - this.startTime = System.currentTimeMillis(); - generateEvents(); + + + + } + + public void pause() { + at.stop(); } /** - * Set the race course - * @param course a list of marks in the course - */ - public void addCourse(List course) { - this.course = course; - } - - /** - * Get a list of marks in the course + * Get a list of marks in the markers * @return */ - public List getCourse() { - return course; + public List getMarkers() { + return markers; } - /** - * Get a map of the events in the race - * @return - */ - public HashMap getEvents() { - return events; - } /** * Set a boat as finished @@ -191,7 +226,11 @@ public class Race { /** * Increment the race time by one second */ - public void incrementRaceTime(){ + public void incrementRaceTime() { this.raceTime += this.timeScale; } + + public List getRaceLegs() { + return raceLegs; + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/TimelineInfo.java b/src/main/java/seng302/models/TimelineInfo.java deleted file mode 100644 index 867ce67b..00000000 --- a/src/main/java/seng302/models/TimelineInfo.java +++ /dev/null @@ -1,31 +0,0 @@ -package seng302.models; - -import javafx.animation.Timeline; -import javafx.beans.property.DoubleProperty; - - -/** - * Created by zyt10 on 17/03/17. - * this class is literally just to associate a timeline with a DoubleProperty x and y - */ -public class TimelineInfo { - private Timeline timeline; - private DoubleProperty x; - private DoubleProperty y; - - public TimelineInfo(Timeline timeline, DoubleProperty x, DoubleProperty y) { - this.timeline = timeline; - this.x = x; - this.y = y; - } - - public Timeline getTimeline() { - return timeline; - } - public DoubleProperty getX() { - return x; - } - public DoubleProperty getY() { - return y; - } -} diff --git a/src/main/resources/config/course.xml b/src/main/resources/config/course.xml index f8bfa00e..2886ff77 100644 --- a/src/main/resources/config/course.xml +++ b/src/main/resources/config/course.xml @@ -1,6 +1,6 @@ - + Start @@ -68,4 +68,4 @@ Leeward Gate Finish - + diff --git a/src/main/resources/views/CanvasView.fxml b/src/main/resources/views/CanvasView.fxml deleted file mode 100644 index bc16ad7e..00000000 --- a/src/main/resources/views/CanvasView.fxml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/main/resources/views/FinishView.fxml b/src/main/resources/views/FinishView.fxml deleted file mode 100644 index debdea26..00000000 --- a/src/main/resources/views/FinishView.fxml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/views/MainView.fxml b/src/main/resources/views/MainView.fxml index ac0b944e..a89478b6 100644 --- a/src/main/resources/views/MainView.fxml +++ b/src/main/resources/views/MainView.fxml @@ -1,11 +1,47 @@ - + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml deleted file mode 100644 index f7fcbfeb..00000000 --- a/src/main/resources/views/RaceView.fxml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/java/seng302/BoatTest.java b/src/test/java/seng302/BoatTest.java deleted file mode 100644 index 0160bb8d..00000000 --- a/src/test/java/seng302/BoatTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.models.Boat; - -import static org.junit.Assert.assertEquals; - -/** - * Unit test for the Team class. - */ -public class BoatTest { - - @Test - public void testBoatCreation() { - Boat boat1 = new Boat("Team 1"); - assertEquals(boat1.getTeamName(), "Team 1"); - assertEquals(boat1.getVelocity(), (double) 10.0, 1e-15); - } - - @Test - public void testChangeTeamName() { - Boat boat1 = new Boat("Team 1"); - boat1.setTeamName("Team 2"); - assertEquals(boat1.getTeamName(), "Team 2"); - } - - @Test - public void testSetVelocity() { - Boat boat1 = new Boat("Team 1", 29.0, ""); - assertEquals(boat1.getVelocity(), (double) 29.0, 1e-15); - - boat1.setVelocity(12.0); - assertEquals(boat1.getVelocity(), (double)12.0, 1e-15); - } -} diff --git a/src/test/java/seng302/ColorsTest.java b/src/test/java/seng302/ColorsTest.java deleted file mode 100644 index a6681b26..00000000 --- a/src/test/java/seng302/ColorsTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package seng302; - -import javafx.scene.paint.Color; -import org.junit.Test; -import seng302.models.Boat; -import seng302.models.Colors; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -/** - * Created by ryan_ on 16/03/2017. - */ -public class ColorsTest { - //@Test - public void testNextColor() { - List boats = new ArrayList<>(); - boats.add(new Boat("Team 1")); - boats.add(new Boat("Team 2")); - boats.add(new Boat("Team 3")); - boats.add(new Boat("Team 4")); - boats.add(new Boat("Team 5")); - boats.add(new Boat("Team 6")); - - int count = 0; - List enumColors = new ArrayList<>(); - while (count < 6) { - Color color = Colors.getColor(); - enumColors.add(color); - count++; - } - - List boatColors = new ArrayList<>(); - for (Boat boat : boats) { - Color color = boat.getColor(); - boatColors.add(color); - } - - assertEquals(enumColors, boatColors); - } -} diff --git a/src/test/java/seng302/EventTest.java b/src/test/java/seng302/EventTest.java deleted file mode 100644 index 0be0fc98..00000000 --- a/src/test/java/seng302/EventTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.models.Boat; -import seng302.models.Event; -import seng302.models.mark.SingleMark; - -import static org.junit.Assert.assertEquals; - -/** - * Test for Event class - * Created by Haoming on 7/03/17. - */ -public class EventTest { - - @Test - public void getTimeString() throws Exception { - Boat boat = new Boat("testBoat"); - Event event = new Event(1231242.2, boat, new SingleMark("mark1"), new SingleMark("mark2"), 0); - assertEquals("20:31:242", event.getTimeString()); - } - - @Test - public void testBoatHeading() throws Exception { - Boat boat = new Boat("testBoat"); - Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1), new SingleMark("mark2", 121.9,99.2), 0); - - assertEquals(event.getBoatHeading(), 228.0266137055349, 1e-15); - } - - @Test - public void testDistanceBetweenMarks() throws Exception { - Boat boat = new Boat("testBoat"); - Event event = new Event(1231242.2, boat, new SingleMark("mark1", 142.5, 122.1), new SingleMark("mark2", 121.9,99.2), 0); - - assertEquals(event.getDistanceBetweenMarks(), 339059.653830461, 1e-15); - } -} \ No newline at end of file diff --git a/src/test/java/seng302/LegTest.java b/src/test/java/seng302/LegTest.java deleted file mode 100644 index 9bb64b6c..00000000 --- a/src/test/java/seng302/LegTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.models.Leg; -import seng302.models.mark.SingleMark; - -import static org.junit.Assert.assertEquals; - -/** - * Unit test for the Leg class. - */ -public class LegTest { - - /** - * Test creation of the leg by specifying a string - * for the marker label - */ - @Test - public void testLegCreationUsingMarkerLabel() { - Leg leg = new Leg(010, 100, "SingleMark"); - - assertEquals(leg.getHeading(), 010); - assertEquals(leg.getDistance(), 100); - assertEquals(leg.getMarkerLabel(), "SingleMark"); - assertEquals(leg.getIsFinishingLeg(), false); - } - - /** - * Test creation of the leg by providing a - * SingleMark object - */ - @Test - public void testLegCreation() { - Leg leg = new Leg(010, 100, new SingleMark("SingleMark")); - - assertEquals(leg.getHeading(), 010); - assertEquals(leg.getDistance(), 100); - assertEquals(leg.getMarkerLabel(), "SingleMark"); - assertEquals(leg.getIsFinishingLeg(), false); - } - - /** - * Test changing whether or not a - * leg is the finishing leg - */ - @Test - public void testSetFinishLeg() { - Leg leg = new Leg(010, 100, "SingleMark"); - - leg.setFinishingLeg(true); - assertEquals(leg.getIsFinishingLeg(), true); - } - -} diff --git a/src/test/java/seng302/RaceTest.java b/src/test/java/seng302/RaceTest.java deleted file mode 100644 index ab318331..00000000 --- a/src/test/java/seng302/RaceTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.models.Boat; -import seng302.models.Race; - -import java.lang.reflect.Array; - -import static org.junit.Assert.assertEquals; - -/** - * Unit test for the Race class. - */ -public class RaceTest { - /** - * Test that all boats were added to the race - */ - @Test - public void testAddingBoatsToRace() { - Boat boat1 = new Boat("Team 1"); - Boat boat2 = new Boat("Team 2"); - - Race race = new Race(); - race.addBoat(boat1); - race.addBoat(boat2); - - assertEquals(Array.getLength(race.getBoats()), 2); - } - - @Test - public void testGetShuffledBoats(){ - Boat boat1 = new Boat("Team 1"); - Boat boat2 = new Boat("Team 2"); - - Race race = new Race(); - race.addBoat(boat1); - race.addBoat(boat2); - - assertEquals(Array.getLength(race.getShuffledBoats()), 2); - } -} diff --git a/src/test/java/seng302/TestRaceTimer.java b/src/test/java/seng302/TestRaceTimer.java deleted file mode 100644 index 542405b1..00000000 --- a/src/test/java/seng302/TestRaceTimer.java +++ /dev/null @@ -1,25 +0,0 @@ -package seng302; - -import org.junit.Test; -import seng302.controllers.RaceViewController; - -import static org.junit.Assert.assertTrue; - - -public class TestRaceTimer { - @Test - public void testPositiveTimeString(){ - RaceViewController controller = new RaceViewController(); - String result = controller.convertTimeToMinutesSeconds(61); - - assertTrue(result.equals("01:01")); - } - - @Test - public void testNegativeTimeString(){ - RaceViewController controller = new RaceViewController(); - String result = controller.convertTimeToMinutesSeconds(-61); - - assertTrue(result.equals("-01:01")); - } -} diff --git a/src/test/java/seng302/models/mark/MarkTest.java b/src/test/java/seng302/models/mark/MarkTest.java deleted file mode 100644 index f1943c64..00000000 --- a/src/test/java/seng302/models/mark/MarkTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package seng302.models.mark; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Created by Haoming on 17/3/17. - */ -public class MarkTest { - - private SingleMark singleMark1; - private SingleMark singleMark2; - private GateMark gateMark; - - @Before - public void setUp() throws Exception { - this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342); - this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352); - this.gateMark = new GateMark("testMark_GM", singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude()); - } - - @Test - public void getName() throws Exception { - assertEquals("testMark_SM1", this.singleMark1.getName()); - assertEquals("testMark_GM", this.gateMark.getName()); - } - - @Test - public void getMarkType() throws Exception { - assertTrue(this.singleMark2.getMarkType() == MarkType.SINGLE_MARK); - assertTrue(this.gateMark.getMarkType() == MarkType.GATE_MARK); - } - - @Test - public void getMarkContent() throws Exception { - assertEquals(12.23234, this.singleMark1.getLatitude(), 1e-10); - assertEquals(-34.342, this.singleMark1.getLongitude(), 1e-10); - assertEquals("testMark_SM1", this.gateMark.getSingleMark1().getName()); - assertEquals(-34.352, this.gateMark.getSingleMark2().getLongitude(), 1e-10); - } - -} \ No newline at end of file diff --git a/src/test/java/seng302/models/parsers/ConfigParserTest.java b/src/test/java/seng302/models/parsers/ConfigParserTest.java deleted file mode 100644 index 8a0c0c8c..00000000 --- a/src/test/java/seng302/models/parsers/ConfigParserTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package seng302.models.parsers; - -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Created by Haoming on 23/03/17. - */ -public class ConfigParserTest { - - private ConfigParser cp; - - @Before - public void initializeParser() throws Exception { - cp = new ConfigParser("/config/config.xml"); - } - - @Test - public void getWindDirection() throws Exception { - assertEquals(135, cp.getWindDirection(), 1e-10); - } - - @Test - public void getTimeScale() throws Exception { - assertEquals(10.0, cp.getTimeScale(), 1e-10); - } - - @Test - public void getDoubleByTagName() throws Exception { - assertEquals(6, cp.getDoubleByTagName("race-size", 0), 1e-10); - assertEquals(100, cp.getDoubleByTagName("noTag", 100), 1e-10); - } - - @Test - public void getStringByTagName() throws Exception { - assertEquals("AC35", cp.getStringByTagName("race-name", "11")); - assertEquals("oops", cp.getStringByTagName("noTag", "oops")); - } - -} \ No newline at end of file diff --git a/src/test/java/seng302/models/parsers/CourseParserTest.java b/src/test/java/seng302/models/parsers/CourseParserTest.java deleted file mode 100644 index 865caec6..00000000 --- a/src/test/java/seng302/models/parsers/CourseParserTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package seng302.models.parsers; - -import org.junit.Before; -import org.junit.Test; -import seng302.models.mark.*; - -import java.util.ArrayList; - -import static org.junit.Assert.*; - -/** - * To test if course parser works as expected. - * Created by Haoming on 17/03/17. - */ -public class CourseParserTest { - - private CourseParser cp; - - @Before - public void initializeParser() throws Exception { - cp = new CourseParser("/config/course.xml"); - } - - @Test - public void getGates() throws Exception { - ArrayList course = cp.getCourse(); - - assertTrue(MarkType.GATE_MARK == course.get(0).getMarkType()); - - GateMark gateMark1 = (GateMark) course.get(0); - assertEquals(32.293771, gateMark1.getSingleMark2().getLatitude(), 0.00000001); - assertEquals(-64.855242, gateMark1.getSingleMark2().getLongitude(), 0.00000001); - - GateMark gateMark2 = (GateMark) course.get(5); - - assertEquals("Finish1", gateMark2.getSingleMark1().getName()); - assertEquals("Finish2", gateMark2.getSingleMark2().getName()); - assertEquals(32.317257, gateMark2.getSingleMark2().getLatitude(), 0.00000001); - assertEquals(-64.83626, gateMark2.getSingleMark2().getLongitude(), 0.00000001); - } - - @Test - public void getMarks() throws Exception { - ArrayList course = cp.getCourse(); - assertEquals("Mid Mark", course.get(1).getName()); - } - - @Test - public void getOrder() { - ArrayList course = cp.getCourse(); - assertEquals(6, course.size()); - assertEquals("Start", course.get(0).getName()); - assertEquals("Mid Mark", course.get(1).getName()); - assertEquals("Leeward Gate", course.get(2).getName()); - assertEquals("Windward Gate", course.get(3).getName()); - assertEquals("Leeward Gate", course.get(4).getName()); - assertEquals("Finish", course.get(5).getName()); - } - -} \ No newline at end of file diff --git a/src/test/java/seng302/models/parsers/TeamsParserTest.java b/src/test/java/seng302/models/parsers/TeamsParserTest.java deleted file mode 100644 index 3c31b519..00000000 --- a/src/test/java/seng302/models/parsers/TeamsParserTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package seng302.models.parsers; - -import org.junit.Before; -import org.junit.Test; -import seng302.models.Boat; - -import java.util.ArrayList; - -import static org.junit.Assert.*; - -/** - * Created by Haoming on 18/03/17. - */ -public class TeamsParserTest { - - private TeamsParser tp; - @Before - public void readFile() { - tp = new TeamsParser("/config/teams.xml"); - } - - @Test - public void getBoats() throws Exception { - ArrayList boats = tp.getBoats(); - - assertEquals(6, boats.size(), 1e-10); - - assertEquals("Oracle Team USA", boats.get(0).getTeamName()); - //assertEquals(30.9, boats.get(0).getVelocity(), 1e-10); - - assertEquals("Groupama Team France", boats.get(5).getTeamName()); - //assertEquals(45.6, boats.get(5).getVelocity(), 1e-10); - } - -} \ No newline at end of file From 15ded667fe9095c99a5fc116f1fb57d95f42d6e2 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Tue, 11 Apr 2017 17:46:02 +1200 Subject: [PATCH 02/97] Started to implement the group over the canvas in the code. Removed basic boat redrawing and timeline and replaced with boats being placed into a group and given coordinates. --- .../seng302/controllers/CanvasController.java | 69 +++++++++--- .../controllers/RaceViewController.java | 88 ++++++++------- src/main/java/seng302/models/Boat.java | 102 ++++++++++++++++++ src/main/java/seng302/models/Event.java | 30 +++++- 4 files changed, 232 insertions(+), 57 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index c0b20e26..e36fd052 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -1,7 +1,9 @@ package seng302.controllers; import javafx.animation.*; +import javafx.beans.property.SimpleDoubleProperty; import javafx.fxml.FXML; +import javafx.scene.Group; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.AnchorPane; @@ -14,6 +16,7 @@ import seng302.models.mark.Mark; import seng302.models.mark.MarkType; import seng302.models.mark.SingleMark; +import java.sql.Time; import java.util.*; /** @@ -27,6 +30,7 @@ public class CanvasController { private RaceViewController raceViewController; private ResizableCanvas canvas; + private Group group; private GraphicsContext gc; private final double ORIGIN_LAT = 32.321504; @@ -38,30 +42,51 @@ public class CanvasController { } public void initialize() { + raceViewController = new RaceViewController(); canvas = new ResizableCanvas(); + group = new Group(); + canvasPane.getChildren().add(canvas); + canvasPane.getChildren().add(group); // Bind canvas size to stack pane size. - canvas.widthProperty().bind(canvasPane.widthProperty()); - canvas.heightProperty().bind(canvasPane.heightProperty()); + canvas.widthProperty().bind(new SimpleDoubleProperty(1000)); + canvas.heightProperty().bind(new SimpleDoubleProperty(1000)); + group.minWidth(1000); + group.minHeight(1000); +// canvas.widthProperty().bind(canvasPane.widthProperty()); +// canvas.heightProperty().bind(canvasPane.heightProperty()); +// group.minWidth(canvas.getWidth()); +// group.minHeight(canvas.getHeight()); + + + } + + + public void setUpBoats(){ + gc = canvas.getGraphicsContext2D(); - - + gc.save(); + gc.setFill(Color.SKYBLUE); + gc.fillRect(0,0, 1000,1000); + gc.restore(); + drawBoats(); + drawCourse(); + drawFps(12); // overriding the handle so that it can clean canvas and redraw boats and course marks AnimationTimer timer = new AnimationTimer() { private long lastUpdate = 0; private long lastFpsUpdate = 0; private int lastFpsCount = 0; private int fpsCount = 0; + boolean done = true; @Override public void handle(long now) { if (true){ //if statement for limiting refresh rate if needed - gc.clearRect(0, 0, canvas.getWidth(),canvas.getHeight()); - gc.setFill(Color.SKYBLUE); - gc.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); - drawCourse(); - drawBoats(); - drawFps(lastFpsCount); +// gc.clearRect(0, 0, canvas.getWidth(),canvas.getHeight()); +// gc.setFill(Color.SKYBLUE); +// gc.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); + // If race has started, draw the boats and play the timeline if (raceViewController.getRace().getRaceTime() > 1){ @@ -129,14 +154,26 @@ public class CanvasController { * Draws all the boats. */ private void drawBoats() { - Map timelineInfos = raceViewController.getTimelineInfos(); - for (Boat boat : timelineInfos.keySet()) { - TimelineInfo timelineInfo = timelineInfos.get(boat); +// Map timelineInfos = raceViewController.getTimelineInfos(); + ArrayList boats = raceViewController.getStartingBoats(); + Double startingY = (ORIGIN_LAT - raceViewController.getRace().getCourse().get(0).getLatitude()) * SCALE; + Double startingX = (ORIGIN_LON - raceViewController.getRace().getCourse().get(0).getLongitude()) * SCALE; - boat.setLocation(timelineInfo.getY().doubleValue(), timelineInfo.getX().doubleValue()); - - drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); + for (Boat boat : boats) { + boat.moveBoatTo(startingX, startingY); + group.getChildren().add(boat.getWake()); + group.getChildren().add(boat.getBoatObject()); + group.getChildren().add(boat.getTeamNameObject()); + group.getChildren().add(boat.getVelocityObject()); +// drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); } +// for (Boat boat : timelineInfos.keySet()) { +// TimelineInfo timelineInfo = timelineInfos.get(boat); +// +// boat.setLocation(timelineInfo.getY().doubleValue(), timelineInfo.getX().doubleValue()); +// +// drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); +// } } /** diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 965fbc7d..83352357 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -42,6 +42,7 @@ public class RaceViewController { @FXML private CanvasController includedCanvasController; + private ArrayList startingBoats = new ArrayList<>(); private boolean displayAnnotations; private boolean displayFps; private Timeline timerTimeline; @@ -50,24 +51,29 @@ public class RaceViewController { private Race race; public void initialize() { - includedCanvasController.setup(this); RaceController raceController = new RaceController(); raceController.initializeRace(); race = raceController.getRace(); + for (Boat boat : race.getBoats()) { + startingBoats.add(boat); + } +// try{ +// initializeTimelines(); +// } +// catch (Exception e){ +// e.printStackTrace(); +// } + includedCanvasController.setup(this); + includedCanvasController.setUpBoats(); initializeTimer(); initializeSettings(); - try{ - initializeTimelines(); - } - catch (Exception e){ - e.printStackTrace(); - } //set wind direction!!!!!!! can't find another place to put my code --haoming double windDirection = new ConfigParser("/config/config.xml").getWindDirection(); windDirectionText.setText(String.format("%.1f°", windDirection)); windArrowText.setRotate(windDirection); + } private void initializeSettings(){ @@ -114,39 +120,39 @@ public class RaceViewController { */ private void initializeTimelines() { HashMap boat_events = race.getEvents(); - for (Boat boat : boat_events.keySet()) { - // x, y are the real time coordinates - DoubleProperty x = new SimpleDoubleProperty(); - DoubleProperty y = new SimpleDoubleProperty(); - - List keyFrames = new ArrayList<>(); - List events = boat_events.get(boat); - - // iterates all events and convert each event to keyFrame, then add them into a list - for (Event event : events) { - if (event.getIsFinishingEvent()) { - keyFrames.add( - new KeyFrame(Duration.seconds(event.getTime()), - onFinished -> {race.setBoatFinished(boat); handleEvent(event);}, - new KeyValue(x, event.getThisMark().getLatitude()), - new KeyValue(y, event.getThisMark().getLongitude()) - ) - ); - } else { - keyFrames.add( - new KeyFrame(Duration.seconds(event.getTime()), - onFinished ->{ - handleEvent(event); - boat.setHeading(event.getBoatHeading()); - }, - new KeyValue(x, event.getThisMark().getLatitude()), - new KeyValue(y, event.getThisMark().getLongitude()) - ) - ); - } - } - timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y)); + startingBoats.add(boat); +// // x, y are the real time coordinates +// DoubleProperty x = new SimpleDoubleProperty(); +// DoubleProperty y = new SimpleDoubleProperty(); +// +// List keyFrames = new ArrayList<>(); +// List events = boat_events.get(boat); +// +// // iterates all events and convert each event to keyFrame, then add them into a list +// for (Event event : events) { +// if (event.getIsFinishingEvent()) { +// keyFrames.add( +// new KeyFrame(Duration.seconds(event.getTime()), +// onFinished -> {race.setBoatFinished(boat); handleEvent(event);}, +// new KeyValue(x, event.getThisMark().getLatitude()), +// new KeyValue(y, event.getThisMark().getLongitude()) +// ) +// ); +// } else { +// keyFrames.add( +// new KeyFrame(Duration.seconds(event.getTime()), +// onFinished ->{ +// handleEvent(event); +// boat.setHeading(event.getBoatHeading()); +// }, +// new KeyValue(x, event.getThisMark().getLatitude()), +// new KeyValue(y, event.getThisMark().getLongitude()) +// ) +// ); +// } +// } +// timelineInfos.put(boat, new TimelineInfo(new Timeline(keyFrames.toArray(new KeyFrame[keyFrames.size()])), x, y)); } setRaceDuration(); } @@ -277,4 +283,8 @@ public class RaceViewController { public Map getTimelineInfos() { return timelineInfos; } + + public ArrayList getStartingBoats(){ + return startingBoats; + } } \ 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 0973ad38..f39f9e3b 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -1,12 +1,24 @@ package seng302.models; import javafx.scene.paint.Color; +import javafx.scene.shape.Polygon; +import javafx.scene.text.Text; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; /** * Represents a boat in the race. */ public class Boat { + private static final double TEAMNAME_X_OFFSET = 15d; + private static final double TEAMNAME_Y_OFFSET = -20d; + private static final double VELOCITY_X_OFFSET = 15d; + private static final double VELOCITY_Y_OFFSET = -10d; + private static final double VELOCITY_WAKE_RATIO = 2d; //Ratio for deciding how long the wake will be wrt velocity + private static final double BOAT_HEIGHT = 15d; + private static final double BOAT_WIDTH = 10d; + private String teamName; // The name of the team, this is also the name of the boat private double velocity; // In meters/second private double lat; // Boats position @@ -17,6 +29,12 @@ public class Boat { private double heading; private String shortName; + //Graphical + private Polygon boatObject; + private Polygon wake; + private Text teamNameObject; + private Text velocityObject; + public Boat(String teamName) { this.teamName = teamName; this.velocity = 10; // Default velocity @@ -39,6 +57,13 @@ public class Boat { this.distanceToNextMark = 0.0; this.color = Colors.getColor(); this.shortName = shortName; + this.boatObject = new Polygon(); + this.boatObject.getPoints().addAll(BOAT_WIDTH /2,0.0, + BOAT_WIDTH, BOAT_HEIGHT, + 0.0, BOAT_HEIGHT); + createWake(); + this.teamNameObject = new Text(shortName); + this.velocityObject = new Text(Double.toString(boatVelocity) + "ms"); } /** @@ -117,6 +142,11 @@ public class Boat { } public void setHeading(double heading){ + boatObject.getTransforms().clear(); + wake.getTransforms().clear(); + wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); + wake.getTransforms().add(new Rotate(heading, BOAT_WIDTH/2, -BOAT_HEIGHT)); + boatObject.getTransforms().add(new Rotate(heading, BOAT_WIDTH/2, 0)); this.heading = heading; } @@ -127,4 +157,76 @@ public class Boat { public String getShortName(){ return this.shortName; } + + + /** + * Moves the boat and its children annotations from its current coordinates by specified amounts. + * @param x The amount to move the X coordinate by + * @param y The amount to move the Y coordinate by + */ + void moveBoatBy(Double x, Double y) { + boatObject.setLayoutX(boatObject.getLayoutX() + x); + boatObject.setLayoutY(boatObject.getLayoutY() + y); + boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); + + teamNameObject.setX(teamNameObject.getX() + x); + teamNameObject.setY(teamNameObject.getY() + y); + teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); + + velocityObject.setX(velocityObject.getX() + x); + velocityObject.setY(velocityObject.getY() + y); + velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + + + wake.setLayoutX(wake.getLayoutX() + x); + wake.setLayoutY(wake.getLayoutY() + y); + wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + } + + /** + * 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 moveBoatTo(Double x, Double y) { + boatObject.setLayoutX(x); + boatObject.setLayoutY(y); + boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); + + teamNameObject.setX(x + TEAMNAME_X_OFFSET); + teamNameObject.setY(y + TEAMNAME_Y_OFFSET); + teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); + + velocityObject.setX(x + VELOCITY_X_OFFSET); + velocityObject.setY(y + VELOCITY_Y_OFFSET); + velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + + wake.setLayoutX(x); + wake.setLayoutY(y); + wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + } + + private void createWake(){ + wake = new Polygon(); + wake.setFill(Color.LIGHTSKYBLUE); + wake.getPoints().addAll(5.0,0.0, + 10.0, velocity * VELOCITY_WAKE_RATIO, + 0.0, velocity * VELOCITY_WAKE_RATIO); + } + + public Polygon getWake() { + return wake; + } + + public Polygon getBoatObject() { + return boatObject; + } + + public Text getTeamNameObject() { + return teamNameObject; + } + + public Text getVelocityObject() { + return velocityObject; + } } \ No newline at end of file diff --git a/src/main/java/seng302/models/Event.java b/src/main/java/seng302/models/Event.java index e803845d..df298741 100644 --- a/src/main/java/seng302/models/Event.java +++ b/src/main/java/seng302/models/Event.java @@ -16,7 +16,7 @@ public class Event { private Mark mark1; // This mark private Mark mark2; // Next mark private int markPosInRace; // the position of the current mark in the race course - + private double heading; private final double ORIGIN_LAT = 32.320504; private final double ORIGIN_LON = -64.857063; private final double SCALE = 16000; @@ -36,6 +36,8 @@ public class Event { this.mark1 = mark1; this.mark2 = mark2; this.markPosInRace = markPosInRace; + this.heading = angleFromCoordinate(mark1, mark2); + } /** @@ -92,7 +94,7 @@ public class Event { if (this.isFinishingEvent) { return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " finished the race"); } - System.out.println(this.getDistanceBetweenMarks()); +// System.out.println(this.getDistanceBetweenMarks()); return (this.getTimeString() + ", " + this.getBoat().getTeamName() + " passed " + this.mark1.getName() + " going heading " + this.getBoatHeading() + "°"); } @@ -138,6 +140,30 @@ public class Event { } + /** + * Calculates the angle between to angular co-ordinates on a sphere. + * + * @param geoPointOne first geographical location + * @param geoPointTwo second geographical location + * @return the angle from point one to point two + */ + private Double angleFromCoordinate(Mark geoPointOne, Mark geoPointTwo) { + if (geoPointTwo == null) + return null; + + double x1 = geoPointOne.getLatitude(); + double y1 = -geoPointOne.getLongitude(); + double x2 = geoPointTwo.getLatitude(); + double y2 = -geoPointTwo.getLongitude(); + + return Math.toDegrees(Math.atan2(x2-x1, y2-y1)); + + } + + public double getHeading() { + return heading; + } + public Mark getThisMark() { return this.mark1; } From edc306da22ace3ea459085af0603f280467d3706 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Wed, 19 Apr 2017 19:05:19 +1200 Subject: [PATCH 03/97] Created AC35 Streaming server - Sends heartbeat messages every 5 seconds - Sends XML at beginning Tags: #story[29] --- src/main/java/seng302/App.java | 2 + .../java/seng302/server/ServerThread.java | 94 +++++++ .../seng302/server/StreamingServerSocket.java | 53 ++++ .../java/seng302/server/messages/Header.java | 71 ++++++ .../seng302/server/messages/Heartbeat.java | 51 ++++ .../java/seng302/server/messages/Message.java | 31 +++ .../seng302/server/messages/MessageType.java | 34 +++ .../seng302/server/messages/XMLMessage.java | 96 +++++++ .../server/messages/XMLMessageSubType.java | 20 ++ src/main/resources/server_config/boats.xml | 234 ++++++++++++++++++ src/main/resources/server_config/race.xml | 85 +++++++ src/main/resources/server_config/regatta.xml | 12 + 12 files changed, 783 insertions(+) create mode 100644 src/main/java/seng302/server/ServerThread.java create mode 100644 src/main/java/seng302/server/StreamingServerSocket.java create mode 100644 src/main/java/seng302/server/messages/Header.java create mode 100644 src/main/java/seng302/server/messages/Heartbeat.java create mode 100644 src/main/java/seng302/server/messages/Message.java create mode 100644 src/main/java/seng302/server/messages/MessageType.java create mode 100644 src/main/java/seng302/server/messages/XMLMessage.java create mode 100644 src/main/java/seng302/server/messages/XMLMessageSubType.java create mode 100644 src/main/resources/server_config/boats.xml create mode 100644 src/main/resources/server_config/race.xml create mode 100644 src/main/resources/server_config/regatta.xml diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 0637d2cc..1a869e78 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -5,6 +5,7 @@ import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; +import seng302.server.ServerThread; public class App extends Application { @@ -18,6 +19,7 @@ public class App extends Application } public static void main(String[] args) { + new ServerThread("Racevision Test Server"); launch(args); } } diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java new file mode 100644 index 00000000..cd4c091f --- /dev/null +++ b/src/main/java/seng302/server/ServerThread.java @@ -0,0 +1,94 @@ +package seng302.server; + +import seng302.server.messages.Heartbeat; +import seng302.server.messages.Message; +import seng302.server.messages.XMLMessage; +import seng302.server.messages.XMLMessageSubType; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Timer; +import java.util.TimerTask; + +public class ServerThread implements Runnable{ + private Thread runner; + private StreamingServerSocket server; + private final int HEARTBEAT_PERIOD = 5000; + private final int PORT_NUMBER = 8085; + + public ServerThread(String threadName){ + runner = new Thread(this, threadName); + System.out.println("Spawning Server Thread"); + runner.start(); + } + + /** + * Creates and returns an XML Message from the file specified + * @param fileName The source XML file + * @param type The XML Message type + * @return The XML Message + */ + public Message getXmlMessage(String fileName, XMLMessageSubType type){ + String fileContents = null; + + try { + fileContents = new String(Files.readAllBytes(Paths.get(this.getClass().getResource(fileName).getPath().substring(1)))); + } catch (IOException e) { + e.printStackTrace(); + } + + if (fileContents != null){ + return new XMLMessage(fileContents, type, server.getSequenceNumber()); + } + + return null; + } + + public void run() { + try{ + server = new StreamingServerSocket(PORT_NUMBER); + } + catch (IOException e){ + System.err.println("Failed to bind socket: " + e.getMessage()); + } + + server.start(); + + try { + // Load and send race XML data + Message raceData = getXmlMessage("/server_config/race.xml", XMLMessageSubType.RACE); + Message boatData = getXmlMessage("/server_config/boats.xml", XMLMessageSubType.BOAT); + Message regatta = getXmlMessage("/server_config/regatta.xml", XMLMessageSubType.REGATTA); + + if (raceData != null){ + server.send(raceData); + } + + if (boatData != null){ + server.send(boatData); + } + + if (regatta != null){ + server.send(regatta); + } + + // Timer to send the heartbeat + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + Message hb = new Heartbeat(server.getSequenceNumber()); + try { + server.send(hb); + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 0, HEARTBEAT_PERIOD); + + } catch (IOException e) { + System.err.println(e.getMessage()); + } + } +} diff --git a/src/main/java/seng302/server/StreamingServerSocket.java b/src/main/java/seng302/server/StreamingServerSocket.java new file mode 100644 index 00000000..d34eae8a --- /dev/null +++ b/src/main/java/seng302/server/StreamingServerSocket.java @@ -0,0 +1,53 @@ +package seng302.server; + +import seng302.server.messages.Message; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +class StreamingServerSocket { + private java.net.ServerSocket socket; + private Socket client; + private List clients; + private short seqNum; + + StreamingServerSocket(int port) throws IOException{ + socket = new java.net.ServerSocket(port); + clients = new ArrayList<>(); + socket.setSoTimeout(10000); + seqNum = 0; + } + + void start(){ + System.out.println("Listening For Connections"); + try { + client = socket.accept(); + } catch (IOException e) { + e.getMessage(); + } + if (client == null){ + start(); + } + else{ + System.out.println("client connected from " + client.getInetAddress()); + } + } + + void send(Message message) throws IOException{ + if (client == null){ + return; + } + + DataOutputStream outputStream = new DataOutputStream(client.getOutputStream()); + message.send(outputStream); + + seqNum++; + } + + public short getSequenceNumber(){ + return seqNum; + } +} diff --git a/src/main/java/seng302/server/messages/Header.java b/src/main/java/seng302/server/messages/Header.java new file mode 100644 index 00000000..9f1a81e0 --- /dev/null +++ b/src/main/java/seng302/server/messages/Header.java @@ -0,0 +1,71 @@ +package seng302.server.messages; + +import java.nio.ByteBuffer; + +public class Header { + // From API spec + private final int syncByte1 = 0x47; + private final int syncByte2 = 0x83; + + private MessageType messageType; + private int timeStamp; + private int sourceId; + private short messageLength; + private static final int MESSAGE_LEN = 15; + + /** + * Message Header from section 3.2 of the AC35 Streaming + * Data spec + * @param messageType The type of the message following this header + * @param sourceId The message source (as defined in the spec) + * @param messageLength The length of the message following this header + */ + public Header(MessageType messageType, int sourceId, Short messageLength){ + this.messageType = messageType; + this.sourceId = sourceId; + this.messageLength = messageLength; + timeStamp = (int) (System.currentTimeMillis() / 1000L); + } + + /** + * @return a ByteBuffer containing the message header + */ + public ByteBuffer getByteBuffer(){ + ByteBuffer buff = ByteBuffer.allocate(15); + + // Sync Byte 1, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)syncByte1).array()); + buff.position(1); + + // Sync Byte 2, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)syncByte2).array()); + buff.position(2); + + // Message Type, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)messageType.getCode()).array()); + buff.position(3); + + // Timestamp, 6 bytes + int x = ((int) Integer.toUnsignedLong(6)); + buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array()); + buff.position(9); + + // Source ID, 4 bytes + buff.put(ByteBuffer.allocate(4).putInt(sourceId).array()); + buff.position(13); + + // Message Length, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort(messageLength).array()); + buff.position(15); + + return buff; + } + + /** + * Returns the size of this message + * @return the size of the message + */ + public static Integer getSize(){ + return MESSAGE_LEN; + } +} diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/server/messages/Heartbeat.java new file mode 100644 index 00000000..a6470240 --- /dev/null +++ b/src/main/java/seng302/server/messages/Heartbeat.java @@ -0,0 +1,51 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.zip.CRC32; + +public class Heartbeat extends Message { + private final int MESSAGE_SIZE = 4; + private int seqNo; + + /** + * Heartbeat from the AC35 Streaming data spec + * @param seqNo Increment every time a message is sent + */ + public Heartbeat(int seqNo){ + this.seqNo = seqNo; + } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + + @Override + public void send(DataOutputStream outputStream) { + setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize())); + + ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + getSize()); + + // Write header + buff.put(getHeader().getByteBuffer()); + buff.position(Header.getSize()); + + // Write seq num + buff.put(ByteBuffer.allocate(4).putInt(seqNo).array()); + buff.position(Header.getSize()+4); + + // Write CRC + CRC32 crc = new CRC32(); + crc.update(buff.array()); + + buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + + try { + outputStream.write(buff.array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java new file mode 100644 index 00000000..8e5c48b4 --- /dev/null +++ b/src/main/java/seng302/server/messages/Message.java @@ -0,0 +1,31 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; + +public abstract class Message { + Header header; + + /** + * @param header Set the header for this message + */ + public void setHeader(Header header){ + this.header = header; + } + + /** + * @return the header specified for this message + */ + public Header getHeader(){ + return header; + } + + /** + * @return the size of the message + */ + public abstract int getSize(); + + /** + * Send the message as through the outputStream + */ + public abstract void send(DataOutputStream outputStream); +} diff --git a/src/main/java/seng302/server/messages/MessageType.java b/src/main/java/seng302/server/messages/MessageType.java new file mode 100644 index 00000000..be856dac --- /dev/null +++ b/src/main/java/seng302/server/messages/MessageType.java @@ -0,0 +1,34 @@ +package seng302.server.messages; + +/** + * Enum containing the types of messages + * sent by the server + */ +public enum MessageType { + HEARTBEAT(1), + RACE_STATUS(12), + DISPLAY_TEXT_MESSAGE(20), + XML_MESSAGE(26), + RACE_START_STATUS(27), + YACHT_EVENT_CODE(29), + YACHT_ACTION_CODE(31), + CHATTER_TEXT(36), + BOAT_LOCATION(37), + MARK_ROUNDING(38), + COURSE_WIND(44), + AVERAGE_WIND(47); + + private int code; + + MessageType(int code){ + this.code = code; + } + + /** + * Get the message code (From the API Spec) + * @return the message code + */ + int getCode(){ + return this.code; + } +} diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/server/messages/XMLMessage.java new file mode 100644 index 00000000..2d7e0431 --- /dev/null +++ b/src/main/java/seng302/server/messages/XMLMessage.java @@ -0,0 +1,96 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.zip.CRC32; + +public class XMLMessage extends Message{ + private final MessageType MESSAGE_TYPE = MessageType.XML_MESSAGE; + private final int MESSAGE_VERSION = 1; //Always set to 1 + private final int MESSAGE_SIZE = 14; + + // Message fields + private int timeStamp; + private short ack = 0x00; //Unused + private XMLMessageSubType xmlMessageSubType; + private Short length; + private Short sequence; + private String content; + private CRC32 crc; + + /** + * XML Message from the AC35 Streaming data spec + * @param content The XML content + * @param type The XML Message Sub Type + */ + public XMLMessage(String content, XMLMessageSubType type, short sequenceNum){ + this.content = content; + this.xmlMessageSubType = type; + crc = new CRC32(); + timeStamp = (int) (System.currentTimeMillis() / 1000L); + ack = 0; + length = (short) this.content.length(); + sequence = sequenceNum; + + setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); + } + + /** + * @return The length of this message + */ + public int getSize(){ + return MESSAGE_SIZE + content.length(); + } + + /** + * Send this message as a stream of bytes + * @param outputStream The output stream to send the message + */ + public void send(DataOutputStream outputStream) { + ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4); + buff.put(getHeader().getByteBuffer()); + buff.position(Header.getSize()); + + // Version Number, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); + buff.position(Header.getSize() + 1); + + // Ack, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort(ack).array()); + buff.position(Header.getSize() + 3); + + // Timestamp, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array()); + buff.position(Header.getSize() + 9); + + // XML message sub type, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)xmlMessageSubType.getType()).array()); + buff.position(Header.getSize() + 10); + + // Seq num, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort(sequence).array()); + buff.position(Header.getSize() + 12); + + // Message length, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort(length).array()); + buff.position(Header.getSize() + 14); + + // XML Content + buff.put(this.content.getBytes()); + buff.position(Header.getSize() + 14 + this.content.getBytes().length); + + // calculate CRC + crc.update(buff.array()); + + // Add CRC to message + buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + + // Send + try { + outputStream.write(buff.array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/XMLMessageSubType.java b/src/main/java/seng302/server/messages/XMLMessageSubType.java new file mode 100644 index 00000000..2e146c5a --- /dev/null +++ b/src/main/java/seng302/server/messages/XMLMessageSubType.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Enum containing the types of XML messages + */ +public enum XMLMessageSubType { + REGATTA(5), + RACE(6), + BOAT(7); + + private int type; + + XMLMessageSubType(int type){ + this.type = type; + } + + public int getType(){ + return this.type; + } +} diff --git a/src/main/resources/server_config/boats.xml b/src/main/resources/server_config/boats.xml new file mode 100644 index 00000000..df5ddca4 --- /dev/null +++ b/src/main/resources/server_config/boats.xml @@ -0,0 +1,234 @@ + + + 2015-08-28T17:32:59+0100 + 12 + 219 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/race.xml b/src/main/resources/server_config/race.xml new file mode 100644 index 00000000..845f2044 --- /dev/null +++ b/src/main/resources/server_config/race.xml @@ -0,0 +1,85 @@ + + + 2015-08-29T13:12:40+02:00 + + 15082901 + Fleet + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/server_config/regatta.xml b/src/main/resources/server_config/regatta.xml new file mode 100644 index 00000000..6abcf2da --- /dev/null +++ b/src/main/resources/server_config/regatta.xml @@ -0,0 +1,12 @@ + + + 24 + Gothenburg World Series 2015 + Gothenburg + 57.6679590 + 11.8503233 + 6.95 + 2 + 3.2 + gothenburg_shoreline + \ No newline at end of file From 6a27dedd74f3c90ccb0e70c97f0264557faa8ddb Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 20 Apr 2017 15:08:50 +1200 Subject: [PATCH 04/97] Simple test to get stream data --- .../models/parsers/InputStreamParser.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/main/java/seng302/models/parsers/InputStreamParser.java diff --git a/src/main/java/seng302/models/parsers/InputStreamParser.java b/src/main/java/seng302/models/parsers/InputStreamParser.java new file mode 100644 index 00000000..19005c1d --- /dev/null +++ b/src/main/java/seng302/models/parsers/InputStreamParser.java @@ -0,0 +1,70 @@ +package seng302.models.parsers; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; + + +public class InputStreamParser { + + private static String currentLine; + private static BufferedReader buffer = null; + + private static void readLine() { + try { + currentLine = buffer.readLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void runTest() { + + Socket host = null; + String hostAddress = "csse-s302staff.canterbury.ac.nz"; + int hostPort = 4941; + + try { + host = new Socket(hostAddress, hostPort); + } catch (IOException e) { + e.printStackTrace(); + } + + try { + //buffer = new DataInputStream(host.getInputStream()); + if (host != null) { + buffer = new BufferedReader(new InputStreamReader(host.getInputStream())); + } + } catch (IOException e) { + e.printStackTrace(); + } + + readLine(); + boolean reading = true; + while(reading) { + System.out.println(currentLine); + readLine(); + if (currentLine == null) { + reading = false; + } + } + + try { + if (host != null) { + host.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + + + } + + public static void main(String[] args) { + + runTest(); + + } + +} From b5129c5c806d06babf54f7dd0bb402151574e96f Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 20 Apr 2017 19:06:32 +1200 Subject: [PATCH 05/97] Moved the canvas drawing implementation from team27's codebase to team13's. #story30b --- .../seng302/controllers/CanvasController.java | 303 ++++++++++++------ .../java/seng302/models/mark/GateMark.java | 16 +- src/main/java/seng302/models/mark/Mark.java | 89 +++++ .../java/seng302/models/mark/MarkType.java | 2 +- .../seng302/models/parsers/CourseParser.java | 6 +- .../java/seng302/models/mark/MarkTest.java | 4 +- .../models/parsers/CourseParserTest.java | 2 +- 7 files changed, 323 insertions(+), 99 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index e36fd052..0499eb05 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -9,8 +9,8 @@ import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; import javafx.scene.text.Font; +import javafx.util.Pair; import seng302.models.Boat; -import seng302.models.TimelineInfo; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.MarkType; @@ -33,9 +33,27 @@ public class CanvasController { private Group group; private GraphicsContext gc; - private final double ORIGIN_LAT = 32.321504; - private final double ORIGIN_LON = -64.857063; - private final int SCALE = 16000; + private final int MARK_SIZE = 10; + private final int BUFFER_SIZE = 25; + private final int CANVAS_SIZE = 1000; + private final int LHS_BUFFER = BUFFER_SIZE; + private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2; + private final int TOP_BUFFER = BUFFER_SIZE; + private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2; + + private double distanceScaleFactor; + private ScaleDirection scaleDirection; + private Mark minLatPoint; + private Mark minLonPoint; + private Mark maxLatPoint; + private Mark maxLonPoint; + private int referencePointX; + private int referencePointY; + + private enum ScaleDirection { + HORIZONTAL, + VERTICAL + } public void setup(RaceViewController raceViewController){ this.raceViewController = raceViewController; @@ -49,10 +67,10 @@ public class CanvasController { canvasPane.getChildren().add(canvas); canvasPane.getChildren().add(group); // Bind canvas size to stack pane size. - canvas.widthProperty().bind(new SimpleDoubleProperty(1000)); - canvas.heightProperty().bind(new SimpleDoubleProperty(1000)); - group.minWidth(1000); - group.minHeight(1000); + canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_SIZE)); + canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_SIZE)); + group.minWidth(CANVAS_SIZE); + group.minHeight(CANVAS_SIZE); // canvas.widthProperty().bind(canvasPane.widthProperty()); // canvas.heightProperty().bind(canvasPane.heightProperty()); // group.minWidth(canvas.getWidth()); @@ -67,10 +85,16 @@ public class CanvasController { gc = canvas.getGraphicsContext2D(); gc.save(); gc.setFill(Color.SKYBLUE); - gc.fillRect(0,0, 1000,1000); + gc.fillRect(0,0, CANVAS_SIZE,CANVAS_SIZE); gc.restore(); - drawBoats(); drawCourse(); + for (Mark m : raceViewController.getRace().getCourse()) + { + System.out.println("MARK NAME - " + m.getName()); + System.out.println("X LOCATION - " + m.getX()); + System.out.println("Y LOCATION - " + m.getY()); + } + drawBoats(); drawFps(12); // overriding the handle so that it can clean canvas and redraw boats and course marks AnimationTimer timer = new AnimationTimer() { @@ -156,8 +180,8 @@ public class CanvasController { private void drawBoats() { // Map timelineInfos = raceViewController.getTimelineInfos(); ArrayList boats = raceViewController.getStartingBoats(); - Double startingY = (ORIGIN_LAT - raceViewController.getRace().getCourse().get(0).getLatitude()) * SCALE; - Double startingX = (ORIGIN_LON - raceViewController.getRace().getCourse().get(0).getLongitude()) * SCALE; + Double startingX = (double) raceViewController.getRace().getCourse().get(0).getX(); + Double startingY = (double) raceViewController.getRace().getCourse().get(0).getY(); for (Boat boat : boats) { boat.moveBoatTo(startingX, startingY); @@ -167,75 +191,8 @@ public class CanvasController { group.getChildren().add(boat.getVelocityObject()); // drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); } -// for (Boat boat : timelineInfos.keySet()) { -// TimelineInfo timelineInfo = timelineInfos.get(boat); -// -// boat.setLocation(timelineInfo.getY().doubleValue(), timelineInfo.getX().doubleValue()); -// -// drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); -// } } - /** - * Draw the wake line behind a boat - * @param gc The graphics context used for drawing the wake - * @param x the x position of the boat - * @param y the y position of the boat - * @param speed the speed of the boat - * @param color the color of the wake line - * @param heading the heading of the boat - */ - private void drawWake(GraphicsContext gc, double x, double y, double speed, Color color, double heading){ - double angle = Math.toRadians(heading); - speed = speed * 2; - Point newP = new Point(0, speed); - newP.rotate(angle); - - gc.setStroke(color); - gc.setLineWidth(1.0); - gc.strokeLine(x, y, newP.x + x, newP.y + y); - } - - /** - * Draws a boat with given (x, y) position in the given color - * - * @param lat - * @param lon - * @param color - * @param name - * @param speed - */ - private void drawBoat(double lat, double lon, Color color, String name, double speed, double heading) { - // Latitude - double x = (lon - ORIGIN_LON) * SCALE; - double y = (ORIGIN_LAT - lat) * SCALE; - - gc.setFill(color); - - if (raceViewController.isDisplayAnnotations()) { - // Set boat text - gc.setFont(new Font(14)); - gc.setLineWidth(3); - gc.fillText(name + ", " + speed + " knots", x + 15, y + 15); - } -// double diameter = 9; -// gc.fillOval(x, y, diameter, diameter); - double angle = Math.toRadians(heading); - - Point p1 = new Point(0, -15); // apex point - Point p2 = new Point(7, 4); // base point - Point p3 = new Point(-7, 4); // base point - p1.rotate(angle); - p2.rotate(angle); - p3.rotate(angle); - double[] xx = new double[] {p1.x + x, p2.x + x, x, p3.x + x}; - double[] yy = new double[] {p1.y + y, p2.y + y, y, p3.y + y}; - gc.fillPolygon(xx, yy, 4); - - if (raceViewController.isDisplayAnnotations()){ - drawWake(gc, x, y, speed, color, heading); - } - } /** * Inner class for creating point so that you can rotate it around origin point. @@ -262,10 +219,11 @@ public class CanvasController { * Draws the course. */ private void drawCourse() { + fitToCanvas(); for (Mark mark : raceViewController.getRace().getCourse()) { if (mark.getMarkType() == MarkType.SINGLE_MARK) { drawSingleMark((SingleMark) mark, Color.BLACK); - } else if (mark.getMarkType() == MarkType.GATE_MARK) { + } else { drawGateMark((GateMark) mark); } } @@ -277,11 +235,8 @@ public class CanvasController { * @param singleMark */ private void drawSingleMark(SingleMark singleMark, Color color) { - double x = (singleMark.getLongitude() - ORIGIN_LON) * SCALE; - double y = (ORIGIN_LAT - singleMark.getLatitude()) * SCALE; - gc.setFill(color); - gc.fillRect(x,y,5.5,5.5); + gc.fillOval(singleMark.getX(), singleMark.getY(),MARK_SIZE,MARK_SIZE); } /** @@ -304,17 +259,181 @@ public class CanvasController { drawSingleMark(gateMark.getSingleMark2(), color); GraphicsContext gc = canvas.getGraphicsContext2D(); - + gc.save(); gc.setStroke(color); + if (gateMark.getMarkType() == MarkType.OPEN_GATE) + gc.setLineDashes(3, 5); - // Convert lat/lon to x,y - double x1 = (gateMark.getSingleMark1().getLongitude()- ORIGIN_LON) * SCALE; - double y1 = (ORIGIN_LAT - gateMark.getSingleMark1().getLatitude()) * SCALE; + gc.setLineWidth(2); + gc.strokeLine( + gateMark.getSingleMark1().getX() + MARK_SIZE / 2, + gateMark.getSingleMark1().getY() + MARK_SIZE / 2, + gateMark.getSingleMark2().getX() + MARK_SIZE / 2, + gateMark.getSingleMark2().getY() + MARK_SIZE / 2 + ); + gc.restore(); + } - double x2 = (gateMark.getSingleMark2().getLongitude() - ORIGIN_LON) * SCALE; - double y2 = (ORIGIN_LAT - gateMark.getSingleMark2().getLatitude()) * SCALE; + /** + * Calculates x and y location for every marker that fits it to the canvas the race will be drawn on. + */ + private void fitToCanvas() { + findMinMaxPoint(); + double minLonToMaxLon = scaleRaceExtremities(); + calculateReferencePointLocation(minLonToMaxLon); + givePointsXY(); + } - gc.setLineWidth(1); - gc.strokeLine(x1, y1, x2, y2); + /** + * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker with the leftmost + * marker, rightmost marker, southern most marker and northern most marker respectively. + */ + private void findMinMaxPoint() { + ArrayList sortedPoints = new ArrayList<>(); + for (Mark mark : raceViewController.getRace().getCourse()) + { + if (mark.getMarkType() == MarkType.SINGLE_MARK) + sortedPoints.add(mark); + else { + sortedPoints.add(((GateMark) mark).getSingleMark1()); + sortedPoints.add(((GateMark) mark).getSingleMark2()); + } + } + sortedPoints.sort(Comparator.comparingDouble(Mark::getLatitude)); + minLatPoint = sortedPoints.get(0); + maxLatPoint = sortedPoints.get(sortedPoints.size()-1); + + sortedPoints.sort(Comparator.comparingDouble(Mark::getLongitude)); + //If the course is on a point on the earth where longitudes wrap around. + // TODO: 30/03/17 cir27 - Correctly account for longitude wrapping around. + if (sortedPoints.get(sortedPoints.size()-1).getLongitude() - sortedPoints.get(0).getLongitude() > 180) + Collections.reverse(sortedPoints); + minLonPoint = sortedPoints.get(0); + maxLonPoint = sortedPoints.get(sortedPoints.size()-1); + } + + /** + * Calculates the location of a reference point, this is always the point with minimum latitude, in relation to the + * canvas. + * + * @param minLonToMaxLon The horizontal distance between the point of minimum longitude to maximum longitude. + */ + private void calculateReferencePointLocation (double minLonToMaxLon) { + Mark referencePoint = minLatPoint; + double referenceAngle; + double mapWidth = canvas.getWidth(); + double mapHeight = canvas.getHeight(); + + if (scaleDirection == ScaleDirection.HORIZONTAL) { + referenceAngle = Mark.calculateHeadingRad(referencePoint, minLonPoint) - (Math.PI * (3/4)); + referencePointX = LHS_BUFFER + (int) Math.round(distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint)); + + referenceAngle = Mark.calculateHeadingRad(referencePoint, maxLatPoint); + if (referenceAngle > (Math.PI / 2)) { + referenceAngle = (Math.PI * 2) - referenceAngle; + } + referencePointY = (int) Math.round(mapHeight - (TOP_BUFFER + BOT_BUFFER)); + referencePointY -= (int) Math.round(distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint)); + referencePointY = (int) Math.round(referencePointY / 2d); + referencePointY += TOP_BUFFER; + referencePointY += (int) Math.round(distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint)); + } else { + referencePointY = (int) Math.round(mapHeight - BOT_BUFFER); + + referenceAngle = (Math.PI * 2) - Mark.calculateHeadingRad(referencePoint, minLonPoint); + + referencePointX = LHS_BUFFER; + referencePointX += (int) Math.round(distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint)); + referencePointX += (int) Math.round(((mapWidth - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2); + } + referencePoint.setX(referencePointX); + referencePoint.setY(referencePointY); + System.out.println("REF POINT = " + referencePoint.getName()); + System.out.println(referencePointX); + System.out.println(referencePointY); + } + + /** + * Finds the scale factor necessary to fit all race markers within the onscreen map and assigns it to distanceScaleFactor + * Returns the max horizontal distance of the map. + */ + private double scaleRaceExtremities () { + double vertAngle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint); + if (vertAngle > Math.PI) + vertAngle = (2 * Math.PI) - vertAngle; + double vertDistance = Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint); + + double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint); + if (horiAngle <= (Math.PI / 2)) + horiAngle = (Math.PI / 2) - horiAngle; + else + horiAngle = horiAngle - (Math.PI / 2); + double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint); + + double vertScale = (canvas.getHeight() - (TOP_BUFFER + BOT_BUFFER)) / vertDistance; + + if ((horiDistance * vertScale) > (canvas.getWidth() - (RHS_BUFFER + LHS_BUFFER))) { + distanceScaleFactor = (canvas.getWidth() - (RHS_BUFFER + LHS_BUFFER)) / horiDistance; + scaleDirection = ScaleDirection.HORIZONTAL; + } else { + distanceScaleFactor = vertScale; + scaleDirection = ScaleDirection.VERTICAL; + } + return horiDistance; + } + + /** + * Give all markers in the course an x,y location relative to a given reference with a known x,y location. Distances + * are scaled according to the distanceScaleFactor variable. + */ + private void givePointsXY() { + Pair canvasLocation; + ArrayList allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); + + for (Mark mark : allPoints) { + if (mark.getMarkType() != MarkType.SINGLE_MARK) { + GateMark gateMark = (GateMark) mark; + + canvasLocation = findScaledXY(gateMark.getSingleMark1()); + gateMark.getSingleMark1().setX(canvasLocation.getKey()); + gateMark.getSingleMark1().setY(canvasLocation.getValue()); + + canvasLocation = findScaledXY(gateMark.getSingleMark2()); + gateMark.getSingleMark2().setX(canvasLocation.getKey()); + gateMark.getSingleMark2().setY(canvasLocation.getValue()); + } + if (mark.getMarkType() == MarkType.CLOSED_GATE) + ((GateMark) mark).assignXYCentered(); + else { + canvasLocation = findScaledXY(mark); + mark.setX(canvasLocation.getKey()); + mark.setY(canvasLocation.getValue()); + } + } + } + + private Pair findScaledXY (Mark unscaled) { + double distanceFromReference; + double angleFromReference; + int yAxisLocation; + int xAxisLocation; + + angleFromReference = Mark.calculateHeadingRad(minLatPoint, unscaled); + distanceFromReference = Mark.calculateDistance(minLatPoint, unscaled); + //angleFromReference = Mark.calculateHeadingRad(lon1, lon2, lat1, lat2); + //distanceFromReference = Mark.calculateDistance(lon1, lon2, lat1, lat2); + + if (angleFromReference > (Math.PI / 2)) { + angleFromReference = (Math.PI * 2) - angleFromReference; + xAxisLocation = referencePointX; + xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + } else { + xAxisLocation = referencePointX; + xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + } + yAxisLocation = referencePointY; + yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + + return new Pair<>(xAxisLocation, yAxisLocation); } } \ No newline at end of file diff --git a/src/main/java/seng302/models/mark/GateMark.java b/src/main/java/seng302/models/mark/GateMark.java index 2b152e65..2dfb9fdd 100644 --- a/src/main/java/seng302/models/mark/GateMark.java +++ b/src/main/java/seng302/models/mark/GateMark.java @@ -16,8 +16,8 @@ public class GateMark extends Mark { * @param singleMark1 one single mark inside of the gate mark * @param singleMark2 the second mark inside of the gate mark */ - public GateMark(String name, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude) { - super(name, MarkType.GATE_MARK, latitude, longitude); + public GateMark(String name, MarkType type, SingleMark singleMark1, SingleMark singleMark2, double latitude, double longitude) { + super(name, type, latitude, longitude); this.singleMark1 = singleMark1; this.singleMark2 = singleMark2; } @@ -47,4 +47,16 @@ public class GateMark extends Mark { //return (this.getSingleMark1().getLongitude() + this.getSingleMark2().getLongitude()) / 2; return (this.getSingleMark1().getLongitude()); } + + public void assignXYCentered () { + System.out.println("POSSIBLE GOOF " + xValue + " " + yValue); + System.out.println(singleMark1.getX() + " " + singleMark1.getY()); + System.out.println(singleMark2.getX() + " " + singleMark2.getY()); + double dx = singleMark2.getX() - singleMark1.getX(); + System.out.println("dx + " + dx); + double dy = singleMark2.getY() - singleMark1.getY(); + xValue = (int) Math.round(singleMark1.getX() + dx / 2); + yValue = (int) Math.round(singleMark1.getY() + dy / 2); + System.out.println("PROBABLE GAAF " + xValue + " " + yValue); + } } diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index 3e635856..44e9877b 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -10,6 +10,8 @@ public abstract class Mark { private MarkType markType; private double latitude; private double longitude; + Integer xValue; + Integer yValue; /** * Create a mark instance by passing its name and type @@ -28,6 +30,76 @@ public abstract class Mark { this.longitude = longitude; } + /** + * Calculated the heading in radians from first Mark to the second Mark. + * + * @param pointOne First Mark + * @param pointTwo Second Mark + * @return Heading in radians + */ + public static Double calculateHeadingRad(Mark pointOne, Mark pointTwo) { + Double longitude1 = pointOne.getLongitude(); + Double longitude2 = pointTwo.getLongitude(); + Double latitude1 = pointOne.getLatitude(); + Double latitude2 = pointTwo.getLatitude(); + return calculateHeadingRad(longitude1, longitude2, latitude1, latitude2); + } + + /** + * Calculate the heading in radians from geographical location with latitude1, longitude 1 to geographical + * latitude2, longitude 2 + * @param longitude1 Longitude of first point in degrees + * @param longitude2 Longitude of second point in degrees + * @param latitude1 Latitude of first point in degrees + * @param latitude2 Latitude of first point in degrees + * @return Heading in radians + */ + public static double calculateHeadingRad (Double longitude1, Double longitude2, Double latitude1, Double latitude2) { + latitude1 = Math.toRadians(latitude1); + latitude2 = Math.toRadians(latitude2); + Double longDiff= Math.toRadians(longitude2-longitude1); + Double y = Math.sin(longDiff)*Math.cos(latitude2); + Double x = Math.cos(latitude1)*Math.sin(latitude2)-Math.sin(latitude1)*Math.cos(latitude2)*Math.cos(longDiff); + return Math.atan2(y, x); + } + + /** + * Calculates the distance in meters from the first Mark to a second Mark + * + * @param pointOne First Mark + * @param pointTwo Second Mark + * @return Distance in meters + */ + public static Double calculateDistance(Mark pointOne, Mark pointTwo) { + Double longitude1 = pointOne.getLongitude(); + Double longitude2 = pointTwo.getLongitude(); + Double latitude1 = pointOne.getLatitude(); + Double latitude2 = pointTwo.getLatitude(); + return calculateDistance(longitude1, longitude2, latitude1, latitude2); + } + + /** + * Calculate the distance in meters from geographical location with latitude1, longitude 1 to geographical + * latitude2, longitude 2 + * + * @param longitude1 Longitude of first point in degrees + * @param longitude2 Longitude of second point in degrees + * @param latitude1 Latitude of first point in degrees + * @param latitude2 Latitude of first point in degrees + * @return Distance in meters + */ + public static Double calculateDistance (Double longitude1, Double longitude2, Double latitude1, Double latitude2) { + Double theta = longitude1 - longitude2; + Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) + + Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) * + Math.cos(Math.toRadians(theta)); + dist = Math.acos(dist); + dist = Math.toDegrees(dist); + dist = dist * 60 * 1.1508; //nautical mile (distance between two degrees) * (degrees in a minute) + dist = dist * 1609.344; //ratio of miles to metres + return dist; + } + public String getName() { return name; } @@ -51,4 +123,21 @@ public abstract class Mark { public double getLongitude() { return longitude; } + + public int getX () { + return xValue; + } + + public int getY () { + return yValue; + } + + public void setX (int x) { + this.xValue = x; + } + + public void setY (int y) { + this.yValue = y; + } + } diff --git a/src/main/java/seng302/models/mark/MarkType.java b/src/main/java/seng302/models/mark/MarkType.java index 3de5cba3..4ac6a9e3 100644 --- a/src/main/java/seng302/models/mark/MarkType.java +++ b/src/main/java/seng302/models/mark/MarkType.java @@ -5,5 +5,5 @@ package seng302.models.mark; * Created by Haoming Yin (hyi25) on 17/3/17. */ public enum MarkType { - SINGLE_MARK, GATE_MARK + SINGLE_MARK, OPEN_GATE, CLOSED_GATE } diff --git a/src/main/java/seng302/models/parsers/CourseParser.java b/src/main/java/seng302/models/parsers/CourseParser.java index 0ad0c471..04a40e0a 100644 --- a/src/main/java/seng302/models/parsers/CourseParser.java +++ b/src/main/java/seng302/models/parsers/CourseParser.java @@ -65,7 +65,11 @@ public class CourseParser extends FileParser { String name = element.getElementsByTagName("name").item(0).getTextContent(); SingleMark mark1 = generateSingleMark(element.getElementsByTagName("mark").item(0)); SingleMark mark2 = generateSingleMark(element.getElementsByTagName("mark").item(1)); - GateMark gateMark = new GateMark(name, mark1, mark2, mark1.getLatitude(), mark1.getLongitude()); + GateMark gateMark; + if (name.equals("Start") || name.equals("Finish")) + gateMark = new GateMark(name, MarkType.CLOSED_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude()); + else + gateMark = new GateMark(name, MarkType.OPEN_GATE, mark1, mark2, mark1.getLatitude(), mark1.getLongitude()); marks.put(name, gateMark); } } diff --git a/src/test/java/seng302/models/mark/MarkTest.java b/src/test/java/seng302/models/mark/MarkTest.java index f1943c64..b48f5818 100644 --- a/src/test/java/seng302/models/mark/MarkTest.java +++ b/src/test/java/seng302/models/mark/MarkTest.java @@ -18,7 +18,7 @@ public class MarkTest { public void setUp() throws Exception { this.singleMark1 = new SingleMark("testMark_SM1", 12.23234, -34.342); this.singleMark2 = new SingleMark("testMark_SM2", 12.23239, -34.352); - this.gateMark = new GateMark("testMark_GM", singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude()); + this.gateMark = new GateMark("testMark_GM", MarkType.OPEN_GATE, singleMark1, singleMark2, singleMark1.getLatitude(), singleMark2.getLongitude()); } @Test @@ -30,7 +30,7 @@ public class MarkTest { @Test public void getMarkType() throws Exception { assertTrue(this.singleMark2.getMarkType() == MarkType.SINGLE_MARK); - assertTrue(this.gateMark.getMarkType() == MarkType.GATE_MARK); + assertTrue(this.gateMark.getMarkType() == MarkType.OPEN_GATE); } @Test diff --git a/src/test/java/seng302/models/parsers/CourseParserTest.java b/src/test/java/seng302/models/parsers/CourseParserTest.java index 865caec6..201c1b4b 100644 --- a/src/test/java/seng302/models/parsers/CourseParserTest.java +++ b/src/test/java/seng302/models/parsers/CourseParserTest.java @@ -25,7 +25,7 @@ public class CourseParserTest { public void getGates() throws Exception { ArrayList course = cp.getCourse(); - assertTrue(MarkType.GATE_MARK == course.get(0).getMarkType()); + assertTrue(MarkType.OPEN_GATE == course.get(0).getMarkType()); GateMark gateMark1 = (GateMark) course.get(0); assertEquals(32.293771, gateMark1.getSingleMark2().getLatitude(), 0.00000001); From 50e7ece477a036d9737cb2f2e87f3d76693a0472 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Thu, 20 Apr 2017 19:17:12 +1200 Subject: [PATCH 06/97] Checking for the header of each packet as the stream parser checks for each byte to see if it matches with the desired header sequence. --- .../models/parsers/InputStreamParser.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/main/java/seng302/models/parsers/InputStreamParser.java b/src/main/java/seng302/models/parsers/InputStreamParser.java index 19005c1d..a086cf02 100644 --- a/src/main/java/seng302/models/parsers/InputStreamParser.java +++ b/src/main/java/seng302/models/parsers/InputStreamParser.java @@ -8,12 +8,16 @@ import java.net.Socket; public class InputStreamParser { - private static String currentLine; + //changed the currentline variable from sring to long in order to check it's value +// private static String currentLine; + private static long currentLine; private static BufferedReader buffer = null; private static void readLine() { try { - currentLine = buffer.readLine(); + //Rather than read strings it reads a long which is used for checking the head +// currentLine = buffer.readline(); + currentLine = buffer.read(); } catch (IOException e) { e.printStackTrace(); } @@ -42,10 +46,19 @@ public class InputStreamParser { readLine(); boolean reading = true; + long prev = 0; + long len = 0; while(reading) { - System.out.println(currentLine); +// System.out.println(currentLine); readLine(); - if (currentLine == null) { + //checking if it is the start of the packet + if(prev == 71 && currentLine == 65533) { + System.out.println("PACKET LENGTH: " + (len)); + len = 0; + } + len += 1; + prev = currentLine; + if (currentLine == -1) { reading = false; } } From 247560ee4366f77a6b551d1537fadb7e36f3ca49 Mon Sep 17 00:00:00 2001 From: Peter Date: Sat, 22 Apr 2017 16:39:03 +1200 Subject: [PATCH 07/97] converted prototype to be reading the stream byte by byte rather than by lines and characters which was very confusing and unreliable. currently extracting message type and payload length. #story[817] --- .../models/parsers/InputStreamParser.java | 85 ++++++++++++------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/src/main/java/seng302/models/parsers/InputStreamParser.java b/src/main/java/seng302/models/parsers/InputStreamParser.java index a086cf02..8dc1ab36 100644 --- a/src/main/java/seng302/models/parsers/InputStreamParser.java +++ b/src/main/java/seng302/models/parsers/InputStreamParser.java @@ -1,65 +1,69 @@ package seng302.models.parsers; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; +import java.io.InputStream; import java.net.Socket; public class InputStreamParser { - //changed the currentline variable from sring to long in order to check it's value -// private static String currentLine; - private static long currentLine; - private static BufferedReader buffer = null; + private static InputStream stream = null; + private static boolean reading = true; - private static void readLine() { + private static void skipBytes(int n){ + for (int i=0; i < n; i++){ + readByte(); + } + } + + private static int readByte() { + int currentByte = -1; try { - //Rather than read strings it reads a long which is used for checking the head -// currentLine = buffer.readline(); - currentLine = buffer.read(); + currentByte = stream.read(); + if (currentByte == -1){ + reading = false; + } } catch (IOException e) { e.printStackTrace(); } + return currentByte; } private static void runTest() { Socket host = null; - String hostAddress = "csse-s302staff.canterbury.ac.nz"; + String hostAddress = "livedata.americascup.com"; int hostPort = 4941; try { host = new Socket(hostAddress, hostPort); - } catch (IOException e) { - e.printStackTrace(); - } - - try { - //buffer = new DataInputStream(host.getInputStream()); if (host != null) { - buffer = new BufferedReader(new InputStreamReader(host.getInputStream())); + stream = host.getInputStream(); } } catch (IOException e) { e.printStackTrace(); } - readLine(); - boolean reading = true; - long prev = 0; - long len = 0; + int sync1; + int sync2; + //currently "reading" will not break the program nicely (because there are multiple readBytes within the while loop) while(reading) { -// System.out.println(currentLine); - readLine(); + sync1 = readByte(); + sync2 = readByte(); //checking if it is the start of the packet - if(prev == 71 && currentLine == 65533) { - System.out.println("PACKET LENGTH: " + (len)); - len = 0; - } - len += 1; - prev = currentLine; - if (currentLine == -1) { - reading = false; + if(sync1 == 0x47 && sync2 == 0x83) { + System.out.println("message type: " + readByte()); + skipBytes(10); + byte[] b = new byte[2]; + try { + stream.read(b); + } catch (IOException e){ + e.printStackTrace(); + } + int payloadLength = bytesToInt(b); + System.out.println("payload length: " + payloadLength); + skipBytes(payloadLength); + skipBytes(4); } } @@ -74,6 +78,23 @@ public class InputStreamParser { } + /** + * takes an array of up to 4 bytes and returns and int + * @return an int if there is less than 4 bytes -1 otherwise + */ + private static int bytesToInt(byte[] bytes){ + int partialInt = 0; + int index = 0; + for (byte b: bytes){ + if (index > 3){ + return -1; + } + partialInt = partialInt | (b & 0xFF) << (index * 8); + index++; + } + return partialInt; + } + public static void main(String[] args) { runTest(); From a649b11bbf54fea95f13bcc91a467624c5841dac Mon Sep 17 00:00:00 2001 From: kre39 Date: Sun, 23 Apr 2017 14:42:10 +1200 Subject: [PATCH 08/97] Reading relevant information (boats and race related info) from the stream so it can be moved to a parse and turned into objects for the actual race. #story[30a] --- .../models/parsers/InputStreamParser.java | 79 ++++++++++++++++++- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/src/main/java/seng302/models/parsers/InputStreamParser.java b/src/main/java/seng302/models/parsers/InputStreamParser.java index 8dc1ab36..4862a520 100644 --- a/src/main/java/seng302/models/parsers/InputStreamParser.java +++ b/src/main/java/seng302/models/parsers/InputStreamParser.java @@ -1,7 +1,9 @@ package seng302.models.parsers; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.Socket; @@ -9,6 +11,9 @@ public class InputStreamParser { private static InputStream stream = null; private static boolean reading = true; + private static BufferedReader buffer = null; + private static String currentLine; + private static boolean isWithinTag = false; private static void skipBytes(int n){ for (int i=0; i < n; i++){ @@ -16,6 +21,15 @@ public class InputStreamParser { } } + private static void readLine() { + try { + //Rather than read strings it reads a long which is used for checking the head + currentLine = buffer.readLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } + private static int readByte() { int currentByte = -1; try { @@ -29,10 +43,11 @@ public class InputStreamParser { return currentByte; } - private static void runTest() { + private static void runPacketLengthTest() { Socket host = null; - String hostAddress = "livedata.americascup.com"; + String hostAddress = "csse-s302staff.canterbury.ac.nz"; +// String hostAddress = "livedata.americascup.com"; int hostPort = 4941; try { @@ -78,6 +93,61 @@ public class InputStreamParser { } + + private static void runParserTest() { + Socket host = null; + String hostAddress = "csse-s302staff.canterbury.ac.nz"; + int hostPort = 4941; + + try { + host = new Socket(hostAddress, hostPort); + } catch (IOException e) { + e.printStackTrace(); + } + + try { + if (host != null) { + buffer = new BufferedReader(new InputStreamReader(host.getInputStream())); + } + } catch (IOException e) { + e.printStackTrace(); + } + + readLine(); + boolean reading = true; + + while(reading) { + parseLine(currentLine); + readLine(); + + if (currentLine == null) { + reading = false; + } + } + + try { + if (host != null) { + host.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private static void parseLine(String line){ + if (line.startsWith(" Date: Sun, 23 Apr 2017 14:43:18 +1200 Subject: [PATCH 09/97] Renamed file to match its functionality more accurately . #story[30a] --- .../parsers/{InputStreamParser.java => StreamReceiver.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/seng302/models/parsers/{InputStreamParser.java => StreamReceiver.java} (99%) diff --git a/src/main/java/seng302/models/parsers/InputStreamParser.java b/src/main/java/seng302/models/parsers/StreamReceiver.java similarity index 99% rename from src/main/java/seng302/models/parsers/InputStreamParser.java rename to src/main/java/seng302/models/parsers/StreamReceiver.java index 4862a520..81563a66 100644 --- a/src/main/java/seng302/models/parsers/InputStreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -7,7 +7,7 @@ import java.io.InputStreamReader; import java.net.Socket; -public class InputStreamParser { +public class StreamReceiver { private static InputStream stream = null; private static boolean reading = true; From ba352183bfc174e7d0ed6e28f2b25104c9171c91 Mon Sep 17 00:00:00 2001 From: Peter Date: Sun, 23 Apr 2017 16:53:35 +1200 Subject: [PATCH 10/97] added functionality to check the CRC for the packet. I ran into a lot of trouble with this regarding everything in java being signed by twos compliment #story[817] --- .../models/parsers/StreamReceiver.java | 133 ++++++------------ 1 file changed, 45 insertions(+), 88 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamReceiver.java b/src/main/java/seng302/models/parsers/StreamReceiver.java index 81563a66..d02fc9d1 100644 --- a/src/main/java/seng302/models/parsers/StreamReceiver.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -1,39 +1,30 @@ package seng302.models.parsers; -import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.net.Socket; +import java.util.zip.CRC32; +import java.util.zip.Checksum; -public class StreamReceiver { +public class InputStreamParser { + private static ByteArrayOutputStream buffer; private static InputStream stream = null; private static boolean reading = true; - private static BufferedReader buffer = null; - private static String currentLine; - private static boolean isWithinTag = false; - private static void skipBytes(int n){ + private static void skipBytes(long n){ for (int i=0; i < n; i++){ readByte(); } } - private static void readLine() { - try { - //Rather than read strings it reads a long which is used for checking the head - currentLine = buffer.readLine(); - } catch (IOException e) { - e.printStackTrace(); - } - } - private static int readByte() { int currentByte = -1; try { currentByte = stream.read(); + buffer.write(currentByte); if (currentByte == -1){ reading = false; } @@ -43,11 +34,10 @@ public class StreamReceiver { return currentByte; } - private static void runPacketLengthTest() { + private static void runTest() { Socket host = null; - String hostAddress = "csse-s302staff.canterbury.ac.nz"; -// String hostAddress = "livedata.americascup.com"; + String hostAddress = "livedata.americascup.com"; int hostPort = 4941; try { @@ -63,22 +53,30 @@ public class StreamReceiver { int sync2; //currently "reading" will not break the program nicely (because there are multiple readBytes within the while loop) while(reading) { + buffer = new ByteArrayOutputStream(); sync1 = readByte(); + System.out.println("sync1 = " + Integer.toBinaryString(sync1)); sync2 = readByte(); //checking if it is the start of the packet if(sync1 == 0x47 && sync2 == 0x83) { System.out.println("message type: " + readByte()); skipBytes(10); - byte[] b = new byte[2]; - try { - stream.read(b); - } catch (IOException e){ - e.printStackTrace(); - } - int payloadLength = bytesToInt(b); +// byte[] b = new byte[2]; +// try { +// stream.read(b); +// } catch (IOException e){ +// e.printStackTrace(); +// } +// System.out.println("b = " + Integer.toBinaryString(b[0])); + long payloadLength = bytesToLong(getBytes(2)); System.out.println("payload length: " + payloadLength); skipBytes(payloadLength); - skipBytes(4); + + Checksum checksum = new CRC32(); + checksum.update(buffer.toByteArray(), 0, buffer.size()); + System.out.println(checksum.getValue()); + long crc = bytesToLong(getBytes(4)); + System.out.println(crc); } } @@ -93,83 +91,42 @@ public class StreamReceiver { } - - private static void runParserTest() { - Socket host = null; - String hostAddress = "csse-s302staff.canterbury.ac.nz"; - int hostPort = 4941; - - try { - host = new Socket(hostAddress, hostPort); - } catch (IOException e) { - e.printStackTrace(); + private static byte[] getBytes(int n){ + byte[] bytes = new byte[n]; + for (int i = 0; i < n; i++){ + bytes[i] = (byte) readByte(); +// System.out.println(Integer.toBinaryString(bytes[i])); +// System.out.println(bytes[i]); } - - try { - if (host != null) { - buffer = new BufferedReader(new InputStreamReader(host.getInputStream())); - } - } catch (IOException e) { - e.printStackTrace(); - } - - readLine(); - boolean reading = true; - - while(reading) { - parseLine(currentLine); - readLine(); - - if (currentLine == null) { - reading = false; - } - } - - try { - if (host != null) { - host.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - - } - - private static void parseLine(String line){ - if (line.startsWith(" 3){ return -1; } - partialInt = partialInt | (b & 0xFF) << (index * 8); + partialLong = partialLong | (b & 0xFFL) << (index * 8); index++; } - return partialInt; + return partialLong; } + public static void main(String[] args) { - runPacketLengthTest(); - runParserTest(); + runTest(); + } - } From 3dc1a7f9c0310bb7011c9f232472bf74740d7523 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Sun, 23 Apr 2017 20:14:41 +1200 Subject: [PATCH 11/97] StreamPacket class created so that we can store all packets generically. The timestamp has also been extracted and stored with the packet so that in the future we may turn the current ArrayList into a priority que. #story[817] --- .../seng302/models/parsers/StreamPacket.java | 21 +++++++++ .../seng302/models/parsers/StreamParser.java | 44 +++++++++++++++++++ .../models/parsers/StreamReceiver.java | 41 +++++++++++------ 3 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 src/main/java/seng302/models/parsers/StreamPacket.java create mode 100644 src/main/java/seng302/models/parsers/StreamParser.java diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java new file mode 100644 index 00000000..e71e5897 --- /dev/null +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -0,0 +1,21 @@ +package seng302.models.parsers; + +/** + * Created by kre39 on 23/04/17. + */ +public class StreamPacket { + + //Change int to an ENUM for the type + private int type; + + private long messageLength; + private long timeStamp; + private byte[] payload; + + public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) { + this.type = type; + this.messageLength = messageLength; + this.timeStamp = timeStamp; + this.payload = payload; + } +} diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java new file mode 100644 index 00000000..aeba5b1b --- /dev/null +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -0,0 +1,44 @@ +package seng302.models.parsers; + + +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.ByteArrayInputStream; +import java.io.IOException; + +/** + * Created by kre39 on 23/04/17. + */ +public class StreamParser { + + private static boolean isWithinTag; + + + static void parseLine(byte[] bytes) { + //TODO overhaul all of this to treat packets as appropriate + String line = new String(bytes); + if (line.startsWith("<")){ + isWithinTag = true; + } +// System.out.println("line = ---------------------------------------------\n" + line); + if (isWithinTag) { +// try { +// Element node = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(line.getBytes())).getDocumentElement(); +// if (node.getAttributes().getNamedItem("Type") != null) { +// System.out.println(node.getAttributes().getNamedItem("Type") ); +// System.out.println(line); +// } +// } catch (Throwable e){ +//// e.printStackTrace(); +// } + } + if (line.startsWith(" priorityQue = new ArrayList<>(); private static void skipBytes(long n){ for (int i=0; i < n; i++){ @@ -37,14 +42,13 @@ public class InputStreamParser { private static void runTest() { Socket host = null; - String hostAddress = "livedata.americascup.com"; +// String hostAddress = "livedata.americascup.com"; + String hostAddress = "csse-s302staff.canterbury.ac.nz"; int hostPort = 4941; try { host = new Socket(hostAddress, hostPort); - if (host != null) { - stream = host.getInputStream(); - } + stream = host.getInputStream(); } catch (IOException e) { e.printStackTrace(); } @@ -55,12 +59,15 @@ public class InputStreamParser { while(reading) { buffer = new ByteArrayOutputStream(); sync1 = readByte(); - System.out.println("sync1 = " + Integer.toBinaryString(sync1)); +// System.out.println("sync1 = " + Integer.toBinaryString(sync1)); sync2 = readByte(); //checking if it is the start of the packet if(sync1 == 0x47 && sync2 == 0x83) { - System.out.println("message type: " + readByte()); - skipBytes(10); + int type = readByte(); +// System.out.println("message type: " + type); + byte[] timeStampBytes = getBytes(6); + skipBytes(4); + // byte[] b = new byte[2]; // try { // stream.read(b); @@ -68,15 +75,23 @@ public class InputStreamParser { // e.printStackTrace(); // } // System.out.println("b = " + Integer.toBinaryString(b[0])); +// System.out.println(timeStamp); + long timeStamp = 0; + long multiplier=1; + for(int i = 0;i < 6;i++) { + timeStamp += timeStampBytes[i]*multiplier; + multiplier *= 256; + } long payloadLength = bytesToLong(getBytes(2)); - System.out.println("payload length: " + payloadLength); - skipBytes(payloadLength); - + //No. of milliseconds since Jan 1st 1970 + System.out.println("timeStamp = " + timeStamp); +// System.out.println("payload length: " + payloadLength); + priorityQue.add(new StreamPacket(type, payloadLength, timeStamp, getBytes((int)payloadLength))); Checksum checksum = new CRC32(); checksum.update(buffer.toByteArray(), 0, buffer.size()); - System.out.println(checksum.getValue()); +// System.out.println(checksum.getValue()); long crc = bytesToLong(getBytes(4)); - System.out.println(crc); +// System.out.println(crc); } } From 403dc480c4a70338265801205d7cfe5356dfc626 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Mon, 24 Apr 2017 15:50:21 +1200 Subject: [PATCH 12/97] Created packet enum to class packets and started progress on how the packets are read and parsed according to the type of packet. #story[820] --- .../seng302/models/parsers/PacketType.java | 53 +++++++++++++++++++ .../seng302/models/parsers/StreamPacket.java | 9 +++- .../seng302/models/parsers/StreamParser.java | 24 +++++++++ .../models/parsers/StreamReceiver.java | 2 +- 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/main/java/seng302/models/parsers/PacketType.java diff --git a/src/main/java/seng302/models/parsers/PacketType.java b/src/main/java/seng302/models/parsers/PacketType.java new file mode 100644 index 00000000..66b86207 --- /dev/null +++ b/src/main/java/seng302/models/parsers/PacketType.java @@ -0,0 +1,53 @@ +package seng302.models.parsers; + +/** + * Created by Kusal on 4/24/2017. + */ +public enum PacketType { + HEARTBEAT, + RACE_STATUS, + DISPLAY_TEXT_MESSAGE, + XML_MESSAGE, + RACE_START_STATUS, + YACHT_EVENT_CODE, + YACHT_ACTION_CODE, + CHATTER_TEXT, + BOAT_LOCATION, + MARK_ROUNDING, + COURSE_WIND, + AVG_WIND, + OTHER; + + static PacketType assignPacketType(int packetType){ + switch(packetType){ + case 1: + return HEARTBEAT; + case 12: + return RACE_STATUS; + case 20: + return DISPLAY_TEXT_MESSAGE; + case 26: + return XML_MESSAGE; + case 27: + return RACE_START_STATUS; + case 29: + return YACHT_EVENT_CODE; + case 31: + return YACHT_ACTION_CODE; + case 36: + return CHATTER_TEXT; + case 37: + return BOAT_LOCATION; + case 38: + return MARK_ROUNDING; + case 44: + return COURSE_WIND; + case 47: + return AVG_WIND; + default: + } + return OTHER; + } + + +} diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index e71e5897..b1e24a68 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -6,16 +6,21 @@ package seng302.models.parsers; public class StreamPacket { //Change int to an ENUM for the type - private int type; + private PacketType type; private long messageLength; private long timeStamp; private byte[] payload; public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) { - this.type = type; + this.type = PacketType.assignPacketType(type); this.messageLength = messageLength; this.timeStamp = timeStamp; this.payload = payload; +// System.out.println("type = " + type); + if (this.type == PacketType.BOAT_LOCATION){ + System.out.println(this.type.toString()); + StreamParser.extractBoatLocation(payload); + } } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index aeba5b1b..65598693 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -8,6 +8,8 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; /** * Created by kre39 on 23/04/17. @@ -40,5 +42,27 @@ public class StreamParser { } } + static void extractBoatLocation(byte[] payload){ + byte[] latBytes = Arrays.copyOfRange(payload,16,20); + byte[] lonBytes = Arrays.copyOfRange(payload,20,24); + byte[] boatIdBytes = Arrays.copyOfRange(payload,8,12); + int boatId = ByteBuffer.wrap(boatIdBytes).getInt(); + int lat = ByteBuffer.wrap(latBytes).getInt(); + int lon = ByteBuffer.wrap(lonBytes).getInt(); +// System.out.println("boatId = " + boatId); +// System.out.println("lon = " + 180 * (lon/Math.pow(2,31))); +// System.out.println("lat = " + 180 * (lat/Math.pow(2,31))); + } + + public static int toInt(byte[] bytes, int offset) { + + int ret = 0; + for (int i=0; i<4 && i+offset Date: Mon, 24 Apr 2017 16:47:41 +1200 Subject: [PATCH 13/97] Started looking into boat location packets, am able to extract the lats an lons but needs validations. Can also see the device type, timestamp, and sequence number. Code needs to be cleaned up and will need to start looking into the set up packets, specifically the packets containing xml data so the course can be created. #story[820] --- .../seng302/models/parsers/StreamPacket.java | 1 - .../seng302/models/parsers/StreamParser.java | 50 +++++++++++++------ .../models/parsers/StreamReceiver.java | 3 +- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index b1e24a68..4afd51f5 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -19,7 +19,6 @@ public class StreamPacket { this.payload = payload; // System.out.println("type = " + type); if (this.type == PacketType.BOAT_LOCATION){ - System.out.println(this.type.toString()); StreamParser.extractBoatLocation(payload); } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 65598693..44321c9a 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -9,7 +9,10 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Map; /** * Created by kre39 on 23/04/17. @@ -17,7 +20,7 @@ import java.util.Arrays; public class StreamParser { private static boolean isWithinTag; - + public static ArrayList ids = new ArrayList<>(); static void parseLine(byte[] bytes) { //TODO overhaul all of this to treat packets as appropriate @@ -43,26 +46,41 @@ public class StreamParser { } static void extractBoatLocation(byte[] payload){ + byte deviceType = payload[15]; + byte[] seqBytes = Arrays.copyOfRange(payload,11,15); byte[] latBytes = Arrays.copyOfRange(payload,16,20); byte[] lonBytes = Arrays.copyOfRange(payload,20,24); byte[] boatIdBytes = Arrays.copyOfRange(payload,8,12); - int boatId = ByteBuffer.wrap(boatIdBytes).getInt(); - int lat = ByteBuffer.wrap(latBytes).getInt(); - int lon = ByteBuffer.wrap(lonBytes).getInt(); -// System.out.println("boatId = " + boatId); -// System.out.println("lon = " + 180 * (lon/Math.pow(2,31))); -// System.out.println("lat = " + 180 * (lat/Math.pow(2,31))); - } - - public static int toInt(byte[] bytes, int offset) { - - int ret = 0; - for (int i=0; i<4 && i+offset 3){ + if (index > 6){ return -1; } partialLong = partialLong | (b & 0xFFL) << (index * 8); @@ -138,9 +123,7 @@ public class StreamReceiver { public static void main(String[] args) { - - runTest(); - +// StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, pq); +// sr.connect(); } - } From f078c34bf9231bb36e8beaa0066bf223ad22f7fd Mon Sep 17 00:00:00 2001 From: Peter Galloway Date: Mon, 24 Apr 2017 18:29:50 +1200 Subject: [PATCH 15/97] the stream receiver can now be passed a threadsafe priorityQueue that it will add the packets to as they are received (note the priority queue passed should be initialized with a comparitor for "StreamPacket"s) #story[817] --- .../seng302/models/parsers/StreamPacket.java | 4 ++ .../seng302/models/parsers/StreamParser.java | 27 ++++++++++--- .../models/parsers/StreamReceiver.java | 39 +++++++++++-------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index 4afd51f5..1392f4d4 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -22,4 +22,8 @@ public class StreamPacket { StreamParser.extractBoatLocation(payload); } } + + public long getTimeStamp() { + return timeStamp; + } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 44321c9a..6dbe2d86 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -53,10 +53,10 @@ public class StreamParser { byte[] boatIdBytes = Arrays.copyOfRange(payload,8,12); extractTimeStamp(Arrays.copyOfRange(payload,1,7)); // int boatSeq = ByteBuffer.wrap(seqBytes).getInt(); - long seq = StreamReceiver.bytesToLong(seqBytes); - long boatId = StreamReceiver.bytesToLong(boatIdBytes); - long lat = StreamReceiver.bytesToLong(latBytes); - long lon = StreamReceiver.bytesToLong(lonBytes); + long seq = bytesToLong(seqBytes); + long boatId = bytesToLong(boatIdBytes); + long lat = bytesToLong(latBytes); + long lon = bytesToLong(lonBytes); if (!ids.contains(boatId)) { ids.add(boatId); } @@ -81,6 +81,23 @@ public class StreamParser { System.out.println("timeStamp = " + timeStamp); } - + /** + * takes an array of up to 7 bytes and returns a positive + * long constructed from the input bytes + * + * @return a positive long if there is less than 7 bytes -1 otherwise + */ + private static long bytesToLong(byte[] bytes){ + long partialLong = 0; + int index = 0; + for (byte b: bytes){ + if (index > 6){ + return -1; + } + partialLong = partialLong | (b & 0xFFL) << (index * 8); + index++; + } + return partialLong; + } } diff --git a/src/main/java/seng302/models/parsers/StreamReceiver.java b/src/main/java/seng302/models/parsers/StreamReceiver.java index bda89dea..1c44337f 100644 --- a/src/main/java/seng302/models/parsers/StreamReceiver.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -47,22 +47,20 @@ public class StreamReceiver { //checking if it is the start of the packet if(sync1 == 0x47 && sync2 == 0x83) { int type = readByte(); - System.out.println("message type: " + type); long timeStamp = bytesToLong(getBytes(6)); skipBytes(4); long payloadLength = bytesToLong(getBytes(2)); //No. of milliseconds since Jan 1st 1970 - System.out.println("timeStamp = " + timeStamp); - System.out.println("payload length: " + payloadLength); - - + byte[] payload = getBytes((int) payloadLength); Checksum checksum = new CRC32(); checksum.update(crcBuffer.toByteArray(), 0, crcBuffer.size()); -// System.out.println(checksum.getValue()); - long crc = bytesToLong(getBytes(4)); -// System.out.println(crc); - if (checksum.getValue() == crc) { - packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, getBytes((int) payloadLength))); + long computedCrc = checksum.getValue(); + long packetCrc = bytesToLong(getBytes(4)); + if (computedCrc == packetCrc) { + System.out.println("message type: " + type); + System.out.println("timeStamp = " + timeStamp); + System.out.println("payload length: " + payloadLength); + packetBuffer.add(new StreamPacket(type, payloadLength, timeStamp, payload)); } else { System.err.println("Packet has been dropped"); } @@ -74,20 +72,21 @@ public class StreamReceiver { } } - private int readByte() { + private int readByte() throws Exception { int currentByte = -1; try { currentByte = stream.read(); crcBuffer.write(currentByte); - if (currentByte == -1){ - } } catch (IOException e) { e.printStackTrace(); } + if (currentByte == -1){ + throw new Exception(); + } return currentByte; } - private byte[] getBytes(int n){ + private byte[] getBytes(int n) throws Exception{ byte[] bytes = new byte[n]; for (int i = 0; i < n; i++){ bytes[i] = (byte) readByte(); @@ -96,7 +95,7 @@ public class StreamReceiver { } - private void skipBytes(long n){ + private void skipBytes(long n) throws Exception{ for (int i=0; i < n; i++){ readByte(); } @@ -123,7 +122,13 @@ public class StreamReceiver { public static void main(String[] args) { -// StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, pq); -// sr.connect(); + PriorityBlockingQueue pq = new PriorityBlockingQueue<>(256, new Comparator() { + @Override + public int compare(StreamPacket s1, StreamPacket s2) { + return (int) (s1.getTimeStamp() - s2.getTimeStamp()); + } + }); + StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, pq); + sr.connect(); } } From 6874f288eeea5d6c3702942aec748300400c0e14 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Mon, 24 Apr 2017 21:53:42 +1200 Subject: [PATCH 16/97] Added Race Status messages to the mock streaming data interface Tags: #story[29] --- .../java/seng302/server/ServerThread.java | 46 ++++++- .../seng302/server/messages/BoatStatus.java | 25 ++++ .../server/messages/BoatSubMessage.java | 91 ++++++++++++ .../seng302/server/messages/RaceStatus.java | 26 ++++ .../server/messages/RaceStatusMessage.java | 130 ++++++++++++++++++ .../seng302/server/messages/RaceType.java | 20 +++ .../server/messages/WindDirection.java | 20 +++ 7 files changed, 354 insertions(+), 4 deletions(-) create mode 100644 src/main/java/seng302/server/messages/BoatStatus.java create mode 100644 src/main/java/seng302/server/messages/BoatSubMessage.java create mode 100644 src/main/java/seng302/server/messages/RaceStatus.java create mode 100644 src/main/java/seng302/server/messages/RaceStatusMessage.java create mode 100644 src/main/java/seng302/server/messages/RaceType.java create mode 100644 src/main/java/seng302/server/messages/WindDirection.java diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index cd4c091f..f84887c2 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -1,13 +1,12 @@ package seng302.server; -import seng302.server.messages.Heartbeat; -import seng302.server.messages.Message; -import seng302.server.messages.XMLMessage; -import seng302.server.messages.XMLMessageSubType; +import seng302.server.messages.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import java.util.Timer; import java.util.TimerTask; @@ -15,6 +14,7 @@ public class ServerThread implements Runnable{ private Thread runner; private StreamingServerSocket server; private final int HEARTBEAT_PERIOD = 5000; + private final int RACE_STATUS_PERIOD = 1000; private final int PORT_NUMBER = 8085; public ServerThread(String threadName){ @@ -45,6 +45,29 @@ public class ServerThread implements Runnable{ return null; } + /** + * @return A sample race status message + */ + public Message getTestRaceStatusMessage(){ + BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + + List boats = new ArrayList(); + boats.add(boat1); + boats.add(boat2); + boats.add(boat3); + + return new RaceStatusMessage(1, RaceStatus.PRESTART, 1000, WindDirection.EAST, + 100, 3, RaceType.MATCH_RACE, 1, boats); + } + public void run() { try{ server = new StreamingServerSocket(PORT_NUMBER); @@ -87,6 +110,21 @@ public class ServerThread implements Runnable{ } }, 0, HEARTBEAT_PERIOD); + // Timer to send the race status messages + Timer t1 = new Timer(); + t1.schedule(new TimerTask() { + @Override + public void run() { + Message statusMessage = getTestRaceStatusMessage(); + + try { + server.send(statusMessage); + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 100, RACE_STATUS_PERIOD); + } catch (IOException e) { System.err.println(e.getMessage()); } diff --git a/src/main/java/seng302/server/messages/BoatStatus.java b/src/main/java/seng302/server/messages/BoatStatus.java new file mode 100644 index 00000000..94418000 --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatStatus.java @@ -0,0 +1,25 @@ +package seng302.server.messages; + +/** + * The current status of a boat + */ +public enum BoatStatus { + UNDEFINED(0), + PRESTART(1), + RACING(2), + FINISHED(3), + DNS(4), + DNF(5), + DSQ(6), + CS(7); + + private long code; + + BoatStatus(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/BoatSubMessage.java b/src/main/java/seng302/server/messages/BoatSubMessage.java new file mode 100644 index 00000000..d403d85b --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatSubMessage.java @@ -0,0 +1,91 @@ +package seng302.server.messages; + +import java.nio.ByteBuffer; + +/** + * The status of each boat, sent within a race status message + */ +public class BoatSubMessage { + private final int MESSAGE_SIZE = 20; + + private long sourceId; + private BoatStatus boatStatus; + private long legNumber; + private long numberPenaltiesAwarded; + private long numberPenaltiesServed; + private long estimatedTimeAtNextMark; + private long estimatedTimeAtFinish; + + /** + * Boat Sub message from section 4.2 of the AC35 streaming data interface spec + * @param sourceId The source ID of the boat + * @param boatStatus The boats status + * @param legNumber The leg the boat is on (0= prestart, 1=start to first mark etc) + * @param numberPenaltiesAwarded The number of penalties awarded to the boat + * @param numberPenaltiesServed The number of penalties served to the boat + * @param estimatedTimeAtFinish The estimated time (UTC) the boat will finish the race + * @param estimatedTimeAtNextMark The estimated time (UTC) the boat will arrive at the next mark + */ + public BoatSubMessage(long sourceId, BoatStatus boatStatus, long legNumber, long numberPenaltiesAwarded, long numberPenaltiesServed, + long estimatedTimeAtFinish, long estimatedTimeAtNextMark){ + this.sourceId = sourceId; + this.boatStatus = boatStatus; + this.legNumber = legNumber; + this.numberPenaltiesAwarded = numberPenaltiesAwarded; + this.numberPenaltiesServed = numberPenaltiesServed; + this.estimatedTimeAtFinish = estimatedTimeAtFinish; + this.estimatedTimeAtNextMark = estimatedTimeAtNextMark; + } + + /** + * @return The size of this message in bytes + */ + public int getSize(){ + return MESSAGE_SIZE; + } + + /** + * @return a ByteBuffer containing this boat status message + */ + public ByteBuffer getByteBuffer(){ + ByteBuffer buff = ByteBuffer.allocate(getSize()); + int buffPos = 0; + + // Source ID, 4 bytes + buff.put(ByteBuffer.allocate(4).putInt((int) sourceId).array()); + buffPos += 4; + buff.position(buffPos); + + // Boat Status, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) boatStatus.getCode()).array()); + buffPos += 1; + buff.position(buffPos); + + // Leg number, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) legNumber).array()); + buffPos += 1; + buff.position(buffPos); + + // Number of penalties awarded, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesAwarded).array()); + buffPos += 1; + buff.position(buffPos); + + // Number of penalties served, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesServed).array()); + buffPos += 1; + buff.position(buffPos); + + // Estimated time at next mark, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtNextMark).array()); + buffPos += 6; + buff.position(buffPos); + + // Estimated time at finish, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt((int) estimatedTimeAtFinish).array()); + buffPos += 6; + buff.position(buffPos); + + return buff; + } +} diff --git a/src/main/java/seng302/server/messages/RaceStatus.java b/src/main/java/seng302/server/messages/RaceStatus.java new file mode 100644 index 00000000..7f123c2d --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStatus.java @@ -0,0 +1,26 @@ +package seng302.server.messages; + +/** + * The current status of the race + */ +public enum RaceStatus { + NOTACTIVE(0), + WARNING(1), // Between 3:00 and 1:00 before start + PREPARATORY(2), // Less than 1:00 before start + STARTED(3), + ABANDONED(6), + POSTPONED(7), + TERMINATED(8), + RACE_START_TIME_NOT_SET(9), + PRESTART(10); // More than 3:00 before start + + private int code; + + RaceStatus(int code){ + this.code = code; + } + + public int getCode(){ + return this.code; + } +} diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java new file mode 100644 index 00000000..17ab82fc --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -0,0 +1,130 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.zip.CRC32; + +public class RaceStatusMessage extends Message{ + private final MessageType MESSAGE_TYPE = MessageType.RACE_STATUS; + private final int MESSAGE_VERSION = 2; //Always set to 1 + private final int MESSAGE_BASE_SIZE = 24; + + // fields + private long currentTime; + private long raceId; + private RaceStatus raceStatus; + private long expectedStartTime; + private WindDirection raceWindDirection; + private long windSpeed; + private long numBoatsInRace; + private RaceType raceType; + private List boats; + private CRC32 crc; + + /** + * A message containing the current status of the race + * @param raceId The ID of the current race + * @param raceStatus The status of the race + * @param expectedStartTime The expected start time + * @param raceWindDirection The wind direction (north, east, south) + * @param windSpeed The wind speed in mm/sec + * @param numBoatsInRace The number of boats in the race + * @param raceType The race type (Match/fleet) + * @param sourceId The source of this message + * @param boats A list of boat status sub messages + */ + public RaceStatusMessage(long raceId, RaceStatus raceStatus, long expectedStartTime, WindDirection raceWindDirection, + long windSpeed, long numBoatsInRace, RaceType raceType, long sourceId, List boats){ + currentTime = System.currentTimeMillis() / 1000L; + this.raceId = raceId; + this.raceStatus = raceStatus; + this.expectedStartTime = expectedStartTime; + this.raceWindDirection = raceWindDirection; + this.windSpeed = windSpeed; + this.numBoatsInRace = numBoatsInRace; + this.raceType = raceType; + this.boats = boats; + crc = new CRC32(); + + setHeader(new Header(MESSAGE_TYPE, (int) sourceId, (short) getSize())); + } + + /** + * @return the size of this message in bytes + */ + @Override + public int getSize() { + return MESSAGE_BASE_SIZE + (20 * (int) numBoatsInRace); + } + + /** + * Send this message as a stream of bytes + * @param outputStream The output stream to send the message + */ + @Override + public void send(DataOutputStream outputStream) { + ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4/*CRC*/); + + buff.put(getHeader().getByteBuffer()); + buff.position(Header.getSize()); + + // Version Number, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); + buff.position(Header.getSize() + 1); + + // Current time, 2 bytes + buff.put(ByteBuffer.allocate(6).putInt((int)currentTime).array()); + buff.position(Header.getSize() + 7); + + // Race id, 4 bytes + buff.put(ByteBuffer.allocate(4).putInt((int)raceId).array()); + buff.position(Header.getSize() + 11); + + // Race status, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)raceStatus.getCode()).array()); + buff.position(Header.getSize() + 12); + + // Expected start time, 6 bytes + buff.put(ByteBuffer.allocate(6).putInt((int)expectedStartTime).array()); + buff.position(Header.getSize() + 18); + + // Wind direction, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort((short)raceWindDirection.getCode()).array()); + buff.position(Header.getSize() + 20); + + // Wind Speed, 2 bytes + buff.put(ByteBuffer.allocate(2).putShort((short)windSpeed).array()); + buff.position(Header.getSize() + 22); + + // Number of boats, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)numBoatsInRace).array()); + buff.position(Header.getSize() + 23); + + // Race Type, 1 byte + buff.put(ByteBuffer.allocate(1).put((byte)raceType.getCode()).array()); + buff.position(Header.getSize() + 24); + + int buffPosition = Header.getSize() + 24; + + for (BoatSubMessage boatSubMessage : boats){ + buff.put(boatSubMessage.getByteBuffer()); + buffPosition += boatSubMessage.getSize(); + buff.position(buffPosition); + } + + // calculate CRC + crc.update(buff.array()); + + // Add CRC to message + buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + + // Send + try { + outputStream.write(buff.array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/RaceType.java b/src/main/java/seng302/server/messages/RaceType.java new file mode 100644 index 00000000..182b5dfd --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceType.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Enum containing the types of races + * sent by the server + */ +public enum RaceType { + MATCH_RACE(1), + FLEET_RACE(2); + + private long code; + + RaceType(long code){ + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/WindDirection.java b/src/main/java/seng302/server/messages/WindDirection.java new file mode 100644 index 00000000..c0b8d767 --- /dev/null +++ b/src/main/java/seng302/server/messages/WindDirection.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Enum containing the supported wind directions + */ +public enum WindDirection { + NORTH(0x0000L), + EAST(0x4000L), + SOUTH(0x8000L); + + private long code; + + WindDirection(long code) { + this.code = code; + } + + public long getCode() { + return code; + } +} From 46037b5aea3b308bfa7a2854fbd22e9cb17207bb Mon Sep 17 00:00:00 2001 From: Calum Date: Mon, 24 Apr 2017 23:06:30 +1200 Subject: [PATCH 17/97] Refactored Boat class to better fit the MVC model by moving all GUI parts to BoatPolygon. Changed the way animation works so that it will work with a constantly updated set of lats and lons. TODO - Change Mark class to no longer store XY pixel data. TODO - Add in a timer force updates boat position if a packet has not been recieved for a while. #story30b #story30c #implement #refactor --- .../seng302/controllers/CanvasController.java | 264 ++++++++++++------ .../controllers/RaceViewController.java | 9 +- src/main/java/seng302/models/Boat.java | 134 ++------- src/main/java/seng302/models/BoatPolygon.java | 171 ++++++++++++ src/main/java/seng302/models/Colors.java | 7 +- src/main/java/seng302/models/Race.java | 1 + src/main/java/seng302/models/mark/Mark.java | 8 +- src/test/java/seng302/ColorsTest.java | 32 +-- 8 files changed, 392 insertions(+), 234 deletions(-) create mode 100644 src/main/java/seng302/models/BoatPolygon.java diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 0499eb05..03db02f1 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -3,6 +3,7 @@ package seng302.controllers; import javafx.animation.*; import javafx.beans.property.SimpleDoubleProperty; import javafx.fxml.FXML; +import javafx.geometry.Point2D; import javafx.scene.Group; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; @@ -11,6 +12,8 @@ import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.util.Pair; import seng302.models.Boat; +import seng302.models.BoatPolygon; +import seng302.models.Colors; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.MarkType; @@ -32,14 +35,17 @@ public class CanvasController { private ResizableCanvas canvas; private Group group; private GraphicsContext gc; + private List boatPolygons = new ArrayList<>(); - private final int MARK_SIZE = 10; - private final int BUFFER_SIZE = 25; - private final int CANVAS_SIZE = 1000; - private final int LHS_BUFFER = BUFFER_SIZE; - private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2; - private final int TOP_BUFFER = BUFFER_SIZE; - private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2; + private final int MARK_SIZE = 10; + private final int BUFFER_SIZE = 25; + private final int CANVAS_WIDTH = 1000; + private final int CANVAS_HEIGHT = 1000; + private final int LHS_BUFFER = BUFFER_SIZE; + private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2; + private final int TOP_BUFFER = BUFFER_SIZE; + private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2; + private final int FRAME_RATE = 60; private double distanceScaleFactor; private ScaleDirection scaleDirection; @@ -49,6 +55,9 @@ public class CanvasController { private Mark maxLonPoint; private int referencePointX; private int referencePointY; + private double metersToPixels; + + public AnimationTimer timer; private enum ScaleDirection { HORIZONTAL, @@ -67,70 +76,119 @@ public class CanvasController { canvasPane.getChildren().add(canvas); canvasPane.getChildren().add(group); // Bind canvas size to stack pane size. - canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_SIZE)); - canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_SIZE)); - group.minWidth(CANVAS_SIZE); - group.minHeight(CANVAS_SIZE); -// canvas.widthProperty().bind(canvasPane.widthProperty()); -// canvas.heightProperty().bind(canvasPane.heightProperty()); -// group.minWidth(canvas.getWidth()); -// group.minHeight(canvas.getHeight()); - - + canvas.widthProperty().bind(new SimpleDoubleProperty(CANVAS_WIDTH)); + canvas.heightProperty().bind(new SimpleDoubleProperty(CANVAS_HEIGHT)); + group.minWidth(CANVAS_WIDTH); + group.minHeight(CANVAS_HEIGHT); } - - public void setUpBoats(){ + public void initializeCanvas (){ gc = canvas.getGraphicsContext2D(); gc.save(); gc.setFill(Color.SKYBLUE); - gc.fillRect(0,0, CANVAS_SIZE,CANVAS_SIZE); + gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT); gc.restore(); drawCourse(); - for (Mark m : raceViewController.getRace().getCourse()) - { - System.out.println("MARK NAME - " + m.getName()); - System.out.println("X LOCATION - " + m.getX()); - System.out.println("Y LOCATION - " + m.getY()); - } drawBoats(); - drawFps(12); - // overriding the handle so that it can clean canvas and redraw boats and course marks - AnimationTimer timer = new AnimationTimer() { - private long lastUpdate = 0; - private long lastFpsUpdate = 0; - private int lastFpsCount = 0; - private int fpsCount = 0; - boolean done = true; +// drawFps(12); +// // overriding the handle so that it can clean canvas and redraw boats and course marks +// AnimationTimer timer = new AnimationTimer() { +// private long lastUpdate = 0; +// private long lastFpsUpdate = 0; +// private int lastFpsCount = 0; +// private int fpsCount = 0; +// boolean done = true; +// +// @Override +// public void handle(long now) { +// if (true){ //if statement for limiting refresh rate if needed +//// gc.clearRect(0, 0, canvas.getWidth(),canvas.getHeight()); +//// gc.setFill(Color.SKYBLUE); +//// gc.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); +// +// +// // If race has started, draw the boats and play the timeline +// if (raceViewController.getRace().getRaceTime() > 1) { +// raceViewController.playTimelines(); +// } +// // Race has not started, pause the timelines +// else { +// raceViewController.pauseTimelines(); +// } +// lastUpdate = now; +// fpsCount ++; +// if (now - lastFpsUpdate >= 1000000000){ +// lastFpsCount = fpsCount; +// fpsCount = 0; +// lastFpsUpdate = now; +// } +// } +// } +// }; +// timer.start(); + //try { + // Thread.sleep(10000); + //}catch (Exception e) { + // e.printStackTrace(); + //} + 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) { - if (true){ //if statement for limiting refresh rate if needed -// gc.clearRect(0, 0, canvas.getWidth(),canvas.getHeight()); -// gc.setFill(Color.SKYBLUE); -// gc.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); - - - // If race has started, draw the boats and play the timeline - if (raceViewController.getRace().getRaceTime() > 1){ - raceViewController.playTimelines(); + boolean raceFinished = true; + boolean descending; + int boatIndex = 0; + Mark nextMark; + if (countdown == 0) { + for (BoatPolygon bp : boatPolygons) { + if (currentRaceMarker[boatIndex] < marks.size()) { + if (currentRaceMarker[boatIndex] == 6) { + int debugLine = 4; + } + double xb4 = bp.getLayoutX(); + double yb4 = bp.getLayoutY(); + nextMark = marks.get(currentRaceMarker[boatIndex]); + if (nextMark.getY() > bp.getLayoutY()) + descending = true; + else + descending = false; + bp.updatePosition(1000 / 60); + if (descending && nextMark.getY() < bp.getLayoutY()) { + currentRaceMarker[boatIndex]++; + bp.setDestination( + marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() + ); + } else if (!descending && nextMark.getY() > bp.getLayoutY()) { + currentRaceMarker[boatIndex]++; + bp.setDestination( + marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() + ); + } + double xnew = bp.getLayoutX(); + double ynew = bp.getLayoutY(); + double dx = xnew - xb4; + double dy = ynew -yb4; + raceFinished = false; + boatIndex++; + } } - // Race has not started, pause the timelines - else { - raceViewController.pauseTimelines(); - } - lastUpdate = now; - fpsCount ++; - if (now - lastFpsUpdate >= 1000000000){ - lastFpsCount = fpsCount; - fpsCount = 0; - lastFpsUpdate = now; + if (raceFinished) { + System.out.println("DONZEO LADS"); + this.stop(); } + } else { + countdown--; } } }; - timer.start(); + for (Mark m : raceViewController.getRace().getCourse()) + System.out.println(m.getName()); + //timer.start(); } class ResizableCanvas extends Canvas { @@ -179,16 +237,22 @@ public class CanvasController { */ private void drawBoats() { // Map timelineInfos = raceViewController.getTimelineInfos(); - ArrayList boats = raceViewController.getStartingBoats(); - Double startingX = (double) raceViewController.getRace().getCourse().get(0).getX(); - Double startingY = (double) raceViewController.getRace().getCourse().get(0).getY(); + List boats = raceViewController.getStartingBoats(); + List marks = raceViewController.getRace().getCourse(); + Double startingX = (double) marks.get(0).getX(); + Double startingY = (double) marks.get(0).getY(); + Double firstMarkX = (double) marks.get(1).getX(); + Double firstMarkY = (double) marks.get(1).getY(); for (Boat boat : boats) { - boat.moveBoatTo(startingX, startingY); - group.getChildren().add(boat.getWake()); - group.getChildren().add(boat.getBoatObject()); - group.getChildren().add(boat.getTeamNameObject()); - group.getChildren().add(boat.getVelocityObject()); + BoatPolygon bp = new BoatPolygon(boat, Colors.getColor()); + bp.moveBoatTo(startingX, startingY); + bp.setDestination(firstMarkX, firstMarkY); + group.getChildren().add(bp.getWake()); + group.getChildren().add(bp); + group.getChildren().add(bp.getTeamNameObject()); + group.getChildren().add(bp.getVelocityObject()); + boatPolygons.add(bp); // drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); } } @@ -248,11 +312,11 @@ public class CanvasController { Color color = Color.BLUE; if (gateMark.getName().equals("Start")){ - color = Color.RED; + color = Color.GREEN; } if (gateMark.getName().equals("Finish")){ - color = Color.GREEN; + color = Color.RED; } drawSingleMark(gateMark.getSingleMark1(), color); @@ -282,14 +346,16 @@ public class CanvasController { double minLonToMaxLon = scaleRaceExtremities(); calculateReferencePointLocation(minLonToMaxLon); givePointsXY(); + findMetersToPixels(); } + /** * Sets the class variables minLatPoint, maxLatPoint, minLonPoint, maxLonPoint to the marker with the leftmost * marker, rightmost marker, southern most marker and northern most marker respectively. */ private void findMinMaxPoint() { - ArrayList sortedPoints = new ArrayList<>(); + List sortedPoints = new ArrayList<>(); for (Mark mark : raceViewController.getRace().getCourse()) { if (mark.getMarkType() == MarkType.SINGLE_MARK) @@ -348,9 +414,6 @@ public class CanvasController { } referencePoint.setX(referencePointX); referencePoint.setY(referencePointY); - System.out.println("REF POINT = " + referencePoint.getName()); - System.out.println(referencePointX); - System.out.println(referencePointY); } /** @@ -387,41 +450,44 @@ public class CanvasController { * are scaled according to the distanceScaleFactor variable. */ private void givePointsXY() { - Pair canvasLocation; - ArrayList allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); + Point2D canvasLocation; + List allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); for (Mark mark : allPoints) { if (mark.getMarkType() != MarkType.SINGLE_MARK) { GateMark gateMark = (GateMark) mark; canvasLocation = findScaledXY(gateMark.getSingleMark1()); - gateMark.getSingleMark1().setX(canvasLocation.getKey()); - gateMark.getSingleMark1().setY(canvasLocation.getValue()); + gateMark.getSingleMark1().setX((int) canvasLocation.getX()); + gateMark.getSingleMark1().setY((int) canvasLocation.getY()); canvasLocation = findScaledXY(gateMark.getSingleMark2()); - gateMark.getSingleMark2().setX(canvasLocation.getKey()); - gateMark.getSingleMark2().setY(canvasLocation.getValue()); + gateMark.getSingleMark2().setX((int) canvasLocation.getX()); + gateMark.getSingleMark2().setY((int) canvasLocation.getY()); } if (mark.getMarkType() == MarkType.CLOSED_GATE) ((GateMark) mark).assignXYCentered(); else { canvasLocation = findScaledXY(mark); - mark.setX(canvasLocation.getKey()); - mark.setY(canvasLocation.getValue()); + mark.setX((int) canvasLocation.getX()); + mark.setY((int) canvasLocation.getY()); } } } - private Pair findScaledXY (Mark unscaled) { + private Point2D findScaledXY (Mark unscaled) { + return findScaledXY (minLatPoint.getLatitude(), minLatPoint.getLongitude(), + unscaled.getLatitude(), unscaled.getLongitude()); + } + + private Point2D findScaledXY (double latA, double lonA, double latB, double lonB) { double distanceFromReference; double angleFromReference; int yAxisLocation; int xAxisLocation; - angleFromReference = Mark.calculateHeadingRad(minLatPoint, unscaled); - distanceFromReference = Mark.calculateDistance(minLatPoint, unscaled); - //angleFromReference = Mark.calculateHeadingRad(lon1, lon2, lat1, lat2); - //distanceFromReference = Mark.calculateDistance(lon1, lon2, lat1, lat2); + angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB); + distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB); if (angleFromReference > (Math.PI / 2)) { angleFromReference = (Math.PI * 2) - angleFromReference; @@ -434,6 +500,40 @@ public class CanvasController { yAxisLocation = referencePointY; yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - return new Pair<>(xAxisLocation, yAxisLocation); + return new Point2D(xAxisLocation, yAxisLocation); + } + + + + /** + * Find the number of meters per pixel. + */ + private void findMetersToPixels () { + Double angularDistance; + Double angle; + Double straightLineDistance; + if (scaleDirection == ScaleDirection.HORIZONTAL) { + angularDistance = Mark.calculateDistance(minLonPoint, maxLonPoint); + angle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint); + if (angle > Math.PI / 2) { + straightLineDistance = Math.cos(angle - Math.PI) * angularDistance; + } else { + straightLineDistance = Math.cos(angle) * angularDistance; + } + metersToPixels = (CANVAS_WIDTH - RHS_BUFFER - LHS_BUFFER) / straightLineDistance; + } else { + angularDistance = Mark.calculateDistance(minLatPoint, maxLatPoint); + angle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint); + if (angle < Math.PI / 2) { + straightLineDistance = Math.cos(angle) * angularDistance; + } else { + straightLineDistance = Math.cos(-angle + Math.PI * 2) * angularDistance; + } + metersToPixels = (CANVAS_HEIGHT - TOP_BUFFER - BOT_BUFFER) / straightLineDistance; + } + } + + private Point2D latLonToXY (double latitude, double longitude) { + return findScaledXY(minLatPoint.getLatitude(), minLatPoint.getLongitude(), latitude, longitude); } } \ No newline at end of file diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 83352357..8acfba2b 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -2,10 +2,7 @@ package seng302.controllers; import javafx.animation.Animation; import javafx.animation.KeyFrame; -import javafx.animation.KeyValue; import javafx.animation.Timeline; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; @@ -65,15 +62,15 @@ public class RaceViewController { // } includedCanvasController.setup(this); - includedCanvasController.setUpBoats(); - initializeTimer(); + includedCanvasController.initializeCanvas(); + //initializeTimer(); initializeSettings(); //set wind direction!!!!!!! can't find another place to put my code --haoming double windDirection = new ConfigParser("/config/config.xml").getWindDirection(); windDirectionText.setText(String.format("%.1f°", windDirection)); windArrowText.setRotate(windDirection); - + includedCanvasController.timer.start(); } private void initializeSettings(){ diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java index f39f9e3b..004ddc57 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -5,42 +5,26 @@ import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; +import javafx.util.Pair; /** * Represents a boat in the race. */ public class Boat { - private static final double TEAMNAME_X_OFFSET = 15d; - private static final double TEAMNAME_Y_OFFSET = -20d; - private static final double VELOCITY_X_OFFSET = 15d; - private static final double VELOCITY_Y_OFFSET = -10d; - private static final double VELOCITY_WAKE_RATIO = 2d; //Ratio for deciding how long the wake will be wrt velocity - private static final double BOAT_HEIGHT = 15d; - private static final double BOAT_WIDTH = 10d; - - private String teamName; // The name of the team, this is also the name of the boat - private double velocity; // In meters/second - private double lat; // Boats position - private double lon; // - - private double distanceToNextMark; - private Color color; - private int markLastPast; + private String teamName; + private double velocity; + private double lat; + private double lon; private double heading; + private int markLastPast; private String shortName; - //Graphical - private Polygon boatObject; - private Polygon wake; - private Text teamNameObject; - private Text velocityObject; - public Boat(String teamName) { this.teamName = teamName; this.velocity = 10; // Default velocity this.lat = 0.0; this.lon = 0.0; - this.distanceToNextMark = 0.0; this.shortName = ""; } @@ -54,16 +38,7 @@ public class Boat { public Boat(String teamName, double boatVelocity, String shortName) { this.teamName = teamName; this.velocity = boatVelocity; - this.distanceToNextMark = 0.0; - this.color = Colors.getColor(); this.shortName = shortName; - this.boatObject = new Polygon(); - this.boatObject.getPoints().addAll(BOAT_WIDTH /2,0.0, - BOAT_WIDTH, BOAT_HEIGHT, - 0.0, BOAT_HEIGHT); - createWake(); - this.teamNameObject = new Text(shortName); - this.velocityObject = new Text(Double.toString(boatVelocity) + "ms"); } /** @@ -113,8 +88,9 @@ public class Boat { this.lon = lon; } - public void setDistanceToNextMark(double distance){ - this.distanceToNextMark = distance; + public Pair getLocation () + { + return new Pair<>(this.lat, this.lon); } public double getLatitude(){ @@ -125,8 +101,12 @@ public class Boat { return this.lon; } - public Color getColor() { - return color; + public void setLatitude (double latitude) { + this.lat = latitude; + } + + public void setlongitude (double longitude) { + this.lon =longitude; } public double getSpeedInKnots(){ @@ -141,92 +121,16 @@ public class Boat { return markLastPast; } - public void setHeading(double heading){ - boatObject.getTransforms().clear(); - wake.getTransforms().clear(); - wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); - wake.getTransforms().add(new Rotate(heading, BOAT_WIDTH/2, -BOAT_HEIGHT)); - boatObject.getTransforms().add(new Rotate(heading, BOAT_WIDTH/2, 0)); - this.heading = heading; - } - public double getHeading(){ return this.heading; } + public void setHeading(double heading) { + this.heading = heading; + } + public String getShortName(){ return this.shortName; } - - /** - * Moves the boat and its children annotations from its current coordinates by specified amounts. - * @param x The amount to move the X coordinate by - * @param y The amount to move the Y coordinate by - */ - void moveBoatBy(Double x, Double y) { - boatObject.setLayoutX(boatObject.getLayoutX() + x); - boatObject.setLayoutY(boatObject.getLayoutY() + y); - boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); - - teamNameObject.setX(teamNameObject.getX() + x); - teamNameObject.setY(teamNameObject.getY() + y); - teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); - - velocityObject.setX(velocityObject.getX() + x); - velocityObject.setY(velocityObject.getY() + y); - velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - - - wake.setLayoutX(wake.getLayoutX() + x); - wake.setLayoutY(wake.getLayoutY() + y); - wake.relocate(wake.getLayoutX(), wake.getLayoutY()); - } - - /** - * 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 moveBoatTo(Double x, Double y) { - boatObject.setLayoutX(x); - boatObject.setLayoutY(y); - boatObject.relocate(boatObject.getLayoutX(), boatObject.getLayoutY()); - - teamNameObject.setX(x + TEAMNAME_X_OFFSET); - teamNameObject.setY(y + TEAMNAME_Y_OFFSET); - teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); - - velocityObject.setX(x + VELOCITY_X_OFFSET); - velocityObject.setY(y + VELOCITY_Y_OFFSET); - velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - - wake.setLayoutX(x); - wake.setLayoutY(y); - wake.relocate(wake.getLayoutX(), wake.getLayoutY()); - } - - private void createWake(){ - wake = new Polygon(); - wake.setFill(Color.LIGHTSKYBLUE); - wake.getPoints().addAll(5.0,0.0, - 10.0, velocity * VELOCITY_WAKE_RATIO, - 0.0, velocity * VELOCITY_WAKE_RATIO); - } - - public Polygon getWake() { - return wake; - } - - public Polygon getBoatObject() { - return boatObject; - } - - public Text getTeamNameObject() { - return teamNameObject; - } - - public Text getVelocityObject() { - return velocityObject; - } } \ No newline at end of file diff --git a/src/main/java/seng302/models/BoatPolygon.java b/src/main/java/seng302/models/BoatPolygon.java new file mode 100644 index 00000000..f2e8ea43 --- /dev/null +++ b/src/main/java/seng302/models/BoatPolygon.java @@ -0,0 +1,171 @@ +package seng302.models; + + +import javafx.scene.paint.Color; +import javafx.scene.shape.Polygon; +import javafx.scene.text.Text; +import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; + +/** + * Created by cir27 on 24/04/17. + */ +public class BoatPolygon extends Polygon { + + private static final double TEAMNAME_X_OFFSET = 15d; + private static final double TEAMNAME_Y_OFFSET = -20d; + private static final double VELOCITY_X_OFFSET = 15d; + private static final double VELOCITY_Y_OFFSET = -10d; + private static final double VELOCITY_WAKE_RATIO = 2d; + private static final double BOAT_HEIGHT = 15d; + private static final double BOAT_WIDTH = 10d; + //Time between sections of race - Should be changed to 200 for actual program. + private static double expectedUpdateInterval = 5000; + + private Boat boat; + private Polygon wake; + private Text teamNameObject; + private Text velocityObject; + + private double rotation; + private double pixelVelocityX; + private double pixelVelocityY; + //private double destinationX; + //private double destinationY; + + public BoatPolygon (Boat boat, Color color){ + super(); + super.setFill(color); + super.getPoints().addAll( + BOAT_WIDTH / 2, 0.0, + BOAT_WIDTH , BOAT_HEIGHT, + 0.0 , BOAT_HEIGHT + ); + this.boat = boat; + initAnnotations(); + } + + public BoatPolygon (Boat boat, Color color, double... points) + { + super(points); + super.setFill(color); + this.boat = boat; + initAnnotations(); + } + + private void initAnnotations () + { + wake = new Polygon(); + wake.setFill(Color.DARKBLUE); + wake.getPoints().addAll( + 5.0,0.0, + 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, + 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO + ); + teamNameObject = new Text(boat.getShortName()); + velocityObject = new Text(String.valueOf(boat.getVelocity())); + } + /** + * Moves the boat and its children annotations from its current coordinates by specified amounts. + * @param dx The amount to move the X coordinate by + * @param dy The amount to move the Y coordinate by + */ + void moveBy(Double dx, Double dy) { + super.setLayoutX(super.getLayoutX() + dx); + super.setLayoutY(super.getLayoutY() + dy); + super.relocate(super.getLayoutX(), super.getLayoutY()); + + teamNameObject.setX(teamNameObject.getX() + dx); + teamNameObject.setY(teamNameObject.getY() + dy); + teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); + + velocityObject.setX(velocityObject.getX() + dx); + velocityObject.setY(velocityObject.getY() + dy); + velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + + wake.setLayoutX(wake.getLayoutX() + dx); + wake.setLayoutY(wake.getLayoutY() + dy); + wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + } + + /** + * 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 moveBoatTo(Double x, Double y) { + super.setLayoutX(x); + super.setLayoutY(y); + super.relocate(super.getLayoutX(), super.getLayoutY()); + + teamNameObject.setX(x + TEAMNAME_X_OFFSET); + teamNameObject.setY(y + TEAMNAME_Y_OFFSET); + teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); + + velocityObject.setX(x + VELOCITY_X_OFFSET); + velocityObject.setY(y + VELOCITY_Y_OFFSET); + velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + + wake.setLayoutX(x); + wake.setLayoutY(y); + wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + } + + public void updatePosition (double timeInterval) { + double dx = pixelVelocityX * timeInterval; + double dy = pixelVelocityY * timeInterval; + moveBy(dx, dy); + } + + public void setDestination (double newXValue, double newYValue) { + this.pixelVelocityX = (newXValue - super.getLayoutX()) / expectedUpdateInterval; + this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; + //this.destinationX = newXValue; + //this.destinationY = newYValue; + this.rotation = Math.abs( + Math.toDegrees( + Math.atan( + (newYValue - super.getLayoutY()) / (newXValue - super.getLayoutX()) + ) + ) + ); + if (super.getLayoutY() >= newYValue && super.getLayoutX() <= newXValue) + rotation = 90 - rotation; + else if (super.getLayoutY() < newYValue && super.getLayoutX() <= newXValue) + rotation = 90 + rotation; + else if (super.getLayoutY() >= newYValue && super.getLayoutX() > newXValue) + rotation = 270 + rotation; + else + rotation = 270 - rotation; + rotateBoat (); + } + + private void rotateBoat () { + super.getTransforms().clear(); + super.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, 0)); + wake.getTransforms().clear(); + wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); + wake.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); + } + + public static double getExpectedUpdateInterval() { + return expectedUpdateInterval; + } + + public static void setExpectedUpdateInterval(double expectedUpdateInterval) { + BoatPolygon.expectedUpdateInterval = expectedUpdateInterval; + } + + public Polygon getWake() { + return wake; + } + + public Text getTeamNameObject() { + return teamNameObject; + } + + public Text getVelocityObject() { + return velocityObject; + } + +} diff --git a/src/main/java/seng302/models/Colors.java b/src/main/java/seng302/models/Colors.java index 419753dc..23ef8f4e 100644 --- a/src/main/java/seng302/models/Colors.java +++ b/src/main/java/seng302/models/Colors.java @@ -11,10 +11,9 @@ public enum Colors { static Integer index = 0; public static Color getColor() { - index++; - if (index > 6) { - index = 1; + if (index == 6) { + index = 0; } - return Color.valueOf(values()[index-1].toString()); + return Color.valueOf(values()[index++].toString()); } } diff --git a/src/main/java/seng302/models/Race.java b/src/main/java/seng302/models/Race.java index 165d9468..01d1adf8 100644 --- a/src/main/java/seng302/models/Race.java +++ b/src/main/java/seng302/models/Race.java @@ -9,6 +9,7 @@ import java.util.*; * Created by mra106 on 8/3/2017. */ public class Race { + private ArrayList boats; // The boats in the race private ArrayList finishingOrder; // The order in which the boats finish the race private HashMap events = new HashMap<>(); // The events that occur in the race diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index 44e9877b..98af4057 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -42,7 +42,7 @@ public abstract class Mark { Double longitude2 = pointTwo.getLongitude(); Double latitude1 = pointOne.getLatitude(); Double latitude2 = pointTwo.getLatitude(); - return calculateHeadingRad(longitude1, longitude2, latitude1, latitude2); + return calculateHeadingRad(latitude1, longitude1, latitude2, longitude2); } /** @@ -54,7 +54,7 @@ public abstract class Mark { * @param latitude2 Latitude of first point in degrees * @return Heading in radians */ - public static double calculateHeadingRad (Double longitude1, Double longitude2, Double latitude1, Double latitude2) { + public static double calculateHeadingRad (Double latitude1, Double longitude1, Double latitude2, Double longitude2) { latitude1 = Math.toRadians(latitude1); latitude2 = Math.toRadians(latitude2); Double longDiff= Math.toRadians(longitude2-longitude1); @@ -75,7 +75,7 @@ public abstract class Mark { Double longitude2 = pointTwo.getLongitude(); Double latitude1 = pointOne.getLatitude(); Double latitude2 = pointTwo.getLatitude(); - return calculateDistance(longitude1, longitude2, latitude1, latitude2); + return calculateDistance(latitude1, longitude1, latitude2, longitude2); } /** @@ -88,7 +88,7 @@ public abstract class Mark { * @param latitude2 Latitude of first point in degrees * @return Distance in meters */ - public static Double calculateDistance (Double longitude1, Double longitude2, Double latitude1, Double latitude2) { + public static Double calculateDistance (Double latitude1, Double longitude1, Double latitude2, Double longitude2) { Double theta = longitude1 - longitude2; Double dist = Math.sin(Math.toRadians(latitude1)) * Math.sin(Math.toRadians(latitude2)) + Math.cos(Math.toRadians(latitude1)) * Math.cos(Math.toRadians(latitude2)) * diff --git a/src/test/java/seng302/ColorsTest.java b/src/test/java/seng302/ColorsTest.java index a6681b26..84fc01dd 100644 --- a/src/test/java/seng302/ColorsTest.java +++ b/src/test/java/seng302/ColorsTest.java @@ -1,8 +1,11 @@ package seng302; import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; +import org.junit.Assert; import org.junit.Test; import seng302.models.Boat; +import seng302.models.BoatPolygon; import seng302.models.Colors; import java.util.ArrayList; @@ -16,30 +19,13 @@ import static org.junit.Assert.assertEquals; * Created by ryan_ on 16/03/2017. */ public class ColorsTest { - //@Test + + @Test public void testNextColor() { - List boats = new ArrayList<>(); - boats.add(new Boat("Team 1")); - boats.add(new Boat("Team 2")); - boats.add(new Boat("Team 3")); - boats.add(new Boat("Team 4")); - boats.add(new Boat("Team 5")); - boats.add(new Boat("Team 6")); - - int count = 0; - List enumColors = new ArrayList<>(); - while (count < 6) { - Color color = Colors.getColor(); - enumColors.add(color); - count++; + Color expectedColors[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.PURPLE}; + for (int i = 0; i<6; i++) + { + Assert.assertEquals(expectedColors[i], Colors.getColor()); } - - List boatColors = new ArrayList<>(); - for (Boat boat : boats) { - Color color = boat.getColor(); - boatColors.add(color); - } - - assertEquals(enumColors, boatColors); } } From ef874b42455138c944e1d786d324e5e72a36bb0d Mon Sep 17 00:00:00 2001 From: cir27 Date: Tue, 25 Apr 2017 03:12:30 +1200 Subject: [PATCH 18/97] Added a transition time to rotational movement. The aim is to make animations smoother when the boat turns. Unsure if current implementation will look good without testing on a datastream. #story30c --- .../seng302/controllers/CanvasController.java | 3 +- src/main/java/seng302/models/BoatPolygon.java | 48 ++++++++++++++----- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 03db02f1..01623b5b 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -246,8 +246,9 @@ public class CanvasController { for (Boat boat : boats) { BoatPolygon bp = new BoatPolygon(boat, Colors.getColor()); - bp.moveBoatTo(startingX, startingY); + bp.moveBoatTo(startingX, startingY, 0d); bp.setDestination(firstMarkX, firstMarkY); + bp.forceRotation(); group.getChildren().add(bp.getWake()); group.getChildren().add(bp); group.getChildren().add(bp.getTeamNameObject()); diff --git a/src/main/java/seng302/models/BoatPolygon.java b/src/main/java/seng302/models/BoatPolygon.java index f2e8ea43..aba75ed5 100644 --- a/src/main/java/seng302/models/BoatPolygon.java +++ b/src/main/java/seng302/models/BoatPolygon.java @@ -1,6 +1,7 @@ package seng302.models; +import com.sun.xml.internal.bind.v2.TODO; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; @@ -27,7 +28,9 @@ public class BoatPolygon extends Polygon { private Text teamNameObject; private Text velocityObject; - private double rotation; + private double rotationalGoal; + private double currentRotation; + private double rotationalVelocity; private double pixelVelocityX; private double pixelVelocityY; //private double destinationX; @@ -70,7 +73,7 @@ public class BoatPolygon extends Polygon { * @param dx The amount to move the X coordinate by * @param dy The amount to move the Y coordinate by */ - void moveBy(Double dx, Double dy) { + void moveBy(Double dx, Double dy, Double rotation) { super.setLayoutX(super.getLayoutX() + dx); super.setLayoutY(super.getLayoutY() + dy); super.relocate(super.getLayoutX(), super.getLayoutY()); @@ -86,6 +89,7 @@ public class BoatPolygon extends Polygon { wake.setLayoutX(wake.getLayoutX() + dx); wake.setLayoutY(wake.getLayoutY() + dy); wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + rotateBoat(rotation); } /** @@ -93,7 +97,7 @@ public class BoatPolygon extends Polygon { * @param x The X coordinate to move the boat to * @param y The Y coordinate to move the boat to */ - public void moveBoatTo(Double x, Double y) { + public void moveBoatTo(Double x, Double y, Double rotation) { super.setLayoutX(x); super.setLayoutY(y); super.relocate(super.getLayoutX(), super.getLayoutY()); @@ -109,12 +113,20 @@ public class BoatPolygon extends Polygon { wake.setLayoutX(x); wake.setLayoutY(y); wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + currentRotation = 0; + rotateBoat(rotation); } public void updatePosition (double timeInterval) { double dx = pixelVelocityX * timeInterval; double dy = pixelVelocityY * timeInterval; - moveBy(dx, dy); + double rotation = 0d; + if (rotationalGoal > currentRotation && rotationalVelocity > 0) { + rotation = rotationalVelocity * timeInterval; + } else if (rotationalGoal < currentRotation && rotationalVelocity < 0) { + rotation = rotationalVelocity * timeInterval; + } + moveBy(dx, dy, rotation); } public void setDestination (double newXValue, double newYValue) { @@ -122,7 +134,7 @@ public class BoatPolygon extends Polygon { this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; //this.destinationX = newXValue; //this.destinationY = newYValue; - this.rotation = Math.abs( + this.rotationalGoal = Math.abs( Math.toDegrees( Math.atan( (newYValue - super.getLayoutY()) / (newXValue - super.getLayoutX()) @@ -130,22 +142,29 @@ public class BoatPolygon extends Polygon { ) ); if (super.getLayoutY() >= newYValue && super.getLayoutX() <= newXValue) - rotation = 90 - rotation; + rotationalGoal = 90 - rotationalGoal; else if (super.getLayoutY() < newYValue && super.getLayoutX() <= newXValue) - rotation = 90 + rotation; + rotationalGoal = 90 + rotationalGoal; else if (super.getLayoutY() >= newYValue && super.getLayoutX() > newXValue) - rotation = 270 + rotation; + rotationalGoal = 270 + rotationalGoal; else - rotation = 270 - rotation; - rotateBoat (); + rotationalGoal = 270 - rotationalGoal; + // TODO: 25/04/2017 cir27 - Verify this logic is correct. Want to produce the shortest path. + if (Math.abs(360 - rotationalGoal + currentRotation) < Math.abs(rotationalGoal - currentRotation)) { + System.out.println("ROTATE"); + this.rotationalVelocity = (360 - rotationalGoal + currentRotation) / expectedUpdateInterval; + } else { + this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; + } } - private void rotateBoat () { + public void rotateBoat (double rotationDeg) { + currentRotation += rotationDeg; super.getTransforms().clear(); - super.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, 0)); + super.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, 0)); wake.getTransforms().clear(); wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); - wake.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); + wake.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); } public static double getExpectedUpdateInterval() { @@ -168,4 +187,7 @@ public class BoatPolygon extends Polygon { return velocityObject; } + public void forceRotation () { + rotateBoat (rotationalGoal - currentRotation); + } } From 42569e6ad749c8bcc32097ed630ec2b4ac3049db Mon Sep 17 00:00:00 2001 From: cir27 Date: Tue, 25 Apr 2017 04:30:44 +1200 Subject: [PATCH 19/97] Changed BoatPolygon is now a group instead of a polygon and is called BoatGroup. BoatPolygon's functionality was more maintainable and scalable by having it extend Group. #story30c --- .../seng302/controllers/CanvasController.java | 59 ++++---- .../{BoatPolygon.java => BoatGroup.java} | 130 ++++++++---------- src/test/java/seng302/ColorsTest.java | 7 - 3 files changed, 90 insertions(+), 106 deletions(-) rename src/main/java/seng302/models/{BoatPolygon.java => BoatGroup.java} (66%) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 01623b5b..a679e52f 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -12,7 +12,7 @@ import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.util.Pair; import seng302.models.Boat; -import seng302.models.BoatPolygon; +import seng302.models.BoatGroup; import seng302.models.Colors; import seng302.models.mark.GateMark; import seng302.models.mark.Mark; @@ -35,7 +35,7 @@ public class CanvasController { private ResizableCanvas canvas; private Group group; private GraphicsContext gc; - private List boatPolygons = new ArrayList<>(); + private List boatGroups = new ArrayList<>(); private final int MARK_SIZE = 10; private final int BUFFER_SIZE = 25; @@ -142,35 +142,47 @@ public class CanvasController { public void handle(long now) { boolean raceFinished = true; boolean descending; + boolean leftToRight; int boatIndex = 0; Mark nextMark; if (countdown == 0) { - for (BoatPolygon bp : boatPolygons) { + for (BoatGroup boatGroup : boatGroups) { if (currentRaceMarker[boatIndex] < marks.size()) { if (currentRaceMarker[boatIndex] == 6) { int debugLine = 4; } - double xb4 = bp.getLayoutX(); - double yb4 = bp.getLayoutY(); + double xb4 = boatGroup.getLayoutX(); + double yb4 = boatGroup.getLayoutY(); nextMark = marks.get(currentRaceMarker[boatIndex]); - if (nextMark.getY() > bp.getLayoutY()) - descending = true; - else - descending = false; - bp.updatePosition(1000 / 60); - if (descending && nextMark.getY() < bp.getLayoutY()) { + + descending = nextMark.getY() > boatGroup.getLayoutY(); + leftToRight = nextMark.getX() < boatGroup.getLayoutX(); + + boatGroup.updatePosition(1000 / 60); + if (descending && nextMark.getY() < boatGroup.getLayoutY()) { currentRaceMarker[boatIndex]++; - bp.setDestination( + boatGroup.setDestination( marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() ); - } else if (!descending && nextMark.getY() > bp.getLayoutY()) { + } else if (!descending && nextMark.getY() > boatGroup.getLayoutY()) { currentRaceMarker[boatIndex]++; - bp.setDestination( + boatGroup.setDestination( + marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() + ); + } else if (leftToRight && nextMark.getX() > boatGroup.getLayoutX()) { + currentRaceMarker[boatIndex]++; + boatGroup.setDestination( + marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() + ); + } else if (!leftToRight && nextMark.getX() < boatGroup.getLayoutX()) { + currentRaceMarker[boatIndex]++; + boatGroup.setDestination( marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() ); } - double xnew = bp.getLayoutX(); - double ynew = bp.getLayoutY(); + + double xnew = boatGroup.getLayoutX(); + double ynew = boatGroup.getLayoutY(); double dx = xnew - xb4; double dy = ynew -yb4; raceFinished = false; @@ -245,15 +257,12 @@ public class CanvasController { Double firstMarkY = (double) marks.get(1).getY(); for (Boat boat : boats) { - BoatPolygon bp = new BoatPolygon(boat, Colors.getColor()); - bp.moveBoatTo(startingX, startingY, 0d); - bp.setDestination(firstMarkX, firstMarkY); - bp.forceRotation(); - group.getChildren().add(bp.getWake()); - group.getChildren().add(bp); - group.getChildren().add(bp.getTeamNameObject()); - group.getChildren().add(bp.getVelocityObject()); - boatPolygons.add(bp); + BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); + boatGroup.moveBoatTo(startingX, startingY, 0d); + boatGroup.setDestination(firstMarkX, firstMarkY); + boatGroup.forceRotation(); + group.getChildren().add(boatGroup); + boatGroups.add(boatGroup); // drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); } } diff --git a/src/main/java/seng302/models/BoatPolygon.java b/src/main/java/seng302/models/BoatGroup.java similarity index 66% rename from src/main/java/seng302/models/BoatPolygon.java rename to src/main/java/seng302/models/BoatGroup.java index aba75ed5..06ea1306 100644 --- a/src/main/java/seng302/models/BoatPolygon.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -1,7 +1,7 @@ package seng302.models; - -import com.sun.xml.internal.bind.v2.TODO; +import javafx.scene.Group; +import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; @@ -9,10 +9,9 @@ import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; /** - * Created by cir27 on 24/04/17. + * Created by CJIRWIN on 25/04/2017. */ -public class BoatPolygon extends Polygon { - +public class BoatGroup extends Group{ private static final double TEAMNAME_X_OFFSET = 15d; private static final double TEAMNAME_Y_OFFSET = -20d; private static final double VELOCITY_X_OFFSET = 15d; @@ -21,52 +20,66 @@ public class BoatPolygon extends Polygon { private static final double BOAT_HEIGHT = 15d; private static final double BOAT_WIDTH = 10d; //Time between sections of race - Should be changed to 200 for actual program. - private static double expectedUpdateInterval = 5000; + private static double expectedUpdateInterval = 2000; private Boat boat; - private Polygon wake; - private Text teamNameObject; - private Text velocityObject; private double rotationalGoal; private double currentRotation; private double rotationalVelocity; private double pixelVelocityX; private double pixelVelocityY; - //private double destinationX; - //private double destinationY; - public BoatPolygon (Boat boat, Color color){ + public BoatGroup (Boat boat, Color color){ super(); - super.setFill(color); - super.getPoints().addAll( - BOAT_WIDTH / 2, 0.0, - BOAT_WIDTH , BOAT_HEIGHT, - 0.0 , BOAT_HEIGHT - ); this.boat = boat; - initAnnotations(); + initChildren(color); } - public BoatPolygon (Boat boat, Color color, double... points) + public BoatGroup (Boat boat, Color color, double... points) { - super(points); - super.setFill(color); - this.boat = boat; - initAnnotations(); + super(); + initChildren(color, points); } - private void initAnnotations () - { - wake = new Polygon(); - wake.setFill(Color.DARKBLUE); - wake.getPoints().addAll( + private void initChildren (Color color, double... points) { + Polygon boatPoly = new Polygon(points); + boatPoly.setFill(color); + + Polygon wake = new Polygon( 5.0,0.0, 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO ); - teamNameObject = new Text(boat.getShortName()); - velocityObject = new Text(String.valueOf(boat.getVelocity())); + wake.setFill(Color.DARKBLUE); + + Text teamNameObject = new Text(boat.getShortName()); + Text velocityObject = new Text(String.valueOf(boat.getVelocity())); + + boatPoly.setLayoutX(0); + boatPoly.setLayoutY(0); + boatPoly.relocate(boatPoly.getLayoutX(), boatPoly.getLayoutY()); + + teamNameObject.setX(TEAMNAME_X_OFFSET); + teamNameObject.setY(TEAMNAME_Y_OFFSET); + teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); + + velocityObject.setX(VELOCITY_X_OFFSET); + velocityObject.setY(VELOCITY_Y_OFFSET); + velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + + wake.setLayoutX(0); + wake.setLayoutY(0); + wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + + super.getChildren().addAll(boatPoly, wake, teamNameObject, velocityObject); + } + + private void initChildren (Color color) { + initChildren(color, + BOAT_WIDTH / 2, 0.0, + BOAT_WIDTH, BOAT_HEIGHT, + 0.0, BOAT_HEIGHT); } /** * Moves the boat and its children annotations from its current coordinates by specified amounts. @@ -76,19 +89,6 @@ public class BoatPolygon extends Polygon { void moveBy(Double dx, Double dy, Double rotation) { super.setLayoutX(super.getLayoutX() + dx); super.setLayoutY(super.getLayoutY() + dy); - super.relocate(super.getLayoutX(), super.getLayoutY()); - - teamNameObject.setX(teamNameObject.getX() + dx); - teamNameObject.setY(teamNameObject.getY() + dy); - teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); - - velocityObject.setX(velocityObject.getX() + dx); - velocityObject.setY(velocityObject.getY() + dy); - velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - - wake.setLayoutX(wake.getLayoutX() + dx); - wake.setLayoutY(wake.getLayoutY() + dy); - wake.relocate(wake.getLayoutX(), wake.getLayoutY()); rotateBoat(rotation); } @@ -98,21 +98,7 @@ public class BoatPolygon extends Polygon { * @param y The Y coordinate to move the boat to */ public void moveBoatTo(Double x, Double y, Double rotation) { - super.setLayoutX(x); - super.setLayoutY(y); - super.relocate(super.getLayoutX(), super.getLayoutY()); - - teamNameObject.setX(x + TEAMNAME_X_OFFSET); - teamNameObject.setY(y + TEAMNAME_Y_OFFSET); - teamNameObject.relocate(teamNameObject.getX(), teamNameObject.getY()); - - velocityObject.setX(x + VELOCITY_X_OFFSET); - velocityObject.setY(y + VELOCITY_Y_OFFSET); - velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - - wake.setLayoutX(x); - wake.setLayoutY(y); - wake.relocate(wake.getLayoutX(), wake.getLayoutY()); + super.relocate(x, y); currentRotation = 0; rotateBoat(rotation); } @@ -160,8 +146,10 @@ public class BoatPolygon extends Polygon { public void rotateBoat (double rotationDeg) { currentRotation += rotationDeg; - super.getTransforms().clear(); - super.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, 0)); + Node boatPoly = super.getChildren().get(0); + boatPoly.getTransforms().clear(); + boatPoly.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, 0)); + Node wake = super.getChildren().get(1); wake.getTransforms().clear(); wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); wake.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); @@ -172,22 +160,16 @@ public class BoatPolygon extends Polygon { } public static void setExpectedUpdateInterval(double expectedUpdateInterval) { - BoatPolygon.expectedUpdateInterval = expectedUpdateInterval; - } - - public Polygon getWake() { - return wake; - } - - public Text getTeamNameObject() { - return teamNameObject; - } - - public Text getVelocityObject() { - return velocityObject; + BoatGroup.expectedUpdateInterval = expectedUpdateInterval; } public void forceRotation () { rotateBoat (rotationalGoal - currentRotation); } + + public void toogleAnnotations () { + super.getChildren().get(1).setVisible(false); + super.getChildren().get(2).setVisible(false); + super.getChildren().get(3).setVisible(false); + } } diff --git a/src/test/java/seng302/ColorsTest.java b/src/test/java/seng302/ColorsTest.java index 84fc01dd..6fc07b41 100644 --- a/src/test/java/seng302/ColorsTest.java +++ b/src/test/java/seng302/ColorsTest.java @@ -1,17 +1,10 @@ package seng302; import javafx.scene.paint.Color; -import javafx.scene.paint.Paint; import org.junit.Assert; import org.junit.Test; -import seng302.models.Boat; -import seng302.models.BoatPolygon; import seng302.models.Colors; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; From d51825ffb79b5c83794a34a66a10ab966a6d5a30 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Tue, 25 Apr 2017 15:32:04 +1200 Subject: [PATCH 20/97] Created separate streams for each different data type to be parsed into so the exact needed data would be able to be extracted. #story[820] --- .../seng302/models/parsers/StreamPacket.java | 17 ++- .../seng302/models/parsers/StreamParser.java | 118 +++++++++++++----- .../models/parsers/StreamReceiver.java | 11 +- 3 files changed, 105 insertions(+), 41 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index 1392f4d4..fa79763d 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -18,11 +18,24 @@ public class StreamPacket { this.timeStamp = timeStamp; this.payload = payload; // System.out.println("type = " + type); - if (this.type == PacketType.BOAT_LOCATION){ - StreamParser.extractBoatLocation(payload); + if (this.type == PacketType.OTHER){ + System.out.println("type = " + type); +// StreamParser.extractBoatLocation(payload); } } + public PacketType getType() { + return type; + } + + public long getMessageLength() { + return messageLength; + } + + public byte[] getPayload() { + return payload; + } + public long getTimeStamp() { return timeStamp; } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 6dbe2d86..d29d49c8 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -19,62 +19,112 @@ import java.util.Map; */ public class StreamParser { - private static boolean isWithinTag; - public static ArrayList ids = new ArrayList<>(); + static void parseLine(StreamPacket packet) { + switch (packet.getType()){ + case HEARTBEAT: + extractHeartBeat(packet); + case RACE_STATUS: + extractRaceStatus(packet); + case DISPLAY_TEXT_MESSAGE: + extractDisplayMessage(packet); + case XML_MESSAGE: + extractXmlMessage(packet); + case RACE_START_STATUS: + extractRaceStartStatus(packet); + case YACHT_EVENT_CODE: + extractYachtEventCode(packet); + case YACHT_ACTION_CODE: + extractYachtActionCode(packet); + case CHATTER_TEXT: + extractChatterText(packet); + case BOAT_LOCATION: + extractBoatLocation(packet); + case MARK_ROUNDING: + extractMarkRounding(packet); + case COURSE_WIND: + extractCourseWind(packet); + case AVG_WIND: + extractAvgWind(packet); + } + - static void parseLine(byte[] bytes) { - //TODO overhaul all of this to treat packets as appropriate - String line = new String(bytes); - if (line.startsWith("<")){ - isWithinTag = true; - } -// System.out.println("line = ---------------------------------------------\n" + line); - if (isWithinTag) { -// try { -// Element node = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(line.getBytes())).getDocumentElement(); -// if (node.getAttributes().getNamedItem("Type") != null) { -// System.out.println(node.getAttributes().getNamedItem("Type") ); -// System.out.println(line); -// } -// } catch (Throwable e){ -//// e.printStackTrace(); -// } - } - if (line.startsWith(" Date: Tue, 25 Apr 2017 15:39:28 +1200 Subject: [PATCH 21/97] Fixed error in the switch (missing breaks) #story[820] --- .../seng302/models/parsers/StreamPacket.java | 10 +++++----- .../seng302/models/parsers/StreamParser.java | 20 +++++++++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index fa79763d..35bd526f 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -12,7 +12,7 @@ public class StreamPacket { private long timeStamp; private byte[] payload; - public StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) { + StreamPacket(int type, long messageLength, long timeStamp, byte[] payload) { this.type = PacketType.assignPacketType(type); this.messageLength = messageLength; this.timeStamp = timeStamp; @@ -20,11 +20,11 @@ public class StreamPacket { // System.out.println("type = " + type); if (this.type == PacketType.OTHER){ System.out.println("type = " + type); -// StreamParser.extractBoatLocation(payload); + StreamParser.parsePacket(this); } } - public PacketType getType() { + PacketType getType() { return type; } @@ -32,11 +32,11 @@ public class StreamPacket { return messageLength; } - public byte[] getPayload() { + byte[] getPayload() { return payload; } - public long getTimeStamp() { + long getTimeStamp() { return timeStamp; } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index d29d49c8..4fb6d4e3 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -19,39 +19,51 @@ import java.util.Map; */ public class StreamParser { - static void parseLine(StreamPacket packet) { + static void parsePacket(StreamPacket packet) { switch (packet.getType()){ case HEARTBEAT: extractHeartBeat(packet); + break; case RACE_STATUS: extractRaceStatus(packet); + break; case DISPLAY_TEXT_MESSAGE: extractDisplayMessage(packet); + break; case XML_MESSAGE: extractXmlMessage(packet); + break; case RACE_START_STATUS: extractRaceStartStatus(packet); + break; case YACHT_EVENT_CODE: extractYachtEventCode(packet); + break; case YACHT_ACTION_CODE: extractYachtActionCode(packet); + break; case CHATTER_TEXT: extractChatterText(packet); + break; case BOAT_LOCATION: extractBoatLocation(packet); + break; case MARK_ROUNDING: extractMarkRounding(packet); + break; case COURSE_WIND: extractCourseWind(packet); + break; case AVG_WIND: extractAvgWind(packet); + break; + default: + System.out.println(packet.getType().toString()); } - - } private static void extractHeartBeat(StreamPacket packet){ - + System.out.println(bytesToLong(packet.getPayload())); } private static void extractRaceStatus(StreamPacket packet){ From 8cbd1cc4aab38dfdc35887a664d9c4c8e726aa94 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Tue, 25 Apr 2017 17:38:26 +1200 Subject: [PATCH 22/97] Added support to import XML packet to XML Document object. #story[820] --- .../seng302/models/parsers/StreamParser.java | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 4fb6d4e3..2a6037cf 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -1,25 +1,24 @@ package seng302.models.parsers; -import org.w3c.dom.Element; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; +import java.io.StringReader; import java.util.Arrays; -import java.util.Map; /** * Created by kre39 on 23/04/17. */ public class StreamParser { - static void parsePacket(StreamPacket packet) { + static void parsePacket(StreamPacket packet) { switch (packet.getType()){ case HEARTBEAT: extractHeartBeat(packet); @@ -74,7 +73,41 @@ public class StreamParser { } - private static void extractXmlMessage(StreamPacket packet){ + static void extractXmlMessage(StreamPacket packet){ + + byte[] payload = packet.getPayload(); + String xmlMessage = ""; + + ByteArrayInputStream payloadStream = new ByteArrayInputStream(payload); + + //Bunch of data we don't want (Message Version Number, AckNumber, Timestamp) + payloadStream.skip(9); + int xmlMessageSubType = payloadStream.read(); + payloadStream.skip(2); + + //checks the length of the xml message itself + int xmlMessageLength = payloadStream.read() | payloadStream.read() << 8; + + //Converts XML message to string to be parsed + int currentChar; + while (payloadStream.available() > 0 && (currentChar = payloadStream.read()) != 0) { + xmlMessage += (char)currentChar; + } + + //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 e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } } From 5eebab274887a6650ee9f78eb06ebcc898b497c4 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Tue, 25 Apr 2017 17:57:22 +1200 Subject: [PATCH 23/97] Completed data extractors for: heartbeat, racestatus, display text msg, race start status, yacht event code, yacht action acode, chatter text, boat location, mark rounding, course wind and average wind. Some of the methods need to be validated but others have been tested. Will now need to link the parses with the model. #story[820] --- .../seng302/models/parsers/StreamPacket.java | 4 +- .../seng302/models/parsers/StreamParser.java | 120 +++++++++++++++--- 2 files changed, 106 insertions(+), 18 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index 35bd526f..80e2936e 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -18,8 +18,8 @@ public class StreamPacket { this.timeStamp = timeStamp; this.payload = payload; // System.out.println("type = " + type); - if (this.type == PacketType.OTHER){ - System.out.println("type = " + type); + //switch the packet type to deal with what ever specific packet you want to deal with + if (this.type == PacketType.AVG_WIND){ StreamParser.parsePacket(this); } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 2a6037cf..6f526ba3 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -11,6 +11,7 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; +import java.util.ArrayList; import java.util.Arrays; /** @@ -61,16 +62,49 @@ public class StreamParser { } } - private static void extractHeartBeat(StreamPacket packet){ - System.out.println(bytesToLong(packet.getPayload())); + private static void extractHeartBeat(StreamPacket packet) { + long heartbeat = bytesToLong(packet.getPayload()); + System.out.println("Heartbeat: " + heartbeat); + } private static void extractRaceStatus(StreamPacket packet){ - + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long currentTime = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); + long raceId = bytesToLong(Arrays.copyOfRange(payload,7,11)); + int raceStatus = payload[11]; + System.out.println("raceStatus = " + raceStatus); + long expectedStartTime = extractTimeStamp(Arrays.copyOfRange(payload,12,18), 6); + long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); + long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); + int noBoats = payload[22]; + int raceType = payload[23]; + ArrayList boatStatuses = new ArrayList<>(); + for (int i = 0; i < noBoats; i++){ + String boatStatus = "SourceID: " + bytesToLong(Arrays.copyOfRange(payload,24 + (i * 20),28+ (i * 20))); + boatStatus += "\nBoat Status: " + (int)payload[28 + (i * 20)]; + boatStatus += "\nLegNumber: " + (int)payload[29 + (i * 20)]; + boatStatus += "\nPenaltiesAwarded: " + (int)payload[29 + (i * 20)]; + boatStatus += "\nPenaltiesServed: " + (int)payload[30 + (i * 20)]; + 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); + } } private static void extractDisplayMessage(StreamPacket packet){ - + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + int numOfLines = payload[3]; + int totalLen = 0; + for (int i = 0; i < numOfLines; i++){ + int lineNum = payload[4 + totalLen]; + int textLength = payload[5 + totalLen]; + byte[] messageTextBytes = Arrays.copyOfRange(payload,6 + totalLen,6 + textLength + totalLen); + String messageText = new String(messageTextBytes); + totalLen += 2 + textLength; + } } static void extractXmlMessage(StreamPacket packet){ @@ -112,19 +146,40 @@ public class StreamParser { } private static void extractRaceStartStatus(StreamPacket packet){ - + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); + long raceStartTime = extractTimeStamp(Arrays.copyOfRange(payload,9,15), 6); + long raceId = bytesToLong(Arrays.copyOfRange(payload,15,19)); + int notificationType = payload[19]; } private static void extractYachtEventCode(StreamPacket packet){ - + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); + long raceId = bytesToLong(Arrays.copyOfRange(payload,9,13)); + long subjectId = bytesToLong(Arrays.copyOfRange(payload,13,17)); + long incidentId = bytesToLong(Arrays.copyOfRange(payload,17,21)); + int eventId = payload[21]; } private static void extractYachtActionCode(StreamPacket packet){ - + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); + long subjectId = bytesToLong(Arrays.copyOfRange(payload,9,13)); + long incidentId = bytesToLong(Arrays.copyOfRange(payload,13,17)); + int eventId = payload[17]; + System.out.println("eventId = " + eventId); } private static void extractChatterText(StreamPacket packet){ - + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + int messageType = payload[1]; + int length = payload[2]; + String message = new String(Arrays.copyOfRange(payload,3,3 + length)); } @@ -135,7 +190,7 @@ public class StreamParser { byte[] latBytes = Arrays.copyOfRange(payload,16,20); byte[] lonBytes = Arrays.copyOfRange(payload,20,24); byte[] boatIdBytes = Arrays.copyOfRange(payload,8,12); - extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); + long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); // int boatSeq = ByteBuffer.wrap(seqBytes).getInt(); long seq = bytesToLong(seqBytes); long boatId = bytesToLong(boatIdBytes); @@ -147,33 +202,66 @@ public class StreamParser { // System.out.println("deviceType = " + (long)deviceType); // System.out.println("seq = " + seq); //needs to be validated -// System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31))); -// System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31))); + System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31))); + System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31))); } } private static void extractMarkRounding(StreamPacket packet){ - + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); + long raceId = bytesToLong(Arrays.copyOfRange(payload,9,13)); + long subjectId = bytesToLong(Arrays.copyOfRange(payload,13,17)); + int boatStatus = payload[17]; + int roundingSide = payload[18]; + int markType = payload[19]; + int markId = payload[20]; } private static void extractCourseWind(StreamPacket packet){ - + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + int selectedWindId = payload[1]; + int loopCount = payload[2]; + ArrayList windInfo = new ArrayList<>(); + for (int i = 0; i < loopCount; i++){ + String wind = "WindId: " + payload[3 + (20 * i)]; + wind += "\nTime: " + extractTimeStamp(Arrays.copyOfRange(payload,4 + (20 * i),10 + (20 * i)), 6); + wind += "\nRaceId: " + bytesToLong(Arrays.copyOfRange(payload,10 + (20 * i),14 + (20 * i))); + wind += "\nWindDirection: " + bytesToLong(Arrays.copyOfRange(payload,14 + (20 * i),16 + (20 * i))); + wind += "\nWindSpeed: " + bytesToLong(Arrays.copyOfRange(payload,16 + (20 * i),18 + (20 * i))); + wind += "\nBestUpWindAngle: " + bytesToLong(Arrays.copyOfRange(payload,18 + (20 * i),20 + (20 * i))); + wind += "\nBestDownWindAngle: " + bytesToLong(Arrays.copyOfRange(payload,20 + (20 * i),22 + (20 * i))); + wind += "\nFlags: " + String.format("%8s", Integer.toBinaryString(payload[22 + (20 * i)] & 0xFF)).replace(' ', '0'); + windInfo.add(wind); + } } private static void extractAvgWind(StreamPacket packet){ - + byte[] payload = packet.getPayload(); + int messageVersionNo = payload[0]; + long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); + long rawPeriod = bytesToLong(Arrays.copyOfRange(payload,7,9)); + long rawSamplePeriod = bytesToLong(Arrays.copyOfRange(payload,9,11)); + long period2 = bytesToLong(Arrays.copyOfRange(payload,11,13)); + long speed2 = bytesToLong(Arrays.copyOfRange(payload,13,15)); + long period3 = bytesToLong(Arrays.copyOfRange(payload,15,17)); + long speed3 = bytesToLong(Arrays.copyOfRange(payload,17,19)); + long period4 = bytesToLong(Arrays.copyOfRange(payload,19,21)); + long speed4 = bytesToLong(Arrays.copyOfRange(payload,21,23)); } - private static void extractTimeStamp(byte[] timeStampBytes, int noOfBytes){ + private static long extractTimeStamp(byte[] timeStampBytes, int noOfBytes){ long timeStamp = 0; long multiplier=1; for(int i = 0;i < noOfBytes;i++) { timeStamp += timeStampBytes[i]*multiplier; multiplier *= 256; } - System.out.println("timeStamp = " + timeStamp); + return timeStamp; } /** From 1f8f1f0f863d6eb557d7c4730b767979f6bbe227 Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Tue, 25 Apr 2017 21:49:51 +1200 Subject: [PATCH 24/97] Added boat location, and race start messages to the mock data interface - Added proper support for signed and unsigned types. This includes automatic conversion to the correct data type (long to int, short, or byte). - Moved code related to adding values into the byte buffer into the abstract Message class Tags: #story[29] --- .../java/seng302/server/ServerThread.java | 34 ++++- .../server/messages/BoatLocationMessage.java | 109 ++++++++++++++ .../server/messages/BoatSubMessage.java | 11 +- .../seng302/server/messages/DeviceType.java | 16 +++ .../seng302/server/messages/Heartbeat.java | 19 +-- .../java/seng302/server/messages/Message.java | 132 ++++++++++++++++- .../messages/RaceStartNotificationType.java | 21 +++ .../messages/RaceStartStatusMessage.java | 59 ++++++++ .../server/messages/RaceStatusMessage.java | 67 ++------- .../seng302/server/messages/XMLMessage.java | 64 +++------ src/test/java/seng302/server/TestHeader.java | 26 ++++ src/test/java/seng302/server/TestMessage.java | 136 ++++++++++++++++++ 12 files changed, 573 insertions(+), 121 deletions(-) create mode 100644 src/main/java/seng302/server/messages/BoatLocationMessage.java create mode 100644 src/main/java/seng302/server/messages/DeviceType.java create mode 100644 src/main/java/seng302/server/messages/RaceStartNotificationType.java create mode 100644 src/main/java/seng302/server/messages/RaceStartStatusMessage.java create mode 100644 src/test/java/seng302/server/TestHeader.java create mode 100644 src/test/java/seng302/server/TestMessage.java diff --git a/src/main/java/seng302/server/ServerThread.java b/src/main/java/seng302/server/ServerThread.java index f84887c2..68015574 100644 --- a/src/main/java/seng302/server/ServerThread.java +++ b/src/main/java/seng302/server/ServerThread.java @@ -15,6 +15,7 @@ public class ServerThread implements Runnable{ private StreamingServerSocket server; private final int HEARTBEAT_PERIOD = 5000; private final int RACE_STATUS_PERIOD = 1000; + private final int BOAT_LOCATION_PERIOD = 1000/5; private final int PORT_NUMBER = 8085; public ServerThread(String threadName){ @@ -68,6 +69,19 @@ public class ServerThread implements Runnable{ 100, 3, RaceType.MATCH_RACE, 1, boats); } + /** + * @return A list of sample boat location messages + */ + public List getTestBoatLocationMessages(){ + List messages = new ArrayList<>(); + + messages.add(new BoatLocationMessage(1, 1, 100, 200, 231, 23)); + messages.add(new BoatLocationMessage(2, 2, 400, 300, 211, 13)); + + return messages; + } + + public void run() { try{ server = new StreamingServerSocket(PORT_NUMBER); @@ -105,7 +119,7 @@ public class ServerThread implements Runnable{ try { server.send(hb); } catch (IOException e) { - e.printStackTrace(); + System.out.print(""); } } }, 0, HEARTBEAT_PERIOD); @@ -120,11 +134,27 @@ public class ServerThread implements Runnable{ try { server.send(statusMessage); } catch (IOException e) { - e.printStackTrace(); + System.out.print(""); } } }, 100, RACE_STATUS_PERIOD); + t1.schedule(new TimerTask() { + @Override + public void run() { + List messages = getTestBoatLocationMessages(); + + for (Message m : messages){ + try { + server.send(m); + } catch (IOException e) { + System.out.print(""); + } + } + + } + }, 100, BOAT_LOCATION_PERIOD); + } catch (IOException e) { System.err.println(e.getMessage()); } diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java new file mode 100644 index 00000000..a475128b --- /dev/null +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -0,0 +1,109 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class BoatLocationMessage extends Message { + private final int MESSAGE_SIZE = 56; + + private long messageVersionNumber; + private long time; + private long sourceId; + private long sequenceNum; + private DeviceType deviceType; + private long latitude; + private long longitude; + private long altitude; + private long heading; + private long pitch; + private long roll; + private long boatSpeed; + private long COG; + private long SOG; + private long apparentWindSpeed; + private long apparentWindAngle; + private long trueWindSpeed; + private long trueWindDirection; + private long trueWindAngle; + private long currentDrift; + private long currentSet; + private long rudderAngle; + + /** + * Describes the location, altitude and sensor data from the boat. + * @param sourceId ID of the boat + * @param sequenceNum Sequence number of the message + * @param latitude The boats latitude + * @param longitude The boats longitude + * @param heading The boats heading + * @param boatSpeed The boats speed + */ + public BoatLocationMessage(int sourceId, int sequenceNum, long latitude, long longitude, long heading, long boatSpeed){ + messageVersionNumber = 1; + time = System.currentTimeMillis() / 1000L; + this.sourceId = sourceId; + this.sequenceNum = sequenceNum; + this.deviceType = DeviceType.RACING_YACHT; + this.latitude = -latitude; + this.longitude = longitude; + this.altitude = 0; + this.heading = heading; + this.pitch = 0; + this.roll = 0; + this.boatSpeed = boatSpeed; + this.COG = 0; + this.SOG = 0; + this.apparentWindSpeed = 0; + this.apparentWindAngle = 0; + this.trueWindSpeed = 0; + this.trueWindDirection = 0; + this.trueWindAngle = 0; + this.currentDrift = 0; + this.currentSet = 0; + this.rudderAngle = 0; + + setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize())); + } + + @Override + public int getSize() { + return 56; + } + + @Override + public void send(DataOutputStream outputStream) { + allocateBuffer(); + writeHeaderToBuffer(); + + putByte((byte) messageVersionNumber); + putInt((int) time, 6); + putInt((int) sourceId, 4); + putUnsignedInt((int) sequenceNum, 4); + putByte((byte) deviceType.getCode()); + putInt((int) latitude, 4); + putInt((int) longitude, 4); + putInt((int) altitude, 4); + putUnsignedInt((int) heading, 2); + putInt((int) pitch, 2); + putInt((int) roll, 2); + putUnsignedInt((int) boatSpeed, 2); + putUnsignedInt((int) COG, 2); + putUnsignedInt((int) SOG, 2); + putUnsignedInt((int) apparentWindSpeed, 2); + putInt((int) apparentWindAngle, 2); + putUnsignedInt((int) trueWindSpeed, 2); + putUnsignedInt((int) trueWindDirection, 2); + putInt((int) trueWindAngle, 2); + putUnsignedInt((int) currentDrift, 2); + putUnsignedInt((int) currentSet, 2); + putInt((int) rudderAngle, 2); + + + writeCRC(); + try { + outputStream.write(getBuffer().array()); + } catch (IOException e) { + System.out.print(""); + } + } +} diff --git a/src/main/java/seng302/server/messages/BoatSubMessage.java b/src/main/java/seng302/server/messages/BoatSubMessage.java index d403d85b..ebe0f6a2 100644 --- a/src/main/java/seng302/server/messages/BoatSubMessage.java +++ b/src/main/java/seng302/server/messages/BoatSubMessage.java @@ -1,11 +1,12 @@ package seng302.server.messages; +import java.io.DataOutputStream; import java.nio.ByteBuffer; /** * The status of each boat, sent within a race status message */ -public class BoatSubMessage { +public class BoatSubMessage{ private final int MESSAGE_SIZE = 20; private long sourceId; @@ -57,22 +58,22 @@ public class BoatSubMessage { buff.position(buffPos); // Boat Status, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) boatStatus.getCode()).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (boatStatus.getCode() & 0xff)).array()); buffPos += 1; buff.position(buffPos); // Leg number, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) legNumber).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (legNumber & 0xff)).array()); buffPos += 1; buff.position(buffPos); // Number of penalties awarded, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesAwarded).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesAwarded & 0xff)).array()); buffPos += 1; buff.position(buffPos); // Number of penalties served, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte) numberPenaltiesServed).array()); + buff.put(ByteBuffer.allocate(1).put((byte) (numberPenaltiesServed & 0xff)).array()); buffPos += 1; buff.position(buffPos); diff --git a/src/main/java/seng302/server/messages/DeviceType.java b/src/main/java/seng302/server/messages/DeviceType.java new file mode 100644 index 00000000..d245c2b1 --- /dev/null +++ b/src/main/java/seng302/server/messages/DeviceType.java @@ -0,0 +1,16 @@ +package seng302.server.messages; + +public enum DeviceType { + UNKNOWN(0), + RACING_YACHT(1); + + private long code; + + DeviceType(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/Heartbeat.java b/src/main/java/seng302/server/messages/Heartbeat.java index a6470240..e5815039 100644 --- a/src/main/java/seng302/server/messages/Heartbeat.java +++ b/src/main/java/seng302/server/messages/Heartbeat.java @@ -26,24 +26,15 @@ public class Heartbeat extends Message { public void send(DataOutputStream outputStream) { setHeader(new Header(MessageType.HEARTBEAT, 0x01, (short) getSize())); - ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + getSize()); + allocateBuffer(); + writeHeaderToBuffer(); - // Write header - buff.put(getHeader().getByteBuffer()); - buff.position(Header.getSize()); + putUnsignedInt(seqNo, 4); - // Write seq num - buff.put(ByteBuffer.allocate(4).putInt(seqNo).array()); - buff.position(Header.getSize()+4); - - // Write CRC - CRC32 crc = new CRC32(); - crc.update(buff.array()); - - buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + writeCRC(); try { - outputStream.write(buff.array()); + outputStream.write(getBuffer().array()); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/seng302/server/messages/Message.java b/src/main/java/seng302/server/messages/Message.java index 8e5c48b4..889a37fa 100644 --- a/src/main/java/seng302/server/messages/Message.java +++ b/src/main/java/seng302/server/messages/Message.java @@ -1,21 +1,27 @@ package seng302.server.messages; import java.io.DataOutputStream; +import java.nio.ByteBuffer; +import java.util.zip.CRC32; public abstract class Message { - Header header; + private final int CRC_SIZE = 4; + private Header header; + private ByteBuffer buffer; + private int bufferPosition; + private CRC32 crc; /** * @param header Set the header for this message */ - public void setHeader(Header header){ + void setHeader(Header header){ this.header = header; } /** * @return the header specified for this message */ - public Header getHeader(){ + Header getHeader(){ return header; } @@ -28,4 +34,124 @@ public abstract class Message { * Send the message as through the outputStream */ public abstract void send(DataOutputStream outputStream); + + /** + * Allocate byte buffer to correct size + */ + void allocateBuffer(){ + buffer = ByteBuffer.allocate(Header.getSize() + getSize() + CRC_SIZE); + bufferPosition = 0; + } + + /** + * Write the set header to the byte buffer + */ + void writeHeaderToBuffer(){ + buffer.put(getHeader().getByteBuffer()); + bufferPosition += Header.getSize(); + buffer.position(bufferPosition); + } + + /** + * Move the buffer position by n bytes + * @param size Number of bytes to move the buffer by + */ + private void moveBufferPositionBy(int size){ + bufferPosition += size; + buffer.position(bufferPosition); + } + + /** + * Put an unsigned byte in the buffer + */ + void putUnsignedByte(byte b){ + buffer.put(ByteBuffer.allocate(1).put((byte) (b & 0xff)).array()); + moveBufferPositionBy(1); + } + + /** + * Put an signed byte in the buffer + */ + void putByte(byte b){ + buffer.put(ByteBuffer.allocate(1).put(b).array()); + moveBufferPositionBy(1); + } + + /** + * Place an unsigned integer of the specified length in the buffer + * @param val The integer value to add (Note: This must be long due to java not supporting unsigned integers) + * @param size The size of the int to be added to the buffer + */ + void putUnsignedInt(long val, int size){ + if (size <= 1){ + putUnsignedByte((byte) val); + + } + else if (size < 4){ + // Use short + buffer.put(ByteBuffer.allocate(size).putShort((short) (val & 0xffff)).array()); + moveBufferPositionBy(size); + } + else{ + // Use int + buffer.put(ByteBuffer.allocate(size).putInt((int) (val & 0xffffffffL)).array()); + moveBufferPositionBy(size); + } + } + + /** + * Put a signed int of a specified length in the buffer + * @param val The integer value to add + * @param size The size of the integer to be added to the buffer + */ + void putInt(int val, int size){ + if (size < 4){ + buffer.put(ByteBuffer.allocate(size).putShort((short) val).array()); + } + else{ + buffer.put(ByteBuffer.allocate(size).putInt((short) val).array()); + } + moveBufferPositionBy(size); + } + + /** + * Write an array of bytes to the buffer + * @param bytes to write + */ + void putBytes(byte[] bytes){ + buffer.put(bytes); + moveBufferPositionBy(bytes.length); + } + + /** + * Write a ByteBuffer of bytes to the buffer + * @param bytes to write + * @param size number of bytes + */ + void putBytes(ByteBuffer bytes, int size){ + buffer.put(bytes); + moveBufferPositionBy(size); + } + + + /** + * Calculate the CRC of the buffer and append it to the end of the buffer + */ + void writeCRC(){ + crc = new CRC32(); + + buffer.position(0); + crc.update(buffer.array()); + buffer.position(bufferPosition); + + putInt((int) crc.getValue(), CRC_SIZE); + } + + /** + * @return The current buffer + */ + public ByteBuffer getBuffer(){ + return buffer; + } + } diff --git a/src/main/java/seng302/server/messages/RaceStartNotificationType.java b/src/main/java/seng302/server/messages/RaceStartNotificationType.java new file mode 100644 index 00000000..29db3f1e --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStartNotificationType.java @@ -0,0 +1,21 @@ +package seng302.server.messages; + +/** + * The types of race start status messages + */ +public enum RaceStartNotificationType { + SET_RACE_START_TIME(1), + RACE_POSTPONED(2), + RACE_ABANDONED(3), + RACE_TERMINATED(4); + + private final long type; + + RaceStartNotificationType(long type) { + this.type = type; + } + + long getType(){ + return type; + } +} diff --git a/src/main/java/seng302/server/messages/RaceStartStatusMessage.java b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java new file mode 100644 index 00000000..d2878bc2 --- /dev/null +++ b/src/main/java/seng302/server/messages/RaceStartStatusMessage.java @@ -0,0 +1,59 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class RaceStartStatusMessage extends Message { + private final int MESSAGE_SIZE = 20; + + private long version; + private long timeStamp; + private long ackNumber; + private long raceStartTime; + private long raceId; + private RaceStartNotificationType notificationType; + + /** + * Message sent to clients with the expected start time of the race + * @param ackNumber Sequence number of message. + * @param raceStartTime Expected race start time + * @param raceId Race ID# + * @param notificationType Type of this notification + */ + public RaceStartStatusMessage(long ackNumber, long raceStartTime, long raceId, RaceStartNotificationType notificationType){ + this.version = 1; + this.timeStamp = System.currentTimeMillis() / 1000L; + this.ackNumber = ackNumber; + this.raceStartTime = raceStartTime; + this.notificationType = notificationType; + this.raceId = raceId; + + setHeader(new Header(MessageType.RACE_START_STATUS, 1, (short) getSize())); + } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + + @Override + public void send(DataOutputStream outputStream) { + allocateBuffer(); + writeHeaderToBuffer(); + + putUnsignedByte((byte) version); + putInt((int) timeStamp, 6); + putInt((int) ackNumber, 2); + putInt((int) raceStartTime, 6); + putInt((int) raceId, 4); + putUnsignedByte((byte) notificationType.getType()); + + writeCRC(); + + try { + outputStream.write(getBuffer().array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/RaceStatusMessage.java b/src/main/java/seng302/server/messages/RaceStatusMessage.java index 17ab82fc..77c0c67a 100644 --- a/src/main/java/seng302/server/messages/RaceStatusMessage.java +++ b/src/main/java/seng302/server/messages/RaceStatusMessage.java @@ -11,7 +11,6 @@ public class RaceStatusMessage extends Message{ private final int MESSAGE_VERSION = 2; //Always set to 1 private final int MESSAGE_BASE_SIZE = 24; - // fields private long currentTime; private long raceId; private RaceStatus raceStatus; @@ -56,7 +55,7 @@ public class RaceStatusMessage extends Message{ */ @Override public int getSize() { - return MESSAGE_BASE_SIZE + (20 * (int) numBoatsInRace); + return MESSAGE_BASE_SIZE + (20 * ((int) numBoatsInRace)); } /** @@ -65,64 +64,28 @@ public class RaceStatusMessage extends Message{ */ @Override public void send(DataOutputStream outputStream) { - ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4/*CRC*/); + allocateBuffer(); + writeHeaderToBuffer(); - buff.put(getHeader().getByteBuffer()); - buff.position(Header.getSize()); - - // Version Number, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); - buff.position(Header.getSize() + 1); - - // Current time, 2 bytes - buff.put(ByteBuffer.allocate(6).putInt((int)currentTime).array()); - buff.position(Header.getSize() + 7); - - // Race id, 4 bytes - buff.put(ByteBuffer.allocate(4).putInt((int)raceId).array()); - buff.position(Header.getSize() + 11); - - // Race status, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)raceStatus.getCode()).array()); - buff.position(Header.getSize() + 12); - - // Expected start time, 6 bytes - buff.put(ByteBuffer.allocate(6).putInt((int)expectedStartTime).array()); - buff.position(Header.getSize() + 18); - - // Wind direction, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort((short)raceWindDirection.getCode()).array()); - buff.position(Header.getSize() + 20); - - // Wind Speed, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort((short)windSpeed).array()); - buff.position(Header.getSize() + 22); - - // Number of boats, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)numBoatsInRace).array()); - buff.position(Header.getSize() + 23); - - // Race Type, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)raceType.getCode()).array()); - buff.position(Header.getSize() + 24); - - int buffPosition = Header.getSize() + 24; + putByte((byte) MESSAGE_VERSION); + putInt((int) currentTime, 6); + putInt((int) raceId, 4); + putByte((byte) raceStatus.getCode()); + putInt((int) expectedStartTime, 6); + putInt((int) raceWindDirection.getCode(), 2); + putInt((int) windSpeed, 2); + putByte((byte) numBoatsInRace); + putByte((byte) raceType.getCode()); for (BoatSubMessage boatSubMessage : boats){ - buff.put(boatSubMessage.getByteBuffer()); - buffPosition += boatSubMessage.getSize(); - buff.position(buffPosition); + putBytes(boatSubMessage.getByteBuffer(), boatSubMessage.getSize()); } - // calculate CRC - crc.update(buff.array()); - - // Add CRC to message - buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + writeCRC(); // Send try { - outputStream.write(buff.array()); + outputStream.write(getBuffer().array()); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/seng302/server/messages/XMLMessage.java b/src/main/java/seng302/server/messages/XMLMessage.java index 2d7e0431..cc862d2f 100644 --- a/src/main/java/seng302/server/messages/XMLMessage.java +++ b/src/main/java/seng302/server/messages/XMLMessage.java @@ -11,26 +11,24 @@ public class XMLMessage extends Message{ private final int MESSAGE_SIZE = 14; // Message fields - private int timeStamp; - private short ack = 0x00; //Unused + private long timeStamp; + private long ack = 0x00; //Unused private XMLMessageSubType xmlMessageSubType; - private Short length; - private Short sequence; + private long length; + private long sequence; private String content; - private CRC32 crc; /** * XML Message from the AC35 Streaming data spec * @param content The XML content * @param type The XML Message Sub Type */ - public XMLMessage(String content, XMLMessageSubType type, short sequenceNum){ + public XMLMessage(String content, XMLMessageSubType type, long sequenceNum){ this.content = content; this.xmlMessageSubType = type; - crc = new CRC32(); - timeStamp = (int) (System.currentTimeMillis() / 1000L); + timeStamp = System.currentTimeMillis() / 1000L; ack = 0; - length = (short) this.content.length(); + length = this.content.length(); sequence = sequenceNum; setHeader(new Header(MESSAGE_TYPE, 0x01, (short) getSize())); @@ -48,47 +46,23 @@ public class XMLMessage extends Message{ * @param outputStream The output stream to send the message */ public void send(DataOutputStream outputStream) { - ByteBuffer buff = ByteBuffer.allocate(Header.getSize() + getSize() + 4); - buff.put(getHeader().getByteBuffer()); - buff.position(Header.getSize()); + allocateBuffer(); + writeHeaderToBuffer(); - // Version Number, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)MESSAGE_VERSION).array()); - buff.position(Header.getSize() + 1); + // Write message fields + putUnsignedByte((byte) MESSAGE_VERSION); + putInt((int) ack, 2); + putInt((int) timeStamp, 6); + putByte((byte)xmlMessageSubType.getType()); + putInt((int) sequence, 2); + putInt((int) length, 2); + putBytes(content.getBytes()); - // Ack, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(ack).array()); - buff.position(Header.getSize() + 3); - - // Timestamp, 6 bytes - buff.put(ByteBuffer.allocate(6).putInt(timeStamp).array()); - buff.position(Header.getSize() + 9); - - // XML message sub type, 1 byte - buff.put(ByteBuffer.allocate(1).put((byte)xmlMessageSubType.getType()).array()); - buff.position(Header.getSize() + 10); - - // Seq num, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(sequence).array()); - buff.position(Header.getSize() + 12); - - // Message length, 2 bytes - buff.put(ByteBuffer.allocate(2).putShort(length).array()); - buff.position(Header.getSize() + 14); - - // XML Content - buff.put(this.content.getBytes()); - buff.position(Header.getSize() + 14 + this.content.getBytes().length); - - // calculate CRC - crc.update(buff.array()); - - // Add CRC to message - buff.put(ByteBuffer.allocate(4).putInt((short)crc.getValue()).array()); + writeCRC(); // Send try { - outputStream.write(buff.array()); + outputStream.write(getBuffer().array()); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/test/java/seng302/server/TestHeader.java b/src/test/java/seng302/server/TestHeader.java new file mode 100644 index 00000000..4fa2bc3d --- /dev/null +++ b/src/test/java/seng302/server/TestHeader.java @@ -0,0 +1,26 @@ +package seng302.server; + +import org.junit.Test; +import seng302.server.messages.Header; +import seng302.server.messages.MessageType; + +import static junit.framework.TestCase.assertTrue; + +/** + * Tests message header + */ +public class TestHeader { + + @Test + public void testHeaderSizeEqualsActualSize(){ + Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1); + assertTrue(h.getSize() == h.getByteBuffer().array().length); + + } + + @Test + public void headerSizeIsSameAsSpec(){ + Header h = new Header(MessageType.DISPLAY_TEXT_MESSAGE, 1, (short) 1); + assertTrue(h.getSize() == 15); // Spec specifies 15 bytes + } +} diff --git a/src/test/java/seng302/server/TestMessage.java b/src/test/java/seng302/server/TestMessage.java new file mode 100644 index 00000000..71521014 --- /dev/null +++ b/src/test/java/seng302/server/TestMessage.java @@ -0,0 +1,136 @@ +package seng302.server; + +import org.junit.Test; +import seng302.server.messages.*; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.TestCase.assertTrue; + +public class TestMessage { + private static int XML_MESSAGE_LEN = 14; + private static int RACE_STATUS_BASE_LEN = 24; + private static int BOAT_SUB_MESSAGE_LEN = 20; + private static int CRC_LEN = 4; + + /** + * Test generated output is the same as the expected output + */ + @Test + public void testHeatBetBufferOutputLength(){ + Message m = new Heartbeat(1); + List output = new ArrayList<>(); + + DataOutputStream ds = new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.add(b); + } + }); + + m.send(ds); + assertTrue(output.size() == (m.getSize() + CRC_LEN + Header.getSize())); + } + + /** + * Test output expected is the same as the spec + */ + @Test + public void testXmlMessageSize(){ + Message m = new XMLMessage("12345", XMLMessageSubType.BOAT, 1); + assertTrue(m.getSize() == (XML_MESSAGE_LEN + "12345".length())); + } + + /** + * Ensure that when no boats are in the race, that only the base message is sent + */ + @Test + public void testRaceStatusMessageBufferLenNoBoats(){ + Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, + 0,RaceType.MATCH_RACE,1, new ArrayList()); + + List output = new ArrayList<>(); + + DataOutputStream ds = new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.add(b); + } + }); + + m.send(ds); + assertTrue(output.size() == RACE_STATUS_BASE_LEN + Header.getSize() + CRC_LEN); + } + + /** + * Test that each boat status is added to the message + */ + @Test + public void testRaceStatusMessageBufferLenWithBoats(){ + List boatMessages = new ArrayList<>(); + List output = new ArrayList<>(); + + BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + boatMessages.add(boat1); + boatMessages.add(boat2); + boatMessages.add(boat3); + + Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, + 3,RaceType.MATCH_RACE,1, boatMessages); + + DataOutputStream ds = new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + output.add(b); + } + }); + + m.send(ds); + assertTrue(output.size() == (RACE_STATUS_BASE_LEN + (BOAT_SUB_MESSAGE_LEN * 3) + CRC_LEN + Header.getSize())); + } + + /** + * IllegalArgumentException should be thrown when numBoatsInRace is smaller + * than the number of boats actually in the race + */ + @Test(expected = IllegalArgumentException.class) + public void testRaceStatusTooManyBoats(){ + List boatMessages = new ArrayList<>(); + List output = new ArrayList<>(); + + BoatSubMessage boat1 = new BoatSubMessage(1, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat2 = new BoatSubMessage(2, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + BoatSubMessage boat3 = new BoatSubMessage(3, BoatStatus.PRESTART, 0, 0, 0, + 10000, 10000); + + boatMessages.add(boat1); + boatMessages.add(boat2); + boatMessages.add(boat3); + + Message m = new RaceStatusMessage(1, RaceStatus.PRESTART,1,WindDirection.EAST,1, + 1,RaceType.MATCH_RACE,1, boatMessages); + + m.send(new DataOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + System.out.print(""); + } + })); + } +} From c73bf7dd3e6269a7ff345f744e83d893addc4476 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Wed, 26 Apr 2017 15:51:15 +1200 Subject: [PATCH 25/97] Started merging received packets from the sample stream and reading lats and lons to move boats from them. #story[820] --- .../seng302/controllers/CanvasController.java | 2 ++ src/main/java/seng302/models/BoatGroup.java | 4 +++- .../seng302/models/parsers/StreamPacket.java | 6 ++++-- .../seng302/models/parsers/StreamParser.java | 16 +++++++++++----- .../java/seng302/models/parsers/TeamsParser.java | 2 +- src/main/resources/config/course.xml | 4 ++-- src/main/resources/config/teams.xml | 6 ++++++ 7 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index a679e52f..a1883304 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -159,6 +159,8 @@ public class CanvasController { leftToRight = nextMark.getX() < boatGroup.getLayoutX(); boatGroup.updatePosition(1000 / 60); +// Point2D p = latLonToXY(lat, lon); +// boatGroup.setDestination(p.getX(), p.getY()); if (descending && nextMark.getY() < boatGroup.getLayoutY()) { currentRaceMarker[boatIndex]++; boatGroup.setDestination( diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 06ea1306..f304ba90 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -12,6 +12,7 @@ import javafx.scene.transform.Translate; * Created by CJIRWIN on 25/04/2017. */ public class BoatGroup extends Group{ + private static final double TEAMNAME_X_OFFSET = 15d; private static final double TEAMNAME_Y_OFFSET = -20d; private static final double VELOCITY_X_OFFSET = 15d; @@ -98,7 +99,8 @@ public class BoatGroup extends Group{ * @param y The Y coordinate to move the boat to */ public void moveBoatTo(Double x, Double y, Double rotation) { - super.relocate(x, y); + super.setLayoutX(x); + super.setLayoutY(y); currentRotation = 0; rotateBoat(rotation); } diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index 80e2936e..87f7bc63 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -17,9 +17,11 @@ public class StreamPacket { this.messageLength = messageLength; this.timeStamp = timeStamp; this.payload = payload; -// System.out.println("type = " + type); +// System.out.println("type = " + this.type.toString()); //switch the packet type to deal with what ever specific packet you want to deal with - if (this.type == PacketType.AVG_WIND){ + if (this.type == PacketType.XML_MESSAGE){ + System.out.println("--------"); + System.out.println(new String(payload)); StreamParser.parsePacket(this); } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 6f526ba3..bdc3ace0 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -19,6 +19,7 @@ import java.util.Arrays; */ public class StreamParser { + private static ArrayList boat_IDS = new ArrayList<>(); static void parsePacket(StreamPacket packet) { switch (packet.getType()){ case HEARTBEAT: @@ -189,7 +190,7 @@ public class StreamParser { byte[] seqBytes = Arrays.copyOfRange(payload,11,15); byte[] latBytes = Arrays.copyOfRange(payload,16,20); byte[] lonBytes = Arrays.copyOfRange(payload,20,24); - byte[] boatIdBytes = Arrays.copyOfRange(payload,8,12); + byte[] boatIdBytes = Arrays.copyOfRange(payload,7,11); long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); // int boatSeq = ByteBuffer.wrap(seqBytes).getInt(); long seq = bytesToLong(seqBytes); @@ -197,15 +198,20 @@ public class StreamParser { long lat = bytesToLong(latBytes); long lon = bytesToLong(lonBytes); - if (boatId != 0){ -// System.out.println("boatId = " + boatId); -// System.out.println("deviceType = " + (long)deviceType); -// System.out.println("seq = " + seq); + if ((int)deviceType == 1){ + if (!boat_IDS.contains(boatId)){ + boat_IDS.add(boatId); + } + System.out.println("boatId = " + boatId); + System.out.println("deviceType = " + (long)deviceType); + System.out.println("seq = " + seq); //needs to be validated System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31))); System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31))); } + System.out.println("boat_IDS = " + boat_IDS); + System.out.println("boat_IDS = " + boat_IDS.size()); } diff --git a/src/main/java/seng302/models/parsers/TeamsParser.java b/src/main/java/seng302/models/parsers/TeamsParser.java index 2986f448..1b19724a 100644 --- a/src/main/java/seng302/models/parsers/TeamsParser.java +++ b/src/main/java/seng302/models/parsers/TeamsParser.java @@ -44,11 +44,11 @@ public class TeamsParser extends FileParser { */ public ArrayList getBoats() { ArrayList boats = new ArrayList<>(); + try { NodeList nodes = this.doc.getElementsByTagName("team"); for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); - boats.add(parseBoat(node)); } return boats; diff --git a/src/main/resources/config/course.xml b/src/main/resources/config/course.xml index f8bfa00e..bcee2788 100644 --- a/src/main/resources/config/course.xml +++ b/src/main/resources/config/course.xml @@ -6,8 +6,8 @@ Start Start1 - 32.296577 - -64.854304 + 57.6703330 + 57.6703330 Start2 diff --git a/src/main/resources/config/teams.xml b/src/main/resources/config/teams.xml index 582f9e51..f8d1e58d 100644 --- a/src/main/resources/config/teams.xml +++ b/src/main/resources/config/teams.xml @@ -5,30 +5,36 @@ Oracle Team USA USA 12.9 + 102 Artemis Racing ART 13.1 + 101 Emirates Team New Zealand NZL 15.6 + 103 Land Rover BAR BAR 13.3 + 104 SoftBank Team Japan JAP 14.7 + 105 Groupama Team France FRC 11.4 + 106 \ No newline at end of file From 912c08160681986b0040b346d061c83baeb7493c Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Wed, 26 Apr 2017 15:55:51 +1200 Subject: [PATCH 26/97] Added marks with the test data coords to the xml #story[820] --- src/main/resources/config/course.xml | 34 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/resources/config/course.xml b/src/main/resources/config/course.xml index bcee2788..90e67b7f 100644 --- a/src/main/resources/config/course.xml +++ b/src/main/resources/config/course.xml @@ -7,56 +7,56 @@ Start1 57.6703330 - 57.6703330 + 11.8278330 Start2 - 32.293771 - -64.855242 + 57.6703330 + 11.8278330 Mid Mark - 32.293039 - -64.843983 + 57.6675700 + 11.8359880 Leeward Gate Leeward Gate1 - 32.284680 - -64.850045 + 57.6708220 + 11.8433900 Leeward Gate2 - 32.280164 - -64.847591 + 57.6708220 + 11.8433900 Windward Gate Windward Gate1 - 32.309693 - -64.835249 + 57.6650170 + 11.8279170 Windward Gate2 - 32.308046 - -64.831785 + 57.6650170 + 11.8279170 Finish Finish1 - 32.317379 - -64.839291 + 57.6715240 + 11.8444950 Finish2 - 32.317257 - -64.836260 + 57.6715240 + 11.8444950 From 749c6b7fefa1f65518ee91fc5b275ef66183b4bc Mon Sep 17 00:00:00 2001 From: Calum Date: Wed, 26 Apr 2017 17:18:33 +1200 Subject: [PATCH 27/97] Fixed bugs caused by horizontally scaling maps --- .../seng302/controllers/CanvasController.java | 86 ++++++++++++------- src/main/resources/config/course.xml | 16 ++-- src/main/resources/config/courseAlt.xml | 72 ++++++++++++++++ 3 files changed, 135 insertions(+), 39 deletions(-) create mode 100644 src/main/resources/config/courseAlt.xml diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index a1883304..41ae6ddf 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -53,8 +53,8 @@ public class CanvasController { private Mark minLonPoint; private Mark maxLatPoint; private Mark maxLonPoint; - private int referencePointX; - private int referencePointY; + private double referencePointX; + private double referencePointY; private double metersToPixels; public AnimationTimer timer; @@ -303,6 +303,13 @@ public class CanvasController { drawGateMark((GateMark) mark); } } + System.out.println("MIN/MAX POINTS"); + System.out.println(minLatPoint.getName() + " " + minLatPoint.getX() + " " + minLatPoint.getY()); + System.out.println(maxLatPoint.getName() + " " + maxLatPoint.getX() + " " + maxLatPoint.getY()); + System.out.println(minLonPoint.getName() + " " + minLonPoint.getX() + " " + minLonPoint.getY()); + System.out.println(maxLonPoint.getName() + " " + maxLonPoint.getX() + " " + maxLonPoint.getY()); + System.out.println(referencePointX); + System.out.println(referencePointY); } /** @@ -312,6 +319,7 @@ public class CanvasController { */ private void drawSingleMark(SingleMark singleMark, Color color) { gc.setFill(color); + System.out.println("DRAWING " + singleMark.getName() + " at " + singleMark.getX() + ", " + singleMark.getY()); gc.fillOval(singleMark.getX(), singleMark.getY(),MARK_SIZE,MARK_SIZE); } @@ -388,6 +396,16 @@ public class CanvasController { Collections.reverse(sortedPoints); minLonPoint = sortedPoints.get(0); maxLonPoint = sortedPoints.get(sortedPoints.size()-1); + System.out.println("ALL POINTS"); + for (Mark m : sortedPoints) + { + System.out.println(m.getName() + " " + m.getLatitude() + " " + m.getLongitude()); + } + System.out.println("MIN/MAX POINTS"); + System.out.println(minLatPoint.getName() + " " + minLatPoint.getLatitude() + " " + minLatPoint.getLongitude()); + System.out.println(maxLatPoint.getName() + " " + maxLatPoint.getLatitude() + " " + maxLatPoint.getLongitude()); + System.out.println(minLonPoint.getName() + " " + minLonPoint.getLatitude() + " " + minLonPoint.getLongitude()); + System.out.println(maxLonPoint.getName() + " " + maxLonPoint.getLatitude() + " " + maxLonPoint.getLongitude()); } /** @@ -399,33 +417,41 @@ public class CanvasController { private void calculateReferencePointLocation (double minLonToMaxLon) { Mark referencePoint = minLatPoint; double referenceAngle; - double mapWidth = canvas.getWidth(); - double mapHeight = canvas.getHeight(); + //double mapWidth = canvas.getWidth(); + //double mapHeight = canvas.getHeight(); if (scaleDirection == ScaleDirection.HORIZONTAL) { - referenceAngle = Mark.calculateHeadingRad(referencePoint, minLonPoint) - (Math.PI * (3/4)); - referencePointX = LHS_BUFFER + (int) Math.round(distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint)); + System.out.println("HORIZONTAL"); + System.out.println("ref angle " + Mark.calculateHeadingRad(referencePoint, minLonPoint)); + //referenceAngle = Mark.calculateHeadingRad(referencePoint, minLonPoint) - (Math.PI * (3/4)); + referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint)); + referencePointX = LHS_BUFFER + distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint); - referenceAngle = Mark.calculateHeadingRad(referencePoint, maxLatPoint); - if (referenceAngle > (Math.PI / 2)) { - referenceAngle = (Math.PI * 2) - referenceAngle; - } - referencePointY = (int) Math.round(mapHeight - (TOP_BUFFER + BOT_BUFFER)); - referencePointY -= (int) Math.round(distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint)); - referencePointY = (int) Math.round(referencePointY / 2d); + //referenceAngle = Mark.calculateHeadingRad(referencePoint, maxLatPoint); + //if (referenceAngle > Math.PI) { + // referenceAngle = (Math.PI * 2) - referenceAngle; + //} + referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, maxLatPoint)); + referencePointY = CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER); + referencePointY -= distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint); + referencePointY = referencePointY / 2; referencePointY += TOP_BUFFER; - referencePointY += (int) Math.round(distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint)); + referencePointY += distanceScaleFactor * Math.cos(referenceAngle) * Mark.calculateDistance(referencePoint, maxLatPoint); } else { - referencePointY = (int) Math.round(mapHeight - BOT_BUFFER); + System.out.println("VERTICAL"); + referencePointY = CANVAS_HEIGHT - BOT_BUFFER; - referenceAngle = (Math.PI * 2) - Mark.calculateHeadingRad(referencePoint, minLonPoint); + //referenceAngle = (Math.PI * 2) - Mark.calculateHeadingRad(referencePoint, minLonPoint); + referenceAngle = Math.abs(Mark.calculateHeadingRad(referencePoint, minLonPoint)); referencePointX = LHS_BUFFER; - referencePointX += (int) Math.round(distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint)); - referencePointX += (int) Math.round(((mapWidth - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2); + referencePointX += distanceScaleFactor * Math.sin(referenceAngle) * Mark.calculateDistance(referencePoint, minLonPoint); + referencePointX += ((CANVAS_WIDTH - (LHS_BUFFER + RHS_BUFFER)) - (minLonToMaxLon * distanceScaleFactor)) / 2; } - referencePoint.setX(referencePointX); - referencePoint.setY(referencePointY); + referencePointX = Math.round(referencePointX); + referencePointY = Math.round(referencePointY); + referencePoint.setX((int) referencePointX); + referencePoint.setY((int) referencePointY); } /** @@ -433,9 +459,10 @@ public class CanvasController { * Returns the max horizontal distance of the map. */ private double scaleRaceExtremities () { - double vertAngle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint); - if (vertAngle > Math.PI) - vertAngle = (2 * Math.PI) - vertAngle; + //double vertAngle = Mark.calculateHeadingRad(minLatPoint, maxLatPoint); + double vertAngle = Math.abs(Mark.calculateHeadingRad(minLatPoint, maxLatPoint)); +// if (vertAngle > Math.PI) +// vertAngle = (2 * Math.PI) - vertAngle; double vertDistance = Math.cos(vertAngle) * Mark.calculateDistance(minLatPoint, maxLatPoint); double horiAngle = Mark.calculateHeadingRad(minLonPoint, maxLonPoint); @@ -445,10 +472,10 @@ public class CanvasController { horiAngle = horiAngle - (Math.PI / 2); double horiDistance = Math.cos(horiAngle) * Mark.calculateDistance(minLonPoint, maxLonPoint); - double vertScale = (canvas.getHeight() - (TOP_BUFFER + BOT_BUFFER)) / vertDistance; + double vertScale = (CANVAS_HEIGHT - (TOP_BUFFER + BOT_BUFFER)) / vertDistance; - if ((horiDistance * vertScale) > (canvas.getWidth() - (RHS_BUFFER + LHS_BUFFER))) { - distanceScaleFactor = (canvas.getWidth() - (RHS_BUFFER + LHS_BUFFER)) / horiDistance; + if ((horiDistance * vertScale) > (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER))) { + distanceScaleFactor = (CANVAS_WIDTH - (RHS_BUFFER + LHS_BUFFER)) / horiDistance; scaleDirection = ScaleDirection.HORIZONTAL; } else { distanceScaleFactor = vertScale; @@ -495,21 +522,18 @@ public class CanvasController { private Point2D findScaledXY (double latA, double lonA, double latB, double lonB) { double distanceFromReference; double angleFromReference; - int yAxisLocation; - int xAxisLocation; + int xAxisLocation = (int) referencePointX; + int yAxisLocation = (int) referencePointY; angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB); distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB); if (angleFromReference > (Math.PI / 2)) { angleFromReference = (Math.PI * 2) - angleFromReference; - xAxisLocation = referencePointX; xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); } else { - xAxisLocation = referencePointX; xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); } - yAxisLocation = referencePointY; yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); return new Point2D(xAxisLocation, yAxisLocation); diff --git a/src/main/resources/config/course.xml b/src/main/resources/config/course.xml index 90e67b7f..e11a24cf 100644 --- a/src/main/resources/config/course.xml +++ b/src/main/resources/config/course.xml @@ -11,8 +11,8 @@ Start2 - 57.6703330 - 11.8278330 + 57.6706330 + 11.8281330 @@ -29,8 +29,8 @@ Leeward Gate2 - 57.6708220 - 11.8433900 + 57.6711220 + 11.8436900 @@ -42,8 +42,8 @@ Windward Gate2 - 57.6650170 - 11.8279170 + 57.6653170 + 11.8282170 @@ -55,8 +55,8 @@ Finish2 - 57.6715240 - 11.8444950 + 57.6718240 + 11.8447950 diff --git a/src/main/resources/config/courseAlt.xml b/src/main/resources/config/courseAlt.xml new file mode 100644 index 00000000..c0f83b32 --- /dev/null +++ b/src/main/resources/config/courseAlt.xml @@ -0,0 +1,72 @@ + + + + + + Start + + Start1 + 32.296577 + -64.854304 + + + Start2 + 32.293771 + -64.855242 + + + + Mid Mark + 32.293039 + -64.843983 + + + Leeward Gate + + Leeward Gate1 + 32.284680 + -64.850045 + + + Leeward Gate2 + 32.280164 + -64.847591 + + + + Windward Gate + + Windward Gate1 + 32.309693 + -64.835249 + + + Windward Gate2 + 32.308046 + -64.831785 + + + + Finish + + Finish1 + 32.317379 + -64.839291 + + + Finish2 + 32.317257 + -64.836260 + + + + + Start + Mid Mark + Leeward Gate + Windward Gate + Leeward Gate + Finish + + + From c776d22941170ec9ba7a4b434bc9e1c7837e2e5b Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Wed, 26 Apr 2017 18:45:58 +1200 Subject: [PATCH 28/97] Linking up course stream with visualiser. Boats moving, and course drawing. Boats however are not moving as intended. Needs to be fixed/looked into. #story[820] #pair[kre39,cir27] --- .../seng302/controllers/CanvasController.java | 138 ++++++++++++------ .../controllers/RaceViewController.java | 5 +- src/main/java/seng302/models/Boat.java | 21 ++- src/main/java/seng302/models/BoatGroup.java | 13 ++ .../seng302/models/parsers/StreamPacket.java | 10 +- .../seng302/models/parsers/StreamParser.java | 66 +++++++-- .../models/parsers/StreamReceiver.java | 49 +++++-- .../seng302/models/parsers/TeamsParser.java | 3 +- src/test/java/seng302/BoatTest.java | 2 +- 9 files changed, 222 insertions(+), 85 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 41ae6ddf..117a88fc 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -4,6 +4,7 @@ import javafx.animation.*; import javafx.beans.property.SimpleDoubleProperty; import javafx.fxml.FXML; import javafx.geometry.Point2D; +import javafx.geometry.Point3D; import javafx.scene.Group; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; @@ -18,6 +19,8 @@ import seng302.models.mark.GateMark; import seng302.models.mark.Mark; import seng302.models.mark.MarkType; import seng302.models.mark.SingleMark; +import seng302.models.parsers.StreamParser; +import seng302.models.parsers.StreamReceiver; import java.sql.Time; import java.util.*; @@ -132,8 +135,12 @@ public class CanvasController { //}catch (Exception e) { // e.printStackTrace(); //} - timer = new AnimationTimer() { + StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1"); +// StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread1"); + sr.start(); + + timer = new AnimationTimer() { private int countdown = 60; private int[] currentRaceMarker = {1, 1, 1, 1, 1, 1}; List marks = raceViewController.getRace().getCourse(); @@ -144,44 +151,56 @@ public class CanvasController { boolean descending; boolean leftToRight; int boatIndex = 0; + Mark nextMark; - if (countdown == 0) { + //if (countdown == 0) { for (BoatGroup boatGroup : boatGroups) { - if (currentRaceMarker[boatIndex] < marks.size()) { - if (currentRaceMarker[boatIndex] == 6) { - int debugLine = 4; - } + //if (currentRaceMarker[boatIndex] < marks.size()) { + //if (currentRaceMarker[boatIndex] == 6) { + // int debugLine = 4; + //} double xb4 = boatGroup.getLayoutX(); double yb4 = boatGroup.getLayoutY(); nextMark = marks.get(currentRaceMarker[boatIndex]); - descending = nextMark.getY() > boatGroup.getLayoutY(); - leftToRight = nextMark.getX() < boatGroup.getLayoutX(); + //descending = nextMark.getY() > boatGroup.getLayoutY(); + //leftToRight = nextMark.getX() < boatGroup.getLayoutX(); - boatGroup.updatePosition(1000 / 60); -// Point2D p = latLonToXY(lat, lon); -// boatGroup.setDestination(p.getX(), p.getY()); - if (descending && nextMark.getY() < boatGroup.getLayoutY()) { - currentRaceMarker[boatIndex]++; - boatGroup.setDestination( - marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() - ); - } else if (!descending && nextMark.getY() > boatGroup.getLayoutY()) { - currentRaceMarker[boatIndex]++; - boatGroup.setDestination( - marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() - ); - } else if (leftToRight && nextMark.getX() > boatGroup.getLayoutX()) { - currentRaceMarker[boatIndex]++; - boatGroup.setDestination( - marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() - ); - } else if (!leftToRight && nextMark.getX() < boatGroup.getLayoutX()) { - currentRaceMarker[boatIndex]++; - boatGroup.setDestination( - marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() - ); + boatGroup.updatePosition(1000 / 6); + Point3D p = StreamParser.boatPositions.get((long)boatGroup.getBoat().getId()); + //System.out.println("boatGroup = " + boatGroup.getBoat().getId()); + //System.out.println("StreamParser.boatPositions.toString() = " + StreamParser.boatPositions.toString()); + if (p != null) { + Point2D p2d = latLonToXY(p.getX(), p.getY()); + System.out.println("p2d = " + p2d); + if (!boatGroup.getBoat().isSamePos(p2d)) { + //System.out.println("p.toString() = " + p.toString()); + double heading = 360.0 / 0xffff * p.getZ(); + System.out.println("heading = " + heading); + boatGroup.setDestination(p.getX(), p.getY(), heading); + } } +// if (descending && nextMark.getY() < boatGroup.getLayoutY()) { +// currentRaceMarker[boatIndex]++; +// boatGroup.setDestination( +// marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() +// ); +// } else if (!descending && nextMark.getY() > boatGroup.getLayoutY()) { +// currentRaceMarker[boatIndex]++; +// boatGroup.setDestination( +// marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() +// ); +// } else if (leftToRight && nextMark.getX() > boatGroup.getLayoutX()) { +// currentRaceMarker[boatIndex]++; +// boatGroup.setDestination( +// marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() +// ); +// } else if (!leftToRight && nextMark.getX() < boatGroup.getLayoutX()) { +// currentRaceMarker[boatIndex]++; +// boatGroup.setDestination( +// marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() +// ); +// } double xnew = boatGroup.getLayoutX(); double ynew = boatGroup.getLayoutY(); @@ -190,18 +209,19 @@ public class CanvasController { raceFinished = false; boatIndex++; } - } - if (raceFinished) { - System.out.println("DONZEO LADS"); - this.stop(); - } - } else { - countdown--; - } + //} + //if (raceFinished) { + // System.out.println("DONZEO LADS"); + // this.stop(); + //} + //} else { + // countdown--; + //} } }; - for (Mark m : raceViewController.getRace().getCourse()) + for (Mark m : raceViewController.getRace().getCourse()) { System.out.println(m.getName()); + } //timer.start(); } @@ -261,7 +281,7 @@ public class CanvasController { for (Boat boat : boats) { BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); boatGroup.moveBoatTo(startingX, startingY, 0d); - boatGroup.setDestination(firstMarkX, firstMarkY); +// boatGroup.setDestination(firstMarkX, firstMarkY); boatGroup.forceRotation(); group.getChildren().add(boatGroup); boatGroups.add(boatGroup); @@ -515,6 +535,7 @@ public class CanvasController { } private Point2D findScaledXY (Mark unscaled) { + System.out.println("unscaled.getName() = " + unscaled.getName()); return findScaledXY (minLatPoint.getLatitude(), minLatPoint.getLongitude(), unscaled.getLatitude(), unscaled.getLongitude()); } @@ -527,15 +548,36 @@ public class CanvasController { angleFromReference = Mark.calculateHeadingRad(latA, lonA, latB, lonB); distanceFromReference = Mark.calculateDistance(latA, lonA, latB, lonB); - - if (angleFromReference > (Math.PI / 2)) { - angleFromReference = (Math.PI * 2) - angleFromReference; - xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); - } else { + if (angleFromReference >= 0 && angleFromReference <= Math.PI / 2) { + //System.out.println("1"); xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + } else if (angleFromReference >= 0) { + //System.out.println("2"); + angleFromReference = angleFromReference - Math.PI / 2; + xAxisLocation += (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { + //System.out.println("3"); + System.out.println(distanceFromReference); + angleFromReference = Math.abs(angleFromReference); + System.out.println(Math.cos(angleFromReference) * distanceFromReference); + xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); + yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + } else { + //System.out.println("4"); + angleFromReference = Math.abs(angleFromReference) - Math.PI / 2; + xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); } - yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); - +// if (angleFromReference > (Math.PI / 2)) { +// angleFromReference = (Math.PI * 2) - angleFromReference; +// xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); +// } else { +// xAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); +// } +// yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); + //System.out.println(xAxisLocation + " *** " + yAxisLocation); return new Point2D(xAxisLocation, yAxisLocation); } diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index 8acfba2b..e781c8d3 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -25,7 +25,7 @@ import java.util.*; /** * Created by ptg19 on 29/03/17. */ -public class RaceViewController { +public class RaceViewController extends Thread{ @FXML private VBox positionVbox; @FXML @@ -48,6 +48,7 @@ public class RaceViewController { private Race race; public void initialize() { + RaceController raceController = new RaceController(); raceController.initializeRace(); race = raceController.getRace(); @@ -73,6 +74,8 @@ public class RaceViewController { includedCanvasController.timer.start(); } + + private void initializeSettings(){ displayAnnotations = true; displayFps = true; diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java index 004ddc57..fda8441b 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -1,5 +1,6 @@ package seng302.models; +import javafx.geometry.Point2D; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; @@ -19,6 +20,8 @@ public class Boat { private double heading; private int markLastPast; private String shortName; + private int id; + private Point2D currentPos; public Boat(String teamName) { this.teamName = teamName; @@ -26,6 +29,16 @@ public class Boat { this.lat = 0.0; this.lon = 0.0; this.shortName = ""; + currentPos = null; + } + + public boolean isSamePos(Point2D newPos){ + if(newPos.equals(currentPos)){ + return true; + } else { + currentPos = newPos; + return false; + } } /** @@ -35,10 +48,13 @@ public class Boat { * @param boatVelocity The speed of the boat in meters/second * @param shortName A shorter version of the teams name */ - public Boat(String teamName, double boatVelocity, String shortName) { + public Boat(String teamName, double boatVelocity, String shortName, int id) { this.teamName = teamName; this.velocity = boatVelocity; this.shortName = shortName; + this.id = id; + currentPos = null; + } /** @@ -133,4 +149,7 @@ public class Boat { return this.shortName; } + public int getId() { + return id; + } } \ 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 f304ba90..82912d08 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -117,6 +117,15 @@ public class BoatGroup extends Group{ moveBy(dx, dy, rotation); } + public void setDestination (double newXValue, double newYValue, double rotation) { + this.pixelVelocityX = (newXValue - super.getLayoutX()) / expectedUpdateInterval; + this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; + //this.destinationX = newXValue; + //this.destinationY = newYValue; + this.rotationalGoal = rotation; + this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; + } + public void setDestination (double newXValue, double newYValue) { this.pixelVelocityX = (newXValue - super.getLayoutX()) / expectedUpdateInterval; this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; @@ -174,4 +183,8 @@ public class BoatGroup extends Group{ super.getChildren().get(2).setVisible(false); super.getChildren().get(3).setVisible(false); } + + public Boat getBoat() { + return boat; + } } diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index 87f7bc63..444d0344 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -19,11 +19,11 @@ public class StreamPacket { this.payload = payload; // System.out.println("type = " + this.type.toString()); //switch the packet type to deal with what ever specific packet you want to deal with - if (this.type == PacketType.XML_MESSAGE){ - System.out.println("--------"); - System.out.println(new String(payload)); - StreamParser.parsePacket(this); - } +// if (this.type == PacketType.XML_MESSAGE){ +// System.out.println("--------"); +// System.out.println(new String(payload)); +// StreamParser.parsePacket(this); +// } } PacketType getType() { diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index bdc3ace0..438bc5b9 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -1,6 +1,8 @@ package seng302.models.parsers; +import javafx.geometry.Point2D; +import javafx.geometry.Point3D; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -13,13 +15,48 @@ import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; /** * Created by kre39 on 23/04/17. */ -public class StreamParser { +public class StreamParser extends Thread{ + public static ConcurrentHashMap boatPositions = new ConcurrentHashMap<>(); private static ArrayList boat_IDS = new ArrayList<>(); + private String threadName; + private Thread t; + + StreamParser(String threadName){ + this.threadName = threadName; + } + + public void run(){ + try { + while (StreamReceiver.packetBuffer.size() <= 1) { + Thread.sleep(1); + } + StreamPacket packet = StreamReceiver.packetBuffer.take(); + while (packet != null){ + parsePacket(packet); + Thread.sleep(10); + packet = StreamReceiver.packetBuffer.take(); + } + } catch (Exception e){ + e.printStackTrace(); + } + } + + public void start () { + System.out.println("Starting " + threadName ); + if (t == null) { + t = new Thread (this, threadName); + t.start (); + } + } + static void parsePacket(StreamPacket packet) { switch (packet.getType()){ case HEARTBEAT: @@ -65,7 +102,7 @@ public class StreamParser { private static void extractHeartBeat(StreamPacket packet) { long heartbeat = bytesToLong(packet.getPayload()); - System.out.println("Heartbeat: " + heartbeat); +// System.out.println("Heartbeat: " + heartbeat); } @@ -75,7 +112,7 @@ public class StreamParser { long currentTime = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); long raceId = bytesToLong(Arrays.copyOfRange(payload,7,11)); int raceStatus = payload[11]; - System.out.println("raceStatus = " + raceStatus); +// System.out.println("raceStatus = " + raceStatus); long expectedStartTime = extractTimeStamp(Arrays.copyOfRange(payload,12,18), 6); long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); @@ -172,7 +209,7 @@ public class StreamParser { long subjectId = bytesToLong(Arrays.copyOfRange(payload,9,13)); long incidentId = bytesToLong(Arrays.copyOfRange(payload,13,17)); int eventId = payload[17]; - System.out.println("eventId = " + eventId); +// System.out.println("eventId = " + eventId); } private static void extractChatterText(StreamPacket packet){ @@ -191,27 +228,26 @@ public class StreamParser { byte[] latBytes = Arrays.copyOfRange(payload,16,20); byte[] lonBytes = Arrays.copyOfRange(payload,20,24); byte[] boatIdBytes = Arrays.copyOfRange(payload,7,11); + byte[] headingBytes = Arrays.copyOfRange(payload,28,30); long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); // int boatSeq = ByteBuffer.wrap(seqBytes).getInt(); long seq = bytesToLong(seqBytes); long boatId = bytesToLong(boatIdBytes); long lat = bytesToLong(latBytes); long lon = bytesToLong(lonBytes); + long heading = bytesToLong(headingBytes); if ((int)deviceType == 1){ - if (!boat_IDS.contains(boatId)){ - boat_IDS.add(boatId); - } - System.out.println("boatId = " + boatId); - System.out.println("deviceType = " + (long)deviceType); - System.out.println("seq = " + seq); +// System.out.println("boatId = " + boatId); +// System.out.println("deviceType = " + (long)deviceType); +// System.out.println("seq = " + seq); //needs to be validated - System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31))); - System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31))); + Point3D point = new Point3D(((180d * (double)lat)/Math.pow(2,31)),((180d *(double)lon)/Math.pow(2,31)),(double)heading); + boatPositions.putIfAbsent(boatId, point); + boatPositions.replace(boatId, point); +// System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31))); +// System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31))); } - - System.out.println("boat_IDS = " + boat_IDS); - System.out.println("boat_IDS = " + boat_IDS.size()); } diff --git a/src/main/java/seng302/models/parsers/StreamReceiver.java b/src/main/java/seng302/models/parsers/StreamReceiver.java index d5127ce4..e357101d 100644 --- a/src/main/java/seng302/models/parsers/StreamReceiver.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -12,22 +12,47 @@ import java.util.zip.CRC32; import java.util.zip.Checksum; -public class StreamReceiver { +public class StreamReceiver extends Thread { private InputStream stream; private Socket host; private ByteArrayOutputStream crcBuffer; - public PriorityBlockingQueue packetBuffer; + private Thread t; + private String threadName; + public static PriorityBlockingQueue packetBuffer; - public StreamReceiver(String hostAddress, int hostPort, PriorityBlockingQueue packetBuffer) { + public StreamReceiver(String hostAddress, int hostPort, String threadName) { + this.threadName = threadName; try { host = new Socket(hostAddress, hostPort); } catch (IOException e) { e.printStackTrace(); System.exit(1); } - this.packetBuffer = packetBuffer; } + public void run(){ + PriorityBlockingQueue pq = new PriorityBlockingQueue<>(256, new Comparator() { + @Override + public int compare(StreamPacket s1, StreamPacket s2) { + return (int) (s1.getTimeStamp() - s2.getTimeStamp()); + } + }); + packetBuffer = pq; + connect(); + StreamParser streamParser = new StreamParser("TestThread2"); + + streamParser.start(); + } + + public void start () { + System.out.println("Starting " + threadName ); + if (t == null) { + t = new Thread (this, threadName); + t.start (); + } + } + + public void connect(){ try { stream = host.getInputStream(); @@ -121,15 +146,13 @@ public class StreamReceiver { } + + public static void main(String[] args) { - PriorityBlockingQueue pq = new PriorityBlockingQueue<>(256, new Comparator() { - @Override - public int compare(StreamPacket s1, StreamPacket s2) { - return (int) (s1.getTimeStamp() - s2.getTimeStamp()); - } - }); - StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, pq); -// StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, pq); - sr.connect(); + + StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1"); + //StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, pq); + sr.start(); + } } diff --git a/src/main/java/seng302/models/parsers/TeamsParser.java b/src/main/java/seng302/models/parsers/TeamsParser.java index 1b19724a..afa31410 100644 --- a/src/main/java/seng302/models/parsers/TeamsParser.java +++ b/src/main/java/seng302/models/parsers/TeamsParser.java @@ -27,7 +27,8 @@ public class TeamsParser extends FileParser { String name = element.getElementsByTagName("name").item(0).getTextContent(); String alias = element.getElementsByTagName("alias").item(0).getTextContent(); double velocity = Double.valueOf(element.getElementsByTagName("velocity").item(0).getTextContent()); - Boat boat = new Boat(name, velocity, alias); + int id = Integer.valueOf(element.getElementsByTagName("id").item(0).getTextContent()); + Boat boat = new Boat(name, velocity, alias, id); return boat; } else { throw new NoSuchElementException("Cannot generate a boat by given node"); diff --git a/src/test/java/seng302/BoatTest.java b/src/test/java/seng302/BoatTest.java index 0160bb8d..c415c046 100644 --- a/src/test/java/seng302/BoatTest.java +++ b/src/test/java/seng302/BoatTest.java @@ -26,7 +26,7 @@ public class BoatTest { @Test public void testSetVelocity() { - Boat boat1 = new Boat("Team 1", 29.0, ""); + Boat boat1 = new Boat("Team 1", 29.0, "", 100); assertEquals(boat1.getVelocity(), (double) 29.0, 1e-15); boat1.setVelocity(12.0); From 95bafdc0d1945fb95af8a20843f319e39f6631ff Mon Sep 17 00:00:00 2001 From: Calum Date: Wed, 26 Apr 2017 19:19:03 +1200 Subject: [PATCH 29/97] Fixed bug which caused boats to all travel to the same position. --- .../seng302/controllers/CanvasController.java | 18 +++++++--- src/main/java/seng302/models/BoatGroup.java | 34 ++++++++++++++++--- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 117a88fc..4e0aea4b 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -166,20 +166,28 @@ public class CanvasController { //descending = nextMark.getY() > boatGroup.getLayoutY(); //leftToRight = nextMark.getX() < boatGroup.getLayoutX(); - boatGroup.updatePosition(1000 / 6); + boatGroup.updatePosition(1000 / 60); Point3D p = StreamParser.boatPositions.get((long)boatGroup.getBoat().getId()); //System.out.println("boatGroup = " + boatGroup.getBoat().getId()); //System.out.println("StreamParser.boatPositions.toString() = " + StreamParser.boatPositions.toString()); if (p != null) { Point2D p2d = latLonToXY(p.getX(), p.getY()); - System.out.println("p2d = " + p2d); + //System.out.println("p2d = " + p2d); if (!boatGroup.getBoat().isSamePos(p2d)) { //System.out.println("p.toString() = " + p.toString()); double heading = 360.0 / 0xffff * p.getZ(); - System.out.println("heading = " + heading); - boatGroup.setDestination(p.getX(), p.getY(), heading); + //System.out.println("heading = " + heading); + + + + boatGroup.setDestination(p2d.getX(), p2d.getY(), heading); + + + + //boatGroup.setDestination(p2d.getX(), p2d.getY()); } } + // if (descending && nextMark.getY() < boatGroup.getLayoutY()) { // currentRaceMarker[boatIndex]++; // boatGroup.setDestination( @@ -281,7 +289,7 @@ public class CanvasController { for (Boat boat : boats) { BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); boatGroup.moveBoatTo(startingX, startingY, 0d); -// boatGroup.setDestination(firstMarkX, firstMarkY); + boatGroup.setDestination(firstMarkX, firstMarkY); boatGroup.forceRotation(); group.getChildren().add(boatGroup); boatGroups.add(boatGroup); diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 82912d08..1292e7e0 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -123,7 +123,29 @@ public class BoatGroup extends Group{ //this.destinationX = newXValue; //this.destinationY = newYValue; this.rotationalGoal = rotation; - this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; +// if (super.getLayoutY() >= newYValue && super.getLayoutX() <= newXValue) +// rotationalGoal = 90 - rotationalGoal; +// else if (super.getLayoutY() < newYValue && super.getLayoutX() <= newXValue) +// rotationalGoal = 90 + rotationalGoal; +// else if (super.getLayoutY() >= newYValue && super.getLayoutX() > newXValue) +// rotationalGoal = 270 + rotationalGoal; +// else +// rotationalGoal = 270 - rotationalGoal; +// if (Math.abs(360 - rotationalGoal + currentRotation) < Math.abs(rotationalGoal - currentRotation)) { +// System.out.println("ROTATE"); +// this.rotationalVelocity = (360 - rotationalGoal + currentRotation) / expectedUpdateInterval; +// } else { +// this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; +// } + if (Math.abs(rotationalGoal - currentRotation) > 180) { + if (rotationalGoal - currentRotation >= 0) { + this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; + } else { + this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; + } + } else { + this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; + } } public void setDestination (double newXValue, double newYValue) { @@ -146,10 +168,12 @@ public class BoatGroup extends Group{ rotationalGoal = 270 + rotationalGoal; else rotationalGoal = 270 - rotationalGoal; - // TODO: 25/04/2017 cir27 - Verify this logic is correct. Want to produce the shortest path. - if (Math.abs(360 - rotationalGoal + currentRotation) < Math.abs(rotationalGoal - currentRotation)) { - System.out.println("ROTATE"); - this.rotationalVelocity = (360 - rotationalGoal + currentRotation) / expectedUpdateInterval; + if (Math.abs(rotationalGoal - currentRotation) > 180) { + if (rotationalGoal - currentRotation >= 0) { + this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; + } else { + this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; + } } else { this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; } From eaff4c5aac8bfbf47e951691fa1ca41e81a7c421 Mon Sep 17 00:00:00 2001 From: cir27 Date: Wed, 26 Apr 2017 21:16:22 +1200 Subject: [PATCH 30/97] Added abstract class for all javafx object that a displayed during race. Began refactoring of mark implementation to be a subclass of the aforementioned abstract class. --- .../seng302/controllers/CanvasController.java | 4 +- src/main/java/seng302/models/Boat.java | 12 -- src/main/java/seng302/models/BoatGroup.java | 33 ++--- src/main/java/seng302/models/RaceObject.java | 32 +++++ src/main/java/seng302/models/mark/Mark.java | 11 ++ .../java/seng302/models/mark/MarkGroup.java | 114 ++++++++++++++++++ .../seng302/models/parsers/StreamPacket.java | 12 +- 7 files changed, 183 insertions(+), 35 deletions(-) create mode 100644 src/main/java/seng302/models/RaceObject.java create mode 100644 src/main/java/seng302/models/mark/MarkGroup.java diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 4e0aea4b..b5d76432 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -173,7 +173,7 @@ public class CanvasController { if (p != null) { Point2D p2d = latLonToXY(p.getX(), p.getY()); //System.out.println("p2d = " + p2d); - if (!boatGroup.getBoat().isSamePos(p2d)) { + if (!boatGroup.isSamePos(p2d)) { //System.out.println("p.toString() = " + p.toString()); double heading = 360.0 / 0xffff * p.getZ(); //System.out.println("heading = " + heading); @@ -288,7 +288,7 @@ public class CanvasController { for (Boat boat : boats) { BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); - boatGroup.moveBoatTo(startingX, startingY, 0d); + boatGroup.moveTo(startingX, startingY, 0d); boatGroup.setDestination(firstMarkX, firstMarkY); boatGroup.forceRotation(); group.getChildren().add(boatGroup); diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java index fda8441b..d275091f 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -21,7 +21,6 @@ public class Boat { private int markLastPast; private String shortName; private int id; - private Point2D currentPos; public Boat(String teamName) { this.teamName = teamName; @@ -29,17 +28,8 @@ public class Boat { this.lat = 0.0; this.lon = 0.0; this.shortName = ""; - currentPos = null; } - public boolean isSamePos(Point2D newPos){ - if(newPos.equals(currentPos)){ - return true; - } else { - currentPos = newPos; - return false; - } - } /** * Represents a boat in the race. @@ -53,8 +43,6 @@ public class Boat { this.velocity = boatVelocity; this.shortName = shortName; this.id = id; - currentPos = null; - } /** diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 1292e7e0..b46f4783 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -1,6 +1,5 @@ package seng302.models; -import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.shape.Polygon; @@ -11,7 +10,7 @@ import javafx.scene.transform.Translate; /** * Created by CJIRWIN on 25/04/2017. */ -public class BoatGroup extends Group{ +public class BoatGroup extends RaceObject{ private static final double TEAMNAME_X_OFFSET = 15d; private static final double TEAMNAME_Y_OFFSET = -20d; @@ -25,21 +24,13 @@ public class BoatGroup extends Group{ private Boat boat; - private double rotationalGoal; - private double currentRotation; - private double rotationalVelocity; - private double pixelVelocityX; - private double pixelVelocityY; - public BoatGroup (Boat boat, Color color){ - super(); this.boat = boat; initChildren(color); } public BoatGroup (Boat boat, Color color, double... points) { - super(); initChildren(color, points); } @@ -87,7 +78,7 @@ public class BoatGroup extends Group{ * @param dx The amount to move the X coordinate by * @param dy The amount to move the Y coordinate by */ - void moveBy(Double dx, Double dy, Double rotation) { + void moveBy(double dx, double dy, double rotation) { super.setLayoutX(super.getLayoutX() + dx); super.setLayoutY(super.getLayoutY() + dy); rotateBoat(rotation); @@ -98,11 +89,15 @@ public class BoatGroup extends Group{ * @param x The X coordinate to move the boat to * @param y The Y coordinate to move the boat to */ - public void moveBoatTo(Double x, Double y, Double rotation) { + public void moveTo (double x, double y, double rotation) { + super.currentRotation = 0; + rotateBoat(rotation); + moveTo(x, y); + } + + public void moveTo (double x, double y) { super.setLayoutX(x); super.setLayoutY(y); - currentRotation = 0; - rotateBoat(rotation); } public void updatePosition (double timeInterval) { @@ -202,7 +197,7 @@ public class BoatGroup extends Group{ rotateBoat (rotationalGoal - currentRotation); } - public void toogleAnnotations () { + public void toggleAnnotations () { super.getChildren().get(1).setVisible(false); super.getChildren().get(2).setVisible(false); super.getChildren().get(3).setVisible(false); @@ -211,4 +206,12 @@ public class BoatGroup extends Group{ public Boat getBoat() { return boat; } + + public boolean hasRaceId (int... raceIds) { + for (int id : raceIds) { + if (id == boat.getId()) + return true; + } + return false; + } } diff --git a/src/main/java/seng302/models/RaceObject.java b/src/main/java/seng302/models/RaceObject.java new file mode 100644 index 00000000..ab831025 --- /dev/null +++ b/src/main/java/seng302/models/RaceObject.java @@ -0,0 +1,32 @@ +package seng302.models; + +import javafx.geometry.Point2D; +import javafx.scene.Group; + +/** + * Created by CJIRWIN on 26/04/2017. + */ +public abstract class RaceObject extends Group { + + double rotationalGoal; + double currentRotation; + double rotationalVelocity; + double pixelVelocityX; + double pixelVelocityY; + + public boolean isSamePos (Point2D point) { + return point.getX() == super.getLayoutX() && point.getY() == super.getLayoutY(); + } + + public Point2D getPosition () { + return new Point2D(super.getLayoutX(), getLayoutY()); + } + public abstract void setDestination (double x, double y, double rotation); + public abstract void setDestination (double x, double y); + public abstract void updatePosition (double timeInterval); + public abstract void moveTo (double x, double y, double rotation); + public abstract void moveTo (double x, double y); + public abstract boolean hasRaceId (int... raceIds); + public abstract void toggleAnnotations (); + +} diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index 98af4057..57de9ac4 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -10,6 +10,7 @@ public abstract class Mark { private MarkType markType; private double latitude; private double longitude; + private int id; Integer xValue; Integer yValue; @@ -21,6 +22,7 @@ public abstract class Mark { public Mark (String name, MarkType markType) { this.name = name; this.markType = markType; + id = 0; } public Mark(String name, MarkType markType, double latitude, double longitude) { @@ -28,6 +30,7 @@ public abstract class Mark { this.markType = markType; this.latitude = latitude; this.longitude = longitude; + id = 0; } /** @@ -140,4 +143,12 @@ public abstract class Mark { this.yValue = y; } + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + } diff --git a/src/main/java/seng302/models/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java new file mode 100644 index 00000000..1a56b336 --- /dev/null +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -0,0 +1,114 @@ +package seng302.models.mark; + +import javafx.geometry.Point2D; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; +import javafx.scene.transform.Rotate; +import seng302.models.RaceObject; + +/** + * Created by CJIRWIN on 26/04/2017. + */ +public class MarkGroup extends RaceObject { + + private static int MARK_RADIUS = 5; + + private Mark mark; + private double pixelVelocityXM1; + private double pixelVelocityYM1; + private double pixelVelocityXM2; + private double pixelVelocityYM3; + + + public MarkGroup (Mark mark, Point2D... points) { + Color color = Color.BLACK; + if (mark.getName().equals("Start")){ + color = Color.GREEN; + } else if (mark.getName().equals("Finish")){ + color = Color.RED; + } + if (mark.getMarkType() == MarkType.SINGLE_MARK) { + super.getChildren().add(new Circle(0, 0, MARK_RADIUS, color)); + } else { + super.getChildren().add(new Circle(0, 0, MARK_RADIUS, color)); + super.getChildren().add( + new Circle( + points[1].getX() - points[0].getX(), + points[1].getY() - points[0].getY(), + MARK_RADIUS, + color + ) + ); + Line line = new Line( + 0, + 0, + points[1].getX() - points[0].getX(), + points[1].getY() - points[0].getY() + ); + line.setStrokeWidth(2); + if (mark.getMarkType() == MarkType.OPEN_GATE) { + line.getStrokeDashArray().addAll(3d, 5d); + } + super.getChildren().add(line); + } + this.mark = mark; + moveTo(points[0].getX(), points[0].getY()); + } + + public void setDestination (double x, double y, double rotation) { + + } + + public void setDestination (double x, double y) { + + } + + public void setMarkDestination (int markId, double x, double y) { + + } + public void updatePosition (double timeInterval) { + + } + + public void moveTo (double x, double y, double rotation) { + moveTo(x, y); + super.getTransforms().clear(); + super.getTransforms().add( + new Rotate( + rotation, + super.getChildren().get(1).getLayoutX() - super.getChildren().get(0).getLayoutX(), + super.getChildren().get(1).getLayoutY() - super.getChildren().get(0).getLayoutY() + ) + ); + } + + public void moveTo (double x, double y) { + super.setLayoutX(x); + super.setLayoutY(y); + } + + public boolean hasRaceId (int... raceIds) { + for (int id : raceIds) { + if (id == mark.getId()) + return true; + if (mark.getMarkType() != MarkType.SINGLE_MARK) { + if (id == ((GateMark) mark).getSingleMark1().getId() || id == ((GateMark) mark).getSingleMark2().getId()) + return true; + } + } + return false; + } + public void toggleAnnotations () { + + } + + public static int getMarkRadius() { + return MARK_RADIUS; + } + + public static void setMarkRadius(int markRadius) { + MARK_RADIUS = markRadius; + } + +} diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index 444d0344..e4b929ca 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -17,13 +17,13 @@ public class StreamPacket { this.messageLength = messageLength; this.timeStamp = timeStamp; this.payload = payload; -// System.out.println("type = " + this.type.toString()); + System.out.println("type = " + this.type.toString()); //switch the packet type to deal with what ever specific packet you want to deal with -// if (this.type == PacketType.XML_MESSAGE){ -// System.out.println("--------"); -// System.out.println(new String(payload)); -// StreamParser.parsePacket(this); -// } + if (this.type == PacketType.XML_MESSAGE){ + System.out.println("--------"); + System.out.println(new String(payload)); + StreamParser.parsePacket(this); + } } PacketType getType() { From bc31987f96f7d629ccf9b81f3a04d6b0c067436a Mon Sep 17 00:00:00 2001 From: Michael Rausch Date: Wed, 26 Apr 2017 22:38:39 +1200 Subject: [PATCH 31/97] Added Boat location messages to the mock streaming data interface - Added static methods to convert between binary packed lat/longs and floating point numbers Tags: #story[829] --- .../server/messages/BoatLocationMessage.java | 57 ++++++++++++++++- .../server/messages/MarkRoundingMessage.java | 62 +++++++++++++++++++ .../seng302/server/messages/MarkType.java | 20 ++++++ .../server/messages/RoundingBoatStatus.java | 21 +++++++ .../seng302/server/messages/RoundingSide.java | 20 ++++++ .../java/seng302/server/TestConversions.java | 35 +++++++++++ 6 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 src/main/java/seng302/server/messages/MarkRoundingMessage.java create mode 100644 src/main/java/seng302/server/messages/MarkType.java create mode 100644 src/main/java/seng302/server/messages/RoundingBoatStatus.java create mode 100644 src/main/java/seng302/server/messages/RoundingSide.java create mode 100644 src/test/java/seng302/server/TestConversions.java diff --git a/src/main/java/seng302/server/messages/BoatLocationMessage.java b/src/main/java/seng302/server/messages/BoatLocationMessage.java index a475128b..4b914699 100644 --- a/src/main/java/seng302/server/messages/BoatLocationMessage.java +++ b/src/main/java/seng302/server/messages/BoatLocationMessage.java @@ -65,11 +65,66 @@ public class BoatLocationMessage extends Message { setHeader(new Header(MessageType.BOAT_LOCATION, 1, (short) getSize())); } + /** + * Convert binary latitude or longitude to floating point number + * @param binaryPackedLatLon Binary packed lat OR lon + * @return Floating point lat/lon + */ + public static double binaryPackedToLatLon(long binaryPackedLatLon){ + return (double)binaryPackedLatLon * 180.0 / 2147483648.0; + } + + /** + * Convert binary packed heading to floating point number + * @param binaryPackedHeading Binary packed heading + * @return heading as a decimal + */ + public static double binaryPackedHeadingToDouble(long binaryPackedHeading){ + return (double)binaryPackedHeading * 360.0 / 65536.0; + } + + /** + * Convert binary packed wind angle to floating point number + * @param binaryPackedWindAngle Binary packed wind angle + * @return wind angle as a decimal + */ + public static double binaryPackedWindAngleToDouble(long binaryPackedWindAngle){ + return (double)binaryPackedWindAngle*180.0/32768.0; + } + + /** + * Convert a latitude or longitude to a binary packed long + * @param latLon A floating point latitude/longitude + * @return A binary packed lat/lon + */ + public static long latLonToBinaryPackedLong(double latLon){ + return (long)((536870912 * latLon) / 45); + } + + /** + * Convert a heading to a binary packed long + * @param heading A floating point heading + * @return A binary packed heading + */ + public static long headingToBinaryPackedLong(double heading){ + return (long)((8192*heading)/45); + } + + /** + * Convert a wind angle to a binary packed long + * @param windAngle Floating point wind angle + * @return A binary packed wind angle + */ + public static long windAngleToBinaryPackedLong(double windAngle){ + return (long)((8192*windAngle)/45); + } + @Override public int getSize() { - return 56; + return MESSAGE_SIZE; } + @Override public void send(DataOutputStream outputStream) { allocateBuffer(); diff --git a/src/main/java/seng302/server/messages/MarkRoundingMessage.java b/src/main/java/seng302/server/messages/MarkRoundingMessage.java new file mode 100644 index 00000000..405c46ff --- /dev/null +++ b/src/main/java/seng302/server/messages/MarkRoundingMessage.java @@ -0,0 +1,62 @@ +package seng302.server.messages; + +import java.io.DataOutputStream; +import java.io.IOException; + +public class MarkRoundingMessage extends Message{ + private final long MESSAGE_VERSION_NUMBER = 1; + private final int MESSAGE_SIZE = 21; + + private long time; + private long ackNumber; + private long raceId; + private long sourceId; + private RoundingBoatStatus boatStatus; + private RoundingSide roundingSide; + private long markId; + + /** + * This message is sent when a boat passes a mark, start line, or finish line + * The purpose of this is to record the time when yachts cross marks + */ + public MarkRoundingMessage(int ackNumber, int raceId, int sourceId, RoundingBoatStatus roundingBoatStatus, + RoundingSide roundingSide, int markId){ + this.time = System.currentTimeMillis() / 1000L; + this.ackNumber = ackNumber; + this.raceId = raceId; + this.sourceId = sourceId; + this.boatStatus = roundingBoatStatus; + this.roundingSide = roundingSide; + this.markId = markId; + + setHeader(new Header(MessageType.MARK_ROUNDING, 1, (short) getSize())); + } + + @Override + public int getSize() { + return MESSAGE_SIZE; + } + + @Override + public void send(DataOutputStream outputStream) { + allocateBuffer(); + writeHeaderToBuffer(); + + putByte((byte) MESSAGE_VERSION_NUMBER); + putInt((int) time, 6); + putInt((int) ackNumber, 2); + putInt((int) raceId, 4); + putInt((int) sourceId, 4); + putByte((byte) boatStatus.getCode()); + putByte((byte) roundingSide.getCode()); + putByte((byte) markId); + + writeCRC(); + + try { + outputStream.write(getBuffer().array()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/seng302/server/messages/MarkType.java b/src/main/java/seng302/server/messages/MarkType.java new file mode 100644 index 00000000..abbacc6f --- /dev/null +++ b/src/main/java/seng302/server/messages/MarkType.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * Types of marks boats can round + */ +public enum MarkType { + UNKNOWN(0), + ROUNDING_MARK(1), + GATE(2); + + private long code; + + MarkType(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/RoundingBoatStatus.java b/src/main/java/seng302/server/messages/RoundingBoatStatus.java new file mode 100644 index 00000000..32eb2447 --- /dev/null +++ b/src/main/java/seng302/server/messages/RoundingBoatStatus.java @@ -0,0 +1,21 @@ +package seng302.server.messages; + +/** + * The status of a boat rounding a mark + */ +public enum RoundingBoatStatus { + UNKNOWN(0), + RACING(1), + DSQ(2), + WITHDRAWN(3); + + private long code; + + RoundingBoatStatus(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/main/java/seng302/server/messages/RoundingSide.java b/src/main/java/seng302/server/messages/RoundingSide.java new file mode 100644 index 00000000..5cc4097c --- /dev/null +++ b/src/main/java/seng302/server/messages/RoundingSide.java @@ -0,0 +1,20 @@ +package seng302.server.messages; + +/** + * The side the boat rounded the mark + */ +public enum RoundingSide { + UNKNOWN(0), + PORT(1), + STARBOARD(2); + + private long code; + + RoundingSide(long code) { + this.code = code; + } + + public long getCode(){ + return code; + } +} diff --git a/src/test/java/seng302/server/TestConversions.java b/src/test/java/seng302/server/TestConversions.java new file mode 100644 index 00000000..91bf44b3 --- /dev/null +++ b/src/test/java/seng302/server/TestConversions.java @@ -0,0 +1,35 @@ +package seng302.server; + +import org.junit.Test; +import seng302.server.messages.BoatLocationMessage; + +import static junit.framework.TestCase.assertEquals; + +/** + * Test conversions used by the boat location messages + */ +public class TestConversions { + @Test + public void testLatLonConversion(){ + long binaryPacked = BoatLocationMessage.latLonToBinaryPackedLong(3232.323); + double original = BoatLocationMessage.binaryPackedToLatLon(binaryPacked); + + assertEquals(3232.323, original, 0.01); + } + + @Test + public void testWindAngleConversion(){ + long binaryPacked = BoatLocationMessage.windAngleToBinaryPackedLong(3232.323); + double original = BoatLocationMessage.binaryPackedWindAngleToDouble(binaryPacked); + + assertEquals(3232.323, original, 0.01); + } + + @Test + public void testHeadingConversion(){ + long binaryPacked = BoatLocationMessage.headingToBinaryPackedLong(3232.323); + double original = BoatLocationMessage.binaryPackedHeadingToDouble(binaryPacked); + + assertEquals(3232.323, original, 0.01); + } +} From 3bdc6ce5cc70c6255539b2125e5ac9c620d901f1 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Apr 2017 00:56:44 +1200 Subject: [PATCH 32/97] Created a new course parser to parse race xml file specified in AC35 spec. #story[828] --- .../java/seng302/models/mark/MarkType.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/seng302/models/mark/MarkType.java b/src/main/java/seng302/models/mark/MarkType.java index 3de5cba3..43366b1d 100644 --- a/src/main/java/seng302/models/mark/MarkType.java +++ b/src/main/java/seng302/models/mark/MarkType.java @@ -4,6 +4,33 @@ package seng302.models.mark; * To represent two types of mark * Created by Haoming Yin (hyi25) on 17/3/17. */ + + public enum MarkType { - SINGLE_MARK, GATE_MARK + + UNKNOWN(0), + ROUNDING_MARK(1), + GATE_MARK(2), + // above mark types are from AC35 spec. + + // more specific types for gate mark + WINDWARD(201), + LEEWARD(202), + START(203), + FINISH(204), + + // single_mark is from old team-13 code base, for compatibility, it has not + // been removed yet + SINGLE_MARK(5); + + private int type; + + MarkType(int markType) { + this.type = markType; + } + + public int getType() { + return this.type; + } + } From f6b7a3042f6f270eb8ecc76068738a715712ad02 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Apr 2017 01:03:37 +1200 Subject: [PATCH 33/97] Rewrote all kind of marks to fit marks specified in AC35 spec. - added compound mark - added corner - rewrote mark as a single mark - added rounding type enum #story[828] --- .../server/simulator/mark/CompoundMark.java | 57 +++++++++++++++++++ .../seng302/server/simulator/mark/Corner.java | 49 ++++++++++++++++ .../seng302/server/simulator/mark/Mark.java | 54 ++++++++++++++++++ .../server/simulator/mark/RoundingType.java | 28 +++++++++ 4 files changed, 188 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/mark/CompoundMark.java create mode 100644 src/main/java/seng302/server/simulator/mark/Corner.java create mode 100644 src/main/java/seng302/server/simulator/mark/Mark.java create mode 100644 src/main/java/seng302/server/simulator/mark/RoundingType.java diff --git a/src/main/java/seng302/server/simulator/mark/CompoundMark.java b/src/main/java/seng302/server/simulator/mark/CompoundMark.java new file mode 100644 index 00000000..59dc1f62 --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/CompoundMark.java @@ -0,0 +1,57 @@ +package seng302.server.simulator.mark; + +public class CompoundMark { + + private int markID; + private String name; + + private Mark mark1; + private Mark mark2; + + public CompoundMark(int markID, String name) { + this.markID = markID; + this.name = name; + } + + public void addMark(int seqId, Mark mark) { + if (seqId == 1) { + setMark1(mark); + } else if (seqId == 2) { + setMark2(mark); + } + } + + public int getMarkID() { + return markID; + } + + public void setMarkID(int markID) { + this.markID = markID; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Mark getMark1() { + return mark1; + } + + public void setMark1(Mark mark1) { + this.mark1 = mark1; + mark1.setSeqID(1); + } + + public Mark getMark2() { + return mark2; + } + + public void setMark2(Mark mark2) { + this.mark2 = mark2; + mark2.setSeqID(2); + } +} diff --git a/src/main/java/seng302/server/simulator/mark/Corner.java b/src/main/java/seng302/server/simulator/mark/Corner.java new file mode 100644 index 00000000..98b4828d --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/Corner.java @@ -0,0 +1,49 @@ +package seng302.server.simulator.mark; + +public class Corner { + + private int seqID; + private CompoundMark compoundMark; + //private int CompoundMarkID; + private RoundingType roundingType; + private int zoneSize; // size of the zone around a mark in boat-lengths. + + public Corner(int seqID, CompoundMark compoundMark, RoundingType roundingType, int zoneSize) { + this.seqID = seqID; + this.compoundMark = compoundMark; + this.roundingType = roundingType; + this.zoneSize = zoneSize; + } + + public int getSeqID() { + return seqID; + } + + public void setSeqID(int seqID) { + this.seqID = seqID; + } + + public CompoundMark getCompoundMark() { + return compoundMark; + } + + public void setCompoundMark(CompoundMark compoundMark) { + this.compoundMark = compoundMark; + } + + public RoundingType getRoundingType() { + return roundingType; + } + + public void setRoundingType(RoundingType roundingType) { + this.roundingType = roundingType; + } + + public int getZoneSize() { + return zoneSize; + } + + public void setZoneSize(int zoneSize) { + this.zoneSize = zoneSize; + } +} diff --git a/src/main/java/seng302/server/simulator/mark/Mark.java b/src/main/java/seng302/server/simulator/mark/Mark.java new file mode 100644 index 00000000..edcac047 --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/Mark.java @@ -0,0 +1,54 @@ +package seng302.server.simulator.mark; + +/** + * An abstract class to represent general marks + * Created by Haoming Yin (hyi25) on 17/3/17. + */ +public class Mark { + + private int seqID; + private String name; + private double lat; + private double lng; + //private int sourceID; + + public Mark(String name, double lat, double lng) { + this.name = name; + this.lat = lat; + this.lng = lng; + } + + public int getSeqID() { + return seqID; + } + + public void setSeqID(int seqID) { + this.seqID = seqID; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLng() { + return lng; + } + + public void setLng(double lng) { + this.lng = lng; + } +} + + diff --git a/src/main/java/seng302/server/simulator/mark/RoundingType.java b/src/main/java/seng302/server/simulator/mark/RoundingType.java new file mode 100644 index 00000000..63fae504 --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/RoundingType.java @@ -0,0 +1,28 @@ +package seng302.server.simulator.mark; + +public enum RoundingType{ + + // the mark should be rounded to port (boat's left) + PORT("PS"), + + // the mark should be rounded to starboard (boat's right) + STARBOARD("Stbd"), + + // the boat within the compound mark with the SeqID of 1 should be rounded + // to starboard and the boat within the compound mark with the SeqID of 2 + // should be rounded to port. + SP("SP"), + + // the opposite of SP + PS("PS"); + + private String type; + + RoundingType(String type) { + this.type = type; + } + + public String getType() { + return this.type; + } +} From 7f38191d034bbe9bfeac498a4daf708f05a6816a Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Apr 2017 01:04:30 +1200 Subject: [PATCH 34/97] Rewrote course parser to parse race xml file specified in AC35 spec. #story[828] --- .../simulator/parsers/CourseParser.java | 115 ++++++++++++++++++ .../server/simulator/parsers/FileParser.java | 35 ++++++ 2 files changed, 150 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/parsers/CourseParser.java create mode 100644 src/main/java/seng302/server/simulator/parsers/FileParser.java diff --git a/src/main/java/seng302/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/server/simulator/parsers/CourseParser.java new file mode 100644 index 00000000..831626e4 --- /dev/null +++ b/src/main/java/seng302/server/simulator/parsers/CourseParser.java @@ -0,0 +1,115 @@ +package seng302.server.simulator.parsers; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import seng302.server.simulator.mark.CompoundMark; +import seng302.server.simulator.mark.Corner; +import seng302.server.simulator.mark.Mark; +import seng302.server.simulator.mark.RoundingType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * parse a course xml file + * Created by Haoming Yin (hyi25) on 16/3/2017 + */ +public class CourseParser extends FileParser { + + private Document doc; + private Map compoundMarksMap; + + public CourseParser(String path) { + super(path); + this.doc = this.parseFile(); + } + + public List getCourse() { + compoundMarksMap = getCompoundMarks(doc); + List corners = new ArrayList<>(); + NodeList cMarksSequence = doc.getElementsByTagName("CompoundMarkSequence"); + + for (int i = 0; i < cMarksSequence.getLength(); i++) { + corners.add(getCorner(cMarksSequence.item(i))); + } + return corners; + } + + + private Corner getCorner(Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) node; + + Integer seqId = Integer.valueOf(e.getAttribute("SeqID")); + Integer cMarkId = Integer.valueOf(e.getAttribute("CompoundMarkID")); + CompoundMark cMark = compoundMarksMap.get(cMarkId); + RoundingType roundingType = RoundingType.valueOf(e.getAttribute("Rounding")); + Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize")); + + return new Corner(seqId, cMark, roundingType, zoneSize); + } + return null; + } + + private Map getCompoundMarks(Node node) { + Map compoundMarksMap = new HashMap<>(); + + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element element = (Element) node; + NodeList course = element.getElementsByTagName("Course"); + + // loop through all compound marks who are the children of course node + for (int i = 0; i < course.getLength(); i++) { + CompoundMark cMark = getCompoundMark(course.item(i)); + if (cMark != null) + compoundMarksMap.put(cMark.getMarkID(), cMark); + } + + return compoundMarksMap; + } + return null; + } + + + private CompoundMark getCompoundMark(Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) node; + Integer markID = Integer.valueOf(e.getAttribute("CompoundmarkID")); + String name = e.getAttribute("Name"); + CompoundMark cMark = new CompoundMark(markID, name); + + NodeList marks = e.getElementsByTagName("Mark"); + for (int i = 0; i < marks.getLength(); i++) { + Mark mark = getMark(marks.item(i)); + if (mark != null) + cMark.addMark(mark.getSeqID(), mark); + } + return cMark; + } + System.out.println("Failed to create compound mark."); + return null; + } + + + private Mark getMark(Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) node; + Integer seqId = Integer.valueOf(e.getAttribute("SeqID")); + String name = e.getAttribute("Name"); + Double lat = Double.valueOf(e.getAttribute("TargetLat")); + Double lng = Double.valueOf(e.getAttribute("TargetLng")); + + Mark mark = new Mark(name, lat, lng); + mark.setSeqID(seqId); + + return mark; + } + System.out.println("Failed to create mark."); + return null; + } + +} diff --git a/src/main/java/seng302/server/simulator/parsers/FileParser.java b/src/main/java/seng302/server/simulator/parsers/FileParser.java new file mode 100644 index 00000000..b82378f5 --- /dev/null +++ b/src/main/java/seng302/server/simulator/parsers/FileParser.java @@ -0,0 +1,35 @@ +package seng302.server.simulator.parsers; + +import org.w3c.dom.Document; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.InputStream; + +/** + * Created by Haoming Yin (hyi25) on 16/3/2017 + */ +public abstract class FileParser { + + private String filePath; + + public FileParser(String path) { + this.filePath = path; + } + + protected Document parseFile() { + try { + InputStream is = getClass().getResourceAsStream(this.filePath); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(is); + // optional, in order to recover info from broken line. + doc.getDocumentElement().normalize(); + return doc; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + } +} From b2ea8196d5885b9d881d92ec8fb8ac8c30893eaa Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Tue, 25 Apr 2017 11:36:27 +1200 Subject: [PATCH 35/97] Fixed a bug of getCourse method as it didn't parse xml correctly. - a typo 'CompoundmarkID'(should be 'CompoundMarkID') which caused parser failed to parse file. - add typeOf method in RoundingType to convert strings to types #story[828] --- .../server/simulator/mark/CompoundMark.java | 13 +++++++++++++ .../seng302/server/simulator/mark/Corner.java | 10 ++++++++++ .../seng302/server/simulator/mark/Mark.java | 9 +++++++++ .../server/simulator/mark/RoundingType.java | 19 +++++++++++++++++-- .../simulator/parsers/CourseParser.java | 18 ++++++++++-------- 5 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/main/java/seng302/server/simulator/mark/CompoundMark.java b/src/main/java/seng302/server/simulator/mark/CompoundMark.java index 59dc1f62..489a4a12 100644 --- a/src/main/java/seng302/server/simulator/mark/CompoundMark.java +++ b/src/main/java/seng302/server/simulator/mark/CompoundMark.java @@ -21,6 +21,19 @@ public class CompoundMark { } } + /** + * Prints out compoundMark's info and its marks, good for testing + * @return a string showing its details + */ + @Override + public String toString(){ + if (mark2 == null) + return String.format("CompoundMark: %d (%s), [%s]", + markID, name, mark1.toString()); + return String.format("CompoundMark: %d (%s), [%s; %s]", + markID, name, mark1.toString(), mark2.toString()); + } + public int getMarkID() { return markID; } diff --git a/src/main/java/seng302/server/simulator/mark/Corner.java b/src/main/java/seng302/server/simulator/mark/Corner.java index 98b4828d..70f231b5 100644 --- a/src/main/java/seng302/server/simulator/mark/Corner.java +++ b/src/main/java/seng302/server/simulator/mark/Corner.java @@ -15,6 +15,16 @@ public class Corner { this.zoneSize = zoneSize; } + /** + * Prints out corner's info and its compound mark, good for testing + * @return a string showing its details + */ + @Override + public String toString() { + return String.format("Corner: %d - %s - %d, %s\n", + seqID, roundingType.getType(), zoneSize, compoundMark.toString()); + } + public int getSeqID() { return seqID; } diff --git a/src/main/java/seng302/server/simulator/mark/Mark.java b/src/main/java/seng302/server/simulator/mark/Mark.java index edcac047..0dd2d761 100644 --- a/src/main/java/seng302/server/simulator/mark/Mark.java +++ b/src/main/java/seng302/server/simulator/mark/Mark.java @@ -18,6 +18,15 @@ public class Mark { this.lng = lng; } + /** + * Prints out mark's info and its geo location, good for testing + * @return a string showing its details + */ + @Override + public String toString() { + return String.format("Mark: %d (%s), lat: %f, lng: %f", seqID, name, lat, lng); + } + public int getSeqID() { return seqID; } diff --git a/src/main/java/seng302/server/simulator/mark/RoundingType.java b/src/main/java/seng302/server/simulator/mark/RoundingType.java index 63fae504..de6f6133 100644 --- a/src/main/java/seng302/server/simulator/mark/RoundingType.java +++ b/src/main/java/seng302/server/simulator/mark/RoundingType.java @@ -1,9 +1,9 @@ package seng302.server.simulator.mark; -public enum RoundingType{ +public enum RoundingType { // the mark should be rounded to port (boat's left) - PORT("PS"), + PORT("Port"), // the mark should be rounded to starboard (boat's right) STARBOARD("Stbd"), @@ -25,4 +25,19 @@ public enum RoundingType{ public String getType() { return this.type; } + + public static RoundingType typeOf(String type) { + switch (type) { + case "Port": + return PORT; + case "Stbd": + return STARBOARD; + case "SP": + return SP; + case "PS": + return PS; + default: + return null; + } + } } diff --git a/src/main/java/seng302/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/server/simulator/parsers/CourseParser.java index 831626e4..d055f88e 100644 --- a/src/main/java/seng302/server/simulator/parsers/CourseParser.java +++ b/src/main/java/seng302/server/simulator/parsers/CourseParser.java @@ -15,7 +15,7 @@ import java.util.List; import java.util.Map; /** - * parse a course xml file + * Parses the race xml file to get course details * Created by Haoming Yin (hyi25) on 16/3/2017 */ public class CourseParser extends FileParser { @@ -28,10 +28,11 @@ public class CourseParser extends FileParser { this.doc = this.parseFile(); } + // TODO: should handle error / invalid file gracefully public List getCourse() { - compoundMarksMap = getCompoundMarks(doc); + compoundMarksMap = getCompoundMarks(doc.getDocumentElement()); List corners = new ArrayList<>(); - NodeList cMarksSequence = doc.getElementsByTagName("CompoundMarkSequence"); + NodeList cMarksSequence = doc.getElementsByTagName("Corner"); for (int i = 0; i < cMarksSequence.getLength(); i++) { corners.add(getCorner(cMarksSequence.item(i))); @@ -47,7 +48,7 @@ public class CourseParser extends FileParser { Integer seqId = Integer.valueOf(e.getAttribute("SeqID")); Integer cMarkId = Integer.valueOf(e.getAttribute("CompoundMarkID")); CompoundMark cMark = compoundMarksMap.get(cMarkId); - RoundingType roundingType = RoundingType.valueOf(e.getAttribute("Rounding")); + RoundingType roundingType = RoundingType.typeOf(e.getAttribute("Rounding")); Integer zoneSize = Integer.valueOf(e.getAttribute("ZoneSize")); return new Corner(seqId, cMark, roundingType, zoneSize); @@ -60,11 +61,11 @@ public class CourseParser extends FileParser { if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) node; - NodeList course = element.getElementsByTagName("Course"); + NodeList cMarks = element.getElementsByTagName("CompoundMark"); // loop through all compound marks who are the children of course node - for (int i = 0; i < course.getLength(); i++) { - CompoundMark cMark = getCompoundMark(course.item(i)); + for (int i = 0; i < cMarks.getLength(); i++) { + CompoundMark cMark = getCompoundMark(cMarks.item(i)); if (cMark != null) compoundMarksMap.put(cMark.getMarkID(), cMark); } @@ -78,7 +79,8 @@ public class CourseParser extends FileParser { private CompoundMark getCompoundMark(Node node) { if (node.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) node; - Integer markID = Integer.valueOf(e.getAttribute("CompoundmarkID")); + Integer markID = Integer.valueOf(e.getAttribute("CompoundMarkID")); + String name = e.getAttribute("Name"); CompoundMark cMark = new CompoundMark(markID, name); From 2a67f04d15bc7137d8dac509b929ba84fd2b99e5 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 26 Apr 2017 20:56:58 +1200 Subject: [PATCH 36/97] Create GeoUtility to process all geo calculations. - calculate distance between two geo positions - calculate the bearing from one geo position to another - calculate the new geo position by passing original position, bearing and distance #story[828] --- .../seng302/server/simulator/GeoUtility.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/GeoUtility.java diff --git a/src/main/java/seng302/server/simulator/GeoUtility.java b/src/main/java/seng302/server/simulator/GeoUtility.java new file mode 100644 index 00000000..a93be4b3 --- /dev/null +++ b/src/main/java/seng302/server/simulator/GeoUtility.java @@ -0,0 +1,76 @@ +package seng302.server.simulator; + +import javafx.geometry.Pos; +import seng302.server.simulator.mark.Mark; +import seng302.server.simulator.mark.Position; + +public class GeoUtility { + + private static double EARTH_RADIUS = 6378.137; + /** + * Calculates the euclidean distance between two markers on the canvas using xy coordinates + * + * @param p1 first geographical position + * @param p2 second geographical position + * @return the distance in meter between two points in meters + */ + public static Double calculateMarkerDistance(Position p1, Position p2) { + + double dLat = Math.toRadians(p2.getLat() - p1.getLat()); + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + + double a = Math.pow(Math.sin(dLat / 2), 2.0) + + Math.cos(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) + * Math.pow(Math.sin(dLon / 2), 2.0); + + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + double d = EARTH_RADIUS * c; + + return d * 1000; // distance from km to meter + } + + /** + * Calculates the angle between to angular co-ordinates on a sphere. + * + * @param p1 the first geographical position, start point + * @param p2 the second geographical position, end point + * @return the bearing in degree from p1 to p2, value range (0 ~ 360 deg.). + * vertical up is 0 deg. horizontal right is 90 deg. + */ + public static Double getBearing(Position p1, Position p2) { + + double dLon = Math.toRadians(p2.getLng() - p1.getLng()); + + double y = Math.sin(dLon) * Math.cos(Math.toRadians(p2.getLat())); + double x = Math.cos(Math.toRadians(p1.getLat())) * Math.sin(Math.toRadians(p2.getLat())) + - Math.sin(Math.toRadians(p1.getLat())) * Math.cos(Math.toRadians(p2.getLat())) * Math.cos(dLon); + + double bearing = Math.toDegrees(Math.atan2(y, x)); + + return (bearing + 360.0) % 360.0; + } + + /** + * Given an existing point in lat/lng, distance in (in meter) and bearing + * (in degrees), calculates the new lat/lng. + * @param origin the original position within lat / lng + * @param bearing the bearing in degree, from original position to the new position + * @param distance the distance in meter, from original position to the new position + * @return the new position + */ + public static Position getGeoCoordinate(Position origin, Double bearing, Double distance) { + double b = Math.toRadians(bearing); // bearing to radians + double d = distance / 1000.0; // distance to km + + double originLat = Math.toRadians(origin.getLat()); + double originLng = Math.toRadians(origin.getLng()); + + double endLat = Math.asin(Math.sin(originLat) * Math.cos(d / EARTH_RADIUS) + + Math.cos(originLat) * Math.sin(d / EARTH_RADIUS) * Math.cos(b)); + double endLng = originLng + + Math.atan2(Math.sin(b) * Math.sin(d / EARTH_RADIUS) * Math.cos(originLat), + Math.cos(d / EARTH_RADIUS) - Math.sin(originLat) * Math.sin(endLat)); + + return new Position(Math.toDegrees(endLat), Math.toDegrees(endLng)); + } +} From 7bf2d4c40ec8f552160418290edb91709a63e1e7 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 26 Apr 2017 22:46:04 +1200 Subject: [PATCH 37/97] Added Position class to better use GeoUtility. - mark now inherit from Position #story[828] --- .../seng302/server/simulator/GeoUtility.java | 12 +++---- .../seng302/server/simulator/mark/Corner.java | 30 ++++++++++++++++++ .../seng302/server/simulator/mark/Mark.java | 30 ++++++------------ .../server/simulator/mark/Position.java | 31 +++++++++++++++++++ .../simulator/parsers/CourseParser.java | 5 +-- 5 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 src/main/java/seng302/server/simulator/mark/Position.java diff --git a/src/main/java/seng302/server/simulator/GeoUtility.java b/src/main/java/seng302/server/simulator/GeoUtility.java index a93be4b3..288e0ee1 100644 --- a/src/main/java/seng302/server/simulator/GeoUtility.java +++ b/src/main/java/seng302/server/simulator/GeoUtility.java @@ -1,12 +1,11 @@ package seng302.server.simulator; -import javafx.geometry.Pos; -import seng302.server.simulator.mark.Mark; import seng302.server.simulator.mark.Position; public class GeoUtility { private static double EARTH_RADIUS = 6378.137; + /** * Calculates the euclidean distance between two markers on the canvas using xy coordinates * @@ -14,7 +13,7 @@ public class GeoUtility { * @param p2 second geographical position * @return the distance in meter between two points in meters */ - public static Double calculateMarkerDistance(Position p1, Position p2) { + public static Double getDistance(Position p1, Position p2) { double dLat = Math.toRadians(p2.getLat() - p1.getLat()); double dLon = Math.toRadians(p2.getLng() - p1.getLng()); @@ -35,7 +34,7 @@ public class GeoUtility { * @param p1 the first geographical position, start point * @param p2 the second geographical position, end point * @return the bearing in degree from p1 to p2, value range (0 ~ 360 deg.). - * vertical up is 0 deg. horizontal right is 90 deg. + * vertical up is 0 deg. horizontal right is 90 deg. */ public static Double getBearing(Position p1, Position p2) { @@ -53,8 +52,9 @@ public class GeoUtility { /** * Given an existing point in lat/lng, distance in (in meter) and bearing * (in degrees), calculates the new lat/lng. - * @param origin the original position within lat / lng - * @param bearing the bearing in degree, from original position to the new position + * + * @param origin the original position within lat / lng + * @param bearing the bearing in degree, from original position to the new position * @param distance the distance in meter, from original position to the new position * @return the new position */ diff --git a/src/main/java/seng302/server/simulator/mark/Corner.java b/src/main/java/seng302/server/simulator/mark/Corner.java index 70f231b5..136212f2 100644 --- a/src/main/java/seng302/server/simulator/mark/Corner.java +++ b/src/main/java/seng302/server/simulator/mark/Corner.java @@ -8,6 +8,10 @@ public class Corner { private RoundingType roundingType; private int zoneSize; // size of the zone around a mark in boat-lengths. + // TODO: this shouldn't be used in the future!!!! + private double bearingToNextCorner, distanceToNextCorner; + private Corner nextCorner; + public Corner(int seqID, CompoundMark compoundMark, RoundingType roundingType, int zoneSize) { this.seqID = seqID; this.compoundMark = compoundMark; @@ -56,4 +60,30 @@ public class Corner { public void setZoneSize(int zoneSize) { this.zoneSize = zoneSize; } + + + // TODO: next six setters & getters shouldn't be used in the future. + public double getBearingToNextCorner() { + return bearingToNextCorner; + } + + public void setBearingToNextCorner(double bearingToNextCorner) { + this.bearingToNextCorner = bearingToNextCorner; + } + + public double getDistanceToNextCorner() { + return distanceToNextCorner; + } + + public void setDistanceToNextCorner(double distanceToNextCorner) { + this.distanceToNextCorner = distanceToNextCorner; + } + + public Corner getNextCorner() { + return nextCorner; + } + + public void setNextCorner(Corner nextCorner) { + this.nextCorner = nextCorner; + } } diff --git a/src/main/java/seng302/server/simulator/mark/Mark.java b/src/main/java/seng302/server/simulator/mark/Mark.java index 0dd2d761..41f00bb6 100644 --- a/src/main/java/seng302/server/simulator/mark/Mark.java +++ b/src/main/java/seng302/server/simulator/mark/Mark.java @@ -4,18 +4,16 @@ package seng302.server.simulator.mark; * An abstract class to represent general marks * Created by Haoming Yin (hyi25) on 17/3/17. */ -public class Mark { +public class Mark extends Position { private int seqID; private String name; - private double lat; - private double lng; - //private int sourceID; + private int sourceID; - public Mark(String name, double lat, double lng) { + public Mark(String name, double lat, double lng, int sourceID) { + super(lat, lng); this.name = name; - this.lat = lat; - this.lng = lng; + this.sourceID = sourceID; } /** @@ -24,7 +22,7 @@ public class Mark { */ @Override public String toString() { - return String.format("Mark: %d (%s), lat: %f, lng: %f", seqID, name, lat, lng); + return String.format("Mark%d: %s, source: %d, lat: %f, lng: %f", seqID, name, sourceID, lat, lng); } public int getSeqID() { @@ -43,20 +41,12 @@ public class Mark { this.name = name; } - public double getLat() { - return lat; + public int getSourceID() { + return sourceID; } - public void setLat(double lat) { - this.lat = lat; - } - - public double getLng() { - return lng; - } - - public void setLng(double lng) { - this.lng = lng; + public void setSourceID(int sourceID) { + this.sourceID = sourceID; } } diff --git a/src/main/java/seng302/server/simulator/mark/Position.java b/src/main/java/seng302/server/simulator/mark/Position.java new file mode 100644 index 00000000..74200e9d --- /dev/null +++ b/src/main/java/seng302/server/simulator/mark/Position.java @@ -0,0 +1,31 @@ +package seng302.server.simulator.mark; + +public class Position { + + double lat, lng; + + public Position(double lat, double lng) { + this.lat = lat; + this.lng = lng; + } + + public String toString() { + return String.format("Position at lat:%f lng:%f.", lat, lng); + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLng() { + return lng; + } + + public void setLng(double lng) { + this.lng = lng; + } +} diff --git a/src/main/java/seng302/server/simulator/parsers/CourseParser.java b/src/main/java/seng302/server/simulator/parsers/CourseParser.java index d055f88e..f7be46cd 100644 --- a/src/main/java/seng302/server/simulator/parsers/CourseParser.java +++ b/src/main/java/seng302/server/simulator/parsers/CourseParser.java @@ -29,7 +29,7 @@ public class CourseParser extends FileParser { } // TODO: should handle error / invalid file gracefully - public List getCourse() { + protected List getCourse() { compoundMarksMap = getCompoundMarks(doc.getDocumentElement()); List corners = new ArrayList<>(); NodeList cMarksSequence = doc.getElementsByTagName("Corner"); @@ -104,8 +104,9 @@ public class CourseParser extends FileParser { String name = e.getAttribute("Name"); Double lat = Double.valueOf(e.getAttribute("TargetLat")); Double lng = Double.valueOf(e.getAttribute("TargetLng")); + Integer sourceId = Integer.valueOf(e.getAttribute("SourceID")); - Mark mark = new Mark(name, lat, lng); + Mark mark = new Mark(name, lat, lng, sourceId); mark.setSeqID(seqId); return mark; From 8b8422de3a78af4c7b23831f4337bee8ab77bbfd Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 26 Apr 2017 22:47:39 +1200 Subject: [PATCH 38/97] Renamed course parser to race parser - because in AC35 spec. race xml file contain course set up and all other general race settings #story[828] --- .../java/seng302/server/simulator/Boat.java | 110 ++++++++++++++++++ .../server/simulator/parsers/BoatsParser.java | 20 ++++ .../server/simulator/parsers/RaceParser.java | 53 +++++++++ 3 files changed, 183 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/Boat.java create mode 100644 src/main/java/seng302/server/simulator/parsers/BoatsParser.java create mode 100644 src/main/java/seng302/server/simulator/parsers/RaceParser.java diff --git a/src/main/java/seng302/server/simulator/Boat.java b/src/main/java/seng302/server/simulator/Boat.java new file mode 100644 index 00000000..3fd6eb87 --- /dev/null +++ b/src/main/java/seng302/server/simulator/Boat.java @@ -0,0 +1,110 @@ +package seng302.server.simulator; + +import seng302.server.simulator.mark.Corner; +import seng302.server.simulator.mark.Position; + +public class Boat { + + private int sourceID; + private double lat; + private double lng; + private double speed; // in mm/sec + private String boatName, shortName, shorterName; + + // haven't been used so far + private Corner lastPassedCorner, headingCorner; + + public Boat(int sourceID, String boatName) { + this.sourceID = sourceID; + this.boatName = boatName; + } + + /** + * Moves boat to the heading direction for a given time duration + * @param heading moving direction in degree. + * @param duration moving duration in millisecond. + */ + public void move(double heading, double duration) { + Double distance = speed * duration / 1000000; // convert mm to meter + Position originPos = new Position(lat, lng); + Position newPos = GeoUtility.getGeoCoordinate(originPos, heading, distance); + this.lat = newPos.getLat(); + this.lng = newPos.getLng(); + } + + public String toString() { + return String.format("Boat (%d): lat: %f, lng: %f", sourceID, lat, lng); + } + + public int getSourceID() { + return sourceID; + } + + public void setSourceID(int sourceID) { + this.sourceID = sourceID; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLng() { + return lng; + } + + public void setLng(double lng) { + this.lng = lng; + } + + public double getSpeed() { + return speed; + } + + public void setSpeed(double speed) { + this.speed = speed; + } + + public String getBoatName() { + return boatName; + } + + public void setBoatName(String boatName) { + this.boatName = boatName; + } + + public String getShortName() { + return shortName; + } + + public void setShortName(String shortName) { + this.shortName = shortName; + } + + public String getShorterName() { + return shorterName; + } + + public void setShorterName(String shorterName) { + this.shorterName = shorterName; + } + + public Corner getLastPassedCorner() { + return lastPassedCorner; + } + + public void setLastPassedCorner(Corner lastPassedCorner) { + this.lastPassedCorner = lastPassedCorner; + } + + public Corner getHeadingCorner() { + return headingCorner; + } + + public void setHeadingCorner(Corner headingCorner) { + this.headingCorner = headingCorner; + } +} diff --git a/src/main/java/seng302/server/simulator/parsers/BoatsParser.java b/src/main/java/seng302/server/simulator/parsers/BoatsParser.java new file mode 100644 index 00000000..5d552a00 --- /dev/null +++ b/src/main/java/seng302/server/simulator/parsers/BoatsParser.java @@ -0,0 +1,20 @@ +package seng302.server.simulator.parsers; + +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + + +/** + * Parses the race xml file to get course details + * Created by Haoming Yin (hyi25) on 16/3/2017 + */ +public class BoatsParser extends FileParser { + + private Document doc; + + public BoatsParser(String path) { + super(path); + this.doc = this.parseFile(); + } + +} diff --git a/src/main/java/seng302/server/simulator/parsers/RaceParser.java b/src/main/java/seng302/server/simulator/parsers/RaceParser.java new file mode 100644 index 00000000..363da64f --- /dev/null +++ b/src/main/java/seng302/server/simulator/parsers/RaceParser.java @@ -0,0 +1,53 @@ +package seng302.server.simulator.parsers; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import seng302.server.simulator.Boat; +import seng302.server.simulator.mark.Corner; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Parses the race xml file to get course details + * Created by Haoming Yin (hyi25) on 16/3/2017 + */ +public class RaceParser extends FileParser { + + private Document doc; + private String path; + + public RaceParser(String path) { + super(path); + this.path = path; + this.doc = this.parseFile(); + } + + public List getCourse() { + CourseParser cp = new CourseParser(path); + return cp.getCourse(); + } + + public List getBoats() { + NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht"); + List boats = new ArrayList<>(); + + for (int i = 0; i < yachts.getLength(); i++) { + boats.add(getBoat(yachts.item(i))); + } + return boats; + } + + private Boat getBoat(Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + Element e = (Element) node; + + Integer sourceId = Integer.valueOf(e.getAttribute("SourceID")); + return new Boat(sourceId, "Test Boat"); + } + return null; + } +} From 8c8f2532338c8c45c40a832931d496f982277cde Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 26 Apr 2017 22:58:13 +1200 Subject: [PATCH 39/97] Created simulator to generate mock data. - simulator runs as a background thread and sleep for a given time lapse. - simulator extends observable, so it can notify all its observers when boats positions have been updated #story[828] --- src/main/java/seng302/App.java | 2 + .../seng302/server/simulator/Simulator.java | 119 ++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/main/java/seng302/server/simulator/Simulator.java diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 1a869e78..6efce9e7 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -6,6 +6,7 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; import seng302.server.ServerThread; +import seng302.server.simulator.Simulator; public class App extends Application { @@ -20,6 +21,7 @@ public class App extends Application public static void main(String[] args) { new ServerThread("Racevision Test Server"); + new Thread(new Simulator(1000)).run(); launch(args); } } diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/server/simulator/Simulator.java new file mode 100644 index 00000000..f76d0eec --- /dev/null +++ b/src/main/java/seng302/server/simulator/Simulator.java @@ -0,0 +1,119 @@ +package seng302.server.simulator; + +import seng302.server.simulator.mark.Corner; +import seng302.server.simulator.mark.Mark; +import seng302.server.simulator.mark.Position; +import seng302.server.simulator.parsers.RaceParser; + +import java.util.List; +import java.util.Observable; + +public class Simulator extends Observable implements Runnable { + + private List course; + private List boats; + private long lapse; + + /** + * Creates a simulator instance with given time lapse. + * @param lapse time duration in millisecond. + */ + public Simulator(long lapse) { + RaceParser rp = new RaceParser("/server_config/race.xml"); + course = rp.getCourse(); + boats = rp.getBoats(); + this.lapse = lapse; + + setLegs(); + + // set start line's coordinate to boats + Double startLat = course.get(0).getCompoundMark().getMark1().getLat(); + Double startLng = course.get(0).getCompoundMark().getMark1().getLng(); + for (Boat boat : boats) { + boat.setLat(startLat); + boat.setLng(startLng); + boat.setLastPassedCorner(course.get(0)); + boat.setHeadingCorner(course.get(1)); + boat.setSpeed(50000); + } + } + + @Override + public void run() { + + int numOfFinishedBoats = 0; + + while (numOfFinishedBoats < boats.size()) { + for (Boat boat : boats) { + numOfFinishedBoats += moveBoat(boat, lapse); + } + System.out.println(boats.get(0)); + + setChanged(); + notifyObservers(boats); + + // sleep for 1 second. + try { + Thread.sleep(lapse); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + /** + * Moves a boat with give time duration. + * + * @param boat the boat to be moved + * @param duration the moving duration in millisecond + * @return 1 if boat reached final line, otherwise 0 + */ + private int moveBoat(Boat boat, double duration) { + if (boat.getHeadingCorner() != null) { + + boat.move(boat.getLastPassedCorner().getBearingToNextCorner(), duration); + + Position boatPos = new Position(boat.getLat(), boat.getLng()); + Position lastMarkPos = boat.getLastPassedCorner().getCompoundMark().getMark1(); + + double distanceFromLastMark = GeoUtility.getDistance(boatPos, lastMarkPos); + // if a boat passes its heading mark + while (distanceFromLastMark >= boat.getLastPassedCorner().getDistanceToNextCorner()) { + double compensateDistance = distanceFromLastMark - boat.getLastPassedCorner().getDistanceToNextCorner(); + boat.setLastPassedCorner(boat.getHeadingCorner()); + boat.setHeadingCorner(boat.getLastPassedCorner().getNextCorner()); + + // heading corner == null means boat has reached the final mark + if (boat.getHeadingCorner() == null) return 1; + + // move compensate distance for the mark just passed + Position pos = GeoUtility.getGeoCoordinate( + boat.getLastPassedCorner().getCompoundMark().getMark1(), + boat.getLastPassedCorner().getBearingToNextCorner(), + compensateDistance); + boat.setLat(pos.getLat()); + boat.setLng(pos.getLng()); + distanceFromLastMark = GeoUtility.getDistance(new Position(boat.getLat(), boat.getLng()), + boat.getLastPassedCorner().getCompoundMark().getMark1()); + } + } + return 0; + } + + private void setLegs() { + // get the bearing from one mark to the next heading mark + for (int i = 0; i < course.size() - 1; i++) { + + Mark mark1 = course.get(i).getCompoundMark().getMark1(); + Mark mark2 = course.get(i + 1).getCompoundMark().getMark1(); + course.get(i).setDistanceToNextCorner(GeoUtility.getDistance(mark1, mark2)); + + course.get(i).setNextCorner(course.get(i + 1)); + + course.get(i).setBearingToNextCorner( + GeoUtility.getBearing(course.get(i).getCompoundMark().getMark1(), + course.get(i + 1).getCompoundMark().getMark1())); + } + } + +} From 245bd184b465ada336a0ec42f9c1ab5d90a3990f Mon Sep 17 00:00:00 2001 From: cir27 Date: Thu, 27 Apr 2017 02:44:25 +1200 Subject: [PATCH 40/97] Mark drawing moved to MarkGroup class. RaceObject and it's sub classes now describe all functionality required for a on screen object. Improved wakes. Branch currently untested. #story[812, 820] #refactor #implement. --- .../seng302/controllers/CanvasController.java | 237 ++++++++++-------- src/main/java/seng302/models/BoatGroup.java | 146 +++++------ src/main/java/seng302/models/RaceObject.java | 38 ++- src/main/java/seng302/models/Wake.java | 37 +++ .../java/seng302/models/mark/GateMark.java | 11 - src/main/java/seng302/models/mark/Mark.java | 18 -- .../java/seng302/models/mark/MarkGroup.java | 164 +++++++++--- 7 files changed, 392 insertions(+), 259 deletions(-) create mode 100644 src/main/java/seng302/models/Wake.java diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index b5d76432..3891f3a1 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -6,6 +6,7 @@ import javafx.fxml.FXML; import javafx.geometry.Point2D; import javafx.geometry.Point3D; import javafx.scene.Group; +import javafx.scene.Node; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.AnchorPane; @@ -15,10 +16,8 @@ import javafx.util.Pair; import seng302.models.Boat; import seng302.models.BoatGroup; import seng302.models.Colors; -import seng302.models.mark.GateMark; -import seng302.models.mark.Mark; -import seng302.models.mark.MarkType; -import seng302.models.mark.SingleMark; +import seng302.models.RaceObject; +import seng302.models.mark.*; import seng302.models.parsers.StreamParser; import seng302.models.parsers.StreamReceiver; @@ -59,6 +58,7 @@ public class CanvasController { private double referencePointX; private double referencePointY; private double metersToPixels; + private List raceObjects = new ArrayList<>(); public AnimationTimer timer; @@ -154,39 +154,51 @@ public class CanvasController { Mark nextMark; //if (countdown == 0) { - for (BoatGroup boatGroup : boatGroups) { + for (RaceObject raceObject : raceObjects) { //if (currentRaceMarker[boatIndex] < marks.size()) { //if (currentRaceMarker[boatIndex] == 6) { // int debugLine = 4; //} - double xb4 = boatGroup.getLayoutX(); - double yb4 = boatGroup.getLayoutY(); - nextMark = marks.get(currentRaceMarker[boatIndex]); + //double xb4 = boatGroup.getLayoutX(); + //double yb4 = boatGroup.getLayoutY(); + //nextMark = marks.get(currentRaceMarker[boatIndex]); //descending = nextMark.getY() > boatGroup.getLayoutY(); //leftToRight = nextMark.getX() < boatGroup.getLayoutX(); - boatGroup.updatePosition(1000 / 60); - Point3D p = StreamParser.boatPositions.get((long)boatGroup.getBoat().getId()); + raceObject.updatePosition(1000 / 60); + for (int id : raceObject.getRaceIds()) { + if (StreamParser.boatPositions.containsKey(id)) { + Point3D p = StreamParser.boatPositions.get((long) id); + Point2D p2d = latLonToXY(p.getX(), p.getY()); + //System.out.println("p2d = " + p2d); + //System.out.println("p.toString() = " + p.toString()); + double heading = 360.0 / 0xffff * p.getZ(); + //System.out.println("heading = " + heading); + raceObject.setDestination(p2d.getX(), p2d.getY(), heading, id); + } + StreamParser.boatPositions.remove((long) id); + } + //Point3D p = StreamParser.boatPositions.get((long) raceObject.getRaceIds()[0]); //System.out.println("boatGroup = " + boatGroup.getBoat().getId()); //System.out.println("StreamParser.boatPositions.toString() = " + StreamParser.boatPositions.toString()); - if (p != null) { - Point2D p2d = latLonToXY(p.getX(), p.getY()); - //System.out.println("p2d = " + p2d); - if (!boatGroup.isSamePos(p2d)) { - //System.out.println("p.toString() = " + p.toString()); - double heading = 360.0 / 0xffff * p.getZ(); - //System.out.println("heading = " + heading); - - - - boatGroup.setDestination(p2d.getX(), p2d.getY(), heading); +// if (p != null) { +// Point2D p2d = latLonToXY(p.getX(), p.getY()); +// //System.out.println("p2d = " + p2d); +// if (!boatGroup.isSamePos(p2d)) { +// //System.out.println("p.toString() = " + p.toString()); +// double heading = 360.0 / 0xffff * p.getZ(); +// //System.out.println("heading = " + heading); +// +// +// +// boatGroup.setDestination(p2d.getX(), p2d.getY(), heading, boatGroup.getRaceIds()[0]); //boatGroup.setDestination(p2d.getX(), p2d.getY()); - } - } + // } + //} // if (descending && nextMark.getY() < boatGroup.getLayoutY()) { // currentRaceMarker[boatIndex]++; @@ -208,14 +220,14 @@ public class CanvasController { // boatGroup.setDestination( // marks.get(currentRaceMarker[boatIndex]).getX(), marks.get(currentRaceMarker[boatIndex]).getY() // ); -// } - double xnew = boatGroup.getLayoutX(); - double ynew = boatGroup.getLayoutY(); - double dx = xnew - xb4; - double dy = ynew -yb4; - raceFinished = false; - boatIndex++; + +// double xnew = boatGroup.getLayoutX(); +// double ynew = boatGroup.getLayoutY(); +// double dx = xnew - xb4; +// double dy = ynew -yb4; +// raceFinished = false; +// boatIndex++; } //} //if (raceFinished) { @@ -280,11 +292,10 @@ public class CanvasController { private void drawBoats() { // Map timelineInfos = raceViewController.getTimelineInfos(); List boats = raceViewController.getStartingBoats(); - List marks = raceViewController.getRace().getCourse(); - Double startingX = (double) marks.get(0).getX(); - Double startingY = (double) marks.get(0).getY(); - Double firstMarkX = (double) marks.get(1).getX(); - Double firstMarkY = (double) marks.get(1).getY(); + Double startingX = group.getChildren().get(0).getLayoutX(); + Double startingY = group.getChildren().get(0).getLayoutY(); + Double firstMarkX = group.getChildren().get(1).getLayoutX(); + Double firstMarkY = group.getChildren().get(1).getLayoutY(); for (Boat boat : boats) { BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); @@ -324,67 +335,67 @@ public class CanvasController { */ private void drawCourse() { fitToCanvas(); - for (Mark mark : raceViewController.getRace().getCourse()) { - if (mark.getMarkType() == MarkType.SINGLE_MARK) { - drawSingleMark((SingleMark) mark, Color.BLACK); - } else { - drawGateMark((GateMark) mark); - } - } - System.out.println("MIN/MAX POINTS"); - System.out.println(minLatPoint.getName() + " " + minLatPoint.getX() + " " + minLatPoint.getY()); - System.out.println(maxLatPoint.getName() + " " + maxLatPoint.getX() + " " + maxLatPoint.getY()); - System.out.println(minLonPoint.getName() + " " + minLonPoint.getX() + " " + minLonPoint.getY()); - System.out.println(maxLonPoint.getName() + " " + maxLonPoint.getX() + " " + maxLonPoint.getY()); - System.out.println(referencePointX); - System.out.println(referencePointY); +// for (Mark mark : raceViewController.getRace().getCourse()) { +// if (mark.getMarkType() == MarkType.SINGLE_MARK) { +// drawSingleMark((SingleMark) mark, Color.BLACK); +// } else { +// drawGateMark((GateMark) mark); +// } +// } +// System.out.println("MIN/MAX POINTS"); +// System.out.println(minLatPoint.getName() + " " + minLatPoint.getX() + " " + minLatPoint.getY()); +// System.out.println(maxLatPoint.getName() + " " + maxLatPoint.getX() + " " + maxLatPoint.getY()); +// System.out.println(minLonPoint.getName() + " " + minLonPoint.getX() + " " + minLonPoint.getY()); +// System.out.println(maxLonPoint.getName() + " " + maxLonPoint.getX() + " " + maxLonPoint.getY()); +// System.out.println(referencePointX); +// System.out.println(referencePointY); } - /** - * Draw a given mark on canvas - * - * @param singleMark - */ - private void drawSingleMark(SingleMark singleMark, Color color) { - gc.setFill(color); - System.out.println("DRAWING " + singleMark.getName() + " at " + singleMark.getX() + ", " + singleMark.getY()); - gc.fillOval(singleMark.getX(), singleMark.getY(),MARK_SIZE,MARK_SIZE); - } - - /** - * Draw a gate mark which contains two single marks - * - * @param gateMark - */ - private void drawGateMark(GateMark gateMark) { - Color color = Color.BLUE; - - if (gateMark.getName().equals("Start")){ - color = Color.GREEN; - } - - if (gateMark.getName().equals("Finish")){ - color = Color.RED; - } - - drawSingleMark(gateMark.getSingleMark1(), color); - drawSingleMark(gateMark.getSingleMark2(), color); - - GraphicsContext gc = canvas.getGraphicsContext2D(); - gc.save(); - gc.setStroke(color); - if (gateMark.getMarkType() == MarkType.OPEN_GATE) - gc.setLineDashes(3, 5); - - gc.setLineWidth(2); - gc.strokeLine( - gateMark.getSingleMark1().getX() + MARK_SIZE / 2, - gateMark.getSingleMark1().getY() + MARK_SIZE / 2, - gateMark.getSingleMark2().getX() + MARK_SIZE / 2, - gateMark.getSingleMark2().getY() + MARK_SIZE / 2 - ); - gc.restore(); - } +// /** +// * Draw a given mark on canvas +// * +// * @param singleMark +// */ +// private void drawSingleMark(SingleMark singleMark, Color color) { +// gc.setFill(color); +// System.out.println("DRAWING " + singleMark.getName() + " at " + singleMark.getX() + ", " + singleMark.getY()); +// gc.fillOval(singleMark.getX(), singleMark.getY(),MARK_SIZE,MARK_SIZE); +// } +// +// /** +// * Draw a gate mark which contains two single marks +// * +// * @param gateMark +// */ +// private void drawGateMark(GateMark gateMark) { +// Color color = Color.BLUE; +// +// if (gateMark.getName().equals("Start")){ +// color = Color.GREEN; +// } +// +// if (gateMark.getName().equals("Finish")){ +// color = Color.RED; +// } +// +// drawSingleMark(gateMark.getSingleMark1(), color); +// drawSingleMark(gateMark.getSingleMark2(), color); +// +// GraphicsContext gc = canvas.getGraphicsContext2D(); +// gc.save(); +// gc.setStroke(color); +// if (gateMark.getMarkType() == MarkType.OPEN_GATE) +// gc.setLineDashes(3, 5); +// +// gc.setLineWidth(2); +// gc.strokeLine( +// gateMark.getSingleMark1().getX() + MARK_SIZE / 2, +// gateMark.getSingleMark1().getY() + MARK_SIZE / 2, +// gateMark.getSingleMark2().getX() + MARK_SIZE / 2, +// gateMark.getSingleMark2().getY() + MARK_SIZE / 2 +// ); +// gc.restore(); +// } /** * Calculates x and y location for every marker that fits it to the canvas the race will be drawn on. @@ -478,8 +489,8 @@ public class CanvasController { } referencePointX = Math.round(referencePointX); referencePointY = Math.round(referencePointY); - referencePoint.setX((int) referencePointX); - referencePoint.setY((int) referencePointY); +// referencePoint.setX((int) referencePointX); +// referencePoint.setY((int) referencePointY); } /** @@ -517,27 +528,31 @@ public class CanvasController { * are scaled according to the distanceScaleFactor variable. */ private void givePointsXY() { - Point2D canvasLocation; - List allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); + //Point2D canvasLocation; +// List allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); + Set unqiuePoints = new HashSet<>(raceViewController.getRace().getCourse()); + System.out.println("unqiuePoints = " + unqiuePoints); + RaceObject markGroup; - for (Mark mark : allPoints) { + for (Mark mark : unqiuePoints) { if (mark.getMarkType() != MarkType.SINGLE_MARK) { GateMark gateMark = (GateMark) mark; +// canvasLocation = findScaledXY(gateMark.getSingleMark1()); +// gateMark.getSingleMark1().setX((int) canvasLocation.getX()); +// gateMark.getSingleMark1().setY((int) canvasLocation.getY()); +// +// canvasLocation = findScaledXY(gateMark.getSingleMark2()); +// gateMark.getSingleMark2().setX((int) canvasLocation.getX()); +// gateMark.getSingleMark2().setY((int) canvasLocation.getY()); - canvasLocation = findScaledXY(gateMark.getSingleMark1()); - gateMark.getSingleMark1().setX((int) canvasLocation.getX()); - gateMark.getSingleMark1().setY((int) canvasLocation.getY()); - - canvasLocation = findScaledXY(gateMark.getSingleMark2()); - gateMark.getSingleMark2().setX((int) canvasLocation.getX()); - gateMark.getSingleMark2().setY((int) canvasLocation.getY()); + markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2())); + group.getChildren().add(markGroup); } - if (mark.getMarkType() == MarkType.CLOSED_GATE) - ((GateMark) mark).assignXYCentered(); else { - canvasLocation = findScaledXY(mark); - mark.setX((int) canvasLocation.getX()); - mark.setY((int) canvasLocation.getY()); +// canvasLocation = findScaledXY(mark); +// mark.setX((int) canvasLocation.getX()); +// mark.setY((int) canvasLocation.getY()); + markGroup = new MarkGroup(mark, findScaledXY(mark)); } } } diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index b46f4783..a37f1852 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -20,9 +20,11 @@ public class BoatGroup extends RaceObject{ private static final double BOAT_HEIGHT = 15d; private static final double BOAT_WIDTH = 10d; //Time between sections of race - Should be changed to 200 for actual program. - private static double expectedUpdateInterval = 2000; + private static double expectedUpdateInterval = 200; + private static int WAKE_FRAME_INTERVAL = 40; private Boat boat; + private int wakeCounter = WAKE_FRAME_INTERVAL; public BoatGroup (Boat boat, Color color){ this.boat = boat; @@ -60,11 +62,7 @@ public class BoatGroup extends RaceObject{ velocityObject.setY(VELOCITY_Y_OFFSET); velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - wake.setLayoutX(0); - wake.setLayoutY(0); - wake.relocate(wake.getLayoutX(), wake.getLayoutY()); - - super.getChildren().addAll(boatPoly, wake, teamNameObject, velocityObject); + super.getChildren().addAll(boatPoly, teamNameObject, velocityObject); } private void initChildren (Color color) { @@ -78,10 +76,10 @@ public class BoatGroup extends RaceObject{ * @param dx The amount to move the X coordinate by * @param dy The amount to move the Y coordinate by */ - void moveBy(double dx, double dy, double rotation) { + public void moveGroupBy(double dx, double dy, double rotation) { super.setLayoutX(super.getLayoutX() + dx); super.setLayoutY(super.getLayoutY() + dy); - rotateBoat(rotation); + rotateTo(currentRotation + rotation); } /** @@ -90,8 +88,7 @@ public class BoatGroup extends RaceObject{ * @param y The Y coordinate to move the boat to */ public void moveTo (double x, double y, double rotation) { - super.currentRotation = 0; - rotateBoat(rotation); + rotateTo(rotation); moveTo(x, y); } @@ -109,98 +106,77 @@ public class BoatGroup extends RaceObject{ } else if (rotationalGoal < currentRotation && rotationalVelocity < 0) { rotation = rotationalVelocity * timeInterval; } - moveBy(dx, dy, rotation); - } - - public void setDestination (double newXValue, double newYValue, double rotation) { - this.pixelVelocityX = (newXValue - super.getLayoutX()) / expectedUpdateInterval; - this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; - //this.destinationX = newXValue; - //this.destinationY = newYValue; - this.rotationalGoal = rotation; -// if (super.getLayoutY() >= newYValue && super.getLayoutX() <= newXValue) -// rotationalGoal = 90 - rotationalGoal; -// else if (super.getLayoutY() < newYValue && super.getLayoutX() <= newXValue) -// rotationalGoal = 90 + rotationalGoal; -// else if (super.getLayoutY() >= newYValue && super.getLayoutX() > newXValue) -// rotationalGoal = 270 + rotationalGoal; -// else -// rotationalGoal = 270 - rotationalGoal; -// if (Math.abs(360 - rotationalGoal + currentRotation) < Math.abs(rotationalGoal - currentRotation)) { -// System.out.println("ROTATE"); -// this.rotationalVelocity = (360 - rotationalGoal + currentRotation) / expectedUpdateInterval; -// } else { -// this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; -// } - if (Math.abs(rotationalGoal - currentRotation) > 180) { - if (rotationalGoal - currentRotation >= 0) { - this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; - } else { - this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; - } - } else { - this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; + moveGroupBy(dx, dy, rotation); + for (Node wake : super.getChildren().subList(4, super.getChildren().size())) { + if (!((Wake) wake).updatePosition(timeInterval)) + super.getChildren().remove(wake); + } + if (wakeCounter-- == 0) { + wakeCounter = WAKE_FRAME_INTERVAL; + super.getChildren().add( + new Wake( + super.getLayoutX(), super.getLayoutY(), pixelVelocityX, pixelVelocityY + ) + ); } } - public void setDestination (double newXValue, double newYValue) { - this.pixelVelocityX = (newXValue - super.getLayoutX()) / expectedUpdateInterval; - this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; - //this.destinationX = newXValue; - //this.destinationY = newYValue; - this.rotationalGoal = Math.abs( - Math.toDegrees( - Math.atan( - (newYValue - super.getLayoutY()) / (newXValue - super.getLayoutX()) - ) - ) - ); - if (super.getLayoutY() >= newYValue && super.getLayoutX() <= newXValue) - rotationalGoal = 90 - rotationalGoal; - else if (super.getLayoutY() < newYValue && super.getLayoutX() <= newXValue) - rotationalGoal = 90 + rotationalGoal; - else if (super.getLayoutY() >= newYValue && super.getLayoutX() > newXValue) - rotationalGoal = 270 + rotationalGoal; - else - rotationalGoal = 270 - rotationalGoal; - if (Math.abs(rotationalGoal - currentRotation) > 180) { - if (rotationalGoal - currentRotation >= 0) { - this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; + public void setDestination (double newXValue, double newYValue, double rotation, int... raceIds) { + if (hasRaceId(raceIds)) { + this.pixelVelocityX = (newXValue - super.getLayoutX()) / expectedUpdateInterval; + this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; + this.rotationalGoal = rotation; + if (Math.abs(rotationalGoal - currentRotation) > 180) { + if (rotationalGoal - currentRotation >= 0) { + this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; + } else { + this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; + } } else { - this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; + this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; } - } else { - this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; } } - public void rotateBoat (double rotationDeg) { - currentRotation += rotationDeg; + public void setDestination (double newXValue, double newYValue, int... raceIDs) { + if (hasRaceId(raceIDs)) { + double rotation = Math.abs( + Math.toDegrees( + Math.atan( + (newYValue - super.getLayoutY()) / (newXValue - super.getLayoutX()) + ) + ) + ); + if (super.getLayoutY() >= newYValue && super.getLayoutX() <= newXValue) + rotation = 90 - rotation; + else if (super.getLayoutY() < newYValue && super.getLayoutX() <= newXValue) + rotation = 90 + rotation; + else if (super.getLayoutY() >= newYValue && super.getLayoutX() > newXValue) + rotation = 270 + rotation; + else + rotation = 270 - rotation; + setDestination(newXValue, newYValue, rotation, raceIDs); + } + } + + public void rotateTo (double rotation) { Node boatPoly = super.getChildren().get(0); boatPoly.getTransforms().clear(); - boatPoly.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, 0)); + boatPoly.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, 0)); Node wake = super.getChildren().get(1); wake.getTransforms().clear(); wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); - wake.getTransforms().add(new Rotate(currentRotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); - } - - public static double getExpectedUpdateInterval() { - return expectedUpdateInterval; - } - - public static void setExpectedUpdateInterval(double expectedUpdateInterval) { - BoatGroup.expectedUpdateInterval = expectedUpdateInterval; + wake.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); } public void forceRotation () { - rotateBoat (rotationalGoal - currentRotation); + rotateTo (rotationalGoal); } public void toggleAnnotations () { - super.getChildren().get(1).setVisible(false); - super.getChildren().get(2).setVisible(false); - super.getChildren().get(3).setVisible(false); + for (Node node : super.getChildren().subList(1, super.getChildren().size())) { + node.setVisible(false); + } } public Boat getBoat() { @@ -214,4 +190,8 @@ public class BoatGroup extends RaceObject{ } return false; } + + public int[] getRaceIds () { + return new int[] {boat.getId()}; + } } diff --git a/src/main/java/seng302/models/RaceObject.java b/src/main/java/seng302/models/RaceObject.java index ab831025..70a015ac 100644 --- a/src/main/java/seng302/models/RaceObject.java +++ b/src/main/java/seng302/models/RaceObject.java @@ -8,11 +8,14 @@ import javafx.scene.Group; */ public abstract class RaceObject extends Group { - double rotationalGoal; - double currentRotation; - double rotationalVelocity; - double pixelVelocityX; - double pixelVelocityY; + //Time between sections of race - Should be changed to 200 for actual program. + protected static double expectedUpdateInterval = 2000; + + protected double rotationalGoal; + protected double currentRotation; + protected double rotationalVelocity; + protected double pixelVelocityX; + protected double pixelVelocityY; public boolean isSamePos (Point2D point) { return point.getX() == super.getLayoutX() && point.getY() == super.getLayoutY(); @@ -21,12 +24,33 @@ public abstract class RaceObject extends Group { public Point2D getPosition () { return new Point2D(super.getLayoutX(), getLayoutY()); } - public abstract void setDestination (double x, double y, double rotation); - public abstract void setDestination (double x, double y); + + public static double getExpectedUpdateInterval() { + return expectedUpdateInterval; + } + + public static void setExpectedUpdateInterval(double expectedUpdateInterval) { + RaceObject.expectedUpdateInterval = expectedUpdateInterval; + } + + public abstract void setDestination (double x, double y, double rotation, int... raceIds); + + public abstract void setDestination (double x, double y, int... raceIds); + public abstract void updatePosition (double timeInterval); + public abstract void moveTo (double x, double y, double rotation); + public abstract void moveTo (double x, double y); + + public abstract void moveGroupBy(double x, double y, double rotation); + + public abstract void rotateTo (double rotation); + 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 new file mode 100644 index 00000000..25e5ea59 --- /dev/null +++ b/src/main/java/seng302/models/Wake.java @@ -0,0 +1,37 @@ +package seng302.models; + +import javafx.scene.paint.Color; +import javafx.scene.shape.Arc; +import javafx.scene.shape.ArcType; + +/** + * Created by CJIRWIN on 27/04/2017. + */ +class Wake extends Arc { + + private static int VELOCITY_SCALE_FACTOR = 3; + private static int MAX_LIFESPAN = 180; + private static double LIFESPAN_PER_FRAME = 1.0 / MAX_LIFESPAN; + + private double velocityX; + private double velocityY; + private int lifespan = MAX_LIFESPAN; + + Wake (double startingX, double startingY, double velocityX, double velocityY) { + super(startingX, startingY, 25, 15, 160, 40); + super.setFill(Color.BLUE); + super.setType(ArcType.OPEN); + super.setStrokeWidth(2.0); + this.velocityX = -velocityX; + this.velocityY = -velocityY; + } + + boolean updatePosition (double timeInterval) { + lifespan--; + super.setLayoutX(super.getLayoutX() + velocityX * timeInterval); + super.setLayoutX(super.getLayoutX() + velocityX * timeInterval); + super.setOpacity(LIFESPAN_PER_FRAME * lifespan * super.getOpacity()); + return lifespan == 0; + } + +} diff --git a/src/main/java/seng302/models/mark/GateMark.java b/src/main/java/seng302/models/mark/GateMark.java index 2dfb9fdd..ffcf2b51 100644 --- a/src/main/java/seng302/models/mark/GateMark.java +++ b/src/main/java/seng302/models/mark/GateMark.java @@ -48,15 +48,4 @@ public class GateMark extends Mark { return (this.getSingleMark1().getLongitude()); } - public void assignXYCentered () { - System.out.println("POSSIBLE GOOF " + xValue + " " + yValue); - System.out.println(singleMark1.getX() + " " + singleMark1.getY()); - System.out.println(singleMark2.getX() + " " + singleMark2.getY()); - double dx = singleMark2.getX() - singleMark1.getX(); - System.out.println("dx + " + dx); - double dy = singleMark2.getY() - singleMark1.getY(); - xValue = (int) Math.round(singleMark1.getX() + dx / 2); - yValue = (int) Math.round(singleMark1.getY() + dy / 2); - System.out.println("PROBABLE GAAF " + xValue + " " + yValue); - } } diff --git a/src/main/java/seng302/models/mark/Mark.java b/src/main/java/seng302/models/mark/Mark.java index 57de9ac4..2c086b80 100644 --- a/src/main/java/seng302/models/mark/Mark.java +++ b/src/main/java/seng302/models/mark/Mark.java @@ -11,8 +11,6 @@ public abstract class Mark { private double latitude; private double longitude; private int id; - Integer xValue; - Integer yValue; /** * Create a mark instance by passing its name and type @@ -127,22 +125,6 @@ public abstract class Mark { return longitude; } - public int getX () { - return xValue; - } - - public int getY () { - return yValue; - } - - public void setX (int x) { - this.xValue = x; - } - - public void setY (int y) { - this.yValue = y; - } - public int getId() { return id; } diff --git a/src/main/java/seng302/models/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index 1a56b336..c15d8f8c 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -1,27 +1,35 @@ package seng302.models.mark; import javafx.geometry.Point2D; +import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Line; import javafx.scene.transform.Rotate; import seng302.models.RaceObject; +import java.util.ArrayList; +import java.util.List; + /** * Created by CJIRWIN on 26/04/2017. */ public class MarkGroup extends RaceObject { - private static int MARK_RADIUS = 5; - - private Mark mark; - private double pixelVelocityXM1; - private double pixelVelocityYM1; - private double pixelVelocityXM2; - private double pixelVelocityYM3; + private static int MARK_RADIUS = 5; + private static int LINE_THICKNESS = 2; + private static double DASHED_GAP_LEN = 2d; + private static double DASHED_LINE_LEN = 5d; + private List marks = new ArrayList<>(); + private Mark mainMark; + private double[] nodePixelVelocitiesX; + private double[] nodePixelVelocitiesY; + private Point2D[] nodeDestinations; public MarkGroup (Mark mark, Point2D... points) { + marks.add(mark); + mainMark = mark; Color color = Color.BLACK; if (mark.getName().equals("Start")){ color = Color.GREEN; @@ -31,11 +39,20 @@ public class MarkGroup extends RaceObject { if (mark.getMarkType() == MarkType.SINGLE_MARK) { super.getChildren().add(new Circle(0, 0, MARK_RADIUS, color)); } else { - super.getChildren().add(new Circle(0, 0, MARK_RADIUS, color)); + marks.add(((GateMark) mark).getSingleMark1()); + marks.add(((GateMark) mark).getSingleMark2()); super.getChildren().add( new Circle( - points[1].getX() - points[0].getX(), - points[1].getY() - points[0].getY(), + (points[1].getX() - points[0].getX() / 2), + (points[1].getY() - points[0].getY() / 2), + MARK_RADIUS, + color + ) + ); + super.getChildren().add( + new Circle( + -(points[1].getX() - points[0].getX() / 2), + -(points[1].getY() - points[0].getY() / 2), MARK_RADIUS, color ) @@ -46,33 +63,60 @@ public class MarkGroup extends RaceObject { points[1].getX() - points[0].getX(), points[1].getY() - points[0].getY() ); - line.setStrokeWidth(2); + line.setStrokeWidth(LINE_THICKNESS); if (mark.getMarkType() == MarkType.OPEN_GATE) { - line.getStrokeDashArray().addAll(3d, 5d); + line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN); } super.getChildren().add(line); + nodePixelVelocitiesX = new double[2]; + nodePixelVelocitiesY = new double[2]; + nodeDestinations = new Point2D[2]; } - this.mark = mark; moveTo(points[0].getX(), points[0].getY()); } - public void setDestination (double x, double y, double rotation) { - + public void setDestination (double x, double y, double rotation, int... raceIds) { + setDestination(x, y, raceIds); + this.rotationalGoal = rotation; + if (Math.abs(rotationalGoal - currentRotation) > 180) { + if (rotationalGoal - currentRotation >= 0) { + this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; + } else { + this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; + } + } else { + this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; + } } - public void setDestination (double x, double y) { - + public void setDestination (double x, double y, int... raceIds) { + int childrenIndex = -1; + for (Mark mark : marks) { + for (int id : raceIds) + if (id == mark.getId() && childrenIndex != -1) + setDestinationChild(x, y, childrenIndex); + else if (id == mark.getId()) + setDestinationGroup(x, y); + childrenIndex++; + } } - public void setMarkDestination (int markId, double x, double y) { - - } - public void updatePosition (double timeInterval) { + 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; } - public void moveTo (double x, double y, double rotation) { - moveTo(x, y); + 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( @@ -83,20 +127,74 @@ public class MarkGroup extends RaceObject { ); } + public void updatePosition (double timeInterval) { + double x = pixelVelocityX * timeInterval; + double y = pixelVelocityY * timeInterval; + double rotation = rotationalVelocity * timeInterval; + moveGroupBy(x, y, rotation); + updateChildren(timeInterval); + } + + public void moveGroupBy (double x, double y, double rotation) { + super.setLayoutX(super.getLayoutX() + x); + super.setLayoutY(super.getOpacity() + y); + rotateTo(rotation + currentRotation); + } + + private void updateChildren (double timeInterval) { + if (mainMark.getMarkType() != MarkType.SINGLE_MARK) { + 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); + } + } + } + + public void moveTo (double x, double y, double rotation) { + moveTo(x, y); + rotateTo(rotation); + } + public void moveTo (double x, double y) { super.setLayoutX(x); super.setLayoutY(y); } public boolean hasRaceId (int... raceIds) { - for (int id : raceIds) { + for (int id : raceIds) + for (Mark mark : marks) if (id == mark.getId()) return true; - if (mark.getMarkType() != MarkType.SINGLE_MARK) { - if (id == ((GateMark) mark).getSingleMark1().getId() || id == ((GateMark) mark).getSingleMark2().getId()) - return true; - } - } return false; } public void toggleAnnotations () { @@ -111,4 +209,12 @@ public class MarkGroup extends RaceObject { MARK_RADIUS = markRadius; } + public int[] getRaceIds () { + int[] idArray = new int[marks.size()]; + int i = 0; + for (Mark mark : marks) + idArray[i++] = mark.getId(); + return idArray; + } + } From 65c0e6f77d7582420ad6cf71930d108e1a7b64e7 Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 27 Apr 2017 11:58:50 +1200 Subject: [PATCH 41/97] Fixed markergroup bugs. Improved wakes. Still WIP --- .../seng302/controllers/CanvasController.java | 59 +++++++++------ src/main/java/seng302/models/BoatGroup.java | 39 ++++++++-- src/main/java/seng302/models/Wake.java | 22 ++++-- .../java/seng302/models/mark/MarkGroup.java | 75 ++++++++++++++----- .../seng302/models/parsers/StreamPacket.java | 6 +- .../seng302/models/parsers/StreamParser.java | 3 +- 6 files changed, 142 insertions(+), 62 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 3891f3a1..bfdf6a08 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -37,7 +37,6 @@ public class CanvasController { private ResizableCanvas canvas; private Group group; private GraphicsContext gc; - private List boatGroups = new ArrayList<>(); private final int MARK_SIZE = 10; private final int BUFFER_SIZE = 25; @@ -154,6 +153,7 @@ public class CanvasController { Mark nextMark; //if (countdown == 0) { + //System.out.println("called the at"); for (RaceObject raceObject : raceObjects) { //if (currentRaceMarker[boatIndex] < marks.size()) { //if (currentRaceMarker[boatIndex] == 6) { @@ -166,18 +166,23 @@ public class CanvasController { //descending = nextMark.getY() > boatGroup.getLayoutY(); //leftToRight = nextMark.getX() < boatGroup.getLayoutX(); + raceObject.updatePosition(1000 / 60); for (int id : raceObject.getRaceIds()) { - if (StreamParser.boatPositions.containsKey(id)) { - Point3D p = StreamParser.boatPositions.get((long) id); - Point2D p2d = latLonToXY(p.getX(), p.getY()); - //System.out.println("p2d = " + p2d); + //System.out.println("id = " + id); + if (id != 0 && StreamParser.boatPositions.size() > 0) { + boolean test = StreamParser.boatPositions.containsKey(id); + if (StreamParser.boatPositions.containsKey((long) id)) { + Point3D p = StreamParser.boatPositions.get((long) id); + Point2D p2d = latLonToXY(p.getX(), p.getY()); + //System.out.println("p2d = " + p2d); //System.out.println("p.toString() = " + p.toString()); - double heading = 360.0 / 0xffff * p.getZ(); + double heading = 360.0 / 0xffff * p.getZ(); //System.out.println("heading = " + heading); - raceObject.setDestination(p2d.getX(), p2d.getY(), heading, id); + raceObject.setDestination(p2d.getX(), p2d.getY(), heading, id); + } + StreamParser.boatPositions.remove((long) id); } - StreamParser.boatPositions.remove((long) id); } //Point3D p = StreamParser.boatPositions.get((long) raceObject.getRaceIds()[0]); //System.out.println("boatGroup = " + boatGroup.getBoat().getId()); @@ -292,10 +297,11 @@ public class CanvasController { private void drawBoats() { // Map timelineInfos = raceViewController.getTimelineInfos(); List boats = raceViewController.getStartingBoats(); - Double startingX = group.getChildren().get(0).getLayoutX(); - Double startingY = group.getChildren().get(0).getLayoutY(); - Double firstMarkX = group.getChildren().get(1).getLayoutX(); - Double firstMarkY = group.getChildren().get(1).getLayoutY(); + System.out.println("raceObjects " + raceObjects); + Double startingX = raceObjects.get(0).getLayoutX(); + Double startingY = raceObjects.get(0).getLayoutY(); + Double firstMarkX = raceObjects.get(1).getLayoutX(); + Double firstMarkY = raceObjects.get(1).getLayoutY(); for (Boat boat : boats) { BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); @@ -303,7 +309,7 @@ public class CanvasController { boatGroup.setDestination(firstMarkX, firstMarkY); boatGroup.forceRotation(); group.getChildren().add(boatGroup); - boatGroups.add(boatGroup); + raceObjects.add(boatGroup); // drawBoat(boat.getLongitude(), boat.getLatitude(), boat.getColor(), boat.getShortName(), boat.getSpeedInKnots(), boat.getHeading()); } } @@ -529,14 +535,16 @@ public class CanvasController { */ private void givePointsXY() { //Point2D canvasLocation; -// List allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); - Set unqiuePoints = new HashSet<>(raceViewController.getRace().getCourse()); - System.out.println("unqiuePoints = " + unqiuePoints); + List allPoints = new ArrayList<>(raceViewController.getRace().getCourse()); + List processed = new ArrayList<>(); + //Set unqiuePoints = new HashSet<>(raceViewController.getRace().getCourse()); + //System.out.println("unqiuePoints = " + unqiuePoints); RaceObject markGroup; - for (Mark mark : unqiuePoints) { - if (mark.getMarkType() != MarkType.SINGLE_MARK) { - GateMark gateMark = (GateMark) mark; + for (Mark mark : allPoints) { + if (!processed.contains(mark)) { + if (mark.getMarkType() != MarkType.SINGLE_MARK) { + GateMark gateMark = (GateMark) mark; // canvasLocation = findScaledXY(gateMark.getSingleMark1()); // gateMark.getSingleMark1().setX((int) canvasLocation.getX()); // gateMark.getSingleMark1().setY((int) canvasLocation.getY()); @@ -545,14 +553,17 @@ public class CanvasController { // gateMark.getSingleMark2().setX((int) canvasLocation.getX()); // gateMark.getSingleMark2().setY((int) canvasLocation.getY()); - markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2())); - group.getChildren().add(markGroup); - } - else { + markGroup = new MarkGroup(mark, findScaledXY(gateMark.getSingleMark1()), findScaledXY(gateMark.getSingleMark2())); + group.getChildren().add(markGroup); + raceObjects.add(markGroup); + } else { // canvasLocation = findScaledXY(mark); // mark.setX((int) canvasLocation.getX()); // mark.setY((int) canvasLocation.getY()); - markGroup = new MarkGroup(mark, findScaledXY(mark)); + markGroup = new MarkGroup(mark, findScaledXY(mark)); + group.getChildren().add(markGroup); + } + processed.add(mark); } } } diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index a37f1852..3b314ab8 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -7,6 +7,9 @@ import javafx.scene.text.Text; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; +import java.util.ArrayList; +import java.util.List; + /** * Created by CJIRWIN on 25/04/2017. */ @@ -21,10 +24,11 @@ public class BoatGroup extends RaceObject{ private static final double BOAT_WIDTH = 10d; //Time between sections of race - Should be changed to 200 for actual program. private static double expectedUpdateInterval = 200; - private static int WAKE_FRAME_INTERVAL = 40; + private static int WAKE_FRAME_INTERVAL = 80; private Boat boat; private int wakeCounter = WAKE_FRAME_INTERVAL; + private List wakes = new ArrayList<>(); public BoatGroup (Boat boat, Color color){ this.boat = boat; @@ -107,21 +111,40 @@ public class BoatGroup extends RaceObject{ rotation = rotationalVelocity * timeInterval; } moveGroupBy(dx, dy, rotation); - for (Node wake : super.getChildren().subList(4, super.getChildren().size())) { - if (!((Wake) wake).updatePosition(timeInterval)) +// if (super.getChildren().size() > 3) { +// for (Node wake : super.getChildren().subList(4, super.getChildren().size())) { +// if (!((Wake) wake).updatePosition(timeInterval)) +// super.getChildren().remove(wake); +// } +// } + for (Wake wake : wakes) { + if (wake.updatePosition(timeInterval)) { super.getChildren().remove(wake); + } } if (wakeCounter-- == 0) { wakeCounter = WAKE_FRAME_INTERVAL; - super.getChildren().add( - new Wake( - super.getLayoutX(), super.getLayoutY(), pixelVelocityX, pixelVelocityY - ) - ); + if (pixelVelocityX > 0 && pixelVelocityY > 0) { +// super.getChildren().add( +// new Wake( +// super.getLayoutX() + BOAT_HEIGHT, super.getLayoutY() + BOAT_HEIGHT, pixelVelocityX, pixelVelocityY +// ) +// ); + Wake wake = new Wake( + super.getLayoutX(), + super.getLayoutY(), + pixelVelocityX, + pixelVelocityY + ); + super.getChildren().add(wake); + wakes.add(wake); + } + } } public void setDestination (double newXValue, double newYValue, double rotation, int... raceIds) { + //System.out.println("MADE IT"); if (hasRaceId(raceIds)) { this.pixelVelocityX = (newXValue - super.getLayoutX()) / expectedUpdateInterval; this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; diff --git a/src/main/java/seng302/models/Wake.java b/src/main/java/seng302/models/Wake.java index 25e5ea59..d2d80764 100644 --- a/src/main/java/seng302/models/Wake.java +++ b/src/main/java/seng302/models/Wake.java @@ -10,27 +10,35 @@ import javafx.scene.shape.ArcType; class Wake extends Arc { private static int VELOCITY_SCALE_FACTOR = 3; - private static int MAX_LIFESPAN = 180; + private static int MAX_LIFESPAN = 420; private static double LIFESPAN_PER_FRAME = 1.0 / MAX_LIFESPAN; + //private static double LENGTH_PER_FRAME = 120 / MAX_LIFESPAN; + private static double LENGTH_PER_FRAME = 0.08; private double velocityX; private double velocityY; + private double opacity; private int lifespan = MAX_LIFESPAN; Wake (double startingX, double startingY, double velocityX, double velocityY) { - super(startingX, startingY, 25, 15, 160, 40); - super.setFill(Color.BLUE); + super(0, 0, 20, 30, 180, 0); + //super.setFill(Color.BLUE); + super.setStroke(Color.BLUE); super.setType(ArcType.OPEN); super.setStrokeWidth(2.0); - this.velocityX = -velocityX; - this.velocityY = -velocityY; + this.velocityX = -velocityX / 2; + this.velocityY = -velocityY / 2; } boolean updatePosition (double timeInterval) { lifespan--; + //super.setOpacity(LIFESPAN_PER_FRAME * lifespan * super.getOpacity()); + opacity = LIFESPAN_PER_FRAME * lifespan * opacity; + super.setFill(new Color(0.0f, 0.0f, 1.0f, opacity)); super.setLayoutX(super.getLayoutX() + velocityX * timeInterval); - super.setLayoutX(super.getLayoutX() + velocityX * timeInterval); - super.setOpacity(LIFESPAN_PER_FRAME * lifespan * super.getOpacity()); + super.setLayoutY(super.getLayoutY() + velocityY * timeInterval); + super.setStartAngle(super.getStartAngle() - LENGTH_PER_FRAME); + super.setLength(super.getLength() + LENGTH_PER_FRAME * 2); return lifespan == 0; } diff --git a/src/main/java/seng302/models/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index c15d8f8c..55581ade 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -28,6 +28,10 @@ public class MarkGroup extends RaceObject { private Point2D[] nodeDestinations; public MarkGroup (Mark mark, Point2D... points) { +// for (Point2D p : points) { +// System.out.println("p.getX() = " + p.getX()); +// System.out.println("p.getY() = " + p.getY()); +// } marks.add(mark); mainMark = mark; Color color = Color.BLACK; @@ -36,43 +40,75 @@ public class MarkGroup extends RaceObject { } else if (mark.getName().equals("Finish")){ color = Color.RED; } + System.out.println("HERE ARE THE CHILDREN LOL"); if (mark.getMarkType() == MarkType.SINGLE_MARK) { super.getChildren().add(new Circle(0, 0, MARK_RADIUS, color)); +// System.out.println("SingleMark?"); +// System.out.println("super.getChildren().get(0).getLayoutX() = " + super.getChildren().get(0).getLayoutX()); +// System.out.println("super.getChildren().get(0).getLayoutY() = " + super.getChildren().get(0).getLayoutY()); } else { marks.add(((GateMark) mark).getSingleMark1()); marks.add(((GateMark) mark).getSingleMark2()); super.getChildren().add( new Circle( - (points[1].getX() - points[0].getX() / 2), - (points[1].getY() - points[0].getY() / 2), + (points[1].getX() - points[0].getX()) / 2d, + (points[1].getY() - points[0].getY()) / 2d, MARK_RADIUS, color ) ); +// super.getChildren().add(new Circle(0, 0, MARK_RADIUS, color)); +// super.getChildren().get(0).setLayoutX((points[1].getX() - points[0].getX()) / 2d); +// super.getChildren().get(0).setLayoutY((points[1].getY() - points[0].getY()) / 2d); +// System.out.println("!!!!!!!!!!!!!!!!!"); +// System.out.println((points[1].getX() - points[0].getX()) / 2d); +// System.out.println((points[1].getY() - points[0].getY()) / 2d); +// System.out.println(super.getChildren().get(0)); super.getChildren().add( new Circle( - -(points[1].getX() - points[0].getX() / 2), - -(points[1].getY() - points[0].getY() / 2), + -(points[1].getX() - points[0].getX()) / 2d, + -(points[1].getY() - points[0].getY()) / 2d, MARK_RADIUS, color ) ); +// super.getChildren().add(new Circle(0, 0, MARK_RADIUS, color)); +// super.getChildren().get(1).setLayoutX(-(points[1].getX() - points[0].getX()) / 2d); +// super.getChildren().get(1).setLayoutY(-(points[1].getY() - points[0].getY()) / 2d); Line line = new Line( - 0, - 0, - points[1].getX() - points[0].getX(), - points[1].getY() - points[0].getY() + (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 ); line.setStrokeWidth(LINE_THICKNESS); + line.setStroke(color); if (mark.getMarkType() == MarkType.OPEN_GATE) { line.getStrokeDashArray().addAll(DASHED_GAP_LEN, DASHED_LINE_LEN); } super.getChildren().add(line); - nodePixelVelocitiesX = new double[2]; - nodePixelVelocitiesY = new double[2]; - nodeDestinations = new Point2D[2]; + 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()) + }; +// nodeDestinations = new Point2D[]{new Point2D(0,0), new Point2D(0,0)}; +// System.out.println("super.getChildren().get(0).getLayoutX() = " + super.getChildren().get(0).getLayoutX()); +// System.out.println("super.getChildren().get(0).getLayoutY() = " + super.getChildren().get(0).getLayoutY()); +// System.out.println("super.getChildren().get(1).getLayoutX() = " + super.getChildren().get(1).getLayoutX()); +// System.out.println("super.getChildren().get(1).getLayoutY() = " + super.getChildren().get(1).getLayoutY()); } moveTo(points[0].getX(), points[0].getY()); +// System.out.println("OKAY HERE IS A MARK"); +// System.out.println("super.getLayoutX() = " + super.getLayoutX()); +// System.out.println("super.getLayoutY() = " + super.getLayoutY()); +// System.out.println("super.getChildren().get(0).getLayoutX() = " + super.getChildren().get(0).getLayoutX()); +// System.out.println("super.getChildren().get(0).getLayoutY() = " + super.getChildren().get(0).getLayoutY()); +// pixelVelocityX = 0; +// pixelVelocityY = 0; +// rotationalVelocity = 0; +// rotationalGoal = 0; } public void setDestination (double x, double y, double rotation, int... raceIds) { @@ -118,13 +154,14 @@ public class MarkGroup extends RaceObject { public void rotateTo (double rotation) { super.getTransforms().clear(); - super.getTransforms().add( - new Rotate( - rotation, - super.getChildren().get(1).getLayoutX() - super.getChildren().get(0).getLayoutX(), - super.getChildren().get(1).getLayoutY() - super.getChildren().get(0).getLayoutY() - ) - ); +// super.getTransforms().add( +// new Rotate( +// rotation, +// super.getChildren().get(1).getLayoutX() - super.getChildren().get(0).getLayoutX(), +// super.getChildren().get(1).getLayoutY() - super.getChildren().get(0).getLayoutY() +// ) +// ); + super.getTransforms().add(new Rotate(rotation, 0 , 0)); } public void updatePosition (double timeInterval) { @@ -137,7 +174,7 @@ public class MarkGroup extends RaceObject { public void moveGroupBy (double x, double y, double rotation) { super.setLayoutX(super.getLayoutX() + x); - super.setLayoutY(super.getOpacity() + y); + super.setLayoutY(super.getLayoutY() + y); rotateTo(rotation + currentRotation); } diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index e4b929ca..c27f9654 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -17,11 +17,11 @@ public class StreamPacket { this.messageLength = messageLength; this.timeStamp = timeStamp; this.payload = payload; - System.out.println("type = " + this.type.toString()); + //System.out.println("type = " + this.type.toString()); //switch the packet type to deal with what ever specific packet you want to deal with if (this.type == PacketType.XML_MESSAGE){ - System.out.println("--------"); - System.out.println(new String(payload)); + //System.out.println("--------"); + //System.out.println(new String(payload)); StreamParser.parsePacket(this); } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 438bc5b9..68572c6a 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -96,7 +96,8 @@ public class StreamParser extends Thread{ extractAvgWind(packet); break; default: - System.out.println(packet.getType().toString()); + break; + //System.out.println(packet.getType().toString()); } } From 67a702ffcdea04522529e462fdd88f62c36c0ead Mon Sep 17 00:00:00 2001 From: Calum Date: Thu, 27 Apr 2017 13:57:19 +1200 Subject: [PATCH 42/97] Wakes still broken. Implemented dashed lines that track the progress of individual boats. #implement #story[483] --- .../seng302/controllers/CanvasController.java | 3 + src/main/java/seng302/models/BoatGroup.java | 112 +++++++++++------- src/main/java/seng302/models/RaceObject.java | 14 ++- src/main/java/seng302/models/Wake.java | 25 ++-- .../java/seng302/models/mark/MarkGroup.java | 10 +- .../seng302/models/parsers/StreamPacket.java | 2 +- .../seng302/models/parsers/StreamParser.java | 2 +- 7 files changed, 105 insertions(+), 63 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index bfdf6a08..eb028a3f 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -179,7 +179,10 @@ public class CanvasController { //System.out.println("p.toString() = " + p.toString()); double heading = 360.0 / 0xffff * p.getZ(); //System.out.println("heading = " + heading); + raceObject.setDestination(p2d.getX(), p2d.getY(), heading, id); + + //raceObject.setDestination(p2d.getX(), p2d.getY(), id); } StreamParser.boatPositions.remove((long) id); } diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 3b314ab8..98d108c1 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -1,7 +1,9 @@ package seng302.models; +import javafx.geometry.Point2D; import javafx.scene.Node; import javafx.scene.paint.Color; +import javafx.scene.shape.Line; import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; @@ -24,11 +26,18 @@ public class BoatGroup extends RaceObject{ private static final double BOAT_WIDTH = 10d; //Time between sections of race - Should be changed to 200 for actual program. private static double expectedUpdateInterval = 200; - private static int WAKE_FRAME_INTERVAL = 80; + private static int WAKE_FRAME_INTERVAL = 30; + private double framesForNewLine = 0; + private Point2D lastPoint; private Boat boat; private int wakeCounter = WAKE_FRAME_INTERVAL; private List wakes = new ArrayList<>(); + private List lines = new ArrayList<>(); + private Polygon boatPoly; + private Polygon wakePoly; + private Text teamNameObject; + private Text velocityObject; public BoatGroup (Boat boat, Color color){ this.boat = boat; @@ -41,22 +50,21 @@ public class BoatGroup extends RaceObject{ } private void initChildren (Color color, double... points) { - Polygon boatPoly = new Polygon(points); + boatPoly = new Polygon(points); boatPoly.setFill(color); +// boatPoly.setLayoutX(0); +// boatPoly.setLayoutY(0); +// boatPoly.relocate(boatPoly.getLayoutX(), boatPoly.getLayoutY()); - Polygon wake = new Polygon( + wakePoly = new Polygon( 5.0,0.0, 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO ); - wake.setFill(Color.DARKBLUE); + wakePoly.setFill(Color.DARKBLUE); - Text teamNameObject = new Text(boat.getShortName()); - Text velocityObject = new Text(String.valueOf(boat.getVelocity())); - - boatPoly.setLayoutX(0); - boatPoly.setLayoutY(0); - boatPoly.relocate(boatPoly.getLayoutX(), boatPoly.getLayoutY()); + teamNameObject = new Text(boat.getShortName()); + velocityObject = new Text(String.valueOf(boat.getVelocity())); teamNameObject.setX(TEAMNAME_X_OFFSET); teamNameObject.setY(TEAMNAME_Y_OFFSET); @@ -66,7 +74,7 @@ public class BoatGroup extends RaceObject{ velocityObject.setY(VELOCITY_Y_OFFSET); velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - super.getChildren().addAll(boatPoly, teamNameObject, velocityObject); + super.getChildren().addAll(wakePoly, boatPoly, teamNameObject, velocityObject); } private void initChildren (Color color) { @@ -75,14 +83,21 @@ public class BoatGroup extends RaceObject{ BOAT_WIDTH, BOAT_HEIGHT, 0.0, BOAT_HEIGHT); } + /** * Moves the boat and its children annotations from its current coordinates by specified amounts. * @param dx The amount to move the X coordinate by * @param dy The amount to move the Y coordinate by */ public void moveGroupBy(double dx, double dy, double rotation) { - super.setLayoutX(super.getLayoutX() + dx); - super.setLayoutY(super.getLayoutY() + dy); + boatPoly.setLayoutX(boatPoly.getLayoutX() + dx); + boatPoly.setLayoutY(boatPoly.getLayoutY() + dy); + teamNameObject.setLayoutX(teamNameObject.getLayoutX() + dx); + teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy); + velocityObject.setLayoutX(velocityObject.getLayoutX() + dx); + velocityObject.setLayoutY(velocityObject.getLayoutY() + dy); + wakePoly.setLayoutX(wakePoly.getLayoutX() + dx); + wakePoly.setLayoutY(wakePoly.getLayoutY() + dy); rotateTo(currentRotation + rotation); } @@ -94,11 +109,18 @@ public class BoatGroup extends RaceObject{ public void moveTo (double x, double y, double rotation) { rotateTo(rotation); moveTo(x, y); + } public void moveTo (double x, double y) { - super.setLayoutX(x); - super.setLayoutY(y); + boatPoly.setLayoutX(x); + boatPoly.setLayoutY(y); + teamNameObject.setLayoutX(x); + teamNameObject.setLayoutY(y); + velocityObject.setLayoutX(x); + velocityObject.setLayoutY(y); + wakePoly.setLayoutX(x); + wakePoly.setLayoutY(y); } public void updatePosition (double timeInterval) { @@ -123,6 +145,8 @@ public class BoatGroup extends RaceObject{ } } if (wakeCounter-- == 0) { +// if (boat.getShortName().equals("BAR")) +// System.out.println("thinking"); wakeCounter = WAKE_FRAME_INTERVAL; if (pixelVelocityX > 0 && pixelVelocityY > 0) { // super.getChildren().add( @@ -131,33 +155,37 @@ public class BoatGroup extends RaceObject{ // ) // ); Wake wake = new Wake( - super.getLayoutX(), - super.getLayoutY(), + boatPoly.getLayoutX(), + boatPoly.getLayoutY(), pixelVelocityX, - pixelVelocityY + pixelVelocityY, + currentRotation ); super.getChildren().add(wake); wakes.add(wake); } } + if (framesForNewLine == 0) { + framesForNewLine = 121; + if (lastPoint != null) { + Line l = new Line(lastPoint.getX(), lastPoint.getY(), boatPoly.getLayoutX(), boatPoly.getLayoutY()); + l.getStrokeDashArray().setAll(4d, 4d); + lines.add(l); + super.getChildren().add(l); + } + lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); + } + framesForNewLine -= 1; } public void setDestination (double newXValue, double newYValue, double rotation, int... raceIds) { //System.out.println("MADE IT"); if (hasRaceId(raceIds)) { - this.pixelVelocityX = (newXValue - super.getLayoutX()) / expectedUpdateInterval; - this.pixelVelocityY = (newYValue - super.getLayoutY()) / expectedUpdateInterval; + this.pixelVelocityX = (newXValue - boatPoly.getLayoutX()) / expectedUpdateInterval; + this.pixelVelocityY = (newYValue - boatPoly.getLayoutY()) / expectedUpdateInterval; this.rotationalGoal = rotation; - if (Math.abs(rotationalGoal - currentRotation) > 180) { - if (rotationalGoal - currentRotation >= 0) { - this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; - } else { - this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; - } - } else { - this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; - } + calculateRotationalVelocity(); } } @@ -166,15 +194,15 @@ public class BoatGroup extends RaceObject{ double rotation = Math.abs( Math.toDegrees( Math.atan( - (newYValue - super.getLayoutY()) / (newXValue - super.getLayoutX()) + (newYValue - boatPoly.getLayoutY()) / (newXValue - boatPoly.getLayoutX()) ) ) ); - if (super.getLayoutY() >= newYValue && super.getLayoutX() <= newXValue) + if (boatPoly.getLayoutY() >= newYValue && boatPoly.getLayoutX() <= newXValue) rotation = 90 - rotation; - else if (super.getLayoutY() < newYValue && super.getLayoutX() <= newXValue) + else if (boatPoly.getLayoutY() < newYValue && boatPoly.getLayoutX() <= newXValue) rotation = 90 + rotation; - else if (super.getLayoutY() >= newYValue && super.getLayoutX() > newXValue) + else if (boatPoly.getLayoutY() >= newYValue && boatPoly.getLayoutX() > newXValue) rotation = 270 + rotation; else rotation = 270 - rotation; @@ -183,13 +211,10 @@ public class BoatGroup extends RaceObject{ } public void rotateTo (double rotation) { - Node boatPoly = super.getChildren().get(0); boatPoly.getTransforms().clear(); - boatPoly.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, 0)); - Node wake = super.getChildren().get(1); - wake.getTransforms().clear(); - wake.getTransforms().add(new Translate(0, BOAT_HEIGHT)); - wake.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); + boatPoly.getTransforms().add(new Rotate(rotation, 0, 0)); + wakePoly.getTransforms().clear(); + wakePoly.getTransforms().add(new Rotate(rotation, 0, 0)); } public void forceRotation () { @@ -197,8 +222,13 @@ public class BoatGroup extends RaceObject{ } public void toggleAnnotations () { - for (Node node : super.getChildren().subList(1, super.getChildren().size())) { - node.setVisible(false); + teamNameObject.setVisible(!teamNameObject.isVisible()); + velocityObject.setVisible(!velocityObject.isVisible()); + for (Wake wake : wakes) { + wake.setVisible(!wake.isVisible()); + } + for (Line line : lines) { + line.setVisible(!line.isVisible()); } } diff --git a/src/main/java/seng302/models/RaceObject.java b/src/main/java/seng302/models/RaceObject.java index 70a015ac..a5dcf6f5 100644 --- a/src/main/java/seng302/models/RaceObject.java +++ b/src/main/java/seng302/models/RaceObject.java @@ -9,7 +9,7 @@ import javafx.scene.Group; public abstract class RaceObject extends Group { //Time between sections of race - Should be changed to 200 for actual program. - protected static double expectedUpdateInterval = 2000; + protected static double expectedUpdateInterval = 200; protected double rotationalGoal; protected double currentRotation; @@ -33,6 +33,18 @@ public abstract class RaceObject extends Group { RaceObject.expectedUpdateInterval = expectedUpdateInterval; } + protected void calculateRotationalVelocity () { + if (Math.abs(rotationalGoal - currentRotation) > 180) { + if (rotationalGoal - currentRotation >= 0) { + this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; + } else { + this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; + } + } else { + this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; + } + } + public abstract void setDestination (double x, double y, double rotation, int... raceIds); public abstract void setDestination (double x, double y, int... raceIds); diff --git a/src/main/java/seng302/models/Wake.java b/src/main/java/seng302/models/Wake.java index d2d80764..5dc04f45 100644 --- a/src/main/java/seng302/models/Wake.java +++ b/src/main/java/seng302/models/Wake.java @@ -3,6 +3,7 @@ package seng302.models; import javafx.scene.paint.Color; import javafx.scene.shape.Arc; import javafx.scene.shape.ArcType; +import javafx.scene.transform.Rotate; /** * Created by CJIRWIN on 27/04/2017. @@ -10,36 +11,40 @@ import javafx.scene.shape.ArcType; class Wake extends Arc { private static int VELOCITY_SCALE_FACTOR = 3; - private static int MAX_LIFESPAN = 420; + private static int MAX_LIFESPAN = 210; private static double LIFESPAN_PER_FRAME = 1.0 / MAX_LIFESPAN; //private static double LENGTH_PER_FRAME = 120 / MAX_LIFESPAN; - private static double LENGTH_PER_FRAME = 0.08; + private static double LENGTH_PER_FRAME = 0.25; private double velocityX; private double velocityY; private double opacity; private int lifespan = MAX_LIFESPAN; - Wake (double startingX, double startingY, double velocityX, double velocityY) { - super(0, 0, 20, 30, 180, 0); + Wake (double startingX, double startingY, double velocityX, double velocityY, double rotation) { + super(startingX + 20, startingY + 30, 20, 30, 180, 0); //super.setFill(Color.BLUE); - super.setStroke(Color.BLUE); + super.setStroke(Color.DEEPSKYBLUE); super.setType(ArcType.OPEN); + super.setFill(new Color(0, 0, 0 ,0)); super.setStrokeWidth(2.0); - this.velocityX = -velocityX / 2; - this.velocityY = -velocityY / 2; + super.getTransforms().add(new Rotate(rotation - 270, startingX + 20, startingY + 20)); +// this.velocityX = -velocityX; +// this.velocityY = -velocityY; + this.velocityX = 0; + this.velocityY = 0; } boolean updatePosition (double timeInterval) { lifespan--; //super.setOpacity(LIFESPAN_PER_FRAME * lifespan * super.getOpacity()); - opacity = LIFESPAN_PER_FRAME * lifespan * opacity; - super.setFill(new Color(0.0f, 0.0f, 1.0f, opacity)); + //opacity = LIFESPAN_PER_FRAME * lifespan * opacity; + //super.setFill(new Color(0.0f, 0.0f, 1.0f, opacity)); super.setLayoutX(super.getLayoutX() + velocityX * timeInterval); super.setLayoutY(super.getLayoutY() + velocityY * timeInterval); super.setStartAngle(super.getStartAngle() - LENGTH_PER_FRAME); super.setLength(super.getLength() + LENGTH_PER_FRAME * 2); - return lifespan == 0; + return lifespan < 0; } } diff --git a/src/main/java/seng302/models/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index 55581ade..9f30056f 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -114,15 +114,7 @@ public class MarkGroup extends RaceObject { public void setDestination (double x, double y, double rotation, int... raceIds) { setDestination(x, y, raceIds); this.rotationalGoal = rotation; - if (Math.abs(rotationalGoal - currentRotation) > 180) { - if (rotationalGoal - currentRotation >= 0) { - this.rotationalVelocity = ((rotationalGoal - currentRotation) - 360) / expectedUpdateInterval; - } else { - this.rotationalVelocity = (360 + (rotationalGoal - currentRotation)) / expectedUpdateInterval; - } - } else { - this.rotationalVelocity = (rotationalGoal - currentRotation) / expectedUpdateInterval; - } + calculateRotationalVelocity(); } public void setDestination (double x, double y, int... raceIds) { diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index c27f9654..6ded78d1 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -21,7 +21,7 @@ public class StreamPacket { //switch the packet type to deal with what ever specific packet you want to deal with if (this.type == PacketType.XML_MESSAGE){ //System.out.println("--------"); - //System.out.println(new String(payload)); + System.out.println(new String(payload)); StreamParser.parsePacket(this); } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 68572c6a..f4902423 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -238,7 +238,7 @@ public class StreamParser extends Thread{ long lon = bytesToLong(lonBytes); long heading = bytesToLong(headingBytes); - if ((int)deviceType == 1){ + if ((int)deviceType == 1 || (int)deviceType == 4){ // System.out.println("boatId = " + boatId); // System.out.println("deviceType = " + (long)deviceType); // System.out.println("seq = " + seq); From 104fd86179ea0593e37abf02eae3827a647b0ba4 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Thu, 27 Apr 2017 14:40:26 +1200 Subject: [PATCH 43/97] Got the live ac35 data working in paralell with the parse and the app. #story[820] --- src/main/java/seng302/App.java | 10 ++++++++++ .../java/seng302/controllers/CanvasController.java | 4 +--- .../java/seng302/models/parsers/StreamPacket.java | 10 +++++----- .../java/seng302/models/parsers/StreamParser.java | 14 +++++++++----- .../seng302/models/parsers/StreamReceiver.java | 2 -- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 0637d2cc..ff943a15 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -5,16 +5,26 @@ import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; +import seng302.models.parsers.StreamParser; +import seng302.models.parsers.StreamReceiver; 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"); + sr.start(); + StreamParser streamParser = new StreamParser("TestThread2"); + + streamParser.start(); + } public static void main(String[] args) { diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index eb028a3f..6b3fdb2e 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -135,9 +135,7 @@ public class CanvasController { // e.printStackTrace(); //} - StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1"); -// StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread1"); - sr.start(); + timer = new AnimationTimer() { private int countdown = 60; diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index 6ded78d1..ad945b83 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -19,11 +19,11 @@ public class StreamPacket { this.payload = payload; //System.out.println("type = " + this.type.toString()); //switch the packet type to deal with what ever specific packet you want to deal with - if (this.type == PacketType.XML_MESSAGE){ - //System.out.println("--------"); - System.out.println(new String(payload)); - StreamParser.parsePacket(this); - } +// if (this.type == PacketType.XML_MESSAGE){ +// //System.out.println("--------"); +// System.out.println(new String(payload)); +// StreamParser.parsePacket(this); +// } } PacketType getType() { diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index f4902423..8fb71e85 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -29,21 +29,25 @@ public class StreamParser extends Thread{ private String threadName; private Thread t; - StreamParser(String threadName){ + public StreamParser(String threadName){ this.threadName = threadName; } public void run(){ try { - while (StreamReceiver.packetBuffer.size() <= 1) { + System.out.println("START OF STREAM"); + while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) { Thread.sleep(1); } - StreamPacket packet = StreamReceiver.packetBuffer.take(); - while (packet != null){ + while (StreamReceiver.packetBuffer.peek() != null){ + StreamPacket packet = StreamReceiver.packetBuffer.take(); parsePacket(packet); Thread.sleep(10); - packet = StreamReceiver.packetBuffer.take(); + while (StreamReceiver.packetBuffer.peek() == null) { + Thread.sleep(1); + } } + System.out.println("END OF STREAM"); } catch (Exception e){ e.printStackTrace(); } diff --git a/src/main/java/seng302/models/parsers/StreamReceiver.java b/src/main/java/seng302/models/parsers/StreamReceiver.java index e357101d..20f4756c 100644 --- a/src/main/java/seng302/models/parsers/StreamReceiver.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -39,9 +39,7 @@ public class StreamReceiver extends Thread { }); packetBuffer = pq; connect(); - StreamParser streamParser = new StreamParser("TestThread2"); - streamParser.start(); } public void start () { From 0a2281216523ab371ac3a7d532a24bc9d454b637 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Thu, 27 Apr 2017 16:18:33 +1200 Subject: [PATCH 44/97] Got the live ac35 data working in paralell with the parse and the app. Boats move and the live data works correctly. We need to fix the markers now #story[818] --- src/main/java/seng302/App.java | 4 ++-- src/main/java/seng302/controllers/CanvasController.java | 2 +- src/main/java/seng302/models/parsers/StreamPacket.java | 2 +- src/main/java/seng302/models/parsers/StreamParser.java | 3 +++ src/main/java/seng302/models/parsers/StreamReceiver.java | 1 - 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index ff943a15..78199b58 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -18,8 +18,8 @@ public class App extends Application 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"); +// StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941,"TestThread1"); + StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread1"); sr.start(); StreamParser streamParser = new StreamParser("TestThread2"); diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 6b3fdb2e..0cb6add7 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -594,7 +594,7 @@ public class CanvasController { yAxisLocation += (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); } else if (angleFromReference < 0 && angleFromReference >= -Math.PI / 2) { //System.out.println("3"); - System.out.println(distanceFromReference); +// System.out.println(distanceFromReference); angleFromReference = Math.abs(angleFromReference); System.out.println(Math.cos(angleFromReference) * distanceFromReference); xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index ad945b83..1b0d7f94 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -17,7 +17,7 @@ public class StreamPacket { this.messageLength = messageLength; this.timeStamp = timeStamp; this.payload = payload; - //System.out.println("type = " + this.type.toString()); +// System.out.println("type = " + this.type.toString()); //switch the packet type to deal with what ever specific packet you want to deal with // if (this.type == PacketType.XML_MESSAGE){ // //System.out.println("--------"); diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 8fb71e85..87e9af6b 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -6,6 +6,7 @@ import javafx.geometry.Point3D; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import sun.awt.UNIXToolkit; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -119,6 +120,7 @@ public class StreamParser extends Thread{ int raceStatus = payload[11]; // System.out.println("raceStatus = " + raceStatus); long expectedStartTime = extractTimeStamp(Arrays.copyOfRange(payload,12,18), 6); +// System.out.println("Race starting in: " + expectedStartTime); long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); int noBoats = payload[22]; @@ -133,6 +135,7 @@ 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); } } diff --git a/src/main/java/seng302/models/parsers/StreamReceiver.java b/src/main/java/seng302/models/parsers/StreamReceiver.java index 20f4756c..b6c1e658 100644 --- a/src/main/java/seng302/models/parsers/StreamReceiver.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -39,7 +39,6 @@ public class StreamReceiver extends Thread { }); packetBuffer = pq; connect(); - } public void start () { From f5b9160304475ccd6c5698d53cec2e7183856051 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 27 Apr 2017 17:22:46 +1200 Subject: [PATCH 45/97] Started parsing the different types of XML messages to Map objects so that we can extract the relevant data for the visualizer. #story[820] --- .../seng302/models/parsers/StreamPacket.java | 2 +- .../seng302/models/parsers/StreamParser.java | 89 ++++++++++++++++++- .../models/parsers/StreamReceiver.java | 4 +- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamPacket.java b/src/main/java/seng302/models/parsers/StreamPacket.java index 80e2936e..85711db2 100644 --- a/src/main/java/seng302/models/parsers/StreamPacket.java +++ b/src/main/java/seng302/models/parsers/StreamPacket.java @@ -19,7 +19,7 @@ public class StreamPacket { this.payload = payload; // System.out.println("type = " + type); //switch the packet type to deal with what ever specific packet you want to deal with - if (this.type == PacketType.AVG_WIND){ + if (this.type == PacketType.XML_MESSAGE){ StreamParser.parsePacket(this); } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 6f526ba3..24c3752a 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -2,6 +2,9 @@ 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 org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -13,6 +16,8 @@ import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; /** * Created by kre39 on 23/04/17. @@ -127,6 +132,7 @@ public class StreamParser { while (payloadStream.available() > 0 && (currentChar = payloadStream.read()) != 0) { xmlMessage += (char)currentChar; } + if (xmlMessageSubType == 6) System.out.println(xmlMessage); //Create XML document Object DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); @@ -134,7 +140,12 @@ public class StreamParser { 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. + switch(xmlMessageSubType) { + case 5: parseRegattaXML(doc); + case 6: parseRaceXML(doc); + case 7: parseBoatXML(doc); + } + } catch (ParserConfigurationException e) { e.printStackTrace(); } catch (SAXException e) { @@ -145,6 +156,82 @@ public class StreamParser { } + private static void parseRegattaXML(Document doc) { + Element docEle = doc.getDocumentElement(); + String[] regattaElements = {"RegattaID", "RegattaName", "CourseName", "CentralLatitude", "CentralLongitude", + "CentralAltitude", "UtcOffset", "MagneticVariation", "ShorelineName"}; + Map outputMap = parseAtomicElements(docEle, regattaElements); // Regatta contains only atomic elements + + System.out.println(outputMap); + //return outputMap; + } + + private static void parseRaceXML(Document doc) { + Element docEle = doc.getDocumentElement(); + String[] atomicRaceElements = {"RaceID", "RaceType", "CreationTimeDate"}; + Map outputMap = parseAtomicElements(docEle, atomicRaceElements); + + //Race Start Time + Map raceStartMap = new HashMap<>(); + Node raceStartTime = docEle.getElementsByTagName("RaceStartTime").item(0); + raceStartMap.put("Start", getNodeNamedAttribute(raceStartTime, "Start")); + raceStartMap.put("Postpone", getNodeNamedAttribute(raceStartTime, "Postpone")); + outputMap.put("RaceStartTime", raceStartMap); + + //participants + Map participantMap = new HashMap<>(); + NodeList participants = docEle.getElementsByTagName("Participants").item(0).getChildNodes(); + for (int i = 0; i < participants.getLength(); i++) { + Integer sourceID = null; + String entry = null; + Node participant = participants.item(i); + if (participant.getNodeName().equals("Yacht")) { + //sourceID = Integer.parseInt(participant.getAttributes().getNamedItem("SourceID").getTextContent()); + sourceID = Integer.parseInt(getNodeNamedAttribute(participant, "SourceID")); + if (participant.getAttributes().getLength() == 2) { + entry = getNodeNamedAttribute(participant, "Entry"); + } + participantMap.put(sourceID, entry); + } + } + outputMap.put("Participants", participantMap); + + //Course + + System.out.println(outputMap); + } + + private static void parseBoatXML(Document doc) { + // TODO: 27/04/17 ajm412 + } + + private static String getNodeNamedAttribute(Node n, String attr) { + return n.getAttributes().getNamedItem(attr).getTextContent(); + } + + private static Map parseAtomicElements(Element docEle, String[] elements) { + Map outputMap = new HashMap<>(); + for (String element : elements) { + Object elementValue = null; + if (docEle.getElementsByTagName(element).getLength() == 1) { + String elementText = docEle.getElementsByTagName(element).item(0).getTextContent(); + // TODO: 27/04/17 ajm412: this seems messy, trying to parse values as ints/doubles to the map rather than as Strings. Possibly use RegEx. + try { + elementValue = Integer.parseInt(elementText); + } catch (NumberFormatException nfe1) { + try { + elementValue = Double.parseDouble(elementText); + } catch (NumberFormatException nfe2) { + elementValue = elementText; + } + } + } + outputMap.put(element, elementValue); + } + + return outputMap; + } + private static void extractRaceStartStatus(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; diff --git a/src/main/java/seng302/models/parsers/StreamReceiver.java b/src/main/java/seng302/models/parsers/StreamReceiver.java index d5127ce4..128683a3 100644 --- a/src/main/java/seng302/models/parsers/StreamReceiver.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -4,8 +4,6 @@ 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; @@ -129,7 +127,7 @@ public class StreamReceiver { } }); StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, pq); -// StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, pq); + //StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, pq); sr.connect(); } } From e1de5e0989d90d235f511140c8b50ff8a74d1c80 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Thu, 27 Apr 2017 18:31:13 +1200 Subject: [PATCH 46/97] Parsed more course data from XML messages #story[820] --- .../seng302/models/parsers/StreamParser.java | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 24c3752a..0c982ec6 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -14,10 +14,7 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * Created by kre39 on 23/04/17. @@ -130,7 +127,7 @@ public class StreamParser { //Converts XML message to string to be parsed int currentChar; while (payloadStream.available() > 0 && (currentChar = payloadStream.read()) != 0) { - xmlMessage += (char)currentChar; + xmlMessage += (char)currentChar; } if (xmlMessageSubType == 6) System.out.println(xmlMessage); @@ -167,6 +164,7 @@ public class StreamParser { } private static void parseRaceXML(Document doc) { + // TODO: 27/04/17 ajm412 This is an extremely long method. Needs to be broken down. Element docEle = doc.getDocumentElement(); String[] atomicRaceElements = {"RaceID", "RaceType", "CreationTimeDate"}; Map outputMap = parseAtomicElements(docEle, atomicRaceElements); @@ -196,7 +194,37 @@ public class StreamParser { } outputMap.put("Participants", participantMap); - //Course + //Course - Order matters. + Map courseMap = new TreeMap<>(); + NodeList course = docEle.getElementsByTagName("Course").item(0).getChildNodes(); + for (int i = 0; i < course.getLength(); i++) { + Integer compoundMarkID = null; + String name = null; + Map compoundMarkMap = new TreeMap<>(); + Node compoundMark = course.item(i); + if (compoundMark.getNodeName().equals("CompoundMark")) { + compoundMarkID = Integer.parseInt(getNodeNamedAttribute(compoundMark, "CompoundMarkID")); + name = getNodeNamedAttribute(compoundMark, "Name"); + //get marks for compound mark + NodeList marks = compoundMark.getChildNodes(); + for (int j = 0; j < marks.getLength(); j++) { + Map markMap = new TreeMap<>(); + Node mark = marks.item(j); + if (mark.getNodeName().equals("Mark")) { + markMap.put("Name", getNodeNamedAttribute(mark, "Name")); + markMap.put("TargetLat", Double.parseDouble(getNodeNamedAttribute(mark, "TargetLat"))); + markMap.put("TargetLng", Double.parseDouble(getNodeNamedAttribute(mark, "TargetLng"))); + markMap.put("SourceID", Integer.parseInt(getNodeNamedAttribute(mark, "SourceID"))); + + compoundMarkMap.put(Integer.parseInt(getNodeNamedAttribute(mark, "SeqID")), markMap); + + } + } + + } + courseMap.put(compoundMarkID, compoundMarkMap); + } + //outputMap.put("Course", courseMap); System.out.println(outputMap); } From d1289b0de10c95c5171a3e8858c3662b47e450a6 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Thu, 27 Apr 2017 18:52:37 +1200 Subject: [PATCH 47/97] Fixed boats moving in the correct heading as according to the stream and attempted to fix the wakes direction. #story[818] --- src/main/java/seng302/App.java | 1 - .../seng302/controllers/CanvasController.java | 4 +- src/main/java/seng302/models/BoatGroup.java | 65 ++++++++++--------- src/main/java/seng302/models/Wake.java | 5 +- .../java/seng302/models/mark/MarkGroup.java | 2 +- .../seng302/models/parsers/StreamParser.java | 26 ++++++-- 6 files changed, 62 insertions(+), 41 deletions(-) diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 78199b58..f6630e7c 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -22,7 +22,6 @@ public class App extends Application StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, "TestThread1"); sr.start(); StreamParser streamParser = new StreamParser("TestThread2"); - streamParser.start(); } diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 0cb6add7..37ef3193 100644 --- a/src/main/java/seng302/controllers/CanvasController.java +++ b/src/main/java/seng302/controllers/CanvasController.java @@ -39,7 +39,7 @@ public class CanvasController { private GraphicsContext gc; private final int MARK_SIZE = 10; - private final int BUFFER_SIZE = 25; + private final int BUFFER_SIZE = 100; private final int CANVAS_WIDTH = 1000; private final int CANVAS_HEIGHT = 1000; private final int LHS_BUFFER = BUFFER_SIZE; @@ -596,7 +596,7 @@ public class CanvasController { //System.out.println("3"); // System.out.println(distanceFromReference); angleFromReference = Math.abs(angleFromReference); - System.out.println(Math.cos(angleFromReference) * distanceFromReference); +// System.out.println(Math.cos(angleFromReference) * distanceFromReference); xAxisLocation -= (int) Math.round(distanceScaleFactor * Math.sin(angleFromReference) * distanceFromReference); yAxisLocation -= (int) Math.round(distanceScaleFactor * Math.cos(angleFromReference) * distanceFromReference); } else { diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 98d108c1..44f5aa1f 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -35,7 +35,7 @@ public class BoatGroup extends RaceObject{ private List wakes = new ArrayList<>(); private List lines = new ArrayList<>(); private Polygon boatPoly; - private Polygon wakePoly; +// private Polygon wakePoly; private Text teamNameObject; private Text velocityObject; @@ -55,13 +55,13 @@ public class BoatGroup extends RaceObject{ // boatPoly.setLayoutX(0); // boatPoly.setLayoutY(0); // boatPoly.relocate(boatPoly.getLayoutX(), boatPoly.getLayoutY()); - - wakePoly = new Polygon( - 5.0,0.0, - 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, - 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO - ); - wakePoly.setFill(Color.DARKBLUE); +// +// wakePoly = new Polygon( +// 5.0,0.0, +// 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, +// 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO +// ); +// wakePoly.setFill(Color.DARKBLUE); teamNameObject = new Text(boat.getShortName()); velocityObject = new Text(String.valueOf(boat.getVelocity())); @@ -74,7 +74,8 @@ public class BoatGroup extends RaceObject{ velocityObject.setY(VELOCITY_Y_OFFSET); velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - super.getChildren().addAll(wakePoly, boatPoly, teamNameObject, velocityObject); +// super.getChildren().addAll(wakePoly, boatPoly, teamNameObject, velocityObject); + super.getChildren().addAll(teamNameObject, velocityObject, boatPoly); } private void initChildren (Color color) { @@ -96,9 +97,9 @@ public class BoatGroup extends RaceObject{ teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy); velocityObject.setLayoutX(velocityObject.getLayoutX() + dx); velocityObject.setLayoutY(velocityObject.getLayoutY() + dy); - wakePoly.setLayoutX(wakePoly.getLayoutX() + dx); - wakePoly.setLayoutY(wakePoly.getLayoutY() + dy); - rotateTo(currentRotation + rotation); +// wakePoly.setLayoutX(wakePoly.getLayoutX() + dx); +// wakePoly.setLayoutY(wakePoly.getLayoutY() + dy); + rotateTo(currentRotation); } /** @@ -109,7 +110,6 @@ public class BoatGroup extends RaceObject{ public void moveTo (double x, double y, double rotation) { rotateTo(rotation); moveTo(x, y); - } public void moveTo (double x, double y) { @@ -119,8 +119,8 @@ public class BoatGroup extends RaceObject{ teamNameObject.setLayoutY(y); velocityObject.setLayoutX(x); velocityObject.setLayoutY(y); - wakePoly.setLayoutX(x); - wakePoly.setLayoutY(y); +// wakePoly.setLayoutX(x); +// wakePoly.setLayoutY(y); } public void updatePosition (double timeInterval) { @@ -158,9 +158,9 @@ public class BoatGroup extends RaceObject{ boatPoly.getLayoutX(), boatPoly.getLayoutY(), pixelVelocityX, - pixelVelocityY, - currentRotation - ); + pixelVelocityY, rotation); +// wake.getTransforms().clear(); +// wake.getTransforms().add(new Rotate(rotation, 0, 0)); super.getChildren().add(wake); wakes.add(wake); } @@ -186,6 +186,7 @@ public class BoatGroup extends RaceObject{ this.pixelVelocityY = (newYValue - boatPoly.getLayoutY()) / expectedUpdateInterval; this.rotationalGoal = rotation; calculateRotationalVelocity(); + rotateTo(rotation); } } @@ -198,23 +199,27 @@ public class BoatGroup extends RaceObject{ ) ) ); - if (boatPoly.getLayoutY() >= newYValue && boatPoly.getLayoutX() <= newXValue) - rotation = 90 - rotation; - else if (boatPoly.getLayoutY() < newYValue && boatPoly.getLayoutX() <= newXValue) - rotation = 90 + rotation; - else if (boatPoly.getLayoutY() >= newYValue && boatPoly.getLayoutX() > newXValue) - rotation = 270 + rotation; - else - rotation = 270 - rotation; + +// if (boatPoly.getLayoutY() >= newYValue && boatPoly.getLayoutX() <= newXValue) +// rotation = 90 - rotation; +// else if (boatPoly.getLayoutY() < newYValue && boatPoly.getLayoutX() <= newXValue) +// rotation = 90 + rotation; +// else if (boatPoly.getLayoutY() >= newYValue && boatPoly.getLayoutX() > newXValue) +// rotation = 270 + rotation; +// else +// rotation = 270 - rotation; setDestination(newXValue, newYValue, rotation, raceIDs); } } public void rotateTo (double rotation) { - boatPoly.getTransforms().clear(); - boatPoly.getTransforms().add(new Rotate(rotation, 0, 0)); - wakePoly.getTransforms().clear(); - wakePoly.getTransforms().add(new Rotate(rotation, 0, 0)); + if(rotation != 0) { + rotationalGoal = rotation; + boatPoly.getTransforms().clear(); + boatPoly.getTransforms().add(new Rotate(rotation, BOAT_WIDTH / 2, 0)); + } +// wakePoly.getTransforms().clear(); +// wakePoly.getTransforms().add(new Rotate(rotation, 0, 0)); } public void forceRotation () { diff --git a/src/main/java/seng302/models/Wake.java b/src/main/java/seng302/models/Wake.java index 5dc04f45..92b2d30d 100644 --- a/src/main/java/seng302/models/Wake.java +++ b/src/main/java/seng302/models/Wake.java @@ -4,6 +4,7 @@ import javafx.scene.paint.Color; import javafx.scene.shape.Arc; import javafx.scene.shape.ArcType; import javafx.scene.transform.Rotate; +import javafx.scene.transform.Translate; /** * Created by CJIRWIN on 27/04/2017. @@ -22,13 +23,13 @@ class Wake extends Arc { private int lifespan = MAX_LIFESPAN; Wake (double startingX, double startingY, double velocityX, double velocityY, double rotation) { - super(startingX + 20, startingY + 30, 20, 30, 180, 0); + super(startingX, startingY, 20, 30, 180, 0); //super.setFill(Color.BLUE); super.setStroke(Color.DEEPSKYBLUE); super.setType(ArcType.OPEN); super.setFill(new Color(0, 0, 0 ,0)); super.setStrokeWidth(2.0); - super.getTransforms().add(new Rotate(rotation - 270, startingX + 20, startingY + 20)); + super.getTransforms().add(new Rotate(rotation, 5, -15)); // this.velocityX = -velocityX; // this.velocityY = -velocityY; this.velocityX = 0; diff --git a/src/main/java/seng302/models/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index 9f30056f..eba7da82 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -167,7 +167,7 @@ public class MarkGroup extends RaceObject { public void moveGroupBy (double x, double y, double rotation) { super.setLayoutX(super.getLayoutX() + x); super.setLayoutY(super.getLayoutY() + y); - rotateTo(rotation + currentRotation); + rotateTo(rotation); } private void updateChildren (double timeInterval) { diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 87e9af6b..f49e4d31 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -14,10 +14,9 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -29,6 +28,7 @@ public class StreamParser extends Thread{ private static ArrayList boat_IDS = new ArrayList<>(); private String threadName; private Thread t; + private static boolean raceStarted = false; public StreamParser(String threadName){ this.threadName = threadName; @@ -120,7 +120,23 @@ public class StreamParser extends Thread{ int raceStatus = payload[11]; // System.out.println("raceStatus = " + raceStatus); long expectedStartTime = extractTimeStamp(Arrays.copyOfRange(payload,12,18), 6); -// System.out.println("Race starting in: " + expectedStartTime); + DateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + long timeTillStart = ((new Date (expectedStartTime)).getTime() - (new Date (currentTime)).getTime())/1000; + if (timeTillStart > 0 && timeTillStart % 10 == 0) { + System.out.println("Time till start: " + timeTillStart + " Seconds"); + } else { + if (raceStatus == 4 || raceStatus == 8){ + System.out.println("RACE HAS FINISHED"); + } else if (!raceStarted){ + raceStarted = true; + System.out.println("RACE HAS STARTED"); + } + if (timeTillStart % 10 == 0){ + System.out.println("Time since start: " + -1 * timeTillStart + " Seconds"); + + } + } long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); long windSpeed = bytesToLong(Arrays.copyOfRange(payload,20,22)); int noBoats = payload[22]; From 705a0a2eaf938c664d85df4c708b8f088869a631 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Fri, 28 Apr 2017 14:34:24 +1200 Subject: [PATCH 48/97] Added document and unit tests for GeoUtility class. - three methods in GeoUtility have been tested and passed. #story[828] --- src/main/java/seng302/App.java | 4 +- .../seng302/server/simulator/GeoUtility.java | 8 +- .../server/simulator/GeoUtilityTest.java | 75 +++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 src/test/java/seng302/server/simulator/GeoUtilityTest.java diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 6efce9e7..caa4313e 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -24,6 +24,4 @@ public class App extends Application new Thread(new Simulator(1000)).run(); launch(args); } -} - - +} \ No newline at end of file diff --git a/src/main/java/seng302/server/simulator/GeoUtility.java b/src/main/java/seng302/server/simulator/GeoUtility.java index 288e0ee1..dff67e50 100644 --- a/src/main/java/seng302/server/simulator/GeoUtility.java +++ b/src/main/java/seng302/server/simulator/GeoUtility.java @@ -33,8 +33,14 @@ public class GeoUtility { * * @param p1 the first geographical position, start point * @param p2 the second geographical position, end point - * @return the bearing in degree from p1 to p2, value range (0 ~ 360 deg.). + * @return the initial bearing in degree from p1 to p2, value range (0 ~ 360 deg.). * vertical up is 0 deg. horizontal right is 90 deg. + * + * NOTE: + * The final bearing will differ from the initial bearing by varying degrees + * according to distance and latitude (if you were to go from say 35°N,45°E + * (≈ Baghdad) to 35°N,135°E (≈ Osaka), you would start on a heading of 60° + * and end up on a heading of 120° */ public static Double getBearing(Position p1, Position p2) { diff --git a/src/test/java/seng302/server/simulator/GeoUtilityTest.java b/src/test/java/seng302/server/simulator/GeoUtilityTest.java new file mode 100644 index 00000000..4cdf01df --- /dev/null +++ b/src/test/java/seng302/server/simulator/GeoUtilityTest.java @@ -0,0 +1,75 @@ +package seng302.server.simulator; + +import org.junit.Test; +import seng302.server.simulator.mark.Position; + +import static org.junit.Assert.*; + +/** + * To test methods in GeoUtility. + * Created by Haoming on 28/04/17. + */ +public class GeoUtilityTest { + + private Position p1 = new Position(57.670333, 11.827833); + private Position p2 = new Position(57.671524, 11.844495); + private Position p3 = new Position(57.670822, 11.843392); + private Position p4 = new Position(25.694829, 98.392049); + + private double toleranceRate = 0.01; + + @Test + public void getDistance() throws Exception { + double expected, actual; + + actual = GeoUtility.getDistance(p1, p2); + expected = 1000; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getDistance(p1, p3); + expected = 927; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getDistance(p2, p4); + expected = 7430180; + assertEquals(expected, actual, expected * toleranceRate); + } + + @Test + public void getBearing() throws Exception { + double expected, actual; + + actual = GeoUtility.getBearing(p1, p2); + expected = 82; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getBearing(p1, p3); + expected = 86; + assertEquals(expected, actual, expected * toleranceRate); + + actual = GeoUtility.getBearing(p2, p4); + expected = 78; + assertEquals(expected, actual, expected * toleranceRate); + } + + @Test + public void getGeoCoordinate() throws Exception { + Position expected, actual; + + actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0); + expected = p2; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + + actual = GeoUtility.getGeoCoordinate(p1, 86.0, 927.0); + expected = p3; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + + actual = GeoUtility.getGeoCoordinate(p2, 78.0, 7430180.0); + expected = p4; + assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate); + assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate); + } + +} \ No newline at end of file From 8a04a0e5b741f0907aaa2337f2e0ffe620e3508c Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Fri, 28 Apr 2017 14:53:26 +1200 Subject: [PATCH 49/97] Added documents for Boat, RaceParser and Simulator classes. #story[828] --- src/main/java/seng302/server/simulator/Boat.java | 1 - .../java/seng302/server/simulator/Simulator.java | 11 ++++++++--- .../server/simulator/parsers/RaceParser.java | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/seng302/server/simulator/Boat.java b/src/main/java/seng302/server/simulator/Boat.java index 3fd6eb87..c5b821bf 100644 --- a/src/main/java/seng302/server/simulator/Boat.java +++ b/src/main/java/seng302/server/simulator/Boat.java @@ -11,7 +11,6 @@ public class Boat { private double speed; // in mm/sec private String boatName, shortName, shorterName; - // haven't been used so far private Corner lastPassedCorner, headingCorner; public Boat(int sourceID, String boatName) { diff --git a/src/main/java/seng302/server/simulator/Simulator.java b/src/main/java/seng302/server/simulator/Simulator.java index f76d0eec..1d285884 100644 --- a/src/main/java/seng302/server/simulator/Simulator.java +++ b/src/main/java/seng302/server/simulator/Simulator.java @@ -62,11 +62,11 @@ public class Simulator extends Observable implements Runnable { } /** - * Moves a boat with give time duration. + * Moves a boat with given time duration. * * @param boat the boat to be moved - * @param duration the moving duration in millisecond - * @return 1 if boat reached final line, otherwise 0 + * @param duration the moving duration in milliseconds + * @return 1 if the boat has reached the final line, otherwise return 0 */ private int moveBoat(Boat boat, double duration) { if (boat.getHeadingCorner() != null) { @@ -100,6 +100,11 @@ public class Simulator extends Observable implements Runnable { return 0; } + /** + * Link all the corners in the course list so that every corner knows its next + * corner, as well as the distance and bearing to its next corner. However, + * the last corner's heading is null, which means it is the final line. + */ private void setLegs() { // get the bearing from one mark to the next heading mark for (int i = 0; i < course.size() - 1; i++) { diff --git a/src/main/java/seng302/server/simulator/parsers/RaceParser.java b/src/main/java/seng302/server/simulator/parsers/RaceParser.java index 363da64f..14bf7bb8 100644 --- a/src/main/java/seng302/server/simulator/parsers/RaceParser.java +++ b/src/main/java/seng302/server/simulator/parsers/RaceParser.java @@ -9,7 +9,6 @@ import seng302.server.simulator.mark.Corner; import java.util.ArrayList; import java.util.List; -import java.util.Map; /** * Parses the race xml file to get course details @@ -26,11 +25,20 @@ public class RaceParser extends FileParser { this.doc = this.parseFile(); } + /** + * Parses race.xml file and returns a list of corner which is the race course. + * @return a list of ordered corner to represent the course. + */ public List getCourse() { CourseParser cp = new CourseParser(path); return cp.getCourse(); } + /** + * Parses race.xml file and return a list of boats which will compete in the + * race. + * @return a list of boats that are going to compete in the race. + */ public List getBoats() { NodeList yachts = doc.getDocumentElement().getElementsByTagName("Yacht"); List boats = new ArrayList<>(); @@ -41,6 +49,11 @@ public class RaceParser extends FileParser { return boats; } + /** + * Parses a single boat from the given node + * @param node a node within a boat tag + * @return a boat instance parsed from the given node + */ private Boat getBoat(Node node) { if (node.getNodeType() == Node.ELEMENT_NODE) { Element e = (Element) node; From fe480d5cb6c41ff03f50931646806dde29f08e08 Mon Sep 17 00:00:00 2001 From: Alistair McIntyre Date: Fri, 28 Apr 2017 15:41:12 +1200 Subject: [PATCH 50/97] Finished parsing the Race XML data. Began making some optimizations to hopefully make parsing the Boat Data a quicker and simpler task. #story[820] --- .../seng302/models/parsers/StreamParser.java | 84 +++++++++++++++---- .../models/parsers/StreamReceiver.java | 4 +- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 0c982ec6..dff14a7f 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -159,7 +159,7 @@ public class StreamParser { "CentralAltitude", "UtcOffset", "MagneticVariation", "ShorelineName"}; Map outputMap = parseAtomicElements(docEle, regattaElements); // Regatta contains only atomic elements - System.out.println(outputMap); + //System.out.println(outputMap); //return outputMap; } @@ -176,30 +176,56 @@ public class StreamParser { raceStartMap.put("Postpone", getNodeNamedAttribute(raceStartTime, "Postpone")); outputMap.put("RaceStartTime", raceStartMap); - //participants - Map participantMap = new HashMap<>(); + //Race Participants NodeList participants = docEle.getElementsByTagName("Participants").item(0).getChildNodes(); + outputMap.put("Participants", parseRaceParticipants(participants)); + + //Course (CompoundMarks) + NodeList course = docEle.getElementsByTagName("Course").item(0).getChildNodes(); + outputMap.put("Course", parseCourse(course)); + + //CompoundMark Sequence + NodeList markSequence = docEle.getElementsByTagName("CompoundMarkSequence").item(0).getChildNodes(); + outputMap.put("CourseMarkSequence", parseMarkSequence(markSequence)); + + //Course Limits + NodeList courseLimits = docEle.getElementsByTagName("CourseLimit").item(0).getChildNodes(); + outputMap.put("CourseLimit", parseCourseLimits(courseLimits)); + + System.out.println(outputMap.get("Course")); +// for (Map.Entry entry : outputMap.entrySet()) { +// System.out.println(entry); +// } + } + + private static ArrayList parseRaceParticipants(NodeList participants) { + ArrayList participantList = new ArrayList<>(); for (int i = 0; i < participants.getLength(); i++) { + Map participantMap = new HashMap<>(); Integer sourceID = null; String entry = null; Node participant = participants.item(i); if (participant.getNodeName().equals("Yacht")) { - //sourceID = Integer.parseInt(participant.getAttributes().getNamedItem("SourceID").getTextContent()); sourceID = Integer.parseInt(getNodeNamedAttribute(participant, "SourceID")); if (participant.getAttributes().getLength() == 2) { entry = getNodeNamedAttribute(participant, "Entry"); } - participantMap.put(sourceID, entry); + participantMap.put("sourceID", sourceID); + participantMap.put("Entry", entry); + participantList.add(participantMap); } } - outputMap.put("Participants", participantMap); + return participantList; + } - //Course - Order matters. + private static Map parseCourse(NodeList course) { Map courseMap = new TreeMap<>(); - NodeList course = docEle.getElementsByTagName("Course").item(0).getChildNodes(); + ArrayList courseList = new ArrayList<>(); for (int i = 0; i < course.getLength(); i++) { + Integer compoundMarkID = null; String name = null; + //map for an individual CompoundMark Map compoundMarkMap = new TreeMap<>(); Node compoundMark = course.item(i); if (compoundMark.getNodeName().equals("CompoundMark")) { @@ -208,6 +234,7 @@ public class StreamParser { //get marks for compound mark NodeList marks = compoundMark.getChildNodes(); for (int j = 0; j < marks.getLength(); j++) { + //map for individual mark details within a compound mark Map markMap = new TreeMap<>(); Node mark = marks.item(j); if (mark.getNodeName().equals("Mark")) { @@ -215,18 +242,47 @@ public class StreamParser { markMap.put("TargetLat", Double.parseDouble(getNodeNamedAttribute(mark, "TargetLat"))); markMap.put("TargetLng", Double.parseDouble(getNodeNamedAttribute(mark, "TargetLng"))); markMap.put("SourceID", Integer.parseInt(getNodeNamedAttribute(mark, "SourceID"))); - compoundMarkMap.put(Integer.parseInt(getNodeNamedAttribute(mark, "SeqID")), markMap); - } } - + courseMap.put(compoundMarkID, compoundMarkMap); } - courseMap.put(compoundMarkID, compoundMarkMap); } - //outputMap.put("Course", courseMap); - System.out.println(outputMap); + return courseMap; + } + + private static Map parseMarkSequence(NodeList markSequence) { + Map markSequenceMap = new TreeMap<>(); + + for (int i = 0; i < markSequence.getLength(); i++) { + Map cornerMap = new TreeMap<>(); + Node corner = markSequence.item(i); + if (corner.getNodeName().equals("Corner")) { + cornerMap.put("CompoundMarkID", Integer.parseInt(getNodeNamedAttribute(corner, "CompoundMarkID"))); + cornerMap.put("Rounding", getNodeNamedAttribute(corner, "Rounding")); + cornerMap.put("ZoneSize", getNodeNamedAttribute(corner, "ZoneSize")); + markSequenceMap.put(Integer.parseInt(getNodeNamedAttribute(corner, "SeqID")), cornerMap); + } + } + + return markSequenceMap; + } + + private static Map parseCourseLimits(NodeList courseLimits) { + Map courseLimitMap = new TreeMap<>(); + + for (int i = 0; i < courseLimits.getLength(); i++) { + Map limitMap = new HashMap<>(); + Node limit = courseLimits.item(i); + if (limit.getNodeName().equals("Limit")) { + limitMap.put("Lat", Double.parseDouble(getNodeNamedAttribute(limit,"Lat"))); + limitMap.put("Lon", Double.parseDouble(getNodeNamedAttribute(limit,"Lon"))); + courseLimitMap.put(Integer.parseInt(getNodeNamedAttribute(limit, "SeqID")), limitMap); + } + } + + return courseLimitMap; } private static void parseBoatXML(Document doc) { diff --git a/src/main/java/seng302/models/parsers/StreamReceiver.java b/src/main/java/seng302/models/parsers/StreamReceiver.java index 128683a3..31912815 100644 --- a/src/main/java/seng302/models/parsers/StreamReceiver.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -126,8 +126,8 @@ public class StreamReceiver { return (int) (s1.getTimeStamp() - s2.getTimeStamp()); } }); - StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, pq); - //StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, pq); + //StreamReceiver sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, pq); + StreamReceiver sr = new StreamReceiver("livedata.americascup.com", 4941, pq); sr.connect(); } } From 0f4ad48de07daa911cc382dcaabdfd6fdd570a9c Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Fri, 28 Apr 2017 16:41:35 +1200 Subject: [PATCH 51/97] Fixed and enables the old wakes. Enabled the fps counter by implementing the team-27s fps counter from their code, fixed trails from starting at the start of the startline no matter at what point in the race the stream is connected to (this is means the map starts a lot cleaner). Added live tracked speeds which are taken from the boat location packet. Linked the speeds coming in to their specified boats and allowed the onscreen speed tracker to keep up with the speeds. Linked the current speeds to the wakes so the wakes are redrawn for each change in speed and size to match the speed. Also added the toggle functionality back to the fps counter so they can be toggled on an off. #story[818] --- .../seng302/controllers/CanvasController.java | 33 +++++++++- .../controllers/RaceViewController.java | 1 + src/main/java/seng302/models/Boat.java | 1 + src/main/java/seng302/models/BoatGroup.java | 65 +++++++++++++------ src/main/java/seng302/models/Wake.java | 2 +- .../seng302/models/parsers/StreamParser.java | 16 ++++- src/main/resources/config/teams.xml | 12 ++-- src/main/resources/views/RaceView.fxml | 19 +++--- 8 files changed, 110 insertions(+), 39 deletions(-) diff --git a/src/main/java/seng302/controllers/CanvasController.java b/src/main/java/seng302/controllers/CanvasController.java index 37ef3193..dbce6849 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.StreamParser; import seng302.models.parsers.StreamReceiver; import java.sql.Time; +import java.text.DecimalFormat; import java.util.*; /** @@ -59,6 +60,13 @@ public class CanvasController { private double metersToPixels; private List raceObjects = new ArrayList<>(); + //FRAME RATE + private static final double UPDATE_TIME = 0.016666; // 1 / 60 ie 60fps + private final long[] frameTimes = new long[30]; + private int frameTimeIndex = 0; + private boolean arrayFilled = false; + private DecimalFormat decimalFormat2dp = new DecimalFormat("0.00"); + public AnimationTimer timer; private enum ScaleDirection { @@ -138,6 +146,8 @@ public class CanvasController { timer = new AnimationTimer() { + + private int countdown = 60; private int[] currentRaceMarker = {1, 1, 1, 1, 1, 1}; List marks = raceViewController.getRace().getCourse(); @@ -150,6 +160,20 @@ public class CanvasController { int boatIndex = 0; Mark nextMark; + + long oldFrameTime = frameTimes[frameTimeIndex] ; + frameTimes[frameTimeIndex] = now ; + frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length ; + if (frameTimeIndex == 0) { + arrayFilled = true ; + } + if (arrayFilled) { + long elapsedNanos = now - oldFrameTime ; + long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ; + Double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ; + drawFps(frameRate.intValue()); + } + //if (countdown == 0) { //System.out.println("called the at"); for (RaceObject raceObject : raceObjects) { @@ -163,8 +187,6 @@ public class CanvasController { //descending = nextMark.getY() > boatGroup.getLayoutY(); //leftToRight = nextMark.getX() < boatGroup.getLayoutX(); - - raceObject.updatePosition(1000 / 60); for (int id : raceObject.getRaceIds()) { //System.out.println("id = " + id); @@ -285,10 +307,15 @@ public class CanvasController { private void drawFps(int fps){ if (raceViewController.isDisplayFps()){ + gc.clearRect(5,5,50,20); gc.setFill(Color.BLACK); gc.setFont(new Font(14)); gc.setLineWidth(3); gc.fillText(fps + " FPS", 5, 20); + } else { + gc.clearRect(5,5,50,20); + gc.setFill(Color.SKYBLUE); + gc.fillRect(4,4,51,21); } } @@ -307,7 +334,7 @@ public class CanvasController { for (Boat boat : boats) { BoatGroup boatGroup = new BoatGroup(boat, Colors.getColor()); boatGroup.moveTo(startingX, startingY, 0d); - boatGroup.setDestination(firstMarkX, firstMarkY); +// boatGroup.setDestination(firstMarkX, firstMarkY); boatGroup.forceRotation(); group.getChildren().add(boatGroup); raceObjects.add(boatGroup); diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index e781c8d3..214cf02f 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -5,6 +5,7 @@ import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.control.CheckBox; diff --git a/src/main/java/seng302/models/Boat.java b/src/main/java/seng302/models/Boat.java index d275091f..04ef14ab 100644 --- a/src/main/java/seng302/models/Boat.java +++ b/src/main/java/seng302/models/Boat.java @@ -140,4 +140,5 @@ public class Boat { public int getId() { return id; } + } \ 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 44f5aa1f..46887e5a 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -8,6 +8,7 @@ import javafx.scene.shape.Polygon; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; +import seng302.models.parsers.StreamParser; import java.util.ArrayList; import java.util.List; @@ -28,6 +29,7 @@ public class BoatGroup extends RaceObject{ private static double expectedUpdateInterval = 200; private static int WAKE_FRAME_INTERVAL = 30; private double framesForNewLine = 0; + private boolean destinationSet; private Point2D lastPoint; private Boat boat; @@ -35,7 +37,7 @@ public class BoatGroup extends RaceObject{ private List wakes = new ArrayList<>(); private List lines = new ArrayList<>(); private Polygon boatPoly; -// private Polygon wakePoly; + private Polygon wakePoly; private Text teamNameObject; private Text velocityObject; @@ -56,12 +58,12 @@ public class BoatGroup extends RaceObject{ // boatPoly.setLayoutY(0); // boatPoly.relocate(boatPoly.getLayoutX(), boatPoly.getLayoutY()); // -// wakePoly = new Polygon( -// 5.0,0.0, -// 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, -// 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO -// ); -// wakePoly.setFill(Color.DARKBLUE); + wakePoly = new Polygon( + 5.0,0.0, + 10.0, boat.getVelocity() * VELOCITY_WAKE_RATIO, + 0.0, boat.getVelocity() * VELOCITY_WAKE_RATIO + ); + wakePoly.setFill(Color.DARKBLUE); teamNameObject = new Text(boat.getShortName()); velocityObject = new Text(String.valueOf(boat.getVelocity())); @@ -73,9 +75,8 @@ public class BoatGroup extends RaceObject{ velocityObject.setX(VELOCITY_X_OFFSET); velocityObject.setY(VELOCITY_Y_OFFSET); velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); - -// super.getChildren().addAll(wakePoly, boatPoly, teamNameObject, velocityObject); - super.getChildren().addAll(teamNameObject, velocityObject, boatPoly); + destinationSet = false; + super.getChildren().addAll(wakePoly, boatPoly, teamNameObject, velocityObject); } private void initChildren (Color color) { @@ -97,8 +98,8 @@ public class BoatGroup extends RaceObject{ teamNameObject.setLayoutY(teamNameObject.getLayoutY() + dy); velocityObject.setLayoutX(velocityObject.getLayoutX() + dx); velocityObject.setLayoutY(velocityObject.getLayoutY() + dy); -// wakePoly.setLayoutX(wakePoly.getLayoutX() + dx); -// wakePoly.setLayoutY(wakePoly.getLayoutY() + dy); + wakePoly.setLayoutX(wakePoly.getLayoutX() + dx); + wakePoly.setLayoutY(wakePoly.getLayoutY() + dy); rotateTo(currentRotation); } @@ -119,8 +120,8 @@ public class BoatGroup extends RaceObject{ teamNameObject.setLayoutY(y); velocityObject.setLayoutX(x); velocityObject.setLayoutY(y); -// wakePoly.setLayoutX(x); -// wakePoly.setLayoutY(y); + wakePoly.setLayoutX(x); + wakePoly.setLayoutY(y); } public void updatePosition (double timeInterval) { @@ -158,9 +159,8 @@ public class BoatGroup extends RaceObject{ boatPoly.getLayoutX(), boatPoly.getLayoutY(), pixelVelocityX, - pixelVelocityY, rotation); -// wake.getTransforms().clear(); -// wake.getTransforms().add(new Rotate(rotation, 0, 0)); + pixelVelocityY, + rotation); super.getChildren().add(wake); wakes.add(wake); } @@ -174,13 +174,18 @@ public class BoatGroup extends RaceObject{ lines.add(l); super.getChildren().add(l); } - lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); + if (destinationSet){ + lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); + } } framesForNewLine -= 1; } public void setDestination (double newXValue, double newYValue, double rotation, int... raceIds) { //System.out.println("MADE IT"); + destinationSet = true; + boat.setVelocity(StreamParser.boatSpeeds.get((long)boat.getId())); + resizeWake(); if (hasRaceId(raceIds)) { this.pixelVelocityX = (newXValue - boatPoly.getLayoutX()) / expectedUpdateInterval; this.pixelVelocityY = (newYValue - boatPoly.getLayoutY()) / expectedUpdateInterval; @@ -191,6 +196,8 @@ public class BoatGroup extends RaceObject{ } public void setDestination (double newXValue, double newYValue, int... raceIDs) { + destinationSet = true; + if (hasRaceId(raceIDs)) { double rotation = Math.abs( Math.toDegrees( @@ -212,16 +219,34 @@ 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) { if(rotation != 0) { rotationalGoal = rotation; boatPoly.getTransforms().clear(); boatPoly.getTransforms().add(new Rotate(rotation, BOAT_WIDTH / 2, 0)); + wakePoly.getTransforms().clear(); + wakePoly.getTransforms().add(new Translate(0, BOAT_HEIGHT)); + wakePoly.getTransforms().add(new Rotate(rotation, BOAT_WIDTH/2, -BOAT_HEIGHT)); } -// wakePoly.getTransforms().clear(); -// wakePoly.getTransforms().add(new Rotate(rotation, 0, 0)); } + + public void forceRotation () { rotateTo (rotationalGoal); } diff --git a/src/main/java/seng302/models/Wake.java b/src/main/java/seng302/models/Wake.java index 92b2d30d..09b037ba 100644 --- a/src/main/java/seng302/models/Wake.java +++ b/src/main/java/seng302/models/Wake.java @@ -29,7 +29,7 @@ class Wake extends Arc { super.setType(ArcType.OPEN); super.setFill(new Color(0, 0, 0 ,0)); super.setStrokeWidth(2.0); - super.getTransforms().add(new Rotate(rotation, 5, -15)); + super.getTransforms().add(new Rotate(rotation - 270, startingX + 20, startingY + 20)); // this.velocityX = -velocityX; // this.velocityY = -velocityY; this.velocityX = 0; diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index f49e4d31..c917cd3c 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -14,6 +14,8 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; @@ -25,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; public class StreamParser extends Thread{ public static ConcurrentHashMap boatPositions = new ConcurrentHashMap<>(); + public static ConcurrentHashMap boatSpeeds = new ConcurrentHashMap<>(); private static ArrayList boat_IDS = new ArrayList<>(); private String threadName; private Thread t; @@ -253,6 +256,8 @@ public class StreamParser extends Thread{ byte[] lonBytes = Arrays.copyOfRange(payload,20,24); byte[] boatIdBytes = Arrays.copyOfRange(payload,7,11); byte[] headingBytes = Arrays.copyOfRange(payload,28,30); + byte[] speedBytes = Arrays.copyOfRange(payload,38,40); + long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); // int boatSeq = ByteBuffer.wrap(seqBytes).getInt(); long seq = bytesToLong(seqBytes); @@ -260,7 +265,13 @@ public class StreamParser extends Thread{ long lat = bytesToLong(latBytes); long lon = bytesToLong(lonBytes); long heading = bytesToLong(headingBytes); - +// long speed = extractTimeStamp(speedBytes, 2); + ByteBuffer bb = ByteBuffer.allocate(2); + bb.order(ByteOrder.LITTLE_ENDIAN); + bb.put(speedBytes[0]); + bb.put(speedBytes[1]); + double speed = bb.getShort(0)/1000.0; + short s = (short) ((speedBytes[1] & 0xFF) << 8 | (speedBytes[0] & 0xFF)); if ((int)deviceType == 1 || (int)deviceType == 4){ // System.out.println("boatId = " + boatId); // System.out.println("deviceType = " + (long)deviceType); @@ -268,13 +279,16 @@ public class StreamParser extends Thread{ //needs to be validated Point3D point = new Point3D(((180d * (double)lat)/Math.pow(2,31)),((180d *(double)lon)/Math.pow(2,31)),(double)heading); boatPositions.putIfAbsent(boatId, point); + boatSpeeds.putIfAbsent(boatId, speed); boatPositions.replace(boatId, point); + boatSpeeds.replace(boatId, speed); // System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31))); // System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31))); } } + private static void extractMarkRounding(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; diff --git a/src/main/resources/config/teams.xml b/src/main/resources/config/teams.xml index f8d1e58d..0ac01cac 100644 --- a/src/main/resources/config/teams.xml +++ b/src/main/resources/config/teams.xml @@ -4,37 +4,37 @@ Oracle Team USA USA - 12.9 + 0.0 102 Artemis Racing ART - 13.1 + 0.0 101 Emirates Team New Zealand NZL - 15.6 + 0.0 103 Land Rover BAR BAR - 13.3 + 0.0 104 SoftBank Team Japan JAP - 14.7 + 0.0 105 Groupama Team France FRC - 11.4 + 0.0 106 \ No newline at end of file diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index f7fcbfeb..2cae1326 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -1,15 +1,18 @@ - - - - - - - + + + + + + + + + + - + From d204bee55df67a8b9e6f77a795ea101f64c3c304 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Fri, 28 Apr 2017 17:01:28 +1200 Subject: [PATCH 52/97] Started documentation on the stream parser. #story[820] --- .../seng302/models/parsers/StreamParser.java | 88 +++++++++++++------ 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index c917cd3c..766296a7 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -1,12 +1,10 @@ package seng302.models.parsers; -import javafx.geometry.Point2D; import javafx.geometry.Point3D; import org.w3c.dom.Document; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import sun.awt.UNIXToolkit; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -22,14 +20,16 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** + * The purpose of this class is to take in the stream of divided packets so they can be read + * and parsed in by turning the byte arrays into useful data. There are two public static hashmaps + * that are threadsafe so the visualiser can always access the latest speed and position avaible * Created by kre39 on 23/04/17. */ public class StreamParser extends Thread{ public static ConcurrentHashMap boatPositions = new ConcurrentHashMap<>(); public static ConcurrentHashMap boatSpeeds = new ConcurrentHashMap<>(); - private static ArrayList boat_IDS = new ArrayList<>(); - private String threadName; + private String threadName; private Thread t; private static boolean raceStarted = false; @@ -37,7 +37,11 @@ public class StreamParser extends Thread{ this.threadName = threadName; } - public void run(){ + /** + * Used to within threading so when the stream parser thread runs, it will keep looking for a packet to + * process until it is unable to find anymore packets + */ + public void run(){ try { System.out.println("START OF STREAM"); while (StreamReceiver.packetBuffer == null || StreamReceiver.packetBuffer.size() < 1) { @@ -57,6 +61,9 @@ public class StreamParser extends Thread{ } } + /** + * Used to start the stream parser thread when multithreading + */ public void start () { System.out.println("Starting " + threadName ); if (t == null) { @@ -65,7 +72,7 @@ public class StreamParser extends Thread{ } } - static void parsePacket(StreamPacket packet) { + private static void parsePacket(StreamPacket packet) { switch (packet.getType()){ case HEARTBEAT: extractHeartBeat(packet); @@ -109,12 +116,20 @@ public class StreamParser extends Thread{ } } + /** + * Extracts the seq num used in the heartbeat packet + * @param packet Packet parsed in to use the payload + */ private static void extractHeartBeat(StreamPacket packet) { long heartbeat = bytesToLong(packet.getPayload()); -// System.out.println("Heartbeat: " + heartbeat); - } + /** + * Extracts the useful race status data from race status type packets. This method will also print to the + * console the current state of the race (if it has started/finished or is about to start), along side + * this it'll also display the amount of time since the race has started or time till it starts + * @param packet Packet parsed in to use the payload + */ private static void extractRaceStatus(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -158,6 +173,10 @@ public class StreamParser extends Thread{ } } + /** + * Used to extract the messages passed through with the display message packet + * @param packet Packet parsed in to use the payload + */ private static void extractDisplayMessage(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -172,7 +191,11 @@ public class StreamParser extends Thread{ } } - static void extractXmlMessage(StreamPacket packet){ + /** + * Used to read in the xml data. Will call the specific methods to create the course and boats + * @param packet Packet parsed in to use the payload + */ + private static void extractXmlMessage(StreamPacket packet){ byte[] payload = packet.getPayload(); String xmlMessage = ""; @@ -200,16 +223,17 @@ public class StreamParser extends Thread{ 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 e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } catch (IOException e) { + } catch (ParserConfigurationException | IOException | SAXException e) { e.printStackTrace(); } } + /** + * Extracts the race start status from the packet, currently is unused within the app but + * is here for potential future use + * @param packet Packet parsed in to use the payload + */ private static void extractRaceStartStatus(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -219,6 +243,11 @@ public class StreamParser extends Thread{ int notificationType = payload[19]; } + /** + * When a yacht event occurs this will parse the byte array to retrieve the necessary info, + * currently unused + * @param packet Packet parsed in to use the payload + */ private static void extractYachtEventCode(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -229,6 +258,11 @@ public class StreamParser extends Thread{ int eventId = payload[21]; } + /** + * When a yacht action occurs this will parse the parse the byte array to retrieve the necessary info, + * currently unused + * @param packet Packet parsed in to use the payload + */ private static void extractYachtActionCode(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -239,6 +273,10 @@ public class StreamParser extends Thread{ // System.out.println("eventId = " + eventId); } + /** + * Strips the message from the chatter text type packets, currently the message is unused + * @param packet Packet parsed in to use the payload + */ private static void extractChatterText(StreamPacket packet){ byte[] payload = packet.getPayload(); int messageVersionNo = payload[0]; @@ -247,8 +285,12 @@ public class StreamParser extends Thread{ String message = new String(Arrays.copyOfRange(payload,3,3 + length)); } - - static void extractBoatLocation(StreamPacket packet){ + /** + * Used to breakdown the boatlocation packets so the boat coordinates, id and groundspeed are all used + * All the other extra data is still being read and translated however is unused. + * @param packet Packet parsed in to use the payload + */ + private static void extractBoatLocation(StreamPacket packet){ byte[] payload = packet.getPayload(); byte deviceType = payload[15]; byte[] seqBytes = Arrays.copyOfRange(payload,11,15); @@ -256,7 +298,7 @@ public class StreamParser extends Thread{ byte[] lonBytes = Arrays.copyOfRange(payload,20,24); byte[] boatIdBytes = Arrays.copyOfRange(payload,7,11); byte[] headingBytes = Arrays.copyOfRange(payload,28,30); - byte[] speedBytes = Arrays.copyOfRange(payload,38,40); + byte[] groundSpeedBytes = Arrays.copyOfRange(payload,38,40); long timeStamp = extractTimeStamp(Arrays.copyOfRange(payload,1,7), 6); // int boatSeq = ByteBuffer.wrap(seqBytes).getInt(); @@ -266,12 +308,8 @@ public class StreamParser extends Thread{ long lon = bytesToLong(lonBytes); long heading = bytesToLong(headingBytes); // long speed = extractTimeStamp(speedBytes, 2); - ByteBuffer bb = ByteBuffer.allocate(2); - bb.order(ByteOrder.LITTLE_ENDIAN); - bb.put(speedBytes[0]); - bb.put(speedBytes[1]); - double speed = bb.getShort(0)/1000.0; - short s = (short) ((speedBytes[1] & 0xFF) << 8 | (speedBytes[0] & 0xFF)); + double groundSpeed = bytesToLong(groundSpeedBytes)/1000.0; + short s = (short) ((groundSpeedBytes[1] & 0xFF) << 8 | (groundSpeedBytes[0] & 0xFF)); if ((int)deviceType == 1 || (int)deviceType == 4){ // System.out.println("boatId = " + boatId); // System.out.println("deviceType = " + (long)deviceType); @@ -279,9 +317,9 @@ public class StreamParser extends Thread{ //needs to be validated Point3D point = new Point3D(((180d * (double)lat)/Math.pow(2,31)),((180d *(double)lon)/Math.pow(2,31)),(double)heading); boatPositions.putIfAbsent(boatId, point); - boatSpeeds.putIfAbsent(boatId, speed); + boatSpeeds.putIfAbsent(boatId, groundSpeed); boatPositions.replace(boatId, point); - boatSpeeds.replace(boatId, speed); + boatSpeeds.replace(boatId, groundSpeed); // System.out.println("lon = " + ((180d * (double)lon)/Math.pow(2,31))); // System.out.println("lat = " + ((180d *(double)lat)/Math.pow(2,31))); } From 765f27f9877e1a9936084e2c7b655f3a486e346a Mon Sep 17 00:00:00 2001 From: Calum Date: Fri, 28 Apr 2017 17:08:08 +1200 Subject: [PATCH 53/97] Starting new wake implementation. --- src/main/java/seng302/models/BoatGroup.java | 97 +++++++++++-------- src/main/java/seng302/models/RaceObject.java | 2 +- src/main/java/seng302/models/Wake2.java | 86 ++++++++++++++++ .../java/seng302/models/mark/MarkGroup.java | 2 +- 4 files changed, 143 insertions(+), 44 deletions(-) create mode 100644 src/main/java/seng302/models/Wake2.java diff --git a/src/main/java/seng302/models/BoatGroup.java b/src/main/java/seng302/models/BoatGroup.java index 44f5aa1f..dd28b6b5 100644 --- a/src/main/java/seng302/models/BoatGroup.java +++ b/src/main/java/seng302/models/BoatGroup.java @@ -1,6 +1,7 @@ package seng302.models; import javafx.geometry.Point2D; +import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.shape.Line; @@ -24,20 +25,23 @@ public class BoatGroup extends RaceObject{ 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 = 120; //Time between sections of race - Should be changed to 200 for actual program. private static double expectedUpdateInterval = 200; private static int WAKE_FRAME_INTERVAL = 30; - private double framesForNewLine = 0; + private double framesForNewLine = LINE_INTERVAL; private Point2D lastPoint; private Boat boat; private int wakeCounter = WAKE_FRAME_INTERVAL; private List wakes = new ArrayList<>(); - private List lines = new ArrayList<>(); + //private List lines = new ArrayList<>(); + private Group lines = new Group(); private Polygon boatPoly; // private Polygon wakePoly; private Text teamNameObject; private Text velocityObject; + private Wake2 wake; public BoatGroup (Boat boat, Color color){ this.boat = boat; @@ -74,8 +78,9 @@ public class BoatGroup extends RaceObject{ velocityObject.setY(VELOCITY_Y_OFFSET); velocityObject.relocate(velocityObject.getX(), velocityObject.getY()); + wake = new Wake2(0, 0, 0); // super.getChildren().addAll(wakePoly, boatPoly, teamNameObject, velocityObject); - super.getChildren().addAll(teamNameObject, velocityObject, boatPoly); + super.getChildren().addAll(lines, wake, teamNameObject, velocityObject, boatPoly); } private void initChildren (Color color) { @@ -119,11 +124,12 @@ public class BoatGroup extends RaceObject{ teamNameObject.setLayoutY(y); velocityObject.setLayoutX(x); velocityObject.setLayoutY(y); + wake.moveTo(x, y, currentRotation); // wakePoly.setLayoutX(x); // wakePoly.setLayoutY(y); } - public void updatePosition (double timeInterval) { + public void updatePosition (long timeInterval) { double dx = pixelVelocityX * timeInterval; double dy = pixelVelocityY * timeInterval; double rotation = 0d; @@ -139,44 +145,51 @@ public class BoatGroup extends RaceObject{ // super.getChildren().remove(wake); // } // } - for (Wake wake : wakes) { - if (wake.updatePosition(timeInterval)) { - super.getChildren().remove(wake); - } - } - if (wakeCounter-- == 0) { -// if (boat.getShortName().equals("BAR")) -// System.out.println("thinking"); - wakeCounter = WAKE_FRAME_INTERVAL; - if (pixelVelocityX > 0 && pixelVelocityY > 0) { -// super.getChildren().add( -// new Wake( -// super.getLayoutX() + BOAT_HEIGHT, super.getLayoutY() + BOAT_HEIGHT, pixelVelocityX, pixelVelocityY -// ) -// ); - Wake wake = new Wake( - boatPoly.getLayoutX(), - boatPoly.getLayoutY(), - pixelVelocityX, - pixelVelocityY, rotation); -// wake.getTransforms().clear(); -// wake.getTransforms().add(new Rotate(rotation, 0, 0)); - super.getChildren().add(wake); - wakes.add(wake); - } - - } +// for (Wake wake : wakes) { +// if (wake.updatePosition(timeInterval)) { +// super.getChildren().remove(wake); +// } +// } +// if (wakeCounter-- == 0) { +//// if (boat.getShortName().equals("BAR")) +//// System.out.println("thinking"); +// wakeCounter = WAKE_FRAME_INTERVAL; +// if (pixelVelocityX > 0 && pixelVelocityY > 0) { +//// super.getChildren().add( +//// new Wake( +//// super.getLayoutX() + BOAT_HEIGHT, super.getLayoutY() + BOAT_HEIGHT, pixelVelocityX, pixelVelocityY +//// ) +//// ); +// Wake wake = new Wake( +// boatPoly.getLayoutX(), +// boatPoly.getLayoutY(), +// pixelVelocityX, +// pixelVelocityY, rotation); +//// wake.getTransforms().clear(); +//// wake.getTransforms().add(new Rotate(rotation, 0, 0)); +// super.getChildren().add(wake); +// wakes.add(wake); +// } +// +// } if (framesForNewLine == 0) { - framesForNewLine = 121; + framesForNewLine = LINE_INTERVAL; if (lastPoint != null) { - Line l = new Line(lastPoint.getX(), lastPoint.getY(), boatPoly.getLayoutX(), boatPoly.getLayoutY()); - l.getStrokeDashArray().setAll(4d, 4d); - lines.add(l); - super.getChildren().add(l); + Line l = new Line( + lastPoint.getX(), + lastPoint.getY(), + boatPoly.getLayoutX(), + boatPoly.getLayoutY() + ); + l.getStrokeDashArray().setAll(4d, 6d); + l.setStroke(boatPoly.getFill()); + //lines.add(l); + lines.getChildren().add(l); } lastPoint = new Point2D(boatPoly.getLayoutX(), boatPoly.getLayoutY()); } framesForNewLine -= 1; + wake.updatePosition(timeInterval); } public void setDestination (double newXValue, double newYValue, double rotation, int... raceIds) { @@ -187,6 +200,7 @@ public class BoatGroup extends RaceObject{ this.rotationalGoal = rotation; calculateRotationalVelocity(); rotateTo(rotation); + wake.setRotationalVelocity(rotationalVelocity); } } @@ -213,11 +227,12 @@ public class BoatGroup extends RaceObject{ } public void rotateTo (double rotation) { - if(rotation != 0) { - rotationalGoal = rotation; + //if(rotation != 0) { + //rotationalGoal = rotation; + currentRotation = rotation; boatPoly.getTransforms().clear(); boatPoly.getTransforms().add(new Rotate(rotation, BOAT_WIDTH / 2, 0)); - } + //} // wakePoly.getTransforms().clear(); // wakePoly.getTransforms().add(new Rotate(rotation, 0, 0)); } @@ -232,9 +247,7 @@ public class BoatGroup extends RaceObject{ for (Wake wake : wakes) { wake.setVisible(!wake.isVisible()); } - for (Line line : lines) { - line.setVisible(!line.isVisible()); - } + lines.setVisible(!lines.isVisible()); } public Boat getBoat() { diff --git a/src/main/java/seng302/models/RaceObject.java b/src/main/java/seng302/models/RaceObject.java index a5dcf6f5..9ec22fd5 100644 --- a/src/main/java/seng302/models/RaceObject.java +++ b/src/main/java/seng302/models/RaceObject.java @@ -49,7 +49,7 @@ public abstract class RaceObject extends Group { public abstract void setDestination (double x, double y, int... raceIds); - public abstract void updatePosition (double timeInterval); + public abstract void updatePosition (long timeInterval); public abstract void moveTo (double x, double y, double rotation); diff --git a/src/main/java/seng302/models/Wake2.java b/src/main/java/seng302/models/Wake2.java new file mode 100644 index 00000000..98090a45 --- /dev/null +++ b/src/main/java/seng302/models/Wake2.java @@ -0,0 +1,86 @@ +package seng302.models; + +import javafx.scene.paint.Color; +import javafx.scene.shape.Arc; +import javafx.scene.transform.Rotate; + +/** + * Created by cir27 on 28/04/17. + */ +class Wake2 extends Arc { + private final double OPACITY_INCREASE = -0.2; + private final double RADIUS_INCREASE = 10; + private final int DELAY = 60; + private double opacity; + private double rotationalVelocity; + private Wake2 subWake; + private boolean hasSubWake = false; + private int delayCount = DELAY; + + Wake2 (double startingX, double startingY, double rotationalVelocity) { + super(startingX, startingY, 30, 30, 150, 60); + opacity = 1.0; + super.setFill(new Color(0.0, 0.0, 0.0, opacity)); + subWake = new Wake2( + startingX, + startingY, + super.getRadiusX() + RADIUS_INCREASE, + rotationalVelocity, + opacity + OPACITY_INCREASE + ); + hasSubWake = true; + this.rotationalVelocity = rotationalVelocity; + + } + Wake2 (double startingX, double startingY, double radius, double rotationalVelocity, double opacity) { + super(startingX, startingY, radius, radius, 150, 60); + super.setFill(new Color(0.0, 0.0, 0.0, opacity)); + this.opacity = opacity; + if (!(opacity < 0)) { + subWake = new Wake2( + startingX, + startingY, + super.getRadiusX() + RADIUS_INCREASE, + rotationalVelocity, + opacity + OPACITY_INCREASE + ); + hasSubWake = true; + } + this.rotationalVelocity = rotationalVelocity; + } + + void setRotationalVelocity (double rotationalVelocity) { + this.rotationalVelocity = rotationalVelocity; + delayCount = DELAY; + } + + void updatePosition (long timeInterval) { + if (delayCount-- == 0) + subWake.setRotationalVelocity(rotationalVelocity); + super.getTransforms().clear(); + super.getTransforms().add( + new Rotate( + rotationalVelocity * timeInterval, + super.getCenterX(), + super.getCenterY() + ) + ); + if(hasSubWake) + subWake.updatePosition(timeInterval); + } + + void moveTo (double x, double y, double rotation) { + super.setLayoutX(x); + super.setLayoutY(y); + super.getTransforms().clear(); + super.getTransforms().add( + new Rotate( + rotation, + super.getCenterX(), + super.getCenterY() + ) + ); + if(hasSubWake) + subWake.moveTo(x, y, rotation); + } +} diff --git a/src/main/java/seng302/models/mark/MarkGroup.java b/src/main/java/seng302/models/mark/MarkGroup.java index eba7da82..caf260fe 100644 --- a/src/main/java/seng302/models/mark/MarkGroup.java +++ b/src/main/java/seng302/models/mark/MarkGroup.java @@ -156,7 +156,7 @@ public class MarkGroup extends RaceObject { super.getTransforms().add(new Rotate(rotation, 0 , 0)); } - public void updatePosition (double timeInterval) { + public void updatePosition (long timeInterval) { double x = pixelVelocityX * timeInterval; double y = pixelVelocityY * timeInterval; double rotation = rotationalVelocity * timeInterval; From 07bbd7e06d29948fd92f345438edba21d0efe6dd Mon Sep 17 00:00:00 2001 From: Peter Galloway Date: Fri, 28 Apr 2017 18:29:35 +1200 Subject: [PATCH 54/97] Added reasonable testing for StreamReciever, further testing would probably need StreamReciever to be rewritten #story[817] --- .../models/parsers/StreamReceiver.java | 12 +- .../models/parsers/StreamReceiverTest.java | 105 ++++++++++++++++++ 2 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 src/test/java/seng302/models/parsers/StreamReceiverTest.java diff --git a/src/main/java/seng302/models/parsers/StreamReceiver.java b/src/main/java/seng302/models/parsers/StreamReceiver.java index 31912815..663f3a8c 100644 --- a/src/main/java/seng302/models/parsers/StreamReceiver.java +++ b/src/main/java/seng302/models/parsers/StreamReceiver.java @@ -19,6 +19,7 @@ public class StreamReceiver { public StreamReceiver(String hostAddress, int hostPort, PriorityBlockingQueue packetBuffer) { try { host = new Socket(hostAddress, hostPort); + } catch (IOException e) { e.printStackTrace(); System.exit(1); @@ -26,6 +27,12 @@ public class StreamReceiver { this.packetBuffer = packetBuffer; } + public StreamReceiver(Socket host, PriorityBlockingQueue packetBuffer){ + this.host=host; + this.packetBuffer = packetBuffer; + } + + public void connect(){ try { stream = host.getInputStream(); @@ -66,7 +73,6 @@ public class StreamReceiver { } catch (Exception e) { moreBytes = false; } - } } @@ -100,8 +106,8 @@ public class StreamReceiver { } /** - * 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 7 bytes -1 otherwise */ diff --git a/src/test/java/seng302/models/parsers/StreamReceiverTest.java b/src/test/java/seng302/models/parsers/StreamReceiverTest.java new file mode 100644 index 00000000..c7951e3b --- /dev/null +++ b/src/test/java/seng302/models/parsers/StreamReceiverTest.java @@ -0,0 +1,105 @@ +package seng302.models.parsers; + +import org.junit.Before; +import org.junit.Test; + +import java.io.*; +import java.lang.reflect.Method; +import java.net.Socket; +import java.util.Comparator; +import java.util.concurrent.PriorityBlockingQueue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by ptg19 on 26/04/17. + */ +public class StreamReceiverTest { + + private PriorityBlockingQueue pq; + private byte[] brokenPacket = {0x47, (byte) 0x83, 37, // sync1 sync2 and message type + 0b00000000, 0b01000000, 0b00100010, 0b00100100, 0b00011000, 0b00000000, //timestamp + 0b00000000, 0b00010000, 0b01000000, 0b00000000, //source id + 0b00100010, 0b00101000, // message length + 0b00010010, 0b00010010, 0b00010010}; //random start of payload + + private byte[] workingPacket = {0x47, (byte) 0x83, 37, // sync1 sync2 and message type + 0b00000000, 0b01000000, 0b00100010, 0b00100100, 0b00011000, 0b00000000, //timestamp + 0b00000000, 0b00010000, 0b01000000, 0b00000000, //source id + 0b00000010, 0b00000000, // message length + 0b00010010, 0b00010010, // payload + 0b00100110, (byte)0b10000111, 0b00110101, 0b01111000}; //crc + + private byte[] crcMismatchPacket = {0x47, (byte) 0x83, 37, // sync1 sync2 and message type + 0b00000000, 0b01000000, 0b00100010, 0b00100100, 0b00011000, 0b00000000, //timestamp + 0b00000000, 0b00000000, 0b01000000, 0b00000000, //source id + 0b00000010, 0b00000000, // message length + 0b00010010, 0b00010010, // payload + 0b00100110, (byte)0b10000111, 0b00110101, 0b01111000}; //crc + + + @Before + public void setup(){ + pq = new PriorityBlockingQueue<>(256, new Comparator() { + @Override + public int compare(StreamPacket s1, StreamPacket s2) { + return (int) (s1.getTimeStamp() - s2.getTimeStamp()); + } + }); + } + + @Test + public void connectExitsOnUnexpectedStreamEnd() throws Exception { + Socket host=mock(Socket.class); + InputStream stream = new ByteArrayInputStream(brokenPacket); + when(host.getInputStream()).thenReturn(stream); + StreamReceiver streamReceiver = new StreamReceiver(host, pq); + + streamReceiver.connect(); + assert pq.size() == 0; + } + + @Test + public void connectReadsAPacket() throws Exception { + Socket host=mock(Socket.class); + InputStream stream = new ByteArrayInputStream(workingPacket); + when(host.getInputStream()).thenReturn(stream); + StreamReceiver streamReceiver = new StreamReceiver(host, pq); + + streamReceiver.connect(); + assert pq.size() == 1; + } + + @Test + public void connectDropsAMismatchedCrc() throws Exception { + Socket host=mock(Socket.class); + InputStream stream = new ByteArrayInputStream(crcMismatchPacket); + when(host.getInputStream()).thenReturn(stream); + StreamReceiver streamReceiver = new StreamReceiver(host, pq); + + streamReceiver.connect(); + assert pq.size() == 0; + } + + @Test + public void bytestoLongTest() { + Socket host=mock(Socket.class); + StreamReceiver streamReceiver = new StreamReceiver(host, pq); + try { + Class[] args = new Class[1]; + args[0] = byte[].class; + Method bytesToLong = streamReceiver.getClass().getDeclaredMethod("bytesToLong", args); + bytesToLong.setAccessible(true); + byte[] sevenBtyeNumber = {0b01100100, 0b00110100, 0b00010100, 0b00000000, 0b00000000, 0b00000000, (byte)0b10000000}; + assert bytesToLong.invoke(streamReceiver, sevenBtyeNumber).equals(36028797020288100L); + byte[] eightByteNumber = {0b01100100, 0b00110100, 0b00010100, 0b00000000, 0b00000000, 0b00000000, (byte)0b10000000, 0b00100101}; + assert bytesToLong.invoke(streamReceiver, eightByteNumber).equals(-1L); + byte[] emptyArray = {}; + assert bytesToLong.invoke(streamReceiver, emptyArray).equals(0L); + } catch (Exception e){ + System.out.println(""); + } + } + +} \ No newline at end of file From ffdfc24e65bc91e6ad58e25f472de4a604df4ed2 Mon Sep 17 00:00:00 2001 From: Zhi You Tan Date: Fri, 28 Apr 2017 20:09:17 +1200 Subject: [PATCH 55/97] Created a start screen with a timer which shows the race progress #story[572] --- .../java/seng302/controllers/Controller.java | 49 +++++++++++++++++++ .../seng302/models/parsers/StreamParser.java | 34 ++++++++++++- src/main/resources/views/MainView.fxml | 39 ++++++++++++++- 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/src/main/java/seng302/controllers/Controller.java b/src/main/java/seng302/controllers/Controller.java index 1892d472..94847735 100644 --- a/src/main/java/seng302/controllers/Controller.java +++ b/src/main/java/seng302/controllers/Controller.java @@ -1,14 +1,25 @@ package seng302.controllers; +import javafx.application.Platform; +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.layout.AnchorPane; import javafx.scene.layout.Pane; +import javafx.scene.paint.*; +import javafx.scene.paint.Color; +import seng302.models.parsers.StreamPacket; +import seng302.models.parsers.StreamParser; +import java.awt.*; 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 +27,12 @@ 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; private void setContentPane(String jfxUrl){ try{ @@ -33,6 +50,38 @@ public class Controller implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { + + } + + 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.getTimeSinceStart() != 0) { + 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"); } } diff --git a/src/main/java/seng302/models/parsers/StreamParser.java b/src/main/java/seng302/models/parsers/StreamParser.java index 766296a7..32d2eb4e 100644 --- a/src/main/java/seng302/models/parsers/StreamParser.java +++ b/src/main/java/seng302/models/parsers/StreamParser.java @@ -29,9 +29,11 @@ public class StreamParser extends Thread{ public static ConcurrentHashMap boatPositions = new ConcurrentHashMap<>(); public static ConcurrentHashMap boatSpeeds = new ConcurrentHashMap<>(); - private String threadName; + private String threadName; private Thread t; private static boolean raceStarted = false; + private static boolean streamStatus = false; + private static long timeSinceStart = 0; public StreamParser(String threadName){ this.threadName = threadName; @@ -44,6 +46,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); } @@ -152,7 +155,7 @@ public class StreamParser extends Thread{ } if (timeTillStart % 10 == 0){ System.out.println("Time since start: " + -1 * timeTillStart + " Seconds"); - + timeSinceStart = timeTillStart; } } long windDir = bytesToLong(Arrays.copyOfRange(payload,18,20)); @@ -400,5 +403,32 @@ 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; + } } diff --git a/src/main/resources/views/MainView.fxml b/src/main/resources/views/MainView.fxml index ac0b944e..15559900 100644 --- a/src/main/resources/views/MainView.fxml +++ b/src/main/resources/views/MainView.fxml @@ -1,11 +1,48 @@ + + - + + + + + + + + + + + + + + + + +