Statistics
| Revision:

gvsig-vectorediting / org.gvsig.vectorediting / trunk / org.gvsig.vectorediting / org.gvsig.vectorediting.lib / org.gvsig.vectorediting.lib.prov / org.gvsig.vectorediting.lib.prov.split / src / main / java / org / gvsig / vectorediting / lib / prov / split / operation / splitsurface / SurfaceSplitOperation.java @ 326

History | View | Annotate | Download (12.3 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright ? 2007-2014 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 2
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
package org.gvsig.vectorediting.lib.prov.split.operation.splitsurface;
26

    
27
import java.util.ArrayList;
28
import java.util.Iterator;
29
import java.util.List;
30

    
31
import com.vividsolutions.jts.algorithm.CGAlgorithms;
32
import com.vividsolutions.jts.geom.Coordinate;
33
import com.vividsolutions.jts.geom.CoordinateArrays;
34
import com.vividsolutions.jts.geom.GeometryFactory;
35
import com.vividsolutions.jts.geom.LineString;
36
import com.vividsolutions.jts.geom.LinearRing;
37
import com.vividsolutions.jts.geom.Location;
38
import com.vividsolutions.jts.geom.Polygon;
39
import com.vividsolutions.jts.geomgraph.DirectedEdge;
40
import com.vividsolutions.jts.geomgraph.Node;
41

    
42
import org.gvsig.fmap.geom.Geometry;
43
import org.gvsig.fmap.geom.GeometryLocator;
44
import org.gvsig.fmap.geom.GeometryManager;
45
import org.gvsig.fmap.geom.operation.GeometryOperationContext;
46
import org.gvsig.fmap.geom.operation.GeometryOperationException;
47
import org.gvsig.fmap.geom.operation.GeometryOperationNotSupportedException;
48
import org.gvsig.vectorediting.lib.prov.split.operation.SplitOperation;
49

    
50
/**
51
 * This class is based in the AXIOS team work for UDIG project.
52
 * 
53
 * @author llmarques
54
 *
55
 */
56

    
57
/*
58
 * Performs a split of a LineString, MultiLineString, Polygon or MultiPolygon
59
 * using a provided
60
 * LineString as cutting edge.
61
 * 
62
 * @author Gabriel Rold?n, Axios Engineering
63
 * 
64
 * @author Mauricio Pazos, Axios Engineering
65
 * 
66
 * @since 1.1.0
67
 */
68
public class SurfaceSplitOperation implements SplitOperation {
69

    
70
    /**
71
     * Strategy:
72
     * <ul>
73
     * <li>Build a graph with all edges and intersection nodes between polygon
74
     * and linestring.
75
     * <li>Weigth the amount of nodes according to the number of incident edges.
76
     * Nodes are only the intersection beetween polygon bondary and linestring
77
     * and initial points of each part of boundary.
78
     * <li>Weigth edges according to shared (linestring node) or non shared
79
     * (boundary of polygon). Store edge cordinates at edge.
80
     * <li>Iterate over node graph. Start at any node, starting at its first
81
     * segment.
82
     * <li>Iterate always to next node, choosing the edge that its first
83
     * linestring segment that has less left angle (CCW) with the last segment
84
     * of edge linestring.
85
     * <li>Delete non shared used edges that was used
86
     * <li>Decrease weight at 1 of used nodes
87
     * <li>Delete of graph nodes with weight 1
88
     * </ul>
89
     * 
90
     * @author Gabriel Rold?n, Axios Engineering
91
     * @author Mauricio Pazos, Axios Engineering
92
     * @since 1.1.0
93
     */
94
    public Geometry split(Geometry geometryToBeSplitted, Geometry splitter)
95
        throws GeometryOperationNotSupportedException,
96
        GeometryOperationException {
97

    
98
        com.vividsolutions.jts.geom.Geometry jtsGeom =
99
            (com.vividsolutions.jts.geom.Geometry) geometryToBeSplitted
100
                .invokeOperation("toJTS", null);
101

    
102
        com.vividsolutions.jts.geom.Geometry jtsSplitter =
103
            (com.vividsolutions.jts.geom.Geometry) splitter.invokeOperation(
104
                "toJTS", null);
105

    
106
        if (jtsGeom instanceof Polygon && jtsSplitter instanceof LineString) {
107
            SplitGraph graph =
108
                new SplitGraph((Polygon) jtsGeom, (LineString) jtsSplitter);
109
            final GeometryFactory gf = jtsGeom.getFactory();
110

    
111
            // store unsplitted holes for later addition
112
            List<LinearRing> unsplittedHoles = findUnsplittedHoles(graph, gf);
113

    
114
            List<List<SplitEdge>> allRings = findRings(graph);
115

    
116
            List<Polygon> resultingPolygons =
117
                buildSimplePolygons(allRings, unsplittedHoles, gf);
118

    
119
            com.vividsolutions.jts.geom.Geometry result;
120
            if (resultingPolygons.size() == 1) {
121
                result = resultingPolygons.get(0);
122
            } else {
123
                Polygon[] array =
124
                    resultingPolygons.toArray(new Polygon[resultingPolygons
125
                        .size()]);
126
                result = gf.createMultiPolygon(array);
127
            }
128

    
129
            GeometryManager manager = GeometryLocator.getGeometryManager();
130
            GeometryOperationContext params = new GeometryOperationContext();
131
            params.setAttribute("JTSGeometry", result);
132
            return (Geometry) manager.invokeOperation("fromJTS", params);
133
        }
134
        return null;
135
    }
136

    
137
    /**
138
     * Finds out and removes from the graph the edges that were originally holes
139
     * in the polygon and were not splitted by the splitting line.
140
     * 
141
     * @param graph
142
     * @param gf
143
     * @return
144
     */
145
    private List<LinearRing> findUnsplittedHoles(SplitGraph graph,
146
        GeometryFactory gf) {
147
        final List<LinearRing> unsplittedHoles = new ArrayList<LinearRing>(2);
148

    
149
        final List<SplitEdge> edges = new ArrayList<SplitEdge>();
150
        for (Iterator it = graph.getEdgeIterator(); it.hasNext();) {
151
            SplitEdge edge = (SplitEdge) it.next();
152
            edges.add(edge);
153
        }
154

    
155
        for (Iterator it = edges.iterator(); it.hasNext();) {
156
            SplitEdge edge = (SplitEdge) it.next();
157
            if (edge.isHoleEdge()) {
158
                Coordinate[] coordinates = edge.getCoordinates();
159
                Coordinate start = coordinates[0];
160
                Coordinate end = coordinates[coordinates.length - 1];
161
                boolean isLinearRing = start.equals2D(end);
162
                if (isLinearRing) {
163
                    graph.remove(edge);
164
                    LinearRing ring = gf.createLinearRing(coordinates);
165
                    unsplittedHoles.add(ring);
166
                }
167
            }
168
        }
169
        return unsplittedHoles;
170
    }
171

    
172
    /**
173
     * Builds a list of rings from the graph's edges
174
     * 
175
     * @param graph
176
     * @return
177
     */
178
    private List<List<SplitEdge>> findRings(SplitGraph graph) {
179
        final List<List<SplitEdge>> rings = new ArrayList<List<SplitEdge>>();
180

    
181
        DirectedEdge startEdge;
182
        // build each ring starting with the first edge belonging to the
183
        // shell found
184
        while ((startEdge = findShellEdge(graph)) != null) {
185
            List<SplitEdge> ring = buildRing(graph, startEdge);
186
            rings.add(ring);
187
        }
188
        return rings;
189
    }
190

    
191
    /**
192
     * Returns the first edge found that belongs to the shell (not an interior
193
     * edge, not one of a hole boundary)
194
     * <p>
195
     * This method relies on shell edges being labeled {@link Location#EXTERIOR
196
     * exterior} to the left and {@link Location#INTERIOR interior} to the
197
     * right.
198
     * </p>
199
     * 
200
     * @param graph
201
     * @return the first shell edge found, or <code>null</code> if there are no
202
     *         more shell edges in <code>graph</code>
203
     */
204
    private DirectedEdge findShellEdge(SplitGraph graph) {
205
        Iterator it = graph.getEdgeEnds().iterator();
206
        DirectedEdge firstShellEdge = null;
207
        while (it.hasNext()) {
208
            DirectedEdge de = (DirectedEdge) it.next();
209
            SplitEdge edge = (SplitEdge) de.getEdge();
210
            if (edge.isShellEdge()) {
211
                firstShellEdge = de;
212
                break;
213
            }
214
        }
215
        return firstShellEdge;
216
    }
217

    
218
    private List<SplitEdge> buildRing(final SplitGraph graph,
219
        final DirectedEdge startEdge) {
220
        final List<SplitEdge> ring = new ArrayList<SplitEdge>();
221

    
222
        // follow this tessellation direction while possible,
223
        // switch to the opposite when not, and continue with
224
        // the same direction while possible.
225
        // Start travelling clockwise, as we start with a shell edge,
226
        // which is in clockwise order
227
        final int direction = CGAlgorithms.COUNTERCLOCKWISE;
228

    
229
        DirectedEdge currentDirectedEdge = startEdge;
230
        DirectedEdge nextDirectedEdge = null;
231

    
232
        while (nextDirectedEdge != startEdge) {
233
            SplitEdge edge = (SplitEdge) currentDirectedEdge.getEdge();
234
            if (ring.contains(edge)) {
235
                throw new IllegalStateException("trying to add edge twice: "
236
                    + edge);
237
            }
238
            ring.add(edge);
239

    
240
            DirectedEdge sym = currentDirectedEdge.getSym();
241
            SplitGraphNode endNode = (SplitGraphNode) sym.getNode();
242
            SplitEdgeStar nodeEdges = (SplitEdgeStar) endNode.getEdges();
243
            nextDirectedEdge =
244
                nodeEdges.findClosestEdgeInDirection(sym, direction);
245

    
246
            assert nextDirectedEdge != null;
247

    
248
            currentDirectedEdge = nextDirectedEdge;
249
        }
250

    
251
        removeUnneededEdges(graph, ring);
252
        return ring;
253
    }
254

    
255
    /**
256
     * Removes from <code>graph</code> the edges in <code>ring</code> that are
257
     * no more needed
258
     * 
259
     * @param graph
260
     * @param ring
261
     */
262
    private void removeUnneededEdges(final SplitGraph graph,
263
        final List<SplitEdge> ring) {
264
        for (SplitEdge edge : ring) {
265
            if (!edge.isInteriorEdge()) {
266
                graph.remove(edge);
267
            }
268
        }
269

    
270
        for (SplitEdge edge : ring) {
271
            if (edge.isInteriorEdge()) {
272
                Node node = graph.find(edge.getCoordinate());
273
                int degree = node.getEdges().getDegree();
274
                if (degree < 2) {
275
                    graph.remove(edge);
276
                }
277
            }
278
        }
279
    }
280

    
281
    private List<Polygon> buildSimplePolygons(List<List<SplitEdge>> allRings,
282
        List<LinearRing> unsplittedHoles, GeometryFactory gf) {
283

    
284
        List<Polygon> polygons = new ArrayList<Polygon>(allRings.size());
285

    
286
        for (List<SplitEdge> edgeList : allRings) {
287
            Polygon poly = buildPolygon(edgeList, gf);
288
            List<LinearRing> thisPolyHoles =
289
                new ArrayList<LinearRing>(unsplittedHoles.size());
290
            for (LinearRing holeRing : unsplittedHoles) {
291
                if (poly.covers(holeRing)) {
292
                    thisPolyHoles.add(holeRing);
293
                }
294
            }
295
            unsplittedHoles.removeAll(thisPolyHoles);
296

    
297
            int numHoles = thisPolyHoles.size();
298
            if (numHoles > 0) {
299
                LinearRing[] holes =
300
                    thisPolyHoles.toArray(new LinearRing[numHoles]);
301
                LinearRing shell =
302
                    gf.createLinearRing(poly.getExteriorRing().getCoordinates());
303
                poly = gf.createPolygon(shell, holes);
304
            }
305

    
306
            polygons.add(poly);
307
        }
308

    
309
        return polygons;
310
    }
311

    
312
    private Polygon buildPolygon(List<SplitEdge> edgeList, GeometryFactory gf) {
313
        List<Coordinate> coords = new ArrayList<Coordinate>();
314
        Coordinate[] lastCoordinates = null;
315
        for (SplitEdge edge : edgeList) {
316
            Coordinate[] coordinates = edge.getCoordinates();
317
            if (lastCoordinates != null) {
318
                Coordinate endPoint =
319
                    lastCoordinates[lastCoordinates.length - 1];
320
                Coordinate startPoint = coordinates[0];
321
                if (!endPoint.equals2D(startPoint)) {
322
                    coordinates = CoordinateArrays.copyDeep(coordinates);
323
                    CoordinateArrays.reverse(coordinates);
324
                }
325
            }
326
            lastCoordinates = coordinates;
327
            for (int i = 0; i < coordinates.length; i++) {
328
                Coordinate coord = coordinates[i];
329
                coords.add(coord);
330
            }
331
        }
332
        Coordinate[] shellCoords = new Coordinate[coords.size()];
333
        coords.toArray(shellCoords);
334
        shellCoords = CoordinateArrays.removeRepeatedPoints(shellCoords);
335
        LinearRing shell = gf.createLinearRing(shellCoords);
336
        Polygon poly = gf.createPolygon(shell, (LinearRing[]) null);
337
        return poly;
338
    }
339

    
340
}