Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.plugin / org.gvsig.editing.app / org.gvsig.editing.app.mainplugin / src / main / java / org / gvsig / editing / gui / cad / tools / split / SplitStrategy.java @ 40557

History | View | Annotate | Download (22.4 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24

    
25
/* Spatial Operations & Editing Tools for uDig
26
 * 
27
 * Axios Engineering under a funding contract with: 
28
 *      Diputaci?n Foral de Gipuzkoa, Ordenaci?n Territorial 
29
 *
30
 *      http://b5m.gipuzkoa.net
31
 *      http://www.axios.es 
32
 *
33
 * (C) 2006, Diputaci?n Foral de Gipuzkoa, Ordenaci?n Territorial (DFG-OT). 
34
 * DFG-OT agrees to licence under Lesser General Public License (LGPL).
35
 * 
36
 * You can redistribute it and/or modify it under the terms of the 
37
 * GNU Lesser General Public License as published by the Free Software 
38
 * Foundation; version 2.1 of the License.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 */
45

    
46

    
47
/*
48
 * Created on 10-abr-2006
49
 *
50
 * gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
51
 *
52
 * Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
53
 *
54
 * This program is free software; you can redistribute it and/or
55
 * modify it under the terms of the GNU General Public License
56
 * as published by the Free Software Foundation; either version 2
57
 * of the License, or (at your option) any later version.
58
 *
59
 * This program is distributed in the hope that it will be useful,
60
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
61
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
62
 * GNU General Public License for more details.
63
 *
64
 * You should have received a copy of the GNU General Public License
65
 * along with this program; if not, write to the Free Software
66
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
67
 *
68
 * For more information, contact:
69
 *
70
 *  Generalitat Valenciana
71
 *   Conselleria d'Infraestructures i Transport
72
 *   Av. Blasco Ib??ez, 50
73
 *   46010 VALENCIA
74
 *   SPAIN
75
 *
76
 *      +34 963862235
77
 *   gvsig@gva.es
78
 *      www.gvsig.gva.es
79
 *
80
 *    or
81
 *
82
 *   IVER T.I. S.A
83
 *   Salamanca 50
84
 *   46005 Valencia
85
 *   Spain
86
 *
87
 *   +34 963163400
88
 *   dac@iver.es
89
 */
90
/* CVS MESSAGES:
91
*
92
* $Id: 
93
* $Log: 
94
*/
95
package org.gvsig.editing.gui.cad.tools.split;
96

    
97
import java.util.ArrayList;
98
import java.util.Collections;
99
import java.util.HashMap;
100
import java.util.Iterator;
101
import java.util.List;
102
import java.util.Map;
103

    
104
import com.vividsolutions.jts.algorithm.CGAlgorithms;
105
import com.vividsolutions.jts.geom.Coordinate;
106
import com.vividsolutions.jts.geom.CoordinateArrays;
107
import com.vividsolutions.jts.geom.Geometry;
108
import com.vividsolutions.jts.geom.GeometryCollection;
109
import com.vividsolutions.jts.geom.GeometryFactory;
110
import com.vividsolutions.jts.geom.LineString;
111
import com.vividsolutions.jts.geom.LinearRing;
112
import com.vividsolutions.jts.geom.Location;
113
import com.vividsolutions.jts.geom.MultiLineString;
114
import com.vividsolutions.jts.geom.MultiPolygon;
115
import com.vividsolutions.jts.geom.Polygon;
116
import com.vividsolutions.jts.geomgraph.DirectedEdge;
117
import com.vividsolutions.jts.geomgraph.Node;
118

    
119

    
120
/**
121
 * This class is based in the AXIOS team work for UDIG project.
122
 * 
123
 * We have adapted it slightly for the gvsig project
124
 * (exchange the I18 system, etc)
125
 * 
126
 * @author Alvaro Zabala
127
 *
128
 */
129

    
130
/*
131
 * Performs a split of a LineString, MultiLineString, Polygon or MultiPolygon using a provided
132
 * LineString as cutting edge.
133
 * 
134
 * @author Gabriel Rold?n, Axios Engineering
135
 * @author Mauricio Pazos, Axios Engineering
136
 * @since 1.1.0
137
 */
138
public class SplitStrategy {
139

    
140
    private final LineString splittingLine;
141

    
142
    private static final Map /* <Class, Class<? extends SpecificSplitOp> */strategies;
143
    static {
144
        Map knownStrategies = new HashMap();
145
        knownStrategies.put(LineString.class, LineStringSplitter.class);
146
        knownStrategies.put(MultiLineString.class, MultiLineStringSplitter.class);
147
        knownStrategies.put(Polygon.class, PolygonSplitter.class);
148
        knownStrategies.put(MultiPolygon.class, MultiPolygonSplitter.class);
149

    
150
        strategies = Collections.unmodifiableMap(knownStrategies);
151
    }
152

    
153
    public SplitStrategy( final LineString splittingLine ) {
154
        if (splittingLine == null) {
155
            throw new NullPointerException();
156
        }
157
        this.splittingLine = splittingLine;
158
    }
159

    
160
    
161
    public static Geometry splitOp( Geometry geom, LineString splitLine ) {
162
        SplitStrategy op = new SplitStrategy(splitLine);
163
        Geometry splittedGeometries = op.split(geom);
164
        return splittedGeometries;
165
    }
166

    
167
    
168
    /**
169
     * @param splitee
170
     * @return a <code>Geometry</code> containing all the splitted parts as aggregates. Use
171
     *         {@link Geometry#getGeometryN(int) getGeometryN(int)} to get each part.
172
     * @throws NullPointerException if geom is null
173
     * @throws IllegalArgumentException if geom is not of an acceptable geometry type to be splitted
174
     *         (i.e. not a linestring, multilinestring, polygon or multipolygon)
175
     */
176
    public Geometry split( final Geometry splitee ) {
177
        if (splitee == null) {
178
            throw new NullPointerException();
179
        }
180

    
181
        Class spliteeClass = splitee.getClass();
182
        SpecificSplitOp splitOp = findSplitOp(spliteeClass);
183

    
184
        Geometry splitResult;
185

    
186
        splitOp.setSplitter(splittingLine);
187
        splitResult = splitOp.split(splitee);
188

    
189
        return splitResult;
190
    }
191

    
192
    
193
    private SpecificSplitOp findSplitOp( Class spliteeClass ) {
194
        if (!strategies.containsKey(spliteeClass)) {
195
            throw new IllegalArgumentException("La geometria especificada no soporta la operacion 'split'");
196
        }
197

    
198
        final Class splitOpClass = (Class) strategies.get(spliteeClass);
199
        SpecificSplitOp splitOp;
200
        try {
201

    
202
            splitOp = (SpecificSplitOp) splitOpClass.newInstance();
203

    
204
        } catch (InstantiationException e) {
205
            throw new RuntimeException("Cannot instantiate " + splitOpClass.getName(), e);
206
        } catch (IllegalAccessException e) {
207
            throw (RuntimeException) new RuntimeException("Illegal access exception for "
208
                    + splitOpClass.getName()).initCause(e);
209
        }
210
        return splitOp;
211
    }
212

    
213
    /**
214
     * @author Gabriel Rold?n, Axios Engineering
215
     * @author Mauricio Pazos, Axios Engineering
216
     * @since 1.1.0
217
     */
218
    private static interface SpecificSplitOp {
219
        public void setSplitter( LineString splitter );
220
        public Geometry split( Geometry splitee );
221
    }
222

    
223
    /**
224
     * @author Gabriel Rold?n, Axios Engineering
225
     * @author Mauricio Pazos, Axios Engineering
226
     * @since 1.1.0
227
     */
228
    private static abstract class AbstractSplitter implements SpecificSplitOp {
229

    
230
        protected LineString splitter;
231

    
232
        public void setSplitter( LineString splitter ) {
233
            this.splitter = splitter;
234
        }
235
    }
236

    
237
    /**
238
     * @author Gabriel Rold?n, Axios Engineering
239
     * @author Mauricio Pazos, Axios Engineering
240
     * @since 1.1.0
241
     */
242
    private static class LineStringSplitter extends AbstractSplitter {
243
        /**
244
         * No-op default constructor required to reflectively instantiate the class
245
         */
246
        public LineStringSplitter() {
247
            // no-op
248
        }
249
        /**
250
         * @param splitee the {@link LineString} to be splitted
251
         */
252
        public Geometry split( Geometry splitee ) {
253
            Geometry splitted = ((LineString) splitee).difference(splitter);
254
            return splitted;
255
        }
256

    
257
    }
258

    
259
    /**
260
     * @author Gabriel Rold?n, Axios Engineering
261
     * @author Mauricio Pazos, Axios Engineering
262
     * @since 1.1.0
263
     */
264
    private static abstract class AbstractGeometryCollectionSplitter implements SpecificSplitOp {
265

    
266
        private SpecificSplitOp singlePartSplitter;
267

    
268
        private AbstractGeometryCollectionSplitter( SpecificSplitOp singlePartSplitter ) {
269
            this.singlePartSplitter = singlePartSplitter;
270
        }
271

    
272
        public final void setSplitter( LineString splitter ) {
273
            singlePartSplitter.setSplitter(splitter);
274
        }
275

    
276
        public final Geometry split( final Geometry splitee ) {
277
            final GeometryCollection coll = (GeometryCollection) splitee;
278
            final int numParts = coll.getNumGeometries();
279

    
280
            List splittedParts = new ArrayList();
281

    
282
            for( int partN = 0; partN < numParts; partN++ ) {
283
                Geometry simplePartN = coll.getGeometryN(partN);
284
                Geometry splittedPart = singlePartSplitter.split(simplePartN);
285
                final int splittedPartsCount = splittedPart.getNumGeometries();
286
                for( int splittedPartN = 0; splittedPartN < splittedPartsCount; splittedPartN++ ) {
287
                    Geometry simpleSplittedPart = splittedPart.getGeometryN(splittedPartN);
288
                    splittedParts.add(simpleSplittedPart);
289
                }
290
            }
291

    
292
            GeometryFactory gf = splitee.getFactory();
293
            GeometryCollection splittedCollection = buildFromParts(gf, splittedParts);
294
            return splittedCollection;
295
        }
296

    
297
        protected abstract GeometryCollection buildFromParts( GeometryFactory gf, List parts );
298

    
299
    }
300

    
301
    /**
302
     * @author Gabriel Rold?n, Axios Engineering
303
     * @author Mauricio Pazos, Axios Engineering
304
     * @since 1.1.0
305
     */
306
    private static class MultiLineStringSplitter extends AbstractGeometryCollectionSplitter {
307

    
308
        public MultiLineStringSplitter() {
309
            super(new LineStringSplitter());
310
        }
311

    
312
        @Override
313
        protected GeometryCollection buildFromParts( GeometryFactory gf, List parts ) {
314
            LineString[] lines = (LineString[]) parts.toArray(new LineString[parts.size()]);
315
            MultiLineString result = gf.createMultiLineString(lines);
316
            return result;
317
        }
318
    }
319

    
320
    /**
321
     * @author Gabriel Rold?n, Axios Engineering
322
     * @author Mauricio Pazos, Axios Engineering
323
     * @since 1.1.0
324
     */
325
    private static class MultiPolygonSplitter extends AbstractGeometryCollectionSplitter {
326

    
327
        public MultiPolygonSplitter() {
328
            super(new PolygonSplitter());
329
        }
330

    
331
        @Override
332
        protected GeometryCollection buildFromParts( GeometryFactory gf, List parts ) {
333
            Polygon[] polygons = (Polygon[]) parts.toArray(new Polygon[parts.size()]);
334
            MultiPolygon result = gf.createMultiPolygon(polygons);
335
            return result;
336
        }
337
    }
338

    
339
    /**
340
     * Estrategia:
341
     * <ul>
342
     * <li> Construir un grafo con todos los edges y nodes de la intersecci?n del pol?gono con la
343
     * l?nea
344
     * <li> Ponderar los nodos seg?n la cantidad de edges incidentes. Nodos son solo las
345
     * intersecciones del boundary del poligono con el linestring y el punto inicial de cada parte
346
     * del boundary.
347
     * <li> Ponderar los edges seg?n son shared (todos los del linestring) o non shared (todos los
348
     * del boundary del poligono). Almacenar la lista de coordenadas del edge en el edge.
349
     * <li> Comenzar a recorrer el grafo por un nodo cualquiera, empezando por su primer edge
350
     * <li> Recorrer siempre hacia el nodo siguiente, seleccionando el edge cuyo primer segmento de
351
     * su linestring presenta un menor angulo a la izquiera (CCW) con el ultimo segmento del
352
     * linestring del edge en curso.
353
     * <li> Eliminar del grafo los edges non-shared que se utilizaron
354
     * <li> Disminuir en 1 la ponderaci?n de los nodos que se utilizaron
355
     * <li> Marcar los edges restantes que tengan algun nodo con ponderaci?n < 3 como non-shared
356
     * <li> Eliminar del grafo los nodos con ponderaci?n 1
357
     * </ul>
358
     * 
359
     * @author Gabriel Rold?n, Axios Engineering
360
     * @author Mauricio Pazos, Axios Engineering
361
     * @since 1.1.0
362
     */
363
    private static class PolygonSplitter extends AbstractSplitter {
364

    
365
        /**
366
         * No-op default constructor required to reflectively instantiate the class
367
         */
368
        public PolygonSplitter() {
369
            // no-op
370
        }
371

    
372
        /**
373
         * 
374
         */
375
        public Geometry split( Geometry splitee ) {
376
            assert splitee instanceof Polygon;;
377

    
378
            final Polygon polygon = (Polygon) splitee;
379
            final Geometry splitted = splitPolygon(polygon);
380

    
381
            return splitted;
382
        }
383

    
384
        /**
385
         * 
386
         */
387
        private Geometry splitPolygon( final Polygon geom ) {
388
            SplitGraph graph = new SplitGraph(geom, splitter);
389

    
390
            final GeometryFactory gf = geom.getFactory();
391

    
392
            // sotore unsplitted holes for later addition
393
            List<LinearRing> unsplittedHoles = findUnsplittedHoles(graph, gf);
394

    
395
            List<List<SplitEdge>> allRings = findRings(graph);
396

    
397
            List<Polygon> resultingPolygons = buildSimplePolygons(allRings, unsplittedHoles, gf);
398

    
399
            Geometry result;
400
            if (resultingPolygons.size() == 1) {
401
                result = resultingPolygons.get(0);
402
            } else {
403
                Polygon[] array = resultingPolygons.toArray(new Polygon[resultingPolygons.size()]);
404
                result = gf.createMultiPolygon(array);
405
            }
406
            return result;
407
        }
408

    
409
        /**
410
         * Finds out and removes from the graph the edges that were originally holes in the polygon
411
         * and were not splitted by the splitting line.
412
         * 
413
         * @param graph
414
         * @param gf
415
         * @return
416
         */
417
        @SuppressWarnings("unchecked")
418
        private List<LinearRing> findUnsplittedHoles( SplitGraph graph, GeometryFactory gf ) {
419
            final List<LinearRing> unsplittedHoles = new ArrayList<LinearRing>(2);
420

    
421
            final List<SplitEdge> edges = new ArrayList<SplitEdge>();
422
            for( Iterator it = graph.getEdgeIterator(); it.hasNext(); ) {
423
                SplitEdge edge = (SplitEdge) it.next();
424
                edges.add(edge);
425
            }
426

    
427
            for( Iterator it = edges.iterator(); it.hasNext(); ) {
428
                SplitEdge edge = (SplitEdge) it.next();
429
                if (edge.isHoleEdge()) {
430
                    Coordinate[] coordinates = edge.getCoordinates();
431
                    Coordinate start = coordinates[0];
432
                    Coordinate end = coordinates[coordinates.length - 1];
433
                    boolean isLinearRing = start.equals2D(end);
434
                    if (isLinearRing) {
435
                        graph.remove(edge);
436
                        LinearRing ring = gf.createLinearRing(coordinates);
437
                        unsplittedHoles.add(ring);
438
                    }
439
                }
440
            }
441
            return unsplittedHoles;
442
        }
443

    
444
        private List<Polygon> buildSimplePolygons( List<List<SplitEdge>> allRings,
445
                                                   List<LinearRing> unsplittedHoles,
446
                                                   GeometryFactory gf ) {
447

    
448
            List<Polygon> polygons = new ArrayList<Polygon>(allRings.size());
449

    
450
            for( List<SplitEdge> edgeList : allRings ) {
451
                Polygon poly = buildPolygon(edgeList, gf);
452
                List<LinearRing> thisPolyHoles = new ArrayList<LinearRing>(unsplittedHoles.size());
453
                for( LinearRing holeRing : unsplittedHoles ) {
454
                    if (poly.covers(holeRing)) {
455
                        thisPolyHoles.add(holeRing);
456
                    }
457
                }
458
                unsplittedHoles.removeAll(thisPolyHoles);
459

    
460
                int numHoles = thisPolyHoles.size();
461
                if (numHoles > 0) {
462
                    LinearRing[] holes = thisPolyHoles.toArray(new LinearRing[numHoles]);
463
                    LinearRing shell = gf.createLinearRing(poly.getExteriorRing().getCoordinates());
464
                    poly = gf.createPolygon(shell, holes);
465
                }
466

    
467
                polygons.add(poly);
468
            }
469

    
470
            return polygons;
471
        }
472

    
473
        private Polygon buildPolygon( List<SplitEdge> edgeList, GeometryFactory gf ) {
474
            List<Coordinate> coords = new ArrayList<Coordinate>();
475
            Coordinate[] lastCoordinates = null;
476
            for( SplitEdge edge : edgeList ) {
477
                Coordinate[] coordinates = edge.getCoordinates();
478
                if (lastCoordinates != null) {
479
                    Coordinate endPoint = lastCoordinates[lastCoordinates.length - 1];
480
                    Coordinate startPoint = coordinates[0];
481
                    if (!endPoint.equals2D(startPoint)) {
482
                        coordinates = CoordinateArrays.copyDeep(coordinates);
483
                        CoordinateArrays.reverse(coordinates);
484
                    }
485
                }
486
                lastCoordinates = coordinates;
487
                for( int i = 0; i < coordinates.length; i++ ) {
488
                    Coordinate coord = coordinates[i];
489
                    coords.add(coord);
490
                }
491
            }
492
            Coordinate[] shellCoords = new Coordinate[coords.size()];
493
            coords.toArray(shellCoords);
494
            shellCoords = CoordinateArrays.removeRepeatedPoints(shellCoords);
495
            LinearRing shell = gf.createLinearRing(shellCoords);
496
            Polygon poly = gf.createPolygon(shell, (LinearRing[]) null);
497
            return poly;
498
        }
499

    
500
        /**
501
         * Builds a list of rings from the graph's edges
502
         * 
503
         * @param graph
504
         * @return
505
         */
506
        @SuppressWarnings("unchecked")
507
        private List<List<SplitEdge>> findRings( SplitGraph graph ) {
508
            final List<List<SplitEdge>> rings = new ArrayList<List<SplitEdge>>();
509

    
510
            DirectedEdge startEdge;
511
            // build each ring starting with the first edge belonging to the
512
            // shell found
513
            while( (startEdge = findShellEdge(graph)) != null ) {
514
                List<SplitEdge> ring = buildRing(graph, startEdge);
515
                rings.add(ring);
516
            }
517
            return rings;
518
        }
519

    
520
        private List<SplitEdge> buildRing( final SplitGraph graph, final DirectedEdge startEdge ) {
521
            // System.out.println("building ring edge list...");
522
            final List<SplitEdge> ring = new ArrayList<SplitEdge>();
523

    
524
            // follow this tessellation direction while possible,
525
            // switch to the opposite when not, and continue with
526
            // the same direction while possible.
527
            // Start travelling clockwise, as we start with a shell edge,
528
            // which is in clockwise order
529
            final int direction = CGAlgorithms.COUNTERCLOCKWISE;
530

    
531
            DirectedEdge currentDirectedEdge = startEdge;
532
            DirectedEdge nextDirectedEdge = null;
533

    
534
            while( nextDirectedEdge != startEdge ) {
535
                SplitEdge edge = (SplitEdge) currentDirectedEdge.getEdge();
536
                // System.out.println("adding " + edge);
537
                if (ring.contains(edge)) {
538
                    throw new IllegalStateException("trying to add edge twice: " + edge);
539
                }
540
                ring.add(edge);
541

    
542
                DirectedEdge sym = currentDirectedEdge.getSym();
543
                SplitGraphNode endNode = (SplitGraphNode) sym.getNode();
544
                SplitEdgeStar nodeEdges = (SplitEdgeStar) endNode.getEdges();
545
                nextDirectedEdge = nodeEdges.findClosestEdgeInDirection(sym, direction);
546

    
547
                assert nextDirectedEdge != null;
548

    
549
                currentDirectedEdge = nextDirectedEdge;
550
            }
551

    
552
            removeUnneededEdges(graph, ring);
553
            return ring;
554
        }
555

    
556
        /**
557
         * Removes from <code>graph</code> the edges in <code>ring</code> that are no more
558
         * needed
559
         * 
560
         * @param graph
561
         * @param ring
562
         */
563
        private void removeUnneededEdges( final SplitGraph graph, final List<SplitEdge> ring ) {
564
            for( SplitEdge edge : ring ) {
565
                if (!edge.isInteriorEdge()) {
566
                    graph.remove(edge);
567
                }
568
            }
569

    
570
            for( SplitEdge edge : ring ) {
571
                if (edge.isInteriorEdge()) {
572
                    Node node = graph.find(edge.getCoordinate());
573
                    int degree = node.getEdges().getDegree();
574
                    if (degree < 2) {
575
                        graph.remove(edge);
576
                    }
577
                }
578
            }
579
        }
580

    
581
        /**
582
         * Returns the first edge found that belongs to the shell (not an interior edge, not one of
583
         * a hole boundary)
584
         * <p>
585
         * This method relies on shell edges being labeled {@link Location#EXTERIOR exterior} to the
586
         * left and {@link Location#INTERIOR interior} to the right.
587
         * </p>
588
         * 
589
         * @param graph
590
         * @return the first shell edge found, or <code>null</code> if there are no more shell
591
         *         edges in <code>graph</code>
592
         */
593
        private DirectedEdge findShellEdge( SplitGraph graph ) {
594
            Iterator it = graph.getEdgeEnds().iterator();
595
            DirectedEdge firstShellEdge = null;
596
            while( it.hasNext() ) {
597
                DirectedEdge de = (DirectedEdge) it.next();
598
                SplitEdge edge = (SplitEdge) de.getEdge();
599
                if (edge.isShellEdge()) {
600
                    firstShellEdge = de;
601
                    break;
602
                }
603
            }
604
            return firstShellEdge;
605
        }
606
    }
607
}