diff options
author | antirez <antirez@gmail.com> | 2015-06-29 12:44:31 +0200 |
---|---|---|
committer | antirez <antirez@gmail.com> | 2015-06-29 12:44:34 +0200 |
commit | f108c687ad122d76e8468f98934255ffb51cc7e8 (patch) | |
tree | 2160d798dc707eded4409fb2818fbb540e6e71c2 | |
parent | a12192f5ff33298eb7082cc3f6e2de17957e7d26 (diff) | |
download | redis-f108c687ad122d76e8468f98934255ffb51cc7e8.tar.gz |
Geo: GEODIST and tests.
-rw-r--r-- | deps/geohash-int/geohash_helper.c | 4 | ||||
-rw-r--r-- | deps/geohash-int/geohash_helper.h | 2 | ||||
-rw-r--r-- | src/geo.c | 84 | ||||
-rw-r--r-- | src/redis.c | 1 | ||||
-rw-r--r-- | src/redis.h | 1 | ||||
-rw-r--r-- | tests/unit/geo.tcl | 22 |
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); @@ -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]} { |