package com.revolsys.geometry.simplify; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.PointList; import com.revolsys.geometry.util.Triangles; /** * Simplifies a linestring (sequence of points) using the * Visvalingam-Whyatt algorithm. * The Visvalingam-Whyatt algorithm simplifies geometry * by removing vertices while trying to minimize the area changed. * * @version 1.7 */ class VWLineSimplifier { static class VWVertex { public static double MAX_AREA = Double.MAX_VALUE; public static VWLineSimplifier.VWVertex buildLine(final LineString pts) { VWLineSimplifier.VWVertex first = null; VWLineSimplifier.VWVertex prev = null; for (int i = 0; i < pts.getVertexCount(); i++) { final VWLineSimplifier.VWVertex v = new VWVertex(pts.getPoint(i)); if (first == null) { first = v; } v.setPrev(prev); if (prev != null) { prev.setNext(v); prev.updateArea(); } prev = v; } return first; } private double area = MAX_AREA; private boolean isLive = true; private VWLineSimplifier.VWVertex next; private VWLineSimplifier.VWVertex prev; private final Point pt; public VWVertex(final Point pt) { this.pt = pt; } public double getArea() { return this.area; } public Point[] getCoordinates() { final PointList coords = new PointList(); VWLineSimplifier.VWVertex curr = this; do { coords.add(curr.pt, false); curr = curr.next; } while (curr != null); return coords.toPointArray(); } public boolean isLive() { return this.isLive; } public VWLineSimplifier.VWVertex remove() { final VWLineSimplifier.VWVertex tmpPrev = this.prev; final VWLineSimplifier.VWVertex tmpNext = this.next; VWLineSimplifier.VWVertex result = null; if (this.prev != null) { this.prev.setNext(tmpNext); this.prev.updateArea(); result = this.prev; } if (this.next != null) { this.next.setPrev(tmpPrev); this.next.updateArea(); if (result == null) { result = this.next; } } this.isLive = false; return result; } public void setNext(final VWLineSimplifier.VWVertex next) { this.next = next; } public void setPrev(final VWLineSimplifier.VWVertex prev) { this.prev = prev; } public void updateArea() { if (this.prev == null || this.next == null) { this.area = MAX_AREA; return; } this.area = Math.abs(Triangles.area(this.prev.pt, this.pt, this.next.pt)); } } public static Point[] simplify(final LineString coords, final double distanceTolerance) { final VWLineSimplifier simp = new VWLineSimplifier(coords, distanceTolerance); return simp.simplify(); } private final LineString pts; private final double tolerance; public VWLineSimplifier(final LineString coords, final double distanceTolerance) { this.pts = coords; this.tolerance = distanceTolerance * distanceTolerance; } public Point[] simplify() { final VWLineSimplifier.VWVertex vwLine = VWVertex.buildLine(this.pts); double minArea = this.tolerance; do { minArea = simplifyVertex(vwLine); } while (minArea < this.tolerance); final Point[] simp = vwLine.getCoordinates(); // ensure computed value is a valid line if (simp.length < 2) { return new Point[] { simp[0], simp[0] }; } return simp; } private double simplifyVertex(final VWLineSimplifier.VWVertex vwLine) { /** * Scan vertices in line and remove the one with smallest effective area. */ // TODO: use an appropriate data structure to optimize finding the smallest // area vertex VWLineSimplifier.VWVertex curr = vwLine; double minArea = curr.getArea(); VWLineSimplifier.VWVertex minVertex = null; while (curr != null) { final double area = curr.getArea(); if (area < minArea) { minArea = area; minVertex = curr; } curr = curr.next; } if (minVertex != null && minArea < this.tolerance) { minVertex.remove(); } if (!vwLine.isLive()) { return -1; } return minArea; } }