From 8fa7829a3c13cb6581d0974dcdf1f4db09d3bfbe Mon Sep 17 00:00:00 2001 From: Haoming Yin Date: Wed, 10 May 2017 20:52:46 +1200 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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()); } }