root / trunk / libraries / libFMap / src / com / vividsolutions / jts / geomgraph / SnappingGeometryGraph.java @ 7591
History | View | Annotate | Download (12.6 KB)
1 |
/*
|
---|---|
2 |
* Created on 12-sep-2006 by azabala
|
3 |
*
|
4 |
*/
|
5 |
package com.vividsolutions.jts.geomgraph; |
6 |
|
7 |
import java.util.Collection; |
8 |
import java.util.HashMap; |
9 |
import java.util.Iterator; |
10 |
import java.util.List; |
11 |
import java.util.Map; |
12 |
|
13 |
import com.iver.cit.gvsig.fmap.topology.geomgraph.SnappingPlanarGraph; |
14 |
import com.vividsolutions.jts.geom.*; |
15 |
import com.vividsolutions.jts.geomgraph.*; |
16 |
import com.vividsolutions.jts.geomgraph.index.EdgeSetIntersector; |
17 |
import com.vividsolutions.jts.geomgraph.index.SegmentIntersector; |
18 |
import com.vividsolutions.jts.geomgraph.index.SimpleMCSweepLineIntersector; |
19 |
import com.vividsolutions.jts.util.Assert; |
20 |
import com.vividsolutions.jts.algorithm.*; |
21 |
|
22 |
/**
|
23 |
*/
|
24 |
public class SnappingGeometryGraph extends SnappingPlanarGraph { |
25 |
|
26 |
/**
|
27 |
* This method implements the Boundary Determination Rule
|
28 |
* for determining whether
|
29 |
* a component (node or edge) that appears multiple times in elements
|
30 |
* of a MultiGeometry is in the boundary or the interior of the Geometry
|
31 |
* <br>
|
32 |
* The SFS uses the "Mod-2 Rule", which this function implements
|
33 |
* <br>
|
34 |
* An alternative (and possibly more intuitive) rule would be
|
35 |
* the "At Most One Rule":
|
36 |
* isInBoundary = (componentCount == 1)
|
37 |
*/
|
38 |
public static boolean isInBoundary(int boundaryCount) |
39 |
{ |
40 |
// the "Mod-2 Rule"
|
41 |
return boundaryCount % 2 == 1; |
42 |
} |
43 |
|
44 |
|
45 |
|
46 |
public static int determineBoundary(int boundaryCount) |
47 |
{ |
48 |
return isInBoundary(boundaryCount) ? Location.BOUNDARY : Location.INTERIOR;
|
49 |
} |
50 |
|
51 |
|
52 |
private Geometry parentGeom;
|
53 |
|
54 |
|
55 |
/**
|
56 |
* The lineEdgeMap is a map of the linestring components of the
|
57 |
* parentGeometry to the edges which are derived from them.
|
58 |
* This is used to efficiently perform findEdge queries
|
59 |
*/
|
60 |
private Map lineEdgeMap = new HashMap(); |
61 |
|
62 |
/**
|
63 |
* If this flag is true, the Boundary Determination Rule will used when deciding
|
64 |
* whether nodes are in the boundary or not
|
65 |
*/
|
66 |
private boolean useBoundaryDeterminationRule = false; |
67 |
|
68 |
private int argIndex; // the index of this geometry as an argument to a spatial function (used for labelling) |
69 |
|
70 |
private Collection boundaryNodes; |
71 |
|
72 |
private boolean hasTooFewPoints = false; |
73 |
|
74 |
private Coordinate invalidPoint = null; |
75 |
|
76 |
|
77 |
private EdgeSetIntersector createEdgeSetIntersector()
|
78 |
{ |
79 |
// various options for computing intersections, from slowest to fastest
|
80 |
|
81 |
//private EdgeSetIntersector esi = new SimpleEdgeSetIntersector();
|
82 |
//private EdgeSetIntersector esi = new MonotoneChainIntersector();
|
83 |
//private EdgeSetIntersector esi = new NonReversingChainIntersector();
|
84 |
//private EdgeSetIntersector esi = new SimpleSweepLineIntersector();
|
85 |
//private EdgeSetIntersector esi = new MCSweepLineIntersector();
|
86 |
|
87 |
//return new SimpleEdgeSetIntersector();
|
88 |
return new SimpleMCSweepLineIntersector(); |
89 |
} |
90 |
|
91 |
/**
|
92 |
* It builds a geometry graph from the parent geometry, applying snapping with
|
93 |
* the specified snap tolerance
|
94 |
*
|
95 |
* @param argIndex
|
96 |
* @param parentGeom
|
97 |
* @param snapTolerance
|
98 |
*/
|
99 |
public SnappingGeometryGraph(int argIndex, Geometry parentGeom, double snapTolerance) { |
100 |
super(snapTolerance);
|
101 |
this.argIndex = argIndex;
|
102 |
this.parentGeom = parentGeom;
|
103 |
if (parentGeom != null) { |
104 |
add(parentGeom); |
105 |
} |
106 |
} |
107 |
|
108 |
|
109 |
public boolean hasTooFewPoints() { |
110 |
return hasTooFewPoints;
|
111 |
} |
112 |
|
113 |
|
114 |
public Coordinate getInvalidPoint() {
|
115 |
return invalidPoint;
|
116 |
} |
117 |
|
118 |
|
119 |
public Geometry getGeometry() {
|
120 |
return parentGeom;
|
121 |
} |
122 |
|
123 |
|
124 |
|
125 |
public Collection getBoundaryNodes() |
126 |
{ |
127 |
if (boundaryNodes == null) |
128 |
boundaryNodes = nodes.getBoundaryNodes(argIndex); |
129 |
return boundaryNodes;
|
130 |
} |
131 |
|
132 |
public Coordinate[] getBoundaryPoints() |
133 |
{ |
134 |
Collection coll = getBoundaryNodes();
|
135 |
Coordinate[] pts = new Coordinate[coll.size()]; |
136 |
int i = 0; |
137 |
for (Iterator it = coll.iterator(); it.hasNext(); ) { |
138 |
Node node = (Node) it.next(); |
139 |
pts[i++] = (Coordinate) node.getCoordinate().clone(); |
140 |
} |
141 |
return pts;
|
142 |
} |
143 |
|
144 |
public Edge findEdge(LineString line)
|
145 |
{ |
146 |
return (Edge) lineEdgeMap.get(line);
|
147 |
} |
148 |
|
149 |
public void computeSplitEdges(List edgelist) |
150 |
{ |
151 |
for (Iterator i = edges.iterator(); i.hasNext(); ) { |
152 |
Edge e = (Edge) i.next(); |
153 |
e.eiList.addSplitEdges(edgelist); |
154 |
} |
155 |
} |
156 |
private void add(Geometry g) |
157 |
{ |
158 |
if (g.isEmpty()) return; |
159 |
|
160 |
// check if this Geometry should obey the Boundary Determination Rule
|
161 |
// all collections except MultiPolygons obey the rule
|
162 |
if (g instanceof GeometryCollection |
163 |
&& ! (g instanceof MultiPolygon))
|
164 |
useBoundaryDeterminationRule = true;
|
165 |
|
166 |
if (g instanceof Polygon) addPolygon((Polygon) g); |
167 |
// LineString also handles LinearRings
|
168 |
else if (g instanceof LineString) addLineString((LineString) g); |
169 |
else if (g instanceof Point) addPoint((Point) g); |
170 |
else if (g instanceof MultiPoint) addCollection((MultiPoint) g); |
171 |
else if (g instanceof MultiLineString) addCollection((MultiLineString) g); |
172 |
else if (g instanceof MultiPolygon) addCollection((MultiPolygon) g); |
173 |
else if (g instanceof GeometryCollection) addCollection((GeometryCollection) g); |
174 |
else throw new UnsupportedOperationException(g.getClass().getName()); |
175 |
} |
176 |
|
177 |
private void addCollection(GeometryCollection gc) |
178 |
{ |
179 |
for (int i = 0; i < gc.getNumGeometries(); i++) { |
180 |
Geometry g = gc.getGeometryN(i); |
181 |
add(g); |
182 |
} |
183 |
} |
184 |
/**
|
185 |
* Add a Point to the graph.
|
186 |
*/
|
187 |
private void addPoint(Point p) |
188 |
{ |
189 |
Coordinate coord = p.getCoordinate(); |
190 |
insertPoint(argIndex, coord, Location.INTERIOR); |
191 |
} |
192 |
/**
|
193 |
* The left and right topological location arguments assume that the ring is oriented CW.
|
194 |
* If the ring is in the opposite orientation,
|
195 |
* the left and right locations must be interchanged.
|
196 |
*/
|
197 |
private void addPolygonRing(LinearRing lr, int cwLeft, int cwRight) |
198 |
{ |
199 |
Coordinate[] coord = CoordinateArrays.removeRepeatedPoints(lr.getCoordinates());
|
200 |
|
201 |
if (coord.length < 4) { |
202 |
hasTooFewPoints = true;
|
203 |
invalidPoint = coord[0];
|
204 |
return;
|
205 |
} |
206 |
|
207 |
int left = cwLeft;
|
208 |
int right = cwRight;
|
209 |
if (cga.isCCW(coord)) {
|
210 |
left = cwRight; |
211 |
right = cwLeft; |
212 |
} |
213 |
Edge e = new Edge(coord,
|
214 |
new Label(argIndex, Location.BOUNDARY, left, right)); |
215 |
lineEdgeMap.put(lr, e); |
216 |
|
217 |
insertEdge(e); |
218 |
// insert the endpoint as a node, to mark that it is on the boundary
|
219 |
insertPoint(argIndex, coord[0], Location.BOUNDARY);
|
220 |
} |
221 |
|
222 |
private void addPolygon(Polygon p) |
223 |
{ |
224 |
addPolygonRing( |
225 |
(LinearRing) p.getExteriorRing(), |
226 |
Location.EXTERIOR, |
227 |
Location.INTERIOR); |
228 |
|
229 |
for (int i = 0; i < p.getNumInteriorRing(); i++) { |
230 |
// Holes are topologically labelled opposite to the shell, since
|
231 |
// the interior of the polygon lies on their opposite side
|
232 |
// (on the left, if the hole is oriented CW)
|
233 |
addPolygonRing( |
234 |
(LinearRing) p.getInteriorRingN(i), |
235 |
Location.INTERIOR, |
236 |
Location.EXTERIOR); |
237 |
} |
238 |
} |
239 |
|
240 |
private void addLineString(LineString line) |
241 |
{ |
242 |
Coordinate[] coord = CoordinateArrays.removeRepeatedPoints(line.getCoordinates());
|
243 |
|
244 |
if (coord.length < 2) { |
245 |
hasTooFewPoints = true;
|
246 |
invalidPoint = coord[0];
|
247 |
return;
|
248 |
} |
249 |
|
250 |
// add the edge for the LineString
|
251 |
// line edges do not have locations for their left and right sides
|
252 |
Edge e = new Edge(coord, new Label(argIndex, Location.INTERIOR)); |
253 |
lineEdgeMap.put(line, e); |
254 |
insertEdge(e); |
255 |
/**
|
256 |
* Add the boundary points of the LineString, if any.
|
257 |
* Even if the LineString is closed, add both points as if they were endpoints.
|
258 |
* This allows for the case that the node already exists and is a boundary point.
|
259 |
*/
|
260 |
Assert.isTrue(coord.length >= 2, "found LineString with single point"); |
261 |
insertBoundaryPoint(argIndex, coord[0]);
|
262 |
insertBoundaryPoint(argIndex, coord[coord.length - 1]);
|
263 |
|
264 |
} |
265 |
|
266 |
/**
|
267 |
* Add an Edge computed externally. The label on the Edge is assumed
|
268 |
* to be correct.
|
269 |
*/
|
270 |
public void addEdge(Edge e) |
271 |
{ |
272 |
insertEdge(e); |
273 |
Coordinate[] coord = e.getCoordinates();
|
274 |
// insert the endpoint as a node, to mark that it is on the boundary
|
275 |
insertPoint(argIndex, coord[0], Location.BOUNDARY);
|
276 |
insertPoint(argIndex, coord[coord.length - 1], Location.BOUNDARY);
|
277 |
} |
278 |
|
279 |
/**
|
280 |
* Add a point computed externally. The point is assumed to be a
|
281 |
* Point Geometry part, which has a location of INTERIOR.
|
282 |
*/
|
283 |
public void addPoint(Coordinate pt) |
284 |
{ |
285 |
insertPoint(argIndex, pt, Location.INTERIOR); |
286 |
} |
287 |
|
288 |
/**
|
289 |
* Compute self-nodes, taking advantage of the Geometry type to
|
290 |
* minimize the number of intersection tests. (E.g. rings are
|
291 |
* not tested for self-intersection, since they are assumed to be valid).
|
292 |
* @param li the LineIntersector to use
|
293 |
* @param computeRingSelfNodes if <false>, intersection checks are optimized to not test rings for self-intersection
|
294 |
* @return the SegmentIntersector used, containing information about the intersections found
|
295 |
*/
|
296 |
public SegmentIntersector computeSelfNodes(LineIntersector li, boolean computeRingSelfNodes) |
297 |
{ |
298 |
SegmentIntersector si = new SegmentIntersector(li, true, false); |
299 |
EdgeSetIntersector esi = createEdgeSetIntersector(); |
300 |
// optimized test for Polygons and Rings
|
301 |
if (! computeRingSelfNodes
|
302 |
&& (parentGeom instanceof LinearRing
|
303 |
|| parentGeom instanceof Polygon |
304 |
|| parentGeom instanceof MultiPolygon)) {
|
305 |
esi.computeIntersections(edges, si, false);
|
306 |
} |
307 |
else {
|
308 |
esi.computeIntersections(edges, si, true);
|
309 |
} |
310 |
// System.out.println("SegmentIntersector # tests = " + si.numTests);
|
311 |
addSelfIntersectionNodes(argIndex); |
312 |
return si;
|
313 |
} |
314 |
|
315 |
/* NOT USED
|
316 |
public SegmentIntersector computeSelfNodes(LineIntersector li)
|
317 |
{
|
318 |
return computeSelfNodes(li, false);
|
319 |
}
|
320 |
*/
|
321 |
public SegmentIntersector computeEdgeIntersections(
|
322 |
GeometryGraph g, |
323 |
LineIntersector li, |
324 |
boolean includeProper)
|
325 |
{ |
326 |
SegmentIntersector si = new SegmentIntersector(li, includeProper, true); |
327 |
si.setBoundaryNodes(this.getBoundaryNodes(), g.getBoundaryNodes());
|
328 |
|
329 |
EdgeSetIntersector esi = createEdgeSetIntersector(); |
330 |
esi.computeIntersections(edges, g.edges, si); |
331 |
/*
|
332 |
for (Iterator i = g.edges.iterator(); i.hasNext();) {
|
333 |
Edge e = (Edge) i.next();
|
334 |
Debug.print(e.getEdgeIntersectionList());
|
335 |
}
|
336 |
*/
|
337 |
return si;
|
338 |
} |
339 |
|
340 |
private void insertPoint(int argIndex, Coordinate coord, int onLocation) |
341 |
{ |
342 |
Node n = nodes.addNode(coord); |
343 |
Label lbl = n.getLabel();
|
344 |
if (lbl == null) { |
345 |
n.label = new Label(argIndex, onLocation); |
346 |
} |
347 |
else
|
348 |
lbl.setLocation(argIndex, onLocation); |
349 |
} |
350 |
|
351 |
/**
|
352 |
* Adds points using the mod-2 rule of SFS. This is used to add the boundary
|
353 |
* points of dim-1 geometries (Curves/MultiCurves). According to the SFS,
|
354 |
* an endpoint of a Curve is on the boundary
|
355 |
* iff if it is in the boundaries of an odd number of Geometries
|
356 |
*/
|
357 |
private void insertBoundaryPoint(int argIndex, Coordinate coord) |
358 |
{ |
359 |
Node n = nodes.addNode(coord); |
360 |
Label lbl = n.getLabel();
|
361 |
// the new point to insert is on a boundary
|
362 |
int boundaryCount = 1; |
363 |
// determine the current location for the point (if any)
|
364 |
int loc = Location.NONE;
|
365 |
if (lbl != null) loc = lbl.getLocation(argIndex, Position.ON); |
366 |
if (loc == Location.BOUNDARY) boundaryCount++;
|
367 |
|
368 |
// determine the boundary status of the point according to the Boundary Determination Rule
|
369 |
int newLoc = determineBoundary(boundaryCount);
|
370 |
lbl.setLocation(argIndex, newLoc); |
371 |
} |
372 |
|
373 |
private void addSelfIntersectionNodes(int argIndex) |
374 |
{ |
375 |
for (Iterator i = edges.iterator(); i.hasNext(); ) { |
376 |
Edge e = (Edge) i.next(); |
377 |
int eLoc = e.getLabel().getLocation(argIndex);
|
378 |
for (Iterator eiIt = e.eiList.iterator(); eiIt.hasNext(); ) { |
379 |
EdgeIntersection ei = (EdgeIntersection) eiIt.next(); |
380 |
addSelfIntersectionNode(argIndex, ei.coord, eLoc); |
381 |
} |
382 |
} |
383 |
} |
384 |
/**
|
385 |
* Add a node for a self-intersection.
|
386 |
* If the node is a potential boundary node (e.g. came from an edge which
|
387 |
* is a boundary) then insert it as a potential boundary node.
|
388 |
* Otherwise, just add it as a regular node.
|
389 |
*/
|
390 |
private void addSelfIntersectionNode(int argIndex, Coordinate coord, int loc) |
391 |
{ |
392 |
// if this node is already a boundary node, don't change it
|
393 |
if (isBoundaryNode(argIndex, coord)) return; |
394 |
if (loc == Location.BOUNDARY && useBoundaryDeterminationRule)
|
395 |
insertBoundaryPoint(argIndex, coord); |
396 |
else
|
397 |
insertPoint(argIndex, coord, loc); |
398 |
} |
399 |
|
400 |
|
401 |
} |