[geos-commits] r3301 - in trunk: include/geos/operation/buffer
src/operation/buffer
svn_geos at osgeo.org
svn_geos at osgeo.org
Wed Apr 27 05:42:31 EDT 2011
Author: strk
Date: 2011-04-27 02:42:31 -0700 (Wed, 27 Apr 2011)
New Revision: 3301
Added:
trunk/include/geos/operation/buffer/OffsetSegmentGenerator.h
trunk/include/geos/operation/buffer/OffsetSegmentString.h
trunk/src/operation/buffer/OffsetSegmentGenerator.cpp
Removed:
trunk/include/geos/operation/buffer/OffsetCurveVertexList.h
Modified:
trunk/include/geos/operation/buffer/BufferBuilder.h
trunk/include/geos/operation/buffer/BufferSubgraph.h
trunk/include/geos/operation/buffer/Makefile.am
trunk/include/geos/operation/buffer/OffsetCurveBuilder.h
trunk/src/operation/buffer/BufferBuilder.cpp
trunk/src/operation/buffer/BufferSubgraph.cpp
trunk/src/operation/buffer/Makefile.am
trunk/src/operation/buffer/OffsetCurveBuilder.cpp
Log:
Refactored offset curve generation (from JTS-1.12)
Modified: trunk/include/geos/operation/buffer/BufferBuilder.h
===================================================================
--- trunk/include/geos/operation/buffer/BufferBuilder.h 2011-04-27 09:42:13 UTC (rev 3300)
+++ trunk/include/geos/operation/buffer/BufferBuilder.h 2011-04-27 09:42:31 UTC (rev 3301)
@@ -4,7 +4,8 @@
* GEOS - Geometry Engine Open Source
* http://geos.refractions.net
*
- * Copyright (C) 2009 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2009-2011 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2008-2010 Safe Software Inc.
* Copyright (C) 2006-2007 Refractions Research Inc.
*
* This is free software; you can redistribute and/or modify it under
@@ -14,7 +15,7 @@
*
**********************************************************************
*
- * Last port: operation/buffer/BufferBuilder.java r320 (JTS-1.12)
+ * Last port: operation/buffer/BufferBuilder.java r378 (JTS-1.12)
*
**********************************************************************/
Modified: trunk/include/geos/operation/buffer/BufferSubgraph.h
===================================================================
--- trunk/include/geos/operation/buffer/BufferSubgraph.h 2011-04-27 09:42:13 UTC (rev 3300)
+++ trunk/include/geos/operation/buffer/BufferSubgraph.h 2011-04-27 09:42:31 UTC (rev 3301)
@@ -13,7 +13,7 @@
*
**********************************************************************
*
- * Last port: operation/buffer/BufferSubgraph.java r320 (JTS-1.12)
+ * Last port: operation/buffer/BufferSubgraph.java r378 (JTS-1.12)
*
**********************************************************************/
Modified: trunk/include/geos/operation/buffer/Makefile.am
===================================================================
--- trunk/include/geos/operation/buffer/Makefile.am 2011-04-27 09:42:13 UTC (rev 3300)
+++ trunk/include/geos/operation/buffer/Makefile.am 2011-04-27 09:42:31 UTC (rev 3301)
@@ -15,6 +15,7 @@
BufferSubgraph.h \
OffsetCurveBuilder.h \
OffsetCurveSetBuilder.h \
- OffsetCurveVertexList.h \
+ OffsetSegmentGenerator.h \
+ OffsetSegmentString.h \
RightmostEdgeFinder.h \
SubgraphDepthLocater.h
Modified: trunk/include/geos/operation/buffer/OffsetCurveBuilder.h
===================================================================
--- trunk/include/geos/operation/buffer/OffsetCurveBuilder.h 2011-04-27 09:42:13 UTC (rev 3300)
+++ trunk/include/geos/operation/buffer/OffsetCurveBuilder.h 2011-04-27 09:42:31 UTC (rev 3301)
@@ -4,7 +4,7 @@
* GEOS - Geometry Engine Open Source
* http://geos.refractions.net
*
- * Copyright (C) 2009 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2009-2011 Sandro Santilli <strk at keybit.net>
* Copyright (C) 2006-2007 Refractions Research Inc.
*
* This is free software; you can redistribute and/or modify it under
@@ -14,7 +14,7 @@
*
**********************************************************************
*
- * Last port: operation/buffer/OffsetCurveBuilder.java r262 (JTS-1.11+)
+ * Last port: operation/buffer/OffsetCurveBuilder.java r378 (JTS-1.12)
*
**********************************************************************/
@@ -23,14 +23,12 @@
#include <geos/export.h>
-#include <vector>
-
-#include <geos/algorithm/LineIntersector.h> // for composition
-#include <geos/geom/Coordinate.h> // for composition
-#include <geos/geom/LineSegment.h> // for composition
#include <geos/operation/buffer/BufferParameters.h> // for composition
-#include <geos/operation/buffer/OffsetCurveVertexList.h> // for composition
+#include <geos/operation/buffer/OffsetSegmentGenerator.h>
+#include <vector>
+#include <memory> // for auto_ptr
+
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class
@@ -59,7 +57,7 @@
* it may contain self-intersections (and usually will).
* The final buffer polygon is computed by forming a topological graph
* of all the noded raw curves and tracing outside contours.
- * The points in the raw curve are rounded to the required precision model.
+ * The points in the raw curve are rounded to a given geom::PrecisionModel.
*
*/
class GEOS_DLL OffsetCurveBuilder {
@@ -73,9 +71,22 @@
* the buffer builder.
*/
OffsetCurveBuilder(const geom::PrecisionModel *newPrecisionModel,
- const BufferParameters& bufParams);
+ const BufferParameters& nBufParams)
+ :
+ distance(0.0),
+ precisionModel(newPrecisionModel),
+ bufParams(nBufParams)
+ {}
- ~OffsetCurveBuilder();
+ /**
+ * Gets the buffer parameters being used to generate the curve.
+ *
+ * @return the buffer parameters being used
+ */
+ const BufferParameters& getBufferParameters() const
+ {
+ return bufParams;
+ }
/**
* This method handles single points as well as lines.
@@ -124,136 +135,12 @@
private:
- /// The mitre will be beveled if it exceeds the mitre ratio limit.
- //
- /// @param offset0 the first offset segment
- /// @param offset1 the second offset segment
- /// @param distance the offset distance
- ///
- void addMitreJoin(const geom::Coordinate& p,
- const geom::LineSegment& offset0,
- const geom::LineSegment& offset1,
- double distance);
-
- /// Adds a limited mitre join connecting the two reflex offset segments.
- //
- /// A limited mitre is a mitre which is beveled at the distance
- /// determined by the mitre ratio limit.
- ///
- /// @param offset0 the first offset segment
- /// @param offset1 the second offset segment
- /// @param distance the offset distance
- /// @param mitreLimit the mitre limit ratio
- ///
- void addLimitedMitreJoin(
- const geom::LineSegment& offset0,
- const geom::LineSegment& offset1,
- double distance, double mitreLimit);
-
- /// \brief
- /// Adds a bevel join connecting the two offset segments
- /// around a reflex corner.
- //
- /// @param offset0 the first offset segment
- /// @param offset1 the second offset segment
- ///
- void addBevelJoin(const geom::LineSegment& offset0,
- const geom::LineSegment& offset1);
-
-
- /**
- * Factor which controls how close curve vertices can be to be snapped
- */
- static const double CURVE_VERTEX_SNAP_DISTANCE_FACTOR; // 1.0E-6;
-
- static const double PI; // 3.14159265358979
-
- static const double MAX_CLOSING_SEG_LEN; // 3.0
-
- /**
- * Factor which controls how close offset segments can be to
- * skip adding a filler or mitre.
- */
- static const double OFFSET_SEGMENT_SEPARATION_FACTOR; // 1.0E-3;
-
- /**
- * Factor which controls how close curve vertices on inside turns
- * can be to be snapped
- */
- static const double INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR; // 1.0E-3;
-
- /** \brief
- * Factor which determines how short closing segs can be
- * for round buffers
- */
- static const int MAX_CLOSING_SEG_FRACTION = 80;
-
- algorithm::LineIntersector li;
-
- /** \brief
- * The angle quantum with which to approximate a fillet curve
- * (based on the input # of quadrant segments)
- */
- double filletAngleQuantum;
-
- /** \brief
- * the max error of approximation (distance) between a quad segment and
- * the true fillet curve
- */
- double maxCurveSegmentError;
-
- /// Owned by this object, destroyed by dtor
- //
- /// This actually gets created multiple times
- /// and each of the old versions is pushed
- /// to the ptLists std::vector to ensure all
- /// created CoordinateSequences are properly
- /// destroyed.
- ///
- OffsetCurveVertexList vertexList;
-
double distance;
const geom::PrecisionModel* precisionModel;
const BufferParameters& bufParams;
- /// The Closing Segment Factor controls how long "closing
- /// segments" are. Closing segments are added at the middle of
- /// inside corners to ensure a smoother boundary for the buffer
- /// offset curve. In some cases (particularly for round joins
- /// with default-or-better quantization) the closing segments
- /// can be made quite short. This substantially improves
- /// performance (due to fewer intersections being created).
- ///
- /// A closingSegFactor of 0 results in lines to the corner vertex.
- /// A closingSegFactor of 1 results in lines halfway
- /// to the corner vertex.
- /// A closingSegFactor of 80 results in lines 1/81 of the way
- /// to the corner vertex (this option is reasonable for the very
- /// common default situation of round joins and quadrantSegs >= 8).
- ///
- /// The default is 1.
- ///
- int closingSegFactor; // 1;
-
- geom::Coordinate s0, s1, s2;
-
- geom::LineSegment seg0;
-
- geom::LineSegment seg1;
-
- geom::LineSegment offset0;
-
- geom::LineSegment offset1;
-
- int side;
-
- // Not in JTS, used for single-sided buffers
- int endCapIndex;
-
- void init(double newDistance);
-
/**
* Use a value which results in a potential distance error which is
* significantly less than the error due to
@@ -272,90 +159,22 @@
*/
double simplifyTolerance(double bufDistance);
- void computeLineBufferCurve(const geom::CoordinateSequence& inputPts);
+ void computeLineBufferCurve(const geom::CoordinateSequence& inputPts,
+ OffsetSegmentGenerator& segGen);
+ void computeSingleSidedBufferCurve(const geom::CoordinateSequence& inputPts,
+ bool isRightSide,
+ OffsetSegmentGenerator& segGen);
+
void computeRingBufferCurve(const geom::CoordinateSequence& inputPts,
- int side);
+ int side, OffsetSegmentGenerator& segGen);
- void initSideSegments(const geom::Coordinate &nS1,
- const geom::Coordinate &nS2, int nSide);
+ std::auto_ptr<OffsetSegmentGenerator> getSegGen(double dist);
- void addNextSegment(const geom::Coordinate &p, bool addStartPoint);
+ void computePointCurve(const geom::Coordinate& pt,
+ OffsetSegmentGenerator& segGen);
- void addCollinear(bool addStartPoint);
- /// Adds the offset points for an outside (convex) turn
- //
- /// @param orientation
- /// @param addStartPoint
- ///
- void addOutsideTurn(int orientation, bool addStartPoint);
-
- /// Adds the offset points for an inside (concave) turn
- //
- /// @param orientation
- /// @param addStartPoint
- ///
- void addInsideTurn(int orientation, bool addStartPoint);
-
- /// Add last offset point
- void addLastSegment();
-
- /** \brief
- * Compute an offset segment for an input segment on a given
- * side and at a given distance.
- *
- * The offset points are computed in full double precision,
- * for accuracy.
- *
- * @param seg the segment to offset
- * @param side the side of the segment the offset lies on
- * @param distance the offset distance
- * @param offset the points computed for the offset segment
- */
- void computeOffsetSegment(const geom::LineSegment& seg,
- int side, double distance,
- geom::LineSegment& offset);
-
- /// \brief
- /// Add an end cap around point p1, terminating a line segment
- /// coming from p0
- void addLineEndCap(const geom::Coordinate &p0,
- const geom::Coordinate &p1);
-
- /**
- * Adds points for a circular fillet around a reflex corner.
- *
- * Adds the start and end points
- *
- * @param p base point of curve
- * @param p0 start point of fillet curve
- * @param p1 endpoint of fillet curve
- * @param direction the orientation of the fillet
- * @param radius the radius of the fillet
- */
- void addFillet(const geom::Coordinate &p, const geom::Coordinate &p0,
- const geom::Coordinate &p1,
- int direction, double radius);
-
- /**
- * Adds points for a circular fillet arc between two specified angles.
- *
- * The start and end point for the fillet are not added -
- * the caller must add them if required.
- *
- * @param direction is -1 for a CW angle, 1 for a CCW angle
- * @param radius the radius of the fillet
- */
- void addFillet(const geom::Coordinate &p, double startAngle,
- double endAngle, int direction, double radius);
-
- /// Adds a CW circle around a point
- void addCircle(const geom::Coordinate &p, double distance);
-
- /// Adds a CW square around a point
- void addSquare(const geom::Coordinate &p, double distance);
-
// Declare type as noncopyable
OffsetCurveBuilder(const OffsetCurveBuilder& other);
OffsetCurveBuilder& operator=(const OffsetCurveBuilder& rhs);
@@ -371,13 +190,3 @@
#endif // ndef GEOS_OP_BUFFER_OFFSETCURVEBUILDER_H
-/**********************************************************************
- * $Log$
- * Revision 1.2 2006/03/27 17:04:17 strk
- * Cleanups and explicit initializations
- *
- * Revision 1.1 2006/03/14 00:19:40 strk
- * opBuffer.h split, streamlined headers in some (not all) files in operation/buffer/
- *
- **********************************************************************/
-
Deleted: trunk/include/geos/operation/buffer/OffsetCurveVertexList.h
===================================================================
--- trunk/include/geos/operation/buffer/OffsetCurveVertexList.h 2011-04-27 09:42:13 UTC (rev 3300)
+++ trunk/include/geos/operation/buffer/OffsetCurveVertexList.h 2011-04-27 09:42:31 UTC (rev 3301)
@@ -1,197 +0,0 @@
-/**********************************************************************
- * $Id$
- *
- * GEOS - Geometry Engine Open Source
- * http://geos.refractions.net
- *
- * Copyright (C) 2007 Refractions Research Inc.
- *
- * This is free software; you can redistribute and/or modify it under
- * the terms of the GNU Lesser General Public Licence as published
- * by the Free Software Foundation.
- * See the COPYING file for more information.
- *
- **********************************************************************
- *
- * Last port: operation/buffer/OffsetCurveVertexList.java r262 (JTS-1.11+)
- *
- **********************************************************************/
-
-#ifndef GEOS_OP_BUFFER_OFFSETCURVEVERTEXLIST_H
-#define GEOS_OP_BUFFER_OFFSETCURVEVERTEXLIST_H
-
-#include <geos/geom/Coordinate.h> // for inlines
-#include <geos/geom/CoordinateSequence.h> // for inlines
-#include <geos/geom/CoordinateArraySequence.h> // for composition
-#include <geos/geom/PrecisionModel.h> // for inlines
-
-#include <vector>
-#include <memory>
-#include <cassert>
-
-// Forward declarations
-namespace geos {
- namespace geom {
- //class CoordinateSequence;
- //class PrecisionModel;
- }
-}
-
-namespace geos {
-namespace operation { // geos.operation
-namespace buffer { // geos.operation.buffer
-
-// ---------------------------------------------
-// OffsetCurveVertexList
-// ---------------------------------------------
-
-/// A list of the vertices in a constructed offset curve.
-//
-/// Automatically removes close adjacent vertices.
-///
-class OffsetCurveVertexList
-{
-
-private:
-
- geom::CoordinateArraySequence* ptList;
-
- const geom::PrecisionModel* precisionModel;
-
- /** \brief
- * The distance below which two adjacent points on the curve
- * are considered to be coincident.
- *
- * This is chosen to be a small fraction of the offset distance.
- */
- double minimumVertexDistance;
-
- /** \brief
- * Tests whether the given point duplicates the previous
- * point in the list (up to tolerance)
- *
- * @param pt
- * @return true if the point duplicates the previous point
- */
- bool isDuplicate(const geom::Coordinate& pt)
- {
- if (ptList->size() < 1)
- return false;
- const geom::Coordinate& lastPt = ptList->back();
- double ptDist = pt.distance(lastPt);
- if (ptDist < minimumVertexDistance)
- return true;
- return false;
- }
-
-
-public:
-
- friend std::ostream& operator<< (std::ostream& os, const OffsetCurveVertexList& node);
-
- OffsetCurveVertexList()
- :
- ptList(new geom::CoordinateArraySequence()),
- precisionModel(NULL),
- minimumVertexDistance (0.0)
- {
- }
-
- ~OffsetCurveVertexList()
- {
- delete ptList;
- }
-
- void reset()
- {
- if ( ptList ) ptList->clear();
- else ptList = new geom::CoordinateArraySequence();
-
- precisionModel = NULL;
- minimumVertexDistance = 0.0;
- }
-
- void setPrecisionModel(const geom::PrecisionModel* nPrecisionModel)
- {
- precisionModel = nPrecisionModel;
- }
-
- void setMinimumVertexDistance(double nMinVertexDistance)
- {
- minimumVertexDistance = nMinVertexDistance;
- }
-
- void addPt(const geom::Coordinate& pt)
- {
- assert(precisionModel);
-
- geom::Coordinate bufPt = pt;
- precisionModel->makePrecise(bufPt);
- // don't add duplicate (or near-duplicate) points
- if (isDuplicate(bufPt))
- {
- return;
- }
- // we ask to allow repeated as we checked this ourself
- // (JTS uses a vector for ptList, not a CoordinateSequence,
- // we should do the same)
- ptList->add(bufPt, true);
- }
-
- /// Check that points are a ring
- //
- /// add the startpoint again if they are not
- void closeRing()
- {
- if (ptList->size() < 1) return;
- const geom::Coordinate& startPt = ptList->front();
- const geom::Coordinate& lastPt = ptList->back();
- if (startPt.equals(lastPt)) return;
- // we ask to allow repeated as we checked this ourself
- ptList->add(startPt, true);
- }
-
- /// Get coordinates by taking ownership of them
- //
- /// After this call, the coordinates reference in
- /// this object are dropped. Calling twice will
- /// segfault...
- ///
- /// FIXME: refactor memory management of this
- ///
- geom::CoordinateSequence* getCoordinates()
- {
- closeRing();
- geom::CoordinateSequence* ret = ptList;
- ptList = 0;
- return ret;
- }
-
- inline int size() const { return ptList ? ptList->size() : 0 ; }
-
-};
-
-inline std::ostream& operator<< (std::ostream& os, const OffsetCurveVertexList& lst)
-{
- if ( lst.ptList )
- {
- os << *(lst.ptList);
- }
- else
- {
- os << "empty (consumed?)";
- }
- return os;
-}
-
-} // namespace geos.operation.buffer
-} // namespace geos.operation
-} // namespace geos
-
-
-#endif // ndef GEOS_OP_BUFFER_OFFSETCURVEVERTEXLIST_H
-
-/**********************************************************************
- * $Log$
- **********************************************************************/
-
Added: trunk/include/geos/operation/buffer/OffsetSegmentGenerator.h
===================================================================
--- trunk/include/geos/operation/buffer/OffsetSegmentGenerator.h (rev 0)
+++ trunk/include/geos/operation/buffer/OffsetSegmentGenerator.h 2011-04-27 09:42:31 UTC (rev 3301)
@@ -0,0 +1,352 @@
+/**********************************************************************
+ * $Id$
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.refractions.net
+ *
+ * Copyright (C) 2011 Sandro Santilli <strk at keybit.net>
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************
+ *
+ * Last port: operation/buffer/OffsetSegmentGenerator.java r378 (JTS-1.12)
+ *
+ **********************************************************************/
+
+#ifndef GEOS_OP_BUFFER_OFFSETSEGMENTGENERATOR_H
+#define GEOS_OP_BUFFER_OFFSETSEGMENTGENERATOR_H
+
+#include <geos/export.h>
+
+#include <vector>
+
+#include <geos/algorithm/LineIntersector.h> // for composition
+#include <geos/geom/Coordinate.h> // for composition
+#include <geos/geom/LineSegment.h> // for composition
+#include <geos/operation/buffer/BufferParameters.h> // for composition
+#include <geos/operation/buffer/OffsetSegmentString.h> // for composition
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4251) // warning C4251: needs to have dll-interface to be used by clients of class
+#endif
+
+// Forward declarations
+namespace geos {
+ namespace geom {
+ class CoordinateSequence;
+ class PrecisionModel;
+ }
+}
+
+namespace geos {
+namespace operation { // geos.operation
+namespace buffer { // geos.operation.buffer
+
+/**
+ * Generates segments which form an offset curve.
+ * Supports all end cap and join options
+ * provided for buffering.
+ * Implements various heuristics to
+ * produce smoother, simpler curves which are
+ * still within a reasonable tolerance of the
+ * true curve.
+ *
+ * @author Martin Davis
+ *
+ */
+class GEOS_DLL OffsetSegmentGenerator {
+
+public:
+
+ /*
+ * @param nBufParams buffer parameters, this object will
+ * keep a reference to the passed parameters
+ * so caller must make sure the object is
+ * kept alive for the whole lifetime of
+ * the buffer builder.
+ */
+ OffsetSegmentGenerator(const geom::PrecisionModel *newPrecisionModel,
+ const BufferParameters& bufParams, double distance);
+
+ /**
+ * Tests whether the input has a narrow concave angle
+ * (relative to the offset distance).
+ * In this case the generated offset curve will contain self-intersections
+ * and heuristic closing segments.
+ * This is expected behaviour in the case of buffer curves.
+ * For pure offset curves,
+ * the output needs to be further treated
+ * before it can be used.
+ *
+ * @return true if the input has a narrow concave angle
+ */
+ bool hasNarrowConcaveAngle() const
+ {
+ return _hasNarrowConcaveAngle;
+ }
+
+ void initSideSegments(const geom::Coordinate &nS1,
+ const geom::Coordinate &nS2, int nSide);
+
+ /// Get coordinates by taking ownership of them
+ //
+ /// After this call, the coordinates reference in
+ /// this object are dropped. Calling twice will
+ /// segfault...
+ ///
+ /// FIXME: refactor memory management of this
+ ///
+ void getCoordinates(std::vector<geom::CoordinateSequence*>& to) {
+ to.push_back(segList.getCoordinates());
+ }
+
+ void closeRing() {
+ segList.closeRing();
+ }
+
+ /// Adds a CW circle around a point
+ void createCircle(const geom::Coordinate &p, double distance);
+
+ /// Adds a CW square around a point
+ void createSquare(const geom::Coordinate &p, double distance);
+
+ /// Add first offset point
+ void addFirstSegment()
+ {
+ segList.addPt(offset1.p0);
+ }
+
+ /// Add last offset point
+ void addLastSegment()
+ {
+ segList.addPt(offset1.p1);
+ }
+
+ void addNextSegment(const geom::Coordinate &p, bool addStartPoint);
+
+ /// \brief
+ /// Add an end cap around point p1, terminating a line segment
+ /// coming from p0
+ void addLineEndCap(const geom::Coordinate &p0,
+ const geom::Coordinate &p1);
+
+ void addSegments(const geom::CoordinateSequence& pts, bool isForward)
+ {
+ segList.addPts(pts, isForward);
+ }
+
+private:
+
+ /**
+ * Factor which controls how close offset segments can be to
+ * skip adding a filler or mitre.
+ */
+ static const double OFFSET_SEGMENT_SEPARATION_FACTOR; // 1.0E-3;
+
+ /**
+ * Factor which controls how close curve vertices on inside turns
+ * can be to be snapped
+ */
+ static const double INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR; // 1.0E-3;
+
+ /**
+ * Factor which controls how close curve vertices can be to be snapped
+ */
+ static const double CURVE_VERTEX_SNAP_DISTANCE_FACTOR; // 1.0E-6;
+
+ /**
+ * Factor which determines how short closing segs can be for round buffers
+ */
+ static const int MAX_CLOSING_SEG_LEN_FACTOR = 80;
+
+ /** \brief
+ * the max error of approximation (distance) between a quad segment and
+ * the true fillet curve
+ */
+ double maxCurveSegmentError; // 0.0
+
+ /** \brief
+ * The angle quantum with which to approximate a fillet curve
+ * (based on the input # of quadrant segments)
+ */
+ double filletAngleQuantum;
+
+ /// The Closing Segment Factor controls how long "closing
+ /// segments" are. Closing segments are added at the middle of
+ /// inside corners to ensure a smoother boundary for the buffer
+ /// offset curve. In some cases (particularly for round joins
+ /// with default-or-better quantization) the closing segments
+ /// can be made quite short. This substantially improves
+ /// performance (due to fewer intersections being created).
+ ///
+ /// A closingSegFactor of 0 results in lines to the corner vertex.
+ /// A closingSegFactor of 1 results in lines halfway
+ /// to the corner vertex.
+ /// A closingSegFactor of 80 results in lines 1/81 of the way
+ /// to the corner vertex (this option is reasonable for the very
+ /// common default situation of round joins and quadrantSegs >= 8).
+ ///
+ /// The default is 1.
+ ///
+ int closingSegLengthFactor; // 1;
+
+ /// Owned by this object, destroyed by dtor
+ //
+ /// This actually gets created multiple times
+ /// and each of the old versions is pushed
+ /// to the ptLists std::vector to ensure all
+ /// created CoordinateSequences are properly
+ /// destroyed.
+ ///
+ OffsetSegmentString segList;
+
+ double distance;
+
+ const geom::PrecisionModel* precisionModel;
+
+ const BufferParameters& bufParams;
+
+ algorithm::LineIntersector li;
+
+ geom::Coordinate s0, s1, s2;
+
+ geom::LineSegment seg0;
+
+ geom::LineSegment seg1;
+
+ geom::LineSegment offset0;
+
+ geom::LineSegment offset1;
+
+ int side;
+
+ bool _hasNarrowConcaveAngle; // =false
+
+ void addCollinear(bool addStartPoint);
+
+ /// The mitre will be beveled if it exceeds the mitre ratio limit.
+ //
+ /// @param offset0 the first offset segment
+ /// @param offset1 the second offset segment
+ /// @param distance the offset distance
+ ///
+ void addMitreJoin(const geom::Coordinate& p,
+ const geom::LineSegment& offset0,
+ const geom::LineSegment& offset1,
+ double distance);
+
+ /// Adds a limited mitre join connecting the two reflex offset segments.
+ //
+ /// A limited mitre is a mitre which is beveled at the distance
+ /// determined by the mitre ratio limit.
+ ///
+ /// @param offset0 the first offset segment
+ /// @param offset1 the second offset segment
+ /// @param distance the offset distance
+ /// @param mitreLimit the mitre limit ratio
+ ///
+ void addLimitedMitreJoin(
+ const geom::LineSegment& offset0,
+ const geom::LineSegment& offset1,
+ double distance, double mitreLimit);
+
+ /// \brief
+ /// Adds a bevel join connecting the two offset segments
+ /// around a reflex corner.
+ //
+ /// @param offset0 the first offset segment
+ /// @param offset1 the second offset segment
+ ///
+ void addBevelJoin(const geom::LineSegment& offset0,
+ const geom::LineSegment& offset1);
+
+ static const double PI; // 3.14159265358979
+
+ // Not in JTS, used for single-sided buffers
+ int endCapIndex;
+
+ void init(double newDistance);
+
+ /**
+ * Use a value which results in a potential distance error which is
+ * significantly less than the error due to
+ * the quadrant segment discretization.
+ * For QS = 8 a value of 100 is reasonable.
+ * This should produce a maximum of 1% distance error.
+ */
+ static const double SIMPLIFY_FACTOR; // 100.0;
+
+ /// Adds the offset points for an outside (convex) turn
+ //
+ /// @param orientation
+ /// @param addStartPoint
+ ///
+ void addOutsideTurn(int orientation, bool addStartPoint);
+
+ /// Adds the offset points for an inside (concave) turn
+ //
+ /// @param orientation
+ /// @param addStartPoint
+ ///
+ void addInsideTurn(int orientation, bool addStartPoint);
+
+ /** \brief
+ * Compute an offset segment for an input segment on a given
+ * side and at a given distance.
+ *
+ * The offset points are computed in full double precision,
+ * for accuracy.
+ *
+ * @param seg the segment to offset
+ * @param side the side of the segment the offset lies on
+ * @param distance the offset distance
+ * @param offset the points computed for the offset segment
+ */
+ void computeOffsetSegment(const geom::LineSegment& seg,
+ int side, double distance,
+ geom::LineSegment& offset);
+
+ /**
+ * Adds points for a circular fillet around a reflex corner.
+ *
+ * Adds the start and end points
+ *
+ * @param p base point of curve
+ * @param p0 start point of fillet curve
+ * @param p1 endpoint of fillet curve
+ * @param direction the orientation of the fillet
+ * @param radius the radius of the fillet
+ */
+ void addFillet(const geom::Coordinate &p, const geom::Coordinate &p0,
+ const geom::Coordinate &p1,
+ int direction, double radius);
+
+ /**
+ * Adds points for a circular fillet arc between two specified angles.
+ *
+ * The start and end point for the fillet are not added -
+ * the caller must add them if required.
+ *
+ * @param direction is -1 for a CW angle, 1 for a CCW angle
+ * @param radius the radius of the fillet
+ */
+ void addFillet(const geom::Coordinate &p, double startAngle,
+ double endAngle, int direction, double radius);
+
+};
+
+} // namespace geos::operation::buffer
+} // namespace geos::operation
+} // namespace geos
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+#endif // ndef GEOS_OP_BUFFER_OFFSETSEGMENTGENERATOR_H
+
Copied: trunk/include/geos/operation/buffer/OffsetSegmentString.h (from rev 3300, trunk/include/geos/operation/buffer/OffsetCurveVertexList.h)
===================================================================
--- trunk/include/geos/operation/buffer/OffsetSegmentString.h (rev 0)
+++ trunk/include/geos/operation/buffer/OffsetSegmentString.h 2011-04-27 09:42:31 UTC (rev 3301)
@@ -0,0 +1,199 @@
+/**********************************************************************
+ * $Id$
+ *
+ * GEOS - Geometry Engine Open Source
+ * http://geos.refractions.net
+ *
+ * Copyright (C) 2011 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2007 Refractions Research Inc.
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************
+ *
+ * Last port: operation/buffer/OffsetSegmentString.java r378 (JTS-1.12)
+ *
+ **********************************************************************/
+
+#ifndef GEOS_OP_BUFFER_OFFSETSEGMENTSTRING_H
+#define GEOS_OP_BUFFER_OFFSETSEGMENTSTRING_H
+
+#include <geos/geom/Coordinate.h> // for inlines
+#include <geos/geom/CoordinateSequence.h> // for inlines
+#include <geos/geom/CoordinateArraySequence.h> // for composition
+#include <geos/geom/PrecisionModel.h> // for inlines
+
+#include <vector>
+#include <memory>
+#include <cassert>
+
+namespace geos {
+namespace operation { // geos.operation
+namespace buffer { // geos.operation.buffer
+
+/// A dynamic list of the vertices in a constructed offset curve.
+//
+/// Automatically removes close vertices
+/// which are closer than a given tolerance.
+///
+/// @author Martin Davis
+///
+class OffsetSegmentString
+{
+
+private:
+
+ geom::CoordinateArraySequence* ptList;
+
+ const geom::PrecisionModel* precisionModel;
+
+ /** \brief
+ * The distance below which two adjacent points on the curve
+ * are considered to be coincident.
+ *
+ * This is chosen to be a small fraction of the offset distance.
+ */
+ double minimumVertexDistance;
+
+ /** \brief
+ * Tests whether the given point is redundant relative to the previous
+ * point in the list (up to tolerance)
+ *
+ * @param pt
+ * @return true if the point is redundant
+ */
+ bool isRedundant(const geom::Coordinate& pt) const
+ {
+ if (ptList->size() < 1)
+ return false;
+ const geom::Coordinate& lastPt = ptList->back();
+ double ptDist = pt.distance(lastPt);
+ if (ptDist < minimumVertexDistance)
+ return true;
+ return false;
+ }
+
+
+public:
+
+ friend std::ostream& operator<< (std::ostream& os, const OffsetSegmentString& node);
+
+ OffsetSegmentString()
+ :
+ ptList(new geom::CoordinateArraySequence()),
+ precisionModel(NULL),
+ minimumVertexDistance (0.0)
+ {
+ }
+
+ ~OffsetSegmentString()
+ {
+ delete ptList;
+ }
+
+ void reset()
+ {
+ if ( ptList ) ptList->clear();
+ else ptList = new geom::CoordinateArraySequence();
+
+ precisionModel = NULL;
+ minimumVertexDistance = 0.0;
+ }
+
+ void setPrecisionModel(const geom::PrecisionModel* nPrecisionModel)
+ {
+ precisionModel = nPrecisionModel;
+ }
+
+ void setMinimumVertexDistance(double nMinVertexDistance)
+ {
+ minimumVertexDistance = nMinVertexDistance;
+ }
+
+ void addPt(const geom::Coordinate& pt)
+ {
+ assert(precisionModel);
+
+ geom::Coordinate bufPt = pt;
+ precisionModel->makePrecise(bufPt);
+ // don't add duplicate (or near-duplicate) points
+ if (isRedundant(bufPt))
+ {
+ return;
+ }
+ // we ask to allow repeated as we checked this ourself
+ // (JTS uses a vector for ptList, not a CoordinateSequence,
+ // we should do the same)
+ ptList->add(bufPt, true);
+ }
+
+ void addPts(const geom::CoordinateSequence& pts, bool isForward)
+ {
+ if ( isForward ) {
+ for (size_t i=0, n=pts.size(); i<n; ++i) {
+ addPt(pts[i]);
+ }
+ } else {
+ for (size_t i=pts.size(); i>0; --i) {
+ addPt(pts[i-1]);
+ }
+ }
+ }
+
+ /// Check that points are a ring
+ //
+ /// add the startpoint again if they are not
+ void closeRing()
+ {
+ if (ptList->size() < 1) return;
+ const geom::Coordinate& startPt = ptList->front();
+ const geom::Coordinate& lastPt = ptList->back();
+ if (startPt.equals(lastPt)) return;
+ // we ask to allow repeated as we checked this ourself
+ ptList->add(startPt, true);
+ }
+
+ /// Get coordinates by taking ownership of them
+ //
+ /// After this call, the coordinates reference in
+ /// this object are dropped. Calling twice will
+ /// segfault...
+ ///
+ /// FIXME: refactor memory management of this
+ ///
+ geom::CoordinateSequence* getCoordinates()
+ {
+ closeRing();
+ geom::CoordinateSequence* ret = ptList;
+ ptList = 0;
+ return ret;
+ }
+
+ inline int size() const { return ptList ? ptList->size() : 0 ; }
+
+};
+
+inline std::ostream& operator<< (std::ostream& os,
+ const OffsetSegmentString& lst)
+{
+ if ( lst.ptList )
+ {
+ os << *(lst.ptList);
+ }
+ else
+ {
+ os << "empty (consumed?)";
+ }
+ return os;
+}
+
+} // namespace geos.operation.buffer
+} // namespace geos.operation
+} // namespace geos
+
+
+#endif // ndef GEOS_OP_BUFFER_OFFSETSEGMENTSTRING_H
+
Modified: trunk/src/operation/buffer/BufferBuilder.cpp
===================================================================
--- trunk/src/operation/buffer/BufferBuilder.cpp 2011-04-27 09:42:13 UTC (rev 3300)
+++ trunk/src/operation/buffer/BufferBuilder.cpp 2011-04-27 09:42:31 UTC (rev 3301)
@@ -4,8 +4,8 @@
* GEOS - Geometry Engine Open Source
* http://geos.refractions.net
*
+ * Copyright (C) 2009-2011 Sandro Santilli <strk at keybit.net>
* Copyright (C) 2008-2010 Safe Software Inc.
- * Copyright (C) 2009 Sandro Santilli <strk at keybit.net>
* Copyright (C) 2005-2007 Refractions Research Inc.
* Copyright (C) 2001-2002 Vivid Solutions Inc.
*
@@ -16,7 +16,7 @@
*
**********************************************************************
*
- * Last port: operation/buffer/BufferBuilder.java r320 (JTS-1.12)
+ * Last port: operation/buffer/BufferBuilder.java r378 (JTS-1.12)
*
**********************************************************************/
Modified: trunk/src/operation/buffer/BufferSubgraph.cpp
===================================================================
--- trunk/src/operation/buffer/BufferSubgraph.cpp 2011-04-27 09:42:13 UTC (rev 3300)
+++ trunk/src/operation/buffer/BufferSubgraph.cpp 2011-04-27 09:42:31 UTC (rev 3301)
@@ -14,7 +14,7 @@
*
**********************************************************************
*
- * Last port: operation/buffer/BufferSubgraph.java r320 (JTS-1.12)
+ * Last port: operation/buffer/BufferSubgraph.java r378 (JTS-1.12)
*
**********************************************************************/
Modified: trunk/src/operation/buffer/Makefile.am
===================================================================
--- trunk/src/operation/buffer/Makefile.am 2011-04-27 09:42:13 UTC (rev 3300)
+++ trunk/src/operation/buffer/Makefile.am 2011-04-27 09:42:31 UTC (rev 3301)
@@ -17,6 +17,7 @@
BufferSubgraph.cpp \
OffsetCurveBuilder.cpp \
OffsetCurveSetBuilder.cpp \
+ OffsetSegmentGenerator.cpp \
RightmostEdgeFinder.cpp \
SubgraphDepthLocater.cpp \
$(NULL)
Modified: trunk/src/operation/buffer/OffsetCurveBuilder.cpp
===================================================================
--- trunk/src/operation/buffer/OffsetCurveBuilder.cpp 2011-04-27 09:42:13 UTC (rev 3300)
+++ trunk/src/operation/buffer/OffsetCurveBuilder.cpp 2011-04-27 09:42:31 UTC (rev 3301)
@@ -4,7 +4,7 @@
* GEOS-Geometry Engine Open Source
* http://geos.refractions.net
*
- * Copyright (C) 2009 Sandro Santilli <strk at keybit.net>
+ * Copyright (C) 2009-2011 Sandro Santilli <strk at keybit.net>
* Copyright (C) 2005 Refractions Research Inc.
* Copyright (C) 2001-2002 Vivid Solutions Inc.
*
@@ -15,7 +15,7 @@
*
**********************************************************************
*
- * Last port: operation/buffer/OffsetCurveBuilder.java r262 (JTS-1.11+)
+ * Last port: operation/buffer/OffsetCurveBuilder.java r378 (JTS-1.12)
*
**********************************************************************/
@@ -44,9 +44,6 @@
#define GEOS_DEBUG 0
#endif
-// Define this to skip the input simplification step (for testing)
-//#define SKIP_INPUT_SIMPLIFICATION
-
using namespace std;
using namespace geos::geomgraph;
using namespace geos::algorithm;
@@ -57,90 +54,55 @@
namespace buffer { // geos.operation.buffer
/*private data*/
-const double OffsetCurveBuilder::CURVE_VERTEX_SNAP_DISTANCE_FACTOR = 1.0E-6;
-const double OffsetCurveBuilder::PI = 3.14159265358979;
-const double OffsetCurveBuilder::MAX_CLOSING_SEG_LEN = 3.0;
-const double OffsetCurveBuilder::OFFSET_SEGMENT_SEPARATION_FACTOR = 1.0E-3;
-const double OffsetCurveBuilder::INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR = 1.0E-3;
const double OffsetCurveBuilder::SIMPLIFY_FACTOR = 100.0;
/*public*/
-OffsetCurveBuilder::OffsetCurveBuilder(const PrecisionModel *newPrecisionModel,
- const BufferParameters& nBufParams)
- :
- li(),
- maxCurveSegmentError(0.0),
- vertexList(),
- distance(0.0),
- precisionModel(newPrecisionModel),
- bufParams(nBufParams),
- closingSegFactor(1),
- s0(),
- s1(),
- s2(),
- seg0(),
- seg1(),
- offset0(),
- offset1(),
- side(0),
- endCapIndex(0)
-{
- // compute intersections in full precision, to provide accuracy
- // the points are rounded as they are inserted into the curve line
- filletAngleQuantum = PI / 2.0 / bufParams.getQuadrantSegments();
-
- /**
- * Non-round joins cause issues with short closing segments,
- * so don't use them. In any case, non-round joins
- * only really make sense for relatively small buffer distances.
- */
- if (bufParams.getQuadrantSegments() >= 8
- && bufParams.getJoinStyle() == BufferParameters::JOIN_ROUND)
- {
- closingSegFactor = MAX_CLOSING_SEG_FRACTION;
- }
-}
-
-/*public*/
-OffsetCurveBuilder::~OffsetCurveBuilder()
-{
-}
-
-
-/*public*/
void
OffsetCurveBuilder::getLineCurve(const CoordinateSequence *inputPts,
- double distance, vector<CoordinateSequence*>& lineList)
+ double nDistance, vector<CoordinateSequence*>& lineList)
{
- // a zero or negative width buffer of a line/point is empty
- if (distance<= 0.0) return;
+ distance = nDistance;
- init(distance);
+ // a zero or (non-singlesided) negative width buffer of a line/point is empty
+ if (distance == 0.0) return;
+ if (distance < 0.0 && ! bufParams.isSingleSided()) return;
- if (inputPts->getSize() <= 1) {
- switch (bufParams.getEndCapStyle()) {
- case BufferParameters::CAP_ROUND:
- addCircle(inputPts->getAt(0), distance);
- break;
- case BufferParameters::CAP_SQUARE:
- addSquare(inputPts->getAt(0), distance);
- break;
- default:
- // default is for buffer to be empty
- // (e.g. for a butt line cap);
- break;
- }
- } else {
- computeLineBufferCurve(*inputPts);
- }
+ double posDistance = std::abs(distance);
- // NOTE: we take ownership of lineCoord here ...
- CoordinateSequence *lineCoord=vertexList.getCoordinates();
+ std::auto_ptr<OffsetSegmentGenerator> segGen = getSegGen(posDistance);
+ if (inputPts->getSize() <= 1) {
+ computePointCurve(inputPts->getAt(0), *segGen);
+ } else {
+ if (bufParams.isSingleSided()) {
+ bool isRightSide = distance < 0.0;
+ computeSingleSidedBufferCurve(*inputPts, isRightSide, *segGen);
+ }
+ else {
+ computeLineBufferCurve(*inputPts, *segGen);
+ }
+ }
- // ... and we give it away here
- lineList.push_back(lineCoord);
+ segGen->getCoordinates(lineList);
}
+/* private */
+void
+OffsetCurveBuilder::computePointCurve(const Coordinate& pt,
+ OffsetSegmentGenerator& segGen)
+{
+ switch (bufParams.getEndCapStyle()) {
+ case BufferParameters::CAP_ROUND:
+ segGen.createCircle(pt, distance);
+ break;
+ case BufferParameters::CAP_SQUARE:
+ segGen.createSquare(pt, distance);
+ break;
+ default:
+ // otherwise curve is empty (e.g. for a butt cap);
+ break;
+ }
+}
+
/*public*/
void
OffsetCurveBuilder::getSingleSidedLineCurve(const CoordinateSequence* inputPts,
@@ -150,98 +112,78 @@
// A zero or negative width buffer of a line/point is empty.
if ( distance <= 0.0 ) return ;
- init( distance ) ;
-
if ( inputPts->getSize() < 2 )
{
// No cap, so just return.
return ;
}
- else
- {
- computeLineBufferCurve( *inputPts ) ;
- }
- // NOTE: we take ownership of lineCoord here ...
- std::auto_ptr<CoordinateSequence> lineCoord (vertexList.getCoordinates());
+ double distTol = simplifyTolerance(distance);
- // [strk] Oct 1, 2009
- // Left side: index [n-1] to [endCapIndex]
- // Right side: index [endCapIndex+1] to [n-2]
- // Where n is the last index (size-1).
- int n = lineCoord->size() - 1 ;
+ std::auto_ptr<OffsetSegmentGenerator> segGen = getSegGen(distance);
- // Add the left side curve to the line list.
- if ( leftSide )
- {
- CoordinateArraySequence* coordSeq = new CoordinateArraySequence() ;
- //coordSeq->add( ( *lineCoord )[n-2] ) ;
- coordSeq->add( ( *lineCoord )[n-1] ) ;
- for ( int i = 0 ; i <= endCapIndex ; ++i )
- {
- coordSeq->add( ( *lineCoord )[i] ) ;
- }
- lineList.push_back( coordSeq ) ;
- }
+ if ( leftSide ) {
+ //--------- compute points for left side of line
+ // Simplify the appropriate side of the line before generating
+ std::auto_ptr<CoordinateSequence> simp1_ =
+ BufferInputLineSimplifier::simplify( *inputPts, distTol );
+ const CoordinateSequence& simp1 = *simp1_;
- // Add the right side curve to the line list.
- if ( rightSide )
- {
- CoordinateArraySequence* coordSeq = new CoordinateArraySequence() ;
- for ( int i = endCapIndex+1 ; i <= n-2 ; ++i )
- {
- coordSeq->add( ( *lineCoord )[i] ) ;
- }
- lineList.push_back( coordSeq ) ;
- }
+ int n1 = simp1.size() - 1;
+ segGen->initSideSegments(simp1[0], simp1[1], Position::LEFT);
+ segGen->addFirstSegment();
+ for (int i = 2; i <= n1; ++i) {
+ segGen->addNextSegment(simp1[i], true);
+ }
+ segGen->addLastSegment();
+ }
+
+ if ( rightSide ) {
+
+ //---------- compute points for right side of line
+ // Simplify the appropriate side of the line before generating
+ std::auto_ptr<CoordinateSequence> simp2_ =
+ BufferInputLineSimplifier::simplify( *inputPts, -distTol );
+ const CoordinateSequence& simp2 = *simp2_;
+
+ int n2 = simp2.size() - 1;
+ segGen->initSideSegments(simp2[n2], simp2[n2-1], Position::LEFT);
+ segGen->addFirstSegment();
+ for (int i = n2-2; i >= 0; --i) {
+ segGen->addNextSegment(simp2[i], true);
+ }
+ segGen->addLastSegment();
+ }
+
+ segGen->getCoordinates(lineList);
}
/*public*/
void
OffsetCurveBuilder::getRingCurve(const CoordinateSequence *inputPts,
- int side, double distance,
+ int side, double nDistance,
vector<CoordinateSequence*>& lineList)
{
+ distance = nDistance;
+
// optimize creating ring for zero distance
if (distance == 0.0) {
lineList.push_back(inputPts->clone());
return;
}
- init(distance);
if (inputPts->getSize() <= 2)
{
getLineCurve(inputPts, distance, lineList);
return;
}
- computeRingBufferCurve(*inputPts, side);
-
- // NOTE: ownership of coordinates is transferred from vertexList
- // to lineList
- lineList.push_back(vertexList.getCoordinates());
+ std::auto_ptr<OffsetSegmentGenerator> segGen = getSegGen(std::abs(distance));
+ computeRingBufferCurve(*inputPts, side, *segGen);
+ segGen->getCoordinates(lineList);
}
-/*private*/
-void
-OffsetCurveBuilder::init(double newDistance)
-{
- distance = newDistance;
- maxCurveSegmentError = distance * (1 - cos(filletAngleQuantum/2.0));
-
- // Point list needs to be reset
- vertexList.reset();
- vertexList.setPrecisionModel(precisionModel);
-
- /**
- * Choose the min vertex separation as a small fraction of
- * the offset distance.
- */
- vertexList.setMinimumVertexDistance(
- distance * CURVE_VERTEX_SNAP_DISTANCE_FACTOR);
-}
-
/* private */
double
OffsetCurveBuilder::simplifyTolerance(double bufDistance)
@@ -251,717 +193,128 @@
/*private*/
void
-OffsetCurveBuilder::computeLineBufferCurve(const CoordinateSequence& inputPts)
+OffsetCurveBuilder::computeLineBufferCurve(const CoordinateSequence& inputPts,
+ OffsetSegmentGenerator& segGen)
{
double distTol = simplifyTolerance(distance);
//--------- compute points for left side of line
-#ifndef SKIP_INPUT_SIMPLIFICATION
// Simplify the appropriate side of the line before generating
std::auto_ptr<CoordinateSequence> simp1_ =
BufferInputLineSimplifier::simplify(inputPts, distTol);
const CoordinateSequence& simp1 = *simp1_;
-#else
- // MD - used for testing only (to eliminate simplification)
- const CoordinateSequence& simp1 = inputPts;
-#endif
int n1 = simp1.size() - 1;
- initSideSegments(simp1[0], simp1[1], Position::LEFT);
+ segGen.initSideSegments(simp1[0], simp1[1], Position::LEFT);
for (int i = 2; i <= n1; ++i) {
- addNextSegment(simp1[i], true);
+ segGen.addNextSegment(simp1[i], true);
}
- addLastSegment();
+ segGen.addLastSegment();
// add line cap for end of line
- addLineEndCap(simp1[n1-1], simp1[n1]);
+ segGen.addLineEndCap(simp1[n1-1], simp1[n1]);
- // Record the index of the end of line cap.
- endCapIndex = vertexList.size() - 2 ;
-
-
//---------- compute points for right side of line
-#ifndef SKIP_INPUT_SIMPLIFICATION
// Simplify the appropriate side of the line before generating
std::auto_ptr<CoordinateSequence> simp2_ =
BufferInputLineSimplifier::simplify(inputPts, -distTol);
const CoordinateSequence& simp2 = *simp2_;
-#else
- // MD - used for testing only (to eliminate simplification)
- const CoordinateSequence& simp2 = inputPts;
-#endif
int n2 = simp2.size() - 1;
- initSideSegments(simp2[n2], simp2[n2-1], Position::LEFT);
+ segGen.initSideSegments(simp2[n2], simp2[n2-1], Position::LEFT);
for (int i = n2-2; i >= 0; --i) {
- addNextSegment(simp2[i], true);
+ segGen.addNextSegment(simp2[i], true);
}
- addLastSegment();
+ segGen.addLastSegment();
// add line cap for start of line
- addLineEndCap(simp2[1], simp2[0]);
+ segGen.addLineEndCap(simp2[1], simp2[0]);
- vertexList.closeRing();
+ segGen.closeRing();
}
/*private*/
void
OffsetCurveBuilder::computeRingBufferCurve(const CoordinateSequence& inputPts,
- int side)
+ int side, OffsetSegmentGenerator& segGen)
{
-
-#ifndef SKIP_INPUT_SIMPLIFICATION
- double distTol = simplifyTolerance(distance);
+ // simplify input line to improve performance
+ double distTol = simplifyTolerance(distance);
// ensure that correct side is simplified
if (side == Position::RIGHT)
distTol = -distTol;
std::auto_ptr<CoordinateSequence> simp_ =
BufferInputLineSimplifier::simplify(inputPts, distTol);
const CoordinateSequence& simp = *simp_;
-#else
- const CoordinateSequence& simp = inputPts;
-#endif
int n = simp.size()-1;
- initSideSegments(simp[n-1], simp[0], side);
+ segGen.initSideSegments(simp[n-1], simp[0], side);
for (int i = 1; i <= n; i++) {
bool addStartPoint = i != 1;
- addNextSegment(simp[i], addStartPoint);
+ segGen.addNextSegment(simp[i], addStartPoint);
}
- vertexList.closeRing();
+ segGen.closeRing();
}
/*private*/
void
-OffsetCurveBuilder::initSideSegments(const Coordinate &nS1,
- const Coordinate &nS2, int nSide)
+OffsetCurveBuilder::computeSingleSidedBufferCurve(
+ const CoordinateSequence& inputPts, bool isRightSide,
+ OffsetSegmentGenerator& segGen)
{
- s1=nS1;
- s2=nS2;
- side=nSide;
- seg1.setCoordinates(s1, s2);
- computeOffsetSegment(seg1, side, distance, offset1);
-}
+ double distTol = simplifyTolerance(distance);
-/*private*/
-void
-OffsetCurveBuilder::addNextSegment(const Coordinate &p, bool addStartPoint)
-{
- // s0-s1-s2 are the coordinates of the previous segment
- // and the current one
- s0=s1;
- s1=s2;
- s2=p;
- seg0.setCoordinates(s0, s1);
- computeOffsetSegment(seg0, side, distance, offset0);
- seg1.setCoordinates(s1, s2);
- computeOffsetSegment(seg1, side, distance, offset1);
+ if ( isRightSide ) {
- // do nothing if points are equal
- if (s1==s2) return;
+ // add original line
+ segGen.addSegments(inputPts, true);
- int orientation=CGAlgorithms::computeOrientation(s0, s1, s2);
- bool outsideTurn =
- (orientation==CGAlgorithms::CLOCKWISE
- && side==Position::LEFT)
- ||
- (orientation==CGAlgorithms::COUNTERCLOCKWISE
- && side==Position::RIGHT);
+ //---------- compute points for right side of line
+ // Simplify the appropriate side of the line before generating
+ std::auto_ptr<CoordinateSequence> simp2_ =
+ BufferInputLineSimplifier::simplify(inputPts, -distTol);
+ const CoordinateSequence& simp2 = *simp2_;
- if (orientation==0)
- {
- // lines are collinear
- addCollinear(addStartPoint);
- }
- else if (outsideTurn)
- {
- addOutsideTurn(orientation, addStartPoint);
- }
- else
- {
- // inside turn
- addInsideTurn(orientation, addStartPoint);
- }
-}
+ int n2 = simp2.size() - 1;
+ segGen.initSideSegments(simp2[n2], simp2[n2-1], Position::LEFT);
+ segGen.addFirstSegment();
+ for (int i = n2-2; i >= 0; --i) {
+ segGen.addNextSegment(simp2[i], true);
+ }
-/*private*/
-void
-OffsetCurveBuilder::addLastSegment()
-{
- vertexList.addPt(offset1.p1);
-}
+ } else {
-/*private*/
-void
-OffsetCurveBuilder::computeOffsetSegment(const LineSegment& seg, int side,
- double distance, LineSegment& offset)
-{
- int sideSign = side == Position::LEFT ? 1 : -1;
- double dx = seg.p1.x - seg.p0.x;
- double dy = seg.p1.y - seg.p0.y;
- double len = sqrt(dx * dx + dy * dy);
- // u is the vector that is the length of the offset,
- // in the direction of the segment
- double ux = sideSign * distance * dx / len;
- double uy = sideSign * distance * dy / len;
- offset.p0.x = seg.p0.x - uy;
- offset.p0.y = seg.p0.y + ux;
- offset.p1.x = seg.p1.x - uy;
- offset.p1.y = seg.p1.y + ux;
-}
+ // add original line
+ segGen.addSegments(inputPts, false);
-/*private*/
-void
-OffsetCurveBuilder::addLineEndCap(const Coordinate &p0, const Coordinate &p1)
-{
- LineSegment seg(p0, p1);
+ //--------- compute points for left side of line
+ // Simplify the appropriate side of the line before generating
+ std::auto_ptr<CoordinateSequence> simp1_ =
+ BufferInputLineSimplifier::simplify(inputPts, distTol);
+ const CoordinateSequence& simp1 = *simp1_;
- LineSegment offsetL;
- computeOffsetSegment(seg, Position::LEFT, distance, offsetL);
- LineSegment offsetR;
- computeOffsetSegment(seg, Position::RIGHT, distance, offsetR);
+ int n1 = simp1.size() - 1;
+ segGen.initSideSegments(simp1[0], simp1[1], Position::LEFT);
+ segGen.addFirstSegment();
+ for (int i = 2; i <= n1; ++i) {
+ segGen.addNextSegment(simp1[i], true);
+ }
- double dx=p1.x-p0.x;
- double dy=p1.y-p0.y;
- double angle=atan2(dy, dx);
-
- switch (bufParams.getEndCapStyle()) {
- case BufferParameters::CAP_ROUND:
- // add offset seg points with a fillet between them
- vertexList.addPt(offsetL.p1);
- addFillet(p1, angle+PI/2.0, angle-PI/2.0,
- CGAlgorithms::CLOCKWISE, distance);
- vertexList.addPt(offsetR.p1);
- break;
- case BufferParameters::CAP_FLAT:
- // only offset segment points are added
- vertexList.addPt(offsetL.p1);
- vertexList.addPt(offsetR.p1);
- break;
- case BufferParameters::CAP_SQUARE:
- // add a square defined by extensions of the offset
- // segment endpoints
- Coordinate squareCapSideOffset;
- squareCapSideOffset.x=fabs(distance)*cos(angle);
- squareCapSideOffset.y=fabs(distance)*sin(angle);
-
- Coordinate squareCapLOffset(
- offsetL.p1.x+squareCapSideOffset.x,
- offsetL.p1.y+squareCapSideOffset.y);
- Coordinate squareCapROffset(
- offsetR.p1.x+squareCapSideOffset.x,
- offsetR.p1.y+squareCapSideOffset.y);
- vertexList.addPt(squareCapLOffset);
- vertexList.addPt(squareCapROffset);
- break;
- }
+ }
+ segGen.addLastSegment();
+ segGen.closeRing();
}
/*private*/
-void
-OffsetCurveBuilder::addFillet(const Coordinate &p, const Coordinate &p0,
- const Coordinate &p1, int direction, double radius)
+std::auto_ptr<OffsetSegmentGenerator>
+OffsetCurveBuilder::getSegGen(double dist)
{
- double dx0 = p0.x - p.x;
- double dy0 = p0.y - p.y;
- double startAngle = atan2(dy0, dx0);
- double dx1 = p1.x - p.x;
- double dy1 = p1.y - p.y;
- double endAngle = atan2(dy1, dx1);
-
- if (direction == CGAlgorithms::CLOCKWISE) {
- if (startAngle <= endAngle) startAngle += 2.0 * PI;
- }
- else { // direction==COUNTERCLOCKWISE
- if (startAngle >= endAngle) startAngle -= 2.0 * PI;
- }
-
- vertexList.addPt(p0);
- addFillet(p, startAngle, endAngle, direction, radius);
- vertexList.addPt(p1);
+ std::auto_ptr<OffsetSegmentGenerator> osg(
+ new OffsetSegmentGenerator(precisionModel, bufParams, dist)
+ );
+ return osg;
}
-/*private*/
-void
-OffsetCurveBuilder::addFillet(const Coordinate &p, double startAngle,
- double endAngle, int direction, double radius)
-{
- int directionFactor = direction == CGAlgorithms::CLOCKWISE ? -1 : 1;
-
- double totalAngle = fabs(startAngle - endAngle);
- int nSegs = (int) (totalAngle / filletAngleQuantum + 0.5);
-
- // no segments because angle is less than increment-nothing to do!
- if (nSegs<1) return;
-
- double initAngle, currAngleInc;
-
- // choose angle increment so that each segment has equal length
- initAngle = 0.0;
- currAngleInc = totalAngle / nSegs;
-
- double currAngle = initAngle;
- Coordinate pt;
- while (currAngle < totalAngle) {
- double angle = startAngle + directionFactor * currAngle;
- pt.x = p.x + radius * cos(angle);
- pt.y = p.y + radius * sin(angle);
- vertexList.addPt(pt);
- currAngle += currAngleInc;
- }
-}
-
-
-/*private*/
-void
-OffsetCurveBuilder::addCircle(const Coordinate &p, double distance)
-{
- // add start point
- Coordinate pt(p.x + distance, p.y);
- vertexList.addPt(pt);
- addFillet(p, 0.0, 2.0*PI, -1, distance);
-}
-
-/*private*/
-void
-OffsetCurveBuilder::addSquare(const Coordinate &p, double distance)
-{
- // add start point
- vertexList.addPt(Coordinate(p.x+distance, p.y+distance));
- vertexList.addPt(Coordinate(p.x+distance, p.y-distance));
- vertexList.addPt(Coordinate(p.x-distance, p.y-distance));
- vertexList.addPt(Coordinate(p.x-distance, p.y+distance));
- vertexList.addPt(Coordinate(p.x+distance, p.y+distance));
-}
-
-/* private */
-void
-OffsetCurveBuilder::addCollinear(bool addStartPoint)
-{
- /**
- * This test could probably be done more efficiently,
- * but the situation of exact collinearity should be fairly rare.
- */
-
- li.computeIntersection(s0,s1,s1,s2);
- int numInt=li.getIntersectionNum();
-
- /**
- * if numInt is<2, the lines are parallel and in the same direction.
- * In this case the point can be ignored, since the offset lines
- * will also be parallel.
- */
- if (numInt>= 2)
- {
- /**
- * Segments are collinear but reversing.
- * Add an "end-cap" fillet
- * all the way around to other direction
- *
- * This case should ONLY happen for LineStrings,
- * so the orientation is always CW (Polygons can never
- * have two consecutive segments which are parallel but
- * reversed, because that would be a self intersection).
- */
- if ( bufParams.getJoinStyle() == BufferParameters::JOIN_BEVEL
- || bufParams.getJoinStyle() == BufferParameters::JOIN_MITRE)
- {
- if (addStartPoint) vertexList.addPt(offset0.p1);
- vertexList.addPt(offset1.p0);
- }
- else
- {
- addFillet(s1, offset0.p1, offset1.p0,
- CGAlgorithms::CLOCKWISE, distance);
- }
- }
-}
-
-/* private */
-void
-OffsetCurveBuilder::addOutsideTurn(int orientation, bool addStartPoint)
-{
- /**
- * Heuristic: If offset endpoints are very close together,
- * just use one of them as the corner vertex.
- * This avoids problems with computing mitre corners in the case
- * where the two segments are almost parallel
- * (which is hard to compute a robust intersection for).
- */
-
- if (offset0.p1.distance(offset1.p0) <
- distance*OFFSET_SEGMENT_SEPARATION_FACTOR)
- {
- vertexList.addPt(offset0.p1);
- return;
- }
-
- if (bufParams.getJoinStyle() == BufferParameters::JOIN_MITRE)
- {
- addMitreJoin(s1, offset0, offset1, distance);
- }
- else if (bufParams.getJoinStyle() == BufferParameters::JOIN_BEVEL)
- {
- addBevelJoin(offset0, offset1);
- }
- else
- {
- // add a circular fillet connecting the endpoints
- // of the offset segments
- if (addStartPoint) vertexList.addPt(offset0.p1);
-
- // TESTING - comment out to produce beveled joins
- addFillet(s1, offset0.p1, offset1.p0, orientation, distance);
- vertexList.addPt(offset1.p0);
- }
-}
-
-/* private */
-void
-OffsetCurveBuilder::addInsideTurn(int orientation, bool addStartPoint)
-{
- ::geos::ignore_unused_variable_warning(orientation);
- ::geos::ignore_unused_variable_warning(addStartPoint);
-
- // add intersection point of offset segments (if any)
- li.computeIntersection(offset0.p0, offset0.p1, offset1.p0, offset1.p1);
- if (li.hasIntersection())
- {
- vertexList.addPt(li.getIntersection(0));
- return;
- }
-
- // If no intersection is detected, it means the angle is so small
- // and/or the offset so large that the offsets segments don't
- // intersect. In this case we must add a "closing segment" to make
- // sure the buffer curve is continuous,
- // fairly smooth (e.g. no sharp reversals in direction)
- // and tracks the buffer correctly around the corner.
- // The curve connects the endpoints of the segment offsets to points
- // which lie toward the centre point of the corner.
- // The joining curve will not appear in the final buffer outline,
- // since it is completely internal to the buffer polygon.
- //
- // In complex buffer cases the closing segment may cut across many
- // other segments in the generated offset curve.
- // In order to improve the performance of the noding, the closing
- // segment should be kept as short as possible.
- // (But not too short, since that would defeat it's purpose).
- // This is the purpose of the closingSegFactor heuristic value.
-
- /**
- * The intersection test above is vulnerable to robustness errors;
- * i.e. it may be that the offsets should intersect very close to
- * their endpoints, but aren't reported as such due to rounding.
- * To handle this situation appropriately, we use the following test:
- * If the offset points are very close, don't add closing segments
- * but simply use one of the offset points
- */
-
- if (offset0.p1.distance(offset1.p0) <
- distance * INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR)
- {
- vertexList.addPt(offset0.p1);
- }
- else
- {
- // add endpoint of this segment offset
- vertexList.addPt(offset0.p1);
-
- // Add "closing segment" of required length.
- if ( closingSegFactor > 0 )
- {
- Coordinate mid0(
- (closingSegFactor*offset0.p1.x + s1.x)/(closingSegFactor + 1),
- (closingSegFactor*offset0.p1.y + s1.y)/(closingSegFactor + 1)
- );
- vertexList.addPt(mid0);
-
- Coordinate mid1(
- (closingSegFactor*offset1.p0.x + s1.x)/(closingSegFactor + 1),
- (closingSegFactor*offset1.p0.y + s1.y)/(closingSegFactor + 1)
- );
- vertexList.addPt(mid1);
- }
- else
- {
- // This branch is not expected to be used
- // except for testing purposes.
- // It is equivalent to the JTS 1.9 logic for
- // closing segments (which results in very poor
- // performance for large buffer distances)
- vertexList.addPt(s1);
- }
-
- // add start point of next segment offset
- vertexList.addPt(offset1.p0);
- }
-}
-
-/* private */
-void
-OffsetCurveBuilder::addMitreJoin(const geom::Coordinate& p,
- const geom::LineSegment& offset0,
- const geom::LineSegment& offset1,
- double distance)
-{
- bool isMitreWithinLimit = true;
- Coordinate intPt;
-
- /**
- * This computation is unstable if the offset segments
- * are nearly collinear.
- * Howver, this situation should have been eliminated earlier
- * by the check for whether the offset segment endpoints are
- * almost coincident
- */
- try
- {
- HCoordinate::intersection(offset0.p0, offset0.p1,
- offset1.p0, offset1.p1,
- intPt);
-
- double mitreRatio = distance <= 0.0 ? 1.0
- : intPt.distance(p) / fabs(distance);
-
- if (mitreRatio > bufParams.getMitreLimit())
- isMitreWithinLimit = false;
- }
- catch (const NotRepresentableException& e)
- {
- ::geos::ignore_unused_variable_warning(e);
-
- intPt = Coordinate(0,0);
- isMitreWithinLimit = false;
- }
-
- if (isMitreWithinLimit)
- {
- vertexList.addPt(intPt);
- }
- else
- {
- addLimitedMitreJoin(offset0, offset1, distance,
- bufParams.getMitreLimit());
- //addBevelJoin(offset0, offset1);
- }
-}
-
-/* private */
-void
-OffsetCurveBuilder::addLimitedMitreJoin(
- const geom::LineSegment& offset0,
- const geom::LineSegment& offset1,
- double distance, double mitreLimit)
-{
- ::geos::ignore_unused_variable_warning(offset0);
- ::geos::ignore_unused_variable_warning(offset1);
-
- const Coordinate& basePt = seg0.p1;
-
- double ang0 = Angle::angle(basePt, seg0.p0);
- //double ang1 = Angle::angle(basePt, seg1.p1); // unused in JTS, bug ?
-
- // oriented angle between segments
- double angDiff = Angle::angleBetweenOriented(seg0.p0, basePt, seg1.p1);
- // half of the interior angle
- double angDiffHalf = angDiff / 2;
-
- // angle for bisector of the interior angle between the segments
- double midAng = Angle::normalize(ang0 + angDiffHalf);
- // rotating this by PI gives the bisector of the reflex angle
- double mitreMidAng = Angle::normalize(midAng + PI);
-
- // the miterLimit determines the distance to the mitre bevel
- double mitreDist = mitreLimit * distance;
- // the bevel delta is the difference between the buffer distance
- // and half of the length of the bevel segment
- double bevelDelta = mitreDist * fabs(sin(angDiffHalf));
- double bevelHalfLen = distance - bevelDelta;
-
- // compute the midpoint of the bevel segment
- double bevelMidX = basePt.x + mitreDist * cos(mitreMidAng);
- double bevelMidY = basePt.y + mitreDist * sin(mitreMidAng);
- Coordinate bevelMidPt(bevelMidX, bevelMidY);
-
- // compute the mitre midline segment from the corner point to
- // the bevel segment midpoint
- LineSegment mitreMidLine(basePt, bevelMidPt);
-
- // finally the bevel segment endpoints are computed as offsets from
- // the mitre midline
- Coordinate bevelEndLeft;
- mitreMidLine.pointAlongOffset(1.0, bevelHalfLen, bevelEndLeft);
- Coordinate bevelEndRight;
- mitreMidLine.pointAlongOffset(1.0, -bevelHalfLen, bevelEndRight);
-
- if (side == Position::LEFT) {
- vertexList.addPt(bevelEndLeft);
- vertexList.addPt(bevelEndRight);
- }
- else {
- vertexList.addPt(bevelEndRight);
- vertexList.addPt(bevelEndLeft);
- }
-
-}
-
-/* private */
-void
-OffsetCurveBuilder::addBevelJoin( const geom::LineSegment& offset0,
- const geom::LineSegment& offset1)
-{
- vertexList.addPt(offset0.p1);
- vertexList.addPt(offset1.p0);
-}
-
} // namespace geos.operation.buffer
} // namespace geos.operation
} // namespace geos
-/**********************************************************************
- * $Log$
- * Revision 1.34 2006/03/27 17:59:00 strk
- * Fixed small leak.
- *
- * Revision 1.33 2006/03/27 17:04:18 strk
- * Cleanups and explicit initializations
- *
- * Revision 1.32 2006/03/20 11:42:29 strk
- * Added missing <cmath> include
- *
- * Revision 1.31 2006/03/14 00:19:40 strk
- * opBuffer.h split, streamlined headers in some (not all) files in operation/buffer/
- *
- * Revision 1.30 2006/03/11 16:58:41 strk
- * Fixed bug in OffsetCurveBuilder::getCoordinates.
- *
- * Revision 1.29 2006/03/09 17:40:24 strk
- * Fixed bug#33 (hopefully)
- *
- * Revision 1.28 2006/03/09 16:46:49 strk
- * geos::geom namespace definition, first pass at headers split
- *
- * Revision 1.27 2006/03/07 14:20:15 strk
- * Big deal of heap allocations reduction
- *
- * Revision 1.26 2006/03/03 10:46:21 strk
- * Removed 'using namespace' from headers, added missing headers in .cpp files, removed useless includes in headers (bug#46)
- *
- * Revision 1.25 2006/03/02 12:12:01 strk
- * Renamed DEBUG macros to GEOS_DEBUG, all wrapped in #ifndef block to allow global override (bug#43)
- *
- * Revision 1.24 2006/02/28 19:22:21 strk
- * Fixed in-place definition of static members in OffsetCurveBuilder (bug#33)
- *
- * Revision 1.23 2006/02/28 14:34:05 strk
- * Added many assertions and debugging output hunting for a bug in BufferOp
- *
- * Revision 1.22 2006/02/19 19:46:49 strk
- * Packages <-> namespaces mapping for most GEOS internal code (uncomplete, but working). Dir-level libs for index/ subdirs.
- *
- * Revision 1.21 2006/02/18 21:08:09 strk
- * - new CoordinateSequence::applyCoordinateFilter method (slow but useful)
- * - SegmentString::getCoordinates() doesn't return a clone anymore.
- * - SegmentString::getCoordinatesRO() obsoleted.
- * - SegmentString constructor does not promises constness of passed
- * CoordinateSequence anymore.
- * - NEW ScaledNoder class
- * - Stubs for MCIndexPointSnapper and MCIndexSnapRounder
- * - Simplified internal interaces of OffsetCurveBuilder and OffsetCurveSetBuilder
- *
- * Revision 1.20 2006/01/31 19:07:34 strk
- * - Renamed DefaultCoordinateSequence to CoordinateArraySequence.
- * - Moved GetNumGeometries() and GetGeometryN() interfaces
- * from GeometryCollection to Geometry class.
- * - Added getAt(int pos, Coordinate &to) funtion to CoordinateSequence class.
- * - Reworked automake scripts to produce a static lib for each subdir and
- * then link all subsystem's libs togheter
- * - Moved C-API in it's own top-level dir capi/
- * - Moved source/bigtest and source/test to tests/bigtest and test/xmltester
- * - Fixed PointLocator handling of LinearRings
- * - Changed CoordinateArrayFilter to reduce memory copies
- * - Changed UniqueCoordinateArrayFilter to reduce memory copies
- * - Added CGAlgorithms::isPointInRing() version working with
- * Coordinate::ConstVect type (faster!)
- * - Ported JTS-1.7 version of ConvexHull with big attention to
- * memory usage optimizations.
- * - Improved XMLTester output and user interface
- * - geos::geom::util namespace used for geom/util stuff
- * - Improved memory use in geos::geom::util::PolygonExtractor
- * - New ShortCircuitedGeometryVisitor class
- * - New operation/predicate package
- *
- * Revision 1.19 2005/06/24 11:09:43 strk
- * Dropped RobustLineIntersector, made LineIntersector a concrete class.
- * Added LineIntersector::hasIntersection(Coordinate&,Coordinate&,Coordinate&)
- * to avoid computing intersection point (Z) when it's not necessary.
- *
- * Revision 1.18 2005/05/19 10:29:28 strk
- * Removed some CGAlgorithms instances substituting them with direct calls
- * to the static functions. Interfaces accepting CGAlgorithms pointers kept
- * for backward compatibility but modified to make the argument optional.
- * Fixed a small memory leak in OffsetCurveBuilder::getRingCurve.
- * Inlined some smaller functions encountered during bug hunting.
- * Updated Copyright notices in the touched files.
- *
- * Revision 1.17 2005/02/17 09:56:31 strk
- * Commented out unused variable.
- *
- * Revision 1.16 2005/02/05 05:44:47 strk
- * Changed geomgraph nodeMap to use Coordinate pointers as keys, reduces
- * lots of other Coordinate copies.
- *
- * Revision 1.15 2004/12/08 13:54:44 strk
- * gcc warnings checked and fixed, general cleanups.
- *
- * Revision 1.14 2004/11/04 19:08:07 strk
- * Cleanups, initializers list, profiling.
- *
- * Revision 1.13 2004/07/13 08:33:53 strk
- * Added missing virtual destructor to virtual classes.
- * Fixed implicit unsigned int -> int casts
- *
- * Revision 1.12 2004/07/08 19:34:49 strk
- * Mirrored JTS interface of CoordinateSequence, factory and
- * default implementations.
- * Added CoordinateArraySequenceFactory::instance() function.
- *
- * Revision 1.11 2004/07/02 13:28:27 strk
- * Fixed all #include lines to reflect headers layout change.
- * Added client application build tips in README.
- *
- * Revision 1.10 2004/05/27 08:37:16 strk
- * Fixed a bug preventing OffsetCurveBuilder point list from being reset.
- *
- * Revision 1.9 2004/05/26 19:48:19 strk
- * Changed abs() to fabs() when working with doubles.
- * Used dynamic_cast<> instead of typeid() when JTS uses instanceof.
- *
- * Revision 1.8 2004/05/19 13:40:49 strk
- * Fixed bug in ::addCircle
- *
- * Revision 1.7 2004/05/05 13:08:01 strk
- * Leaks fixed, explicit allocations/deallocations reduced.
- *
- * Revision 1.6 2004/04/20 10:58:04 strk
- * More memory leaks removed.
- *
- * Revision 1.5 2004/04/19 16:14:52 strk
- * Some memory leaks plugged in noding algorithms.
- *
- * Revision 1.4 2004/04/19 15:14:46 strk
- * Added missing virtual destructor in SpatialIndex class.
- * Memory leaks fixes. Const and throw specifications added.
- *
- * Revision 1.3 2004/04/16 13:03:17 strk
- * More leaks fixed
- *
- * Revision 1.2 2004/04/16 12:48:07 strk
- * Leak fixes.
- *
- * Revision 1.1 2004/04/10 08:40:01 ybychkov
- * "operation/buffer" upgraded to JTS 1.4
- *
- *
- **********************************************************************/
-
Added: trunk/src/operation/buffer/OffsetSegmentGenerator.cpp
===================================================================
--- trunk/src/operation/buffer/OffsetSegmentGenerator.cpp (rev 0)
+++ trunk/src/operation/buffer/OffsetSegmentGenerator.cpp 2011-04-27 09:42:31 UTC (rev 3301)
@@ -0,0 +1,602 @@
+/**********************************************************************
+ * $Id$
+ *
+ * GEOS-Geometry Engine Open Source
+ * http://geos.refractions.net
+ *
+ * Copyright (C) 2011 Sandro Santilli <strk at keybit.net>
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU Lesser General Public Licence as published
+ * by the Free Software Foundation.
+ * See the COPYING file for more information.
+ *
+ **********************************************************************
+ *
+ * Last port: operation/buffer/OffsetSegmentGenerator.java r378 (JTS-1.12)
+ *
+ **********************************************************************/
+
+#include <cassert>
+#include <cmath>
+#include <vector>
+
+#include <geos/algorithm/CGAlgorithms.h>
+#include <geos/algorithm/Angle.h>
+#include <geos/operation/buffer/OffsetSegmentGenerator.h>
+#include <geos/operation/buffer/OffsetSegmentString.h>
+#include <geos/operation/buffer/BufferOp.h>
+#include <geos/operation/buffer/BufferParameters.h>
+#include <geos/geomgraph/Position.h>
+#include <geos/geom/CoordinateArraySequence.h>
+#include <geos/geom/CoordinateSequence.h>
+#include <geos/geom/Coordinate.h>
+#include <geos/geom/PrecisionModel.h>
+#include <geos/algorithm/NotRepresentableException.h>
+#include <geos/algorithm/HCoordinate.h>
+#include <geos/util.h>
+
+#include "BufferInputLineSimplifier.h"
+
+#ifndef GEOS_DEBUG
+#define GEOS_DEBUG 0
+#endif
+
+using namespace std;
+using namespace geos::geomgraph;
+using namespace geos::algorithm;
+using namespace geos::geom;
+
+namespace geos {
+namespace operation { // geos.operation
+namespace buffer { // geos.operation.buffer
+
+/*private data*/
+const double OffsetSegmentGenerator::CURVE_VERTEX_SNAP_DISTANCE_FACTOR = 1.0E-6;
+const double OffsetSegmentGenerator::PI = 3.14159265358979;
+const double OffsetSegmentGenerator::OFFSET_SEGMENT_SEPARATION_FACTOR = 1.0E-3;
+const double OffsetSegmentGenerator::INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR = 1.0E-3;
+const double OffsetSegmentGenerator::SIMPLIFY_FACTOR = 100.0;
+
+/*public*/
+OffsetSegmentGenerator::OffsetSegmentGenerator(
+ const PrecisionModel *newPrecisionModel,
+ const BufferParameters& nBufParams,
+ double dist)
+ :
+ maxCurveSegmentError(0.0),
+ closingSegLengthFactor(1),
+ segList(),
+ distance(dist),
+ precisionModel(newPrecisionModel),
+ bufParams(nBufParams),
+ li(),
+ s0(),
+ s1(),
+ s2(),
+ seg0(),
+ seg1(),
+ offset0(),
+ offset1(),
+ side(0),
+ _hasNarrowConcaveAngle(false),
+ endCapIndex(0)
+{
+ // compute intersections in full precision, to provide accuracy
+ // the points are rounded as they are inserted into the curve line
+ filletAngleQuantum = PI / 2.0 / bufParams.getQuadrantSegments();
+
+ /**
+ * Non-round joins cause issues with short closing segments,
+ * so don't use them. In any case, non-round joins
+ * only really make sense for relatively small buffer distances.
+ */
+ if (bufParams.getQuadrantSegments() >= 8
+ && bufParams.getJoinStyle() == BufferParameters::JOIN_ROUND)
+ {
+ closingSegLengthFactor = MAX_CLOSING_SEG_LEN_FACTOR;
+ }
+
+ init(distance);
+}
+
+/*private*/
+void
+OffsetSegmentGenerator::init(double newDistance)
+{
+ distance = newDistance;
+ maxCurveSegmentError = distance * (1 - cos(filletAngleQuantum/2.0));
+
+ // Point list needs to be reset
+ segList.reset();
+ segList.setPrecisionModel(precisionModel);
+
+ /**
+ * Choose the min vertex separation as a small fraction of
+ * the offset distance.
+ */
+ segList.setMinimumVertexDistance(
+ distance * CURVE_VERTEX_SNAP_DISTANCE_FACTOR);
+}
+
+/*public*/
+void
+OffsetSegmentGenerator::initSideSegments(const Coordinate &nS1,
+ const Coordinate &nS2, int nSide)
+{
+ s1 = nS1;
+ s2 = nS2;
+ side = nSide;
+ seg1.setCoordinates(s1, s2);
+ computeOffsetSegment(seg1, side, distance, offset1);
+}
+
+/*public*/
+void
+OffsetSegmentGenerator::addNextSegment(const Coordinate &p, bool addStartPoint)
+{
+ // s0-s1-s2 are the coordinates of the previous segment
+ // and the current one
+ s0=s1;
+ s1=s2;
+ s2=p;
+ seg0.setCoordinates(s0, s1);
+ computeOffsetSegment(seg0, side, distance, offset0);
+ seg1.setCoordinates(s1, s2);
+ computeOffsetSegment(seg1, side, distance, offset1);
+
+ // do nothing if points are equal
+ if (s1==s2) return;
+
+ int orientation=CGAlgorithms::computeOrientation(s0, s1, s2);
+ bool outsideTurn =
+ (orientation==CGAlgorithms::CLOCKWISE
+ && side==Position::LEFT)
+ ||
+ (orientation==CGAlgorithms::COUNTERCLOCKWISE
+ && side==Position::RIGHT);
+
+ if (orientation==0)
+ {
+ // lines are collinear
+ addCollinear(addStartPoint);
+ }
+ else if (outsideTurn)
+ {
+ addOutsideTurn(orientation, addStartPoint);
+ }
+ else
+ {
+ // inside turn
+ addInsideTurn(orientation, addStartPoint);
+ }
+}
+
+/*private*/
+void
+OffsetSegmentGenerator::computeOffsetSegment(const LineSegment& seg, int side,
+ double distance, LineSegment& offset)
+{
+ int sideSign = side == Position::LEFT ? 1 : -1;
+ double dx = seg.p1.x - seg.p0.x;
+ double dy = seg.p1.y - seg.p0.y;
+ double len = sqrt(dx * dx + dy * dy);
+ // u is the vector that is the length of the offset,
+ // in the direction of the segment
+ double ux = sideSign * distance * dx / len;
+ double uy = sideSign * distance * dy / len;
+ offset.p0.x = seg.p0.x - uy;
+ offset.p0.y = seg.p0.y + ux;
+ offset.p1.x = seg.p1.x - uy;
+ offset.p1.y = seg.p1.y + ux;
+}
+
+/*public*/
+void
+OffsetSegmentGenerator::addLineEndCap(const Coordinate &p0, const Coordinate &p1)
+{
+ LineSegment seg(p0, p1);
+
+ LineSegment offsetL;
+ computeOffsetSegment(seg, Position::LEFT, distance, offsetL);
+ LineSegment offsetR;
+ computeOffsetSegment(seg, Position::RIGHT, distance, offsetR);
+
+ double dx=p1.x-p0.x;
+ double dy=p1.y-p0.y;
+ double angle=atan2(dy, dx);
+
+ switch (bufParams.getEndCapStyle()) {
+ case BufferParameters::CAP_ROUND:
+ // add offset seg points with a fillet between them
+ segList.addPt(offsetL.p1);
+ addFillet(p1, angle+PI/2.0, angle-PI/2.0,
+ CGAlgorithms::CLOCKWISE, distance);
+ segList.addPt(offsetR.p1);
+ break;
+ case BufferParameters::CAP_FLAT:
+ // only offset segment points are added
+ segList.addPt(offsetL.p1);
+ segList.addPt(offsetR.p1);
+ break;
+ case BufferParameters::CAP_SQUARE:
+ // add a square defined by extensions of the offset
+ // segment endpoints
+ Coordinate squareCapSideOffset;
+ squareCapSideOffset.x=fabs(distance)*cos(angle);
+ squareCapSideOffset.y=fabs(distance)*sin(angle);
+
+ Coordinate squareCapLOffset(
+ offsetL.p1.x+squareCapSideOffset.x,
+ offsetL.p1.y+squareCapSideOffset.y);
+ Coordinate squareCapROffset(
+ offsetR.p1.x+squareCapSideOffset.x,
+ offsetR.p1.y+squareCapSideOffset.y);
+ segList.addPt(squareCapLOffset);
+ segList.addPt(squareCapROffset);
+ break;
+ }
+}
+
+/*private*/
+void
+OffsetSegmentGenerator::addFillet(const Coordinate &p, const Coordinate &p0,
+ const Coordinate &p1, int direction, double radius)
+{
+ double dx0 = p0.x - p.x;
+ double dy0 = p0.y - p.y;
+ double startAngle = atan2(dy0, dx0);
+ double dx1 = p1.x - p.x;
+ double dy1 = p1.y - p.y;
+ double endAngle = atan2(dy1, dx1);
+
+ if (direction == CGAlgorithms::CLOCKWISE) {
+ if (startAngle <= endAngle) startAngle += 2.0 * PI;
+ }
+ else { // direction==COUNTERCLOCKWISE
+ if (startAngle >= endAngle) startAngle -= 2.0 * PI;
+ }
+
+ segList.addPt(p0);
+ addFillet(p, startAngle, endAngle, direction, radius);
+ segList.addPt(p1);
+}
+
+/*private*/
+void
+OffsetSegmentGenerator::addFillet(const Coordinate &p, double startAngle,
+ double endAngle, int direction, double radius)
+{
+ int directionFactor = direction == CGAlgorithms::CLOCKWISE ? -1 : 1;
+
+ double totalAngle = fabs(startAngle - endAngle);
+ int nSegs = (int) (totalAngle / filletAngleQuantum + 0.5);
+
+ // no segments because angle is less than increment-nothing to do!
+ if (nSegs<1) return;
+
+ double initAngle, currAngleInc;
+
+ // choose angle increment so that each segment has equal length
+ initAngle = 0.0;
+ currAngleInc = totalAngle / nSegs;
+
+ double currAngle = initAngle;
+ Coordinate pt;
+ while (currAngle < totalAngle) {
+ double angle = startAngle + directionFactor * currAngle;
+ pt.x = p.x + radius * cos(angle);
+ pt.y = p.y + radius * sin(angle);
+ segList.addPt(pt);
+ currAngle += currAngleInc;
+ }
+}
+
+
+/*private*/
+void
+OffsetSegmentGenerator::createCircle(const Coordinate &p, double distance)
+{
+ // add start point
+ Coordinate pt(p.x + distance, p.y);
+ segList.addPt(pt);
+ addFillet(p, 0.0, 2.0*PI, -1, distance);
+ segList.closeRing();
+}
+
+/*private*/
+void
+OffsetSegmentGenerator::createSquare(const Coordinate &p, double distance)
+{
+ segList.addPt(Coordinate(p.x+distance, p.y+distance));
+ segList.addPt(Coordinate(p.x+distance, p.y-distance));
+ segList.addPt(Coordinate(p.x-distance, p.y-distance));
+ segList.addPt(Coordinate(p.x-distance, p.y+distance));
+ segList.closeRing();
+}
+
+/* private */
+void
+OffsetSegmentGenerator::addCollinear(bool addStartPoint)
+{
+ /**
+ * This test could probably be done more efficiently,
+ * but the situation of exact collinearity should be fairly rare.
+ */
+
+ li.computeIntersection(s0,s1,s1,s2);
+ int numInt=li.getIntersectionNum();
+
+ /**
+ * if numInt is<2, the lines are parallel and in the same direction.
+ * In this case the point can be ignored, since the offset lines
+ * will also be parallel.
+ */
+ if (numInt>= 2)
+ {
+ /**
+ * Segments are collinear but reversing.
+ * Add an "end-cap" fillet
+ * all the way around to other direction
+ *
+ * This case should ONLY happen for LineStrings,
+ * so the orientation is always CW (Polygons can never
+ * have two consecutive segments which are parallel but
+ * reversed, because that would be a self intersection).
+ */
+ if ( bufParams.getJoinStyle() == BufferParameters::JOIN_BEVEL
+ || bufParams.getJoinStyle() == BufferParameters::JOIN_MITRE)
+ {
+ if (addStartPoint) segList.addPt(offset0.p1);
+ segList.addPt(offset1.p0);
+ }
+ else
+ {
+ addFillet(s1, offset0.p1, offset1.p0,
+ CGAlgorithms::CLOCKWISE, distance);
+ }
+ }
+}
+
+/* private */
+void
+OffsetSegmentGenerator::addOutsideTurn(int orientation, bool addStartPoint)
+{
+ /**
+ * Heuristic: If offset endpoints are very close together,
+ * just use one of them as the corner vertex.
+ * This avoids problems with computing mitre corners in the case
+ * where the two segments are almost parallel
+ * (which is hard to compute a robust intersection for).
+ */
+
+ if (offset0.p1.distance(offset1.p0) <
+ distance*OFFSET_SEGMENT_SEPARATION_FACTOR)
+ {
+ segList.addPt(offset0.p1);
+ return;
+ }
+
+ if (bufParams.getJoinStyle() == BufferParameters::JOIN_MITRE)
+ {
+ addMitreJoin(s1, offset0, offset1, distance);
+ }
+ else if (bufParams.getJoinStyle() == BufferParameters::JOIN_BEVEL)
+ {
+ addBevelJoin(offset0, offset1);
+ }
+ else
+ {
+ // add a circular fillet connecting the endpoints
+ // of the offset segments
+ if (addStartPoint) segList.addPt(offset0.p1);
+
+ // TESTING - comment out to produce beveled joins
+ addFillet(s1, offset0.p1, offset1.p0, orientation, distance);
+ segList.addPt(offset1.p0);
+ }
+}
+
+/* private */
+void
+OffsetSegmentGenerator::addInsideTurn(int orientation, bool addStartPoint)
+{
+ ::geos::ignore_unused_variable_warning(orientation);
+ ::geos::ignore_unused_variable_warning(addStartPoint);
+
+ // add intersection point of offset segments (if any)
+ li.computeIntersection(offset0.p0, offset0.p1, offset1.p0, offset1.p1);
+ if (li.hasIntersection())
+ {
+ segList.addPt(li.getIntersection(0));
+ return;
+ }
+
+ // If no intersection is detected, it means the angle is so small
+ // and/or the offset so large that the offsets segments don't
+ // intersect. In this case we must add a "closing segment" to make
+ // sure the buffer curve is continuous,
+ // fairly smooth (e.g. no sharp reversals in direction)
+ // and tracks the buffer correctly around the corner.
+ // The curve connects the endpoints of the segment offsets to points
+ // which lie toward the centre point of the corner.
+ // The joining curve will not appear in the final buffer outline,
+ // since it is completely internal to the buffer polygon.
+ //
+ // In complex buffer cases the closing segment may cut across many
+ // other segments in the generated offset curve.
+ // In order to improve the performance of the noding, the closing
+ // segment should be kept as short as possible.
+ // (But not too short, since that would defeat it's purpose).
+ // This is the purpose of the closingSegLengthFactor heuristic value.
+
+ /**
+ * The intersection test above is vulnerable to robustness errors;
+ * i.e. it may be that the offsets should intersect very close to
+ * their endpoints, but aren't reported as such due to rounding.
+ * To handle this situation appropriately, we use the following test:
+ * If the offset points are very close, don't add closing segments
+ * but simply use one of the offset points
+ */
+
+ if (offset0.p1.distance(offset1.p0) <
+ distance * INSIDE_TURN_VERTEX_SNAP_DISTANCE_FACTOR)
+ {
+ segList.addPt(offset0.p1);
+ }
+ else
+ {
+ // add endpoint of this segment offset
+ segList.addPt(offset0.p1);
+
+ // Add "closing segment" of required length.
+ if ( closingSegLengthFactor > 0 )
+ {
+ Coordinate mid0(
+ (closingSegLengthFactor*offset0.p1.x + s1.x)/(closingSegLengthFactor + 1),
+ (closingSegLengthFactor*offset0.p1.y + s1.y)/(closingSegLengthFactor + 1)
+ );
+ segList.addPt(mid0);
+
+ Coordinate mid1(
+ (closingSegLengthFactor*offset1.p0.x + s1.x)/(closingSegLengthFactor + 1),
+ (closingSegLengthFactor*offset1.p0.y + s1.y)/(closingSegLengthFactor + 1)
+ );
+ segList.addPt(mid1);
+ }
+ else
+ {
+ // This branch is not expected to be used
+ // except for testing purposes.
+ // It is equivalent to the JTS 1.9 logic for
+ // closing segments (which results in very poor
+ // performance for large buffer distances)
+ segList.addPt(s1);
+ }
+
+ // add start point of next segment offset
+ segList.addPt(offset1.p0);
+ }
+}
+
+/* private */
+void
+OffsetSegmentGenerator::addMitreJoin(const geom::Coordinate& p,
+ const geom::LineSegment& offset0,
+ const geom::LineSegment& offset1,
+ double distance)
+{
+ bool isMitreWithinLimit = true;
+ Coordinate intPt;
+
+ /**
+ * This computation is unstable if the offset segments
+ * are nearly collinear.
+ * Howver, this situation should have been eliminated earlier
+ * by the check for whether the offset segment endpoints are
+ * almost coincident
+ */
+ try
+ {
+ HCoordinate::intersection(offset0.p0, offset0.p1,
+ offset1.p0, offset1.p1,
+ intPt);
+
+ double mitreRatio = distance <= 0.0 ? 1.0
+ : intPt.distance(p) / fabs(distance);
+
+ if (mitreRatio > bufParams.getMitreLimit())
+ isMitreWithinLimit = false;
+ }
+ catch (const NotRepresentableException& e)
+ {
+ ::geos::ignore_unused_variable_warning(e);
+
+ intPt = Coordinate(0,0);
+ isMitreWithinLimit = false;
+ }
+
+ if (isMitreWithinLimit)
+ {
+ segList.addPt(intPt);
+ }
+ else
+ {
+ addLimitedMitreJoin(offset0, offset1, distance,
+ bufParams.getMitreLimit());
+ //addBevelJoin(offset0, offset1);
+ }
+}
+
+/* private */
+void
+OffsetSegmentGenerator::addLimitedMitreJoin(
+ const geom::LineSegment& offset0,
+ const geom::LineSegment& offset1,
+ double distance, double mitreLimit)
+{
+ ::geos::ignore_unused_variable_warning(offset0);
+ ::geos::ignore_unused_variable_warning(offset1);
+
+ const Coordinate& basePt = seg0.p1;
+
+ double ang0 = Angle::angle(basePt, seg0.p0);
+ //double ang1 = Angle::angle(basePt, seg1.p1); // unused in JTS, bug ?
+
+ // oriented angle between segments
+ double angDiff = Angle::angleBetweenOriented(seg0.p0, basePt, seg1.p1);
+ // half of the interior angle
+ double angDiffHalf = angDiff / 2;
+
+ // angle for bisector of the interior angle between the segments
+ double midAng = Angle::normalize(ang0 + angDiffHalf);
+ // rotating this by PI gives the bisector of the reflex angle
+ double mitreMidAng = Angle::normalize(midAng + PI);
+
+ // the miterLimit determines the distance to the mitre bevel
+ double mitreDist = mitreLimit * distance;
+ // the bevel delta is the difference between the buffer distance
+ // and half of the length of the bevel segment
+ double bevelDelta = mitreDist * fabs(sin(angDiffHalf));
+ double bevelHalfLen = distance - bevelDelta;
+
+ // compute the midpoint of the bevel segment
+ double bevelMidX = basePt.x + mitreDist * cos(mitreMidAng);
+ double bevelMidY = basePt.y + mitreDist * sin(mitreMidAng);
+ Coordinate bevelMidPt(bevelMidX, bevelMidY);
+
+ // compute the mitre midline segment from the corner point to
+ // the bevel segment midpoint
+ LineSegment mitreMidLine(basePt, bevelMidPt);
+
+ // finally the bevel segment endpoints are computed as offsets from
+ // the mitre midline
+ Coordinate bevelEndLeft;
+ mitreMidLine.pointAlongOffset(1.0, bevelHalfLen, bevelEndLeft);
+ Coordinate bevelEndRight;
+ mitreMidLine.pointAlongOffset(1.0, -bevelHalfLen, bevelEndRight);
+
+ if (side == Position::LEFT) {
+ segList.addPt(bevelEndLeft);
+ segList.addPt(bevelEndRight);
+ }
+ else {
+ segList.addPt(bevelEndRight);
+ segList.addPt(bevelEndLeft);
+ }
+
+}
+
+/* private */
+void
+OffsetSegmentGenerator::addBevelJoin( const geom::LineSegment& offset0,
+ const geom::LineSegment& offset1)
+{
+ segList.addPt(offset0.p1);
+ segList.addPt(offset1.p0);
+}
+
+} // namespace geos.operation.buffer
+} // namespace geos.operation
+} // namespace geos
+
More information about the geos-commits
mailing list