Commit b18c68aa authored by antirez's avatar antirez
Browse files

Geo: JSON features removed

The command can only return data in the normal Redis protocol. It is up
to the caller to translate to JSON if needed.
parent f193b3ca
......@@ -117,7 +117,7 @@ endif
REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o geo.o zset.o geojson.o
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o geo.o zset.o
REDIS_GEOHASH_OBJ=../deps/geohash-int/geohash.o ../deps/geohash-int/geohash_helper.o
REDIS_CLI_NAME=redis-cli
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
......
......@@ -29,7 +29,6 @@
#include "geo.h"
#include "geohash_helper.h"
#include "geojson.h"
#include "zset.h"
/* ====================================================================
......@@ -110,49 +109,15 @@ static double extractDistanceOrReply(redisClient *c, robj **argv,
return distance * to_meters;
}
/* Output Reply Helper */
static void latLongToGeojsonAndReply(redisClient *c, struct geojsonPoint *gp,
char *units) {
sds geojson = geojsonLatLongToPointFeature(
gp->latitude, gp->longitude, gp->set, gp->member, gp->dist, units);
addReplyBulkCBuffer(c, geojson, sdslen(geojson));
sdsfree(geojson);
}
/* Output Reply Helper */
static void decodeGeohashToGeojsonBoundsAndReply(redisClient *c,
uint64_t hashbits,
struct geojsonPoint *gp) {
GeoHashArea area = {{0,0},{0,0},{0,0}};
GeoHashBits hash = { .bits = hashbits, .step = GEO_STEP_MAX };
geohashDecodeWGS84(hash, &area);
sds geojson = geojsonBoxToPolygonFeature(
area.latitude.min, area.longitude.min, area.latitude.max,
area.longitude.max, gp->set, gp->member);
addReplyBulkCBuffer(c, geojson, sdslen(geojson));
sdsfree(geojson);
}
/* The defailt addReplyDouble has too much accuracy. We use this
* for returning location distances. "5.21 meters away" is nicer
* than "5.2144992818115 meters away." */
static inline void addReplyDoubleNicer(redisClient *c, double d) {
static inline void addReplyDoubleMeters(redisClient *c, double d) {
char dbuf[128] = { 0 };
int dlen = snprintf(dbuf, sizeof(dbuf), "%.2f", d);
addReplyBulkCBuffer(c, dbuf, dlen);
}
/* Output Reply Helper */
static void replyGeojsonCollection(redisClient *c, struct geojsonPoint *gp,
long result_length, char *units) {
sds geojson = geojsonFeatureCollection(gp, result_length, units);
addReplyBulkCBuffer(c, geojson, sdslen(geojson));
sdsfree(geojson);
}
/* geohash range+zset access helper */
/* Obtain all members between the min/max of this geohash bounding box. */
/* Returns list of results. List must be listRelease()'d later. */
......@@ -291,7 +256,7 @@ static int publishLocationUpdate(const sds zset, const sds member,
/* Sort comparators for qsort() */
static int sort_gp_asc(const void *a, const void *b) {
const struct geojsonPoint *gpa = a, *gpb = b;
const struct geoPoint *gpa = a, *gpb = b;
/* We can't do adist - bdist because they are doubles and
* the comparator returns an int. */
if (gpa->dist > gpb->dist)
......@@ -431,12 +396,8 @@ static void geoRadiusGeneric(redisClient *c, int type) {
return;
}
sds units = c->argv[base_args - 2 + 1]->ptr;
/* Discover and populate all optional parameters. */
int withdist = 0, withhash = 0, withcoords = 0,
withgeojson = 0, withgeojsonbounds = 0,
withgeojsoncollection = 0, noproperties = 0;
int withdist = 0, withhash = 0, withcoords = 0, noproperties = 0;
int sort = SORT_NONE;
if (c->argc > base_args) {
int remaining = c->argc - base_args;
......@@ -448,14 +409,6 @@ static void geoRadiusGeneric(redisClient *c, int type) {
withhash = 1;
else if (!strncasecmp(arg, "withcoord", 9))
withcoords = 1;
else if (!strncasecmp(arg, "withgeojsonbound", 16))
withgeojsonbounds = 1;
else if (!strncasecmp(arg, "withgeojsoncollection", 21))
withgeojsoncollection = 1;
else if (!strncasecmp(arg, "withgeo", 7) ||
!strcasecmp(arg, "geojson") || !strcasecmp(arg, "json") ||
!strcasecmp(arg, "withjson"))
withgeojson = 1;
else if (!strncasecmp(arg, "noprop", 6) ||
!strncasecmp(arg, "withoutprop", 11))
noproperties = 1;
......@@ -471,8 +424,6 @@ static void geoRadiusGeneric(redisClient *c, int type) {
}
}
int withgeo = withgeojsonbounds || withgeojsoncollection || withgeojson;
/* Get all neighbor geohash boxes for our radius search */
GeoHashRadius georadius =
geohashGetAreasByRadiusWGS84(latlong[0], latlong[1], radius_meters);
......@@ -508,23 +459,17 @@ static void geoRadiusGeneric(redisClient *c, int type) {
if (withhash)
option_length++;
if (withgeojson)
option_length++;
if (withgeojsonbounds)
option_length++;
/* The multibulk len we send is exactly result_length. The result is either
* all strings of just zset members *or* a nested multi-bulk reply
* containing the zset member string _and_ all the additional options the
* user enabled for this request. */
addReplyMultiBulkLen(c, result_length + withgeojsoncollection);
addReplyMultiBulkLen(c, result_length);
/* Iterate over results, populate struct used for sorting and result sending
*/
listIter li;
listRewind(found_matches, &li);
struct geojsonPoint gp[result_length];
struct geoPoint gp[result_length];
/* populate gp array from our results */
for (int i = 0; i < result_length; i++) {
struct zipresult *zr = listNodeValue(listNext(&li));
......@@ -534,7 +479,7 @@ static void geoRadiusGeneric(redisClient *c, int type) {
gp[i].dist = zr->distance / conversion;
gp[i].userdata = zr;
/* The layout of geojsonPoint allows us to pass the start offset
/* The layout of geoPoint allows us to pass the start offset
* of the struct directly to decodeGeohash. */
decodeGeohash(zr->score, (double *)(gp + i));
}
......@@ -558,18 +503,14 @@ static void geoRadiusGeneric(redisClient *c, int type) {
switch (zr->type) {
case ZR_LONG:
addReplyBulkLongLong(c, zr->val.v);
if (withgeo && !noproperties)
gp[i].member = sdscatprintf(sdsempty(), "%llu", zr->val.v);
break;
case ZR_STRING:
addReplyBulkCBuffer(c, zr->val.s, sdslen(zr->val.s));
if (withgeo && !noproperties)
gp[i].member = sdsdup(zr->val.s);
break;
}
if (withdist)
addReplyDoubleNicer(c, gp[i].dist);
addReplyDoubleMeters(c, gp[i].dist);
if (withhash)
addReplyLongLong(c, zr->score);
......@@ -579,21 +520,7 @@ static void geoRadiusGeneric(redisClient *c, int type) {
addReplyDouble(c, gp[i].latitude);
addReplyDouble(c, gp[i].longitude);
}
if (withgeojson)
latLongToGeojsonAndReply(c, gp + i, units);
if (withgeojsonbounds)
decodeGeohashToGeojsonBoundsAndReply(c, zr->score, gp + i);
}
if (withgeojsoncollection)
replyGeojsonCollection(c, gp, result_length, units);
if (withgeo && !noproperties)
for (int i = 0; i < result_length; i++)
sdsfree(gp[i].member);
listRelease(found_matches);
}
......@@ -610,18 +537,11 @@ void geoRadiusByMemberCommand(redisClient *c) {
}
void geoDecodeCommand(redisClient *c) {
/* args 0-1: ["geodecode", geohash];
* optional: [geojson] */
GeoHashBits geohash;
if (getLongLongFromObjectOrReply(c, c->argv[1], (long long *)&geohash.bits,
NULL) != REDIS_OK)
return;
int withgeojson = 0;
if (c->argc == 3)
withgeojson = 1;
GeoHashArea area;
geohash.step = GEO_STEP_MAX;
geohashDecodeWGS84(geohash, &area);
......@@ -630,7 +550,7 @@ void geoDecodeCommand(redisClient *c) {
double x = (area.longitude.min + area.longitude.max) / 2;
/* Returning three nested replies */
addReplyMultiBulkLen(c, 3 + withgeojson * 2);
addReplyMultiBulkLen(c, 3);
/* First, the minimum corner */
addReplyMultiBulkLen(c, 2);
......@@ -646,50 +566,23 @@ void geoDecodeCommand(redisClient *c) {
addReplyMultiBulkLen(c, 2);
addReplyDouble(c, y);
addReplyDouble(c, x);
if (withgeojson) {
struct geojsonPoint gp = { .latitude = y,
.longitude = x,
.member = NULL };
/* Return geojson Feature Point */
latLongToGeojsonAndReply(c, &gp, NULL);
/* Return geojson Feature Polygon */
decodeGeohashToGeojsonBoundsAndReply(c, geohash.bits, &gp);
}
}
void geoEncodeCommand(redisClient *c) {
/* args 0-2: ["geoencode", lat, long];
* optionals: [radius, units]
* - AND / OR -
* optional: [geojson] */
int withgeojson = 0;
for (int i = 3; i < c->argc; i++) {
char *arg = c->argv[i]->ptr;
if (!strncasecmp(arg, "withgeo", 7) || !strcasecmp(arg, "geojson") ||
!strcasecmp(arg, "json") || !strcasecmp(arg, "withjson")) {
withgeojson = 1;
break;
}
}
* optionals: [radius, units] */
double radius_meters = 0;
if (c->argc >= 5) {
if ((radius_meters = extractDistanceOrReply(c, c->argv + 3, NULL)) <
0) {
if ((radius_meters = extractDistanceOrReply(c, c->argv + 3, NULL)) < 0)
return;
}
} else if (c->argc == 4 && !withgeojson) {
} else if (c->argc == 4) {
addReplyError(c, "must provide units when asking for radius encode");
return;
}
double latlong[2];
if (!extractLatLongOrReply(c, c->argv + 1, latlong))
return;
if (!extractLatLongOrReply(c, c->argv + 1, latlong)) return;
/* Encode lat/long into our geohash */
GeoHashBits geohash;
......@@ -709,8 +602,8 @@ void geoEncodeCommand(redisClient *c) {
double y = (area.latitude.min + area.latitude.max) / 2;
double x = (area.longitude.min + area.longitude.max) / 2;
/* Return four nested multibulk replies with optional geojson returns */
addReplyMultiBulkLen(c, 4 + withgeojson * 2);
/* Return four nested multibulk replies. */
addReplyMultiBulkLen(c, 4);
/* Return the binary geohash we calculated as 52-bit integer */
addReplyLongLong(c, bits);
......@@ -729,22 +622,4 @@ void geoEncodeCommand(redisClient *c) {
addReplyMultiBulkLen(c, 2);
addReplyDouble(c, y);
addReplyDouble(c, x);
if (withgeojson) {
struct geojsonPoint gp = { .latitude = y,
.longitude = x,
.member = NULL };
/* Return geojson Feature Point */
latLongToGeojsonAndReply(c, &gp, NULL);
/* Return geojson Feature Polygon (bounding box for this step size) */
/* We don't use the helper function here because we can't re-calculate
* the area if we have a non-GEO_STEP_MAX step size. */
sds geojson = geojsonBoxToPolygonFeature(
area.latitude.min, area.longitude.min, area.latitude.max,
area.longitude.max, gp.set, gp.member);
addReplyBulkCBuffer(c, geojson, sdslen(geojson));
sdsfree(geojson);
}
}
......@@ -9,4 +9,13 @@ void geoRadiusByMemberCommand(redisClient *c);
void geoRadiusCommand(redisClient *c);
void geoAddCommand(redisClient *c);
struct geoPoint {
double latitude;
double longitude;
double dist;
char *set;
char *member;
void *userdata;
};
#endif
/*
* 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();
}
/*
* 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.
*/
#ifndef __GEOJSON_H__
#define __GEOJSON_H__
#include "redis.h"
#include "geohash_helper.h"
struct geojsonPoint {
double latitude;
double longitude;
double dist;
char *set;
char *member;
void *userdata;
};
sds geojsonLatLongToPointFeature(const double latitude, const double longitude,
const char *set, const char *member,
const double dist, const char *units);
sds geojsonBoxToPolygonFeature(const double x1, const double y1,
const double x2, const double y2,
const char *set, const char *member);
sds geojsonFeatureCollection(const struct geojsonPoint *pts, const size_t len,
const char *units);
#endif
......@@ -286,7 +286,7 @@ struct redisCommand redisCommandTable[] = {
{"georadius",geoRadiusCommand,-6,"r",0,NULL,1,1,1,0,0},
{"georadiusbymember",geoRadiusByMemberCommand,-5,"r",0,NULL,1,1,1,0,0},
{"geoencode",geoEncodeCommand,-3,"r",0,NULL,0,0,0,0,0},
{"geodecode",geoDecodeCommand,-2,"r",0,NULL,0,0,0,0,0},
{"geodecode",geoDecodeCommand,2,"r",0,NULL,0,0,0,0,0},
{"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
{"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0},
{"pfcount",pfcountCommand,-2,"r",0,NULL,1,1,1,0,0},
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment