Geo: from lat,lon API to lon,lat API according to GIS standard

The GIS standard and all the major DBs implementing GIS related
functions take coordinates as x,y that is longitude,latitude.
It was a bad start for Redis to do things differently, so even if this
means that existing users of the Geo module will be required to change
their code, Redis now conforms to the standard.

Usually Redis is very backward compatible, but this is not an exception
to this rule, since this is the first Geo implementation entering the
official Redis source code. It is not wise to try to be backward
compatible with code forks... :-)

Close #2637.
This commit is contained in:
antirez 2015-06-25 18:05:45 +02:00
parent 03ce189628
commit fa9d62d34f
7 changed files with 146 additions and 176 deletions

View File

@ -43,17 +43,17 @@
* ----------------- * -----------------
*/ */
void geohashGetCoordRange(GeoHashRange *lat_range, GeoHashRange *long_range) { void geohashGetCoordRange(GeoHashRange *long_range, GeoHashRange *lat_range) {
/* These are constraints from EPSG:900913 / EPSG:3785 / OSGEO:41001 */ /* These are constraints from EPSG:900913 / EPSG:3785 / OSGEO:41001 */
/* We can't geocode at the north/south pole. */ /* We can't geocode at the north/south pole. */
lat_range->max = 85.05112878;
lat_range->min = -85.05112878;
long_range->max = 180.0; long_range->max = 180.0;
long_range->min = -180.0; long_range->min = -180.0;
lat_range->max = 85.05112878;
lat_range->min = -85.05112878;
} }
int geohashEncode(GeoHashRange *lat_range, GeoHashRange *long_range, int geohashEncode(GeoHashRange *long_range, GeoHashRange *lat_range,
double latitude, double longitude, uint8_t step, double longitude, double latitude, uint8_t step,
GeoHashBits *hash) { GeoHashBits *hash) {
uint8_t i; uint8_t i;
@ -96,22 +96,22 @@ int geohashEncode(GeoHashRange *lat_range, GeoHashRange *long_range,
return 1; return 1;
} }
int geohashEncodeType(double latitude, double longitude, uint8_t step, GeoHashBits *hash) { int geohashEncodeType(double longitude, double latitude, uint8_t step, GeoHashBits *hash) {
GeoHashRange r[2] = { { 0 } }; GeoHashRange r[2] = { { 0 } };
geohashGetCoordRange(&r[0], &r[1]); geohashGetCoordRange(&r[0], &r[1]);
return geohashEncode(&r[0], &r[1], latitude, longitude, step, hash); return geohashEncode(&r[0], &r[1], longitude, latitude, step, hash);
} }
int geohashEncodeWGS84(double latitude, double longitude, uint8_t step, int geohashEncodeWGS84(double longitude, double latitude, uint8_t step,
GeoHashBits *hash) { GeoHashBits *hash) {
return geohashEncodeType(latitude, longitude, step, hash); return geohashEncodeType(longitude, latitude, step, hash);
} }
static inline uint8_t get_bit(uint64_t bits, uint8_t pos) { static inline uint8_t get_bit(uint64_t bits, uint8_t pos) {
return (bits >> pos) & 0x01; return (bits >> pos) & 0x01;
} }
int geohashDecode(const GeoHashRange lat_range, const GeoHashRange long_range, int geohashDecode(const GeoHashRange long_range, const GeoHashRange lat_range,
const GeoHashBits hash, GeoHashArea *area) { const GeoHashBits hash, GeoHashArea *area) {
uint8_t i; uint8_t i;
@ -121,10 +121,10 @@ int geohashDecode(const GeoHashRange lat_range, const GeoHashRange long_range,
} }
area->hash = hash; area->hash = hash;
area->latitude.min = lat_range.min;
area->latitude.max = lat_range.max;
area->longitude.min = long_range.min; area->longitude.min = long_range.min;
area->longitude.max = long_range.max; area->longitude.max = long_range.max;
area->latitude.min = lat_range.min;
area->latitude.max = lat_range.max;
for (i = 0; i < hash.step; i++) { for (i = 0; i < hash.step; i++) {
uint8_t lat_bit, long_bit; uint8_t lat_bit, long_bit;
@ -159,28 +159,22 @@ int geohashDecodeWGS84(const GeoHashBits hash, GeoHashArea *area) {
return geohashDecodeType(hash, area); return geohashDecodeType(hash, area);
} }
int geohashDecodeAreaToLatLong(const GeoHashArea *area, double *latlong) { int geohashDecodeAreaToLongLat(const GeoHashArea *area, double *xy) {
double y, x; if (!xy) return 0;
xy[0] = (area->longitude.min + area->longitude.max) / 2;
if (!latlong) return 0; xy[1] = (area->latitude.min + area->latitude.max) / 2;
y = (area->latitude.min + area->latitude.max) / 2;
x = (area->longitude.min + area->longitude.max) / 2;
latlong[0] = y;
latlong[1] = x;
return 1; return 1;
} }
int geohashDecodeToLatLongType(const GeoHashBits hash, double *latlong) { int geohashDecodeToLongLatType(const GeoHashBits hash, double *xy) {
GeoHashArea area = { { 0 } }; GeoHashArea area = { { 0 } };
if (!latlong || !geohashDecodeType(hash, &area)) if (!xy || !geohashDecodeType(hash, &area))
return 0; return 0;
return geohashDecodeAreaToLatLong(&area, latlong); return geohashDecodeAreaToLongLat(&area, xy);
} }
int geohashDecodeToLatLongWGS84(const GeoHashBits hash, double *latlong) { int geohashDecodeToLongLatWGS84(const GeoHashBits hash, double *xy) {
return geohashDecodeToLatLongType(hash, latlong); return geohashDecodeToLongLatType(hash, xy);
} }
static void geohash_move_x(GeoHashBits *hash, int8_t d) { static void geohash_move_x(GeoHashBits *hash, int8_t d) {

View File

@ -62,14 +62,14 @@ typedef struct {
} GeoHashBits; } GeoHashBits;
typedef struct { typedef struct {
double max;
double min; double min;
double max;
} GeoHashRange; } GeoHashRange;
typedef struct { typedef struct {
GeoHashBits hash; GeoHashBits hash;
GeoHashRange latitude;
GeoHashRange longitude; GeoHashRange longitude;
GeoHashRange latitude;
} GeoHashArea; } GeoHashArea;
typedef struct { typedef struct {
@ -87,22 +87,22 @@ typedef struct {
* 0:success * 0:success
* -1:failed * -1:failed
*/ */
void geohashGetCoordRange(GeoHashRange *lat_range, GeoHashRange *long_range); void geohashGetCoordRange(GeoHashRange *long_range, GeoHashRange *lat_range);
int geohashEncode(GeoHashRange *lat_range, GeoHashRange *long_range, int geohashEncode(GeoHashRange *long_range, GeoHashRange *lat_range,
double latitude, double longitude, uint8_t step, double longitude, double latitude, uint8_t step,
GeoHashBits *hash); GeoHashBits *hash);
int geohashEncodeType(double latitude, double longitude, int geohashEncodeType(double longitude, double latitude,
uint8_t step, GeoHashBits *hash); uint8_t step, GeoHashBits *hash);
int geohashEncodeWGS84(double latitude, double longitude, uint8_t step, int geohashEncodeWGS84(double longitude, double latitude, uint8_t step,
GeoHashBits *hash); GeoHashBits *hash);
int geohashDecode(const GeoHashRange lat_range, const GeoHashRange long_range, int geohashDecode(const GeoHashRange long_range, const GeoHashRange lat_range,
const GeoHashBits hash, GeoHashArea *area); const GeoHashBits hash, GeoHashArea *area);
int geohashDecodeType(const GeoHashBits hash, GeoHashArea *area); int geohashDecodeType(const GeoHashBits hash, GeoHashArea *area);
int geohashDecodeWGS84(const GeoHashBits hash, GeoHashArea *area); int geohashDecodeWGS84(const GeoHashBits hash, GeoHashArea *area);
int geohashDecodeAreaToLatLong(const GeoHashArea *area, double *latlong); int geohashDecodeAreaToLongLat(const GeoHashArea *area, double *xy);
int geohashDecodeToLatLongType(const GeoHashBits hash, double *latlong); int geohashDecodeToLongLatType(const GeoHashBits hash, double *xy);
int geohashDecodeToLatLongWGS84(const GeoHashBits hash, double *latlong); int geohashDecodeToLongLatWGS84(const GeoHashBits hash, double *xy);
int geohashDecodeToLatLongMercator(const GeoHashBits hash, double *latlong); int geohashDecodeToLongLatMercator(const GeoHashBits hash, double *xy);
void geohashNeighbors(const GeoHashBits *hash, GeoHashNeighbors *neighbors); void geohashNeighbors(const GeoHashBits *hash, GeoHashNeighbors *neighbors);
#if defined(__cplusplus) #if defined(__cplusplus)

View File

@ -80,13 +80,13 @@ int geohashBitsComparator(const GeoHashBits *a, const GeoHashBits *b) {
return a->step != b->step ? a->step - b->step : a->bits - b->bits; return a->step != b->step ? a->step - b->step : a->bits - b->bits;
} }
int geohashBoundingBox(double latitude, double longitude, double radius_meters, int geohashBoundingBox(double longitude, double latitude, double radius_meters,
double *bounds) { double *bounds) {
if (!bounds) return 0; if (!bounds) return 0;
double latr, lonr; double lonr, latr;
latr = deg_rad(latitude);
lonr = deg_rad(longitude); lonr = deg_rad(longitude);
latr = deg_rad(latitude);
double distance = radius_meters / EARTH_RADIUS_IN_METERS; double distance = radius_meters / EARTH_RADIUS_IN_METERS;
double min_latitude = latr - distance; double min_latitude = latr - distance;
@ -98,37 +98,36 @@ int geohashBoundingBox(double latitude, double longitude, double radius_meters,
min_longitude = lonr - difference_longitude; min_longitude = lonr - difference_longitude;
max_longitude = lonr + difference_longitude; max_longitude = lonr + difference_longitude;
bounds[0] = rad_deg(min_latitude); bounds[0] = rad_deg(min_longitude);
bounds[1] = rad_deg(min_longitude); bounds[1] = rad_deg(min_latitude);
bounds[2] = rad_deg(max_latitude); bounds[2] = rad_deg(max_longitude);
bounds[3] = rad_deg(max_longitude); bounds[3] = rad_deg(max_latitude);
return 1; return 1;
} }
GeoHashRadius geohashGetAreasByRadius(double latitude, double longitude, double radius_meters) { GeoHashRadius geohashGetAreasByRadius(double longitude, double latitude, double radius_meters) {
GeoHashRange lat_range, long_range; GeoHashRange long_range, lat_range;
GeoHashRadius radius = { { 0 } }; GeoHashRadius radius = { { 0 } };
GeoHashBits hash = { 0 }; GeoHashBits hash = { 0 };
GeoHashNeighbors neighbors = { { 0 } }; GeoHashNeighbors neighbors = { { 0 } };
GeoHashArea area = { { 0 } }; GeoHashArea area = { { 0 } };
double min_lat, max_lat, min_lon, max_lon; double min_lon, max_lon, min_lat, max_lat;
double bounds[4]; double bounds[4];
int steps; int steps;
geohashBoundingBox(latitude, longitude, radius_meters, bounds); geohashBoundingBox(longitude, latitude, radius_meters, bounds);
min_lat = bounds[0]; min_lon = bounds[0];
min_lon = bounds[1]; min_lat = bounds[1];
max_lat = bounds[2]; max_lon = bounds[2];
max_lon = bounds[3]; max_lat = bounds[3];
steps = geohashEstimateStepsByRadius(radius_meters,latitude); steps = geohashEstimateStepsByRadius(radius_meters,latitude);
geohashGetCoordRange(&lat_range, &long_range); geohashGetCoordRange(&long_range, &lat_range);
geohashEncode(&lat_range, &long_range, latitude, longitude, steps, &hash); geohashEncode(&long_range, &lat_range, longitude, latitude, steps, &hash);
geohashNeighbors(&hash, &neighbors); geohashNeighbors(&hash, &neighbors);
geohashGetCoordRange(&lat_range, &long_range); geohashGetCoordRange(&long_range, &lat_range);
geohashDecode(lat_range, long_range, hash, &area); geohashDecode(long_range, lat_range, hash, &area);
if (area.latitude.min < min_lat) { if (area.latitude.min < min_lat) {
GZERO(neighbors.south); GZERO(neighbors.south);
@ -156,9 +155,9 @@ GeoHashRadius geohashGetAreasByRadius(double latitude, double longitude, double
return radius; return radius;
} }
GeoHashRadius geohashGetAreasByRadiusWGS84(double latitude, double longitude, GeoHashRadius geohashGetAreasByRadiusWGS84(double longitude, double latitude,
double radius_meters) { double radius_meters) {
return geohashGetAreasByRadius(latitude, longitude, radius_meters); return geohashGetAreasByRadius(longitude, latitude, radius_meters);
} }
GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash) { GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash) {
@ -167,8 +166,8 @@ GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash) {
return bits; return bits;
} }
/* calculate distance using haversin great circle distance formula */ /* Calculate distance using haversin great circle distance formula. */
double distanceEarth(double lat1d, double lon1d, double lat2d, double lon2d) { double distanceEarth(double lon1d, double lat1d, double lon2d, double lat2d) {
double lat1r, lon1r, lat2r, lon2r, u, v; double lat1r, lon1r, lat2r, lon2r, u, v;
lat1r = deg_rad(lat1d); lat1r = deg_rad(lat1d);
lon1r = deg_rad(lon1d); lon1r = deg_rad(lon1d);
@ -183,7 +182,7 @@ double distanceEarth(double lat1d, double lon1d, double lat2d, double lon2d) {
int geohashGetDistanceIfInRadius(double x1, double y1, int geohashGetDistanceIfInRadius(double x1, double y1,
double x2, double y2, double radius, double x2, double y2, double radius,
double *distance) { double *distance) {
*distance = distanceEarth(y1, x1, y2, x2); *distance = distanceEarth(x1, y1, x2, y2);
if (*distance > radius) return 0; if (*distance > radius) return 0;
return 1; return 1;
} }
@ -193,14 +192,3 @@ int geohashGetDistanceIfInRadiusWGS84(double x1, double y1, double x2,
double *distance) { double *distance) {
return geohashGetDistanceIfInRadius(x1, y1, x2, y2, radius, distance); return geohashGetDistanceIfInRadius(x1, y1, x2, y2, radius, distance);
} }
int geohashVerifyCoordinates(double x, double y) {
GeoHashRange lat_range, long_range;
geohashGetCoordRange(&lat_range, &long_range);
if (x < long_range.min || x > long_range.max || y < lat_range.min ||
y > lat_range.max) {
return 0;
}
return 1;
}

View File

@ -49,29 +49,20 @@ typedef struct {
int GeoHashBitsComparator(const GeoHashBits *a, const GeoHashBits *b); int GeoHashBitsComparator(const GeoHashBits *a, const GeoHashBits *b);
uint8_t geohashEstimateStepsByRadius(double range_meters, double lat); uint8_t geohashEstimateStepsByRadius(double range_meters, double lat);
int geohashBoundingBox(double latitude, double longitude, double radius_meters, int geohashBoundingBox(double longitude, double latitude, double radius_meters,
double *bounds); double *bounds);
GeoHashRadius geohashGetAreasByRadius(double latitude, GeoHashRadius geohashGetAreasByRadius(double longitude,
double longitude, double radius_meters); double latitude, double radius_meters);
GeoHashRadius geohashGetAreasByRadiusWGS84(double latitude, double longitude, GeoHashRadius geohashGetAreasByRadiusWGS84(double longitude, double latitude,
double radius_meters); double radius_meters);
GeoHashRadius geohashGetAreasByRadiusMercator(double latitude, double longitude, GeoHashRadius geohashGetAreasByRadiusMercator(double longitude, double latitude,
double radius_meters); double radius_meters);
GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash); GeoHashFix52Bits geohashAlign52Bits(const GeoHashBits hash);
double geohashGetXMercator(double longtitude);
double geohashGetYMercator(double latitude);
double geohashGetXWGS84(double x);
double geohashGetYWGS84(double y);
int geohashVerifyCoordinates(double x, double y);
int geohashGetDistanceIfInRadius(double x1, double y1, int geohashGetDistanceIfInRadius(double x1, double y1,
double x2, double y2, double radius, double x2, double y2, double radius,
double *distance); double *distance);
int geohashGetDistanceIfInRadiusWGS84(double x1, double y1, double x2, int geohashGetDistanceIfInRadiusWGS84(double x1, double y1, double x2,
double y2, double radius, double y2, double radius,
double *distance); double *distance);
int geohashGetDistanceSquaredIfInRadiusMercator(double x1, double y1,
double x2, double y2,
double radius,
double *distance);
#endif /* GEOHASH_HELPER_HPP_ */ #endif /* GEOHASH_HELPER_HPP_ */

113
src/geo.c
View File

@ -82,18 +82,18 @@ void geoArrayFree(geoArray *ga) {
/* ==================================================================== /* ====================================================================
* Helpers * Helpers
* ==================================================================== */ * ==================================================================== */
static inline int decodeGeohash(double bits, double *latlong) { static inline int decodeGeohash(double bits, double *xy) {
GeoHashBits hash = { .bits = (uint64_t)bits, .step = GEO_STEP_MAX }; GeoHashBits hash = { .bits = (uint64_t)bits, .step = GEO_STEP_MAX };
return geohashDecodeToLatLongWGS84(hash, latlong); return geohashDecodeToLongLatWGS84(hash, xy);
} }
/* Input Argument Helper */ /* Input Argument Helper */
/* Take a pointer to the latitude arg then use the next arg for longitude. /* Take a pointer to the latitude arg then use the next arg for longitude.
* On parse error REDIS_ERR is returned, otherwise REDIS_OK. */ * On parse error REDIS_ERR is returned, otherwise REDIS_OK. */
static inline int extractLatLongOrReply(redisClient *c, robj **argv, static inline int extractLongLatOrReply(redisClient *c, robj **argv,
double *latlong) { double *xy) {
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
if (getDoubleFromObjectOrReply(c, argv[i], latlong + i, NULL) != if (getDoubleFromObjectOrReply(c, argv[i], xy + i, NULL) !=
REDIS_OK) { REDIS_OK) {
return REDIS_ERR; return REDIS_ERR;
} }
@ -104,11 +104,11 @@ static inline int extractLatLongOrReply(redisClient *c, robj **argv,
/* Input Argument Helper */ /* Input Argument Helper */
/* Decode lat/long from a zset member's score. /* Decode lat/long from a zset member's score.
* Returns REDIS_OK on successful decoding, otherwise REDIS_ERR is returned. */ * Returns REDIS_OK on successful decoding, otherwise REDIS_ERR is returned. */
static int latLongFromMember(robj *zobj, robj *member, double *latlong) { static int longLatFromMember(robj *zobj, robj *member, double *xy) {
double score = 0; double score = 0;
if (zsetScore(zobj, member, &score) == REDIS_ERR) return REDIS_ERR; if (zsetScore(zobj, member, &score) == REDIS_ERR) return REDIS_ERR;
if (!decodeGeohash(score, latlong)) return REDIS_ERR; if (!decodeGeohash(score, xy)) return REDIS_ERR;
return REDIS_OK; return REDIS_OK;
} }
@ -166,13 +166,13 @@ static inline void addReplyDoubleDistance(redisClient *c, double d) {
* only if the point is within the search area. * only if the point is within the search area.
* *
* returns REDIS_OK if the point is included, or REIDS_ERR if it is outside. */ * returns REDIS_OK if the point is included, or REIDS_ERR if it is outside. */
int geoAppendIfWithinRadius(geoArray *ga, double lat, double lon, double radius, double score, sds member) { int geoAppendIfWithinRadius(geoArray *ga, double lon, double lat, double radius, double score, sds member) {
double distance, latlong[2]; double distance, xy[2];
if (!decodeGeohash(score,latlong)) return REDIS_ERR; /* Can't decode. */ if (!decodeGeohash(score,xy)) return REDIS_ERR; /* Can't decode. */
/* Note that geohashGetDistanceIfInRadiusWGS84() takes arguments in /* Note that geohashGetDistanceIfInRadiusWGS84() takes arguments in
* reverse order: longitude first, latitude later. */ * reverse order: longitude first, latitude later. */
if (!geohashGetDistanceIfInRadiusWGS84(lon,lat,latlong[1], latlong[0], if (!geohashGetDistanceIfInRadiusWGS84(lon,lat, xy[0], xy[1],
radius, &distance)) radius, &distance))
{ {
return REDIS_ERR; return REDIS_ERR;
@ -180,8 +180,8 @@ int geoAppendIfWithinRadius(geoArray *ga, double lat, double lon, double radius,
/* Append the new element. */ /* Append the new element. */
geoPoint *gp = geoArrayAppend(ga); geoPoint *gp = geoArrayAppend(ga);
gp->latitude = latlong[0]; gp->longitude = xy[0];
gp->longitude = latlong[1]; gp->latitude = xy[1];
gp->dist = distance; gp->dist = distance;
gp->member = member; gp->member = member;
gp->score = score; gp->score = score;
@ -200,7 +200,7 @@ int geoAppendIfWithinRadius(geoArray *ga, double lat, double lon, double radius,
* using multiple queries to the sorted set, that we later need to sort * using multiple queries to the sorted set, that we later need to sort
* via qsort. Similarly we need to be able to reject points outside the search * via qsort. Similarly we need to be able to reject points outside the search
* radius area ASAP in order to allocate and process more points than needed. */ * radius area ASAP in order to allocate and process more points than needed. */
int geoGetPointsInRange(robj *zobj, double min, double max, double lat, double lon, double radius, geoArray *ga) { int geoGetPointsInRange(robj *zobj, double min, double max, double lon, double lat, double radius, geoArray *ga) {
/* minex 0 = include min in range; maxex 1 = exclude max in range */ /* minex 0 = include min in range; maxex 1 = exclude max in range */
/* That's: min <= val < max */ /* That's: min <= val < max */
zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 }; zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 };
@ -232,7 +232,7 @@ int geoGetPointsInRange(robj *zobj, double min, double max, double lat, double l
ziplistGet(eptr, &vstr, &vlen, &vlong); ziplistGet(eptr, &vstr, &vlen, &vlong);
member = (vstr == NULL) ? sdsfromlonglong(vlong) : member = (vstr == NULL) ? sdsfromlonglong(vlong) :
sdsnewlen(vstr,vlen); sdsnewlen(vstr,vlen);
if (geoAppendIfWithinRadius(ga,lat,lon,radius,score,member) if (geoAppendIfWithinRadius(ga,lon,lat,radius,score,member)
== REDIS_ERR) sdsfree(member); == REDIS_ERR) sdsfree(member);
zzlNext(zl, &eptr, &sptr); zzlNext(zl, &eptr, &sptr);
} }
@ -255,7 +255,7 @@ int geoGetPointsInRange(robj *zobj, double min, double max, double lat, double l
member = (o->encoding == REDIS_ENCODING_INT) ? member = (o->encoding == REDIS_ENCODING_INT) ?
sdsfromlonglong((long)o->ptr) : sdsfromlonglong((long)o->ptr) :
sdsdup(o->ptr); sdsdup(o->ptr);
if (geoAppendIfWithinRadius(ga,lat,lon,radius,ln->score,member) if (geoAppendIfWithinRadius(ga,lon,lat,radius,ln->score,member)
== REDIS_ERR) sdsfree(member); == REDIS_ERR) sdsfree(member);
ln = ln->level[0].forward; ln = ln->level[0].forward;
} }
@ -266,7 +266,7 @@ int geoGetPointsInRange(robj *zobj, double min, double max, double lat, double l
/* Obtain all members between the min/max of this geohash bounding box. /* Obtain all members between the min/max of this geohash bounding box.
* Populate a geoArray of GeoPoints by calling geoGetPointsInRange(). * Populate a geoArray of GeoPoints by calling geoGetPointsInRange().
* Return the number of points added to the array. */ * Return the number of points added to the array. */
int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lat, double lon, double radius) { int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lon, double lat, double radius) {
GeoHashFix52Bits min, max; GeoHashFix52Bits min, max;
/* We want to compute the sorted set scores that will include all the /* We want to compute the sorted set scores that will include all the
@ -293,11 +293,11 @@ int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lat,
hash.bits++; hash.bits++;
max = geohashAlign52Bits(hash); max = geohashAlign52Bits(hash);
return geoGetPointsInRange(zobj, min, max, lat, lon, radius, ga); return geoGetPointsInRange(zobj, min, max, lon, lat, radius, ga);
} }
/* Search all eight neighbors + self geohash box */ /* Search all eight neighbors + self geohash box */
int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lat, double lon, double radius, geoArray *ga) { int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, double radius, geoArray *ga) {
GeoHashBits neighbors[9]; GeoHashBits neighbors[9];
unsigned int i, count = 0; unsigned int i, count = 0;
@ -316,7 +316,7 @@ int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lat, double lon, d
for (i = 0; i < sizeof(neighbors) / sizeof(*neighbors); i++) { for (i = 0; i < sizeof(neighbors) / sizeof(*neighbors); i++) {
if (HASHISZERO(neighbors[i])) if (HASHISZERO(neighbors[i]))
continue; continue;
count += membersOfGeoHashBox(zobj, neighbors[i], ga, lat, lon, radius); count += membersOfGeoHashBox(zobj, neighbors[i], ga, lon, lat, radius);
} }
return count; return count;
} }
@ -342,9 +342,9 @@ static int sort_gp_desc(const void *a, const void *b) {
* Commands * Commands
* ==================================================================== */ * ==================================================================== */
void geoAddCommand(redisClient *c) { void geoAddCommand(redisClient *c) {
/* args 0-4: [cmd, key, lat, lng, val]; optional 5-6: [radius, units] /* args 0-4: [cmd, key, lng, lat, val]; optional 5-6: [radius, units]
* - OR - * - OR -
* args 0-N: [cmd, key, lat, lng, val, lat2, lng2, val2, ...] */ * args 0-N: [cmd, key, lng, lat, val, lng2, lat2, val2, ...] */
/* Prepare for the three different forms of the add command. */ /* Prepare for the three different forms of the add command. */
double radius_meters = 0; double radius_meters = 0;
@ -358,8 +358,8 @@ void geoAddCommand(redisClient *c) {
return; return;
} else if ((c->argc - 2) % 3 != 0) { } else if ((c->argc - 2) % 3 != 0) {
/* Need an odd number of arguments if we got this far... */ /* Need an odd number of arguments if we got this far... */
addReplyError(c, "format is: geoadd [key] [lat1] [long1] [member1] " addReplyError(c, "format is: geoadd [key] [x1] [y1] [member1] "
"[lat2] [long2] [member2] ... "); "[x2] [y2] [member2] ... ");
return; return;
} }
@ -376,9 +376,9 @@ void geoAddCommand(redisClient *c) {
uint8_t step = geohashEstimateStepsByRadius(radius_meters,0); uint8_t step = geohashEstimateStepsByRadius(radius_meters,0);
int i; int i;
for (i = 0; i < elements; i++) { for (i = 0; i < elements; i++) {
double latlong[elements * 2]; double xy[2];
if (extractLatLongOrReply(c, (c->argv+2)+(i*3),latlong) == REDIS_ERR) { if (extractLongLatOrReply(c, (c->argv+2)+(i*3),xy) == REDIS_ERR) {
for (i = 0; i < argc; i++) for (i = 0; i < argc; i++)
if (argv[i]) decrRefCount(argv[i]); if (argv[i]) decrRefCount(argv[i]);
zfree(argv); zfree(argv);
@ -391,10 +391,7 @@ void geoAddCommand(redisClient *c) {
/* Turn the coordinates into the score of the element. */ /* Turn the coordinates into the score of the element. */
GeoHashBits hash; GeoHashBits hash;
double latitude = latlong[0]; geohashEncodeWGS84(xy[0], xy[1], step, &hash);
double longitude = latlong[1];
geohashEncodeWGS84(latitude, longitude, step, &hash);
GeoHashFix52Bits bits = geohashAlign52Bits(hash); GeoHashFix52Bits bits = geohashAlign52Bits(hash);
robj *score = createObject(REDIS_STRING, sdsfromlonglong(bits)); robj *score = createObject(REDIS_STRING, sdsfromlonglong(bits));
robj *val = c->argv[2 + i * 3 + 2]; robj *val = c->argv[2 + i * 3 + 2];
@ -416,7 +413,7 @@ void geoAddCommand(redisClient *c) {
#define RADIUS_MEMBER 2 #define RADIUS_MEMBER 2
static void geoRadiusGeneric(redisClient *c, int type) { static void geoRadiusGeneric(redisClient *c, int type) {
/* type == cords: [cmd, key, lat, long, radius, units, [optionals]] /* type == cords: [cmd, key, long, lat, radius, units, [optionals]]
* type == member: [cmd, key, member, radius, units, [optionals]] */ * type == member: [cmd, key, member, radius, units, [optionals]] */
robj *key = c->argv[1]; robj *key = c->argv[1];
@ -427,17 +424,17 @@ static void geoRadiusGeneric(redisClient *c, int type) {
return; return;
} }
/* Find lat/long to use for radius search based on inquiry type */ /* Find long/lat to use for radius search based on inquiry type */
int base_args; int base_args;
double latlong[2] = { 0 }; double xy[2] = { 0 };
if (type == RADIUS_COORDS) { if (type == RADIUS_COORDS) {
base_args = 6; base_args = 6;
if (extractLatLongOrReply(c, c->argv + 2, latlong) == REDIS_ERR) if (extractLongLatOrReply(c, c->argv + 2, xy) == REDIS_ERR)
return; return;
} else if (type == RADIUS_MEMBER) { } else if (type == RADIUS_MEMBER) {
base_args = 5; base_args = 5;
robj *member = c->argv[2]; robj *member = c->argv[2];
if (latLongFromMember(zobj, member, latlong) == REDIS_ERR) { if (longLatFromMember(zobj, member, xy) == REDIS_ERR) {
addReplyError(c, "could not decode requested zset member"); addReplyError(c, "could not decode requested zset member");
return; return;
} }
@ -483,7 +480,7 @@ static void geoRadiusGeneric(redisClient *c, int type) {
/* Get all neighbor geohash boxes for our radius search */ /* Get all neighbor geohash boxes for our radius search */
GeoHashRadius georadius = GeoHashRadius georadius =
geohashGetAreasByRadiusWGS84(latlong[0], latlong[1], radius_meters); geohashGetAreasByRadiusWGS84(xy[0], xy[1], radius_meters);
#ifdef DEBUG #ifdef DEBUG
printf("Searching with step size: %d\n", georadius.hash.step); printf("Searching with step size: %d\n", georadius.hash.step);
@ -491,7 +488,7 @@ static void geoRadiusGeneric(redisClient *c, int type) {
/* Search the zset for all matching points */ /* Search the zset for all matching points */
geoArray *ga = geoArrayCreate(); geoArray *ga = geoArrayCreate();
membersOfAllNeighbors(zobj, georadius, latlong[0], latlong[1], radius_meters, ga); membersOfAllNeighbors(zobj, georadius, xy[0], xy[1], radius_meters, ga);
/* If no matching results, the user gets an empty reply. */ /* If no matching results, the user gets an empty reply. */
if (ga->used == 0) { if (ga->used == 0) {
@ -549,15 +546,15 @@ static void geoRadiusGeneric(redisClient *c, int type) {
if (withcoords) { if (withcoords) {
addReplyMultiBulkLen(c, 2); addReplyMultiBulkLen(c, 2);
addReplyDouble(c, gp->latitude);
addReplyDouble(c, gp->longitude); addReplyDouble(c, gp->longitude);
addReplyDouble(c, gp->latitude);
} }
} }
geoArrayFree(ga); geoArrayFree(ga);
} }
void geoRadiusCommand(redisClient *c) { void geoRadiusCommand(redisClient *c) {
/* args 0-5: ["georadius", key, lat, long, radius, units]; /* args 0-5: ["georadius", key, long, lat, radius, units];
* optionals: [withdist, withcoords, asc|desc] */ * optionals: [withdist, withcoords, asc|desc] */
geoRadiusGeneric(c, RADIUS_COORDS); geoRadiusGeneric(c, RADIUS_COORDS);
} }
@ -578,30 +575,30 @@ void geoDecodeCommand(redisClient *c) {
geohash.step = GEO_STEP_MAX; geohash.step = GEO_STEP_MAX;
geohashDecodeWGS84(geohash, &area); geohashDecodeWGS84(geohash, &area);
double lat = (area.latitude.min + area.latitude.max) / 2;
double lon = (area.longitude.min + area.longitude.max) / 2; double lon = (area.longitude.min + area.longitude.max) / 2;
double lat = (area.latitude.min + area.latitude.max) / 2;
/* Returning three nested replies */ /* Returning three nested replies */
addReplyMultiBulkLen(c, 3); addReplyMultiBulkLen(c, 3);
/* First, the minimum corner */ /* First, the minimum corner */
addReplyMultiBulkLen(c, 2); addReplyMultiBulkLen(c, 2);
addReplyDouble(c, area.latitude.min);
addReplyDouble(c, area.longitude.min); addReplyDouble(c, area.longitude.min);
addReplyDouble(c, area.latitude.min);
/* Next, the maximum corner */ /* Next, the maximum corner */
addReplyMultiBulkLen(c, 2); addReplyMultiBulkLen(c, 2);
addReplyDouble(c, area.latitude.max);
addReplyDouble(c, area.longitude.max); addReplyDouble(c, area.longitude.max);
addReplyDouble(c, area.latitude.max);
/* Last, the averaged center of this bounding box */ /* Last, the averaged center of this bounding box */
addReplyMultiBulkLen(c, 2); addReplyMultiBulkLen(c, 2);
addReplyDouble(c, lat);
addReplyDouble(c, lon); addReplyDouble(c, lon);
addReplyDouble(c, lat);
} }
void geoEncodeCommand(redisClient *c) { void geoEncodeCommand(redisClient *c) {
/* args 0-2: ["geoencode", lat, long]; /* args 0-2: ["geoencode", long, lat];
* optionals: [radius, units] */ * optionals: [radius, units] */
double radius_meters = 0; double radius_meters = 0;
@ -613,13 +610,13 @@ void geoEncodeCommand(redisClient *c) {
return; return;
} }
double latlong[2]; double xy[2];
if (extractLatLongOrReply(c, c->argv + 1, latlong) == REDIS_ERR) return; if (extractLongLatOrReply(c, c->argv + 1, xy) == REDIS_ERR) return;
/* Encode lat/long into our geohash */ /* Encode lat/long into our geohash */
GeoHashBits geohash; GeoHashBits geohash;
uint8_t step = geohashEstimateStepsByRadius(radius_meters,0); uint8_t step = geohashEstimateStepsByRadius(radius_meters,0);
geohashEncodeWGS84(latlong[0], latlong[1], step, &geohash); geohashEncodeWGS84(xy[0], xy[1], step, &geohash);
/* Align the hash to a valid 52-bit integer based on step size */ /* Align the hash to a valid 52-bit integer based on step size */
GeoHashFix52Bits bits = geohashAlign52Bits(geohash); GeoHashFix52Bits bits = geohashAlign52Bits(geohash);
@ -631,8 +628,8 @@ void geoEncodeCommand(redisClient *c) {
GeoHashArea area; GeoHashArea area;
geohashDecodeWGS84(geohash, &area); geohashDecodeWGS84(geohash, &area);
double lat = (area.latitude.min + area.latitude.max) / 2;
double lon = (area.longitude.min + area.longitude.max) / 2; double lon = (area.longitude.min + area.longitude.max) / 2;
double lat = (area.latitude.min + area.latitude.max) / 2;
/* Return four nested multibulk replies. */ /* Return four nested multibulk replies. */
addReplyMultiBulkLen(c, 4); addReplyMultiBulkLen(c, 4);
@ -642,18 +639,18 @@ void geoEncodeCommand(redisClient *c) {
/* Return the minimum corner */ /* Return the minimum corner */
addReplyMultiBulkLen(c, 2); addReplyMultiBulkLen(c, 2);
addReplyDouble(c, area.latitude.min);
addReplyDouble(c, area.longitude.min); addReplyDouble(c, area.longitude.min);
addReplyDouble(c, area.latitude.min);
/* Return the maximum corner */ /* Return the maximum corner */
addReplyMultiBulkLen(c, 2); addReplyMultiBulkLen(c, 2);
addReplyDouble(c, area.latitude.max);
addReplyDouble(c, area.longitude.max); addReplyDouble(c, area.longitude.max);
addReplyDouble(c, area.latitude.max);
/* Return the averaged center */ /* Return the averaged center */
addReplyMultiBulkLen(c, 2); addReplyMultiBulkLen(c, 2);
addReplyDouble(c, lat);
addReplyDouble(c, lon); addReplyDouble(c, lon);
addReplyDouble(c, lat);
} }
/* GEOHASH key ele1 ele2 ... eleN /* GEOHASH key ele1 ele2 ... eleN
@ -684,8 +681,8 @@ void geoHashCommand(redisClient *c) {
* standard ranges in order to output a valid geohash string. */ * standard ranges in order to output a valid geohash string. */
/* Decode... */ /* Decode... */
double latlong[2]; double xy[2];
if (!decodeGeohash(score,latlong)) { if (!decodeGeohash(score,xy)) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
continue; continue;
} }
@ -693,11 +690,11 @@ void geoHashCommand(redisClient *c) {
/* Re-encode */ /* Re-encode */
GeoHashRange r[2]; GeoHashRange r[2];
GeoHashBits hash; GeoHashBits hash;
r[0].min = -90; r[0].min = -180;
r[0].max = 90; r[0].max = 180;
r[1].min = -180; r[1].min = -90;
r[1].max = 180; r[1].max = 90;
geohashEncode(&r[0],&r[1],latlong[0],latlong[1],26,&hash); geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
char buf[12]; char buf[12];
int i; int i;

View File

@ -12,8 +12,8 @@ void geoAddCommand(redisClient *c);
/* Structures used inside geo.c in order to represent points and array of /* Structures used inside geo.c in order to represent points and array of
* points on the earth. */ * points on the earth. */
typedef struct geoPoint { typedef struct geoPoint {
double latitude;
double longitude; double longitude;
double latitude;
double dist; double dist;
double score; double score;
char *member; char *member;

View File

@ -2,46 +2,46 @@
# verify the Redis implementation with a fuzzy test. # verify the Redis implementation with a fuzzy test.
proc geo_degrad deg {expr {$deg*atan(1)*8/360}} proc geo_degrad deg {expr {$deg*atan(1)*8/360}}
proc geo_distance {lat1d lon1d lat2d lon2d} { proc geo_distance {lon1d lat1d lon2d lat2d} {
set lat1r [geo_degrad $lat1d]
set lon1r [geo_degrad $lon1d] set lon1r [geo_degrad $lon1d]
set lat2r [geo_degrad $lat2d] set lat1r [geo_degrad $lat1d]
set lon2r [geo_degrad $lon2d] set lon2r [geo_degrad $lon2d]
set u [expr {sin(($lat2r - $lat1r) / 2)}] set lat2r [geo_degrad $lat2d]
set v [expr {sin(($lon2r - $lon1r) / 2)}] set v [expr {sin(($lon2r - $lon1r) / 2)}]
set u [expr {sin(($lat2r - $lat1r) / 2)}]
expr {2.0 * 6372797.560856 * \ expr {2.0 * 6372797.560856 * \
asin(sqrt($u * $u + cos($lat1r) * cos($lat2r) * $v * $v))} asin(sqrt($u * $u + cos($lat1r) * cos($lat2r) * $v * $v))}
} }
proc geo_random_point {latvar lonvar} { proc geo_random_point {lonvar latvar} {
upvar 1 $latvar lat
upvar 1 $lonvar lon upvar 1 $lonvar lon
upvar 1 $latvar lat
# Note that the actual latitude limit should be -85 to +85, we restrict # Note that the actual latitude limit should be -85 to +85, we restrict
# the test to -70 to +70 since in this range the algorithm is more precise # the test to -70 to +70 since in this range the algorithm is more precise
# while outside this range occasionally some element may be missing. # while outside this range occasionally some element may be missing.
set lat [expr {-70 + rand()*140}]
set lon [expr {-180 + rand()*360}] set lon [expr {-180 + rand()*360}]
set lat [expr {-70 + rand()*140}]
} }
start_server {tags {"geo"}} { start_server {tags {"geo"}} {
test {GEOADD create} { test {GEOADD create} {
r geoadd nyc 40.747533 -73.9454966 "lic market" r geoadd nyc -73.9454966 40.747533 "lic market"
} {1} } {1}
test {GEOADD update} { test {GEOADD update} {
r geoadd nyc 40.747533 -73.9454966 "lic market" r geoadd nyc -73.9454966 40.747533 "lic market"
} {0} } {0}
test {GEOADD invalid coordinates} { test {GEOADD invalid coordinates} {
catch { catch {
r geoadd nyc 40.747533 -73.9454966 "lic market" \ r geoadd nyc -73.9454966 40.747533 "lic market" \
foo bar "luck market" foo bar "luck market"
} err } err
set err set err
} {*valid*} } {*valid*}
test {GEOADD multi add} { test {GEOADD multi add} {
r geoadd nyc 40.7648057 -73.9733487 "central park n/q/r" 40.7362513 -73.9903085 "union square" 40.7126674 -74.0131604 "wtc one" 40.6428986 -73.7858139 "jfk" 40.7498929 -73.9375699 "q4" 40.7480973 -73.9564142 4545 r geoadd nyc -73.9733487 40.7648057 "central park n/q/r" -73.9903085 40.7362513 "union square" -74.0131604 40.7126674 "wtc one" -73.7858139 40.6428986 "jfk" -73.9375699 40.7498929 "q4" -73.9564142 40.7480973 4545
} {6} } {6}
test {Check geoset values} { test {Check geoset values} {
@ -49,11 +49,11 @@ start_server {tags {"geo"}} {
} {{wtc one} 1791873972053020 {union square} 1791875485187452 {central park n/q/r} 1791875761332224 4545 1791875796750882 {lic market} 1791875804419201 q4 1791875830079666 jfk 1791895905559723} } {{wtc one} 1791873972053020 {union square} 1791875485187452 {central park n/q/r} 1791875761332224 4545 1791875796750882 {lic market} 1791875804419201 q4 1791875830079666 jfk 1791895905559723}
test {GEORADIUS simple (sorted)} { test {GEORADIUS simple (sorted)} {
r georadius nyc 40.7598464 -73.9798091 3 km ascending r georadius nyc -73.9798091 40.7598464 3 km ascending
} {{central park n/q/r} 4545 {union square}} } {{central park n/q/r} 4545 {union square}}
test {GEORADIUS withdistance (sorted)} { test {GEORADIUS withdistance (sorted)} {
r georadius nyc 40.7598464 -73.9798091 3 km withdistance ascending r georadius nyc -73.9798091 40.7598464 3 km withdistance ascending
} {{{central park n/q/r} 0.7750} {4545 2.3651} {{union square} 2.7697}} } {{{central park n/q/r} 0.7750} {4545 2.3651} {{union square} 2.7697}}
test {GEORADIUSBYMEMBER simple (sorted)} { test {GEORADIUSBYMEMBER simple (sorted)} {
@ -65,21 +65,21 @@ start_server {tags {"geo"}} {
} {{{wtc one} 0.0000} {{union square} 3.2544} {{central park n/q/r} 6.7000} {4545 6.1975} {{lic market} 6.8969}} } {{{wtc one} 0.0000} {{union square} 3.2544} {{central park n/q/r} 6.7000} {4545 6.1975} {{lic market} 6.8969}}
test {GEOENCODE simple} { test {GEOENCODE simple} {
r geoencode 41.2358883 1.8063239 r geoencode 1.8063239 41.2358883
} {3471579339700058 {41.235888125243704 1.8063229322433472}\ } {3471579339700058 {1.8063229322433472 41.235888125243704}\
{41.235890659964866 1.806328296661377}\ {1.806328296661377 41.235890659964866}\
{41.235889392604285 1.8063256144523621}} {1.8063256144523621 41.235889392604285}}
test {GEODECODE simple} { test {GEODECODE simple} {
r geodecode 3471579339700058 r geodecode 3471579339700058
} {{41.235888125243704 1.8063229322433472}\ } {{1.8063229322433472 41.235888125243704}\
{41.235890659964866 1.806328296661377}\ {1.806328296661377 41.235890659964866}\
{41.235889392604285 1.8063256144523621}} {1.8063256144523621 41.235889392604285}}
test {GEOHASH is able to return geohash strings} { test {GEOHASH is able to return geohash strings} {
# Example from Wikipedia. # Example from Wikipedia.
r del points r del points
r geoadd points 42.6 -5.6 test r geoadd points -5.6 42.6 test
lindex [r geohash points test] 0 lindex [r geohash points test] 0
} {ezs42e44yx0} } {ezs42e44yx0}
@ -93,20 +93,20 @@ start_server {tags {"geo"}} {
r del mypoints r del mypoints
set radius_km [expr {[randomInt 200]+10}] set radius_km [expr {[randomInt 200]+10}]
set radius_m [expr {$radius_km*1000}] set radius_m [expr {$radius_km*1000}]
geo_random_point search_lat search_lon geo_random_point search_lon search_lat
lappend debuginfo "Search area: $search_lat,$search_lon $radius_km km" lappend debuginfo "Search area: $search_lon,$search_lat $radius_km km"
set tcl_result {} set tcl_result {}
set argv {} set argv {}
for {set j 0} {$j < 20000} {incr j} { for {set j 0} {$j < 20000} {incr j} {
geo_random_point lat lon geo_random_point lon lat
lappend argv $lat $lon "place:$j" lappend argv $lon $lat "place:$j"
if {[geo_distance $lat $lon $search_lat $search_lon] < $radius_m} { if {[geo_distance $lon $lat $search_lon $search_lat] < $radius_m} {
lappend tcl_result "place:$j" lappend tcl_result "place:$j"
lappend debuginfo "place:$j $lat $lon [expr {[geo_distance $lat $lon $search_lat $search_lon]/1000}] km" lappend debuginfo "place:$j $lon $lat [expr {[geo_distance $lon $lat $search_lon $search_lat]/1000}] km"
} }
} }
r geoadd mypoints {*}$argv r geoadd mypoints {*}$argv
set res [lsort [r georadius mypoints $search_lat $search_lon $radius_km km]] set res [lsort [r georadius mypoints $search_lon $search_lat $radius_km km]]
set res2 [lsort $tcl_result] set res2 [lsort $tcl_result]
set test_result OK set test_result OK
if {$res != $res2} { if {$res != $res2} {