Skip to content

Commit

Permalink
Refactor Geometry classes to subclass the C extension type (#983)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisvandenbossche committed Nov 4, 2020
1 parent 1d734de commit 4f28db9
Show file tree
Hide file tree
Showing 34 changed files with 489 additions and 929 deletions.
42 changes: 2 additions & 40 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,61 +13,21 @@ matrix:
GEOSVERSION="3.5.2"
SPEEDUPS=1
NUMPY=1
- python: "3.5"
env:
GEOSVERSION="3.5.2"
SPEEDUPS=0
NUMPY=1
- python: "3.5"
env:
GEOSVERSION="3.5.2"
SPEEDUPS=0
NUMPY=0
- python: "3.6"
env:
GEOSVERSION="3.6.4"
SPEEDUPS=1
NUMPY=1
- python: "3.6"
env:
GEOSVERSION="3.6.4"
SPEEDUPS=0
NUMPY=1
- python: "3.6"
env:
GEOSVERSION="3.6.4"
SPEEDUPS=0
NUMPY=0
- python: "3.7"
env:
GEOSVERSION="3.7.3"
SPEEDUPS=1
NUMPY=1
- python: "3.7"
env:
GEOSVERSION="3.7.3"
SPEEDUPS=0
NUMPY=1
- python: "3.7"
env:
GEOSVERSION="3.7.3"
SPEEDUPS=0
NUMPY=0
- python: "3.8"
env:
GEOSVERSION="3.8.1"
SPEEDUPS=1
NUMPY=1
- python: "3.8"
env:
GEOSVERSION="3.8.1"
SPEEDUPS=0
NUMPY=1
- python: "3.8"
env:
GEOSVERSION="3.8.1"
SPEEDUPS=0
NUMPY=0
- python: "3.9-dev"
env:
GEOSVERSION="master"
Expand All @@ -83,6 +43,8 @@ matrix:
before_install:
- ./ci/travis/install_geos.sh
- pip install --upgrade pip
- export GEOS_CONFIG=$HOME/geosinstall/geos-$GEOSVERSION/bin/geos-config
- pip install git+https://github.com/pygeos/pygeos.git -v
# 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
Expand Down
14 changes: 12 additions & 2 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,26 @@ build_script:
- python -c "import sys; print('Python ' + sys.version)"
- python -c "import ctypes, os; print(ctypes.CDLL(os.environ['GEOS_LIBRARY_PATH']))"

- ps: 'Write-Host "Building extension" -ForegroundColor Magenta'
- ps: 'Write-Host "Installing dev requirements" -ForegroundColor Magenta'
- cd %APPVEYOR_BUILD_FOLDER%
- pip install -r requirements-dev.txt

- ps: 'Write-Host "Installing PyGEOS" -ForegroundColor Magenta'
- set GEOS_LIBRARY_PATH=%GEOSINSTALL%\lib
- set GEOS_INCLUDE_PATH=%GEOSINSTALL%\include
- pip install git+https://github.com/pygeos/pygeos.git -v

- ps: 'Write-Host "Building extension" -ForegroundColor Magenta'
- set GEOS_LIBRARY_PATH=%GEOSINSTALL%\bin\geos_c.dll
- python setup.py build_ext --inplace -I%CYTHONPF%\\include -lgeos_c -L%CYTHONPF%\\lib

- ps: 'Write-Host "Running pytest" -ForegroundColor Magenta'
- pytest
- python -c "import shapely; print(shapely.__version__)"
- python -c "from shapely.geos import geos_version_string; print(geos_version_string)"
- python -c "from shapely import speedups; assert speedups.enabled"
# this fails on python>=3.8 with pygeos due to wrong DLL detection,
# temporarily skipping since the speedups module will be removed later anyway
#- python -c "from shapely import speedups; assert speedups.enabled"


cache:
Expand Down
10 changes: 8 additions & 2 deletions shapely/algorithms/cga.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pygeos


def signed_area(ring):
"""Return the signed area enclosed by a ring in linear time using the
Expand All @@ -8,9 +10,13 @@ def signed_area(ring):
ys.append(ys[1])
return sum(xs[i]*(ys[i+1]-ys[i-1]) for i in range(1, len(ring.coords)))/2.0

def is_ccw_impl(name):
def is_ccw_impl(name=None):
"""Predicate implementation"""
def is_ccw_op(ring):
return signed_area(ring) >= 0.0
return is_ccw_op

if pygeos.geos_version >= (3, 7, 0):
return pygeos.is_ccw
else:
return is_ccw_op

107 changes: 11 additions & 96 deletions shapely/coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,133 +25,48 @@ class CoordinateSequence(object):
"""

# Attributes
# ----------
# _cseq : c_void_p
# Ctypes pointer to GEOS coordinate sequence
# _ndim : int
# Number of dimensions (2 or 3, generally)
# __p__ : object
# Parent (Shapely) geometry
_cseq = None
_ndim = None
__p__ = None

def __init__(self, parent):
self.__p__ = parent

def _update(self):
self._ndim = self.__p__._ndim
self._cseq = lgeos.GEOSGeom_getCoordSeq(self.__p__._geom)
def __init__(self, coords):
self._coords = coords

def __len__(self):
self._update()
cs_len = c_uint(0)
lgeos.GEOSCoordSeq_getSize(self._cseq, byref(cs_len))
return cs_len.value
return self._coords.shape[0]

def __iter__(self):
self._update()
dx = c_double()
dy = c_double()
dz = c_double()
has_z = self._ndim == 3
for i in range(self.__len__()):
lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(dx))
lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(dy))
if has_z:
lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(dz))
yield (dx.value, dy.value, dz.value)
else:
yield (dx.value, dy.value)
yield tuple(self._coords[i].tolist())

