diff --git a/demo/AndroidManifest.xml b/demo/AndroidManifest.xml
index f022f4ada..ecb7cac7e 100644
--- a/demo/AndroidManifest.xml
+++ b/demo/AndroidManifest.xml
@@ -49,6 +49,7 @@
+
diff --git a/demo/src/com/google/maps/android/utils/demo/MainActivity.java b/demo/src/com/google/maps/android/utils/demo/MainActivity.java
index 09fdc9d10..7f8a7f7fe 100644
--- a/demo/src/com/google/maps/android/utils/demo/MainActivity.java
+++ b/demo/src/com/google/maps/android/utils/demo/MainActivity.java
@@ -39,6 +39,7 @@ protected void onCreate(Bundle savedInstanceState) {
addDemo("Clustering: Custom Look", CustomMarkerClusteringDemoActivity.class);
addDemo("Clustering: 2K markers", BigClusteringDemoActivity.class);
addDemo("PolyUtil.decode", PolyDecodeDemoActivity.class);
+ addDemo("PolyUtil.simplify", PolySimplifyDemoActivity.class);
addDemo("IconGenerator", IconGeneratorDemoActivity.class);
addDemo("SphericalUtil.computeDistanceBetween", DistanceDemoActivity.class);
addDemo("Generating tiles", TileProviderAndProjectionDemo.class);
diff --git a/demo/src/com/google/maps/android/utils/demo/PolySimplifyDemoActivity.java b/demo/src/com/google/maps/android/utils/demo/PolySimplifyDemoActivity.java
new file mode 100644
index 000000000..58ca5aca3
--- /dev/null
+++ b/demo/src/com/google/maps/android/utils/demo/PolySimplifyDemoActivity.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2015 Sean J. Barbeau
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.maps.android.utils.demo;
+
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.PolygonOptions;
+import com.google.android.gms.maps.model.PolylineOptions;
+import com.google.maps.android.PolyUtil;
+
+import android.graphics.Color;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PolySimplifyDemoActivity extends BaseDemoActivity {
+
+ private final static String LINE = "elfjD~a}uNOnFN~Em@fJv@tEMhGDjDe@hG^nF??@lA?n@IvAC`Ay@A{@DwCA{CF_EC{CEi@PBTFDJBJ?V?n@?D@?A@?@?F?F?LAf@?n@@`@@T@~@FpA?fA?p@?r@?vAH`@OR@^ETFJCLD?JA^?J?P?fAC`B@d@?b@A\\@`@Ad@@\\?`@?f@?V?H?DD@DDBBDBD?D?B?B@B@@@B@B@B@D?D?JAF@H@FCLADBDBDCFAN?b@Af@@x@@";
+ private final static String OVAL_POLYGON = "}wgjDxw_vNuAd@}AN{A]w@_Au@kAUaA?{@Ke@@_@C]D[FULWFOLSNMTOVOXO\\I\\CX?VJXJTDTNXTVVLVJ`@FXA\\AVLZBTATBZ@ZAT?\\?VFT@XGZAP";
+ private final static int ALPHA_ADJUSTMENT = 0x77000000;
+
+ @Override
+ protected void startDemo() {
+ GoogleMap mMap = getMap();
+
+ // Original line
+ List line = PolyUtil.decode(LINE);
+ mMap.addPolyline(new PolylineOptions()
+ .addAll(line)
+ .color(Color.BLACK));
+
+ getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(28.05870, -82.4090), 15));
+
+ List simplifiedLine;
+
+ /**
+ * Simplified lines - increasing the tolerance will result in fewer points in the simplified
+ * line
+ */
+ double tolerance = 5; // meters
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ mMap.addPolyline(new PolylineOptions()
+ .addAll(simplifiedLine)
+ .color(Color.RED - ALPHA_ADJUSTMENT));
+
+ tolerance = 20; // meters
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ mMap.addPolyline(new PolylineOptions()
+ .addAll(simplifiedLine)
+ .color(Color.GREEN - ALPHA_ADJUSTMENT));
+
+ tolerance = 50; // meters
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ mMap.addPolyline(new PolylineOptions()
+ .addAll(simplifiedLine)
+ .color(Color.MAGENTA - ALPHA_ADJUSTMENT));
+
+ tolerance = 500; // meters
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ mMap.addPolyline(new PolylineOptions()
+ .addAll(simplifiedLine)
+ .color(Color.YELLOW - ALPHA_ADJUSTMENT));
+
+ tolerance = 1000; // meters
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ mMap.addPolyline(new PolylineOptions()
+ .addAll(simplifiedLine)
+ .color(Color.BLUE - ALPHA_ADJUSTMENT));
+
+
+ // Triangle polygon - the polygon should be closed
+ ArrayList triangle = new ArrayList<>();
+ triangle.add(new LatLng(28.06025,-82.41030)); // Should match last point
+ triangle.add(new LatLng(28.06129,-82.40945));
+ triangle.add(new LatLng(28.06206,-82.40917));
+ triangle.add(new LatLng(28.06125,-82.40850));
+ triangle.add(new LatLng(28.06035,-82.40834));
+ triangle.add(new LatLng(28.06038, -82.40924));
+ triangle.add(new LatLng(28.06025,-82.41030)); // Should match first point
+
+ mMap.addPolygon(new PolygonOptions()
+ .addAll(triangle)
+ .fillColor(Color.BLUE - ALPHA_ADJUSTMENT)
+ .strokeColor(Color.BLUE)
+ .strokeWidth(5));
+
+ // Simplified triangle polygon
+ tolerance = 88; // meters
+ List simplifiedTriangle = PolyUtil.simplify(triangle, tolerance);
+ mMap.addPolygon(new PolygonOptions()
+ .addAll(simplifiedTriangle)
+ .fillColor(Color.YELLOW - ALPHA_ADJUSTMENT)
+ .strokeColor(Color.YELLOW)
+ .strokeWidth(5));
+
+ // Oval polygon - the polygon should be closed
+ List oval = PolyUtil.decode(OVAL_POLYGON);
+ mMap.addPolygon(new PolygonOptions()
+ .addAll(oval)
+ .fillColor(Color.BLUE - ALPHA_ADJUSTMENT)
+ .strokeColor(Color.BLUE)
+ .strokeWidth(5));
+
+ // Simplified oval polygon
+ tolerance = 10; // meters
+ List simplifiedOval= PolyUtil.simplify(oval, tolerance);
+ mMap.addPolygon(new PolygonOptions()
+ .addAll(simplifiedOval)
+ .fillColor(Color.YELLOW - ALPHA_ADJUSTMENT)
+ .strokeColor(Color.YELLOW)
+ .strokeWidth(5));
+ }
+}
diff --git a/library/src/com/google/maps/android/PolyUtil.java b/library/src/com/google/maps/android/PolyUtil.java
index 6ab7957b6..0d521a374 100644
--- a/library/src/com/google/maps/android/PolyUtil.java
+++ b/library/src/com/google/maps/android/PolyUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 Google Inc.
+ * Copyright 2008, 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,9 @@
import java.util.List;
import java.util.ArrayList;
+import java.util.Stack;
+import static com.google.maps.android.SphericalUtil.*;
import static java.lang.Math.*;
import static com.google.maps.android.MathUtil.*;
@@ -38,11 +40,11 @@ private static double tanLatGC(double lat1, double lat2, double lng2, double lng
/**
* Returns mercator(latitude-at-lng3) on the Rhumb line (lat1, lng1) to (lat2, lng2). lng1==0.
- */
+ */
private static double mercatorLatRhumb(double lat1, double lat2, double lng2, double lng3) {
return (mercator(lat1) * (lng2 - lng3) + mercator(lat2) * lng3) / lng2;
}
-
+
/**
* Computes whether the vertical segment (lat3, lng3) to South Pole intersects the segment
* (lat1, lng1) to (lat2, lng2).
@@ -84,7 +86,7 @@ private static boolean intersects(double lat1, double lat2, double lng2,
tan(lat3) >= tanLatGC(lat1, lat2, lng2, lng3) :
mercator(lat3) >= mercatorLatRhumb(lat1, lat2, lng2, lng3);
}
-
+
/**
* Computes whether the given point lies inside the specified polygon.
* The polygon is always cosidered closed, regardless of whether the last point equals
@@ -201,7 +203,7 @@ private static boolean isLocationOnEdgeOrPath(LatLng point, List poly, b
for (LatLng point2 : poly) {
double lat2 = toRadians(point2.latitude);
double y2 = mercator(lat2);
- double lng2 = toRadians(point2.longitude);
+ double lng2 = toRadians(point2.longitude);
if (max(lat1, lat2) >= minAcceptable && min(lat1, lat2) <= maxAcceptable) {
// We offset longitudes by -lng1; the implicit x1 is 0.
double x2 = wrap(lng2 - lng1, -PI, PI);
@@ -283,6 +285,150 @@ private static boolean isOnSegmentGC(double lat1, double lng1, double lat2, doub
return sinSumAlongTrack > 0; // Compare with half-circle == PI using sign of sin().
}
+ /**
+ * Simplifies the given poly (polyline or polygon) using the Douglas-Peucker decimation
+ * algorithm. Increasing the tolerance will result in fewer points in the simplified polyline
+ * or polygon.
+ *
+ * When the providing a polygon as input, the first and last point of the list MUST have the
+ * same latitude and longitude (i.e., the polygon must be closed). If the input polygon is not
+ * closed, the resulting polygon may not be fully simplified.
+ *
+ * The time complexity of Douglas-Peucker is O(n^2), so take care that you do not call this
+ * algorithm too frequently in your code.
+ *
+ * @param poly polyline or polygon to be simplified. Polygon should be closed (i.e.,
+ * first and last points should have the same latitude and longitude).
+ * @param tolerance in meters. Increasing the tolerance will result in fewer points in the
+ * simplified poly.
+ * @return a simplified poly produced by the Douglas-Peucker algorithm
+ */
+ public static List simplify(List poly, double tolerance) {
+ final int n = poly.size();
+ if (n < 1) {
+ throw new IllegalArgumentException("Polyline must have at least 1 point");
+ }
+ if (tolerance <= 0) {
+ throw new IllegalArgumentException("Tolerance must be greater than zero");
+ }
+
+ boolean closedPolygon = isClosedPolygon(poly);
+
+ // Check if the provided poly is a closed polygon
+ if (closedPolygon) {
+ // Add a small offset to the last point for Douglas-Peucker on polygons (see #201)
+ final double OFFSET = 0.00000000001;
+ LatLng lastPoint = poly.get(poly.size() - 1);
+ // LatLng.latitude and .longitude are immutable, so replace the last point
+ poly.remove(poly.size() - 1);
+ poly.add(new LatLng(lastPoint.latitude + OFFSET, lastPoint.longitude + OFFSET));
+ }
+
+ int idx;
+ int maxIdx = 0;
+ Stack stack = new Stack<>();
+ double[] dists = new double[n];
+ dists[0] = 1;
+ dists[n - 1] = 1;
+ double maxDist;
+ double dist = 0.0;
+ int[] current;
+
+ if (n > 2) {
+ int[] stackVal = new int[]{0, (n - 1)};
+ stack.push(stackVal);
+ while (stack.size() > 0) {
+ current = stack.pop();
+ maxDist = 0;
+ for (idx = current[0] + 1; idx < current[1]; ++idx) {
+ dist = distanceToLine(poly.get(idx), poly.get(current[0]),
+ poly.get(current[1]));
+ if (dist > maxDist) {
+ maxDist = dist;
+ maxIdx = idx;
+ }
+ }
+ if (maxDist > tolerance) {
+ dists[maxIdx] = maxDist;
+ int[] stackValCurMax = {current[0], maxIdx};
+ stack.push(stackValCurMax);
+ int[] stackValMaxCur = {maxIdx, current[1]};
+ stack.push(stackValMaxCur);
+ }
+ }
+ }
+
+ if (closedPolygon) {
+ // Replace last point of input (w/ offset) with a copy of first to re-close the polygon
+ poly.remove(poly.size() - 1);
+ poly.add(new LatLng(poly.get(0).latitude, poly.get(0).longitude));
+ }
+
+ // Generate the simplified line
+ idx = 0;
+ ArrayList simplifiedLine = new ArrayList<>();
+ for (LatLng l : poly) {
+ if (dists[idx] != 0) {
+ simplifiedLine.add(l);
+ }
+ idx++;
+ }
+
+ return simplifiedLine;
+ }
+
+ /**
+ * Returns true if the provided list of points is a closed polygon (i.e., the first and last
+ * points are the same), and false if it is not
+ * @param poly polyline or polygon
+ * @return true if the provided list of points is a closed polygon (i.e., the first and last
+ * points are the same), and false if it is not
+ */
+ public static boolean isClosedPolygon(List poly) {
+ LatLng firstPoint = poly.get(0);
+ LatLng lastPoint = poly.get(poly.size()-1);
+ if (firstPoint.equals(lastPoint)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Computes the distance on the sphere between the point p and the line segment start to end.
+ *
+ * @param p the point to be measured
+ * @param start the beginning of the line segment
+ * @param end the end of the line segment
+ * @return the distance in meters (assuming spherical earth)
+ */
+ public static double distanceToLine(final LatLng p, final LatLng start, final LatLng end) {
+ if (start.equals(end)) {
+ computeDistanceBetween(end, p);
+ }
+
+ final double s0lat = toRadians(p.latitude);
+ final double s0lng = toRadians(p.longitude);
+ final double s1lat = toRadians(start.latitude);
+ final double s1lng = toRadians(start.longitude);
+ final double s2lat = toRadians(end.latitude);
+ final double s2lng = toRadians(end.longitude);
+
+ double s2s1lat = s2lat - s1lat;
+ double s2s1lng = s2lng - s1lng;
+ final double u = ((s0lat - s1lat) * s2s1lat + (s0lng - s1lng) * s2s1lng)
+ / (s2s1lat * s2s1lat + s2s1lng * s2s1lng);
+ if (u <= 0) {
+ return computeDistanceBetween(p, start);
+ }
+ if (u >= 1) {
+ return computeDistanceBetween(p, end);
+ }
+ LatLng sa = new LatLng(p.latitude - start.latitude, p.longitude - start.longitude);
+ LatLng sb = new LatLng(u * (end.latitude - start.latitude), u * (end.longitude - start.longitude));
+ return computeDistanceBetween(sa, sb);
+ }
+
/**
* Decodes an encoded path string into a sequence of LatLngs.
*/
diff --git a/library/tests/src/com/google/maps/android/PolyUtilTest.java b/library/tests/src/com/google/maps/android/PolyUtilTest.java
index 828f8e3ab..3b825658e 100644
--- a/library/tests/src/com/google/maps/android/PolyUtilTest.java
+++ b/library/tests/src/com/google/maps/android/PolyUtilTest.java
@@ -17,14 +17,13 @@
package com.google.maps.android;
import com.google.android.gms.maps.model.LatLng;
-import com.google.maps.android.PolyUtil;
import junit.framework.Assert;
import junit.framework.TestCase;
import java.lang.String;
+import java.lang.reflect.Array;
import java.util.List;
-import java.util.Arrays;
import java.util.ArrayList;
public class PolyUtilTest extends TestCase {
@@ -162,6 +161,239 @@ public void testContainsLocation() {
makeList(2.5, 10, 1, 0),
makeList(15, 10, 0, -15, 0, 25, -1, 0));
}
+
+ public void testSimplify() {
+ /**
+ * Polyline
+ */
+ final String LINE = "elfjD~a}uNOnFN~Em@fJv@tEMhGDjDe@hG^nF??@lA?n@IvAC`Ay@A{@DwCA{CF_EC{CEi@PBTFDJBJ?V?n@?D@?A@?@?F?F?LAf@?n@@`@@T@~@FpA?fA?p@?r@?vAH`@OR@^ETFJCLD?JA^?J?P?fAC`B@d@?b@A\\@`@Ad@@\\?`@?f@?V?H?DD@DDBBDBD?D?B?B@B@@@B@B@B@D?D?JAF@H@FCLADBDBDCFAN?b@Af@@x@@";
+ List line = PolyUtil.decode(LINE);
+ assertEquals(95, line.size());
+
+ List simplifiedLine;
+ List copy;
+
+ double tolerance = 5; // meters
+ copy = copyList(line);
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ assertEquals(21, simplifiedLine.size());
+ assertEndPoints(line, simplifiedLine);
+ assertSimplifiedPointsFromLine(line, simplifiedLine);
+ assertLineLength(line, simplifiedLine);
+ assertInputUnchanged(line, copy);
+
+ tolerance = 10; // meters
+ copy = copyList(line);
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ assertEquals(14, simplifiedLine.size());
+ assertEndPoints(line, simplifiedLine);
+ assertSimplifiedPointsFromLine(line, simplifiedLine);
+ assertLineLength(line, simplifiedLine);
+ assertInputUnchanged(line, copy);
+
+ tolerance = 15; // meters
+ copy = copyList(line);
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ assertEquals(10, simplifiedLine.size());
+ assertEndPoints(line, simplifiedLine);
+ assertSimplifiedPointsFromLine(line, simplifiedLine);
+ assertLineLength(line, simplifiedLine);
+ assertInputUnchanged(line, copy);
+
+ tolerance = 20; // meters
+ copy = copyList(line);
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ assertEquals(8, simplifiedLine.size());
+ assertEndPoints(line, simplifiedLine);
+ assertSimplifiedPointsFromLine(line, simplifiedLine);
+ assertLineLength(line, simplifiedLine);
+ assertInputUnchanged(line, copy);
+
+ tolerance = 50; // meters
+ copy = copyList(line);
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ assertEquals(6, simplifiedLine.size());
+ assertEndPoints(line, simplifiedLine);
+ assertSimplifiedPointsFromLine(line, simplifiedLine);
+ assertLineLength(line, simplifiedLine);
+ assertInputUnchanged(line, copy);
+
+ tolerance = 500; // meters
+ copy = copyList(line);
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ assertEquals(3, simplifiedLine.size());
+ assertEndPoints(line, simplifiedLine);
+ assertSimplifiedPointsFromLine(line, simplifiedLine);
+ assertLineLength(line, simplifiedLine);
+ assertInputUnchanged(line, copy);
+
+ tolerance = 1000; // meters
+ copy = copyList(line);
+ simplifiedLine = PolyUtil.simplify(line, tolerance);
+ assertEquals(2, simplifiedLine.size());
+ assertEndPoints(line, simplifiedLine);
+ assertSimplifiedPointsFromLine(line, simplifiedLine);
+ assertLineLength(line, simplifiedLine);
+ assertInputUnchanged(line, copy);
+
+ /**
+ * Polygons
+ */
+ // Open triangle
+ ArrayList triangle = new ArrayList<>();
+ triangle.add(new LatLng(28.06025,-82.41030));
+ triangle.add(new LatLng(28.06129, -82.40945));
+ triangle.add(new LatLng(28.06206, -82.40917));
+ triangle.add(new LatLng(28.06125, -82.40850));
+ triangle.add(new LatLng(28.06035, -82.40834));
+ triangle.add(new LatLng(28.06038, -82.40924));
+ assertFalse(PolyUtil.isClosedPolygon(triangle));
+
+ copy = copyList(triangle);
+ tolerance = 88; // meters
+ List simplifiedTriangle = PolyUtil.simplify(triangle, tolerance);
+ assertEquals(4, simplifiedTriangle.size());
+ assertEndPoints(triangle, simplifiedTriangle);
+ assertSimplifiedPointsFromLine(triangle, simplifiedTriangle);
+ assertLineLength(triangle, simplifiedTriangle);
+ assertInputUnchanged(triangle, copy);
+
+ // Close the triangle
+ LatLng p = triangle.get(0);
+ LatLng closePoint = new LatLng(p.latitude, p.longitude);
+ triangle.add(closePoint);
+ assertTrue(PolyUtil.isClosedPolygon(triangle));
+
+ copy = copyList(triangle);
+ tolerance = 88; // meters
+ simplifiedTriangle = PolyUtil.simplify(triangle, tolerance);
+ assertEquals(4, simplifiedTriangle.size());
+ assertEndPoints(triangle, simplifiedTriangle);
+ assertSimplifiedPointsFromLine(triangle, simplifiedTriangle);
+ assertLineLength(triangle, simplifiedTriangle);
+ assertInputUnchanged(triangle, copy);
+
+ // Open oval
+ final String OVAL_POLYGON = "}wgjDxw_vNuAd@}AN{A]w@_Au@kAUaA?{@Ke@@_@C]D[FULWFOLSNMTOVOXO\\I\\CX?VJXJTDTNXTVVLVJ`@FXA\\AVLZBTATBZ@ZAT?\\?VFT@XGZ";
+ List oval = PolyUtil.decode(OVAL_POLYGON);
+ assertFalse(PolyUtil.isClosedPolygon(oval));
+
+ copy = copyList(oval);
+ tolerance = 10; // meters
+ List simplifiedOval= PolyUtil.simplify(oval, tolerance);
+ assertEquals(13, simplifiedOval.size());
+ assertEndPoints(oval, simplifiedOval);
+ assertSimplifiedPointsFromLine(oval, simplifiedOval);
+ assertLineLength(oval, simplifiedOval);
+ assertInputUnchanged(oval, copy);
+
+ // Close the oval
+ p = oval.get(0);
+ closePoint = new LatLng(p.latitude, p.longitude);
+ oval.add(closePoint);
+ assertTrue(PolyUtil.isClosedPolygon(oval));
+
+ copy = copyList(oval);
+ tolerance = 10; // meters
+ simplifiedOval= PolyUtil.simplify(oval, tolerance);
+ assertEquals(13, simplifiedOval.size());
+ assertEndPoints(oval, simplifiedOval);
+ assertSimplifiedPointsFromLine(oval, simplifiedOval);
+ assertLineLength(oval, simplifiedOval);
+ assertInputUnchanged(oval, copy);
+ }
+
+ /**
+ * Asserts that the beginning point of the original line matches the beginning point of the
+ * simplified line, and that the end point of the original line matches the end point of the
+ * simplified line.
+ * @param line original line
+ * @param simplifiedLine simplified line
+ */
+ private void assertEndPoints(List line, List simplifiedLine) {
+ assertEquals(line.get(0), simplifiedLine.get(0));
+ assertEquals(line.get(line.size() - 1), simplifiedLine.get(simplifiedLine.size() - 1));
+ }
+
+ /**
+ * Asserts that the simplified line is composed of points from the original line.
+ * @param line original line
+ * @param simplifiedLine simplified line
+ */
+ private void assertSimplifiedPointsFromLine(List line, List simplifiedLine) {
+ for (LatLng l : simplifiedLine) {
+ assertTrue(line.contains(l));
+ }
+ }
+
+ /**
+ * Asserts that the length of the simplified line is always equal to or less than the length of
+ * the original line, if simplification has eliminated any points from the original line
+ * @param line original line
+ * @param simplifiedLine simplified line
+ */
+ private void assertLineLength(List line, List simplifiedLine) {
+ if (line.size() == simplifiedLine.size()) {
+ // If no points were eliminated, then the length of both lines should be the same
+ assertTrue(SphericalUtil.computeLength(simplifiedLine) == SphericalUtil.computeLength(line));
+ } else {
+ assertTrue(simplifiedLine.size() < line.size());
+ // If points were eliminated, then the simplified line should always be shorter
+ assertTrue(SphericalUtil.computeLength(simplifiedLine) < SphericalUtil.computeLength(line));
+ }
+ }
+
+ /**
+ * Returns a copy of the LatLng objects contained in one list to another list. LatLng.latitude
+ * and LatLng.longitude are immutable, so having references to the same LatLng object is
+ * sufficient to guarantee that the contents are the same.
+ * @param original original list
+ * @return a copy of the original list, containing references to the same LatLng elements in
+ * the same order.
+ */
+ private List copyList(List original) {
+ ArrayList copy = new ArrayList<>(original.size());
+ for (LatLng l : original) {
+ copy.add(l);
+ }
+ return copy;
+ }
+
+ /**
+ * Asserts that the contents of the original List passed into the PolyUtil.simplify() method
+ * doesn't change after the method is executed. We test for this because the poly is modified
+ * (a small offset is added to the last point) to allow for polygon simplification.
+ * @param afterInput the list passed into PolyUtil.simplify(), after PolyUtil.simplify() has
+ * finished executing
+ * @param beforeInput a copy of the list before it is passed into PolyUtil.simplify()
+ */
+ private void assertInputUnchanged(List afterInput, List beforeInput) {
+ assertEquals(beforeInput, afterInput);
+ }
+
+ public void testIsClosedPolygon() {
+ ArrayList poly = new ArrayList<>();
+ poly.add(new LatLng(28.06025, -82.41030));
+ poly.add(new LatLng(28.06129, -82.40945));
+ poly.add(new LatLng(28.06206, -82.40917));
+ poly.add(new LatLng(28.06125, -82.40850));
+ poly.add(new LatLng(28.06035, -82.40834));
+
+ assertFalse(PolyUtil.isClosedPolygon(poly));
+
+ // Add the closing point that's same as the first
+ poly.add(new LatLng(28.06025, -82.41030));
+ assertTrue(PolyUtil.isClosedPolygon(poly));
+ }
+
+ public void testDistanceToLine() {
+ LatLng startLine = new LatLng(28.05359, -82.41632);
+ LatLng endLine = new LatLng(28.05310, -82.41634);
+ LatLng p = new LatLng(28.05342, -82.41594);
+
+ double distance = PolyUtil.distanceToLine(p, startLine, endLine);
+ expectNearNumber(42.989894, distance, 1e-6);
+ }
public void testDecodePath() {
List latLngs = PolyUtil.decode(TEST_LINE);