Skip to content

Commit

Permalink
Merge branch 'main' into lint_format_io_iohandlers
Browse files Browse the repository at this point in the history
  • Loading branch information
jGaboardi committed Nov 12, 2023
2 parents c5e1bc2 + ff63bd2 commit 023699a
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 114 deletions.
2 changes: 2 additions & 0 deletions libpysal/io/tests/test_FileIO.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# ruff: noqa: N999

from ...examples import get_path
from ..fileio import FileIO

Expand Down
11 changes: 6 additions & 5 deletions libpysal/io/tests/test_Tables.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# ruff: noqa: N999

import numpy as np

# import pysal_examples
from ... import examples as pysal_examples
from ...common import pandas
from ..fileio import FileIO as psopen
from ..fileio import FileIO

PANDAS_EXTINCT = pandas is None


class TestTable:
def setup_method(self):
self.filehandler = psopen(pysal_examples.get_path("columbus.dbf"))
self.filehandler = FileIO(pysal_examples.get_path("columbus.dbf"))
self.df = self.filehandler.to_df()
self.filehandler.seek(0)
self.shapefile = psopen(pysal_examples.get_path("columbus.shp"))
self.csvhandler = psopen(pysal_examples.get_path("usjoin.csv"))
self.shapefile = FileIO(pysal_examples.get_path("columbus.shp"))
self.csvhandler = FileIO(pysal_examples.get_path("usjoin.csv"))
self.csv_df = self.csvhandler.to_df()
self.csvhandler.seek(0)

Expand Down
144 changes: 73 additions & 71 deletions libpysal/io/util/wkb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
Where PySAL shapes support multiple parts, "MULTI"type shapes
will be converted to a single multi-part shape:
MULTIPOLYGON -> Polygon
MULTILINESTRING -> Chain
Otherwise a list of shapes will be returned:
MULTIPOINT -> [pt0, ..., ptN]
Some concepts aren't well supported by PySAL shapes. For example:
wkt = 'MULTIPOLYGON EMPTY' -> '\x01 \x06\x00\x00\x00 \x00\x00\x00\x00'
| < | WKBMultiPolygon | 0 parts |
``pysal.cg.Polygon`` does not support 0 part polygons. ``None`` is returned in this case.
Expand All @@ -28,12 +28,13 @@
Building Blocks : Point, LinearRing
"""
""" # noqa E501

import struct
import sys
from io import StringIO

from ... import cg
import sys
import struct

