Skip to content

Commit

Permalink
Remove ctypes module
Browse files Browse the repository at this point in the history
We now raise RuntimeError for both failed encode and decode so there's no real need to use ctypes
  • Loading branch information
urschrei committed May 3, 2024
1 parent e512b23 commit ec93ff9
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 225 deletions.
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,9 @@ polyline = encode_coordinates(coords, 5)
decoded_coords = decode_polyline(polyline, 5)
```

## Cython Module 🔥
If you're comfortable with a lack of built-in exceptions, you should use the compiled Cython version of the functions, giving a 3x speedup over the `ctypes` functions:
```python
from pypolyline.cutil import encode_coordinates, decode_polyline
```
- Longitude errors will return strings beginning with `Longitude error:`
- Latitude errors will return strings beginning with `Latitude error:`
- Polyline errors will return `[[nan, nan]]`
## Error Handling
Failure to encode coordinates, or to decode a supplied Polyline, will raise a `RuntimeError` which can be caught.

Otherwise, import from `util` instead, for a slower, `ctypes`-based interface. Attempts to decode an invalid Polyline will throw `util.EncodingError`
Attempts to encode invalid coordinates will throw `util.DecodingError`

## How it Works
FFI and a [Rust binary](https://github.com/urschrei/polyline-ffi)
Expand Down
4 changes: 4 additions & 0 deletions src/pypolyline/cutil.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def encode_coordinates(coords, int precision):
cdef char* result = encode_coordinates_ffi(coords_ffi, precision)
cdef bytes polyline = result
drop_cstring(result)
if np.char.startswith(polyline, b"Latitude") or np.char.startswith(polyline, b"Longitude"):
raise RuntimeError("The input coordinates could not be encoded")
return polyline

def decode_polyline(bytes polyline, int precision):
Expand All @@ -77,4 +79,6 @@ def decode_polyline(bytes polyline, int precision):
cdef double[:, ::1] view = <double[:result.len,:2:1]>incoming_ptr
cdef coords = np.copy(view).tolist()
drop_float_array(result)
if np.isnan(coords[0][0]):
raise RuntimeError("Polyline could not be decoded. Is it valid?")
return coords
185 changes: 0 additions & 185 deletions src/pypolyline/util.py

This file was deleted.

54 changes: 24 additions & 30 deletions tests/test_pypolyline.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import math
import unittest

import pytest
from pypolyline.cutil import (
decode_polyline as cdecode_polyline,
)
from pypolyline.cutil import (
encode_coordinates as cencode_coordinates,
)
from pypolyline.util import (
DecodingError,
EncodingError,
decode_polyline,
encode_coordinates,
)


class PolylineTests(unittest.TestCase):
Expand All @@ -24,24 +18,16 @@ def setUp(self):
try:
self.polyline = bytes("_p~iF~ps|U_ulLnnqC_mqNvxq`@", "utf-8")
self.bad_polyline = bytes("ugh_ugh_ugh", "utf-8")
self.bad_coordinates = [
[-120.2, 38.5],
[-120.95, 40.7],
[-126.453, 430.252],
]
except TypeError:
# python 2
self.polyline = "_p~iF~ps|U_ulLnnqC_mqNvxq`@"
self.bad_polyline = "ugh_ugh_ugh"

def testDecodePolyline(self):
"""Test that Polylines can be decoded"""
expected = self.coords
result = decode_polyline(self.polyline, 5)
for _ in range(100):
self.assertEqual(result, expected)

def testEncodeCoordinates(self):
"""Test that coordinates can be encoded"""
expected = self.polyline
result = encode_coordinates(self.coords, 5)
self.assertEqual(result, expected)

def testCDecodePolyline(self):
"""Test that Polylines can be decoded (Cython)"""
expected = self.coords
Expand All @@ -55,16 +41,24 @@ def testCEncodeCoordinates(self):
result = cencode_coordinates(self.coords, 5)
self.assertEqual(result, expected)

def testBadCoordinates(self):
"""Test that bad coordinates throw the correct error"""
coords = [[110.0, 95.0], [1.0, 2.0]]
with self.assertRaises(EncodingError):
encode_coordinates(coords, 5)

def testDecodeBadPolyline(self):
"""Test that bad Polylines throw the correct error"""
res = cdecode_polyline(self.bad_polyline, 6)
self.assertTrue(math.isnan(res[0][0]))
with pytest.raises(RuntimeError) as exc_info:
cdecode_polyline(self.bad_polyline, 6)
assert str(exc_info.value) == "Polyline could not be decoded. Is it valid?"

def testEncodeBadCoordinates(self):
"""Test that bad Polylines throw the correct error"""
with pytest.raises(RuntimeError) as exc_info:
cencode_coordinates(self.bad_coordinates, 6)
assert str(exc_info.value) == "The input coordinates could not be encoded"

# def testEncodeBadCoordinatesB(self):
# """Test that bad Polylines throw the correct error"""
# # with pytest.raises(RuntimeError) as exc_info:
# res = cencode_coordinates(self.bad_coordinates, 6)
# self.assertEqual(res, "ug")
# # assert str(exc_info.value) == "The input coordinates could not be encoded"

def testLongCoords(self):
"""Test that round-tripping is OK.
Expand Down Expand Up @@ -168,10 +162,10 @@ def testLongCoords(self):
for _ in range(10000):
# encode using ctypes and cython
cencoded = cencode_coordinates(coords, 5)
encoded = encode_coordinates(coords, 5)
encoded = cencode_coordinates(coords, 5)
# decode using ctypes and cython
cdecoded = cdecode_polyline(cencoded, 5)
decoded = decode_polyline(encoded, 5)
decoded = cdecode_polyline(encoded, 5)
# is round-tripping OK
self.assertEqual(
decoded,
Expand Down

0 comments on commit ec93ff9

Please sign in to comment.