Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cythonize EWKB reader and writer #2

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions benchmark/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from timeit import timeit

from postgis import Point


def main():
point = Point([1.23456789, 48.765432])
ewkb = point.to_ewkb()
time = timeit('read(ewkb)',
setup='from postgis.ewkb import read',
number=1000000, globals=locals())
print("Read Point", time)
time = timeit('point.to_ewkb()',
number=1000000, globals=locals())
print("Write Point", time)


if __name__ == '__main__':
main()
20 changes: 11 additions & 9 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
machine:
python:
version: 3.6.1
python:
version: 3.6.1

database:
pre:
- psql -U ubuntu -c "CREATE DATABASE test;"
- psql -U ubuntu -c "create extension postgis" -d test
pre:
- psql -U ubuntu -c "CREATE DATABASE test;"
- psql -U ubuntu -c "create extension postgis" -d test

test:
override:
- py.test tests/
override:
- py.test tests/

dependencies:
pre:
- pip install pytest psycopg2 asyncpg cython
override:
- python setup.py develop
pre:
- pip install -U pip setuptools pytest psycopg2 asyncpg cython
49 changes: 28 additions & 21 deletions postgis/ewkb.py → postgis/ewkb.pyx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import binascii
from io import BytesIO
import struct
from cpython cimport bool


class Typed(type):
Expand All @@ -20,17 +21,23 @@ def __call__(cls, *args, **kwargs):
return super().__call__(*args, **kwargs)


class Reader:
cdef class Reader:

__slots__ = ['stream', 'endianness', 'has_z', 'has_m']
cdef object stream
cdef bytes endianness
cdef public bool has_z
cdef public bool has_m

def __init__(self, stream):
def __cinit__(self, object stream):
self.stream = stream

def clone(self):
cpdef clone(self):
return type(self)(self.stream)

def read(self):
cpdef read(self):
return self._read()

cdef _read(self):
# https://en.wikipedia.org/wiki/Well-known_text#Well-known_binary
byte_order = self.stream.read(1)
if byte_order == b'\x00':
Expand All @@ -53,22 +60,22 @@ def read(self):
else:
return class_.from_ewkb_body(self, srid)

def read_int(self):
cpdef read_int(self):
return struct.unpack(self.endianness + b'I', self.stream.read(4))[0]

def read_double(self):
cpdef read_double(self):
return struct.unpack(self.endianness + b'd', self.stream.read(8))[0]

@classmethod
def from_hex(cls, value):
return cls(BytesIO(binascii.a2b_hex(value))).read()

cpdef read(str value):
return Reader(BytesIO(binascii.a2b_hex(value))).read()


class Writer:
cdef class Writer:

__slots__ = ['stream']
cdef object stream

def __init__(self, geometry, stream=None):
def __cinit__(self, object geometry, object stream=None):
self.stream = stream or BytesIO()
try:
type_ = geometry.TYPE
Expand All @@ -85,17 +92,17 @@ def __init__(self, geometry, stream=None):
if geometry.has_srid:
self.write_int(geometry.srid)

def write_int(self, value):
cpdef write_int(self, value):
self.stream.write(struct.pack(b'<I', value))

def write_double(self, value):
cpdef write_double(self, value):
self.stream.write(struct.pack(b'<d', value))

def clone(self, geometry):
cpdef clone(self, object geometry):
return type(self)(geometry, self.stream)

@classmethod
def to_hex(cls, value):
writer = cls(value)
value.write_ewkb_body(writer)
return binascii.b2a_hex(writer.stream.getvalue()).upper()

cpdef bytes write(object value):
writer = Writer(value)
value.write_ewkb_body(writer)
return binascii.b2a_hex(writer.stream.getvalue()).upper()
6 changes: 3 additions & 3 deletions postgis/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
warnings.warn('psycopg2 not installed', ImportWarning)


from .ewkb import Reader, Typed, Writer
from .ewkb import read, Typed, write
from .geojson import GeoJSON


Expand All @@ -29,10 +29,10 @@ def has_srid(self):
def from_ewkb(value, cursor=None):
if not value:
return None
return Reader.from_hex(value)
return read(value)

def to_ewkb(self):
return Writer.to_hex(self).decode()
return write(self).decode()

def write_ewkb(self, writer):
self.write_ewkb_body(writer.clone(self))
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from codecs import open # To use a consistent encoding
from os import path
import sys
# from Cython.Build import cythonize

HERE = path.abspath(path.dirname(__file__))

Expand Down Expand Up @@ -42,6 +43,7 @@ def list_modules(dirname):
ext_modules = [
Extension('postgis.' + ext, [path.join('postgis', ext + '.py')])
for ext in list_modules(path.join(HERE, 'postgis'))]
ext_modules.append(Extension('postgis.ewkb', ['postgis/ewkb.pyx']))

cmdclass = {'build_ext': build_ext}

Expand Down