From 8fa7829a3c13cb6581d0974dcdf1f4db09d3bfbe Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 10 May 2017 20:52:46 +1200 Subject: [PATCH 01/30] Created a canvas map class to fetch map image from google - also added Bound class to encapsulate map boundary. - created TestMapView and its controller just for testing. #story[928] --- src/main/java/seng302/App.java | 109 +++++++++--------- src/main/java/seng302/models/map/Bound.java | 44 +++++++ .../java/seng302/models/map/CanvasMap.java | 49 ++++++++ .../seng302/models/map/TestMapController.java | 23 ++++ src/main/resources/views/TestMapView.fxml | 13 +++ 5 files changed, 184 insertions(+), 54 deletions(-) create mode 100644 src/main/java/seng302/models/map/Bound.java create mode 100644 src/main/java/seng302/models/map/CanvasMap.java create mode 100644 src/main/java/seng302/models/map/TestMapController.java create mode 100644 src/main/resources/views/TestMapView.fxml diff --git a/src/main/java/seng302/App.java b/src/main/java/seng302/App.java index 1a400afd..4c38a47a 100644 --- a/src/main/java/seng302/App.java +++ b/src/main/java/seng302/App.java @@ -13,67 +13,68 @@ public class App extends Application { @Override public void start(Stage primaryStage) throws Exception { - Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml")); +// Parent root = FXMLLoader.load(getClass().getResource("/views/MainView.fxml")); + Parent root = FXMLLoader.load(getClass().getResource("/views/TestMapView.fxml")); primaryStage.setTitle("RaceVision"); primaryStage.setScene(new Scene(root)); - primaryStage.setMaximized(true); +// primaryStage.setMaximized(true); primaryStage.show(); - primaryStage.setOnCloseRequest(e -> { - StreamParser.appClose(); - StreamReceiver.noMoreBytes(); - System.out.println("[CLIENT] Exiting program"); - System.exit(0); - }); +// primaryStage.setOnCloseRequest(e -> { +// StreamParser.appClose(); +// StreamReceiver.noMoreBytes(); +// System.out.println("[CLIENT] Exiting program"); +// System.exit(0); +// }); } - public static void main(String[] args) { - StreamReceiver sr = null; - - new ServerThread("Racevision Test Server"); - - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (args.length == 1 && args[0].equals("-standalone")){ - return; - } - - if (args.length == 3 && args[0].equals("-server")){ - - sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream"); - - } else if(args.length == 2 && args[0].equals("-server")){ - switch (args[1]) { - case "internal": - sr = new StreamReceiver("localhost", 4949, "RaceStream"); - break; - case "staffserver": - sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); - break; - case "official": - sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); - break; - } - } - //Change the StreamReceiver in this else block to change the default data source. - else{ - sr = new StreamReceiver("localhost", 4949, "RaceStream"); - } - - sr.start(); - StreamParser streamParser = new StreamParser("StreamParser"); - streamParser.start(); - - launch(args); - - - - } +// public static void main(String[] args) { +// StreamReceiver sr = null; +// +// new ServerThread("Racevision Test Server"); +// +// try { +// Thread.sleep(2000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// +// if (args.length == 1 && args[0].equals("-standalone")){ +// return; +// } +// +// if (args.length == 3 && args[0].equals("-server")){ +// +// sr = new StreamReceiver(args[1], Integer.valueOf(args[2]), "RaceStream"); +// +// } else if(args.length == 2 && args[0].equals("-server")){ +// switch (args[1]) { +// case "internal": +// sr = new StreamReceiver("localhost", 4949, "RaceStream"); +// break; +// case "staffserver": +// sr = new StreamReceiver("csse-s302staff.canterbury.ac.nz", 4941, "RaceStream"); +// break; +// case "official": +// sr = new StreamReceiver("livedata.americascup.com", 4941, "RaceStream"); +// break; +// } +// } +// //Change the StreamReceiver in this else block to change the default data source. +// else{ +// sr = new StreamReceiver("localhost", 4949, "RaceStream"); +// } +// +// sr.start(); +// StreamParser streamParser = new StreamParser("StreamParser"); +// streamParser.start(); +// +// launch(args); +// +// +// +// } } diff --git a/src/main/java/seng302/models/map/Bound.java b/src/main/java/seng302/models/map/Bound.java new file mode 100644 index 00000000..aa700423 --- /dev/null +++ b/src/main/java/seng302/models/map/Bound.java @@ -0,0 +1,44 @@ +package seng302.models.map; + +/** + * The Bound class is to represent square territorial bounds 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 Bound { + + private double north, east, south, west; + + public Bound(double north, double east, double south, double west) { + this.north = north; + this.east = east; + this.south = south; + this.west = west; + } + + public double getCentreLat() { + return (north + south) / 2; + } + + public double getCentreLng() { + return (east + west) / 2; + } + + public double getNorth() { + return north; + } + + public double getEast() { + return east; + } + + public double getSouth() { + return south; + } + + public double getWest() { + return west; + } +} diff --git a/src/main/java/seng302/models/map/CanvasMap.java b/src/main/java/seng302/models/map/CanvasMap.java new file mode 100644 index 00000000..4a524e49 --- /dev/null +++ b/src/main/java/seng302/models/map/CanvasMap.java @@ -0,0 +1,49 @@ +package seng302.models.map; + +import javafx.scene.image.Image; + +import javax.imageio.ImageIO; +import javax.net.ssl.HttpsURLConnection; +import java.awt.image.BufferedImage; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; + +public class CanvasMap { + + private Bound bound; + private double width, height; // desired image size + private int zoom; + private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE"; + + public CanvasMap(Bound bound, double width, double height) { + this.bound = bound; + this.width = width; + this.height = height; + } + + public Image getMapImage() { + + try { + System.out.println(getRequest()); + 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() { + zoom = 14; + StringBuilder sb = new StringBuilder(); + sb.append("https://maps.googleapis.com/maps/api/staticmap?"); + sb.append(String.format("center=%f,%f", bound.getCentreLat(), bound.getCentreLng())); + sb.append(String.format("&zoom=%d", zoom)); + sb.append(String.format("&size=%.0fx%.0f&scale=2", width / 2, height / 2)); + sb.append(String.format("&key=%s", KEY)); + return sb.toString(); + } +} diff --git a/src/main/java/seng302/models/map/TestMapController.java b/src/main/java/seng302/models/map/TestMapController.java new file mode 100644 index 00000000..720dd408 --- /dev/null +++ b/src/main/java/seng302/models/map/TestMapController.java @@ -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(); + Bound bound = new Bound(57.662943, 11.848501, 57.673945, 11.824966); + CanvasMap canvasMap = new CanvasMap(bound, 1280, 960); + gc.drawImage(canvasMap.getMapImage(), 0, 0, 1280, 960); + } +} diff --git a/src/main/resources/views/TestMapView.fxml b/src/main/resources/views/TestMapView.fxml new file mode 100644 index 00000000..84df98f3 --- /dev/null +++ b/src/main/resources/views/TestMapView.fxml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + From 3fd8b1b8555d163399217737995a40afdcb8f6ef Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Mon, 15 May 2017 12:24:36 +1200 Subject: [PATCH 02/30] Created Mercator projection to convert between Geo location and planar projection point. - MapGeo and MapPoint encapsulate geo location and planar projection point into classes. #story[928] --- .../models/map/{Bound.java => Boundary.java} | 6 +-- .../java/seng302/models/map/CanvasMap.java | 18 +++---- src/main/java/seng302/models/map/MapGeo.java | 27 ++++++++++ .../java/seng302/models/map/MapPoint.java | 27 ++++++++++ .../models/map/MercatorProjection.java | 52 +++++++++++++++++++ .../seng302/models/map/TestMapController.java | 2 +- 6 files changed, 119 insertions(+), 13 deletions(-) rename src/main/java/seng302/models/map/{Bound.java => Boundary.java} (78%) create mode 100644 src/main/java/seng302/models/map/MapGeo.java create mode 100644 src/main/java/seng302/models/map/MapPoint.java create mode 100644 src/main/java/seng302/models/map/MercatorProjection.java diff --git a/src/main/java/seng302/models/map/Bound.java b/src/main/java/seng302/models/map/Boundary.java similarity index 78% rename from src/main/java/seng302/models/map/Bound.java rename to src/main/java/seng302/models/map/Boundary.java index aa700423..f2d1302c 100644 --- a/src/main/java/seng302/models/map/Bound.java +++ b/src/main/java/seng302/models/map/Boundary.java @@ -1,17 +1,17 @@ package seng302.models.map; /** - * The Bound class is to represent square territorial bounds on a map. It contains + * The Boundary class represents a square territorial bound 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 Bound { +public class Boundary { private double north, east, south, west; - public Bound(double north, double east, double south, double west) { + public Boundary(double north, double east, double south, double west) { this.north = north; this.east = east; this.south = south; diff --git a/src/main/java/seng302/models/map/CanvasMap.java b/src/main/java/seng302/models/map/CanvasMap.java index 4a524e49..a86be90b 100644 --- a/src/main/java/seng302/models/map/CanvasMap.java +++ b/src/main/java/seng302/models/map/CanvasMap.java @@ -2,28 +2,27 @@ package seng302.models.map; import javafx.scene.image.Image; -import javax.imageio.ImageIO; import javax.net.ssl.HttpsURLConnection; -import java.awt.image.BufferedImage; -import java.io.BufferedReader; -import java.io.InputStreamReader; import java.net.URL; +import java.lang.Math; + public class CanvasMap { - private Bound bound; + private Boundary bound; private double width, height; // desired image size private int zoom; + + private int MERCATOR_RANGE = 256; private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE"; - public CanvasMap(Bound bound, double width, double height) { + public CanvasMap(Boundary bound, double width, double height) { this.bound = bound; this.width = width; this.height = height; } public Image getMapImage() { - try { System.out.println(getRequest()); URL url = new URL(getRequest()); @@ -37,13 +36,14 @@ public class CanvasMap { } private String getRequest() { - zoom = 14; + zoom = 15; StringBuilder sb = new StringBuilder(); sb.append("https://maps.googleapis.com/maps/api/staticmap?"); sb.append(String.format("center=%f,%f", bound.getCentreLat(), bound.getCentreLng())); sb.append(String.format("&zoom=%d", zoom)); sb.append(String.format("&size=%.0fx%.0f&scale=2", width / 2, height / 2)); - sb.append(String.format("&key=%s", KEY)); + sb.append("&style=feature:all|element:labels|visibility:off"); // hide all labels on map +// sb.append(String.format("&key=%s", KEY)); return sb.toString(); } } diff --git a/src/main/java/seng302/models/map/MapGeo.java b/src/main/java/seng302/models/map/MapGeo.java new file mode 100644 index 00000000..96af1dd5 --- /dev/null +++ b/src/main/java/seng302/models/map/MapGeo.java @@ -0,0 +1,27 @@ +package seng302.models.map; + +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; + } +} diff --git a/src/main/java/seng302/models/map/MapPoint.java b/src/main/java/seng302/models/map/MapPoint.java new file mode 100644 index 00000000..aa0e55d0 --- /dev/null +++ b/src/main/java/seng302/models/map/MapPoint.java @@ -0,0 +1,27 @@ +package seng302.models.map; + +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; + } +} diff --git a/src/main/java/seng302/models/map/MercatorProjection.java b/src/main/java/seng302/models/map/MercatorProjection.java new file mode 100644 index 00000000..4a442123 --- /dev/null +++ b/src/main/java/seng302/models/map/MercatorProjection.java @@ -0,0 +1,52 @@ +package seng302.models.map; + +public class MercatorProjection { + + private double MERCATOR_RANGE = 256; + private double pixelsPerLngDegree, pixelsPerLngRadian; + + + public MercatorProjection() { + pixelsPerLngDegree = MERCATOR_RANGE / 360.0; + 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 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 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 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); + } +} diff --git a/src/main/java/seng302/models/map/TestMapController.java b/src/main/java/seng302/models/map/TestMapController.java index 720dd408..6d656231 100644 --- a/src/main/java/seng302/models/map/TestMapController.java +++ b/src/main/java/seng302/models/map/TestMapController.java @@ -16,7 +16,7 @@ public class TestMapController implements Initializable{ @Override public void initialize(URL location, ResourceBundle resources) { GraphicsContext gc = mapCanvas.getGraphicsContext2D(); - Bound bound = new Bound(57.662943, 11.848501, 57.673945, 11.824966); + Boundary bound = new Boundary(57.662943, 11.848501, 57.673945, 11.824966); CanvasMap canvasMap = new CanvasMap(bound, 1280, 960); gc.drawImage(canvasMap.getMapImage(), 0, 0, 1280, 960); } From 4b1a4aae871a5bd44c19ef2853b7952d4e2e4f61 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Mon, 15 May 2017 13:23:04 +1200 Subject: [PATCH 03/30] Added unit tests for Mercator projection class. - changed its methods to static - add some documentation for its methods #story[928] --- .../java/seng302/models/map/Boundary.java | 32 +++++++------- .../models/map/MercatorProjection.java | 17 +++----- .../models/map/MercatorProjectionTest.java | 42 +++++++++++++++++++ 3 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 src/test/java/seng302/models/map/MercatorProjectionTest.java diff --git a/src/main/java/seng302/models/map/Boundary.java b/src/main/java/seng302/models/map/Boundary.java index f2d1302c..d39a60f0 100644 --- a/src/main/java/seng302/models/map/Boundary.java +++ b/src/main/java/seng302/models/map/Boundary.java @@ -9,36 +9,36 @@ package seng302.models.map; */ public class Boundary { - private double north, east, south, west; + private double northLat, eastLng, southLat, westLng; - public Boundary(double north, double east, double south, double west) { - this.north = north; - this.east = east; - this.south = south; - this.west = west; + public Boundary(double northLat, double eastLng, double southLat, double westLng) { + this.northLat = northLat; + this.eastLng = eastLng; + this.southLat = southLat; + this.westLng = westLng; } public double getCentreLat() { - return (north + south) / 2; + return (northLat + southLat) / 2; } public double getCentreLng() { - return (east + west) / 2; + return (eastLng + westLng) / 2; } - public double getNorth() { - return north; + public double getNorthLat() { + return northLat; } - public double getEast() { - return east; + public double getEastLng() { + return eastLng; } - public double getSouth() { - return south; + public double getSouthLat() { + return southLat; } - public double getWest() { - return west; + public double getWestLng() { + return westLng; } } diff --git a/src/main/java/seng302/models/map/MercatorProjection.java b/src/main/java/seng302/models/map/MercatorProjection.java index 4a442123..915712ba 100644 --- a/src/main/java/seng302/models/map/MercatorProjection.java +++ b/src/main/java/seng302/models/map/MercatorProjection.java @@ -2,21 +2,16 @@ package seng302.models.map; public class MercatorProjection { - private double MERCATOR_RANGE = 256; - private double pixelsPerLngDegree, pixelsPerLngRadian; - - - public MercatorProjection() { - pixelsPerLngDegree = MERCATOR_RANGE / 360.0; - pixelsPerLngRadian = MERCATOR_RANGE / (2 * Math.PI); - } + 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 double bound(double value) { + private static double bound(double value) { return Math.min(Math.max(value, -0.9999), 0.9999); } @@ -25,7 +20,7 @@ public class MercatorProjection { * @param geo MapGeo (lat, lng) location to be projected * @return the projection GeoPoint (x, y) on planar */ - public MapPoint toMapPoint(MapGeo geo) { + 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); @@ -42,7 +37,7 @@ public class MercatorProjection { * @param point MapPoint (x, y) to be converted back * @return the original Geo location converted from the given projection point */ - public MapGeo toMapGeo(MapPoint 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); diff --git a/src/test/java/seng302/models/map/MercatorProjectionTest.java b/src/test/java/seng302/models/map/MercatorProjectionTest.java new file mode 100644 index 00000000..a4350c18 --- /dev/null +++ b/src/test/java/seng302/models/map/MercatorProjectionTest.java @@ -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); + } + +} \ No newline at end of file From eda3d760774806de3b8bddb94e34337ee1674929 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Mon, 15 May 2017 17:06:28 +1200 Subject: [PATCH 04/30] Added get map size (width and height) method in canvasMap with given boundary #story[928] --- .../java/seng302/models/map/CanvasMap.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/seng302/models/map/CanvasMap.java b/src/main/java/seng302/models/map/CanvasMap.java index a86be90b..104bb219 100644 --- a/src/main/java/seng302/models/map/CanvasMap.java +++ b/src/main/java/seng302/models/map/CanvasMap.java @@ -13,7 +13,6 @@ public class CanvasMap { private double width, height; // desired image size private int zoom; - private int MERCATOR_RANGE = 256; private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE"; public CanvasMap(Boundary bound, double width, double height) { @@ -46,4 +45,23 @@ public class CanvasMap { // sb.append(String.format("&key=%s", KEY)); return sb.toString(); } + + 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()), + Math.abs(pointNE.getY() - pointSW.getY())); + } + + class MapSize { + long width, height; + + MapSize(double width, double height) { + this.width = (long) width; + this.height = (long) height; + } + } } From 8dec458ba9f33cff2e0a4947c9285a30480d9c36 Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Mon, 15 May 2017 19:57:23 +1200 Subject: [PATCH 05/30] Added methods to calculate optimal map size given a geo boundary. - From zoom level 20 to 1, once find a size that contains the whole boundary, then the size will be used to retrieve map image from google #story[928] --- .../java/seng302/models/map/Boundary.java | 18 +++--- .../java/seng302/models/map/CanvasMap.java | 64 +++++++++++++++---- src/main/java/seng302/models/map/MapGeo.java | 4 ++ .../java/seng302/models/map/MapPoint.java | 4 ++ .../models/map/MercatorProjection.java | 5 ++ .../seng302/models/map/TestMapController.java | 4 +- 6 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/main/java/seng302/models/map/Boundary.java b/src/main/java/seng302/models/map/Boundary.java index d39a60f0..4396d95d 100644 --- a/src/main/java/seng302/models/map/Boundary.java +++ b/src/main/java/seng302/models/map/Boundary.java @@ -1,9 +1,9 @@ package seng302.models.map; /** - * The Boundary class represents a square territorial bound 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. + * 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 */ @@ -18,27 +18,27 @@ public class Boundary { this.westLng = westLng; } - public double getCentreLat() { + double getCentreLat() { return (northLat + southLat) / 2; } - public double getCentreLng() { + double getCentreLng() { return (eastLng + westLng) / 2; } - public double getNorthLat() { + double getNorthLat() { return northLat; } - public double getEastLng() { + double getEastLng() { return eastLng; } - public double getSouthLat() { + double getSouthLat() { return southLat; } - public double getWestLng() { + double getWestLng() { return westLng; } } diff --git a/src/main/java/seng302/models/map/CanvasMap.java b/src/main/java/seng302/models/map/CanvasMap.java index 104bb219..3302112c 100644 --- a/src/main/java/seng302/models/map/CanvasMap.java +++ b/src/main/java/seng302/models/map/CanvasMap.java @@ -7,18 +7,25 @@ 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 bound; - private double width, height; // desired image size + private Boundary boundary; + private long width, height; // desired image size private int zoom; private String KEY = "AIzaSyC-5oOShMCY5Oy_9L7guYMPUPFHDMr37wE"; - public CanvasMap(Boundary bound, double width, double height) { - this.bound = bound; - this.width = width; - this.height = height; + public CanvasMap(Boundary boundary) { + this.boundary = boundary; + calculateOptimalMapSize(); } public Image getMapImage() { @@ -35,33 +42,64 @@ public class CanvasMap { } private String getRequest() { - zoom = 15; StringBuilder sb = new StringBuilder(); sb.append("https://maps.googleapis.com/maps/api/staticmap?"); - sb.append(String.format("center=%f,%f", bound.getCentreLat(), bound.getCentreLng())); + sb.append(String.format("center=%f,%f", boundary.getCentreLat(), boundary.getCentreLng())); sb.append(String.format("&zoom=%d", zoom)); - sb.append(String.format("&size=%.0fx%.0f&scale=2", width / 2, height / 2)); + 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()), - Math.abs(pointNE.getY() - pointSW.getY())); + 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 = (long) width; - this.height = (long) 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; + } } diff --git a/src/main/java/seng302/models/map/MapGeo.java b/src/main/java/seng302/models/map/MapGeo.java index 96af1dd5..43d02565 100644 --- a/src/main/java/seng302/models/map/MapGeo.java +++ b/src/main/java/seng302/models/map/MapGeo.java @@ -1,5 +1,9 @@ package seng302.models.map; +/** + * A class represent Geo location (latitude, longitude). + * Created by Haoming on 15/5/2017 + */ class MapGeo { private double lat, lng; diff --git a/src/main/java/seng302/models/map/MapPoint.java b/src/main/java/seng302/models/map/MapPoint.java index aa0e55d0..41be919a 100644 --- a/src/main/java/seng302/models/map/MapPoint.java +++ b/src/main/java/seng302/models/map/MapPoint.java @@ -1,5 +1,9 @@ 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; diff --git a/src/main/java/seng302/models/map/MercatorProjection.java b/src/main/java/seng302/models/map/MercatorProjection.java index 915712ba..b4bf647d 100644 --- a/src/main/java/seng302/models/map/MercatorProjection.java +++ b/src/main/java/seng302/models/map/MercatorProjection.java @@ -1,5 +1,10 @@ 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; diff --git a/src/main/java/seng302/models/map/TestMapController.java b/src/main/java/seng302/models/map/TestMapController.java index 6d656231..fc319bcc 100644 --- a/src/main/java/seng302/models/map/TestMapController.java +++ b/src/main/java/seng302/models/map/TestMapController.java @@ -17,7 +17,7 @@ public class TestMapController implements Initializable{ 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, 1280, 960); - gc.drawImage(canvasMap.getMapImage(), 0, 0, 1280, 960); + CanvasMap canvasMap = new CanvasMap(bound); + gc.drawImage(canvasMap.getMapImage(), 0, 0, canvasMap.getWidth(), canvasMap.getHeight()); } } From b17bba3629c8dfa3e9b79d29adbe9fe5b4fed4ec Mon Sep 17 00:00:00 2001 From: Zhi You Tan Date: Thu, 18 May 2017 12:18:51 +1200 Subject: [PATCH 06/30] Fixed leaderboard on start screen and race view to show all boats correctly during pre-race. #story[923] --- .../seng302/controllers/RaceController.java | 13 +----- .../controllers/RaceViewController.java | 26 +++++++---- .../controllers/StartScreenController.java | 43 +++++++++++-------- src/main/java/seng302/models/Yacht.java | 18 +++++--- 4 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/main/java/seng302/controllers/RaceController.java b/src/main/java/seng302/controllers/RaceController.java index d0ea0d42..4e40e001 100644 --- a/src/main/java/seng302/controllers/RaceController.java +++ b/src/main/java/seng302/controllers/RaceController.java @@ -2,7 +2,6 @@ package seng302.controllers; import seng302.models.Race; import seng302.models.Yacht; -import seng302.models.parsers.ConfigParser; import seng302.models.parsers.CourseParser; import seng302.models.parsers.StreamParser; @@ -18,6 +17,7 @@ import java.util.Random; * the CanvasController then uses the event data to make the animations */ public class RaceController { + Race race = null; public void initializeRace() { @@ -39,24 +39,13 @@ public class RaceController { public Race createRace(String configFile, String teamsConfigFile) throws Exception { Race race = new Race(); -// StreamParser.xmlObject - // 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(); Map teams = StreamParser.getBoatsPos(); //get race size int numberOfBoats = teams.size(); - //get time scale -// double timeScale = config.getTimeScale(); -// race.setTimeScale(timeScale); - for (Yacht boat : teams.values()) { boatNames.add(boat.getBoatName()); race.addBoat(boat); diff --git a/src/main/java/seng302/controllers/RaceViewController.java b/src/main/java/seng302/controllers/RaceViewController.java index aa09d9d1..4cfa1d92 100644 --- a/src/main/java/seng302/controllers/RaceViewController.java +++ b/src/main/java/seng302/controllers/RaceViewController.java @@ -251,21 +251,31 @@ public class RaceViewController extends Thread implements ImportantAnnotationDel positionVbox.getChildren().removeAll(); positionVbox.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - for (Yacht boat : StreamParser.getBoatsPos().values()) { - if (boat.getBoatStatus() == 3) { // 3 is finish status - Text textToAdd = new Text(boat.getPosition() + ". " + - boat.getShortName() + " (Finished)"); - textToAdd.setFill(Paint.valueOf("#d3d3d3")); - positionVbox.getChildren().add(textToAdd); + if (StreamParser.isRaceStarted()) { + for (Yacht boat : StreamParser.getBoatsPos().values()) { + if (boat.getBoatStatus() == 3) { // 3 is finish status + Text textToAdd = new Text(boat.getPosition() + ". " + + boat.getShortName() + " (Finished)"); + textToAdd.setFill(Paint.valueOf("#d3d3d3")); + positionVbox.getChildren().add(textToAdd); - } else { + } 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()) { Text textToAdd = new Text(boat.getPosition() + ". " + boat.getShortName() + " "); textToAdd.setFill(Paint.valueOf("#d3d3d3")); textToAdd.setStyle(""); positionVbox.getChildren().add(textToAdd); } - } } diff --git a/src/main/java/seng302/controllers/StartScreenController.java b/src/main/java/seng302/controllers/StartScreenController.java index 8a4d563b..39e44be1 100644 --- a/src/main/java/seng302/controllers/StartScreenController.java +++ b/src/main/java/seng302/controllers/StartScreenController.java @@ -25,6 +25,7 @@ import seng302.models.Yacht; import seng302.models.parsers.StreamParser; public class StartScreenController implements Initializable { + @FXML private GridPane gridPane; @FXML @@ -48,19 +49,18 @@ public class StartScreenController implements Initializable { private boolean switchedToRaceView = false; - private void setContentPane(String jfxUrl){ - try{ + private void setContentPane(String jfxUrl) { + try { // get the main controller anchor pane (MainView.fxml) AnchorPane contentPane = (AnchorPane) gridPane.getParent(); contentPane.getChildren().removeAll(); contentPane.getChildren().clear(); contentPane.getStylesheets().add(getClass().getResource("/css/master.css").toString()); - contentPane.getChildren().addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); - } - catch(javafx.fxml.LoadException e){ + contentPane.getChildren() + .addAll((Pane) FXMLLoader.load(getClass().getResource(jfxUrl))); + } catch (javafx.fxml.LoadException e) { System.err.println(e.getCause()); - } - catch(IOException e){ + } catch (IOException e) { System.err.println(e); } } @@ -72,7 +72,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() { if (StreamParser.isStreamStatus()) { @@ -102,8 +103,10 @@ public class StartScreenController implements Initializable { updateTeamList(); timeTillLive.setTextFill(Color.RED); switchToRaceViewButton.setDisable(false); - String timerMinute = Long.toString(StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(StreamParser.getTimeSinceStart() % 60); + String timerMinute = Long + .toString(StreamParser.getTimeSinceStart() / 60); + String timerSecond = Long + .toString(StreamParser.getTimeSinceStart() % 60); if (timerSecond.length() == 1) { timerSecond = "0" + timerSecond; } @@ -114,8 +117,10 @@ public class StartScreenController implements Initializable { updateTeamList(); timeTillLive.setTextFill(Color.BLACK); switchToRaceViewButton.setDisable(false); - String timerMinute = Long.toString(-1 * StreamParser.getTimeSinceStart() / 60); - String timerSecond = Long.toString(-1 * StreamParser.getTimeSinceStart() % 60); + String timerMinute = Long + .toString(-1 * StreamParser.getTimeSinceStart() / 60); + String timerSecond = Long + .toString(-1 * StreamParser.getTimeSinceStart() % 60); if (timerSecond.length() == 1) { timerSecond = "0" + timerSecond; } @@ -142,19 +147,23 @@ public class StartScreenController implements Initializable { teamList.setItems(data); boatNameCol.setCellValueFactory( - new PropertyValueFactory<>("boatName") + new PropertyValueFactory<>("boatName") ); shortNameCol.setCellValueFactory( - new PropertyValueFactory<>("shortName") + new PropertyValueFactory<>("shortName") ); countryCol.setCellValueFactory( - new PropertyValueFactory<>("country") + new PropertyValueFactory<>("country") ); posCol.setCellValueFactory( - new PropertyValueFactory<>("position") + new PropertyValueFactory<>("position") ); - data.addAll(StreamParser.getBoatsPos().values()); + if (StreamParser.isRaceStarted()) { + data.addAll(StreamParser.getBoatsPos().values()); + } else { + data.addAll(StreamParser.getBoats().values()); + } teamList.refresh(); } } diff --git a/src/main/java/seng302/models/Yacht.java b/src/main/java/seng302/models/Yacht.java index e21384f9..fbfe952f 100644 --- a/src/main/java/seng302/models/Yacht.java +++ b/src/main/java/seng302/models/Yacht.java @@ -9,9 +9,10 @@ import java.text.SimpleDateFormat; * Yacht class for the racing boat. * * 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 { + // Used in boat group private Color colour; private double velocity; @@ -38,25 +39,27 @@ public class Yacht { * * @param boatName Create a yacht object with name. */ - public Yacht (String boatName) { + public Yacht(String boatName) { this.boatName = boatName; } /** * 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 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) { this.boatName = boatName; this.velocity = boatVelocity; this.shortName = shortName; this.sourceID = id; + this.position = "-"; } - 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.sourceID = sourceID; this.hullID = hullID; @@ -68,18 +71,23 @@ public class Yacht { public String getBoatType() { return boatType; } + public Integer getSourceID() { return sourceID; } + public String getHullID() { return hullID; } + public String getShortName() { return shortName; } + public String getBoatName() { return boatName; } + public String getCountry() { return country; } From ecc0e722b5f2ea50a2af20bd73d07735f8263ef0 Mon Sep 17 00:00:00 2001 From: Kusal Ekanayake Date: Thu, 18 May 2017 13:19:59 +1200 Subject: [PATCH 07/30] Started sparkline prototype #story[952] --- src/main/resources/views/RaceView.fxml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/resources/views/RaceView.fxml b/src/main/resources/views/RaceView.fxml index cfb36f23..49c9d188 100644 --- a/src/main/resources/views/RaceView.fxml +++ b/src/main/resources/views/RaceView.fxml @@ -1,5 +1,6 @@ + @@ -61,6 +62,14 @@