__author__ = "Charles R Schmidt <schmidtc@gmail.com>"
__all__ = ["loads"]
Expand Down Expand Up @@ -81,24 +82,21 @@ def loads(s: str):
WKBMultiPolygon mpolygon;
}
};
Returns
-------
geom : {None, libpysal.cg.{Point, Chain, Polygon}}
The geometric object or ``None``.
Raises
------
TypeError
Raised when an unsupported shape type is passed in.
"""

# To allow recursive calls, read only the bytes we need.
if hasattr(s, "read"):
dat = s
else:
dat = StringIO(s)
dat = s if hasattr(s, "read") else StringIO(s)
endian = ENDIAN[dat.read(1)]
typ = struct.unpack("I", dat.read(4))[0]
if typ == 1:
Expand Down Expand Up @@ -186,10 +184,7 @@ def loads(s: str):
holes = sum([p.holes for p in polys if p.holes[0]], [])

# MULTIPOLYGON EMPTY, isn't well supported by PySAL shape types.
if not parts:
geom = None
else:
geom = cg.Polygon(parts, holes)
geom = None if not parts else cg.Polygon(parts, holes)
elif typ == 7:
"""
WKBGeometryCollection {
Expand All @@ -205,56 +200,63 @@ def loads(s: str):
try:
return geom
except NameError:
raise TypeError("Type (%d) is unknown or unsupported." % typ)


if __name__ == "__main__":

# TODO: Refactor below into Unit Tests
wktExamples = [
"POINT(6 10)",
"LINESTRING(3 4,10 50,20 25)",
"POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))",
"MULTIPOINT(3.5 5.6,4.8 10.5)",
"MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))",
# This MULTIPOLYGON is not valid, the 2nd shell instects the 1st.
#'MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2)),((3 3,6 2,6 4,3 3)))',
"MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2)),((5 3,6 2,6 4,5 3)))",
"GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))",
#'POINT ZM (1 1 5 60)', <-- ZM is not supported by WKB ?
#'POINT M (1 1 80)', <-- M is not supported by WKB ?
#'POINT EMPTY', <-- NOT SUPPORT
"MULTIPOLYGON EMPTY",
]

# shapely only used for testing.
try:
import shapely.wkt, shapely.geometry
from pysal.contrib.shapely_ext import to_wkb
except ImportError:
print("shapely is used to test this module.")
raise
for example in wktExamples:
print(example)
shape0 = shapely.wkt.loads(example)
shape1 = loads(shape0.to_wkb())
if example.startswith("MULTIPOINT"):
shape2 = shapely.geometry.asMultiPoint(shape1)
elif example.startswith("GEOMETRYCOLLECTION"):
shape2 = shapely.geometry.collection.GeometryCollection(
list(map(shapely.geometry.asShape, shape1))
)
elif example == "MULTIPOLYGON EMPTY":
# Skip Test
shape2 = None
else:
shape2 = shapely.geometry.asShape(shape1)

print(shape1)
if shape2:
assert shape0.equals(shape2)
print(shape0.equals(shape2))
else:
print("Skip")

print("")
raise TypeError("Type (%d) is unknown or unsupported." % typ) from None


# if __name__ == "__main__":
#
# # TODO: Refactor below into Unit Tests
# wktExamples = [
# "POINT(6 10)",
# "LINESTRING(3 4,10 50,20 25)",
# "POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))",
# "MULTIPOINT(3.5 5.6,4.8 10.5)",
# "MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))",
# # This MULTIPOLYGON is not valid, the 2nd shell instects the 1st.
# #(
# # 'MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),"
# # "(2 2, 3 2, 3 3, 2 3,2 2)),((3 3,6 2,6 4,3 3)))'
# #,
# (
# "MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),"
# "(2 2, 3 2, 3 3, 2 3,2 2)),((5 3,6 2,6 4,5 3)))"
# ),
# "GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))",
# #'POINT ZM (1 1 5 60)', <-- ZM is not supported by WKB ?
# #'POINT M (1 1 80)', <-- M is not supported by WKB ?
# #'POINT EMPTY', <-- NOT SUPPORT
# "MULTIPOLYGON EMPTY",
# ]
#
# # shapely only used for testing.
# try:
# import shapely.geometry
# import shapely.wkt
# from pysal.contrib.shapely_ext import to_wkb
# except ImportError:
# print("shapely is used to test this module.")
# raise
# for example in wktExamples:
# print(example)
# shape0 = shapely.wkt.loads(example)
# shape1 = loads(shape0.to_wkb())
# if example.startswith("MULTIPOINT"):
# shape2 = shapely.geometry.asMultiPoint(shape1)
# elif example.startswith("GEOMETRYCOLLECTION"):
# shape2 = shapely.geometry.collection.GeometryCollection(
# list(map(shapely.geometry.asShape, shape1))
# )
# elif example == "MULTIPOLYGON EMPTY":
# # Skip Test
# shape2 = None
# else:
# shape2 = shapely.geometry.asShape(shape1)
#
# print(shape1)
# if shape2:
# assert shape0.equals(shape2)
# print(shape0.equals(shape2))
# else:
# print("Skip")
#
# print("")
81 changes: 43 additions & 38 deletions libpysal/io/util/wkt.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from ... import cg
# ruff: noqa: N802, N815

import re

from ... import cg

__author__ = "Charles R Schmidt <schmidtc@gmail.com>"
__all__ = ["WKTParser"]

Expand All @@ -13,7 +16,6 @@ class WKTParser:
Examples
--------
>>> import libpysal
Create some Well-Known Text objects.
Expand All @@ -31,10 +33,10 @@ class WKTParser:
>>> parser(p).parts
[[(1.0, 1.0), (1.0, 5.0), (5.0, 5.0), (5.0, 1.0), (1.0, 1.0)],
[(2.0, 2.0), (2.0, 3.0), (3.0, 3.0), (3.0, 2.0), (2.0, 2.0)]]
>>> parser(p).centroid
(2.9705882352941178, 2.9705882352941178)
>>> parser(p).area
17.0
Expand All @@ -47,7 +49,7 @@ class WKTParser:
>>> parser(l).len
73.45538453219989
>>> parser(l).parts
[[(3.0, 4.0), (10.0, 50.0), (20.0, 25.0)]]
Expand All @@ -56,12 +58,11 @@ class WKTParser:
>>> f = libpysal.io.open(libpysal.examples.get_path('stl_hom.wkt'))
>>> f.mode
'r'
>>> f.header
[]
See local doctest output for the items not tested.
"""

regExes = {
Expand All @@ -78,20 +79,20 @@ def __init__(self):
p["linestring"] = self.LineString
p["polygon"] = self.Polygon

def Point(self, geoStr):
def Point(self, geo_str):
"""Returns a ``libpysal.cg.Point`` object."""
coords = self.regExes["spaces"].split(geoStr.strip())
coords = self.regExes["spaces"].split(geo_str.strip())
return cg.Point((coords[0], coords[1]))

def LineString(self, geoStr):
def LineString(self, geo_str):
"""Returns a ``libpysal.cg.Chain`` object."""
points = geoStr.strip().split(",")
points = geo_str.strip().split(",")
points = list(map(self.Point, points))
return cg.Chain(points)

def Polygon(self, geoStr):
def Polygon(self, geo_str):
"""Returns a ``libpysal.cg.Polygon`` object."""
rings = self.regExes["parenComma"].split(geoStr.strip())
rings = self.regExes["parenComma"].split(geo_str.strip())
for i, ring in enumerate(rings):
ring = self.regExes["trimParens"].match(ring).groups()[0]
ring = self.LineString(ring).vertices
Expand All @@ -100,44 +101,48 @@ def Polygon(self, geoStr):

def fromWKT(self, wkt):
"""Returns geometric representation from WKT or ``None``.
Raises
------
NotImplementedError
Raised when a unknown/unsupported format is passed in.
"""

matches = self.regExes["typeStr"].match(wkt)
if matches:
geoType, geoStr = matches.groups()
geoType = geoType.lower().strip()
geo_type, geo_str = matches.groups()
geo_type = geo_type.lower().strip()
try:
return self.parsers[geoType](geoStr)
return self.parsers[geo_type](geo_str)
except KeyError:
raise NotImplementedError("Unsupported WKT Type: %s." % geoType)
raise NotImplementedError(
f"Unsupported WKT Type: {geo_type}."
) from None
else:
return None

__call__ = fromWKT


if __name__ == "__main__":

p = "POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))"
pt = "POINT(6 10)"
l = "LINESTRING(3 4,10 50,20 25)"
wktExamples = [
"POINT(6 10)",
"LINESTRING(3 4,10 50,20 25)",
"POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))",
"MULTIPOINT(3.5 5.6,4.8 10.5)",
"MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))",
"MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2)),((3 3,6 2,6 4,3 3)))",
"GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))",
"POINT ZM (1 1 5 60)",
"POINT M (1 1 80)",
"POINT EMPTY",
"MULTIPOLYGON EMPTY",
]
wkt = WKTParser()
# if __name__ == "__main__":
#
# p = "POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))"
# pt = "POINT(6 10)"
# l_ = "LINESTRING(3 4,10 50,20 25)"
# wktExamples = [
# "POINT(6 10)",
# "LINESTRING(3 4,10 50,20 25)",
# "POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))",
# "MULTIPOINT(3.5 5.6,4.8 10.5)",
# "MULTILINESTRING((3 4,10 50,20 25),(-5 -8,-10 -8,-15 -4))",
# (
# "MULTIPOLYGON(((1 1,5 1,5 5,1 5,1 1),"
# "(2 2, 3 2, 3 3, 2 3,2 2)),((3 3,6 2,6 4,3 3)))"
# ),
# "GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))",
# "POINT ZM (1 1 5 60)",
# "POINT M (1 1 80)",
# "POINT EMPTY",
# "MULTIPOLYGON EMPTY",
# ]
# wkt = WKTParser()

0 comments on commit 023699a

Please sign in to comment.