diff --git a/pom.xml b/pom.xml
index 5d994c9..768827b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,6 +83,9 @@
maven-surefire-plugin
2.22.2
+
+ -Xmx1024m
+
diff --git a/src/main/java/com/masterisehomes/geometryapi/geodesy/Harversine.java b/src/main/java/com/masterisehomes/geometryapi/geodesy/Harversine.java
index 264d68e..b06e9bf 100644
--- a/src/main/java/com/masterisehomes/geometryapi/geodesy/Harversine.java
+++ b/src/main/java/com/masterisehomes/geometryapi/geodesy/Harversine.java
@@ -2,6 +2,8 @@
import java.lang.Math;
+import com.masterisehomes.geometryapi.hexagon.Coordinates;
+
public class Harversine {
/*
* Earth's radius at the equator (WGS-84)
@@ -15,20 +17,41 @@ public class Harversine {
*/
public static double distance(double lat1, double lng1, double lat2, double lng2) {
// Convert latitudes to Radians
- double phi_1 = Math.toRadians(lat1);
- double phi_2 = Math.toRadians(lat2);
+ final double phi_1 = Math.toRadians(lat1);
+ final double phi_2 = Math.toRadians(lat2);
// Distance between latitudes and longitudes
- double delta_phi = Math.toRadians(lat2 - lat1);
- double delta_lambda = Math.toRadians(lng2 - lng1);
+ final double delta_phi = Math.toRadians(lat2 - lat1);
+ final double delta_lambda = Math.toRadians(lng2 - lng1);
// Apply Harversine formula
- double a = Math.pow(Math.sin(delta_phi / 2), 2)
+ final double a = Math.pow(Math.sin(delta_phi / 2), 2)
+ Math.pow(Math.sin(delta_lambda / 2), 2) * Math.cos(phi_1) * Math.cos(phi_2);
+ final double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ final double distance = R * c; // meters
+
+ return distance;
+ }
+
+ public static double distance(Coordinates coordinates1, Coordinates coordinates2) {
+ final double lat1 = coordinates1.getLatitude();
+ final double lng1 = coordinates1.getLongitude();
+ final double lat2 = coordinates2.getLatitude();
+ final double lng2 = coordinates2.getLongitude();
- double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ // Convert latitudes to Radians
+ final double phi_1 = Math.toRadians(lat1);
+ final double phi_2 = Math.toRadians(lat2);
+
+ // Distance between latitudes and longitudes
+ final double delta_phi = Math.toRadians(lat2 - lat1);
+ final double delta_lambda = Math.toRadians(lng2 - lng1);
- double distance = R * c; // meters
+ // Apply Harversine formula
+ final double a = Math.pow(Math.sin(delta_phi / 2), 2)
+ + Math.pow(Math.sin(delta_lambda / 2), 2) * Math.cos(phi_1) * Math.cos(phi_2);
+ final double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ final double distance = R * c; // meters
return distance;
}
diff --git a/src/main/java/com/masterisehomes/geometryapi/tessellation/AxialClockwiseTessellation.java b/src/main/java/com/masterisehomes/geometryapi/tessellation/AxialClockwiseTessellation.java
index 0980cdf..096afc2 100644
--- a/src/main/java/com/masterisehomes/geometryapi/tessellation/AxialClockwiseTessellation.java
+++ b/src/main/java/com/masterisehomes/geometryapi/tessellation/AxialClockwiseTessellation.java
@@ -1,6 +1,8 @@
package com.masterisehomes.geometryapi.tessellation;
import java.lang.Math;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
import java.util.List;
import java.util.ArrayList;
@@ -87,6 +89,8 @@ public class AxialClockwiseTessellation {
/* Basic stats here */
@Getter
private int totalHexagons = 0;
+ @Getter
+ private double tessellationDistance = 0;
/*
* Corner Hexagons - used to find Edge Hexagons (based on nthRing)
@@ -491,7 +495,7 @@ private final List generateGisEdgeHexagons(Hexagon gisCornerHexagon, Ne
/* Calculate RequiredRings */
private final int calculateRequiredRings(Boundary boundary) {
/*
- * ARBITRARY RING ADJUSTMENT CONSTANT
+ * ERROR MARGINS - TODO: needs to update descriptions
*
* Geometrically, at the outer most ring, 1/6 the area of each hexagon can be
* missed.
@@ -504,17 +508,29 @@ private final int calculateRequiredRings(Boundary boundary) {
final int CENTROID_PLACEMENT_ERROR_MARGIN = 1;
final int RING_ERROR_MARGIN = GRID_GEOMETRIC_ERROR_MARGIN + CENTROID_PLACEMENT_ERROR_MARGIN;
- /* Coordinates */
- final double minLat = boundary.getMinLat();
- final double minLng = boundary.getMinLng();
- final double maxLat = boundary.getMaxLat();
- final double maxLng = boundary.getMaxLng();
-
/*
- * Calculate the Great-circle Distance between the START and END boundary
- * coordinates
+ * Calculate the requiredTessellationDistance from boundary by Harversine formula
+ *
+ * is the minimum distance required to generate Grid Map that can cover boundary
+ * fully - this distance can grow up to maxBoundaryDistance, depending where the
+ * rootHexagon is placed within Boundary.
*/
- final double maxBoundaryDistance = Harversine.distance(minLat, minLng, maxLat, maxLng);
+ final Coordinates minCoordinates = boundary.getMinCoordinates();
+ final Coordinates maxCoordinates = boundary.getMaxCoordinates();
+ final Coordinates centroidCoordinates = rootHexagon.getCentroid();
+
+ // Calculate distance between Boundary's minCoordinates/maxCoordinates to rootCentroid
+ final double minCoordinatesDistance = Harversine.distance(minCoordinates, centroidCoordinates);
+ final double maxCoordinatesDistance = Harversine.distance(maxCoordinates, centroidCoordinates);
+
+ // Determine requiredTessellationDistance: the larger distance == the requiredTessellationDistance
+ final double requiredTessellationDistance;
+ if (minCoordinatesDistance >= maxCoordinatesDistance) {
+ requiredTessellationDistance = minCoordinatesDistance;
+ } else {
+ requiredTessellationDistance = maxCoordinatesDistance;
+ }
+ this.tessellationDistance = requiredTessellationDistance;
/*
* Neighbor's distance - distance between each hexagon's neighbor centroid:
@@ -531,10 +547,10 @@ private final int calculateRequiredRings(Boundary boundary) {
/*
* In Hexagons grids, we can look at it with 3 primary axes (the 6 neighbor
* directions):
- * - minAxialHexagons is the minimum amount of hexagons that required to stack
- * up (from edges) in those 3 axes to cover the grid map largest diameter.
+ * - requiredAxialHexagons is the minimum amount of hexagons that required to stack
+ * up (from edges) in those 3 axes to cover the boundary largest diameter.
*/
- final int requiredAxialHexagons = (int) Math.ceil(maxBoundaryDistance / neighborDistance); // round up
+ final int requiredAxialHexagons = (int) Math.ceil(requiredTessellationDistance / neighborDistance); // round up
/*
* Calculate the Minimum Required Rings
@@ -598,33 +614,57 @@ private final void clearHexagons() {
public static void main(String[] args) {
Gson gson = new GsonBuilder().create();
- Coordinates origin = new Coordinates(106.7064, 10.7744);
+ Coordinates origin = new Coordinates(105.8121224, 21.0358791);
+ // Coordinates origin = new Coordinates(109.466667, 23.383333);
- Hexagon hexagon = new Hexagon(origin, 200);
+ Hexagon hexagon = new Hexagon(origin, 500);
Neighbors neighbors = new Neighbors(hexagon);
AxialClockwiseTessellation tessellation = new AxialClockwiseTessellation(hexagon);
Boundary boundary = new Boundary(
- new Coordinates(106.6959, 10.7826),
- new Coordinates(106.7064, 10.7743));
+ new Coordinates(105, 21),
+ new Coordinates(109.466667, 23.383333));
// Test harversine
double greatCircleDistance = Harversine.distance(boundary.getMinLat(), boundary.getMinLng(),
boundary.getMaxLat(), boundary.getMaxLng());
tessellation.tessellate(boundary);
- tessellation.tessellate(boundary);
- System.out.println("Great-circle distance: " + greatCircleDistance);
- System.out.println("Total rings: " + tessellation.totalRings);
- System.out.println("Minimum required rings: " + tessellation.requiredRings);
- System.out.println("Current nthRing: " + tessellation.nthRing);
- System.out.println("Total hexagons: " + tessellation.totalHexagons);
- System.out.println("Boundary: " + tessellation.boundary + "\n");
+ // Set rounding format
+ DecimalFormat df = new DecimalFormat("#.##");
+ df.setRoundingMode(RoundingMode.CEILING);
+
+ final double oldCoverageDistance = 531312.330953638;
+ final int oldRequiredRings = 616;
+ final int oldTotalHexagons = 1136521;
+
+ final String newCoverageDistanceDiffPct = df.format(
+ (tessellation.tessellationDistance / oldCoverageDistance - 1) * 100);
+ final String newRingsDiffPct = df.format(
+ (tessellation.requiredRings / (double) oldRequiredRings - 1) * 100);
+ final String newTotalHexagonsDiffPct = df.format(
+ (tessellation.totalHexagons / (double) oldTotalHexagons - 1) * 100);
+
+ System.out.println("\n------------ Tessellation optimization stats ------------");
+ System.out.println("Boundary: " + tessellation.boundary.gisBoundary());
+ System.out.println("Hexagon's circumradius: " + hexagon.getCircumradius());
+
+ System.out.println("\nOld coverage Distance: " + oldCoverageDistance);
+ System.out.println("New Coverage Distance: " + tessellation.tessellationDistance);
+ System.out.println("*Difference percentage: " + newCoverageDistanceDiffPct + "%");
+
+ System.out.println("\nOld minimum required Rings: " + oldRequiredRings);
+ System.out.println("New minimum required Rings: " + tessellation.requiredRings);
+ System.out.println("*Difference percentage: " + newRingsDiffPct + "%");
+
+ System.out.println("\nOld total Hexagons: " + oldTotalHexagons);
+ System.out.println("New total hexagons: " + tessellation.totalHexagons);
+ System.out.println("*Difference percentage: " + newTotalHexagonsDiffPct + "%");
- GeoJsonManager tessellationManager = new GeoJsonManager(tessellation);
- System.out.println(
- gson.toJson(tessellationManager.getFeatureCollection()));
+ // GeoJsonManager tessellationManager = new GeoJsonManager(tessellation);
+ // System.out.println(
+ // gson.toJson(tessellationManager.getFeatureCollection()));
}
}
\ No newline at end of file
diff --git a/src/main/java/com/masterisehomes/geometryapi/tessellation/AxialClockwiseTessellationDto.java b/src/main/java/com/masterisehomes/geometryapi/tessellation/AxialClockwiseTessellationDto.java
index 6201ffa..a246499 100644
--- a/src/main/java/com/masterisehomes/geometryapi/tessellation/AxialClockwiseTessellationDto.java
+++ b/src/main/java/com/masterisehomes/geometryapi/tessellation/AxialClockwiseTessellationDto.java
@@ -6,6 +6,11 @@
import lombok.Getter;
import com.google.gson.JsonObject;
+import com.google.gson.Gson;
+import com.masterisehomes.geometryapi.neighbors.Neighbors;
+import com.masterisehomes.geometryapi.geodesy.Harversine;
+import com.masterisehomes.geometryapi.geojson.GeoJsonManager;
+
import com.masterisehomes.geometryapi.hexagon.Coordinates;
import com.masterisehomes.geometryapi.hexagon.Hexagon;
@@ -93,4 +98,35 @@ public AxialClockwiseTessellationDto(JsonObject payload) {
this.totalRings = this.tessellation.getTotalRings();
this.totalHexagons = this.tessellation.getTotalHexagons();
}
+
+
+ public static void main(String[] args) {
+ Gson gson = new Gson();
+
+ Coordinates origin = new Coordinates(107, 23);
+
+ Hexagon hexagon = new Hexagon(origin, 250);
+ Neighbors neighbors = new Neighbors(hexagon);
+
+ AxialClockwiseTessellation tessellation = new AxialClockwiseTessellation(hexagon);
+
+ Boundary boundary = new Boundary(
+ new Coordinates(106, 20),
+ new Coordinates(109.466667, 23.383333));
+
+ // Test harversine
+ double greatCircleDistance = Harversine.distance(boundary.getMinLat(), boundary.getMinLng(),
+ boundary.getMaxLat(), boundary.getMaxLng());
+
+ tessellation.tessellate(boundary);
+
+ AxialClockwiseTessellationDto dto = new AxialClockwiseTessellationDto(tessellation);
+
+ System.out.println("Great-circle distance: " + greatCircleDistance);
+ System.out.println("Total rings: " + dto.getTessellation().getTotalRings());
+ System.out.println("Total hexagons: " + dto.getTessellation().getTotalHexagons());
+ System.out.println("Boundary: " + dto.getTessellation().getBoundary() + "\n");
+
+ GeoJsonManager tessellationManager = new GeoJsonManager(tessellation);
+ }
}
diff --git a/src/main/java/com/masterisehomes/geometryapi/tessellation/Boundary.java b/src/main/java/com/masterisehomes/geometryapi/tessellation/Boundary.java
index 1ea425f..988a9e5 100644
--- a/src/main/java/com/masterisehomes/geometryapi/tessellation/Boundary.java
+++ b/src/main/java/com/masterisehomes/geometryapi/tessellation/Boundary.java
@@ -3,6 +3,7 @@
import lombok.Getter;
import lombok.ToString;
+import com.masterisehomes.geometryapi.geodesy.Harversine;
import com.masterisehomes.geometryapi.hexagon.Coordinates;
/* Similar to setup() in Processing
@@ -15,33 +16,53 @@
*/
@ToString
-@Getter
public class Boundary {
+ @Getter
+ private final Coordinates minCoordinates, maxCoordinates;
+
// Processing attributes
private int width, height;
- private Coordinates start, end;
// WGS84 Coordinates attributes
- private double minLat, minLng;
- private double maxLat, maxLng;
+ @Getter
+ private final double minLat, minLng;
+ @Getter
+ private final double maxLat, maxLng;
// Builder pattern to take in dimension ,
public Boundary(float x, float y, int width, int height) {
- this.start = new Coordinates(x, y);
+ this.minCoordinates = new Coordinates(x, y);
this.width = width;
this.height = height;
- this.end = new Coordinates(x + width, y + height);
+ this.maxCoordinates = new Coordinates(x + width, y + height);
+
+ this.minLat = minCoordinates.getLatitude();
+ this.minLng = minCoordinates.getLongitude();
+ this.maxLat = maxCoordinates.getLatitude();
+ this.maxLng = maxCoordinates.getLongitude();
}
// WGS84 Coordinates Boundary
public Boundary(Coordinates minCoordinates, Coordinates maxCoordinates) {
+ this.minCoordinates = minCoordinates;
+ this.maxCoordinates = maxCoordinates;
+
this.minLat = minCoordinates.getLatitude();
this.minLng = minCoordinates.getLongitude();
this.maxLat = maxCoordinates.getLatitude();
this.maxLng = maxCoordinates.getLongitude();
}
- // Comparison methods
+ /* Calculate Great-circle distance */
+ public double greatCircleDistance() {
+ double greatCircleDistance = Harversine.distance(
+ minLat, minLng,
+ maxLat, maxLng);
+
+ return greatCircleDistance;
+ }
+
+ /* Comparison methods */
public boolean contains(Coordinates centroid) {
double centroidLat = centroid.getLatitude();
double centroidLng = centroid.getLongitude();
@@ -69,4 +90,9 @@ private boolean containsLng(double lng) {
return false;
}
}
+
+ /* Getters */
+ public String gisBoundary() {
+ return String.format("GisBoundary(minLat=%s, minLng=%s, maxLat=%s, maxLng=%s)", minLat, minLng, maxLat, maxLng);
+ }
}
\ No newline at end of file