Statistics
| Revision:

root / trunk / libraries / libFMap / src / com / iver / cit / gvsig / fmap / topology / SnappingOverlayOperation.java @ 7762

History | View | Annotate | Download (18 KB)

1
/*
2
 * Created on 12-sep-2006 by azabala
3
 *
4
 */
5
package com.iver.cit.gvsig.fmap.topology;
6

    
7
import java.util.ArrayList;
8
import java.util.Iterator;
9
import java.util.List;
10

    
11
import com.iver.cit.gvsig.fmap.topology.geomgraph.SnappingPlanarGraph;
12
import com.vividsolutions.jts.algorithm.LineIntersector;
13
import com.vividsolutions.jts.algorithm.PointLocator;
14
import com.vividsolutions.jts.algorithm.RobustLineIntersector;
15
import com.vividsolutions.jts.geom.Coordinate;
16
import com.vividsolutions.jts.geom.Geometry;
17
import com.vividsolutions.jts.geom.GeometryFactory;
18
import com.vividsolutions.jts.geom.Location;
19
import com.vividsolutions.jts.geomgraph.Depth;
20
import com.vividsolutions.jts.geomgraph.DirectedEdge;
21
import com.vividsolutions.jts.geomgraph.DirectedEdgeStar;
22
import com.vividsolutions.jts.geomgraph.Edge;
23
import com.vividsolutions.jts.geomgraph.EdgeList;
24
import com.vividsolutions.jts.geomgraph.Label;
25
import com.vividsolutions.jts.geomgraph.Node;
26
import com.vividsolutions.jts.geomgraph.PlanarGraph;
27
import com.vividsolutions.jts.geomgraph.Position;
28
import com.vividsolutions.jts.geomgraph.SnappingGeometryGraph;
29
import com.vividsolutions.jts.io.ParseException;
30
import com.vividsolutions.jts.operation.overlay.LineBuilder;
31
import com.vividsolutions.jts.operation.overlay.OverlayNodeFactory;
32
import com.vividsolutions.jts.operation.overlay.OverlayOp;
33
import com.vividsolutions.jts.operation.overlay.PointBuilder;
34
import com.vividsolutions.jts.operation.overlay.PolygonBuilder;
35
import com.vividsolutions.jts.util.Assert;
36

    
37
/**
38
 */
