
Current todo: - replace functions in zset.{c,h} with a new unified Redis zset access API. Once we get the zset interface fixed, we can squash relevant commits in this branch and have one nice commit to merge into unstable. This commit adds: - Geo commands - Tests; runnable with: ./runtest --single unit/geo - Geo helpers in deps/geohash-int/ - src/geo.{c,h} and src/geojson.{c,h} implementing geo commands - Updated build configurations to get everything working - TEMPORARY: src/zset.{c,h} implementing zset score and zset range reading without writing to client output buffers. - Modified linkage of one t_zset.c function for use in zset.c Conflicts: src/Makefile src/redis.c
266 lines
9.1 KiB
C
266 lines
9.1 KiB
C
/*
|
|
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Redis nor the names of its contributors may be used
|
|
* to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "geojson.h"
|
|
|
|
#define L server.lua
|
|
|
|
/* ====================================================================
|
|
* The Encoder
|
|
* ==================================================================== */
|
|
static sds jsonEncode() {
|
|
/* When entering this function, stack is: [1:[geojson table to encode]] */
|
|
lua_getglobal(L, "cjson");
|
|
lua_getfield(L, -1, "encode");
|
|
|
|
/* Stack is now: [1:[geojson table], 2:'cjson', 3:'encode'] */
|
|
|
|
/* Move current top ('encode') to bottom of stack */
|
|
lua_insert(L, 1);
|
|
|
|
/* Move current top ('cjson') to bottom of stack so we can 'cjson.encode' */
|
|
lua_insert(L, 1);
|
|
|
|
/* Stack is now: [1:'cjson', 2:'encode', 3:[table of geojson to encode]] */
|
|
|
|
/* Call cjson.encode on the element above it on the stack;
|
|
* obtain one return value */
|
|
if (lua_pcall(L, 1, 1, 0) != 0)
|
|
redisLog(REDIS_WARNING, "Could not encode geojson: %s",
|
|
lua_tostring(L, -1));
|
|
|
|
sds geojson = sdsnew(lua_tostring(L, -1));
|
|
|
|
/* We're done. Remove entire stack. Drop mic. Walk away. */
|
|
lua_pop(L, lua_gettop(L));
|
|
|
|
/* Return sds the caller must sdsfree() on their own */
|
|
return geojson;
|
|
}
|
|
|
|
/* ====================================================================
|
|
* The Lua Helpers
|
|
* ==================================================================== */
|
|
static inline void luaCreateFieldFromPrevious(const char *field) {
|
|
lua_setfield(L, -2, field);
|
|
}
|
|
|
|
static inline void luaCreateFieldStr(const char *field, const char *value) {
|
|
lua_pushstring(L, value);
|
|
luaCreateFieldFromPrevious(field);
|
|
}
|
|
|
|
/* Creates [Lat, Long] array attached to "coordinates" key */
|
|
static void luaCreateCoordinates(const double x, const double y) {
|
|
/* Create array table with two elements */
|
|
lua_createtable(L, 2, 0);
|
|
|
|
lua_pushnumber(L, x);
|
|
lua_rawseti(L, -2, 1);
|
|
lua_pushnumber(L, y);
|
|
lua_rawseti(L, -2, 2);
|
|
}
|
|
|
|
static void luaCreatePropertyNull(void) {
|
|
/* Create empty table and give it a name. This is a json {} value. */
|
|
lua_createtable(L, 0, 0);
|
|
luaCreateFieldFromPrevious("properties");
|
|
}
|
|
|
|
static void _luaCreateProperties(const char *k1, const char *v1, const char *k2,
|
|
const char *v2, const int noclose) {
|
|
/* we may add additional properties outside of here, so newtable instead of
|
|
* fixed-size createtable */
|
|
lua_newtable(L);
|
|
|
|
luaCreateFieldStr(k1, v1);
|
|
luaCreateFieldStr(k2, v2);
|
|
|
|
if (!noclose)
|
|
luaCreateFieldFromPrevious("properties");
|
|
}
|
|
|
|
static void luaCreateProperties(const char *k1, const char *v1, const char *k2,
|
|
const char *v2) {
|
|
_luaCreateProperties(k1, v1, k2, v2, 0);
|
|
}
|
|
|
|
/* ====================================================================
|
|
* The Lua Aggregation Helpers
|
|
* ==================================================================== */
|
|
static void attachProperties(const char *set, const char *member) {
|
|
if (member)
|
|
luaCreateProperties("set", set, "member", member);
|
|
else
|
|
luaCreatePropertyNull();
|
|
}
|
|
|
|
static void attachPropertiesWithDist(const char *set, const char *member,
|
|
double dist, const char *units) {
|
|
if (member) {
|
|
_luaCreateProperties("set", set, "member", member, 1);
|
|
if (units) {
|
|
/* Add units then distance. After encoding it comes
|
|
* out as distance followed by units in the json. */
|
|
lua_pushstring(L, units);
|
|
luaCreateFieldFromPrevious("units");
|
|
lua_pushnumber(L, dist);
|
|
luaCreateFieldFromPrevious("distance");
|
|
}
|
|
|
|
/* We requested to leave the properties table open, but now we
|
|
* are done and can close it. */
|
|
luaCreateFieldFromPrevious("properties");
|
|
} else {
|
|
luaCreatePropertyNull();
|
|
}
|
|
}
|
|
|
|
static void createGeometryPoint(const double x, const double y) {
|
|
lua_createtable(L, 0, 2);
|
|
|
|
/* coordinates = [x, y] */
|
|
luaCreateCoordinates(x, y);
|
|
luaCreateFieldFromPrevious("coordinates");
|
|
|
|
/* type = Point */
|
|
luaCreateFieldStr("type", "Point");
|
|
|
|
/* geometry = (coordinates = [x, y]) */
|
|
luaCreateFieldFromPrevious("geometry");
|
|
}
|
|
|
|
static void createGeometryBox(const double x1, const double y1, const double x2,
|
|
const double y2) {
|
|
lua_createtable(L, 0, 2);
|
|
|
|
/* Result = [[[x1,y1],[x2,y1],[x2,y2],[x1,y2], [x1,y1]] */
|
|
/* The end coord is the start coord to make a closed polygon */
|
|
lua_createtable(L, 1, 0);
|
|
lua_createtable(L, 5, 0);
|
|
|
|
/* Bottom left */
|
|
luaCreateCoordinates(x1, y1);
|
|
lua_rawseti(L, -2, 1);
|
|
|
|
/* Top Left */
|
|
luaCreateCoordinates(x2, y1);
|
|
lua_rawseti(L, -2, 2);
|
|
|
|
/* Top Right */
|
|
luaCreateCoordinates(x2, y2);
|
|
lua_rawseti(L, -2, 3);
|
|
|
|
/* Bottom Right */
|
|
luaCreateCoordinates(x1, y2);
|
|
lua_rawseti(L, -2, 4);
|
|
|
|
/* Bottom Left (Again) */
|
|
luaCreateCoordinates(x1, y1);
|
|
lua_rawseti(L, -2, 5);
|
|
|
|
/* Set the outer array of our inner array of the inner coords */
|
|
lua_rawseti(L, -2, 1);
|
|
|
|
/* Bundle those together in coordinates: [a, b, c, d] */
|
|
luaCreateFieldFromPrevious("coordinates");
|
|
|
|
/* Add type field */
|
|
luaCreateFieldStr("type", "Polygon");
|
|
|
|
luaCreateFieldFromPrevious("geometry");
|
|
}
|
|
|
|
static void createFeature() {
|
|
/* Features have three fields: type, geometry, and properties */
|
|
lua_createtable(L, 0, 3);
|
|
|
|
luaCreateFieldStr("type", "Feature");
|
|
|
|
/* You must call attachProperties on your own */
|
|
}
|
|
|
|
static void createCollection(size_t size) {
|
|
/* FeatureCollections have two fields: type and features */
|
|
lua_createtable(L, 0, 2);
|
|
|
|
luaCreateFieldStr("type", "FeatureCollection");
|
|
}
|
|
|
|
static void pointsToCollection(const struct geojsonPoint *pts, const size_t len,
|
|
const char *units) {
|
|
createCollection(len);
|
|
|
|
lua_createtable(L, len, 0);
|
|
for (int i = 0; i < len; i++) {
|
|
createFeature();
|
|
createGeometryPoint(pts[i].longitude, pts[i].latitude); /* x, y */
|
|
attachPropertiesWithDist(pts[i].set, pts[i].member, pts[i].dist, units);
|
|
lua_rawseti(L, -2, i + 1); /* Attach this Feature to "features" array */
|
|
}
|
|
luaCreateFieldFromPrevious("features");
|
|
}
|
|
|
|
static void latLongToPointFeature(const double latitude,
|
|
const double longitude) {
|
|
createFeature();
|
|
createGeometryPoint(longitude, latitude); /* geojson is: x,y */
|
|
}
|
|
|
|
static void squareToPolygonFeature(const double x1, const double y1,
|
|
const double x2, const double y2) {
|
|
createFeature();
|
|
createGeometryBox(x1, y1, x2, y2);
|
|
}
|
|
|
|
/* ====================================================================
|
|
* The Interface Functions
|
|
* ==================================================================== */
|
|
sds geojsonFeatureCollection(const struct geojsonPoint *pts, const size_t len,
|
|
const char *units) {
|
|
pointsToCollection(pts, len, units);
|
|
return jsonEncode();
|
|
}
|
|
|
|
sds geojsonLatLongToPointFeature(const double latitude, const double longitude,
|
|
const char *set, const char *member,
|
|
const double dist, const char *units) {
|
|
latLongToPointFeature(latitude, longitude);
|
|
attachPropertiesWithDist(set, member, dist, units);
|
|
return jsonEncode();
|
|
}
|
|
|
|
sds geojsonBoxToPolygonFeature(const double y1, const double x1,
|
|
const double y2, const double x2,
|
|
const char *set, const char *member) {
|
|
squareToPolygonFeature(x1, y1, x2, y2);
|
|
attachProperties(set, member);
|
|
return jsonEncode();
|
|
}
|