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

Add spatial data types #64

Open
wants to merge 11 commits 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ pymgclient.egg-info/
.venv
*.pyc
*.pyo
__pycache__/
__pycache__/
.pytest_cache/
44 changes: 44 additions & 0 deletions src/glue.c
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,20 @@ PyObject *mg_duration_to_py_delta(const mg_duration *dur) {
return make_py_delta(days, seconds, (nanoseconds / 1000));
}

PyObject *mg_point_2d_to_py_point2d(const mg_point_2d *point2d) {
PyObject *ret = PyObject_CallFunction(
(PyObject *)&Point2DType, "Hdd", mg_point_2d_srid(point2d),
mg_point_2d_x(point2d), mg_point_2d_y(point2d));
return ret;
}

PyObject *mg_point_3d_to_py_point3d(const mg_point_3d *point3d) {
PyObject *ret = PyObject_CallFunction(
(PyObject *)&Point3DType, "Hddd", mg_point_3d_srid(point3d),
mg_point_3d_x(point3d), mg_point_3d_y(point3d), mg_point_3d_z(point3d));
return ret;
}

PyObject *mg_value_to_py_object(const mg_value *value) {
switch (mg_value_get_type(value)) {
case MG_VALUE_TYPE_NULL:
Expand Down Expand Up @@ -379,6 +393,10 @@ PyObject *mg_value_to_py_object(const mg_value *value) {
return mg_local_date_time_to_py_datetime(mg_value_local_date_time(value));
case MG_VALUE_TYPE_DURATION:
return mg_duration_to_py_delta(mg_value_duration(value));
case MG_VALUE_TYPE_POINT_2D:
return mg_point_2d_to_py_point2d(mg_value_point_2d(value));
case MG_VALUE_TYPE_POINT_3D:
return mg_point_3d_to_py_point3d(mg_value_point_3d(value));
default:
PyErr_SetString(PyExc_RuntimeError,
"encountered a mg_value of unknown type");
Expand Down Expand Up @@ -587,6 +605,20 @@ mg_duration *py_delta_to_mg_duration(PyObject *obj) {
return mg_duration_make(0, days, seconds, microseconds * 1000);
}

mg_point_2d *py_point2d_to_mg_point_2d(PyObject *point_object) {
assert(Py_TYPE(point_object) == &Point2DType);
Point2DObject *py_point2d = (Point2DObject *)point_object;
return mg_point_2d_make(py_point2d->srid, py_point2d->x_longitude,
py_point2d->y_latitude);
}

mg_point_3d *py_point3d_to_mg_point_3d(PyObject *point_object) {
assert(Py_TYPE(point_object) == &Point3DType);
Point3DObject *py_point3d = (Point3DObject *)point_object;
return mg_point_3d_make(py_point3d->srid, py_point3d->x_longitude,
py_point3d->y_latitude, py_point3d->z_height);
}

mg_value *py_object_to_mg_value(PyObject *object) {
mg_value *ret = NULL;

Expand Down Expand Up @@ -648,6 +680,18 @@ mg_value *py_object_to_mg_value(PyObject *object) {
return NULL;
}
ret = mg_value_make_duration(dur);
} else if (Py_TYPE(object) == &Point2DType) {
mg_point_2d *point = py_point2d_to_mg_point_2d(object);
if (!point) {
return NULL;
}
ret = mg_value_make_point_2d(point);
} else if (Py_TYPE(object) == &Point3DType) {
mg_point_3d *point = py_point3d_to_mg_point_3d(object);
if (!point) {
return NULL;
}
ret = mg_value_make_point_3d(point);
} else {
PyErr_Format(PyExc_ValueError,
"value of type '%s' can't be used as query parameter",
Expand Down
2 changes: 2 additions & 0 deletions src/mgclientmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ static struct {
{"Node", &NodeType},
{"Relationship", &RelationshipType},
{"Path", &PathType},
{"Point2D", &Point2DType},
{"Point3D", &Point3DType},
{NULL, NULL}};

static int add_module_types(PyObject *module) {
Expand Down
278 changes: 277 additions & 1 deletion src/types.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,282 @@ PyTypeObject PathType = {
.tp_init = (initproc)path_init,
.tp_new = PyType_GenericNew
};
// clang-format on

#undef CHECK_ATTRIBUTE
static void point2d_dealloc(Point2DObject *point2d) {
Py_TYPE(point2d)->tp_free(point2d);
}

static PyObject *point2d_repr(Point2DObject *point2d) {
char buffer[256];
snprintf(buffer, sizeof(buffer),
"<%s(srid=%u, x_longitude=%f, y_latitude=%f) at %p>",
Py_TYPE(point2d)->tp_name, point2d->srid, point2d->x_longitude,
point2d->y_latitude, point2d);
return PyUnicode_FromFormat("%s", buffer);
}

static PyObject *point2d_str(Point2DObject *point2d) {
// NOTE: Somehow, PyUnicode_FromFormat doesn't suppord formatting double
// values.
// https://stackoverflow.com/questions/1701055/what-is-the-maximum-length-in-chars-needed-to-represent-any-double-value
char buffer[256];
snprintf(buffer, sizeof(buffer),
"Point2D({ srid=%u, x_longitude=%f, y_latitude=%f })", point2d->srid,
point2d->x_longitude, point2d->y_latitude);
return PyUnicode_FromFormat("%s", buffer);
}

// Helper function for implementing richcompare.
static PyObject *point2d_astuple(Point2DObject *point2d) {
PyObject *tuple = NULL;
PyObject *srid = NULL;
PyObject *x_longitude = NULL;
PyObject *y_latitude = NULL;

if (!(srid = PyLong_FromUnsignedLong(point2d->srid))) {
goto cleanup;
}
if (!(x_longitude = PyFloat_FromDouble(point2d->x_longitude))) {
goto cleanup;
}
if (!(y_latitude = PyFloat_FromDouble(point2d->y_latitude))) {
goto cleanup;
}
if (!(tuple = PyTuple_New(3))) {
goto cleanup;
}

PyTuple_SET_ITEM(tuple, 0, srid);
PyTuple_SET_ITEM(tuple, 1, x_longitude);
PyTuple_SET_ITEM(tuple, 2, y_latitude);
return tuple;

cleanup:
Py_XDECREF(tuple);
Py_XDECREF(srid);
Py_XDECREF(x_longitude);
Py_XDECREF(y_latitude);
return NULL;
}

static PyObject *point2d_richcompare(Point2DObject *lhs, PyObject *rhs,
int op) {
PyObject *tlhs = NULL;
PyObject *trhs = NULL;
PyObject *ret = NULL;

if (Py_TYPE(rhs) == &Point2DType) {
if (!(tlhs = point2d_astuple(lhs))) {
goto exit;
}
if (!(trhs = point2d_astuple((Point2DObject *)rhs))) {
goto exit;
}
ret = PyObject_RichCompare(tlhs, trhs, op);
} else {
Py_INCREF(Py_False);
ret = Py_False;
}

exit:
Py_XDECREF(tlhs);
Py_XDECREF(trhs);
return ret;
}

int point2d_init(Point2DObject *point2d, PyObject *args, PyObject *kwargs) {
uint16_t srid = 0;
double x_longitude = 0;
double y_latitude = 0;
static char *kwlist[] = {"", "", "", NULL};
// https://docs.python.org/3/c-api/arg.html#numbers
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Hdd", kwlist, &srid,
&x_longitude, &y_latitude)) {
return -1;
}

point2d->srid = srid;
point2d->x_longitude = x_longitude;
point2d->y_latitude = y_latitude;
return 0;
}

PyDoc_STRVAR(Point2DType_srid_doc,
"Point2D srid (a unique identifier associated with a specific "
"coordinate system, tolerance, and resolution).");
PyDoc_STRVAR(Point2DType_x_longitude_doc, "Point2D x or longitude value.");
PyDoc_STRVAR(Point2DType_y_latitude_doc, "Point2D y or latitude value.");
static PyMemberDef point2d_members[] = {
{"srid", T_USHORT, offsetof(Point2DObject, srid), READONLY,
Point2DType_srid_doc},
{"x_longitude", T_DOUBLE, offsetof(Point2DObject, x_longitude), READONLY,
Point2DType_x_longitude_doc},
{"y_latitude", T_DOUBLE, offsetof(Point2DObject, y_latitude), READONLY,
Point2DType_y_latitude_doc},
{NULL}};

PyDoc_STRVAR(Point2DType_doc, "A Point2D object.");
// clang-format off
PyTypeObject Point2DType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "mgclient.Point2D",
.tp_basicsize = sizeof(Point2DObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)point2d_dealloc,
.tp_repr = (reprfunc)point2d_repr,
.tp_str = (reprfunc)point2d_str,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = Point2DType_doc,
.tp_richcompare = (richcmpfunc)point2d_richcompare,
.tp_members = point2d_members,
.tp_init = (initproc)point2d_init,
.tp_new = PyType_GenericNew
};
// clang-format on

static void point3d_dealloc(Point3DObject *point3d) {
Py_TYPE(point3d)->tp_free(point3d);
}

static PyObject *point3d_repr(Point3DObject *point3d) {
char buffer[256];
snprintf(buffer, sizeof(buffer),
"<%s(srid=%u, x_longitude=%f, y_latitude=%f, z_height=%f) at %p>",
Py_TYPE(point3d)->tp_name, point3d->srid, point3d->x_longitude,
point3d->y_latitude, point3d->z_height, point3d);
return PyUnicode_FromFormat("%s", buffer);
}

static PyObject *point3d_str(Point3DObject *point3d) {
// NOTE: Somehow, PyUnicode_FromFormat doesn't suppord formatting double
// values.
// https://stackoverflow.com/questions/1701055/what-is-the-maximum-length-in-chars-needed-to-represent-any-double-value
char buffer[256];
snprintf(buffer, sizeof(buffer),
"Point3D({ srid=%u, x_longitude=%f, y_latitude=%f, z_height=%f })",
point3d->srid, point3d->x_longitude, point3d->y_latitude,
point3d->z_height);
return PyUnicode_FromFormat("%s", buffer);
}

// Helper function for implementing richcompare.
static PyObject *point3d_astuple(Point3DObject *point3d) {
PyObject *tuple = NULL;
PyObject *srid = NULL;
PyObject *x_longitude = NULL;
PyObject *y_latitude = NULL;
PyObject *z_height = NULL;

if (!(srid = PyLong_FromUnsignedLong(point3d->srid))) {
goto cleanup;
}
if (!(x_longitude = PyFloat_FromDouble(point3d->x_longitude))) {
goto cleanup;
}
if (!(y_latitude = PyFloat_FromDouble(point3d->y_latitude))) {
goto cleanup;
}
if (!(z_height = PyFloat_FromDouble(point3d->z_height))) {
goto cleanup;
}
if (!(tuple = PyTuple_New(4))) {
goto cleanup;
}

PyTuple_SET_ITEM(tuple, 0, srid);
PyTuple_SET_ITEM(tuple, 1, x_longitude);
PyTuple_SET_ITEM(tuple, 2, y_latitude);
PyTuple_SET_ITEM(tuple, 3, z_height);
return tuple;

cleanup:
Py_XDECREF(tuple);
Py_XDECREF(srid);
Py_XDECREF(x_longitude);
Py_XDECREF(y_latitude);
Py_XDECREF(z_height);
return NULL;
}

static PyObject *point3d_richcompare(Point3DObject *lhs, PyObject *rhs,
int op) {
PyObject *tlhs = NULL;
PyObject *trhs = NULL;
PyObject *ret = NULL;

if (Py_TYPE(rhs) == &Point3DType) {
if (!(tlhs = point3d_astuple(lhs))) {
goto exit;
}
if (!(trhs = point3d_astuple((Point3DObject *)rhs))) {
goto exit;
}
ret = PyObject_RichCompare(tlhs, trhs, op);
} else {
Py_INCREF(Py_False);
ret = Py_False;
}

exit:
Py_XDECREF(tlhs);
Py_XDECREF(trhs);
return ret;
}

int point3d_init(Point3DObject *point3d, PyObject *args, PyObject *kwargs) {
uint16_t srid = 0;
double x_longitude = 0;
double y_latitude = 0;
double z_height = 0;
static char *kwlist[] = {"", "", "", "", NULL};
// https://docs.python.org/3/c-api/arg.html#numbers
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Hddd", kwlist, &srid,
&x_longitude, &y_latitude, &z_height)) {
return -1;
}

point3d->srid = srid;
point3d->x_longitude = x_longitude;
point3d->y_latitude = y_latitude;
point3d->z_height = z_height;
return 0;
}

PyDoc_STRVAR(Point3DType_srid_doc,
"Point3D srid (a unique identifier associated with a specific "
"coordinate system, tolerance, and resolution).");
PyDoc_STRVAR(Point3DType_x_longitude_doc, "Point3D x or longitude value.");
PyDoc_STRVAR(Point3DType_y_latitude_doc, "Point3D y or latitude value.");
PyDoc_STRVAR(Point3DType_z_height_doc, "Point3D z or height value.");
static PyMemberDef point3d_members[] = {
{"srid", T_USHORT, offsetof(Point3DObject, srid), READONLY,
Point3DType_srid_doc},
{"x_longitude", T_DOUBLE, offsetof(Point3DObject, x_longitude), READONLY,
Point3DType_x_longitude_doc},
{"y_latitude", T_DOUBLE, offsetof(Point3DObject, y_latitude), READONLY,
Point3DType_y_latitude_doc},
{"z_height", T_DOUBLE, offsetof(Point3DObject, z_height), READONLY,
Point3DType_z_height_doc},
{NULL}};

PyDoc_STRVAR(Point3DType_doc, "A Point3D object.");
// clang-format off
PyTypeObject Point3DType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "mgclient.Point3D",
.tp_basicsize = sizeof(Point3DObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)point3d_dealloc,
.tp_repr = (reprfunc)point3d_repr,
.tp_str = (reprfunc)point3d_str,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = Point3DType_doc,
.tp_richcompare = (richcmpfunc)point3d_richcompare,
.tp_members = point3d_members,
.tp_init = (initproc)point3d_init,
.tp_new = PyType_GenericNew
};
// clang-format on

#undef CHECK_ATTRIBUTE
Loading