39
public class SnappingOverlayOperation extends OverlayOp {
40
        
41
        private final PointLocator ptLocator = new PointLocator();
42
        private GeometryFactory geomFact;
43
        private Geometry resultGeom;
44

    
45
        protected final LineIntersector li;
46
        
47
        
48
        /*Planar graph of the overlay operation*/
49
        private SnappingPlanarGraph graph;
50
        
51
        /*Geometry graph of each individual geometry*/
52
        private SnappingGeometryGraph[] arg;
53

    
54
        
55
        /*It saves all the new edges resulting from intersections of
56
         * edges of geometries A and B. It is a temporal repository, before
57
         * to save them in SnappingPlanarGraph*/
58
        private EdgeList edgeList = new EdgeList();
59

    
60
        
61
/*
62
 * El resultado de una operacion de overlay puede contener
63
 * puntos, lineas y poligonos.
64
 * */        
65
        private List resultPolyList = new ArrayList();
66

    
67
        private List resultLineList = new ArrayList();
68

    
69
        private List resultPointList = new ArrayList();
70
        
71

    
72
        
73
        public static Geometry overlayOp(Geometry geom0, Geometry geom1,
74
                        int opCode, double tolerance) {
75
                SnappingOverlayOperation gov = new SnappingOverlayOperation(geom0,
76
                                geom1, tolerance);
77
                Geometry geomOv = gov.getResultGeometry(opCode);
78
                return geomOv;
79
        }
80
        
81

    
82
        
83
        public SnappingOverlayOperation(Geometry g0, Geometry g1, double tolerance) {
84
                super(g0, g1);
85
                graph = new SnappingPlanarGraph(new OverlayNodeFactory(), tolerance);
86
                arg = new SnappingGeometryGraph[2];
87
                arg[0] = new SnappingGeometryGraph(tolerance, 0, g0);
88
                arg[1] = new SnappingGeometryGraph(tolerance, 1, g1);
89
                geomFact = g0.getFactory();
90
                li = new SnapLineIntersector(tolerance);
91
        }
92

    
93
        public Geometry getResultGeometry(int funcCode) {
94
                computeOverlay(funcCode);
95
                return resultGeom;
96
        }
97

    
98
        
99
        public PlanarGraph getGraph() {
100
                return graph;
101
        }
102

    
103
        
104
        /*
105
         * ************************************************************
106
         * METODO PRINCIPAL
107
         * ************************************************************
108
         * */
109
        private void computeOverlay(int opCode) {
110
                
111
                /*
112
                 * Se copian los NODOS de las dos geometrias. 
113
                 * ESTO ES IMPORTANTE, PUES:
114
                 * a) un punto origina un nodo.
115
                 * b) una linea origina dos nodos
116
                 * c) un poligono origina un nodo.
117
                 * 
118
                 * */
119
                copyPoints(0);
120
                copyPoints(1);
121
                
122
                graph.dumpNodes();
123

    
124
                /*
125
                 * Empieza a ver las intersecciones que existen entre los 
126
                 * Edges de las geometrias de entrada (por ejemplo, en una
127
                 * multigeometria, entre sus elementos)
128
                 * 
129
                 * TODO Cambiar Edge, EdgeIntersectionList para que aplique
130
                 * snapping
131
                 * 
132
                 * */
133
                arg[0].computeSelfNodes(li, false);
134
                arg[1].computeSelfNodes(li, false);
135

    
136
                
137
                /*
138
                 * Calcula las intersecciones.
139
                 * Se supone que daran lugar a Nodes, ?NO?
140
                 * Como resultado, cada Edge guardar? en sus EdgeIntersectionList
141
                 * las intersecciones que hay en sus segmentos (EdgeIntersection).
142
                 * 
143
                 * Estas intersecciones se representan por:
144
                 * -segmento del edge en que ocurren.
145
                 * -coordenada
146
                 * -distancia al primer vertice del segmento
147
                 * 
148
                 * ?OJO? COMO RESULTADO DE ESTO NO SE GENERAN EJES NUEVOS.
149
                 * PARA HACER SNAP EN LAS INTERSECCIONES TENDRIAMOS QUE RETOCAR LA 
150
                 * CLASE EDGEINTERSECTIONLIST
151
                 * 
152
                 * */
153
                arg[0].computeEdgeIntersections(arg[1], li, true);
154
                /*
155
                 * Ahora lo que se hace es: para cada Edge del grafo,
156
                 * se parte (en funci?n de sus intersecciones) y se a?aden
157
                 * los Edges fragmentados a la colecci?n baseSplitEdges
158
                 * 
159
                 * */
160
                
161
                List baseSplitEdges = new ArrayList();
162
                arg[0].computeSplitEdges(baseSplitEdges);
163
                arg[1].computeSplitEdges(baseSplitEdges);
164
                
165
                
166
                /*
167
                 * Edges resulting of A intersection B, that are in baseSplitEdges 
168
                 * Collection, are saved in EdgeList.
169
                 * ?OJO? Si aparecen ejes repetidos, no se duplican (pero si 
170
                 * que se cambia su etiqueta)
171
                 * */
172
                //Se copian los nuevos Edges generados en EdgeList
173
                insertUniqueEdges(baseSplitEdges);
174
                
175
                
176
                /*Se etiquetan*/
177
                computeLabelsFromDepths();
178
                
179
                /*
180
                 * Quita los Edges que hayan sufrido colapso dimensional
181
                 * (en la documentac?on de JTS viene algo de esto)
182
                 * */
183
                replaceCollapsedEdges();
184

    
185
                
186
        /*
187
         * Finalmente, se a?ade al SnappingPlanarGraph resultado los Edges
188
         * calculados como fruto de las intersecciones (contenidos en EdgeList).
189
         * 
190
         * Aqu? se hace algo muy importante tambi?n: se a?aden nuevos nodos
191
         * al grafo (correspondientes con los extremos de los nuevos Edge
192
         * que no estuvieran ya en el grafo)
193
         * 
194
         * */
195
                graph.addEdges(edgeList.getEdges());
196
                
197
                
198
                computeLabelling();
199
                labelIncompleteNodes();
200
                
201
                /**
202
                 * The ordering of building the result Geometries is important. Areas
203
                 * must be built before lines, which must be built before points. This
204
                 * is so that lines which are covered by areas are not included
205
                 * explicitly, and similarly for points.
206
                 */
207
                findResultAreaEdges(opCode);
208
                cancelDuplicateResultEdges();
209
                
210
                
211
                
212
                PolygonBuilder polyBuilder = new PolygonBuilder(geomFact, cga);
213
                polyBuilder.add(graph);
214
                resultPolyList = polyBuilder.getPolygons();
215

    
216
                LineBuilder lineBuilder = new LineBuilder(this, geomFact, ptLocator);
217
                resultLineList = lineBuilder.build(opCode);
218

    
219
                PointBuilder pointBuilder = new PointBuilder(this, geomFact, ptLocator);
220
                resultPointList = pointBuilder.build(opCode);
221

    
222
                // gather the results from all calculations into a single Geometry for
223
                // the result set
224
                resultGeom = computeGeometry(resultPointList, resultLineList,
225
                                resultPolyList);
226
        }
227

    
228
        private void insertUniqueEdges(List edges) {
229
                for (Iterator i = edges.iterator(); i.hasNext();) {
230
                        Edge e = (Edge) i.next();
231
                        insertUniqueEdge(e);
232
                }
233
        }
234

    
235
        /**
236
         * Insert an edge from one of the noded input graphs. Checks edges that are
237
         * inserted to see if an identical edge already exists. If so, the edge is
238
         * not inserted, but its label is merged with the existing edge.
239
         */
240
        protected void insertUniqueEdge(Edge e) {
241
                
242
                //TODO Crear una clase SnapEdge y SnapEdgeList puede ser necesario???
243
                //creo que si pq SnapEdgeList mantiene una cache que no considera snap
244
                Edge existingEdge = edgeList.findEqualEdge(e);
245
                // If an identical edge already exists, simply update its label
246
                if (existingEdge != null) {
247
                        Label existingLabel = existingEdge.getLabel();
248
                        Label labelToMerge = e.getLabel();
249
                        
250
                        // check if new edge is in reverse direction to existing edge
251
                        // if so, must flip the label before merging it
252
                        if (!existingEdge.isPointwiseEqual(e)) {
253
                                labelToMerge = new Label(e.getLabel());
254
                                labelToMerge.flip();
255
                        }
256
                        Depth depth = existingEdge.getDepth();
257
                        // if this is the first duplicate found for this edge, initialize
258
                        // the depths
259
                        if (depth.isNull()) {
260
                                depth.add(existingLabel);
261
                        }
262
                        depth.add(labelToMerge);
263
                        existingLabel.merge(labelToMerge);
264
                } else { // no matching existing edge was found
265
                        // add this new edge to the list of edges in this graph
266
                        edgeList.add(e);
267
                }
268
        }
269

    
270
        
271
        /**
272
         * Update the labels for edges according to their depths. For each edge, the
273
         * depths are first normalized. Then, if the depths for the edge are equal,
274
         * this edge must have collapsed into a line edge. If the depths are not
275
         * equal, update the label with the locations corresponding to the depths
276
         * (i.e. a depth of 0 corresponds to a Location of EXTERIOR, a depth of 1
277
         * corresponds to INTERIOR)
278
         */
279
        private void computeLabelsFromDepths() {
280
                for (Iterator it = edgeList.iterator(); it.hasNext();) {
281
                        Edge e = (Edge) it.next();
282
                        Label lbl = e.getLabel();
283
                        Depth depth = e.getDepth();
284
                        /*
285
                         * Only check edges for which there were duplicates, since these are
286
                         * the only ones which might be the result of dimensional collapses.
287
                         */
288
                        if (!depth.isNull()) {
289
                                depth.normalize();
290
                                for (int i = 0; i < 2; i++) {
291
                                        if (!lbl.isNull(i) && lbl.isArea() && !depth.isNull(i)) {
292
                                                /**
293
                                                 * if the depths are equal, this edge is the result of
294
                                                 * the dimensional collapse of two or more edges. It has
295
                                                 * the same location on both sides of the edge, so it
296
                                                 * has collapsed to a line.
297
                                                 */
298
                                                if (depth.getDelta(i) == 0) {
299
                                                        lbl.toLine(i);
300
                                                } else {
301
                                                        /**
302
                                                         * This edge may be the result of a dimensional
303
                                                         * collapse, but it still has different locations on
304
                                                         * both sides. The label of the edge must be updated
305
                                                         * to reflect the resultant side locations indicated
306
                                                         * by the depth values.
307
                                                         */
308
                                                        Assert
309
                                                                        .isTrue(!depth.isNull(i, Position.LEFT),
310
                                                                                        "depth of LEFT side has not been initialized");
311
                                                        lbl.setLocation(i, Position.LEFT, depth
312
                                                                        .getLocation(i, Position.LEFT));
313
                                                        Assert
314
                                                                        .isTrue(!depth.isNull(i, Position.RIGHT),
315
                                                                                        "depth of RIGHT side has not been initialized");
316
                                                        lbl.setLocation(i, Position.RIGHT, depth
317
                                                                        .getLocation(i, Position.RIGHT));
318
                                                }
319
                                        }
320
                                }
321
                        }
322
                }
323
        }
324

    
325
        /**
326
         * If edges which have undergone dimensional collapse are found, replace
327
         * them with a new edge which is a L edge
328
         */
329
        private void replaceCollapsedEdges() {
330
                List newEdges = new ArrayList();
331
                for (Iterator it = edgeList.iterator(); it.hasNext();) {
332
                        Edge e = (Edge) it.next();
333
                        if (e.isCollapsed()) {
334
                                //        Debug.print(e);
335
                                it.remove();
336
                                newEdges.add(e.getCollapsedEdge());
337
                        }
338
                }
339
                edgeList.addAll(newEdges);
340
        }
341

    
342
        /**
343
         * Copy all nodes from an arg geometry into this graph. The node label in
344
         * the arg geometry overrides any previously computed label for that
345
         * argIndex. (E.g. a node may be an intersection node with a previously
346
         * computed label of BOUNDARY, but in the original arg Geometry it is
347
         * actually in the interior due to the Boundary Determination Rule)
348
         */
349
        private void copyPoints(int argIndex) {
350
                for (Iterator i = arg[argIndex].getNodeIterator(); i.hasNext();) {
351
                        Node graphNode = (Node) i.next();
352
                        Node newNode = graph.addNode(graphNode.getCoordinate());
353
                        newNode.setLabel(argIndex, graphNode.getLabel().getLocation(
354
                                        argIndex));
355
                }
356
        }
357

    
358
        /**
359
         * Compute initial labelling for all DirectedEdges at each node. In this
360
         * step, DirectedEdges will acquire a complete labelling (i.e. one with
361
         * labels for both Geometries) only if they are incident on a node which has
362
         * edges for both Geometries
363
         */
364
        private void computeLabelling() {
365
                for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext();) {
366
                        Node node = (Node) nodeit.next();
367
                        node.getEdges().computeLabelling(arg);
368
                }
369
                mergeSymLabels();
370
                updateNodeLabelling();
371
        }
