[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