Skip to content

Commit

Permalink
ENH: add wkt / wkb ufuncs (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisvandenbossche authored and caspervdw committed Oct 21, 2019
1 parent 0034785 commit 2ae2813
Show file tree
Hide file tree
Showing 7 changed files with 678 additions and 247 deletions.
1 change: 1 addition & 0 deletions pygeos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .set_operations import *
from .linear import *
from .coordinates import *
from .io import *

from ._version import get_versions

Expand Down
174 changes: 174 additions & 0 deletions pygeos/io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import numpy as np

from . import Geometry # noqa
from . import lib


__all__ = ["from_wkb", "from_wkt", "to_wkb", "to_wkt"]


def to_wkt(
geometry,
rounding_precision=6,
trim=True,
output_dimension=3,
old_3d=False,
**kwargs
):
"""
Converts to the Well-Known Text (WKT) representation of a Geometry.
The Well-known Text format is defined in the `OGC Simple Features
Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__.
Parameters
----------
geometry : Geometry or array_like
rounding_precision : int, default 6
The rounding precision when writing the WKT string. Set to a value of
-1 to indicate the full precision.
trim : bool, default True
Whether to trim unnecessary decimals (trailing zeros).
output_dimension : int, default 3
The output dimension for the WKT string. Supported values are 2 and 3.
Specifying 3 means that up to 3 dimensions will be written but 2D
geometries will still be represented as 2D in the WKT string.
old_3d : bool, default False
Enable old style 3D/4D WKT generation. By default, new style 3D/4D WKT
(ie. "POINT Z (10 20 30)") is returned, but with ``old_3d=True``
the WKT will be formatted in the style "POINT (10 20 30)".
Examples
--------
>>> to_wkt(Geometry("POINT (0 0)"))
'POINT (0 0)'
>>> to_wkt(Geometry("POINT (0 0)"), rounding_precision=3, trim=False)
'POINT (0.000 0.000)'
>>> to_wkt(Geometry("POINT (0 0)"), rounding_precision=-1, trim=False)
'POINT (0.0000000000000000 0.0000000000000000)'
>>> to_wkt(Geometry("POINT (1 2 3)"), trim=True)
'POINT Z (1 2 3)'
>>> to_wkt(Geometry("POINT (1 2 3)"), trim=True, output_dimension=2)
'POINT (1 2)'
>>> to_wkt(Geometry("POINT (1 2 3)"), trim=True, old_3d=True)
'POINT (1 2 3)'
Notes
-----
The defaults differ from the default of the GEOS library. To mimic this,
use::
to_wkt(geometry, rounding_precision=-1, trim=False, output_dimension=2)
"""
if not np.isscalar(rounding_precision):
raise TypeError("rounding_precision only accepts scalar values")
if not np.isscalar(trim):
raise TypeError("trim only accepts scalar values")
if not np.isscalar(output_dimension):
raise TypeError("output_dimension only accepts scalar values")
if not np.isscalar(old_3d):
raise TypeError("old_3d only accepts scalar values")

return lib.to_wkt(
geometry,
np.intc(rounding_precision),
np.bool(trim),
np.intc(output_dimension),
np.bool(old_3d),
**kwargs,
)


def to_wkb(geometry, hex=False, output_dimension=3, byte_order=-1, include_srid=False, **kwargs):
r"""
Converts to the Well-Known Binary (WKB) representation of a Geometry.
The Well-Known Binary format is defined in the `OGC Simple Features
Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__.
Parameters
----------
geometry : Geometry or array_like
hex : bool, default False
If true, export the WKB as a hexidecimal string. The default is to
return a binary bytes object.
output_dimension : int, default 3
The output dimension for the WKB. Supported values are 2 and 3.
Specifying 3 means that up to 3 dimensions will be written but 2D
geometries will still be represented as 2D in the WKB represenation.
byte_order : int
Defaults to native machine byte order (-1). Use 0 to force big endian
and 1 for little endian.
include_srid : bool, default False
Whether the SRID should be included in WKB (this is an extension
to the OGC WKB specification).
Examples
--------
>>> to_wkb(Geometry("POINT (1 1)"))
b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?'
>>> to_wkb(Geometry("POINT (1 1)"), hex=True)
'0101000000000000000000F03F000000000000F03F'
"""
if not np.isscalar(hex):
raise TypeError("hex only accepts scalar values")
if not np.isscalar(output_dimension):
raise TypeError("output_dimension only accepts scalar values")
if not np.isscalar(byte_order):
raise TypeError("byte_order only accepts scalar values")
if not np.isscalar(include_srid):
raise TypeError("include_srid only accepts scalar values")

return lib.to_wkb(
geometry,
np.bool(hex),
np.intc(output_dimension),
np.intc(byte_order),
np.bool(include_srid),
**kwargs,
)


def from_wkt(geometry, **kwargs):
"""
Creates geometries from the Well-Known Text (WKT) representation.
The Well-known Text format is defined in the `OGC Simple Features
Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__.
Parameters
----------
geometry : str or array_like
The WKT string(s) to convert.
Examples
--------
>>> from_wkt('POINT (0 0)')
<pygeos.Geometry POINT (0 0)>
"""
return lib.from_wkt(geometry, **kwargs)


def from_wkb(geometry, **kwargs):
r"""
Creates geometries from the Well-Known Binary (WKB) representation.
The Well-Known Binary format is defined in the `OGC Simple Features
Specification for SQL <https://www.opengeospatial.org/standards/sfs>`__.
Parameters
----------
geometry : str or array_like
The WKB byte object(s) to convert.
Examples
--------
>>> from_wkb(b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?')
<pygeos.Geometry POINT (1 1)>
"""
# ensure the input has object dtype, to avoid numpy inferring it as a
# fixed-length string dtype (which removes trailing null bytes upon access
# of array elements)
geometry = np.asarray(geometry, dtype=object)
return lib.from_wkb(geometry, **kwargs)
44 changes: 22 additions & 22 deletions pygeos/test/test_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ def box_tpl(x1, y1, x2, y2):

def test_points_from_coords():
actual = pygeos.points([[0, 0], [2, 2]])
assert actual[0].to_wkt() == "POINT (0 0)"
assert actual[1].to_wkt() == "POINT (2 2)"
assert str(actual[0]) == "POINT (0 0)"
assert str(actual[1]) == "POINT (2 2)"


def test_points_from_xy():
actual = pygeos.points(2, [0, 1])
assert actual[0].to_wkt() == "POINT (2 0)"
assert actual[1].to_wkt() == "POINT (2 1)"
assert str(actual[0]) == "POINT (2 0)"
assert str(actual[1]) == "POINT (2 1)"


def test_points_from_xyz():
actual = pygeos.points(1, 1, [0, 1])
assert actual[0].to_wkt() == "POINT Z (1 1 0)"
assert actual[1].to_wkt() == "POINT Z (1 1 1)"
assert str(actual[0]) == "POINT Z (1 1 0)"
assert str(actual[1]) == "POINT Z (1 1 1)"


def test_points_invalid_ndim():
Expand All @@ -34,51 +34,51 @@ def test_points_invalid_ndim():

def test_linestrings_from_coords():
actual = pygeos.linestrings([[[0, 0], [1, 1]], [[0, 0], [2, 2]]])
assert actual[0].to_wkt() == "LINESTRING (0 0, 1 1)"
assert actual[1].to_wkt() == "LINESTRING (0 0, 2 2)"
assert str(actual[0]) == "LINESTRING (0 0, 1 1)"
assert str(actual[1]) == "LINESTRING (0 0, 2 2)"


def test_linestrings_from_xy():
actual = pygeos.linestrings([0, 1], [2, 3])
assert actual.to_wkt() == "LINESTRING (0 2, 1 3)"
assert str(actual) == "LINESTRING (0 2, 1 3)"


def test_linestrings_from_xy_broadcast():
x = [0, 1] # the same X coordinates for both linestrings
y = [2, 3], [4, 5] # each linestring has a different set of Y coordinates
actual = pygeos.linestrings(x, y)
assert actual[0].to_wkt() == "LINESTRING (0 2, 1 3)"
assert actual[1].to_wkt() == "LINESTRING (0 4, 1 5)"
assert str(actual[0]) == "LINESTRING (0 2, 1 3)"
assert str(actual[1]) == "LINESTRING (0 4, 1 5)"


def test_linestrings_from_xyz():
actual = pygeos.linestrings([0, 1], [2, 3], 0)
assert actual.to_wkt() == "LINESTRING Z (0 2 0, 1 3 0)"
assert str(actual) == "LINESTRING Z (0 2 0, 1 3 0)"


def test_linearrings():
actual = pygeos.linearrings(box_tpl(0, 0, 1, 1))
assert actual.to_wkt() == "LINEARRING (1 0, 1 1, 0 1, 0 0, 1 0)"
assert str(actual) == "LINEARRING (1 0, 1 1, 0 1, 0 0, 1 0)"


def test_linearrings_from_xy():
actual = pygeos.linearrings([0, 1, 2, 0], [3, 4, 5, 3])
assert actual.to_wkt() == "LINEARRING (0 3, 1 4, 2 5, 0 3)"
assert str(actual) == "LINEARRING (0 3, 1 4, 2 5, 0 3)"


def test_linearrings_unclosed():
actual = pygeos.linearrings(box_tpl(0, 0, 1, 1)[:-1])
assert actual.to_wkt() == "LINEARRING (1 0, 1 1, 0 1, 0 0, 1 0)"
assert str(actual) == "LINEARRING (1 0, 1 1, 0 1, 0 0, 1 0)"


def test_polygon_from_linearring():
actual = pygeos.polygons(pygeos.linearrings(box_tpl(0, 0, 1, 1)))
assert actual.to_wkt() == "POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))"
assert str(actual) == "POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))"


def test_polygons():
actual = pygeos.polygons(box_tpl(0, 0, 1, 1))
assert actual.to_wkt() == "POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))"
assert str(actual) == "POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))"


def test_polygon_no_hole_list_raises():
Expand Down Expand Up @@ -123,20 +123,20 @@ def test_2_polygons_with_different_holes():

def test_create_collection_only_none():
actual = pygeos.multipoints(np.array([None], dtype=object))
assert actual.to_wkt() == "MULTIPOINT EMPTY"
assert str(actual) == "MULTIPOINT EMPTY"


def test_create_collection_skips_none():
actual = pygeos.multipoints([point, None, None, point])
assert actual.to_wkt() == "MULTIPOINT (2 3, 2 3)"
assert str(actual) == "MULTIPOINT (2 3, 2 3)"


def test_box():
actual = pygeos.box(0, 0, 1, 1)
assert actual.to_wkt() == "POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))"
assert str(actual) == "POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))"


def test_box_multiple():
actual = pygeos.box(0, 0, [1, 2], [1, 2])
assert actual[0].to_wkt() == "POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))"
assert actual[1].to_wkt() == "POLYGON ((2 0, 2 2, 0 2, 0 0, 2 0))"
assert str(actual[0]) == "POLYGON ((1 0, 1 1, 0 1, 0 0, 1 0))"
assert str(actual[1]) == "POLYGON ((2 0, 2 2, 0 2, 0 0, 2 0))"

0 comments on commit 2ae2813

Please sign in to comment.