Repository: Toblerity/Shapely
Pull request #795 (2019-11-22 23:53:03)

added homebrew path for supports QGIS3 for macOS
I just get an error when I install QGIS 'vector tile plugin' into macOS with homebrew.
QGIS3 can't find path and use [this search path](https://github.com/Toblerity/Shapely/blob/master/shapely/geos.py#L128) but it doesn't include homebrew path.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/27189241/badge)](https://coveralls.io/builds/27189241)

Coverage increased (+0.009%) to 81.082% when pulling **fa74e25bd912117b5bd8866ba174e09551775a79 on smellman:added-homebrew-path** into **97ab1eb1f0fd51de3a6eee5d25c9ddcba4fafa80 on Toblerity:master**.


Patch:
diff --git a/.travis.yml b/.travis.yml
index bd6056b..9f071fe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,72 +1,114 @@
 language: python
-cache: pip
 dist: xenial
 
+cache:
+  directories:
+    - $HOME/geosinstall
+    - ~/.cache/pip
+
 matrix:
   include:
     - python: "2.7"
       env:
-        - SPEEDUPS=1
-        - NUMPY=1
+        GEOSVERSION="3.4.3"
+        SPEEDUPS=1
+        NUMPY=1
     - python: "2.7"
       env:
-        - SPEEDUPS=0
-        - NUMPY=1
+        GEOSVERSION="3.4.3"
+        SPEEDUPS=0
+        NUMPY=1
     - python: "2.7"
       env:
-        - SPEEDUPS=0
-        - NUMPY=0
+        GEOSVERSION="3.4.3"
+        SPEEDUPS=0
+        NUMPY=0
+    - python: "3.5"
+      env:
+        GEOSVERSION="3.5.2"
+        SPEEDUPS=1
+        NUMPY=1
     - python: "3.5"
       env:
-        - SPEEDUPS=1
-        - NUMPY=1
+        GEOSVERSION="3.5.2"
+        SPEEDUPS=0
+        NUMPY=1
     - python: "3.5"
       env:
-        - SPEEDUPS=0
-        - NUMPY=1
+        GEOSVERSION="3.5.2"
+        SPEEDUPS=0
+        NUMPY=0
     - python: "3.6"
       env:
-        - SPEEDUPS=1
-        - NUMPY=1
+        GEOSVERSION="3.6.4"
+        SPEEDUPS=1
+        NUMPY=1
     - python: "3.6"
       env:
-        - SPEEDUPS=0
-        - NUMPY=1
+        GEOSVERSION="3.6.4"
+        SPEEDUPS=0
+        NUMPY=1
     - python: "3.6"
       env:
-        - SPEEDUPS=0
-        - NUMPY=0
+        GEOSVERSION="3.6.4"
+        SPEEDUPS=0
+        NUMPY=0
     - python: "3.7"
       env:
-        - SPEEDUPS=1
-        - NUMPY=1
+        GEOSVERSION="3.7.3"
+        SPEEDUPS=1
+        NUMPY=1
     - python: "3.7"
       env:
-        - SPEEDUPS=0
-        - NUMPY=1
+        GEOSVERSION="3.7.3"
+        SPEEDUPS=0
+        NUMPY=1
     - python: "3.7"
       env:
-        - SPEEDUPS=0
-        - NUMPY=0
-
-addons:
-  apt:
-    packages:
-      - libgeos-dev
+        GEOSVERSION="3.7.3"
+        SPEEDUPS=0
+        NUMPY=0
+    - python: "3.8"
+      env:
+        GEOSVERSION="3.8.0"
+        SPEEDUPS=1
+        NUMPY=1
+    - python: "3.8"
+      env:
+        GEOSVERSION="3.8.0"
+        SPEEDUPS=0
+        NUMPY=1
+    - python: "3.8"
+      env:
+        GEOSVERSION="3.8.0"
+        SPEEDUPS=0
+        NUMPY=0
+    - python: "3.8-dev"
+      env:
+        GEOSVERSION="master"
+        SPEEDUPS=1
+        NUMPY=1
+  allow_failures:
+    - python: "3.8-dev"
+      env:
+        GEOSVERSION="master"
+        SPEEDUPS=1
+        NUMPY=1
 
 before_install:
-  - python -m pip install -U pip
-  - python -m pip --version
+  - ./ci/travis/install_geos.sh
+  - pip install --upgrade pip
   # if building with speedups install cython
   - if [ "$SPEEDUPS" == "1" ]; then pip install --install-option="--no-cython-compile" cython; fi
   # if testing without numpy explicitly remove it
   - if [ "$NUMPY" == "0" ]; then pip uninstall --yes numpy; fi
   # convert SPEEDUPS to --with-speedups/--without-speedups
   - if [ "$SPEEDUPS" == "1" ]; then SPEEDUPS_FLAG=--with-speedups; else SPEEDUPS_FLAG=--without-speedups; fi
+  - pip install --upgrade coveralls pytest-cov pytest>=3.8
 
 install:
-  - python -m pip install --upgrade coveralls pytest-cov pytest>=3.8
-  - python -m pip install -e .[all]
+  - export GEOS_CONFIG=$HOME/geosinstall/geos-$GEOSVERSION/bin/geos-config
+  - pip install -v -e .[all]
 
 script:
   - python -m pytest --cov shapely --cov-report term-missing "${SPEEDUPS_FLAG}"
diff --git a/CHANGES.txt b/CHANGES.txt
index 9dfd342..2423f1f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,9 +1,15 @@
 Changes
 =======
 
-Next
-----
+1.7a3 (2019-12-30)
+------------------
+
+Bug fixes:
 
+- ops.substring now returns a line with coords in end-to-front order when given
+  a start position that is greater than the end position (#628).
+- Implement ``__bool__()`` for geometry base classes so that ``bool(geom)``
+  returns the logical complement of ``geom.is_empty`` (#754).
 - Remove assertion on the number of version-like strings found in the GEOS
   version string. It could be 2 or 3.
 
diff --git a/FAQ.rst b/FAQ.rst
new file mode 100644
index 0000000..2f73cda
--- /dev/null
+++ b/FAQ.rst
@@ -0,0 +1,11 @@
+Frequently asked questions and answers
+======================================
+
+Are there references for the algorithms used by shapely?
+--------------------------------------------------------
+
+Generally speaking, shapely's predicates and operations are derived from
+methods of the same name from the `JTS Topology Suite
+<https://locationtech.github.io/jts/>`__.  See the JTS FAQ for `references
+describing the JTS algorithms
+<https://locationtech.github.io/jts/jts-faq.html#E1>`__.
diff --git a/appveyor.yml b/appveyor.yml
index a9b81ff..1afd72f 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -10,7 +10,7 @@ environment:
         # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
         # /E:ON and /V:ON options are not enabled in the batch script interpreter
         # See: http://stackoverflow.com/a/13751649/163740
-        CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd"
+        CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci\\appveyor\\run_with_env.cmd"
         GDAL_HOME: "C:\\gdal"
 
     matrix:
@@ -38,7 +38,7 @@ install:
 
   # Install Python (from the official .msi of http://python.org) and pip when
   # not already installed.
-  # - ps: if (-not(Test-Path($env:PYTHON))) { & appveyor\install.ps1 }
+  # - ps: if (-not(Test-Path($env:PYTHON))) { & ci\appveyor\install.ps1 }
 
   # Prepend newly installed Python to the PATH of this build (this cannot be
   # done from inside the powershell script as it would require to restart
diff --git a/appveyor/install.ps1 b/ci/appveyor/install.ps1
similarity index 100%
rename from appveyor/install.ps1
rename to ci/appveyor/install.ps1
diff --git a/appveyor/run_with_env.cmd b/ci/appveyor/run_with_env.cmd
similarity index 100%
rename from appveyor/run_with_env.cmd
rename to ci/appveyor/run_with_env.cmd
diff --git a/ci/travis/install_geos.sh b/ci/travis/install_geos.sh
new file mode 100755
index 0000000..852224a
--- /dev/null
+++ b/ci/travis/install_geos.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+# based on contribution by @rbuffat to Toblerity/Fiona
+set -e
+
+# Cache install directory from .travis.yml
+CACHEGEOSINST=$HOME/geosinstall
+
+# Create directories, if they don't exit
+GEOSINSTVERSION=$CACHEGEOSINST/geos-$GEOSVERSION
+mkdir -p $GEOSINSTVERSION
+
+function build_geos {
+    echo "Building geos-$GEOSVERSION"
+    mkdir build
+    cd build
+    # disable building tests to speed up compilation
+    if [ "$GEOSVERSION" = "master" ] || dpkg --compare-versions "$GEOSVERSION" "gt" "3.8.0"; then
+        BUILD_TESTING=BUILD_TESTING
+    else
+        # cmake variable used for older GEOS releases
+        BUILD_TESTING=GEOS_ENABLE_TESTS
+        # note that this intentially also catches GEOS "3.8.0"
+        # see https://trac.osgeo.org/geos/ticket/1006
+    fi
+    cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$GEOSINSTVERSION -D$BUILD_TESTING=OFF ..
+    make -j 2
+    make install
+}
+
+if [ "$GEOSVERSION" = "master" ]; then
+    # use GitHub mirror
+    git clone --depth 1 https://github.com/libgeos/geos.git geos-$GEOSVERSION
+    cd geos-$GEOSVERSION
+    git rev-parse HEAD > newrev.txt
+    BUILD=no
+    # Only build if nothing cached or if the GEOS revision changed
+    if test ! -f $GEOSINSTVERSION/rev.txt; then
+        BUILD=yes
+    elif ! diff newrev.txt $GEOSINSTVERSION/rev.txt >/dev/null; then
+        BUILD=yes
+    fi
+    if test "$BUILD" = "no"; then
+        echo "Using cached install $GEOSINSTVERSION"
+    else
+        cp newrev.txt $GEOSINSTVERSION/rev.txt
+        build_geos
+    fi
+else
+    if [ -d "$GEOSINSTVERSION/include/geos" ]; then
+        echo "Using cached install $GEOSINSTVERSION"
+    else
+        wget -q http://download.osgeo.org/geos/geos-$GEOSVERSION.tar.bz2
+        tar xfj geos-$GEOSVERSION.tar.bz2
+        cd geos-$GEOSVERSION
+        build_geos
+    fi
+fi
diff --git a/docs/project.rst b/docs/project.rst
index ce70278..05c3490 100644
--- a/docs/project.rst
+++ b/docs/project.rst
@@ -4,3 +4,4 @@
 
 .. include:: ../CHANGES.txt
 
+.. include:: ../FAQ.rst
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 12088cd..0b686d5 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,7 +1,7 @@
-setuptools
-Numpy>=1.4.1
-Cython>=0.19
+cython==0.29.14
 descartes==1.0.1
+numpy>=1.4.1
 pytest
 pytest-cov
+setuptools
 wheel
diff --git a/setup.py b/setup.py
index 1a97de2..702cc2b 100755
--- a/setup.py
+++ b/setup.py
@@ -220,10 +220,10 @@ setup_args = dict(
         'Programming Language :: Python :: 2',
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
         'Topic :: Scientific/Engineering :: GIS',
     ],
     cmdclass           = {},
diff --git a/shapely/__init__.py b/shapely/__init__.py
index 9c19158..8117773 100644
--- a/shapely/__init__.py
+++ b/shapely/__init__.py
@@ -1,1 +1,1 @@
-__version__ = "1.7a2"
+__version__ = "1.7a3"
diff --git a/shapely/_buildcfg.py b/shapely/_buildcfg.py
index 8f28b89..8bf68bf 100644
--- a/shapely/_buildcfg.py
+++ b/shapely/_buildcfg.py
@@ -181,6 +181,8 @@ elif sys.platform == 'darwin':
                 "/Library/Frameworks/GEOS.framework/Versions/Current/GEOS",
                 # macports
                 '/opt/local/lib/libgeos_c.dylib',
+                # homebrew
+                '/usr/local/lib/libgeos_c.dylib',
             ]
         lgeos = load_dll('geos_c', fallbacks=alt_paths)
 
diff --git a/shapely/_geos.pxi b/shapely/_geos.pxi
index 7cf7777..8975405 100644
--- a/shapely/_geos.pxi
+++ b/shapely/_geos.pxi
@@ -43,6 +43,8 @@ cdef extern from "geos_c.h":
     char GEOSHasZ_r(GEOSContextHandle_t, GEOSGeometry *) nogil
     char GEOSisRing_r(GEOSContextHandle_t, GEOSGeometry *) nogil
     char GEOSisClosed_r(GEOSContextHandle_t, GEOSGeometry *) nogil
+    char GEOSisValid_r(GEOSContextHandle_t, GEOSGeometry *) nogil
+    char GEOSisSimple_r(GEOSContextHandle_t, GEOSGeometry *) nogil
 
 
 cdef GEOSContextHandle_t get_geos_context_handle():
diff --git a/shapely/ctypes_declarations.py b/shapely/ctypes_declarations.py
index 6d3b0e1..7ecfb3a 100644
--- a/shapely/ctypes_declarations.py
+++ b/shapely/ctypes_declarations.py
@@ -135,6 +135,28 @@ def prototype(lgeos, geos_version):
             lgeos.GEOSOffsetCurve.argtypes = [
                 c_void_p, c_double, c_int, c_int, c_double]
 
+            lgeos.GEOSBufferParams_create.restype = c_void_p
+            lgeos.GEOSBufferParams_create.argtypes = None
+
+            lgeos.GEOSBufferParams_setEndCapStyle.restype = c_int
+            lgeos.GEOSBufferParams_setEndCapStyle.argtypes = [c_void_p, c_int]
+
+            lgeos.GEOSBufferParams_setJoinStyle.restype = c_int
+            lgeos.GEOSBufferParams_setJoinStyle.argtypes = [c_void_p, c_int]
+
+            lgeos.GEOSBufferParams_setMitreLimit.restype = c_int
+            lgeos.GEOSBufferParams_setMitreLimit.argtypes = [c_void_p, c_double]
+
+            lgeos.GEOSBufferParams_setQuadrantSegments.restype = c_int
+            lgeos.GEOSBufferParams_setQuadrantSegments.argtypes = [c_void_p, c_int]
+
+            lgeos.GEOSBufferParams_setSingleSided.restype = c_int
+            lgeos.GEOSBufferParams_setSingleSided.argtypes = [c_void_p, c_int]
+
+            lgeos.GEOSBufferWithParams.restype = c_void_p
+            lgeos.GEOSBufferWithParams.argtypes = [
+                c_void_p, c_void_p, c_double]
+
         else:
 
             # deprecated in GEOS 3.3.0 in favour of GEOSOffsetCurve
@@ -509,7 +531,7 @@ def prototype(lgeos, geos_version):
 
         lgeos.GEOSSTRtree_destroy.argtypes = [c_void_p]
         lgeos.GEOSSTRtree_destroy.restype = None
-    
+
     if geos_version >= (3, 6, 0):
         lgeos.GEOSDistanceCallback = CFUNCTYPE(c_int, c_void_p, c_void_p, c_void_p, c_void_p)
 
diff --git a/shapely/geometry/base.py b/shapely/geometry/base.py
index 04bb78a..ef29c80 100644
--- a/shapely/geometry/base.py
+++ b/shapely/geometry/base.py
@@ -230,6 +230,9 @@ class BaseGeometry(object):
         self._is_empty = True
         self.__geom__ = val
 
+    def __bool__(self):
+        return self.is_empty is False
+
     def __del__(self):
         self.empty(val=None)
         self.__p__ = None
@@ -531,29 +534,42 @@ class BaseGeometry(object):
 
     def buffer(self, distance, resolution=16, quadsegs=None,
                cap_style=CAP_STYLE.round, join_style=JOIN_STYLE.round,
-               mitre_limit=5.0):
+               mitre_limit=5.0, single_sided=False):
         """Returns a geometry with an envelope at a distance from the object's
         envelope
 
-        A negative distance has a "shrink" effect. A zero distance may be used
-        to "tidy" a polygon. The resolution of the buffer around each vertex of
-        the object increases by increasing the resolution keyword parameter
-        or second positional parameter. Note: the use of a `quadsegs` parameter
-        is deprecated and will be gone from the next major release.
-
-        The styles of caps are: CAP_STYLE.round (1), CAP_STYLE.flat (2), and
-        CAP_STYLE.square (3).
-
-        The styles of joins between offset segments are: JOIN_STYLE.round (1),
-        JOIN_STYLE.mitre (2), and JOIN_STYLE.bevel (3).
-
-        The mitre limit ratio is used for very sharp corners. The mitre ratio
-        is the ratio of the distance from the corner to the end of the mitred
-        offset corner. When two line segments meet at a sharp angle, a miter
-        join will extend the original geometry. To prevent unreasonable
-        geometry, the mitre limit allows controlling the maximum length of the
-        join corner. Corners with a ratio which exceed the limit will be
-        beveled.
+        Parameters
+        ==========
+        distance: float
+            The distance to buffer around the object. A negative distance has a "shrink" effect.
+            A zero distance may be used to "tidy" a polygon.
+        resolution: int, optional
+            The resolution of the buffer around each vertex of the object.
+        quadsegs: int, optional
+            Sets the number of line segments used to approximate an angle fillet.
+            Note: the use of a `quadsegs` parameter is deprecated and will be gone from
+            the next major release.
+        cap_style: int, optional
+            The styles of caps are: CAP_STYLE.round (1), CAP_STYLE.flat (2), and
+            CAP_STYLE.square (3).
+        join_style: int, optional
+            The styles of joins between offset segments are: JOIN_STYLE.round (1),
+            JOIN_STYLE.mitre (2), and JOIN_STYLE.bevel (3).
+        mitre_limit: float, optional
+            The mitre limit ratio is used for very sharp corners. The mitre ratio
+            is the ratio of the distance from the corner to the end of the mitred
+            offset corner. When two line segments meet at a sharp angle, a miter
+            join will extend the original geometry. To prevent unreasonable
+            geometry, the mitre limit allows controlling the maximum length of the
+            join corner. Corners with a ratio which exceed the limit will be
+            beveled.
+        single_side: bool, optional
+            The side used is determined by the sign of the buffer distance:
+                a positive distance indicates the left-hand side
+                a negative distance indicates the right-hand side
+            The single-sided buffer of point geometries is the same as the regular buffer.
+            The End Cap Style for single-sided buffers is always ignored, and forced to the
+            equivalent of CAP_FLAT.
 
         Example:
 
@@ -580,6 +596,16 @@ class BaseGeometry(object):
         if mitre_limit == 0.0:
             raise ValueError(
                 'Cannot compute offset from zero-length line segment')
+
+        if 'buffer_with_params' in self.impl:
+            params = self._lgeos.GEOSBufferParams_create()
+            self._lgeos.GEOSBufferParams_setEndCapStyle(params, cap_style)
+            self._lgeos.GEOSBufferParams_setJoinStyle(params, join_style)
+            self._lgeos.GEOSBufferParams_setMitreLimit(params, mitre_limit)
+            self._lgeos.GEOSBufferParams_setQuadrantSegments(params, res)
+            self._lgeos.GEOSBufferParams_setSingleSided(params, single_sided)
+            return geom_factory(self.impl['buffer_with_params'](self, params, distance))
+
         if cap_style == CAP_STYLE.round and join_style == JOIN_STYLE.round:
             return geom_factory(self.impl['buffer'](self, distance, res))
 
@@ -700,7 +726,7 @@ class BaseGeometry(object):
 
     def equals(self, other):
         """Returns True if geometries are equal, else False
-        
+
         Refers to point-set equality (or topological equality), and is equivalent to
         (self.within(other) & self.contains(other))
         """
@@ -725,8 +751,8 @@ class BaseGeometry(object):
     def equals_exact(self, other, tolerance):
         """Returns True if geometries are equal to within a specified
         tolerance
-        
-        Refers to coordinate equality, which requires coordinates to be equal 
+
+        Refers to coordinate equality, which requires coordinates to be equal
         and in the same order for all components of a geometry
         """
         return bool(self.impl['equals_exact'](self, other, tolerance))
@@ -817,6 +843,9 @@ class BaseMultipartGeometry(BaseGeometry):
             return []
         return GeometrySequence(self, self.shape_factory)
 
+    def __bool__(self):
+        return self.is_empty is False
+
     def __iter__(self):
         if not self.is_empty:
             return iter(self.geoms)
diff --git a/shapely/geometry/polygon.py b/shapely/geometry/polygon.py
index b1ba4e5..58cb46f 100644
--- a/shapely/geometry/polygon.py
+++ b/shapely/geometry/polygon.py
@@ -10,12 +10,13 @@ from ctypes import c_void_p, cast, POINTER
 import weakref
 
 from shapely.algorithms.cga import signed_area
-#from shapely.coords import required
 from shapely.geos import lgeos
 from shapely.geometry.base import BaseGeometry, geos_geom_from_py
 from shapely.geometry.linestring import LineString, LineStringAdapter
 from shapely.geometry.point import Point
 from shapely.geometry.proxy import PolygonProxy
+from shapely.errors import TopologicalError
+
 
 __all__ = ['Polygon', 'asPolygon', 'LinearRing', 'asLinearRing']
 
@@ -406,7 +407,7 @@ def orient(polygon, sign=1.0):
 
 def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
     # If a LinearRing is passed in, clone it and return
-    # If a LineString is passed in, clone the coord seq and return a
+    # If a valid LineString is passed in, clone the coord seq and return a
     # LinearRing.
     #
     # NB: access to coordinates using the array protocol has been moved
@@ -415,6 +416,8 @@ def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
     if isinstance(ob, LineString):
         if type(ob) == LinearRing:
             return geos_geom_from_py(ob)
+        elif not ob.is_valid:
+            raise TopologicalError("An input LineString must be valid.")
         elif ob.is_closed and len(ob.coords) >= 4:
             return geos_geom_from_py(ob, lgeos.GEOSGeom_createLinearRing)
         else:
@@ -518,7 +521,7 @@ def geos_polygon_from_py(shell, holes=None):
                 N = exemplar._ndim
             if not L >= 1:
                 raise ValueError("number of holes must be non zero")
-            if not N in (2, 3):
+            if N not in (2, 3):
                 raise ValueError("insufficiant coordinate dimension")
 
             # Array of pointers to ring geometries
diff --git a/shapely/geos.py b/shapely/geos.py
index f4e6a4b..d5a67d2 100644
--- a/shapely/geos.py
+++ b/shapely/geos.py
@@ -130,6 +130,8 @@ elif sys.platform == 'darwin':
                 "/Library/Frameworks/GEOS.framework/Versions/Current/GEOS",
                 # macports
                 '/opt/local/lib/libgeos_c.dylib',
+                # homebrew
+                '/usr/local/lib/libgeos_c.dylib',
             ]
         _lgeos = load_dll('geos_c', fallbacks=alt_paths)
 
@@ -842,6 +844,7 @@ class LGEOS330(LGEOS320):
         self.methods['cascaded_union'] = self.methods['unary_union']
         self.methods['snap'] = self.GEOSSnap
         self.methods['shared_paths'] = self.GEOSSharedPaths
+        self.methods['buffer_with_params'] = self.GEOSBufferWithParams
 
 
 class LGEOS340(LGEOS330):
diff --git a/shapely/impl.py b/shapely/impl.py
index ac179ac..cc6550a 100644
--- a/shapely/impl.py
+++ b/shapely/impl.py
@@ -144,7 +144,9 @@ IMPL320 = {
     }
 
 IMPL330 = {
-    'is_closed': (UnaryPredicate, 'is_closed')}
+    'is_closed': (UnaryPredicate, 'is_closed'),
+    'buffer_with_params': (UnaryTopologicalOp, 'buffer_with_params'),
+}
 
 
 def impl_items(defs):
diff --git a/shapely/ops.py b/shapely/ops.py
index 718c811..eff851f 100644
--- a/shapely/ops.py
+++ b/shapely/ops.py
@@ -520,20 +520,22 @@ def substring(geom, start_dist, end_dist, normalized=False):
         min_dist *= geom.length
         max_dist *= geom.length
     
-    vertex_list = [(start_point.x, start_point.y)]
+    if start_dist < end_dist:
+        vertex_list = [(start_point.x, start_point.y)]
+    else:
+        vertex_list = [(end_point.x, end_point.y)]
     coords = list(geom.coords)
     for p in coords:
         pd = geom.project(Point(p))
-        if pd <= min_dist:
-            pass
-        elif min_dist < pd < max_dist:
+        if min_dist < pd < max_dist:
             vertex_list.append(p)
-        else:
+        elif pd >= max_dist:
             break
-    vertex_list.append((end_point.x, end_point.y))
-
-    # reverse direction of section
-    if start_dist > end_dist:
+    if start_dist < end_dist:
+        vertex_list.append((end_point.x, end_point.y))
+    else:
+        vertex_list.append((start_point.x, start_point.y))
+        # reverse direction result
         vertex_list = reversed(vertex_list)
 
     return LineString(vertex_list)
diff --git a/shapely/speedups/_speedups.pyx b/shapely/speedups/_speedups.pyx
index 60d0492..a66ef9b 100644
--- a/shapely/speedups/_speedups.pyx
+++ b/shapely/speedups/_speedups.pyx
@@ -11,6 +11,8 @@ import logging
 from shapely.geos import lgeos
 from shapely.geometry import Point, LineString, LinearRing
 from shapely.geometry.base import geom_factory
+from shapely.errors import TopologicalError
+
 
 include "../_geos.pxi"
 
@@ -216,7 +218,7 @@ def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
     cdef int i, n, M, sm, sn
 
     # If a LinearRing is passed in, just clone it and return
-    # If a LineString is passed in, clone the coord seq and return a LinearRing
+    # If a valid LineString is passed in, clone the coord seq and return a LinearRing
     if isinstance(ob, LineString):
         g = cast_geom(ob._geom)
         if GEOSHasZ_r(handle, g):
@@ -229,9 +231,12 @@ def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
         else:
             cs = <GEOSCoordSequence*>GEOSGeom_getCoordSeq_r(handle, g)
             GEOSCoordSeq_getSize_r(handle, cs, &m)
-            if GEOSisClosed_r(handle, g) and m >= 4:
+            if not GEOSisValid_r(handle, g):
+                raise TopologicalError("A LineString must be valid.")
+            elif GEOSisClosed_r(handle, g) and m >= 4:
                 cs = GEOSCoordSeq_clone_r(handle, cs)
                 return <uintptr_t>GEOSGeom_createLinearRing_r(handle, cs), n
+            # else continue below.
 
     # If numpy is present, we use numpy.require to ensure that we have a
     # C-continguous array that owns its data. View data will be copied.


Test patch:
diff --git a/tests/test_buffer.py b/tests/test_buffer.py
new file mode 100644
index 0000000..454c3cf
--- /dev/null
+++ b/tests/test_buffer.py
@@ -0,0 +1,80 @@
+from . import unittest
+from shapely import geometry
+
+
+class BufferSingleSidedCase(unittest.TestCase):
+    """ Test Buffer Point/Line/Polygon with and without single_sided params """
+
+    def test_empty(self):
+        g = geometry.Point(0,0)
+        h = g.buffer(0)
+        assert h.is_empty
+
+    def test_point(self):
+        g = geometry.Point(0, 0)
+        h = g.buffer(1, resolution=1)
+        self.assertEqual(h.geom_type, 'Polygon')
+        expected_coord = [(1.0, 0.0), (0, -1.0), (-1.0, 0), (0, 1.0), (1.0, 0.0)]
+        for index, coord in enumerate(h.exterior.coords):
+            self.assertAlmostEqual(coord[0], expected_coord[index][0])
+            self.assertAlmostEqual(coord[1], expected_coord[index][1])
+
+    def test_point_single_sidedd(self):
+        g = geometry.Point(0, 0)
+        h = g.buffer(1, resolution=1, single_sided=True)
+        self.assertEqual(h.geom_type, 'Polygon')
+        expected_coord = [(1.0, 0.0), (0, -1.0), (-1.0, 0), (0, 1.0), (1.0, 0.0)]
+        for index, coord in enumerate(h.exterior.coords):
+            self.assertAlmostEqual(coord[0], expected_coord[index][0])
+            self.assertAlmostEqual(coord[1], expected_coord[index][1])
+
+    def test_line(self):
+        g = geometry.LineString([[0, 0], [0, 1]])
+        h = g.buffer(1, resolution=1)
+        self.assertEqual(h.geom_type, 'Polygon')
+        expected_coord = [(-1.0, 1.0), (0, 2.0), (1.0, 1.0), (1.0, 0.0), (0, -1.0), (-1.0, 0.0),
+                          (-1.0, 1.0)]
+        for index, coord in enumerate(h.exterior.coords):
+            self.assertAlmostEqual(coord[0], expected_coord[index][0])
+            self.assertAlmostEqual(coord[1], expected_coord[index][1])
+
+    def test_line_single_sideded_left(self):
+        g = geometry.LineString([[0, 0], [0, 1]])
+        h = g.buffer(1, resolution=1, single_sided=True)
+        self.assertEqual(h.geom_type, 'Polygon')
+        expected_coord = [(0.0, 1.0), (0.0, 0.0), (-1.0, 0.0), (-1.0, 1.0), (0.0, 1.0)]
+        for index, coord in enumerate(h.exterior.coords):
+            self.assertAlmostEqual(coord[0], expected_coord[index][0])
+            self.assertAlmostEqual(coord[1], expected_coord[index][1])
+
+    def test_line_single_sideded_right(self):
+        g = geometry.LineString([[0, 0], [0, 1]])
+        h = g.buffer(-1, resolution=1, single_sided=True)
+        self.assertEqual(h.geom_type, 'Polygon')
+        expected_coord = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0), (0.0, 0.0)]
+        for index, coord in enumerate(h.exterior.coords):
+            self.assertAlmostEqual(coord[0], expected_coord[index][0])
+            self.assertAlmostEqual(coord[1], expected_coord[index][1])
+
+    def test_polygon(self):
+        g = geometry.Polygon([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
+        h = g.buffer(1, resolution=1)
+        self.assertEqual(h.geom_type, 'Polygon')
+        expected_coord = [(-1.0, 0.0), (-1.0, 1.0), (0.0, 2.0), (1.0, 2.0), (2.0, 1.0), (2.0, 0.0),
+                          (1.0, -1.0), (0.0, -1.0), (-1.0, 0.0)]
+        for index, coord in enumerate(h.exterior.coords):
+            self.assertAlmostEqual(coord[0], expected_coord[index][0])
+            self.assertAlmostEqual(coord[1], expected_coord[index][1])
+
+    def test_polygon_single_sideded(self):
+        g = geometry.Polygon([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
+        h = g.buffer(1, resolution=1, single_sided=True)
+        self.assertEqual(h.geom_type, 'Polygon')
+        expected_coord = [(-1.0, 0.0), (-1.0, 1.0), (0.0, 2.0), (1.0, 2.0), (2.0, 1.0), (2.0, 0.0),
+                          (1.0, -1.0), (0.0, -1.0), (-1.0, 0.0)]
+        for index, coord in enumerate(h.exterior.coords):
+            self.assertAlmostEqual(coord[0], expected_coord[index][0])
+            self.assertAlmostEqual(coord[1], expected_coord[index][1])
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(BufferSingleSidedCase)
diff --git a/tests/test_geos_err_handler.py b/tests/test_geos_err_handler.py
index ceea3ab..29ff874 100644
--- a/tests/test_geos_err_handler.py
+++ b/tests/test_geos_err_handler.py
@@ -35,7 +35,7 @@ def test_error_handler(tmpdir):
     LineString([(0, 0), (2, 2)]).project(LineString([(1, 1), (1.5, 1.5)]))
 
     log = open(logfile).read()
-    assert "third argument of GEOSProject_r must be Point*" in log
+    assert "third argument of GEOSProject_r must be Point" in log
 
 
 def test_error_handler_wrong_type():
diff --git a/tests/test_operations.py b/tests/test_operations.py
index 5c81c37..dee57cb 100644
--- a/tests/test_operations.py
+++ b/tests/test_operations.py
@@ -22,8 +22,7 @@ class OperationsTestCase(unittest.TestCase):
         self.assertIsInstance(point.envelope, Point)
 
         # Intersection
-        self.assertIsInstance(point.intersection(Point(-1, -1)),
-                              GeometryCollection)
+        self.assertTrue(point.intersection(Point(-1, -1)).is_empty)
 
         # Buffer
         self.assertIsInstance(point.buffer(10.0), Polygon)
diff --git a/tests/test_polygon.py b/tests/test_polygon.py
index 2bd76f7..a00b7ed 100644
--- a/tests/test_polygon.py
+++ b/tests/test_polygon.py
@@ -1,7 +1,9 @@
 """Polygons and Linear Rings
 """
+
 from . import unittest, numpy
 from shapely.wkb import loads as load_wkb
+from shapely.errors import TopologicalError
 from shapely.geos import lgeos
 from shapely.geometry import Point, Polygon, asPolygon
 from shapely.geometry.polygon import LinearRing, LineString, asLinearRing
@@ -138,27 +140,21 @@ class PolygonTestCase(unittest.TestCase):
         self.assertEqual('LinearRing',
                          lgeos.GEOSGeomType(ring._geom).decode('ascii'))
 
-
-    def test_linearring_from_short_closed_linestring(self):
-        # Create linearring from linestring where the coordinate sequence is
-        # too short but appears to be closed (first and last coordinates
-        # are the same)
+    def test_linearring_from_invalid(self):
         coords = [(0.0, 0.0), (0.0, 0.0), (0.0, 0.0)]
         line = LineString(coords)
-        ring1 = LinearRing(coords)
-        ring2 = LinearRing(line)
-        assert ring1.coords[:] == ring2.coords[:]
-
+        self.assertFalse(line.is_valid)
+        with self.assertRaises(TopologicalError):
+            ring = LinearRing(line)
 
     def test_linearring_from_too_short_linestring(self):
         # Creation of LinearRing request at least 3 coordinates (unclosed) or
         # 4 coordinates (closed)
         coords = [(0.0, 0.0), (0.0, 0.0)]
         line = LineString(coords)
-        with self.assertRaises(ValueError):
+        with self.assertRaises(TopologicalError):
             LinearRing(line)
 
-
     @unittest.skipIf(not numpy, 'Numpy required')
     def test_numpy(self):
 
diff --git a/tests/test_substring.py b/tests/test_substring.py
index 1cf19d0..8917753 100644
--- a/tests/test_substring.py
+++ b/tests/test_substring.py
@@ -70,7 +70,11 @@ class SubstringTestCase(unittest.TestCase):
         self.assertTrue(substring(self.line2, 0.2, 0.9, True).equals(LineString(([3, 1.5], [3, 6], [3.75, 6]))))
         self.assertTrue(substring(self.line2, 0, 0.9, True).equals(LineString(([3, 0], [3, 6], [3.75, 6]))))
         self.assertTrue(substring(self.line2, 0.2, 1, True).equals(LineString(([3, 1.5], [3, 6], [4.5, 6]))))
-        
+
+    @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required')
+    def test_return_substring_issue682(self):
+        assert list(substring(self.line2, 0.1, 0).coords) == [(3.0, 0.1), (3.0, 0.0)]
+
 
 def test_suite():
     loader = unittest.TestLoader()