Class Densifier

  1 /*
  2  * Copyright (c) 2016 Vivid Solutions.
  3  *
  4  * All rights reserved. This program and the accompanying materials
  5  * are made available under the terms of the Eclipse Public License 2.0
  6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
  7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
  8  * and the Eclipse Distribution License is available at
  9  *
 10  * http://www.eclipse.org/org/documents/edl-v10.php.
 11  */
 12 package org.locationtech.jts.densify;
 13  
 14 import org.locationtech.jts.geom.Coordinate;
 15 import org.locationtech.jts.geom.CoordinateList;
 16 import org.locationtech.jts.geom.CoordinateSequence;
 17 import org.locationtech.jts.geom.Geometry;
 18 import org.locationtech.jts.geom.LineSegment;
 19 import org.locationtech.jts.geom.LineString;
 20 import org.locationtech.jts.geom.MultiPolygon;
 21 import org.locationtech.jts.geom.Polygon;
 22 import org.locationtech.jts.geom.PrecisionModel;
 23 import org.locationtech.jts.geom.util.GeometryTransformer;
 24  
 25 /**
 26  * Densifies a {@link Geometry} by inserting extra vertices along the line segments
 27  * contained in the geometry. 
 28  * All segments in the created densified geometry will be no longer than
 29  * than the given distance tolerance.
 30  * Densified polygonal geometries are guaranteed to be topologically correct.
 31  * The coordinates created during densification respect the input geometry's
 32  * {@link PrecisionModel}.
 33  * <p>
 34  * <b>Note:</b> At some future point this class will
 35  * offer a variety of densification strategies.
 36  * 
 37  * @author Martin Davis
 38  */
 39 public class Densifier {
 40     /**
 41      * Densifies a geometry using a given distance tolerance,
 42    * and respecting the input geometry's {@link PrecisionModel}.
 43      * 
 44      * @param geom the geometry to densify
 45      * @param distanceTolerance the distance tolerance to densify
 46      * @return the densified geometry
 47      */
 48     public static Geometry densify(Geometry geom, double distanceTolerance) {
 49         Densifier densifier = new Densifier(geom);
 50         densifier.setDistanceTolerance(distanceTolerance);
 51         return densifier.getResultGeometry();
 52     }
 53  
 54     /**
 55      * Densifies a coordinate sequence.
 56      * 
 57      * @param pts
 58      * @param distanceTolerance
 59      * @return the densified coordinate sequence
 60      */
 61     private static Coordinate[] densifyPoints(Coordinate[] pts,
 62             double distanceTolerance, PrecisionModel precModel) {
 63         LineSegment seg = new LineSegment();
 64         CoordinateList coordList = new CoordinateList();
 65         for (int i = 0; i < pts.length - 1; i++) {
 66             seg.p0 = pts[i];
 67             seg.p1 = pts[i + 1];
 68             coordList.add(seg.p0, false);
 69             double len = seg.getLength();
 70             int densifiedSegCount = (int) (len / distanceTolerance) + 1;
 71             if (densifiedSegCount > 1) {
 72                 double densifiedSegLen = len / densifiedSegCount;
 73                 for (int j = 1; j < densifiedSegCount; j++) {
 74                     double segFract = (j * densifiedSegLen) / len;
 75                     Coordinate p = seg.pointAlong(segFract);
 76           precModel.makePrecise(p);
 77                     coordList.add(p, false);
 78                 }
 79             }
 80         }
 81         coordList.add(pts[pts.length - 1], false);
 82         return coordList.toCoordinateArray();
 83     }
 84  
 85     private Geometry inputGeom;
 86  
 87     private double distanceTolerance;
 88  
 89     /**
 90      * Creates a new densifier instance.
 91      * 
 92      * @param inputGeom
 93      */
 94     public Densifier(Geometry inputGeom) {
 95         this.inputGeom = inputGeom;
 96     }
 97  
 98     /**
 99      * Sets the distance tolerance for the densification. All line segments
100      * in the densified geometry will be no longer than the distance tolerance.
101      * simplified geometry will be within this distance of the original geometry.
102      * The distance tolerance must be positive.
103      * 
104      * @param distanceTolerance
105      *          the densification tolerance to use
106      */
107     public void setDistanceTolerance(double distanceTolerance) {
108         if (distanceTolerance <= 0.0)
109             throw new IllegalArgumentException("Tolerance must be positive");
110         this.distanceTolerance = distanceTolerance;
111     }
112  
113     /**
114      * Gets the densified geometry.
115      * 
116      * @return the densified geometry
117      */
118     public Geometry getResultGeometry() {
119         return (new DensifyTransformer(distanceTolerance)).transform(inputGeom);
120     }
121  
122     static class DensifyTransformer extends GeometryTransformer {
123       double distanceTolerance;
124       
125       DensifyTransformer(double distanceTolerance) {
126         this.distanceTolerance = distanceTolerance;
127     }
128       
129         protected CoordinateSequence transformCoordinates(
130                 CoordinateSequence coords, Geometry parent) {
131             Coordinate[] inputPts = coords.toCoordinateArray();
132             Coordinate[] newPts = Densifier
133                     .densifyPoints(inputPts, distanceTolerance, parent.getPrecisionModel());
134             // prevent creation of invalid linestrings
135             if (parent instanceof LineString && newPts.length == 1) {
136                 newPts = new Coordinate[0];
137             }
138             return factory.getCoordinateSequenceFactory().create(newPts);
139         }
140  
141         protected Geometry transformPolygon(Polygon geom, Geometry parent) {
142             Geometry roughGeom = super.transformPolygon(geom, parent);
143             // don't try and correct if the parent is going to do this
144             if (parent instanceof MultiPolygon) {
145                 return roughGeom;
146             }
147             return createValidArea(roughGeom);
148         }
149  
150         protected Geometry transformMultiPolygon(MultiPolygon geom, Geometry parent) {
151             Geometry roughGeom = super.transformMultiPolygon(geom, parent);
152             return createValidArea(roughGeom);
153         }
154  
155         /**
156          * Creates a valid area geometry from one that possibly has bad topology
157          * (i.e. self-intersections). Since buffer can handle invalid topology, but
158          * always returns valid geometry, constructing a 0-width buffer "corrects"
159          * the topology. Note this only works for area geometries, since buffer
160          * always returns areas. This also may return empty geometries, if the input
161          * has no actual area.
162          * 
163          * @param roughAreaGeom
164          *          an area geometry possibly containing self-intersections
165          * @return a valid area geometry
166          */
167         private Geometry createValidArea(Geometry roughAreaGeom) {
168             return roughAreaGeom.buffer(0.0);
169         }
170     }
171  
172 }
173