/src/geos/src/operation/buffer/OffsetSegmentGenerator.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * GEOS-Geometry Engine Open Source |
4 | | * http://geos.osgeo.org |
5 | | * |
6 | | * Copyright (C) 2011 Sandro Santilli <strk@kbt.io> |
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 | | * Last port: operation/buffer/OffsetSegmentGenerator.java r378 (JTS-1.12) |
16 | | * |
17 | | **********************************************************************/ |
18 | | |
19 | | #include <cassert> |
20 | | #include <cmath> |
21 | | #include <vector> |
22 | | |
23 | | #include <geos/algorithm/Angle.h> |
24 | | #include <geos/algorithm/Distance.h> |
25 | | #include <geos/algorithm/Orientation.h> |
26 | | #include <geos/operation/buffer/OffsetSegmentGenerator.h> |
27 | | #include <geos/operation/buffer/OffsetSegmentString.h> |
28 | | #include <geos/operation/buffer/BufferInputLineSimplifier.h> |
29 | | #include <geos/operation/buffer/BufferParameters.h> |
30 | | #include <geos/geom/Position.h> |
31 | | #include <geos/geom/CoordinateSequence.h> |
32 | | #include <geos/geom/Coordinate.h> |
33 | | #include <geos/geom/PrecisionModel.h> |
34 | | #include <geos/algorithm/NotRepresentableException.h> |
35 | | #include <geos/algorithm/CGAlgorithmsDD.h> |
36 | | #include <geos/util.h> |
37 | | |
38 | | |
39 | | using namespace geos::algorithm; |
40 | | using namespace geos::geom; |
41 | | |
42 | | namespace geos { |
43 | | namespace operation { // geos.operation |
44 | | namespace buffer { // geos.operation.buffer |
45 | | |
46 | | /*private data*/ |
47 | | const double OffsetSegmentGenerator::CURVE_VERTEX_SNAP_DISTANCE_FACTOR = 1.0E-4; |
48 | | const double OffsetSegmentGenerator::OFFSET_SEGMENT_SEPARATION_FACTOR = 1.0E-3; |
49 | | const double OffsetSegmentGenerator::INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR = 1.0E-3; |
50 | | const double OffsetSegmentGenerator::SIMPLIFY_FACTOR = 100.0; |
51 | | |
52 | | /*public*/ |
53 | | OffsetSegmentGenerator::OffsetSegmentGenerator( |
54 | | const PrecisionModel* newPrecisionModel, |
55 | | const BufferParameters& nBufParams, |
56 | | double dist) |
57 | | : |
58 | 0 | maxCurveSegmentError(0.0), |
59 | 0 | closingSegLengthFactor(1), |
60 | 0 | segList(), |
61 | 0 | distance(dist), |
62 | 0 | precisionModel(newPrecisionModel), |
63 | 0 | bufParams(nBufParams), |
64 | 0 | li(), |
65 | 0 | s0(), |
66 | 0 | s1(), |
67 | 0 | s2(), |
68 | 0 | seg0(), |
69 | 0 | seg1(), |
70 | 0 | offset0(), |
71 | 0 | offset1(), |
72 | 0 | side(0), |
73 | 0 | _hasNarrowConcaveAngle(false), |
74 | 0 | endCapIndex(0) |
75 | 0 | { |
76 | | // compute intersections in full precision, to provide accuracy |
77 | | // the points are rounded as they are inserted into the curve line |
78 | 0 | filletAngleQuantum = Angle::PI_OVER_2 / bufParams.getQuadrantSegments(); |
79 | |
|
80 | 0 | int quadSegs = bufParams.getQuadrantSegments(); |
81 | 0 | if (quadSegs < 1) quadSegs = 1; |
82 | 0 | filletAngleQuantum = Angle::PI_OVER_2 / quadSegs; |
83 | | |
84 | | /* |
85 | | * Non-round joins cause issues with short closing segments, |
86 | | * so don't use them. In any case, non-round joins |
87 | | * only really make sense for relatively small buffer distances. |
88 | | */ |
89 | 0 | if(bufParams.getQuadrantSegments() >= 8 |
90 | 0 | && bufParams.getJoinStyle() == BufferParameters::JOIN_ROUND) { |
91 | 0 | closingSegLengthFactor = MAX_CLOSING_SEG_LEN_FACTOR; |
92 | 0 | } |
93 | |
|
94 | 0 | init(distance); |
95 | 0 | } |
96 | | |
97 | | /*private*/ |
98 | | void |
99 | | OffsetSegmentGenerator::init(double newDistance) |
100 | 0 | { |
101 | 0 | distance = newDistance; |
102 | 0 | maxCurveSegmentError = distance * (1 - cos(filletAngleQuantum / 2.0)); |
103 | | |
104 | | // Point list needs to be reset |
105 | 0 | segList.reset(); |
106 | 0 | segList.setPrecisionModel(precisionModel); |
107 | | |
108 | | /* |
109 | | * Choose the min vertex separation as a small fraction of |
110 | | * the offset distance. |
111 | | */ |
112 | 0 | segList.setMinimumVertexDistance( |
113 | 0 | distance * CURVE_VERTEX_SNAP_DISTANCE_FACTOR); |
114 | 0 | } |
115 | | |
116 | | /*public*/ |
117 | | void |
118 | | OffsetSegmentGenerator::initSideSegments(const Coordinate& nS1, |
119 | | const Coordinate& nS2, int nSide) |
120 | 0 | { |
121 | 0 | s1 = nS1; |
122 | 0 | s2 = nS2; |
123 | 0 | side = nSide; |
124 | 0 | seg1.setCoordinates(s1, s2); |
125 | 0 | computeOffsetSegment(seg1, side, distance, offset1); |
126 | 0 | } |
127 | | |
128 | | /*public*/ |
129 | | void |
130 | | OffsetSegmentGenerator::addNextSegment(const Coordinate& p, bool addStartPoint) |
131 | 0 | { |
132 | | |
133 | | // do nothing if points are equal |
134 | 0 | if(s2 == p) { |
135 | 0 | return; |
136 | 0 | } |
137 | | |
138 | | // s0-s1-s2 are the coordinates of the previous segment |
139 | | // and the current one |
140 | 0 | s0 = s1; |
141 | 0 | s1 = s2; |
142 | 0 | s2 = p; |
143 | 0 | seg0.setCoordinates(s0, s1); |
144 | 0 | computeOffsetSegment(seg0, side, distance, offset0); |
145 | 0 | seg1.setCoordinates(s1, s2); |
146 | 0 | computeOffsetSegment(seg1, side, distance, offset1); |
147 | |
|
148 | 0 | int orientation = Orientation::index(s0, s1, s2); |
149 | 0 | bool outsideTurn = |
150 | 0 | (orientation == Orientation::CLOCKWISE |
151 | 0 | && side == Position::LEFT) |
152 | 0 | || |
153 | 0 | (orientation == Orientation::COUNTERCLOCKWISE |
154 | 0 | && side == Position::RIGHT); |
155 | |
|
156 | 0 | if(orientation == 0) { |
157 | | // lines are collinear |
158 | 0 | addCollinear(addStartPoint); |
159 | 0 | } |
160 | 0 | else if(outsideTurn) { |
161 | 0 | addOutsideTurn(orientation, addStartPoint); |
162 | 0 | } |
163 | 0 | else { |
164 | | // inside turn |
165 | 0 | addInsideTurn(orientation, addStartPoint); |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | | /*private*/ |
170 | | void |
171 | | OffsetSegmentGenerator::computeOffsetSegment(const LineSegment& seg, int p_side, |
172 | | double p_distance, LineSegment& offset) |
173 | 0 | { |
174 | 0 | int sideSign = p_side == Position::LEFT ? 1 : -1; |
175 | 0 | double dx = seg.p1.x - seg.p0.x; |
176 | 0 | double dy = seg.p1.y - seg.p0.y; |
177 | 0 | double len = std::sqrt(dx * dx + dy * dy); |
178 | | // u is the vector that is the length of the offset, |
179 | | // in the direction of the segment |
180 | 0 | double ux = sideSign * p_distance * dx / len; |
181 | 0 | double uy = sideSign * p_distance * dy / len; |
182 | 0 | offset.p0.x = seg.p0.x - uy; |
183 | 0 | offset.p0.y = seg.p0.y + ux; |
184 | 0 | offset.p1.x = seg.p1.x - uy; |
185 | 0 | offset.p1.y = seg.p1.y + ux; |
186 | 0 | } |
187 | | |
188 | | /*public*/ |
189 | | void |
190 | | OffsetSegmentGenerator::addLineEndCap(const Coordinate& p0, const Coordinate& p1, const Coordinate& p2) |
191 | 0 | { |
192 | 0 | LineSegment segL(p0, p1); |
193 | 0 | LineSegment segR(p2, p1); |
194 | |
|
195 | 0 | LineSegment offsetL; |
196 | 0 | computeOffsetSegment(segL, Position::LEFT, distance, offsetL); |
197 | 0 | LineSegment offsetR; |
198 | 0 | computeOffsetSegment(segR, Position::RIGHT, distance, offsetR); |
199 | |
|
200 | 0 | switch(bufParams.getEndCapStyle()) { |
201 | 0 | case BufferParameters::CAP_ROUND: |
202 | | // add offset seg points with a fillet between them |
203 | 0 | segList.addPt(offsetL.p1); |
204 | 0 | addDirectedFillet(p1, offsetL.p1, offsetR.p1, Orientation::CLOCKWISE, distance); |
205 | 0 | segList.addPt(offsetR.p1); |
206 | 0 | break; |
207 | 0 | case BufferParameters::CAP_FLAT: |
208 | | // only offset segment points are added |
209 | 0 | segList.addPt(offsetL.p1); |
210 | 0 | segList.addPt(offsetR.p1); |
211 | 0 | break; |
212 | 0 | case BufferParameters::CAP_SQUARE: |
213 | | // add a square defined by extensions of the offset |
214 | | // segment endpoints |
215 | |
|
216 | 0 | Coordinate squareCapSideOffset; |
217 | | // take average in case angles of left and right sides differ |
218 | 0 | double dx = p1.x - p0.x/2 - p2.x/2; |
219 | 0 | double dy = p1.y - p0.y/2 - p2.y/2; |
220 | 0 | double angle = atan2(dy, dx); |
221 | 0 | double sinangle, cosangle; |
222 | 0 | Angle::sinCosSnap(angle, sinangle, cosangle); |
223 | 0 | squareCapSideOffset.x = fabs(distance) * cosangle; |
224 | 0 | squareCapSideOffset.y = fabs(distance) * sinangle; |
225 | |
|
226 | 0 | Coordinate squareCapLOffset( |
227 | 0 | offsetL.p1.x + squareCapSideOffset.x, |
228 | 0 | offsetL.p1.y + squareCapSideOffset.y); |
229 | 0 | Coordinate squareCapROffset( |
230 | 0 | offsetR.p1.x + squareCapSideOffset.x, |
231 | 0 | offsetR.p1.y + squareCapSideOffset.y); |
232 | 0 | segList.addPt(squareCapLOffset); |
233 | 0 | segList.addPt(squareCapROffset); |
234 | 0 | break; |
235 | 0 | } |
236 | 0 | } |
237 | | |
238 | | /*private*/ |
239 | | void |
240 | | OffsetSegmentGenerator::addDirectedFillet(const Coordinate& p, const Coordinate& p0, |
241 | | const Coordinate& p1, int direction, double radius) |
242 | 0 | { |
243 | 0 | double dx0 = p0.x - p.x; |
244 | 0 | double dy0 = p0.y - p.y; |
245 | 0 | double startAngle = atan2(dy0, dx0); |
246 | 0 | double dx1 = p1.x - p.x; |
247 | 0 | double dy1 = p1.y - p.y; |
248 | 0 | double endAngle = atan2(dy1, dx1); |
249 | |
|
250 | 0 | if(direction == Orientation::CLOCKWISE) { |
251 | 0 | if(startAngle <= endAngle) { |
252 | 0 | startAngle += Angle::PI_TIMES_2; |
253 | 0 | } |
254 | 0 | } |
255 | 0 | else { // direction==COUNTERCLOCKWISE |
256 | 0 | if(startAngle >= endAngle) { |
257 | 0 | startAngle -= Angle::PI_TIMES_2; |
258 | 0 | } |
259 | 0 | } |
260 | |
|
261 | 0 | segList.addPt(p0); |
262 | 0 | addDirectedFillet(p, startAngle, endAngle, direction, radius); |
263 | 0 | segList.addPt(p1); |
264 | 0 | } |
265 | | |
266 | | /*private*/ |
267 | | void |
268 | | OffsetSegmentGenerator::addDirectedFillet(const Coordinate& p, double startAngle, |
269 | | double endAngle, int direction, double radius) |
270 | 0 | { |
271 | 0 | int directionFactor = direction == Orientation::CLOCKWISE ? -1 : 1; |
272 | |
|
273 | 0 | double totalAngle = fabs(startAngle - endAngle); |
274 | 0 | int nSegs = (int)(totalAngle / filletAngleQuantum + 0.5); |
275 | | |
276 | | // no segments because angle is less than increment-nothing to do! |
277 | 0 | if(nSegs < 1) return; |
278 | | |
279 | 0 | double angleInc = totalAngle / nSegs; |
280 | 0 | double sinangle, cosangle; |
281 | 0 | Coordinate pt; |
282 | 0 | for (int i = 0; i < nSegs; i++) { |
283 | 0 | Angle::sinCosSnap(startAngle + directionFactor * i * angleInc, sinangle, cosangle); |
284 | 0 | pt.x = p.x + radius * cosangle; |
285 | 0 | pt.y = p.y + radius * sinangle; |
286 | 0 | segList.addPt(pt); |
287 | 0 | } |
288 | 0 | } |
289 | | |
290 | | /*private*/ |
291 | | void |
292 | | OffsetSegmentGenerator::createCircle(const Coordinate& p, double p_distance) |
293 | 0 | { |
294 | | // add start point |
295 | 0 | Coordinate pt(p.x + p_distance, p.y); |
296 | 0 | segList.addPt(pt); |
297 | 0 | addDirectedFillet(p, 0.0, Angle::PI_TIMES_2, -1, p_distance); |
298 | 0 | segList.closeRing(); |
299 | 0 | } |
300 | | |
301 | | /*private*/ |
302 | | void |
303 | | OffsetSegmentGenerator::createSquare(const Coordinate& p, double p_distance) |
304 | 0 | { |
305 | 0 | segList.addPt(Coordinate(p.x + p_distance, p.y + p_distance)); |
306 | 0 | segList.addPt(Coordinate(p.x + p_distance, p.y - p_distance)); |
307 | 0 | segList.addPt(Coordinate(p.x - p_distance, p.y - p_distance)); |
308 | 0 | segList.addPt(Coordinate(p.x - p_distance, p.y + p_distance)); |
309 | 0 | segList.closeRing(); |
310 | 0 | } |
311 | | |
312 | | /* private */ |
313 | | void |
314 | | OffsetSegmentGenerator::addCollinear(bool addStartPoint) |
315 | 0 | { |
316 | | /* |
317 | | * This test could probably be done more efficiently, |
318 | | * but the situation of exact collinearity should be fairly rare. |
319 | | */ |
320 | |
|
321 | 0 | li.computeIntersection(s0, s1, s1, s2); |
322 | 0 | auto numInt = li.getIntersectionNum(); |
323 | | |
324 | | /* |
325 | | * if numInt is<2, the lines are parallel and in the same direction. |
326 | | * In this case the point can be ignored, since the offset lines |
327 | | * will also be parallel. |
328 | | */ |
329 | 0 | if(numInt >= 2) { |
330 | | /* |
331 | | * Segments are collinear but reversing. |
332 | | * Add an "end-cap" fillet |
333 | | * all the way around to other direction |
334 | | * |
335 | | * This case should ONLY happen for LineStrings, |
336 | | * so the orientation is always CW (Polygons can never |
337 | | * have two consecutive segments which are parallel but |
338 | | * reversed, because that would be a self intersection). |
339 | | */ |
340 | 0 | if(bufParams.getJoinStyle() == BufferParameters::JOIN_BEVEL |
341 | 0 | || bufParams.getJoinStyle() == BufferParameters::JOIN_MITRE) { |
342 | 0 | if(addStartPoint) { |
343 | 0 | segList.addPt(offset0.p1); |
344 | 0 | } |
345 | 0 | segList.addPt(offset1.p0); |
346 | 0 | } |
347 | 0 | else { |
348 | 0 | addDirectedFillet(s1, offset0.p1, offset1.p0, |
349 | 0 | Orientation::CLOCKWISE, distance); |
350 | 0 | } |
351 | 0 | } |
352 | 0 | } |
353 | | |
354 | | /* private */ |
355 | | void |
356 | | OffsetSegmentGenerator::addOutsideTurn(int orientation, bool addStartPoint) |
357 | 0 | { |
358 | | /* |
359 | | * Heuristic: If offset endpoints are very close together, |
360 | | * just use one of them as the corner vertex. |
361 | | * This avoids problems with computing mitre corners in the case |
362 | | * where the two segments are almost parallel |
363 | | * (which is hard to compute a robust intersection for). |
364 | | */ |
365 | |
|
366 | 0 | if(offset0.p1.distance(offset1.p0) < |
367 | 0 | distance * OFFSET_SEGMENT_SEPARATION_FACTOR) { |
368 | 0 | segList.addPt(offset0.p1); |
369 | 0 | return; |
370 | 0 | } |
371 | | |
372 | 0 | if(bufParams.getJoinStyle() == BufferParameters::JOIN_MITRE) { |
373 | 0 | addMitreJoin(s1, offset0, offset1, distance); |
374 | 0 | } |
375 | 0 | else if(bufParams.getJoinStyle() == BufferParameters::JOIN_BEVEL) { |
376 | 0 | addBevelJoin(offset0, offset1); |
377 | 0 | } |
378 | 0 | else { |
379 | | // add a circular fillet connecting the endpoints |
380 | | // of the offset segments |
381 | 0 | if(addStartPoint) { |
382 | 0 | segList.addPt(offset0.p1); |
383 | 0 | } |
384 | | |
385 | | // TESTING - comment out to produce beveled joins |
386 | 0 | addDirectedFillet(s1, offset0.p1, offset1.p0, orientation, distance); |
387 | 0 | segList.addPt(offset1.p0); |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | | /* private */ |
392 | | void |
393 | | OffsetSegmentGenerator::addInsideTurn(int orientation, bool addStartPoint) |
394 | 0 | { |
395 | 0 | ::geos::ignore_unused_variable_warning(orientation); |
396 | 0 | ::geos::ignore_unused_variable_warning(addStartPoint); |
397 | | |
398 | | // add intersection point of offset segments (if any) |
399 | 0 | li.computeIntersection(offset0.p0, offset0.p1, offset1.p0, offset1.p1); |
400 | 0 | if(li.hasIntersection()) { |
401 | 0 | segList.addPt(li.getIntersection(0)); |
402 | 0 | return; |
403 | 0 | } |
404 | | |
405 | | // If no intersection is detected, it means the angle is so small |
406 | | // and/or the offset so large that the offsets segments don't |
407 | | // intersect. In this case we must add a "closing segment" to make |
408 | | // sure the buffer curve is continuous, |
409 | | // fairly smooth (e.g. no sharp reversals in direction) |
410 | | // and tracks the buffer correctly around the corner. |
411 | | // The curve connects the endpoints of the segment offsets to points |
412 | | // which lie toward the centre point of the corner. |
413 | | // The joining curve will not appear in the final buffer outline, |
414 | | // since it is completely internal to the buffer polygon. |
415 | | // |
416 | | // In complex buffer cases the closing segment may cut across many |
417 | | // other segments in the generated offset curve. |
418 | | // In order to improve the performance of the noding, the closing |
419 | | // segment should be kept as short as possible. |
420 | | // (But not too short, since that would defeat it's purpose). |
421 | | // This is the purpose of the closingSegLengthFactor heuristic value. |
422 | | |
423 | | /* |
424 | | * The intersection test above is vulnerable to robustness errors; |
425 | | * i.e. it may be that the offsets should intersect very close to |
426 | | * their endpoints, but aren't reported as such due to rounding. |
427 | | * To handle this situation appropriately, we use the following test: |
428 | | * If the offset points are very close, don't add closing segments |
429 | | * but simply use one of the offset points |
430 | | */ |
431 | | |
432 | 0 | if(offset0.p1.distance(offset1.p0) < |
433 | 0 | distance * INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR) { |
434 | 0 | segList.addPt(offset0.p1); |
435 | 0 | } |
436 | 0 | else { |
437 | | // add endpoint of this segment offset |
438 | 0 | segList.addPt(offset0.p1); |
439 | | |
440 | | // Add "closing segment" of required length. |
441 | 0 | if(closingSegLengthFactor > 0) { |
442 | 0 | Coordinate mid0( |
443 | 0 | (closingSegLengthFactor * offset0.p1.x + s1.x) / (closingSegLengthFactor + 1), |
444 | 0 | (closingSegLengthFactor * offset0.p1.y + s1.y) / (closingSegLengthFactor + 1) |
445 | 0 | ); |
446 | 0 | segList.addPt(mid0); |
447 | |
|
448 | 0 | Coordinate mid1( |
449 | 0 | (closingSegLengthFactor * offset1.p0.x + s1.x) / (closingSegLengthFactor + 1), |
450 | 0 | (closingSegLengthFactor * offset1.p0.y + s1.y) / (closingSegLengthFactor + 1) |
451 | 0 | ); |
452 | 0 | segList.addPt(mid1); |
453 | 0 | } |
454 | 0 | else { |
455 | | // This branch is not expected to be used |
456 | | // except for testing purposes. |
457 | | // It is equivalent to the JTS 1.9 logic for |
458 | | // closing segments (which results in very poor |
459 | | // performance for large buffer distances) |
460 | 0 | segList.addPt(s1); |
461 | 0 | } |
462 | | |
463 | | // add start point of next segment offset |
464 | 0 | segList.addPt(offset1.p0); |
465 | 0 | } |
466 | 0 | } |
467 | | |
468 | | /* private */ |
469 | | void |
470 | | OffsetSegmentGenerator::addMitreJoin(const geom::Coordinate& cornerPt, |
471 | | const geom::LineSegment& p_offset0, |
472 | | const geom::LineSegment& p_offset1, |
473 | | double p_distance) |
474 | 0 | { |
475 | 0 | double mitreLimitDistance = bufParams.getMitreLimit() * p_distance; |
476 | | /** |
477 | | * First try a non-beveled join. |
478 | | * Compute the intersection point of the lines determined by the offsets. |
479 | | * Parallel or collinear lines will return a null point ==> need to be beveled |
480 | | * |
481 | | * Note: This computation is unstable if the offset segments are nearly collinear. |
482 | | * However, this situation should have been eliminated earlier by the check |
483 | | * for whether the offset segment endpoints are almost coincident |
484 | | */ |
485 | 0 | CoordinateXY intPt = algorithm::CGAlgorithmsDD::intersection(p_offset0.p0, p_offset0.p1, p_offset1.p0, p_offset1.p1); |
486 | |
|
487 | 0 | if (!intPt.isNull() && intPt.distance(cornerPt) <= mitreLimitDistance) { |
488 | 0 | segList.addPt(Coordinate(intPt)); |
489 | 0 | return; |
490 | 0 | } |
491 | | /** |
492 | | * In case the mitre limit is very small, try a plain bevel. |
493 | | * Use it if it's further than the limit. |
494 | | */ |
495 | 0 | double bevelDist = algorithm::Distance::pointToSegment(cornerPt, p_offset0.p1, p_offset1.p0); |
496 | 0 | if (bevelDist >= mitreLimitDistance) { |
497 | 0 | addBevelJoin(p_offset0, p_offset1); |
498 | 0 | return; |
499 | 0 | } |
500 | | /** |
501 | | * Have to construct a limited mitre bevel. |
502 | | */ |
503 | 0 | addLimitedMitreJoin(p_offset0, p_offset1, p_distance, mitreLimitDistance); |
504 | 0 | } |
505 | | |
506 | | |
507 | | /* private */ |
508 | | void |
509 | | OffsetSegmentGenerator::addLimitedMitreJoin( |
510 | | const geom::LineSegment& p_offset0, |
511 | | const geom::LineSegment& p_offset1, |
512 | | double p_distance, |
513 | | double p_mitreLimitDistance) |
514 | 0 | { |
515 | 0 | const Coordinate& cornerPt = seg0.p1; |
516 | | |
517 | | // oriented angle of the corner formed by segments |
518 | 0 | double angInterior = Angle::angleBetweenOriented(seg0.p0, cornerPt, seg1.p1); |
519 | | // half of the interior angle |
520 | 0 | double angInterior2 = angInterior/2.0; |
521 | | |
522 | | // direction of bisector of the interior angle between the segments |
523 | 0 | double dir0 = Angle::angle(cornerPt, seg0.p0); |
524 | 0 | double dirBisector = Angle::normalize(dir0 + angInterior2); |
525 | | // rotating by PI gives the bisector of the outside angle, |
526 | | // which is the direction of the bevel midpoint from the corner apex |
527 | 0 | double dirBisectorOut = Angle::normalize(dirBisector + MATH_PI); |
528 | | |
529 | | // compute the midpoint of the bevel segment |
530 | 0 | Coordinate bevelMidPt = project(cornerPt, p_mitreLimitDistance, dirBisectorOut); |
531 | | |
532 | | // slope angle of bevel segment |
533 | 0 | double dirBevel = Angle::normalize(dirBisectorOut + Angle::PI_OVER_2); |
534 | | |
535 | | // compute the candidate bevel segment by projecting both sides of the midpoint |
536 | 0 | Coordinate bevel0 = project(bevelMidPt, p_distance, dirBevel); |
537 | 0 | Coordinate bevel1 = project(bevelMidPt, p_distance, dirBevel + MATH_PI); |
538 | |
|
539 | 0 | Coordinate bevelInt0(Intersection::intersectionLineSegment(p_offset0.p0, p_offset0.p1, bevel0, bevel1)); |
540 | 0 | Coordinate bevelInt1(Intersection::intersectionLineSegment(p_offset1.p0, p_offset1.p1, bevel0, bevel1)); |
541 | | |
542 | | //-- add the limited bevel, if it intersects the offsets |
543 | 0 | if (!bevelInt0.isNull() && !bevelInt1.isNull()) { |
544 | 0 | segList.addPt(bevelInt0); |
545 | 0 | segList.addPt(bevelInt1); |
546 | 0 | return; |
547 | 0 | } |
548 | | /** |
549 | | * If the corner is very flat or the mitre limit is very small |
550 | | * the limited bevel segment may not intersect the offsets. |
551 | | * In this case just bevel the join. |
552 | | */ |
553 | 0 | addBevelJoin(p_offset0, p_offset1); |
554 | 0 | } |
555 | | |
556 | | |
557 | | /* private static */ |
558 | | Coordinate |
559 | | OffsetSegmentGenerator::project(const Coordinate& pt, double d, double dir) |
560 | 0 | { |
561 | 0 | double sindir, cosdir; |
562 | 0 | Angle::sinCosSnap(dir, sindir, cosdir); |
563 | 0 | double x = pt.x + d * cosdir; |
564 | 0 | double y = pt.y + d * sindir; |
565 | 0 | return Coordinate(x, y); |
566 | 0 | } |
567 | | |
568 | | |
569 | | /* private */ |
570 | | void |
571 | | OffsetSegmentGenerator::addBevelJoin(const geom::LineSegment& p_offset0, |
572 | | const geom::LineSegment& p_offset1) |
573 | 0 | { |
574 | 0 | segList.addPt(p_offset0.p1); |
575 | 0 | segList.addPt(p_offset1.p0); |
576 | 0 | } |
577 | | |
578 | | } // namespace geos.operation.buffer |
579 | | } // namespace geos.operation |
580 | | } // namespace geos |