xref: /redis-3.2.3/src/geo.c (revision 38cf2bd2)
1 /*
2  * Copyright (c) 2014, Matt Stancliff <[email protected]>.
3  * Copyright (c) 2015, Salvatore Sanfilippo <[email protected]>.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *   * Redistributions of source code must retain the above copyright notice,
10  *     this list of conditions and the following disclaimer.
11  *   * Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  *   * Neither the name of Redis nor the names of its contributors may be used
15  *     to endorse or promote products derived from this software without
16  *     specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "geo.h"
32 #include "geohash_helper.h"
33 
34 /* Things exported from t_zset.c only for geo.c, since it is the only other
35  * part of Redis that requires close zset introspection. */
36 unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range);
37 int zslValueLteMax(double value, zrangespec *spec);
38 
39 /* ====================================================================
40  * This file implements the following commands:
41  *
42  *   - geoadd - add coordinates for value to geoset
43  *   - georadius - search radius by coordinates in geoset
44  *   - georadiusbymember - search radius based on geoset member position
45  * ==================================================================== */
46 
47 /* ====================================================================
48  * geoArray implementation
49  * ==================================================================== */
50 
51 /* Create a new array of geoPoints. */
52 geoArray *geoArrayCreate(void) {
53     geoArray *ga = zmalloc(sizeof(*ga));
54     /* It gets allocated on first geoArrayAppend() call. */
55     ga->array = NULL;
56     ga->buckets = 0;
57     ga->used = 0;
58     return ga;
59 }
60 
61 /* Add a new entry and return its pointer so that the caller can populate
62  * it with data. */
63 geoPoint *geoArrayAppend(geoArray *ga) {
64     if (ga->used == ga->buckets) {
65         ga->buckets = (ga->buckets == 0) ? 8 : ga->buckets*2;
66         ga->array = zrealloc(ga->array,sizeof(geoPoint)*ga->buckets);
67     }
68     geoPoint *gp = ga->array+ga->used;
69     ga->used++;
70     return gp;
71 }
72 
73 /* Destroy a geoArray created with geoArrayCreate(). */
74 void geoArrayFree(geoArray *ga) {
75     size_t i;
76     for (i = 0; i < ga->used; i++) sdsfree(ga->array[i].member);
77     zfree(ga->array);
78     zfree(ga);
79 }
80 
81 /* ====================================================================
82  * Helpers
83  * ==================================================================== */
84 int decodeGeohash(double bits, double *xy) {
85     GeoHashBits hash = { .bits = (uint64_t)bits, .step = GEO_STEP_MAX };
86     return geohashDecodeToLongLatWGS84(hash, xy);
87 }
88 
89 /* Input Argument Helper */
90 /* Take a pointer to the latitude arg then use the next arg for longitude.
91  * On parse error C_ERR is returned, otherwise C_OK. */
92 int extractLongLatOrReply(client *c, robj **argv, double *xy) {
93     int i;
94     for (i = 0; i < 2; i++) {
95         if (getDoubleFromObjectOrReply(c, argv[i], xy + i, NULL) !=
96             C_OK) {
97             return C_ERR;
98         }
99     }
100     if (xy[0] < GEO_LONG_MIN || xy[0] > GEO_LONG_MAX ||
101         xy[1] < GEO_LAT_MIN  || xy[1] > GEO_LAT_MAX) {
102         addReplySds(c, sdscatprintf(sdsempty(),
103             "-ERR invalid longitude,latitude pair %f,%f\r\n",xy[0],xy[1]));
104         return C_ERR;
105     }
106     return C_OK;
107 }
108 
109 /* Input Argument Helper */
110 /* Decode lat/long from a zset member's score.
111  * Returns C_OK on successful decoding, otherwise C_ERR is returned. */
112 int longLatFromMember(robj *zobj, robj *member, double *xy) {
113     double score = 0;
114 
115     if (zsetScore(zobj, member, &score) == C_ERR) return C_ERR;
116     if (!decodeGeohash(score, xy)) return C_ERR;
117     return C_OK;
118 }
119 
120 /* Check that the unit argument matches one of the known units, and returns
121  * the conversion factor to meters (you need to divide meters by the conversion
122  * factor to convert to the right unit).
123  *
124  * If the unit is not valid, an error is reported to the client, and a value
125  * less than zero is returned. */
126 double extractUnitOrReply(client *c, robj *unit) {
127     char *u = unit->ptr;
128 
129     if (!strcmp(u, "m")) {
130         return 1;
131     } else if (!strcmp(u, "km")) {
132         return 1000;
133     } else if (!strcmp(u, "ft")) {
134         return 0.3048;
135     } else if (!strcmp(u, "mi")) {
136         return 1609.34;
137     } else {
138         addReplyError(c,
139             "unsupported unit provided. please use m, km, ft, mi");
140         return -1;
141     }
142 }
143 
144 /* Input Argument Helper.
145  * Extract the dinstance from the specified two arguments starting at 'argv'
146  * that shouldbe in the form: <number> <unit> and return the dinstance in the
147  * specified unit on success. *conversino is populated with the coefficient
148  * to use in order to convert meters to the unit.
149  *
150  * On error a value less than zero is returned. */
151 double extractDistanceOrReply(client *c, robj **argv,
152                                      double *conversion) {
153     double distance;
154     if (getDoubleFromObjectOrReply(c, argv[0], &distance,
155                                    "need numeric radius") != C_OK) {
156         return -1;
157     }
158 
159     double to_meters = extractUnitOrReply(c,argv[1]);
160     if (to_meters < 0) return -1;
161 
162     if (conversion) *conversion = to_meters;
163     return distance * to_meters;
164 }
165 
166 /* The default addReplyDouble has too much accuracy.  We use this
167  * for returning location distances. "5.2145 meters away" is nicer
168  * than "5.2144992818115 meters away." We provide 4 digits after the dot
169  * so that the returned value is decently accurate even when the unit is
170  * the kilometer. */
171 void addReplyDoubleDistance(client *c, double d) {
172     char dbuf[128];
173     int dlen = snprintf(dbuf, sizeof(dbuf), "%.4f", d);
174     addReplyBulkCBuffer(c, dbuf, dlen);
175 }
176 
177 /* Helper function for geoGetPointsInRange(): given a sorted set score
178  * representing a point, and another point (the center of our search) and
179  * a radius, appends this entry as a geoPoint into the specified geoArray
180  * only if the point is within the search area.
181  *
182  * returns C_OK if the point is included, or REIDS_ERR if it is outside. */
183 int geoAppendIfWithinRadius(geoArray *ga, double lon, double lat, double radius, double score, sds member) {
184     double distance, xy[2];
185 
186     if (!decodeGeohash(score,xy)) return C_ERR; /* Can't decode. */
187     /* Note that geohashGetDistanceIfInRadiusWGS84() takes arguments in
188      * reverse order: longitude first, latitude later. */
189     if (!geohashGetDistanceIfInRadiusWGS84(lon,lat, xy[0], xy[1],
190                                            radius, &distance))
191     {
192         return C_ERR;
193     }
194 
195     /* Append the new element. */
196     geoPoint *gp = geoArrayAppend(ga);
197     gp->longitude = xy[0];
198     gp->latitude = xy[1];
199     gp->dist = distance;
200     gp->member = member;
201     gp->score = score;
202     return C_OK;
203 }
204 
205 /* Query a Redis sorted set to extract all the elements between 'min' and
206  * 'max', appending them into the array of geoPoint structures 'gparray'.
207  * The command returns the number of elements added to the array.
208  *
209  * Elements which are farest than 'radius' from the specified 'x' and 'y'
210  * coordinates are not included.
211  *
212  * The ability of this function to append to an existing set of points is
213  * important for good performances because querying by radius is performed
214  * using multiple queries to the sorted set, that we later need to sort
215  * via qsort. Similarly we need to be able to reject points outside the search
216  * radius area ASAP in order to allocate and process more points than needed. */
217 int geoGetPointsInRange(robj *zobj, double min, double max, double lon, double lat, double radius, geoArray *ga) {
218     /* minex 0 = include min in range; maxex 1 = exclude max in range */
219     /* That's: min <= val < max */
220     zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 };
221     size_t origincount = ga->used;
222     sds member;
223 
224     if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
225         unsigned char *zl = zobj->ptr;
226         unsigned char *eptr, *sptr;
227         unsigned char *vstr = NULL;
228         unsigned int vlen = 0;
229         long long vlong = 0;
230         double score = 0;
231 
232         if ((eptr = zzlFirstInRange(zl, &range)) == NULL) {
233             /* Nothing exists starting at our min.  No results. */
234             return 0;
235         }
236 
237         sptr = ziplistNext(zl, eptr);
238         while (eptr) {
239             score = zzlGetScore(sptr);
240 
241             /* If we fell out of range, break. */
242             if (!zslValueLteMax(score, &range))
243                 break;
244 
245             /* We know the element exists. ziplistGet should always succeed */
246             ziplistGet(eptr, &vstr, &vlen, &vlong);
247             member = (vstr == NULL) ? sdsfromlonglong(vlong) :
248                                       sdsnewlen(vstr,vlen);
249             if (geoAppendIfWithinRadius(ga,lon,lat,radius,score,member)
250                 == C_ERR) sdsfree(member);
251             zzlNext(zl, &eptr, &sptr);
252         }
253     } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
254         zset *zs = zobj->ptr;
255         zskiplist *zsl = zs->zsl;
256         zskiplistNode *ln;
257 
258         if ((ln = zslFirstInRange(zsl, &range)) == NULL) {
259             /* Nothing exists starting at our min.  No results. */
260             return 0;
261         }
262 
263         while (ln) {
264             robj *o = ln->obj;
265             /* Abort when the node is no longer in range. */
266             if (!zslValueLteMax(ln->score, &range))
267                 break;
268 
269             member = (o->encoding == OBJ_ENCODING_INT) ?
270                         sdsfromlonglong((long)o->ptr) :
271                         sdsdup(o->ptr);
272             if (geoAppendIfWithinRadius(ga,lon,lat,radius,ln->score,member)
273                 == C_ERR) sdsfree(member);
274             ln = ln->level[0].forward;
275         }
276     }
277     return ga->used - origincount;
278 }
279 
280 /* Compute the sorted set scores min (inclusive), max (exclusive) we should
281  * query in order to retrieve all the elements inside the specified area
282  * 'hash'. The two scores are returned by reference in *min and *max. */
283 void scoresOfGeoHashBox(GeoHashBits hash, GeoHashFix52Bits *min, GeoHashFix52Bits *max) {
284     /* We want to compute the sorted set scores that will include all the
285      * elements inside the specified Geohash 'hash', which has as many
286      * bits as specified by hash.step * 2.
287      *
288      * So if step is, for example, 3, and the hash value in binary
289      * is 101010, since our score is 52 bits we want every element which
290      * is in binary: 101010?????????????????????????????????????????????
291      * Where ? can be 0 or 1.
292      *
293      * To get the min score we just use the initial hash value left
294      * shifted enough to get the 52 bit value. Later we increment the
295      * 6 bit prefis (see the hash.bits++ statement), and get the new
296      * prefix: 101011, which we align again to 52 bits to get the maximum
297      * value (which is excluded from the search). So we get everything
298      * between the two following scores (represented in binary):
299      *
300      * 1010100000000000000000000000000000000000000000000000 (included)
301      * and
302      * 1010110000000000000000000000000000000000000000000000 (excluded).
303      */
304     *min = geohashAlign52Bits(hash);
305     hash.bits++;
306     *max = geohashAlign52Bits(hash);
307 }
308 
309 /* Obtain all members between the min/max of this geohash bounding box.
310  * Populate a geoArray of GeoPoints by calling geoGetPointsInRange().
311  * Return the number of points added to the array. */
312 int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lon, double lat, double radius) {
313     GeoHashFix52Bits min, max;
314 
315     scoresOfGeoHashBox(hash,&min,&max);
316     return geoGetPointsInRange(zobj, min, max, lon, lat, radius, ga);
317 }
318 
319 /* Search all eight neighbors + self geohash box */
320 int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, double radius, geoArray *ga) {
321     GeoHashBits neighbors[9];
322     unsigned int i, count = 0, last_processed = 0;
323 
324     neighbors[0] = n.hash;
325     neighbors[1] = n.neighbors.north;
326     neighbors[2] = n.neighbors.south;
327     neighbors[3] = n.neighbors.east;
328     neighbors[4] = n.neighbors.west;
329     neighbors[5] = n.neighbors.north_east;
330     neighbors[6] = n.neighbors.north_west;
331     neighbors[7] = n.neighbors.south_east;
332     neighbors[8] = n.neighbors.south_west;
333 
334     /* For each neighbor (*and* our own hashbox), get all the matching
335      * members and add them to the potential result list. */
336     for (i = 0; i < sizeof(neighbors) / sizeof(*neighbors); i++) {
337         if (HASHISZERO(neighbors[i]))
338             continue;
339 
340         /* When a huge Radius (in the 5000 km range or more) is used,
341          * adjacent neighbors can be the same, leading to duplicated
342          * elements. Skip every range which is the same as the one
343          * processed previously. */
344         if (last_processed &&
345             neighbors[i].bits == neighbors[last_processed].bits &&
346             neighbors[i].step == neighbors[last_processed].step)
347             continue;
348         count += membersOfGeoHashBox(zobj, neighbors[i], ga, lon, lat, radius);
349         last_processed = i;
350     }
351     return count;
352 }
353 
354 /* Sort comparators for qsort() */
355 static int sort_gp_asc(const void *a, const void *b) {
356     const struct geoPoint *gpa = a, *gpb = b;
357     /* We can't do adist - bdist because they are doubles and
358      * the comparator returns an int. */
359     if (gpa->dist > gpb->dist)
360         return 1;
361     else if (gpa->dist == gpb->dist)
362         return 0;
363     else
364         return -1;
365 }
366 
367 static int sort_gp_desc(const void *a, const void *b) {
368     return -sort_gp_asc(a, b);
369 }
370 
371 /* ====================================================================
372  * Commands
373  * ==================================================================== */
374 
375 /* GEOADD key long lat name [long2 lat2 name2 ... longN latN nameN] */
376 void geoaddCommand(client *c) {
377     /* Check arguments number for sanity. */
378     if ((c->argc - 2) % 3 != 0) {
379         /* Need an odd number of arguments if we got this far... */
380         addReplyError(c, "syntax error. Try GEOADD key [x1] [y1] [name1] "
381                          "[x2] [y2] [name2] ... ");
382         return;
383     }
384 
385     int elements = (c->argc - 2) / 3;
386     int argc = 2+elements*2; /* ZADD key score ele ... */
387     robj **argv = zcalloc(argc*sizeof(robj*));
388     argv[0] = createRawStringObject("zadd",4);
389     argv[1] = c->argv[1]; /* key */
390     incrRefCount(argv[1]);
391 
392     /* Create the argument vector to call ZADD in order to add all
393      * the score,value pairs to the requested zset, where score is actually
394      * an encoded version of lat,long. */
395     int i;
396     for (i = 0; i < elements; i++) {
397         double xy[2];
398 
399         if (extractLongLatOrReply(c, (c->argv+2)+(i*3),xy) == C_ERR) {
400             for (i = 0; i < argc; i++)
401                 if (argv[i]) decrRefCount(argv[i]);
402             zfree(argv);
403             return;
404         }
405 
406         /* Turn the coordinates into the score of the element. */
407         GeoHashBits hash;
408         geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
409         GeoHashFix52Bits bits = geohashAlign52Bits(hash);
410         robj *score = createObject(OBJ_STRING, sdsfromlonglong(bits));
411         robj *val = c->argv[2 + i * 3 + 2];
412         argv[2+i*2] = score;
413         argv[3+i*2] = val;
414         incrRefCount(val);
415     }
416 
417     /* Finally call ZADD that will do the work for us. */
418     replaceClientCommandVector(c,argc,argv);
419     zaddCommand(c);
420 }
421 
422 #define SORT_NONE 0
423 #define SORT_ASC 1
424 #define SORT_DESC 2
425 
426 #define RADIUS_COORDS 1
427 #define RADIUS_MEMBER 2
428 
429 /* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
430  *                               [COUNT count] [STORE key] [STOREDIST key]
431  * GEORADIUSBYMEMBER key member radius unit ... options ... */
432 void georadiusGeneric(client *c, int type) {
433     robj *key = c->argv[1];
434     robj *storekey = NULL;
435     int storedist = 0; /* 0 for STORE, 1 for STOREDIST. */
436 
437     /* Look up the requested zset */
438     robj *zobj = NULL;
439     if ((zobj = lookupKeyReadOrReply(c, key, shared.emptymultibulk)) == NULL ||
440         checkType(c, zobj, OBJ_ZSET)) {
441         return;
442     }
443 
444     /* Find long/lat to use for radius search based on inquiry type */
445     int base_args;
446     double xy[2] = { 0 };
447     if (type == RADIUS_COORDS) {
448         base_args = 6;
449         if (extractLongLatOrReply(c, c->argv + 2, xy) == C_ERR)
450             return;
451     } else if (type == RADIUS_MEMBER) {
452         base_args = 5;
453         robj *member = c->argv[2];
454         if (longLatFromMember(zobj, member, xy) == C_ERR) {
455             addReplyError(c, "could not decode requested zset member");
456             return;
457         }
458     } else {
459         addReplyError(c, "unknown georadius search type");
460         return;
461     }
462 
463     /* Extract radius and units from arguments */
464     double radius_meters = 0, conversion = 1;
465     if ((radius_meters = extractDistanceOrReply(c, c->argv + base_args - 2,
466                                                 &conversion)) < 0) {
467         return;
468     }
469 
470     /* Discover and populate all optional parameters. */
471     int withdist = 0, withhash = 0, withcoords = 0;
472     int sort = SORT_NONE;
473     long long count = 0;
474     if (c->argc > base_args) {
475         int remaining = c->argc - base_args;
476         for (int i = 0; i < remaining; i++) {
477             char *arg = c->argv[base_args + i]->ptr;
478             if (!strcasecmp(arg, "withdist")) {
479                 withdist = 1;
480             } else if (!strcasecmp(arg, "withhash")) {
481                 withhash = 1;
482             } else if (!strcasecmp(arg, "withcoord")) {
483                 withcoords = 1;
484             } else if (!strcasecmp(arg, "asc")) {
485                 sort = SORT_ASC;
486             } else if (!strcasecmp(arg, "desc")) {
487                 sort = SORT_DESC;
488             } else if (!strcasecmp(arg, "count") && (i+1) < remaining) {
489                 if (getLongLongFromObjectOrReply(c, c->argv[base_args+i+1],
490                     &count, NULL) != C_OK) return;
491                 if (count <= 0) {
492                     addReplyError(c,"COUNT must be > 0");
493                     return;
494                 }
495                 i++;
496             } else if (!strcasecmp(arg, "store") && (i+1) < remaining) {
497                 storekey = c->argv[base_args+i+1];
498                 storedist = 0;
499                 i++;
500             } else if (!strcasecmp(arg, "storedist") && (i+1) < remaining) {
501                 storekey = c->argv[base_args+i+1];
502                 storedist = 1;
503                 i++;
504             } else {
505                 addReply(c, shared.syntaxerr);
506                 return;
507             }
508         }
509     }
510 
511     /* Trap options not compatible with STORE and STOREDIST. */
512     if (storekey && (withdist || withhash || withcoords)) {
513         addReplyError(c,
514             "STORE option in GEORADIUS is not compatible with "
515             "WITHDIST, WITHHASH and WITHCOORDS options");
516         return;
517     }
518 
519     /* COUNT without ordering does not make much sense, force ASC
520      * ordering if COUNT was specified but no sorting was requested. */
521     if (count != 0 && sort == SORT_NONE) sort = SORT_ASC;
522 
523     /* Get all neighbor geohash boxes for our radius search */
524     GeoHashRadius georadius =
525         geohashGetAreasByRadiusWGS84(xy[0], xy[1], radius_meters);
526 
527     /* Search the zset for all matching points */
528     geoArray *ga = geoArrayCreate();
529     membersOfAllNeighbors(zobj, georadius, xy[0], xy[1], radius_meters, ga);
530 
531     /* If no matching results, the user gets an empty reply. */
532     if (ga->used == 0 && storekey == NULL) {
533         addReply(c, shared.emptymultibulk);
534         geoArrayFree(ga);
535         return;
536     }
537 
538     long result_length = ga->used;
539     long returned_items = (count == 0 || result_length < count) ?
540                           result_length : count;
541     long option_length = 0;
542 
543     /* Process [optional] requested sorting */
544     if (sort == SORT_ASC) {
545         qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_asc);
546     } else if (sort == SORT_DESC) {
547         qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_desc);
548     }
549 
550     if (storekey == NULL) {
551         /* No target key, return results to user. */
552 
553         /* Our options are self-contained nested multibulk replies, so we
554          * only need to track how many of those nested replies we return. */
555         if (withdist)
556             option_length++;
557 
558         if (withcoords)
559             option_length++;
560 
561         if (withhash)
562             option_length++;
563 
564         /* The multibulk len we send is exactly result_length. The result is
565          * either all strings of just zset members  *or* a nested multi-bulk
566          * reply containing the zset member string _and_ all the additional
567          * options the user enabled for this request. */
568         addReplyMultiBulkLen(c, returned_items);
569 
570         /* Finally send results back to the caller */
571         int i;
572         for (i = 0; i < returned_items; i++) {
573             geoPoint *gp = ga->array+i;
574             gp->dist /= conversion; /* Fix according to unit. */
575 
576             /* If we have options in option_length, return each sub-result
577              * as a nested multi-bulk.  Add 1 to account for result value
578              * itself. */
579             if (option_length)
580                 addReplyMultiBulkLen(c, option_length + 1);
581 
582             addReplyBulkSds(c,gp->member);
583             gp->member = NULL;
584 
585             if (withdist)
586                 addReplyDoubleDistance(c, gp->dist);
587 
588             if (withhash)
589                 addReplyLongLong(c, gp->score);
590 
591             if (withcoords) {
592                 addReplyMultiBulkLen(c, 2);
593                 addReplyHumanLongDouble(c, gp->longitude);
594                 addReplyHumanLongDouble(c, gp->latitude);
595             }
596         }
597     } else {
598         /* Target key, create a sorted set with the results. */
599         robj *zobj;
600         zset *zs;
601         int i;
602         size_t maxelelen = 0;
603 
604         if (returned_items) {
605             zobj = createZsetObject();
606             zs = zobj->ptr;
607         }
608 
609         for (i = 0; i < returned_items; i++) {
610             zskiplistNode *znode;
611             geoPoint *gp = ga->array+i;
612             gp->dist /= conversion; /* Fix according to unit. */
613             double score = storedist ? gp->dist : gp->score;
614             size_t elelen = sdslen(gp->member);
615             robj *ele = createObject(OBJ_STRING,gp->member);
616 
617             if (maxelelen < elelen) maxelelen = elelen;
618             incrRefCount(ele); /* Set refcount to 2 since we reference the
619                                   object both in the skiplist and dict. */
620             znode = zslInsert(zs->zsl,score,ele);
621             serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
622             gp->member = NULL;
623         }
624 
625         if (returned_items) {
626             zsetConvertToZiplistIfNeeded(zobj,maxelelen);
627             setKey(c->db,storekey,zobj);
628             decrRefCount(zobj);
629             notifyKeyspaceEvent(NOTIFY_LIST,"georadiusstore",storekey,
630                                 c->db->id);
631             server.dirty += returned_items;
632         } else if (dbDelete(c->db,storekey)) {
633             signalModifiedKey(c->db,storekey);
634             notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
635             server.dirty++;
636         }
637         addReplyLongLong(c, returned_items);
638     }
639     geoArrayFree(ga);
640 }
641 
642 /* GEORADIUS wrapper function. */
643 void georadiusCommand(client *c) {
644     georadiusGeneric(c, RADIUS_COORDS);
645 }
646 
647 /* GEORADIUSBYMEMBER wrapper function. */
648 void georadiusByMemberCommand(client *c) {
649     georadiusGeneric(c, RADIUS_MEMBER);
650 }
651 
652 /* GEOHASH key ele1 ele2 ... eleN
653  *
654  * Returns an array with an 11 characters geohash representation of the
655  * position of the specified elements. */
656 void geohashCommand(client *c) {
657     char *geoalphabet= "0123456789bcdefghjkmnpqrstuvwxyz";
658     int j;
659 
660     /* Look up the requested zset */
661     robj *zobj = NULL;
662     if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
663         == NULL || checkType(c, zobj, OBJ_ZSET)) return;
664 
665     /* Geohash elements one after the other, using a null bulk reply for
666      * missing elements. */
667     addReplyMultiBulkLen(c,c->argc-2);
668     for (j = 2; j < c->argc; j++) {
669         double score;
670         if (zsetScore(zobj, c->argv[j], &score) == C_ERR) {
671             addReply(c,shared.nullbulk);
672         } else {
673             /* The internal format we use for geocoding is a bit different
674              * than the standard, since we use as initial latitude range
675              * -85,85, while the normal geohashing algorithm uses -90,90.
676              * So we have to decode our position and re-encode using the
677              * standard ranges in order to output a valid geohash string. */
678 
679             /* Decode... */
680             double xy[2];
681             if (!decodeGeohash(score,xy)) {
682                 addReply(c,shared.nullbulk);
683                 continue;
684             }
685 
686             /* Re-encode */
687             GeoHashRange r[2];
688             GeoHashBits hash;
689             r[0].min = -180;
690             r[0].max = 180;
691             r[1].min = -90;
692             r[1].max = 90;
693             geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
694 
695             char buf[12];
696             int i;
697             for (i = 0; i < 11; i++) {
698                 int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
699                 buf[i] = geoalphabet[idx];
700             }
701             buf[11] = '\0';
702             addReplyBulkCBuffer(c,buf,11);
703         }
704     }
705 }
706 
707 /* GEOPOS key ele1 ele2 ... eleN
708  *
709  * Returns an array of two-items arrays representing the x,y position of each
710  * element specified in the arguments. For missing elements NULL is returned. */
711 void geoposCommand(client *c) {
712     int j;
713 
714     /* Look up the requested zset */
715     robj *zobj = NULL;
716     if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
717         == NULL || checkType(c, zobj, OBJ_ZSET)) return;
718 
719     /* Report elements one after the other, using a null bulk reply for
720      * missing elements. */
721     addReplyMultiBulkLen(c,c->argc-2);
722     for (j = 2; j < c->argc; j++) {
723         double score;
724         if (zsetScore(zobj, c->argv[j], &score) == C_ERR) {
725             addReply(c,shared.nullmultibulk);
726         } else {
727             /* Decode... */
728             double xy[2];
729             if (!decodeGeohash(score,xy)) {
730                 addReply(c,shared.nullmultibulk);
731                 continue;
732             }
733             addReplyMultiBulkLen(c,2);
734             addReplyHumanLongDouble(c,xy[0]);
735             addReplyHumanLongDouble(c,xy[1]);
736         }
737     }
738 }
739 
740 /* GEODIST key ele1 ele2 [unit]
741  *
742  * Return the distance, in meters by default, otherwise accordig to "unit",
743  * between points ele1 and ele2. If one or more elements are missing NULL
744  * is returned. */
745 void geodistCommand(client *c) {
746     double to_meter = 1;
747 
748     /* Check if there is the unit to extract, otherwise assume meters. */
749     if (c->argc == 5) {
750         to_meter = extractUnitOrReply(c,c->argv[4]);
751         if (to_meter < 0) return;
752     } else if (c->argc > 5) {
753         addReply(c,shared.syntaxerr);
754         return;
755     }
756 
757     /* Look up the requested zset */
758     robj *zobj = NULL;
759     if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptybulk))
760         == NULL || checkType(c, zobj, OBJ_ZSET)) return;
761 
762     /* Get the scores. We need both otherwise NULL is returned. */
763     double score1, score2, xyxy[4];
764     if (zsetScore(zobj, c->argv[2], &score1) == C_ERR ||
765         zsetScore(zobj, c->argv[3], &score2) == C_ERR)
766     {
767         addReply(c,shared.nullbulk);
768         return;
769     }
770 
771     /* Decode & compute the distance. */
772     if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
773         addReply(c,shared.nullbulk);
774     else
775         addReplyDoubleDistance(c,
776             geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
777 }
778