summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantirez <antirez@gmail.com>2015-06-29 12:44:31 +0200
committerantirez <antirez@gmail.com>2015-06-29 12:44:34 +0200
commitf108c687ad122d76e8468f98934255ffb51cc7e8 (patch)
tree2160d798dc707eded4409fb2818fbb540e6e71c2
parenta12192f5ff33298eb7082cc3f6e2de17957e7d26 (diff)
downloadredis-f108c687ad122d76e8468f98934255ffb51cc7e8.tar.gz
Geo: GEODIST and tests.
-rw-r--r--deps/geohash-int/geohash_helper.c4
-rw-r--r--deps/geohash-int/geohash_helper.h2
-rw-r--r--src/geo.c84
-rw-r--r--src/redis.c1
-rw-r--r--src/redis.h1
-rw-r--r--tests/unit/geo.tcl22
6 files changed, 94 insertions, 20 deletions
diff --git a/deps/geohash-int/geohash_helper.c b/deps/geohash-int/geohash_helper.c
index 729f010ea..88a972b47 100644
--- a/deps/geohash-int/geohash_helper.c
+++ b/deps/geohash-int/geohash_helper.c
@@ -167,7 +167,7 @@ GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash) {
}
/* Calculate distance using haversin great circle distance formula. */
-double distanceEarth(double lon1d, double lat1d, double lon2d, double lat2d) {
+double geohashGetDistance(double lon1d, double lat1d, double lon2d, double lat2d) {
double lat1r, lon1r, lat2r, lon2r, u, v;
lat1r = deg_rad(lat1d);
lon1r = deg_rad(lon1d);
@@ -182,7 +182,7 @@ double distanceEarth(double lon1d, double lat1d, double lon2d, double lat2d) {
int geohashGetDistanceIfInRadius(double x1, double y1,
double x2, double y2, double radius,
double *distance) {
- *distance = distanceEarth(x1, y1, x2, y2);
+ *distance = geohashGetDistance(x1, y1, x2, y2);
if (*distance > radius) return 0;
return 1;
}
diff --git a/deps/geohash-int/geohash_helper.h b/deps/geohash-int/geohash_helper.h
index 0e38740de..70c6b2095 100644
--- a/deps/geohash-int/geohash_helper.h
+++ b/deps/geohash-int/geohash_helper.h
@@ -58,6 +58,8 @@ GeoHashRadius geohashGetAreasByRadiusWGS84(double longitude, double latitude,
GeoHashRadius geohashGetAreasByRadiusMercator(double longitude, double latitude,
double radius_meters);
GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash);
+double geohashGetDistance(double lon1d, double lat1d,
+ double lon2d, double lat2d);
int geohashGetDistanceIfInRadius(double x1, double y1,
double x2, double y2, double radius,
double *distance);
diff --git a/src/geo.c b/src/geo.c
index 2869550ad..e11f5b371 100644
--- a/src/geo.c
+++ b/src/geo.c
@@ -112,6 +112,30 @@ static int longLatFromMember(robj *zobj, robj *member, double *xy) {
return REDIS_OK;
}
+/* Check that the unit argument matches one of the known units, and returns
+ * the conversion factor to meters (you need to divide meters by the conversion
+ * factor to convert to the right unit).
+ *
+ * If the unit is not valid, an error is reported to the client, and a value
+ * less than zero is returned. */
+double extractUnitOrReply(redisClient *c, robj *unit) {
+ char *u = unit->ptr;
+
+ if (!strcmp(u, "m") || !strncmp(u, "meter", 5)) {
+ return 1;
+ } else if (!strcmp(u, "ft") || !strncmp(u, "feet", 4)) {
+ return 0.3048;
+ } else if (!strcmp(u, "mi") || !strncmp(u, "mile", 4)) {
+ return 1609.34;
+ } else if (!strcmp(u, "km") || !strncmp(u, "kilometer", 9)) {
+ return 1000;
+ } else {
+ addReplyError(c, "unsupported unit provided. please use meters (m), "
+ "kilometers (km), miles (mi), or feet (ft)");
+ return -1;
+ }
+}
+
/* Input Argument Helper.
* Extract the dinstance from the specified two arguments starting at 'argv'
* that shouldbe in the form: <number> <unit> and return the dinstance in the
@@ -127,25 +151,10 @@ static double extractDistanceOrReply(redisClient *c, robj **argv,
return -1;
}
- double to_meters;
- sds units = argv[1]->ptr;
- if (!strcmp(units, "m") || !strncmp(units, "meter", 5)) {
- to_meters = 1;
- } else if (!strcmp(units, "ft") || !strncmp(units, "feet", 4)) {
- to_meters = 0.3048;
- } else if (!strcmp(units, "mi") || !strncmp(units, "mile", 4)) {
- to_meters = 1609.34;
- } else if (!strcmp(units, "km") || !strncmp(units, "kilometer", 9)) {
- to_meters = 1000;
- } else {
- addReplyError(c, "unsupported unit provided. please use meters (m), "
- "kilometers (km), miles (mi), or feet (ft)");
- return -1;
- }
-
- if (conversion)
- *conversion = to_meters;
+ double to_meters = extractUnitOrReply(c,argv[1]);
+ if (to_meters < 0) return -1;
+ if (conversion) *conversion = to_meters;
return distance * to_meters;
}
@@ -742,3 +751,42 @@ void geoposCommand(redisClient *c) {
}
}
}
+
+/* GEODIST key ele1 ele2 [unit]
+ *
+ * Return the distance, in meters by default, otherwise accordig to "unit",
+ * between points ele1 and ele2. If one or more elements are missing NULL
+ * is returned. */
+void geodistCommand(redisClient *c) {
+ double to_meter = 1;
+
+ /* Check if there is the unit to extract, otherwise assume meters. */
+ if (c->argc == 5) {
+ to_meter = extractUnitOrReply(c,c->argv[4]);
+ if (to_meter < 0) return;
+ } else if (c->argc > 5) {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+
+ /* Look up the requested zset */
+ robj *zobj = NULL;
+ if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptybulk))
+ == NULL || checkType(c, zobj, REDIS_ZSET)) return;
+
+ /* Get the scores. We need both otherwise NULL is returned. */
+ double score1, score2, xyxy[4];
+ if (zsetScore(zobj, c->argv[2], &score1) == REDIS_ERR ||
+ zsetScore(zobj, c->argv[3], &score2) == REDIS_ERR)
+ {
+ addReply(c,shared.nullbulk);
+ return;
+ }
+
+ /* Decode & compute the distance. */
+ if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
+ addReply(c,shared.nullbulk);
+ else
+ addReplyDouble(c,
+ geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
+}
diff --git a/src/redis.c b/src/redis.c
index e6707d5f2..cb5c73771 100644
--- a/src/redis.c
+++ b/src/redis.c
@@ -289,6 +289,7 @@ struct redisCommand redisCommandTable[] = {
{"geodecode",geodecodeCommand,2,"r",0,NULL,0,0,0,0,0},
{"geohash",geohashCommand,-2,"r",0,NULL,0,0,0,0,0},
{"geopos",geoposCommand,-2,"r",0,NULL,0,0,0,0,0},
+ {"geodist",geodistCommand,-4,"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},
diff --git a/src/redis.h b/src/redis.h
index 70b301a59..b64a7697a 100644
--- a/src/redis.h
+++ b/src/redis.h
@@ -1565,6 +1565,7 @@ void georadiusCommand(redisClient *c);
void geoaddCommand(redisClient *c);
void geohashCommand(redisClient *c);
void geoposCommand(redisClient *c);
+void geodistCommand(redisClient *c);
void pfselftestCommand(redisClient *c);
void pfaddCommand(redisClient *c);
void pfcountCommand(redisClient *c);
diff --git a/tests/unit/geo.tcl b/tests/unit/geo.tcl
index 191f88c58..cf6d8c614 100644
--- a/tests/unit/geo.tcl
+++ b/tests/unit/geo.tcl
@@ -109,6 +109,28 @@ start_server {tags {"geo"}} {
lindex [r geopos points a x b] 1
} {}
+ test {GEODIST simple & unit} {
+ r del points
+ r geoadd points 13.361389 38.115556 "Palermo" \
+ 15.087269 37.502669 "Catania"
+ set m [r geodist points Palermo Catania]
+ assert {$m > 166274 && $m < 166275}
+ set km [r geodist points Palermo Catania km]
+ assert {$km > 166.2 && $km < 166.3}
+ }
+
+ test {GEODIST missing elements} {
+ r del points
+ r geoadd points 13.361389 38.115556 "Palermo" \
+ 15.087269 37.502669 "Catania"
+ set m [r geodist points Palermo Agrigento]
+ assert {$m eq {}}
+ set m [r geodist points Ragusa Agrigento]
+ assert {$m eq {}}
+ set m [r geodist empty_key Palermo Catania]
+ assert {$m eq {}}
+ }
+
test {GEOADD + GEORANGE randomized test} {
set attempt 10
while {[incr attempt -1]} {