372

    
373
        /**
374
         * For nodes which have edges from only one Geometry incident on them, the
375
         * previous step will have left their dirEdges with no labelling for the
376
         * other Geometry. However, the sym dirEdge may have a labelling for the
377
         * other Geometry, so merge the two labels.
378
         */
379
        private void mergeSymLabels() {
380
                for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext();) {
381
                        Node node = (Node) nodeit.next();
382
                        ((DirectedEdgeStar) node.getEdges()).mergeSymLabels();
383
                        //        node.print(System.out);
384
                }
385
        }
386

    
387
        private void updateNodeLabelling() {
388
                // update the labels for nodes
389
                // The label for a node is updated from the edges incident on it
390
                // (Note that a node may have already been labelled
391
                // because it is a point in one of the input geometries)
392
                for (Iterator nodeit = graph.getNodes().iterator(); nodeit.hasNext();) {
393
                        Node node = (Node) nodeit.next();
394
                        Label lbl = ((DirectedEdgeStar) node.getEdges()).getLabel();
395
                        node.getLabel().merge(lbl);
396
                }
397
        }
398

    
399
        /**
400
         * Incomplete nodes are nodes whose labels are incomplete. (e.g. the
401
         * location for one Geometry is null). These are either isolated nodes, or
402
         * nodes which have edges from only a single Geometry incident on them.
403
         * 
404
         * Isolated nodes are found because nodes in one graph which don't intersect
405
         * nodes in the other are not completely labelled by the initial process of
406
         * adding nodes to the nodeList. To complete the labelling we need to check
407
         * for nodes that lie in the interior of edges, and in the interior of
408
         * areas.
409
         * <p>
410
         * When each node labelling is completed, the labelling of the incident
411
         * edges is updated, to complete their labelling as well.
412
         */
413
        private void labelIncompleteNodes() {
414
                for (Iterator ni = graph.getNodes().iterator(); ni.hasNext();) {
415
                        Node n = (Node) ni.next();
416
                        Label label = n.getLabel();
417
                        if (n.isIsolated()) {
418
                                if (label.isNull(0))
419
                                        labelIncompleteNode(n, 0);
420
                                else
421
                                        labelIncompleteNode(n, 1);
422
                        }
423
                        // now update the labelling for the DirectedEdges incident on this
424
                        // node
425
                        ((DirectedEdgeStar) n.getEdges()).updateLabelling(label);
426
                        //        n.print(System.out);
427
                }
428
        }
429

    
430
        /**
431
         * Label an isolated node with its relationship to the target geometry.
432
         */
433
        private void labelIncompleteNode(Node n, int targetIndex) {
434
                int loc = ptLocator.locate(n.getCoordinate(), arg[targetIndex]
435
                                .getGeometry());
436
                n.getLabel().setLocation(targetIndex, loc);
437
        }
438

    
439
        /**
440
         * Find all edges whose label indicates that they are in the result area(s),
441
         * according to the operation being performed. Since we want polygon shells
442
         * to be oriented CW, choose dirEdges with the interior of the result on the
443
         * RHS. Mark them as being in the result. Interior Area edges are the result
444
         * of dimensional collapses. They do not form part of the result area
445
         * boundary.
446
         */
