@@ -27,7 +27,7 @@ import com.google.maps.android.MathUtil.sinFromHav
2727import com.google.maps.android.MathUtil.sinSumFromHav
2828import com.google.maps.android.MathUtil.wrap
2929import com.google.maps.android.SphericalUtil.computeDistanceBetween
30- import java.util.Stack
30+ import kotlin.collections.ArrayDeque
3131import kotlin.math.cos
3232import kotlin.math.max
3333import kotlin.math.min
@@ -300,65 +300,93 @@ object PolyUtil {
300300 * @return a simplified poly produced by the Douglas-Peucker algorithm
301301 */
302302 @JvmStatic
303- fun simplify (poly : MutableList <LatLng >, tolerance : Double ): List <LatLng > {
304- val n = poly.size
305- require(n >= 1 ) { " Polyline must have at least 1 point" }
303+ fun simplify (poly : List <LatLng >, tolerance : Double ): List <LatLng > {
304+ require(poly.isNotEmpty()) { " Polyline must have at least 1 point" }
306305 require(tolerance > 0 ) { " Tolerance must be greater than zero" }
307306
308- val closedPolygon = isClosedPolygon(poly)
309- var lastPoint: LatLng ? = null
310-
311- // Check if the provided poly is a closed polygon
312- if (closedPolygon) {
313- // Add a small offset to the last point for Douglas-Peucker on polygons (see #201)
314- val OFFSET = 0.00000000001
315- lastPoint = poly.last()
316- poly.removeAt(poly.size - 1 )
317- poly.add(LatLng (lastPoint.latitude + OFFSET , lastPoint.longitude + OFFSET ))
307+ // The simplification process is handled by the Douglas-Peucker algorithm,
308+ // which is implemented in a separate private function for clarity.
309+ // Before we can apply the algorithm, we need to handle a special case for closed polygons.
310+ val workingPoly = if (isClosedPolygon(poly)) {
311+ // For closed polygons, the Douglas-Peucker algorithm needs to "see" the connection
312+ // between the last and first points. A common trick to achieve this is to temporarily
313+ // open the polygon and add a point that is very close to the last point. This ensures
314+ // that the simplification process takes the closing segment into account.
315+ val lastPoint = poly.last()
316+ val offset = 0.00000000001
317+ poly.toMutableList().apply {
318+ removeAt(size - 1 )
319+ add(LatLng (lastPoint.latitude + offset, lastPoint.longitude + offset))
320+ }
321+ } else {
322+ poly
318323 }
319324
320- var maxIdx = 0
321- val stack = Stack <IntArray >()
322- val dists = DoubleArray (n)
323- dists[0 ] = 1.0
324- dists[n - 1 ] = 1.0
325- var maxDist: Double
326- var dist: Double
327- var current: IntArray
325+ // The douglasPeucker function returns a boolean array indicating which points to keep.
326+ val pointsToKeep = douglasPeucker(workingPoly, tolerance)
327+
328+ // We then filter the original, unmodified polyline based on the results of the
329+ // simplification algorithm. This ensures that the original points are preserved in the
330+ // final output.
331+ return poly.filterIndexed { index, _ -> pointsToKeep[index] }
332+ }
333+
334+ /* *
335+ * Implements the Douglas-Peucker algorithm for simplifying a polyline.
336+ *
337+ * The algorithm works by recursively dividing the polyline into smaller segments and finding
338+ * the point that is farthest from the line segment connecting the start and end points.
339+ * If this point is farther than the specified tolerance, it is kept, and the algorithm is
340+ * applied recursively to the two new segments.
341+ *
342+ * @param poly The polyline to be simplified.
343+ * @param tolerance The tolerance in meters.
344+ * @return A boolean array where `true` indicates that the point at the corresponding index
345+ * should be kept in the simplified polyline.
346+ */
347+ private fun douglasPeucker (poly : List <LatLng >, tolerance : Double ): BooleanArray {
348+ val n = poly.size
349+ // We start with a boolean array that will mark the points to keep.
350+ // Initially, only the first and last points are marked for keeping.
351+ val keepPoint = BooleanArray (n) { false }
352+ keepPoint[0 ] = true
353+ keepPoint[n - 1 ] = true
328354
355+ // The algorithm is only needed if the polyline has more than 2 points.
329356 if (n > 2 ) {
330- val stackVal = intArrayOf(0 , n - 1 )
331- stack.push(stackVal)
357+ // We use a stack (implemented with ArrayDeque for efficiency) to manage the
358+ // segments that we need to process. Initially, this contains the entire polyline.
359+ val stack = ArrayDeque <Pair <Int , Int >>()
360+ stack.addLast(0 to n - 1 )
361+
362+ // We process segments from the stack until it's empty.
332363 while (stack.isNotEmpty()) {
333- current = stack.pop()
334- maxDist = 0.0
335- for (idx in current[0 ] + 1 until current[1 ]) {
336- dist = distanceToLine(poly[idx], poly[current[0 ]], poly[current[1 ]])
364+ val (start, end) = stack.removeLast()
365+ var maxDist = 0.0
366+ var maxIdx = 0
367+
368+ // For the current segment, we find the point that is farthest from the line
369+ // connecting the start and end points.
370+ for (idx in start + 1 until end) {
371+ val dist = distanceToLine(poly[idx], poly[start], poly[end])
337372 if (dist > maxDist) {
338373 maxDist = dist
339374 maxIdx = idx
340375 }
341376 }
377+
378+ // If the farthest point is farther than the tolerance, we mark it to be kept.
379+ // We then push two new segments onto the stack to be processed recursively:
380+ // one from the start to the farthest point, and one from the farthest point to the end.
342381 if (maxDist > tolerance) {
343- dists[maxIdx] = maxDist
344- val stackValCurMax = intArrayOf(current[0 ], maxIdx)
345- stack.push(stackValCurMax)
346- val stackValMaxCur = intArrayOf(maxIdx, current[1 ])
347- stack.push(stackValMaxCur)
382+ keepPoint[maxIdx] = true
383+ stack.addLast(start to maxIdx)
384+ stack.addLast(maxIdx to end)
348385 }
349386 }
350387 }
351388
352- if (closedPolygon) {
353- // Replace last point w/ offset with the original last point to re-close the polygon
354- poly.removeAt(poly.size - 1 )
355- if (lastPoint != null ) {
356- poly.add(lastPoint)
357- }
358- }
359-
360- // Generate the simplified line
361- return poly.filterIndexed { idx, _ -> dists[idx] != 0.0 }
389+ return keepPoint
362390 }
363391
364392 /* *
0 commit comments