def __getitem__(self, key):
self._update()
dx = c_double()
dy = c_double()
dz = c_double()
m = self.__len__()
has_z = self._ndim == 3
if isinstance(key, int):
if key + m < 0 or key >= m:
raise IndexError("index out of range")
if key < 0:
i = m + key
else:
i = key
lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(dx))
lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(dy))
if has_z:
lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(dz))
return (dx.value, dy.value, dz.value)
else:
return (dx.value, dy.value)
return tuple(self._coords[i].tolist())
elif isinstance(key, slice):
res = []
start, stop, stride = key.indices(m)
for i in range(start, stop, stride):
lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(dx))
lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(dy))
if has_z:
lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(dz))
res.append((dx.value, dy.value, dz.value))
else:
res.append((dx.value, dy.value))
res.append(tuple(self._coords[i].tolist()))
return res
else:
raise TypeError("key must be an index or slice")

@property
def _ctypes(self):
self._update()
has_z = self._ndim == 3
n = self._ndim
m = self.__len__()
array_type = c_double * (m * n)
data = array_type()
temp = c_double()
for i in range(m):
lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(temp))
data[n*i] = temp.value
lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(temp))
data[n*i+1] = temp.value
if has_z:
lgeos.GEOSCoordSeq_getZ(self._cseq, i, byref(temp))
data[n*i+2] = temp.value
return data

def array_interface(self):
"""Provide the Numpy array protocol."""
if sys.byteorder == 'little':
typestr = '<f8'
elif sys.byteorder == 'big':
typestr = '>f8'
else:
raise ValueError(
"Unsupported byteorder: neither little nor big-endian")
ai = {
'version': 3,
'typestr': typestr,
'data': self._ctypes,
}
ai.update({'shape': (len(self), self._ndim)})
return ai

__array_interface__ = property(array_interface)
def __array__(self, dtype=None):
return self._coords

@property
def xy(self):
"""X and Y arrays"""
self._update()
m = self.__len__()
x = array('d')
y = array('d')
temp = c_double()
for i in range(m):
lgeos.GEOSCoordSeq_getX(self._cseq, i, byref(temp))
x.append(temp.value)
lgeos.GEOSCoordSeq_getY(self._cseq, i, byref(temp))
y.append(temp.value)
xy = self._coords[i].tolist()
x.append(xy[0])
y.append(xy[1])
return x, y


Expand Down

0 comments on commit 4f28db9

Please sign in to comment.