1 /**********************************************************************
2  *
3  * GEOS - Geometry Engine Open Source
4  * http://geos.osgeo.org
5  *
6  * Copyright (C) 2001-2002 Vivid Solutions Inc.
7  * Copyright (C) 2005 Refractions Research Inc.
8  *
9  * This is free software; you can redistribute and/or modify it under
10  * the terms of the GNU Lesser General Public Licence as published
11  * by the Free Software Foundation.
12  * See the COPYING file for more information.
13  *
14  **********************************************************************
15  *
16  * Last port: operation/overlay/PolygonBuilder.java rev. 1.20 (JTS-1.10)
17  *
18  **********************************************************************/
19 
20 #include <geos/operation/overlay/PolygonBuilder.h>
21 #include <geos/operation/overlay/OverlayOp.h>
22 #include <geos/operation/overlay/MaximalEdgeRing.h>
23 #include <geos/operation/overlay/MinimalEdgeRing.h>
24 #include <geos/operation/polygonize/EdgeRing.h>
25 #include <geos/geomgraph/Node.h>
26 #include <geos/geomgraph/NodeMap.h>
27 #include <geos/geomgraph/DirectedEdgeStar.h>
28 #include <geos/geom/GeometryFactory.h>
29 #include <geos/geom/LinearRing.h>
30 #include <geos/geom/Polygon.h>
31 #include <geos/geom/CoordinateArraySequence.h>
32 #include <geos/algorithm/PointLocation.h>
33 #include <geos/util/TopologyException.h>
34 #include <geos/util/GEOSException.h>
35 #include <geos/util.h>
36 
37 
38 #include <vector>
39 #include <cassert>
40 
41 #ifndef GEOS_DEBUG
42 #define GEOS_DEBUG 0
43 #endif
44 
45 using namespace std;
46 using namespace geos::geomgraph;
47 using namespace geos::algorithm;
48 using namespace geos::geom;
49 
50 namespace geos {
51 namespace operation { // geos.operation
52 namespace overlay { // geos.operation.overlay
53 
PolygonBuilder(const GeometryFactory * newGeometryFactory)54 PolygonBuilder::PolygonBuilder(const GeometryFactory* newGeometryFactory)
55     :
56     geometryFactory(newGeometryFactory)
57 {
58 }
59 
~PolygonBuilder()60 PolygonBuilder::~PolygonBuilder()
61 {
62     for(size_t i = 0, n = shellList.size(); i < n; ++i) {
63         delete shellList[i];
64     }
65 }
66 
67 /*public*/
68 void
add(PlanarGraph * graph)69 PolygonBuilder::add(PlanarGraph* graph)
70 //throw(TopologyException *)
71 {
72     const vector<EdgeEnd*>* eeptr = graph->getEdgeEnds();
73     assert(eeptr);
74     const vector<EdgeEnd*>& ee = *eeptr;
75 
76     size_t eeSize = ee.size();
77 
78 #if GEOS_DEBUG
79     cerr << __FUNCTION__ << ": PlanarGraph has " << eeSize << " EdgeEnds" << endl;
80 #endif
81 
82     vector<DirectedEdge*> dirEdges(eeSize);
83     for(size_t i = 0; i < eeSize; ++i) {
84         DirectedEdge* de = detail::down_cast<DirectedEdge*>(ee[i]);
85         dirEdges[i] = de;
86     }
87 
88     NodeMap::container& nodeMap = graph->getNodeMap()->nodeMap;
89     vector<Node*> nodes;
90     nodes.reserve(nodeMap.size());
91     for(NodeMap::iterator it = nodeMap.begin(), itEnd = nodeMap.end();
92             it != itEnd; ++it) {
93         Node* node = it->second;
94         nodes.push_back(node);
95     }
96 
97     add(&dirEdges, &nodes); // might throw a TopologyException *
98 }
99 
100 /*public*/
101 void
add(const vector<DirectedEdge * > * dirEdges,const vector<Node * > * nodes)102 PolygonBuilder::add(const vector<DirectedEdge*>* dirEdges,
103                     const vector<Node*>* nodes)
104 //throw(TopologyException *)
105 {
106     PlanarGraph::linkResultDirectedEdges(nodes->begin(), nodes->end());
107 
108     vector<MaximalEdgeRing*> maxEdgeRings;
109     buildMaximalEdgeRings(dirEdges, maxEdgeRings);
110 
111     vector<EdgeRing*> freeHoleList;
112     vector<MaximalEdgeRing*> edgeRings;
113     buildMinimalEdgeRings(maxEdgeRings, shellList, freeHoleList, edgeRings);
114 
115     sortShellsAndHoles(edgeRings, shellList, freeHoleList);
116 
117     vector<FastPIPRing> indexedshellist;
118     for(auto const& shell : shellList) {
119         FastPIPRing pipRing { shell, new geos::algorithm::locate::IndexedPointInAreaLocator(*shell->getLinearRing()) };
120         indexedshellist.push_back(pipRing);
121     }
122     placeFreeHoles(indexedshellist, freeHoleList);
123     //Assert: every hole on freeHoleList has a shell assigned to it
124 
125     for(auto const& shell : indexedshellist) {
126         delete shell.pipLocator;
127     }
128 }
129 
130 /*public*/
131 vector<Geometry*>*
getPolygons()132 PolygonBuilder::getPolygons()
133 {
134     vector<Geometry*>* resultPolyList = computePolygons(shellList);
135     return resultPolyList;
136 }
137 
138 
139 /*private*/
140 void
buildMaximalEdgeRings(const vector<DirectedEdge * > * dirEdges,vector<MaximalEdgeRing * > & maxEdgeRings)141 PolygonBuilder::buildMaximalEdgeRings(const vector<DirectedEdge*>* dirEdges,
142                                       vector<MaximalEdgeRing*>& maxEdgeRings)
143 // throw(const TopologyException &)
144 {
145 #if GEOS_DEBUG
146     cerr << "PolygonBuilder::buildMaximalEdgeRings got " << dirEdges->size() << " dirEdges" << endl;
147 #endif
148 
149     vector<MaximalEdgeRing*>::size_type oldSize = maxEdgeRings.size();
150 
151     for(size_t i = 0, n = dirEdges->size(); i < n; i++) {
152         DirectedEdge* de = (*dirEdges)[i];
153 #if GEOS_DEBUG
154         cerr << "  dirEdge " << i << endl
155              << de->printEdge() << endl
156              << " inResult:" << de->isInResult() << endl
157              << " isArea:" << de->getLabel().isArea() << endl;
158 #endif
159         if(de->isInResult() && de->getLabel().isArea()) {
160             // if this edge has not yet been processed
161             if(de->getEdgeRing() == nullptr) {
162                 MaximalEdgeRing* er;
163                 try {
164                     // MaximalEdgeRing constructor may throw
165                     er = new MaximalEdgeRing(de, geometryFactory);
166                 }
167                 catch(util::GEOSException&) {
168                     // cleanup if that happens (see stmlf-cases-20061020.xml)
169                     for(size_t p_i = oldSize, p_n = maxEdgeRings.size(); p_i < p_n; p_i++) {
170                         delete maxEdgeRings[p_i];
171                     }
172                     //cerr << "Exception! " << e.what() << endl;
173                     throw;
174                 }
175                 maxEdgeRings.push_back(er);
176                 er->setInResult();
177                 //System.out.println("max node degree=" + er.getMaxDegree());
178             }
179         }
180     }
181 #if GEOS_DEBUG
182     cerr << "  pushed " << maxEdgeRings.size() - oldSize << " maxEdgeRings" << endl;
183 #endif
184 }
185 
186 /*private*/
187 void
buildMinimalEdgeRings(vector<MaximalEdgeRing * > & maxEdgeRings,vector<EdgeRing * > & newShellList,vector<EdgeRing * > & freeHoleList,vector<MaximalEdgeRing * > & edgeRings)188 PolygonBuilder::buildMinimalEdgeRings(
189     vector<MaximalEdgeRing*>& maxEdgeRings,
190     vector<EdgeRing*>& newShellList, vector<EdgeRing*>& freeHoleList,
191     vector<MaximalEdgeRing*>& edgeRings)
192 {
193     for(size_t i = 0, n = maxEdgeRings.size(); i < n; ++i) {
194         MaximalEdgeRing* er = maxEdgeRings[i];
195 #if GEOS_DEBUG
196         cerr << "buildMinimalEdgeRings: maxEdgeRing " << i << " has " << er->getMaxNodeDegree() << " maxNodeDegree" << endl;
197 #endif
198         if(er->getMaxNodeDegree() > 2) {
199             er->linkDirectedEdgesForMinimalEdgeRings();
200             vector<MinimalEdgeRing*> minEdgeRings;
201             er->buildMinimalRings(minEdgeRings);
202             // at this point we can go ahead and attempt to place
203             // holes, if this EdgeRing is a polygon
204             EdgeRing* shell = findShell(&minEdgeRings);
205             if(shell != nullptr) {
206                 placePolygonHoles(shell, &minEdgeRings);
207                 newShellList.push_back(shell);
208             }
209             else {
210                 freeHoleList.insert(freeHoleList.end(),
211                                     minEdgeRings.begin(),
212                                     minEdgeRings.end());
213             }
214             delete er;
215         }
216         else {
217             edgeRings.push_back(er);
218         }
219     }
220 }
221 
222 /*private*/
223 EdgeRing*
findShell(vector<MinimalEdgeRing * > * minEdgeRings)224 PolygonBuilder::findShell(vector<MinimalEdgeRing*>* minEdgeRings)
225 {
226     int shellCount = 0;
227     EdgeRing* shell = nullptr;
228 
229 #if GEOS_DEBUG
230     cerr << "PolygonBuilder::findShell got " << minEdgeRings->size() << " minEdgeRings" << endl;
231 #endif
232 
233     for(size_t i = 0, n = minEdgeRings->size(); i < n; ++i) {
234         EdgeRing* er = (*minEdgeRings)[i];
235         if(! er->isHole()) {
236             shell = er;
237             ++shellCount;
238         }
239     }
240 
241     if(shellCount > 1) {
242         throw util::TopologyException("found two shells in MinimalEdgeRing list");
243     }
244 
245     return shell;
246 }
247 
248 /*private*/
249 void
placePolygonHoles(EdgeRing * shell,vector<MinimalEdgeRing * > * minEdgeRings)250 PolygonBuilder::placePolygonHoles(EdgeRing* shell,
251                                   vector<MinimalEdgeRing*>* minEdgeRings)
252 {
253     for(size_t i = 0, n = minEdgeRings->size(); i < n; ++i) {
254         MinimalEdgeRing* er = (*minEdgeRings)[i];
255         if(er->isHole()) {
256             er->setShell(shell);
257         }
258     }
259 }
260 
261 /*private*/
262 void
sortShellsAndHoles(vector<MaximalEdgeRing * > & edgeRings,vector<EdgeRing * > & newShellList,vector<EdgeRing * > & freeHoleList)263 PolygonBuilder::sortShellsAndHoles(vector<MaximalEdgeRing*>& edgeRings,
264                                    vector<EdgeRing*>& newShellList, vector<EdgeRing*>& freeHoleList)
265 {
266     for(size_t i = 0, n = edgeRings.size(); i < n; i++) {
267         EdgeRing* er = edgeRings[i];
268         //er->setInResult();
269         if(er->isHole()) {
270             freeHoleList.push_back(er);
271         }
272         else {
273             newShellList.push_back(er);
274         }
275     }
276 }
277 
278 /*private*/
279 void
placeFreeHoles(vector<FastPIPRing> & newShellList,std::vector<EdgeRing * > & freeHoleList)280 PolygonBuilder::placeFreeHoles(vector<FastPIPRing>& newShellList,
281                                std::vector<EdgeRing*>& freeHoleList)
282 {
283     for(std::vector<EdgeRing*>::iterator
284             it = freeHoleList.begin(), itEnd = freeHoleList.end();
285             it != itEnd;
286             ++it) {
287         EdgeRing* hole = *it;
288         // only place this hole if it doesn't yet have a shell
289         if(hole->getShell() == nullptr) {
290             EdgeRing* shell = findEdgeRingContaining(hole, newShellList);
291             if(shell == nullptr) {
292 #if GEOS_DEBUG
293                 Geometry* geom;
294                 std::cerr << "CREATE TABLE shells (g geometry);" << std::endl;
295                 std::cerr << "CREATE TABLE hole (g geometry);" << std::endl;
296                 for(std::vector<EdgeRing*>::iterator rIt = newShellList.begin(),
297                         rEnd = newShellList.end(); rIt != rEnd; rIt++) {
298                     geom = (*rIt)->toPolygon(geometryFactory);
299                     std::cerr << "INSERT INTO shells VALUES ('"
300                               << *geom
301                               << "');" << std::endl;
302                     delete geom;
303                 }
304                 geom = hole->toPolygon(geometryFactory);
305                 std::cerr << "INSERT INTO hole VALUES ('"
306                           << *geom
307                           << "');" << std::endl;
308                 delete geom;
309 #endif
310                 //assert(shell!=NULL); // unable to assign hole to a shell
311                 throw util::TopologyException("unable to assign hole to a shell");
312             }
313 
314             hole->setShell(shell);
315         }
316     }
317 }
318 
319 /*private*/
320 EdgeRing*
findEdgeRingContaining(EdgeRing * testEr,vector<FastPIPRing> & newShellList)321 PolygonBuilder::findEdgeRingContaining(EdgeRing* testEr,
322                                        vector<FastPIPRing>& newShellList)
323 {
324     LinearRing* testRing = testEr->getLinearRing();
325     const Envelope* testEnv = testRing->getEnvelopeInternal();
326     EdgeRing* minShell = nullptr;
327     const Envelope* minShellEnv = nullptr;
328 
329     for(auto const& tryShell : newShellList) {
330         LinearRing* tryShellRing = tryShell.edgeRing->getLinearRing();
331         const Envelope* tryShellEnv = tryShellRing->getEnvelopeInternal();
332         // the hole envelope cannot equal the shell envelope
333         // (also guards against testing rings against themselves)
334         if(tryShellEnv->equals(testEnv)) {
335             continue;
336         }
337         // hole must be contained in shell
338         if(!tryShellEnv->contains(testEnv)) {
339             continue;
340         }
341 
342         const CoordinateSequence* tsrcs = tryShellRing->getCoordinatesRO();
343         const Coordinate& testPt = operation::polygonize::EdgeRing::ptNotInList(testRing->getCoordinatesRO(), tsrcs);
344 
345         bool isContained = false;
346         if(tryShell.pipLocator->locate(&testPt) != Location::EXTERIOR) {
347             isContained = true;
348         }
349 
350         // check if this new containing ring is smaller than
351         // the current minimum ring
352         if(isContained) {
353             if(minShell == nullptr
354                     || minShellEnv->contains(tryShellEnv)) {
355                 minShell = tryShell.edgeRing;
356                 minShellEnv = minShell->getLinearRing()->getEnvelopeInternal();
357             }
358         }
359     }
360     return minShell;
361 }
362 
363 /*private*/
364 vector<Geometry*>*
computePolygons(vector<EdgeRing * > & newShellList)365 PolygonBuilder::computePolygons(vector<EdgeRing*>& newShellList)
366 {
367 #if GEOS_DEBUG
368     cerr << "PolygonBuilder::computePolygons: got " << newShellList.size() << " shells" << endl;
369 #endif
370     vector<Geometry*>* resultPolyList = new vector<Geometry*>();
371 
372     // add Polygons for all shells
373     for(size_t i = 0, n = newShellList.size(); i < n; i++) {
374         EdgeRing* er = newShellList[i];
375         Polygon* poly = er->toPolygon(geometryFactory).release();
376         resultPolyList->push_back(poly);
377     }
378     return resultPolyList;
379 }
380 
381 
382 } // namespace geos.operation.overlay
383 } // namespace geos.operation
384 } // namespace geos
385 
386