Class BoundaryOp

  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.operation;
 13  
 14 import java.util.ArrayList;
 15 import java.util.Iterator;
 16 import java.util.List;
 17 import java.util.Map;
 18 import java.util.TreeMap;
 19  
 20 import org.locationtech.jts.algorithm.BoundaryNodeRule;
 21 import org.locationtech.jts.geom.Coordinate;
 22 import org.locationtech.jts.geom.CoordinateArrays;
 23 import org.locationtech.jts.geom.CoordinateSequence;
 24 import org.locationtech.jts.geom.Geometry;
 25 import org.locationtech.jts.geom.GeometryCollection;
 26 import org.locationtech.jts.geom.GeometryFactory;
 27 import org.locationtech.jts.geom.LineString;
 28 import org.locationtech.jts.geom.MultiLineString;
 29 import org.locationtech.jts.geom.MultiPoint;
 30 import org.locationtech.jts.geom.Point;
 31  
 32 /**
 33  * Computes the boundary of a {@link Geometry}.
 34  * Allows specifying the {@link BoundaryNodeRule} to be used.
 35  * This operation will always return a {@link Geometry} of the appropriate
 36  * dimension for the boundary (even if the input geometry is empty).
 37  * The boundary of zero-dimensional geometries (Points) is
 38  * always the empty {@link GeometryCollection}.
 39  *
 40  * @author Martin Davis
 41  * @version 1.7
 42  */
 43  
 44 public class BoundaryOp
 45 {
 46   /**
 47    * Computes a geometry representing the boundary of a geometry.
 48    * 
 49    * @param g the input geometry
 50    * @return the computed boundary
 51    */
 52   public static Geometry getBoundary(Geometry g)
 53   {
 54     BoundaryOp bop = new BoundaryOp(g);
 55     return bop.getBoundary();
 56   }
 57   
 58   /**
 59    * Computes a geometry representing the boundary of a geometry,
 60    * using an explicit {@link BoundaryNodeRule}.
 61    * 
 62    * @param g the input geometry
 63    * @param bnRule the Boundary Node Rule to use
 64    * @return the computed boundary
 65    */
 66   public static Geometry getBoundary(Geometry g, BoundaryNodeRule bnRule)
 67   {
 68     BoundaryOp bop = new BoundaryOp(g, bnRule);
 69     return bop.getBoundary();
 70   }
 71   
 72   private Geometry geom;
 73   private GeometryFactory geomFact;
 74   private BoundaryNodeRule bnRule;
 75  
 76   /**
 77    * Creates a new instance for the given geometry.
 78    * 
 79    * @param geom the input geometry
 80    */
 81   public BoundaryOp(Geometry geom)
 82   {
 83     this(geom, BoundaryNodeRule.MOD2_BOUNDARY_RULE);
 84   }
 85  
 86   /**
 87    * Creates a new instance for the given geometry.
 88    * 
 89    * @param geom the input geometry
 90    * @param bnRule the Boundary Node Rule to use
 91    */
 92   public BoundaryOp(Geometry geom, BoundaryNodeRule bnRule)
 93   {
 94     this.geom = geom;
 95     geomFact = geom.getFactory();
 96     this.bnRule = bnRule;
 97   }
 98  
 99   /**
100    * Gets the computed boundary.
101    * 
102    * @return the boundary geometry
103    */
104   public Geometry getBoundary()
105   {
106     if (geom instanceof LineString) return boundaryLineString((LineString) geom);
107     if (geom instanceof MultiLineString) return boundaryMultiLineString((MultiLineString) geom);
108     return geom.getBoundary();
109   }
110  
111   private MultiPoint getEmptyMultiPoint()
112   {
113     return geomFact.createMultiPoint();
114   }
115  
116   private Geometry boundaryMultiLineString(MultiLineString mLine)
117   {
118     if (geom.isEmpty()) {
119       return getEmptyMultiPoint();
120     }
121  
122     Coordinate[] bdyPts = computeBoundaryCoordinates(mLine);
123  
124     // return Point or MultiPoint
125     if (bdyPts.length == 1) {
126       return geomFact.createPoint(bdyPts[0]);
127     }
128     // this handles 0 points case as well
129     return geomFact.createMultiPointFromCoords(bdyPts);
130   }
131  
132 /*
133 // MD - superseded
134   private Coordinate[] computeBoundaryFromGeometryGraph(MultiLineString mLine)
135   {
136     GeometryGraph g = new GeometryGraph(0, mLine, bnRule);
137     Coordinate[] bdyPts = g.getBoundaryPoints();
138     return bdyPts;
139   }
140 */
141  
142   private Map endpointMap;
143  
144   private Coordinate[] computeBoundaryCoordinates(MultiLineString mLine)
145   {
146     List bdyPts = new ArrayList();
147     endpointMap = new TreeMap();
148     for (int i = 0; i < mLine.getNumGeometries(); i++) {
149       LineString line = (LineString) mLine.getGeometryN(i);
150       if (line.getNumPoints() == 0)
151         continue;
152       addEndpoint(line.getCoordinateN(0));
153       addEndpoint(line.getCoordinateN(line.getNumPoints() - 1));
154     }
155  
156     for (Iterator it = endpointMap.entrySet().iterator(); it.hasNext(); ) {
157       Map.Entry entry = (Map.Entry) it.next();
158       Counter counter = (Counter) entry.getValue();
159       int valence = counter.count;
160       if (bnRule.isInBoundary(valence)) {
161         bdyPts.add(entry.getKey());
162       }
163     }
164  
165     return CoordinateArrays.toCoordinateArray(bdyPts);
166   }
167  
168   private void addEndpoint(Coordinate pt)
169   {
170     Counter counter = (Counter) endpointMap.get(pt);
171     if (counter == null) {
172       counter = new Counter();
173       endpointMap.put(pt, counter);
174     }
175     counter.count++;
176   }
177  
178   private Geometry boundaryLineString(LineString line)
179   {
180     if (geom.isEmpty()) {
181       return getEmptyMultiPoint();
182     }
183  
184     if (line.isClosed()) {
185       // check whether endpoints of valence 2 are on the boundary or not
186       boolean closedEndpointOnBoundary = bnRule.isInBoundary(2);
187       if (closedEndpointOnBoundary) {
188         return line.getStartPoint();
189       }
190       else {
191         return geomFact.createMultiPoint();
192       }
193     }
194     return geomFact.createMultiPoint(new Point[]{
195                                      line.getStartPoint(),
196                                      line.getEndPoint()
197     });
198   }
199 }
200  
201 /**
202  * Stores an integer count, for use as a Map entry.
203  *
204  * @author Martin Davis
205  * @version 1.7
206  */
207 class Counter
208 {
209   /**
210    * The value of the count
211    */
212   int count;
213 }
214