447
        private void findResultAreaEdges(int opCode) {
448
                for (Iterator it = graph.getEdgeEnds().iterator(); it.hasNext();) {
449
                        DirectedEdge de = (DirectedEdge) it.next();
450
                        // mark all dirEdges with the appropriate label
451
                        Label label = de.getLabel();
452
                        if (label.isArea()
453
                                        && !de.isInteriorAreaEdge()
454
                                        && isResultOfOp(label.getLocation(0, Position.RIGHT), label
455
                                                        .getLocation(1, Position.RIGHT), opCode)) {
456
                                de.setInResult(true);
457
                                //        Debug.print("in result "); Debug.println(de);
458
                        }
459
                }
460
        }
461

    
462
        /**
463
         * If both a dirEdge and its sym are marked as being in the result, cancel
464
         * them out.
465
         */
466
        private void cancelDuplicateResultEdges() {
467
                // remove any dirEdges whose sym is also included
468
                // (they "cancel each other out")
469
                for (Iterator it = graph.getEdgeEnds().iterator(); it.hasNext();) {
470
                        DirectedEdge de = (DirectedEdge) it.next();
471
                        DirectedEdge sym = de.getSym();
472
                        if (de.isInResult() && sym.isInResult()) {
473
                                de.setInResult(false);
474
                                sym.setInResult(false);
475
                                //        Debug.print("cancelled "); Debug.println(de);
476
                                // Debug.println(sym);
477
                        }
478
                }
479
        }
