mirror of
https://github.com/michaelrausch/Party-Parrots-At-Sea.git
synced 2026-05-09 06:18:44 +00:00
Merge branch 'develop' into 38b_LayLines
# Conflicts: # src/main/java/seng302/controllers/RaceViewController.java # src/main/java/seng302/models/Yacht.java
This commit is contained in:
@@ -18,8 +18,10 @@ public class App extends Application {
|
|||||||
|
|
||||||
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml"));
|
||||||
primaryStage.setTitle("RaceVision");
|
primaryStage.setTitle("RaceVision");
|
||||||
primaryStage.setScene(new Scene(root));
|
primaryStage.setScene(new Scene(root, 1530, 960));
|
||||||
primaryStage.setMaximized(true);
|
primaryStage.setMaxWidth(1530);
|
||||||
|
primaryStage.setMaxHeight(960);
|
||||||
|
// primaryStage.setMaximized(true);
|
||||||
|
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
primaryStage.setOnCloseRequest(e -> {
|
primaryStage.setOnCloseRequest(e -> {
|
||||||
@@ -66,6 +68,7 @@ public class App extends Application {
|
|||||||
//Change the StreamReceiver in this else block to change the default data source.
|
//Change the StreamReceiver in this else block to change the default data source.
|
||||||
else {
|
else {
|
||||||
// sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
// sr = new StreamReceiver("localhost", 4949, "RaceStream");
|
||||||
|
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream");
|
||||||
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
|
sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream");
|
||||||
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4942, "RaceStream");
|
// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4942, "RaceStream");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
package seng302.controllers;
|
package seng302.controllers;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.PriorityBlockingQueue;
|
|
||||||
import javafx.animation.AnimationTimer;
|
import javafx.animation.AnimationTimer;
|
||||||
import javafx.beans.property.SimpleDoubleProperty;
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@@ -12,22 +7,29 @@ import javafx.geometry.Point2D;
|
|||||||
import javafx.scene.Group;
|
import javafx.scene.Group;
|
||||||
import javafx.scene.canvas.Canvas;
|
import javafx.scene.canvas.Canvas;
|
||||||
import javafx.scene.canvas.GraphicsContext;
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.AnchorPane;
|
import javafx.scene.layout.AnchorPane;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.text.Font;
|
import javafx.scene.text.Font;
|
||||||
import seng302.models.BoatGroup;
|
import seng302.models.BoatGroup;
|
||||||
import seng302.models.Colors;
|
import seng302.models.Colors;
|
||||||
import seng302.models.Yacht;
|
import seng302.models.Yacht;
|
||||||
import seng302.models.mark.GateMark;
|
import seng302.models.map.Boundary;
|
||||||
import seng302.models.mark.Mark;
|
import seng302.models.map.CanvasMap;
|
||||||
import seng302.models.mark.MarkGroup;
|
import seng302.models.mark.*;
|
||||||
import seng302.models.mark.MarkType;
|
|
||||||
import seng302.models.mark.SingleMark;
|
|
||||||
import seng302.models.stream.StreamParser;
|
import seng302.models.stream.StreamParser;
|
||||||
import seng302.models.stream.XMLParser;
|
import seng302.models.stream.XMLParser;
|
||||||
import seng302.models.stream.XMLParser.RaceXMLObject.Limit;
|
import seng302.models.stream.XMLParser.RaceXMLObject.Limit;
|
||||||
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||||
import seng302.models.stream.packets.BoatPositionPacket;
|
import seng302.models.stream.packets.BoatPositionPacket;
|
||||||
|
import seng302.server.simulator.GeoUtility;
|
||||||
|
import seng302.server.simulator.mark.Position;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by ptg19 on 15/03/17.
|
* Created by ptg19 on 15/03/17.
|
||||||
@@ -42,15 +44,18 @@ public class CanvasController {
|
|||||||
private ResizableCanvas canvas;
|
private ResizableCanvas canvas;
|
||||||
private Group group;
|
private Group group;
|
||||||
private GraphicsContext gc;
|
private GraphicsContext gc;
|
||||||
|
private ImageView mapImage;
|
||||||
|
|
||||||
private final int MARK_SIZE = 10;
|
private final int MARK_SIZE = 10;
|
||||||
private final int BUFFER_SIZE = 50;
|
private final int BUFFER_SIZE = 50;
|
||||||
|
private final int PANEL_WIDTH = 1260; // it should be 1280 but, minors 40 to cancel the bias.
|
||||||
|
private final int PANEL_HEIGHT = 960;
|
||||||
private final int CANVAS_WIDTH = 720;
|
private final int CANVAS_WIDTH = 720;
|
||||||
private final int CANVAS_HEIGHT = 720;
|
private final int CANVAS_HEIGHT = 720;
|
||||||
private final int LHS_BUFFER = BUFFER_SIZE;
|
private final int LHS_BUFFER = BUFFER_SIZE;
|
||||||
private final int RHS_BUFFER = BUFFER_SIZE + MARK_SIZE / 2;
|
private final int RHS_BUFFER = BUFFER_SIZE;
|
||||||
private final int TOP_BUFFER = BUFFER_SIZE;
|
private final int TOP_BUFFER = BUFFER_SIZE;
|
||||||
private final int BOT_BUFFER = TOP_BUFFER + MARK_SIZE / 2;
|
private final int BOT_BUFFER = TOP_BUFFER;
|
||||||
private boolean horizontalInversion = false;
|
private boolean horizontalInversion = false;
|
||||||
|
|
||||||
private double distanceScaleFactor;
|
private double distanceScaleFactor;
|
||||||
@@ -61,6 +66,8 @@ public class CanvasController {
|
|||||||
private Mark maxLonPoint;
|
private Mark maxLonPoint;
|
||||||
private double referencePointX;
|
private double referencePointX;
|
||||||
private double referencePointY;
|
private double referencePointY;
|
||||||
|
private double metersPerPixelX;
|
||||||
|
private double metersPerPixelY;
|
||||||
|
|
||||||
private List<MarkGroup> markGroups = new ArrayList<>();
|
private List<MarkGroup> markGroups = new ArrayList<>();
|
||||||
private List<BoatGroup> boatGroups = new ArrayList<>();
|
private List<BoatGroup> boatGroups = new ArrayList<>();
|
||||||
@@ -87,6 +94,12 @@ public class CanvasController {
|
|||||||
canvas = new ResizableCanvas();
|
canvas = new ResizableCanvas();
|
||||||
group = new Group();
|
group = new Group();
|
||||||
|
|
||||||
|
// create image view for map, bind panel size to image
|
||||||
|
mapImage = new ImageView();
|
||||||
|
canvasPane.getChildren().add(mapImage);
|
||||||
|
mapImage.fitWidthProperty().bind(canvasPane.widthProperty());
|
||||||
|
mapImage.fitHeightProperty().bind(canvasPane.heightProperty());
|
||||||
|
|
||||||
canvasPane.getChildren().add(canvas);
|
canvasPane.getChildren().add(canvas);
|
||||||
canvasPane.getChildren().add(group);
|
canvasPane.getChildren().add(group);
|
||||||
// Bind canvas size to stack pane size.
|
// Bind canvas size to stack pane size.
|
||||||
@@ -97,11 +110,9 @@ public class CanvasController {
|
|||||||
public void initializeCanvas (){
|
public void initializeCanvas (){
|
||||||
|
|
||||||
gc = canvas.getGraphicsContext2D();
|
gc = canvas.getGraphicsContext2D();
|
||||||
gc.save();
|
gc.setGlobalAlpha(0.5);
|
||||||
gc.setFill(Color.SKYBLUE);
|
|
||||||
gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
|
||||||
gc.restore();
|
|
||||||
fitMarksToCanvas();
|
fitMarksToCanvas();
|
||||||
|
drawGoogleMap();
|
||||||
|
|
||||||
|
|
||||||
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
|
// TODO: 1/05/17 wmu16 - Change this call to now draw the marks as from the xml
|
||||||
@@ -109,6 +120,9 @@ public class CanvasController {
|
|||||||
initializeMarks();
|
initializeMarks();
|
||||||
timer = new AnimationTimer() {
|
timer = new AnimationTimer() {
|
||||||
|
|
||||||
|
private int UPDATE_FPM_PERIOD = 50; // update FPM label every 50 frames
|
||||||
|
private int updateFPMCounter = 100;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(long now) {
|
public void handle(long now) {
|
||||||
|
|
||||||
@@ -124,7 +138,11 @@ public class CanvasController {
|
|||||||
elapsedNanos = now - oldFrameTime ;
|
elapsedNanos = now - oldFrameTime ;
|
||||||
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
|
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length ;
|
||||||
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
|
frameRate = 1_000_000_000.0 / elapsedNanosPerFrame ;
|
||||||
drawFps(frameRate.intValue());
|
if (updateFPMCounter++ > UPDATE_FPM_PERIOD) {
|
||||||
|
updateFPMCounter = 0;
|
||||||
|
drawFps(frameRate.intValue());
|
||||||
|
}
|
||||||
|
raceViewController.updateSparkLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
|
// TODO: 1/05/17 cir27 - Make the RaceObjects update on the actual delay.
|
||||||
@@ -137,6 +155,30 @@ public class CanvasController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First find the top right and bottom left points' geo locations, then retrieve
|
||||||
|
* map from google to display on image view. - Haoming 22/5/2017
|
||||||
|
*/
|
||||||
|
private void drawGoogleMap() {
|
||||||
|
findMetersPerPixel();
|
||||||
|
Point2D topLeftPoint = findScaledXY(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
|
||||||
|
// distance from top left extreme to panel origin (top left corner)
|
||||||
|
double distanceFromTopLeftToOrigin = Math.sqrt(Math.pow(topLeftPoint.getX() * metersPerPixelX, 2) + Math.pow(topLeftPoint.getY() * metersPerPixelY, 2));
|
||||||
|
// angle from top left extreme to panel origin
|
||||||
|
double bearingFromTopLeftToOrigin = Math.toDegrees(Math.atan2(-topLeftPoint.getX(), topLeftPoint.getY()));
|
||||||
|
// the top left extreme
|
||||||
|
Position topLeftPos = new Position(maxLatPoint.getLatitude(), minLonPoint.getLongitude());
|
||||||
|
Position originPos = GeoUtility.getGeoCoordinate(topLeftPos, bearingFromTopLeftToOrigin, distanceFromTopLeftToOrigin);
|
||||||
|
|
||||||
|
// distance from origin corner to bottom right corner of the panel
|
||||||
|
double distanceFromOriginToBottomRight = Math.sqrt(Math.pow(PANEL_HEIGHT* metersPerPixelY, 2) + Math.pow(PANEL_WIDTH * metersPerPixelX, 2));
|
||||||
|
double bearingFromOriginToBottomRight = Math.toDegrees(Math.atan2(PANEL_WIDTH, -PANEL_HEIGHT));
|
||||||
|
Position bottomRightPos = GeoUtility.getGeoCoordinate(originPos, bearingFromOriginToBottomRight, distanceFromOriginToBottomRight);
|
||||||
|
|
||||||
|
Boundary boundary = new Boundary(originPos.getLat(), bottomRightPos.getLng(), bottomRightPos.getLat(), originPos.getLng());
|
||||||
|
CanvasMap canvasMap = new CanvasMap(boundary);
|
||||||
|
mapImage.setImage(canvasMap.getMapImage());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds border marks to the canvas, taken from the XML file
|
* Adds border marks to the canvas, taken from the XML file
|
||||||
@@ -174,8 +216,8 @@ public class CanvasController {
|
|||||||
borderPoint2.getX(), borderPoint2.getY());
|
borderPoint2.getX(), borderPoint2.getY());
|
||||||
xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX();
|
xBoundaryPoints[courseLimits.size()-1] = borderPoint1.getX();
|
||||||
yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY();
|
yBoundaryPoints[courseLimits.size()-1] = borderPoint1.getY();
|
||||||
gc.setFill(Color.LIGHTBLUE);
|
// gc.setFill(Color.LIGHTBLUE);
|
||||||
gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
|
// gc.fillPolygon(xBoundaryPoints,yBoundaryPoints,yBoundaryPoints.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateGroups(){
|
private void updateGroups(){
|
||||||
@@ -201,11 +243,9 @@ public class CanvasController {
|
|||||||
|
|
||||||
private void checkForCourseChanges() {
|
private void checkForCourseChanges() {
|
||||||
if (StreamParser.isNewRaceXmlReceived()){
|
if (StreamParser.isNewRaceXmlReceived()){
|
||||||
gc.setFill(Color.SKYBLUE);
|
gc.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
||||||
gc.fillRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
|
drawGoogleMap();
|
||||||
gc.restore();
|
|
||||||
addRaceBorder();
|
addRaceBorder();
|
||||||
canvas.toBack();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,17 +355,14 @@ public class CanvasController {
|
|||||||
|
|
||||||
private void drawFps(int fps){
|
private void drawFps(int fps){
|
||||||
if (raceViewController.isDisplayFps()){
|
if (raceViewController.isDisplayFps()){
|
||||||
gc.clearRect(5,5,50,20);
|
gc.clearRect(5, 5, 60, 30);
|
||||||
gc.setFill(Color.SKYBLUE);
|
gc.setFont(new Font(16));
|
||||||
gc.fillRect(4,4,51,21);
|
gc.setLineWidth(4);
|
||||||
gc.setFill(Color.BLACK);
|
gc.setGlobalAlpha(0.75);
|
||||||
gc.setFont(new Font(14));
|
|
||||||
gc.setLineWidth(3);
|
|
||||||
gc.fillText(fps + " FPS", 5, 20);
|
gc.fillText(fps + " FPS", 5, 20);
|
||||||
|
gc.setGlobalAlpha(0.5);
|
||||||
} else {
|
} else {
|
||||||
gc.clearRect(5,5,50,20);
|
gc.clearRect(5,5,60,30);
|
||||||
gc.setFill(Color.SKYBLUE);
|
|
||||||
gc.fillRect(4,4,51,21);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,6 +502,27 @@ public class CanvasController {
|
|||||||
return new Point2D(xAxisLocation, yAxisLocation);
|
return new Point2D(xAxisLocation, yAxisLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the number of meters per pixel.
|
||||||
|
*/
|
||||||
|
private void findMetersPerPixel () {
|
||||||
|
Point2D p1, p2;
|
||||||
|
Mark m1, m2;
|
||||||
|
double theta, distance, dx, dy, dHorizontal, dVertical;
|
||||||
|
m1 = new SingleMark("m1", maxLatPoint.getLatitude(), minLonPoint.getLongitude(), 1);
|
||||||
|
m2 = new SingleMark("m2", minLatPoint.getLatitude(), maxLonPoint.getLongitude(), 2);
|
||||||
|
p1 = findScaledXY(m1);
|
||||||
|
p2 = findScaledXY(m2);
|
||||||
|
theta = Mark.calculateHeadingRad(m1, m2);
|
||||||
|
distance = Mark.calculateDistance(m1, m2);
|
||||||
|
dHorizontal = Math.abs(Math.sin(theta) * distance);
|
||||||
|
dVertical = Math.abs(Math.cos(theta) * distance);
|
||||||
|
dx = Math.abs(p1.getX() - p2.getX());
|
||||||
|
dy = Math.abs(p1.getY() - p2.getY());
|
||||||
|
metersPerPixelX = dHorizontal / dx;
|
||||||
|
metersPerPixelY = dVertical / dy;
|
||||||
|
}
|
||||||
|
|
||||||
List<BoatGroup> getBoatGroups() {
|
List<BoatGroup> getBoatGroups() {
|
||||||
return boatGroups;
|
return boatGroups;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import javafx.collections.ObservableList;
|
|||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.geometry.Point2D;
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.geometry.Side;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.chart.LineChart;
|
||||||
|
import javafx.scene.chart.NumberAxis;
|
||||||
|
import javafx.scene.chart.XYChart;
|
||||||
|
import javafx.scene.chart.XYChart.Series;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
@@ -37,12 +42,18 @@ import seng302.models.stream.XMLParser;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by ptg19 on 29/03/17.
|
* Created by ptg19 on 29/03/17.
|
||||||
*/
|
*/
|
||||||
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
public class RaceViewController extends Thread implements ImportantAnnotationDelegate {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private LineChart raceSparkLine;
|
||||||
|
@FXML
|
||||||
|
private NumberAxis sparklineYAxis;
|
||||||
@FXML
|
@FXML
|
||||||
private VBox positionVbox;
|
private VBox positionVbox;
|
||||||
@FXML
|
@FXML
|
||||||
@@ -66,7 +77,8 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
private boolean displayFps;
|
private boolean displayFps;
|
||||||
private Timeline timerTimeline;
|
private Timeline timerTimeline;
|
||||||
private Stage stage;
|
private Stage stage;
|
||||||
|
private static HashMap<Integer, Series<String, Double>> sparkLineData = new HashMap<>();
|
||||||
|
private static ArrayList<Yacht> racingBoats = new ArrayList<>();
|
||||||
private ImportantAnnotationsState importantAnnotations;
|
private ImportantAnnotationsState importantAnnotations;
|
||||||
private Yacht selectedBoat;
|
private Yacht selectedBoat;
|
||||||
|
|
||||||
@@ -74,6 +86,14 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
// Load a default important annotation state
|
// Load a default important annotation state
|
||||||
importantAnnotations = new ImportantAnnotationsState();
|
importantAnnotations = new ImportantAnnotationsState();
|
||||||
|
|
||||||
|
//Formatting the y axis of the sparkline
|
||||||
|
raceSparkLine.getYAxis().setRotate(180);
|
||||||
|
raceSparkLine.getYAxis().setTickLabelRotation(180);
|
||||||
|
raceSparkLine.getYAxis().setTranslateX(15);
|
||||||
|
raceSparkLine.getYAxis().setAutoRanging(false);
|
||||||
|
|
||||||
|
startingBoats = new ArrayList<>(StreamParser.getBoats().values());
|
||||||
|
|
||||||
includedCanvasController.setup(this);
|
includedCanvasController.setup(this);
|
||||||
includedCanvasController.initializeCanvas();
|
includedCanvasController.initializeCanvas();
|
||||||
initializeUpdateTimer();
|
initializeUpdateTimer();
|
||||||
@@ -81,14 +101,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
initialiseAnnotationSlider();
|
initialiseAnnotationSlider();
|
||||||
initialiseBoatSelectionComboBox();
|
initialiseBoatSelectionComboBox();
|
||||||
includedCanvasController.timer.start();
|
includedCanvasController.timer.start();
|
||||||
|
selectAnnotationBtn.setOnAction(event -> loadSelectAnnotationView());
|
||||||
selectAnnotationBtn.setOnAction(event -> {
|
|
||||||
loadSelectAnnotationView();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The important annotations have been changed, update this view
|
* The important annotations have been changed, update this view
|
||||||
|
*
|
||||||
* @param importantAnnotationsState The current state of the selected annotations
|
* @param importantAnnotationsState The current state of the selected annotations
|
||||||
*/
|
*/
|
||||||
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
|
public void importantAnnotationsChanged(ImportantAnnotationsState importantAnnotationsState) {
|
||||||
@@ -96,6 +115,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
|
setAnnotations((int) annotationSlider.getValue()); // Refresh the displayed annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the "select annotations" view in a new window
|
* Loads the "select annotations" view in a new window
|
||||||
*/
|
*/
|
||||||
@@ -173,6 +193,79 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to add any new boats into the race that may have started late or not have had data received yet
|
||||||
|
*/
|
||||||
|
void updateSparkLine(){
|
||||||
|
// Collect the racing boats that aren't already in the chart
|
||||||
|
ArrayList<Yacht> sparkLineCandidates = startingBoats.stream().filter(yacht -> !sparkLineData.containsKey(yacht.getSourceID())
|
||||||
|
&& yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
|
||||||
|
// Obtain the qualifying boats to set the max on the Y axis
|
||||||
|
racingBoats = startingBoats.stream().filter(yacht ->
|
||||||
|
yacht.getPosition() != null & yacht.getPosition() != "-").collect(Collectors.toCollection(ArrayList::new));
|
||||||
|
sparklineYAxis.setUpperBound(racingBoats.size() + 1);
|
||||||
|
|
||||||
|
// Create a new data series for new boats
|
||||||
|
sparkLineCandidates.stream().filter(yacht -> yacht.getPosition() != null).forEach(yacht -> {
|
||||||
|
Series<String, Double> yachtData = new Series<>();
|
||||||
|
yachtData.setName(yacht.getBoatName());
|
||||||
|
yachtData.getData().add(new XYChart.Data<>(Integer.toString(yacht.getLegNumber()), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
|
||||||
|
sparkLineData.put(yacht.getSourceID(), yachtData);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lambda function to sort the series in order of leg (later legs shown more to the right)
|
||||||
|
List<XYChart.Series<String, Double>> positions = new ArrayList<>(sparkLineData.values());
|
||||||
|
Collections.sort(positions, (o1, o2) -> {
|
||||||
|
Integer leg1 = Integer.parseInt(o1.getData().get(o1.getData().size()-1).getXValue());
|
||||||
|
Integer leg2 = Integer.parseInt(o2.getData().get(o2.getData().size()-1).getXValue());
|
||||||
|
if (leg2 < leg1){
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Adds the new data series to the sparkline (and set the colour of the series)
|
||||||
|
raceSparkLine.setCreateSymbols(false);
|
||||||
|
positions.stream().filter(spark -> !raceSparkLine.getData().contains(spark)).forEach(spark -> {
|
||||||
|
raceSparkLine.getData().add(spark);
|
||||||
|
spark.getNode().lookup(".chart-series-line").setStyle("-fx-stroke:" + getBoatColorAsRGB(spark.getName()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the yachts sparkline of the desired boat and using the new leg number
|
||||||
|
* @param yacht The yacht to be updated on the sparkline
|
||||||
|
* @param legNumber the leg number that the position will be assigned to
|
||||||
|
*/
|
||||||
|
public static void updateYachtPositionSparkline(Yacht yacht, Integer legNumber){
|
||||||
|
XYChart.Series<String, Double> positionData = sparkLineData.get(yacht.getSourceID());
|
||||||
|
positionData.getData().add(new XYChart.Data<>(Integer.toString(legNumber), 1 + racingBoats.size() - Double.parseDouble(yacht.getPosition())));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets the rgb string of the boats colour to use for the chart via css
|
||||||
|
* @param boatName boat passed in to get the boats colour
|
||||||
|
* @return the colour as an rgb string
|
||||||
|
*/
|
||||||
|
private String getBoatColorAsRGB(String boatName){
|
||||||
|
Color color = Color.WHITE;
|
||||||
|
for (Yacht yacht: startingBoats){
|
||||||
|
if (Objects.equals(yacht.getBoatName(), boatName)){
|
||||||
|
color = yacht.getColour();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String.format( "#%02X%02X%02X",
|
||||||
|
(int)( color.getRed() * 255 ),
|
||||||
|
(int)( color.getGreen() * 255 ),
|
||||||
|
(int)( color.getBlue() * 255 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initalises a timer which updates elements of the RaceView such as wind direction, boat
|
* Initalises a timer which updates elements of the RaceView such as wind direction, boat
|
||||||
* orderings etc.. which are dependent on the info from the stream parser constantly.
|
* orderings etc.. which are dependent on the info from the stream parser constantly.
|
||||||
@@ -277,21 +370,42 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
positionVbox.getChildren().removeAll();
|
positionVbox.getChildren().removeAll();
|
||||||
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
|
|
||||||
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
// list of racing boat id
|
||||||
if (boat.getBoatStatus() == 3) { // 3 is finish status
|
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
|
||||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
.getParticipants();
|
||||||
boat.getShortName() + " (Finished)");
|
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
for (Participant p : participants) {
|
||||||
positionVbox.getChildren().add(textToAdd);
|
participantIDs.add(p.getsourceID());
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
if (StreamParser.isRaceStarted()) {
|
||||||
Text textToAdd = new Text(boat.getPosition() + ". " +
|
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
||||||
boat.getShortName() + " ");
|
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
|
||||||
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
if (boat.getBoatStatus() == 3) { // 3 is finish status
|
||||||
textToAdd.setStyle("");
|
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||||
positionVbox.getChildren().add(textToAdd);
|
boat.getShortName() + " (Finished)");
|
||||||
|
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||||
|
positionVbox.getChildren().add(textToAdd);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||||
|
boat.getShortName() + " ");
|
||||||
|
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||||
|
textToAdd.setStyle("");
|
||||||
|
positionVbox.getChildren().add(textToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Yacht boat : StreamParser.getBoats().values()) {
|
||||||
|
if (participantIDs.contains(boat.getSourceID())) { // check if the boat is racing
|
||||||
|
Text textToAdd = new Text(boat.getPosition() + ". " +
|
||||||
|
boat.getShortName() + " ");
|
||||||
|
textToAdd.setFill(Paint.valueOf("#d3d3d3"));
|
||||||
|
textToAdd.setStyle("");
|
||||||
|
positionVbox.getChildren().add(textToAdd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -422,7 +536,7 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean isDisplayFps() {
|
boolean isDisplayFps() {
|
||||||
return displayFps;
|
return displayFps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,4 +641,13 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel
|
|||||||
Stage getStage() {
|
Stage getStage() {
|
||||||
return stage;
|
return stage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for when the boat attempts to add data to the sparkline (first checks if the sparkline contains info on it)
|
||||||
|
* @param yachtId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean sparkLineStatus(Integer yachtId) {
|
||||||
|
return sparkLineData.containsKey(yachtId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package seng302.controllers;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
@@ -23,8 +24,10 @@ import javafx.scene.layout.Pane;
|
|||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import seng302.models.Yacht;
|
import seng302.models.Yacht;
|
||||||
import seng302.models.stream.StreamParser;
|
import seng302.models.stream.StreamParser;
|
||||||
|
import seng302.models.stream.XMLParser.RaceXMLObject.Participant;
|
||||||
|
|
||||||
public class StartScreenController implements Initializable {
|
public class StartScreenController implements Initializable {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private GridPane gridPane;
|
private GridPane gridPane;
|
||||||
@FXML
|
@FXML
|
||||||
@@ -48,19 +51,18 @@ public class StartScreenController implements Initializable {
|
|||||||
|
|
||||||
private boolean switchedToRaceView = false;
|
private boolean switchedToRaceView = false;
|
||||||
|
|
||||||
private void setContentPane(String jfxUrl){
|
private void setContentPane(String jfxUrl) {
|
||||||
try{
|
try {
|
||||||
// get the main controller anchor pane (MainView.fxml)
|
// get the main controller anchor pane (MainView.fxml)
|
||||||
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
|
AnchorPane contentPane = (AnchorPane) gridPane.getParent();
|
||||||
contentPane.getChildren().removeAll();
|
contentPane.getChildren().removeAll();
|
||||||
contentPane.getChildren().clear();
|
contentPane.getChildren().clear();
|
||||||
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString());
|
||||||
contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
|
contentPane.getChildren()
|
||||||
}
|
.addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl)));
|
||||||
catch(javafx.fxml.LoadException e){
|
} catch (javafx.fxml.LoadException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
} catch (IOException e) {
|
||||||
catch(IOException e){
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +74,8 @@ public class StartScreenController implements Initializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Running a timer to update the livestream status on welcome screen. Update interval is 1 second.
|
* Running a timer to update the livestream status on welcome screen. Update interval is 1
|
||||||
|
* second.
|
||||||
*/
|
*/
|
||||||
public void startStream() {
|
public void startStream() {
|
||||||
if (StreamParser.isStreamStatus()) {
|
if (StreamParser.isStreamStatus()) {
|
||||||
@@ -102,8 +105,10 @@ public class StartScreenController implements Initializable {
|
|||||||
updateTeamList();
|
updateTeamList();
|
||||||
timeTillLive.setTextFill(Color.RED);
|
timeTillLive.setTextFill(Color.RED);
|
||||||
switchToRaceViewButton.setDisable(false);
|
switchToRaceViewButton.setDisable(false);
|
||||||
String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60);
|
String timerMinute = Long
|
||||||
String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60);
|
.toString(StreamParser.getTimeSinceStart() / 60);
|
||||||
|
String timerSecond = Long
|
||||||
|
.toString(StreamParser.getTimeSinceStart() % 60);
|
||||||
if (timerSecond.length() == 1) {
|
if (timerSecond.length() == 1) {
|
||||||
timerSecond = "0" + timerSecond;
|
timerSecond = "0" + timerSecond;
|
||||||
}
|
}
|
||||||
@@ -114,8 +119,10 @@ public class StartScreenController implements Initializable {
|
|||||||
updateTeamList();
|
updateTeamList();
|
||||||
timeTillLive.setTextFill(Color.BLACK);
|
timeTillLive.setTextFill(Color.BLACK);
|
||||||
switchToRaceViewButton.setDisable(false);
|
switchToRaceViewButton.setDisable(false);
|
||||||
String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60);
|
String timerMinute = Long
|
||||||
String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60);
|
.toString(-1 * StreamParser.getTimeSinceStart() / 60);
|
||||||
|
String timerSecond = Long
|
||||||
|
.toString(-1 * StreamParser.getTimeSinceStart() % 60);
|
||||||
if (timerSecond.length() == 1) {
|
if (timerSecond.length() == 1) {
|
||||||
timerSecond = "0" + timerSecond;
|
timerSecond = "0" + timerSecond;
|
||||||
}
|
}
|
||||||
@@ -143,19 +150,40 @@ public class StartScreenController implements Initializable {
|
|||||||
teamList.setItems(data);
|
teamList.setItems(data);
|
||||||
|
|
||||||
boatNameCol.setCellValueFactory(
|
boatNameCol.setCellValueFactory(
|
||||||
new PropertyValueFactory<>("boatName")
|
new PropertyValueFactory<>("boatName")
|
||||||
);
|
);
|
||||||
shortNameCol.setCellValueFactory(
|
shortNameCol.setCellValueFactory(
|
||||||
new PropertyValueFactory<>("shortName")
|
new PropertyValueFactory<>("shortName")
|
||||||
);
|
);
|
||||||
countryCol.setCellValueFactory(
|
countryCol.setCellValueFactory(
|
||||||
new PropertyValueFactory<>("country")
|
new PropertyValueFactory<>("country")
|
||||||
);
|
);
|
||||||
posCol.setCellValueFactory(
|
posCol.setCellValueFactory(
|
||||||
new PropertyValueFactory<>("position")
|
new PropertyValueFactory<>("position")
|
||||||
);
|
);
|
||||||
|
|
||||||
data.addAll(StreamParser.getBoatsPos().values());
|
// check if the boat is racing
|
||||||
|
ArrayList<Participant> participants = StreamParser.getXmlObject().getRaceXML()
|
||||||
|
.getParticipants();
|
||||||
|
ArrayList<Integer> participantIDs = new ArrayList<>();
|
||||||
|
for (Participant p : participants) {
|
||||||
|
participantIDs.add(p.getsourceID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// add boats to the start screen list
|
||||||
|
if (StreamParser.isRaceStarted()) { // if race is started, use StreamParser.getBoatsPos()
|
||||||
|
for (Yacht boat : StreamParser.getBoatsPos().values()) {
|
||||||
|
if (participantIDs.contains(boat.getSourceID())) {
|
||||||
|
data.add(boat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // else use StreamParser.getBoats()
|
||||||
|
for (Yacht boat : StreamParser.getBoats().values()) {
|
||||||
|
if (participantIDs.contains(boat.getSourceID())) {
|
||||||
|
data.add(boat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
teamList.refresh();
|
teamList.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package seng302.models;
|
|||||||
|
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import seng302.models.mark.Mark;
|
import seng302.models.mark.Mark;
|
||||||
|
import seng302.controllers.RaceViewController;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@@ -11,9 +12,10 @@ import seng302.models.stream.XMLParser.RaceXMLObject.Corner;
|
|||||||
* Yacht class for the racing boat.
|
* Yacht class for the racing boat.
|
||||||
*
|
*
|
||||||
* Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class,
|
* Class created to store more variables (eg. boat statuses) compared to the XMLParser boat class,
|
||||||
* also done outside Boat class because some old variables are not used anymore.
|
* also done outside Boat class because some old variables are not used anymore.
|
||||||
*/
|
*/
|
||||||
public class Yacht {
|
public class Yacht {
|
||||||
|
|
||||||
// Used in boat group
|
// Used in boat group
|
||||||
private Color colour;
|
private Color colour;
|
||||||
private double velocity;
|
private double velocity;
|
||||||
@@ -37,21 +39,22 @@ public class Yacht {
|
|||||||
private Mark lastMarkRounded;
|
private Mark lastMarkRounded;
|
||||||
private Mark nextMark;
|
private Mark nextMark;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used in EventTest and RaceTest.
|
* Used in EventTest and RaceTest.
|
||||||
*
|
*
|
||||||
* @param boatName Create a yacht object with name.
|
* @param boatName Create a yacht object with name.
|
||||||
*/
|
*/
|
||||||
public Yacht (String boatName) {
|
public Yacht(String boatName) {
|
||||||
this.boatName = boatName;
|
this.boatName = boatName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used in BoatGroupTest.
|
* Used in BoatGroupTest.
|
||||||
*
|
*
|
||||||
* @param boatName The name of the team sailing the boat
|
* @param boatName The name of the team sailing the boat
|
||||||
* @param boatVelocity The speed of the boat in meters/second
|
* @param boatVelocity The speed of the boat in meters/second
|
||||||
* @param shortName A shorter version of the teams name
|
* @param shortName A shorter version of the teams name
|
||||||
*/
|
*/
|
||||||
public Yacht(String boatName, double boatVelocity, String shortName, int id) {
|
public Yacht(String boatName, double boatVelocity, String shortName, int id) {
|
||||||
this.boatName = boatName;
|
this.boatName = boatName;
|
||||||
@@ -60,30 +63,37 @@ public class Yacht {
|
|||||||
this.sourceID = id;
|
this.sourceID = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Yacht(String boatType, Integer sourceID, String hullID, String shortName, String boatName, String country) {
|
public Yacht(String boatType, Integer sourceID, String hullID, String shortName,
|
||||||
|
String boatName, String country) {
|
||||||
this.boatType = boatType;
|
this.boatType = boatType;
|
||||||
this.sourceID = sourceID;
|
this.sourceID = sourceID;
|
||||||
this.hullID = hullID;
|
this.hullID = hullID;
|
||||||
this.shortName = shortName;
|
this.shortName = shortName;
|
||||||
this.boatName = boatName;
|
this.boatName = boatName;
|
||||||
this.country = country;
|
this.country = country;
|
||||||
|
this.position = "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBoatType() {
|
public String getBoatType() {
|
||||||
return boatType;
|
return boatType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getSourceID() {
|
public Integer getSourceID() {
|
||||||
return sourceID;
|
return sourceID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHullID() {
|
public String getHullID() {
|
||||||
return hullID;
|
return hullID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getShortName() {
|
public String getShortName() {
|
||||||
return shortName;
|
return shortName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBoatName() {
|
public String getBoatName() {
|
||||||
return boatName;
|
return boatName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCountry() {
|
public String getCountry() {
|
||||||
return country;
|
return country;
|
||||||
}
|
}
|
||||||
@@ -101,6 +111,9 @@ public class Yacht {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setLegNumber(Integer legNumber) {
|
public void setLegNumber(Integer legNumber) {
|
||||||
|
if (colour != null && position != "-" && legNumber != this.legNumber&& RaceViewController.sparkLineStatus(sourceID)) {
|
||||||
|
RaceViewController.updateYachtPositionSparkline(this, legNumber);
|
||||||
|
}
|
||||||
this.legNumber = legNumber;
|
this.legNumber = legNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Boundary class represents a rectangle territorial boundary on a map. It
|
||||||
|
* contains four extremity double values(N, E, S, W). N and S are represented as
|
||||||
|
* latitudes in radians. E and W are represented as longitudes in radians.
|
||||||
|
*
|
||||||
|
* Created by Haoming on 10/5/17
|
||||||
|
*/
|
||||||
|
public class Boundary {
|
||||||
|
|
||||||
|
private double northLat, eastLng, southLat, westLng;
|
||||||
|
|
||||||
|
public Boundary(double northLat, double eastLng, double southLat, double westLng) {
|
||||||
|
this.northLat = northLat;
|
||||||
|
this.eastLng = eastLng;
|
||||||
|
this.southLat = southLat;
|
||||||
|
this.westLng = westLng;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getCentreLat() {
|
||||||
|
return (northLat + southLat) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getCentreLng() {
|
||||||
|
return (eastLng + westLng) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getNorthLat() {
|
||||||
|
return northLat;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getEastLng() {
|
||||||
|
return eastLng;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getSouthLat() {
|
||||||
|
return southLat;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getWestLng() {
|
||||||
|
return westLng;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import java.lang.Math;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CanvasMap retrieves a map image with given geo boundary from Google Map server.
|
||||||
|
* By passing a rectangle like geo boundary, it returns a map image with the
|
||||||
|
* highest resolution. However, due to free quote account usage limit, the maximum
|
||||||
|
* resolution is only 1280 * 1280.
|
||||||
|
*
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
public class CanvasMap {
|
||||||
|
|
||||||
|
private Boundary boundary;
|
||||||
|
private long width, height; // desired image size
|
||||||
|
private int zoom;
|
||||||
|
|
||||||
|
private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE";
|
||||||
|
|
||||||
|
public CanvasMap(Boundary boundary) {
|
||||||
|
this.boundary = boundary;
|
||||||
|
calculateOptimalMapSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Image getMapImage() {
|
||||||
|
try {
|
||||||
|
URL url = new URL(getRequest());
|
||||||
|
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
|
||||||
|
return new Image(connection.getInputStream());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRequest() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("https://maps.googleapis.com/maps/api/staticmap?");
|
||||||
|
sb.append(String.format("center=%f,%f", boundary.getCentreLat(), boundary.getCentreLng()));
|
||||||
|
sb.append(String.format("&zoom=%d", zoom));
|
||||||
|
sb.append(String.format("&size=%dx%d&scale=2", width, height));
|
||||||
|
sb.append("&style=feature:all|element:labels|visibility:off"); // hide all labels on map
|
||||||
|
// sb.append(String.format("&markers=%f,%f", boundary.getSouthLat(), boundary.getWestLng()));
|
||||||
|
// sb.append(String.format("&key=%s", KEY));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateOptimalMapSize() {
|
||||||
|
for (int z = 20; z > 0; z--) {
|
||||||
|
MapSize mapSize = getMapSize(z, boundary);
|
||||||
|
zoom = z;
|
||||||
|
width = mapSize.width;
|
||||||
|
height = mapSize.height;
|
||||||
|
// if map size is valid, exit the loop as we have the highest resolution
|
||||||
|
if (mapSize.isValid()) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapSize getMapSize(int zoom, Boundary boundary) {
|
||||||
|
double scale = Math.pow(2, zoom);
|
||||||
|
MapGeo geoSW = new MapGeo(boundary.getSouthLat(), boundary.getWestLng());
|
||||||
|
MapGeo geoNE = new MapGeo(boundary.getNorthLat(), boundary.getEastLng());
|
||||||
|
MapPoint pointSW = MercatorProjection.toMapPoint(geoSW);
|
||||||
|
MapPoint pointNE = MercatorProjection.toMapPoint(geoNE);
|
||||||
|
return new MapSize(Math.abs(pointNE.getX() - pointSW.getX()) * scale,
|
||||||
|
Math.abs(pointNE.getY() - pointSW.getY()) * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MapSize {
|
||||||
|
long width, height;
|
||||||
|
|
||||||
|
MapSize(double width, double height) {
|
||||||
|
this.width = Math.round(width);
|
||||||
|
this.height = Math.round(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map size is valid when width and height are both less than 640 pixels
|
||||||
|
* @return true if both dimensions are less than 640px
|
||||||
|
*/
|
||||||
|
boolean isValid() {
|
||||||
|
return Math.max(width, height) <= 640;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getZoom() {
|
||||||
|
return zoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class represent Geo location (latitude, longitude).
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
class MapGeo {
|
||||||
|
|
||||||
|
private double lat, lng;
|
||||||
|
|
||||||
|
MapGeo(double lat, double lng) {
|
||||||
|
this.lat = lat;
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getLat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLat(double lat) {
|
||||||
|
this.lat = lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getLng() {
|
||||||
|
return lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLng(double lng) {
|
||||||
|
this.lng = lng;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class represent euclidean planar point (x, y)
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
class MapPoint {
|
||||||
|
|
||||||
|
private double x, y;
|
||||||
|
|
||||||
|
MapPoint(double x, double y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setX(double x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setY(double y) {
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An utility class useful to convert between Geo locations and Mercator projection
|
||||||
|
* planar coordinates.
|
||||||
|
* Created by Haoming on 15/5/2017
|
||||||
|
*/
|
||||||
|
public class MercatorProjection {
|
||||||
|
|
||||||
|
private static final double MERCATOR_RANGE = 256;
|
||||||
|
private static final double pixelsPerLngDegree = MERCATOR_RANGE / 360.0;
|
||||||
|
private static final double pixelsPerLngRadian = MERCATOR_RANGE / (2 * Math.PI);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A help function keeps the value in bound between -0.9999 and 0.9999.
|
||||||
|
* @param value in bound value
|
||||||
|
* @return the value in bound
|
||||||
|
*/
|
||||||
|
private static double bound(double value) {
|
||||||
|
return Math.min(Math.max(value, -0.9999), 0.9999);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Projects a Geo Location (lat, lng) on a planar
|
||||||
|
* @param geo MapGeo (lat, lng) location to be projected
|
||||||
|
* @return the projection GeoPoint (x, y) on planar
|
||||||
|
*/
|
||||||
|
public static MapPoint toMapPoint(MapGeo geo) {
|
||||||
|
MapPoint point = new MapPoint(0, 0);
|
||||||
|
MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
|
||||||
|
point.setX(origin.getX() + geo.getLng() * pixelsPerLngDegree);
|
||||||
|
|
||||||
|
// NOTE(appleton): Truncating to 0.9999 effectively limits latitude to
|
||||||
|
// 89.189. This is about a third of a tile past the edge of the world tile.
|
||||||
|
double sinY = bound(Math.sin(Math.toRadians(geo.getLat())));
|
||||||
|
point.setY(origin.getY() + 0.5 * Math.log((1 + sinY) / (1 - sinY)) * (-pixelsPerLngRadian));
|
||||||
|
return point;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the planar projection (x, y) back to Geo Location (lat, lng)
|
||||||
|
* @param point MapPoint (x, y) to be converted back
|
||||||
|
* @return the original Geo location converted from the given projection point
|
||||||
|
*/
|
||||||
|
public static MapGeo toMapGeo(MapPoint point) {
|
||||||
|
MapPoint origin = new MapPoint(MERCATOR_RANGE / 2.0, MERCATOR_RANGE / 2.0);
|
||||||
|
double lng = (point.getX() - origin.getX()) / pixelsPerLngDegree;
|
||||||
|
double latRadians = (point.getY() - origin.getY()) / (-pixelsPerLngRadian);
|
||||||
|
double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians)) - Math.PI / 2.0);
|
||||||
|
return new MapGeo(lat, lng);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.scene.canvas.Canvas;
|
||||||
|
import javafx.scene.canvas.GraphicsContext;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class TestMapController implements Initializable{
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Canvas mapCanvas;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
GraphicsContext gc = mapCanvas.getGraphicsContext2D();
|
||||||
|
Boundary bound = new Boundary(57.662943, 11.848501, 57.673945, 11.824966);
|
||||||
|
CanvasMap canvasMap = new CanvasMap(bound);
|
||||||
|
gc.drawImage(canvasMap.getMapImage(), 0, 0, canvasMap.getWidth(), canvasMap.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -181,4 +181,8 @@ Remove scroll bars
|
|||||||
-fx-background-radius: 0;
|
-fx-background-radius: 0;
|
||||||
-fx-background-insets: 0;
|
-fx-background-insets: 0;
|
||||||
-fx-padding: 0;
|
-fx-padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart{
|
||||||
|
-fx-background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
|
|
||||||
<AnchorPane fx:id="canvasPane" prefHeight="960.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.CanvasController" />
|
<AnchorPane fx:id="canvasPane" maxHeight="960.0" maxWidth="1280.0" prefHeight="960.0" prefWidth="1280.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.CanvasController" />
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.chart.*?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.shape.*?>
|
<?import javafx.scene.shape.*?>
|
||||||
<?import javafx.scene.text.*?>
|
<?import javafx.scene.text.*?>
|
||||||
|
|
||||||
<GridPane prefHeight="1080.0" prefWidth="1920.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.RaceViewController">
|
<GridPane maxHeight="960.0" maxWidth="1530.0" prefHeight="960.0" prefWidth="1530.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.controllers.RaceViewController">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
|
<ColumnConstraints maxWidth="246.0" minWidth="246.0" prefWidth="246.0" />
|
||||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />
|
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="1034.0" />
|
||||||
@@ -16,7 +18,7 @@
|
|||||||
<RowConstraints />
|
<RowConstraints />
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<AnchorPane prefHeight="200.0" prefWidth="200.0" style="-fx-background-color: #2C2c36;" GridPane.rowSpan="3">
|
<AnchorPane prefHeight="960.0" prefWidth="250.0" style="-fx-background-color: #2C2c36;" GridPane.rowSpan="3">
|
||||||
<children>
|
<children>
|
||||||
<Label layoutX="11.0" layoutY="259.0" text="Team Position" textFill="WHITE" />
|
<Label layoutX="11.0" layoutY="259.0" text="Team Position" textFill="WHITE" />
|
||||||
<Label layoutX="13.0" layoutY="432.0" text="Settings" textFill="WHITE" />
|
<Label layoutX="13.0" layoutY="432.0" text="Settings" textFill="WHITE" />
|
||||||
@@ -49,9 +51,17 @@
|
|||||||
<Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
|
<Button fx:id="selectAnnotationBtn" layoutX="35.0" layoutY="578.0" mnemonicParsing="false" prefHeight="18.0" prefWidth="170.0" styleClass="blue-ui-btn" text="Select Annotations" textFill="WHITE" />
|
||||||
<Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" />
|
<Text fill="WHITE" layoutX="11.0" layoutY="649.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Boat Selection" />
|
||||||
<ComboBox fx:id="boatSelectionComboBox" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
|
<ComboBox fx:id="boatSelectionComboBox" layoutX="37.0" layoutY="664.0" prefHeight="25.0" prefWidth="170.0" promptText="Select Boat" styleClass="combo-box-base" />
|
||||||
|
<LineChart fx:id="raceSparkLine" layoutX="-15.0" layoutY="719.0" legendVisible="false" prefHeight="277.0" prefWidth="260.0" title="Boat Positions">
|
||||||
|
<xAxis>
|
||||||
|
<CategoryAxis label="Leg Number" side="BOTTOM" styleClass="spark-line-xaxis" />
|
||||||
|
</xAxis>
|
||||||
|
<yAxis>
|
||||||
|
<NumberAxis fx:id="sparklineYAxis" minorTickCount="1" minorTickLength="1.0" side="LEFT" styleClass="spark-line-yaxis" tickLabelGap="1.0" tickUnit="1.0" upperBound="7.0" />
|
||||||
|
</yAxis>
|
||||||
|
</LineChart>
|
||||||
</children>
|
</children>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
<AnchorPane fx:id="contentAnchorPane" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
|
<AnchorPane fx:id="contentAnchorPane" maxHeight="960.0" maxWidth="1280.0" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: skyblue;" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.rowSpan="2147483647" GridPane.valignment="TOP">
|
||||||
<children>
|
<children>
|
||||||
<fx:include fx:id="includedCanvas" source="CanvasView.fxml" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
<fx:include fx:id="includedCanvas" source="CanvasView.fxml" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
||||||
</children></AnchorPane>
|
</children></AnchorPane>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<?import java.lang.*?>
|
||||||
|
<?import javafx.scene.canvas.*?>
|
||||||
|
|
||||||
|
|
||||||
|
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="960.0" prefWidth="1280.0" style="-fx-background-color: #ddd;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="seng302.models.map.TestMapController">
|
||||||
|
<children>
|
||||||
|
<Canvas fx:id="mapCanvas" height="960.0" width="1280.0" />
|
||||||
|
</children>
|
||||||
|
</Pane>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package seng302.models.map;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for Mercator Project class.
|
||||||
|
* Created by hyi25 on 15/05/17.
|
||||||
|
*/
|
||||||
|
public class MercatorProjectionTest {
|
||||||
|
@Test
|
||||||
|
public void toMapPoint() throws Exception {
|
||||||
|
MapGeo geo1 = new MapGeo(12.485394, 19.38947);
|
||||||
|
MapPoint actualPoint1 = MercatorProjection.toMapPoint(geo1);
|
||||||
|
MapPoint expectedPoint1 = new MapPoint(141.78806755555556, 119.0503853635612);
|
||||||
|
assertEquals(expectedPoint1.getX(), actualPoint1.getX(), 0.0001);
|
||||||
|
assertEquals(expectedPoint1.getY(), actualPoint1.getY(), 0.0001);
|
||||||
|
|
||||||
|
MapGeo geo2 = new MapGeo(77.456432, -23.456462);
|
||||||
|
MapPoint actualPoint2 = MercatorProjection.toMapPoint(geo2);
|
||||||
|
MapPoint expectedPoint2 = new MapPoint(111.31984924444444, 38.03143323746788);
|
||||||
|
assertEquals(expectedPoint2.getX(), actualPoint2.getX(), 0.0001);
|
||||||
|
assertEquals(expectedPoint2.getY(), actualPoint2.getY(), 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void toMapGeo() throws Exception {
|
||||||
|
MapPoint point1 = new MapPoint(123.1234, 25.4565);
|
||||||
|
MapGeo actualGeo1 = MercatorProjection.toMapGeo(point1);
|
||||||
|
MapGeo expectedGeo1 = new MapGeo(80.77043127275441, -6.857718749999995);
|
||||||
|
assertEquals(expectedGeo1.getLat(), actualGeo1.getLat(), 0.0001);
|
||||||
|
assertEquals(expectedGeo1.getLng(), actualGeo1.getLng(), 0.0001);
|
||||||
|
|
||||||
|
MapPoint point2 = new MapPoint(1.235, 255.4565);
|
||||||
|
MapGeo actualGeo2 = MercatorProjection.toMapGeo(point2);
|
||||||
|
MapGeo expectedGeo2 = new MapGeo(-84.98475532898011, -178.26328125);
|
||||||
|
assertEquals(expectedGeo2.getLat(), actualGeo2.getLat(), 0.0001);
|
||||||
|
assertEquals(expectedGeo2.getLng(), actualGeo2.getLng(), 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user