Skip to content

Commit

Permalink
ENH: Implement hash, eq, neq (#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
caspervdw committed Mar 14, 2020
1 parent fb70038 commit da86af4
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 1 deletion.
1 change: 1 addition & 0 deletions pygeos/test/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
empty_point = pygeos.Geometry("POINT EMPTY")
empty_line_string = pygeos.Geometry("LINESTRING EMPTY")
empty = pygeos.Geometry("GEOMETRYCOLLECTION EMPTY")
point_nan = pygeos.points(np.nan, np.nan)

all_types = (
point,
Expand Down
49 changes: 49 additions & 0 deletions pygeos/test/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

from .common import point
from .common import point_nan
from .common import line_string
from .common import linear_ring
from .common import polygon
Expand Down Expand Up @@ -166,3 +167,51 @@ def test_adapt_ptr_raises():
point = pygeos.Geometry("POINT (2 2)")
with pytest.raises(AttributeError):
point._ptr += 1


@pytest.mark.parametrize("geom", all_types + (pygeos.points(np.nan, np.nan),))
def test_hash_same_equal(geom):
assert hash(geom) == hash(pygeos.apply(geom, lambda x: x))


@pytest.mark.parametrize("geom", all_types[:-1])
def test_hash_same_not_equal(geom):
assert hash(geom) != hash(pygeos.apply(geom, lambda x: x + 1))


@pytest.mark.parametrize("geom", all_types)
def test_eq(geom):
assert geom == pygeos.apply(geom, lambda x: x)


@pytest.mark.parametrize("geom", all_types[:-1])
def test_neq(geom):
assert geom != pygeos.apply(geom, lambda x: x + 1)


@pytest.mark.parametrize("geom", all_types)
def test_set_unique(geom):
a = {geom, pygeos.apply(geom, lambda x: x)}
assert len(a) == 1


def test_eq_nan():
assert point_nan != point_nan


def test_neq_nan():
assert not (point_nan == point_nan)


def test_set_nan():
# As NaN != NaN, you can have multiple "NaN" points in a set
# set([float("nan"), float("nan")]) also returns a set with 2 elements
a = set(pygeos.points([[np.nan, np.nan]] * 10))
assert len(a) == 10 # different objects: NaN != NaN


def test_set_nan_same_objects():
# You can't put identical objects in a set.
# x = float("nan"); set([x, x]) also retuns a set with 1 element
a = set([point_nan] * 10)
assert len(a) == 1
66 changes: 66 additions & 0 deletions src/pygeom.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,70 @@ static PyObject *GeometryObject_str(GeometryObject *self)
return GeometryObject_ToWKT(self, "%s");
}

static Py_hash_t GeometryObject_hash(GeometryObject *self)
{
void *context = geos_context[0];
unsigned char *wkb;
size_t size;
Py_hash_t x;

if (self->ptr == NULL) {
return -1;
}
GEOSWKBWriter *writer = GEOSWKBWriter_create_r(context);
if (writer == NULL) {
return -1;
}

GEOSWKBWriter_setOutputDimension_r(context, writer, 3);
GEOSWKBWriter_setIncludeSRID_r(context, writer, 1);
wkb = GEOSWKBWriter_write_r(context, writer, self->ptr, &size);
GEOSWKBWriter_destroy_r(context, writer);
if (wkb == NULL) {
return -1;
}
x = PyHash_GetFuncDef()->hash(wkb, size);
if (x == -1) {
x = -2;
} else {
x ^= 374761393UL; // to make the result distinct from the actual WKB hash //
}
GEOSFree_r(context, wkb);
return x;
}

static PyObject *GeometryObject_richcompare(GeometryObject *self, PyObject *other, int op) {
PyObject *result = NULL;
void *context = geos_context[0];
if (Py_TYPE(self)->tp_richcompare != Py_TYPE(other)->tp_richcompare) {
result = Py_NotImplemented;
} else {
GeometryObject *other_geom = (GeometryObject *) other;
switch (op) {
case Py_LT:
result = Py_NotImplemented;
break;
case Py_LE:
result = Py_NotImplemented;
break;
case Py_EQ:
result = GEOSEqualsExact_r(context, self->ptr, other_geom->ptr, 0) ? Py_True : Py_False;
break;
case Py_NE:
result = GEOSEqualsExact_r(context, self->ptr, other_geom->ptr, 0) ? Py_False : Py_True;
break;
case Py_GT:
result = Py_NotImplemented;
break;
case Py_GE:
result = Py_NotImplemented;
break;
}
}
Py_XINCREF(result);
return result;
}

static PyObject *GeometryObject_FromWKT(PyTypeObject *type, PyObject *value)
{
void *context_handle = geos_context[0];
Expand Down Expand Up @@ -143,6 +207,8 @@ PyTypeObject GeometryType = {
.tp_members = GeometryObject_members,
.tp_methods = GeometryObject_methods,
.tp_repr = (reprfunc) GeometryObject_repr,
.tp_hash = (hashfunc) GeometryObject_hash,
.tp_richcompare = (richcmpfunc) GeometryObject_richcompare,
.tp_str = (reprfunc) GeometryObject_str,
};

Expand Down
2 changes: 1 addition & 1 deletion src/ufuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1112,7 +1112,7 @@ static void from_wkt_func(char **args, npy_intp *dimensions,
GEOSWKTReader *reader;
const char *wkt;

/* Create the WKB reader */
/* Create the WKT reader */
reader = GEOSWKTReader_create_r(context_handle);
if (reader == NULL) {
return;
Expand Down

0 comments on commit da86af4

Please sign in to comment.