480

    
481
        /**
482
         * This method is used to decide if a point node should be included in the
483
         * result or not.
484
         * 
485
         * @return true if the coord point is covered by a result Line or Area
486
         *         geometry
487
         */
488
        public boolean isCoveredByLA(Coordinate coord) {
489
                if (isCovered(coord, resultLineList))
490
                        return true;
491
                if (isCovered(coord, resultPolyList))
492
                        return true;
493
                return false;
494
        }
495

    
496
        /**
497
         * This method is used to decide if an L edge should be included in the
498
         * result or not.
499
         * 
500
         * @return true if the coord point is covered by a result Area geometry
501
         */
502
        public boolean isCoveredByA(Coordinate coord) {
503
                if (isCovered(coord, resultPolyList))
504
                        return true;
505
                return false;
506
        }
507

    
508
        /**
509
         * @return true if the coord is located in the interior or boundary of a
510
         *         geometry in the list.
511
         */
512
        private boolean isCovered(Coordinate coord, List geomList) {
513
                for (Iterator it = geomList.iterator(); it.hasNext();) {
514
                        Geometry geom = (Geometry) it.next();
515
                        int loc = ptLocator.locate(coord, geom);
516
                        if (loc != Location.EXTERIOR)
517
                                return true;
518
                }
519
                return false;
520
        }
521

    
522
        private Geometry computeGeometry(List resultPointList, List resultLineList,
523
                        List resultPolyList) {
524
                List geomList = new ArrayList();
525
                // element geometries of the result are always in the order P,L,A
526
                geomList.addAll(resultPointList);
527
                geomList.addAll(resultLineList);
528
                geomList.addAll(resultPolyList);
529
                // build the most specific geometry possible
530
                return geomFact.buildGeometry(geomList);
531
        }
532
        
533
        
534
        public static void main(String[] args){
535
                GeometryFactory factory = new GeometryFactory();
536
                com.vividsolutions.jts.io.WKTReader reader = new com.vividsolutions.jts.io.WKTReader(factory);
537
                Geometry a, b;
538
                try {
539
//                        a = reader.read("POLYGON ((7.008775237210479 383.60542879370206, 10.016604662315718 383.60542879370206 , 10.016604662315718 386.67903531618094, 7.008775237210479 386.67903531618094, 7.008775237210479 383.60542879370206))");
540
//                        Geometry b = reader.read("POLYGON ((13.024434087420959 383.60542879370206, 12.655055089709187 383.60542879370206, 7.008775237210479 389.375185029807 , 7.008775237210479 389.75264183865977, 13.024434087420959 383.60542879370206))");
541
                        
542
                        //TODO METER ESTO EN JUNIT
543
                        
544
                        a = reader.read("LINESTRING(0.001 0.001, 5.001 5.001)");
545
                        b = reader.read("LINESTRING(2.1 -3, 0.0 -0.001, -2.22 4.88, 10.0 10.0, 5.002 5.002)");
546
                        
547
                        
548
                        
549
                        System.out.println(SnappingOverlayOperation.overlayOp(a, b, OverlayOp.INTERSECTION, 0.01));
550
                } catch (ParseException e) {
551
                        e.printStackTrace();
552
                }
553
                   
554

    
555
        }
556

    
557
}