/src/geos/src/operation/overlayng/RingClipper.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * GEOS - Geometry Engine Open Source |
4 | | * http://geos.osgeo.org |
5 | | * |
6 | | * Copyright (C) 2020 Paul Ramsey <pramsey@cleverelephant.ca> |
7 | | * |
8 | | * This is free software; you can redistribute and/or modify it under |
9 | | * the terms of the GNU Lesser General Public Licence as published |
10 | | * by the Free Software Foundation. |
11 | | * See the COPYING file for more information. |
12 | | * |
13 | | **********************************************************************/ |
14 | | |
15 | | #include <geos/operation/overlayng/RingClipper.h> |
16 | | |
17 | | using geos::geom::CoordinateSequence; |
18 | | using geos::geom::CoordinateXYZM; |
19 | | |
20 | | namespace geos { // geos |
21 | | namespace operation { // geos.operation |
22 | | namespace overlayng { // geos.operation.overlayng |
23 | | |
24 | | |
25 | | /*public*/ |
26 | | std::unique_ptr<CoordinateSequence> |
27 | | RingClipper::clip(const CoordinateSequence* cs) const |
28 | 0 | { |
29 | 0 | std::unique_ptr<CoordinateSequence> pts; |
30 | 0 | for (int edgeIndex = 0; edgeIndex < 4; edgeIndex++) { |
31 | 0 | bool closeRing = (edgeIndex == 3); |
32 | 0 | pts = clipToBoxEdge(cs, edgeIndex, closeRing); |
33 | 0 | if (pts->size() == 0) |
34 | 0 | return pts; |
35 | 0 | cs = pts.get(); |
36 | 0 | } |
37 | 0 | return pts; |
38 | 0 | } |
39 | | |
40 | | /*private*/ |
41 | | std::unique_ptr<CoordinateSequence> |
42 | | RingClipper::clipToBoxEdge(const CoordinateSequence* pts, int edgeIndex, bool closeRing) const |
43 | 0 | { |
44 | | // TODO: is it possible to avoid copying array 4 times? |
45 | 0 | auto ptsClip = std::make_unique<CoordinateSequence>(0, pts->hasZ(), pts->hasM()); |
46 | |
|
47 | 0 | CoordinateXYZM p0; |
48 | 0 | pts->getAt(pts->size() - 1, p0); |
49 | 0 | for (std::size_t i = 0; i < pts->size(); i++) { |
50 | 0 | CoordinateXYZM p1; |
51 | 0 | pts->getAt(i, p1); |
52 | 0 | if (isInsideEdge(p1, edgeIndex)) { |
53 | 0 | if (!isInsideEdge(p0, edgeIndex)) { |
54 | 0 | CoordinateXY intPt; |
55 | 0 | intersection(p0, p1, edgeIndex, intPt); |
56 | 0 | ptsClip->add(intPt, false); |
57 | 0 | } |
58 | | // TODO: avoid copying so much? |
59 | 0 | ptsClip->add(p1, false); |
60 | |
|
61 | 0 | } |
62 | 0 | else if (isInsideEdge(p0, edgeIndex)) { |
63 | 0 | CoordinateXY intPt; |
64 | 0 | intersection(p0, p1, edgeIndex, intPt); |
65 | 0 | ptsClip->add(intPt, false); |
66 | 0 | } |
67 | | |
68 | | // else p0-p1 is outside box, so it is dropped |
69 | 0 | p0 = p1; |
70 | 0 | } |
71 | | |
72 | | // add closing point if required |
73 | 0 | if (closeRing) { |
74 | 0 | ptsClip->closeRing(); |
75 | 0 | } |
76 | |
|
77 | 0 | return ptsClip; |
78 | 0 | } |
79 | | |
80 | | /*private*/ |
81 | | void |
82 | | RingClipper::intersection(const CoordinateXY& a, const CoordinateXY& b, int edgeIndex, CoordinateXY& rsltPt) const |
83 | 0 | { |
84 | 0 | switch (edgeIndex) { |
85 | 0 | case BOX_BOTTOM: |
86 | 0 | rsltPt = CoordinateXY(intersectionLineY(a, b, clipEnv.getMinY()), clipEnv.getMinY()); |
87 | 0 | break; |
88 | 0 | case BOX_RIGHT: |
89 | 0 | rsltPt = CoordinateXY(clipEnv.getMaxX(), intersectionLineX(a, b, clipEnv.getMaxX())); |
90 | 0 | break; |
91 | 0 | case BOX_TOP: |
92 | 0 | rsltPt = CoordinateXY(intersectionLineY(a, b, clipEnv.getMaxY()), clipEnv.getMaxY()); |
93 | 0 | break; |
94 | 0 | case BOX_LEFT: |
95 | 0 | default: |
96 | 0 | rsltPt = Coordinate(clipEnv.getMinX(), intersectionLineX(a, b, clipEnv.getMinX())); |
97 | 0 | } |
98 | 0 | return; |
99 | 0 | } |
100 | | |
101 | | /*private*/ |
102 | | double |
103 | | RingClipper::intersectionLineY(const CoordinateXY& a, const CoordinateXY& b, double y) |
104 | 0 | { |
105 | 0 | double m = (b.x - a.x) / (b.y - a.y); |
106 | 0 | double intercept = (y - a.y) * m; |
107 | 0 | return a.x + intercept; |
108 | 0 | } |
109 | | |
110 | | /*private*/ |
111 | | double |
112 | | RingClipper::intersectionLineX(const CoordinateXY& a, const CoordinateXY& b, double x) |
113 | 0 | { |
114 | 0 | double m = (b.y - a.y) / (b.x - a.x); |
115 | 0 | double intercept = (x - a.x) * m; |
116 | 0 | return a.y + intercept; |
117 | 0 | } |
118 | | |
119 | | /*private*/ |
120 | | bool |
121 | | RingClipper::isInsideEdge(const CoordinateXY& p, int edgeIndex) const |
122 | 0 | { |
123 | 0 | if (clipEnv.isNull()) { |
124 | 0 | return false; |
125 | 0 | } |
126 | | |
127 | 0 | bool isInside = false; |
128 | 0 | switch (edgeIndex) { |
129 | 0 | case BOX_BOTTOM: // bottom |
130 | 0 | isInside = p.y > clipEnv.getMinY(); |
131 | 0 | break; |
132 | 0 | case BOX_RIGHT: // right |
133 | 0 | isInside = p.x < clipEnv.getMaxX(); |
134 | 0 | break; |
135 | 0 | case BOX_TOP: // top |
136 | 0 | isInside = p.y < clipEnv.getMaxY(); |
137 | 0 | break; |
138 | 0 | case BOX_LEFT: |
139 | 0 | default: // left |
140 | 0 | isInside = p.x > clipEnv.getMinX(); |
141 | 0 | } |
142 | 0 | return isInside; |
143 | 0 | } |
144 | | |
145 | | |
146 | | |
147 | | } // namespace geos.operation.overlayng |
148 | | } // namespace geos.operation |
149 | | } // namespace geos |