xref: /redis-3.2.3/src/geo.c (revision 5b5e6520)
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     if (distance < 0) {
160         addReplyError(c,"radius cannot be negative");
161         return -1;
162     }
163 
164     double to_meters = extractUnitOrReply(c,argv[1]);
165     if (to_meters < 0) {
166         return -1;
167     }
168 
169     if (conversion) *conversion = to_meters;
170     return distance * to_meters;
171 }
172 
173 /* The default addReplyDouble has too much accuracy.  We use this
174  * for returning location distances. "5.2145 meters away" is nicer
175  * than "5.2144992818115 meters away." We provide 4 digits after the dot
176  * so that the returned value is decently accurate even when the unit is
177  * the kilometer. */
178 void addReplyDoubleDistance(client *c, double d) {
179     char dbuf[128];
180     int dlen = snprintf(dbuf, sizeof(dbuf), "%.4f", d);
181     addReplyBulkCBuffer(c, dbuf, dlen);
182 }
183 
184 /* Helper function for geoGetPointsInRange(): given a sorted set score
185  * representing a point, and another point (the center of our search) and
186  * a radius, appends this entry as a geoPoint into the specified geoArray
187  * only if the point is within the search area.
188  *
189  * returns C_OK if the point is included, or REIDS_ERR if it is outside. */
190 int geoAppendIfWithinRadius(geoArray *ga, double lon, double lat, double radius, double score, sds member) {
191     double distance, xy[2];
192 
193     if (!decodeGeohash(score,xy)) return C_ERR; /* Can't decode. */
194     /* Note that geohashGetDistanceIfInRadiusWGS84() takes arguments in
195      * reverse order: longitude first, latitude later. */
196     if (!geohashGetDistanceIfInRadiusWGS84(lon,lat, xy[0], xy[1],
197                                            radius, &distance))
198     {
199         return C_ERR;
200     }
201 
202     /* Append the new element. */
203     geoPoint *gp = geoArrayAppend(ga);
204     gp->longitude = xy[0];
205     gp->latitude = xy[1];
206     gp->dist = distance;
207     gp->member = member;
208     gp->score = score;
209     return C_OK;
210 }
211 
212 /* Query a Redis sorted set to extract all the elements between 'min' and
213  * 'max', appending them into the array of geoPoint structures 'gparray'.
214  * The command returns the number of elements added to the array.
215  *
216  * Elements which are farest than 'radius' from the specified 'x' and 'y'
217  * coordinates are not included.
218  *
219  * The ability of this function to append to an existing set of points is
220  * important for good performances because querying by radius is performed
221  * using multiple queries to the sorted set, that we later need to sort
222  * via qsort. Similarly we need to be able to reject points outside the search
223  * radius area ASAP in order to allocate and process more points than needed. */
224 int geoGetPointsInRange(robj *zobj, double min, double max, double lon, double lat, double radius, geoArray *ga) {
225     /* minex 0 = include min in range; maxex 1 = exclude max in range */
226     /* That's: min <= val < max */
227     zrangespec range = { .min = min, .max = max, .minex = 0, .maxex = 1 };
228     size_t origincount = ga->used;
229     sds member;
230 
231     if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
232         unsigned char *zl = zobj->ptr;
233         unsigned char *eptr, *sptr;
234         unsigned char *vstr = NULL;
235         unsigned int vlen = 0;
236         long long vlong = 0;
237         double score = 0;
238 
239         if ((eptr = zzlFirstInRange(zl, &range)) == NULL) {
240             /* Nothing exists starting at our min.  No results. */
241             return 0;
242         }
243 
244         sptr = ziplistNext(zl, eptr);
245         while (eptr) {
246             score = zzlGetScore(sptr);
247 
248             /* If we fell out of range, break. */
249             if (!zslValueLteMax(score, &range))
250                 break;
251 
252             /* We know the element exists. ziplistGet should always succeed */
253             ziplistGet(eptr, &vstr, &vlen, &vlong);
254             member = (vstr == NULL) ? sdsfromlonglong(vlong) :
255                                       sdsnewlen(vstr,vlen);
256             if (geoAppendIfWithinRadius(ga,lon,lat,radius,score,member)
257                 == C_ERR) sdsfree(member);
258             zzlNext(zl, &eptr, &sptr);
259         }
260     } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
261         zset *zs = zobj->ptr;
262         zskiplist *zsl = zs->zsl;
263         zskiplistNode *ln;
264 
265         if ((ln = zslFirstInRange(zsl, &range)) == NULL) {
266             /* Nothing exists starting at our min.  No results. */
267             return 0;
268         }
269 
270         while (ln) {
271             robj *o = ln->obj;
272             /* Abort when the node is no longer in range. */
273             if (!zslValueLteMax(ln->score, &range))
274                 break;
275 
276             member = (o->encoding == OBJ_ENCODING_INT) ?
277                         sdsfromlonglong((long)o->ptr) :
278                         sdsdup(o->ptr);
279             if (geoAppendIfWithinRadius(ga,lon,lat,radius,ln->score,member)
280                 == C_ERR) sdsfree(member);
281             ln = ln->level[0].forward;
282         }
283     }
284     return ga->used - origincount;
285 }
286 
287 /* Compute the sorted set scores min (inclusive), max (exclusive) we should
288  * query in order to retrieve all the elements inside the specified area
289  * 'hash'. The two scores are returned by reference in *min and *max. */
290 void scoresOfGeoHashBox(GeoHashBits hash, GeoHashFix52Bits *min, GeoHashFix52Bits *max) {
291     /* We want to compute the sorted set scores that will include all the
292      * elements inside the specified Geohash 'hash', which has as many
293      * bits as specified by hash.step * 2.
294      *
295      * So if step is, for example, 3, and the hash value in binary
296      * is 101010, since our score is 52 bits we want every element which
297      * is in binary: 101010?????????????????????????????????????????????
298      * Where ? can be 0 or 1.
299      *
300      * To get the min score we just use the initial hash value left
301      * shifted enough to get the 52 bit value. Later we increment the
302      * 6 bit prefis (see the hash.bits++ statement), and get the new
303      * prefix: 101011, which we align again to 52 bits to get the maximum
304      * value (which is excluded from the search). So we get everything
305      * between the two following scores (represented in binary):
306      *
307      * 1010100000000000000000000000000000000000000000000000 (included)
308      * and
309      * 1010110000000000000000000000000000000000000000000000 (excluded).
310      */
311     *min = geohashAlign52Bits(hash);
312     hash.bits++;
313     *max = geohashAlign52Bits(hash);
314 }
315 
316 /* Obtain all members between the min/max of this geohash bounding box.
317  * Populate a geoArray of GeoPoints by calling geoGetPointsInRange().
318  * Return the number of points added to the array. */
319 int membersOfGeoHashBox(robj *zobj, GeoHashBits hash, geoArray *ga, double lon, double lat, double radius) {
320     GeoHashFix52Bits min, max;
321 
322     scoresOfGeoHashBox(hash,&min,&max);
323     return geoGetPointsInRange(zobj, min, max, lon, lat, radius, ga);
324 }
325 
326 /* Search all eight neighbors + self geohash box */
327 int membersOfAllNeighbors(robj *zobj, GeoHashRadius n, double lon, double lat, double radius, geoArray *ga) {
328     GeoHashBits neighbors[9];
329     unsigned int i, count = 0, last_processed = 0;
330 
331     neighbors[0] = n.hash;
332     neighbors[1] = n.neighbors.north;
333     neighbors[2] = n.neighbors.south;
334     neighbors[3] = n.neighbors.east;
335     neighbors[4] = n.neighbors.west;
336     neighbors[5] = n.neighbors.north_east;
337     neighbors[6] = n.neighbors.north_west;
338     neighbors[7] = n.neighbors.south_east;
339     neighbors[8] = n.neighbors.south_west;
340 
341     /* For each neighbor (*and* our own hashbox), get all the matching
342      * members and add them to the potential result list. */
343     for (i = 0; i < sizeof(neighbors) / sizeof(*neighbors); i++) {
344         if (HASHISZERO(neighbors[i]))
345             continue;
346 
347         /* When a huge Radius (in the 5000 km range or more) is used,
348          * adjacent neighbors can be the same, leading to duplicated
349          * elements. Skip every range which is the same as the one
350          * processed previously. */
351         if (last_processed &&
352             neighbors[i].bits == neighbors[last_processed].bits &&
353             neighbors[i].step == neighbors[last_processed].step)
354             continue;
355         count += membersOfGeoHashBox(zobj, neighbors[i], ga, lon, lat, radius);
356         last_processed = i;
357     }
358     return count;
359 }
360 
361 /* Sort comparators for qsort() */
362 static int sort_gp_asc(const void *a, const void *b) {
363     const struct geoPoint *gpa = a, *gpb = b;
364     /* We can't do adist - bdist because they are doubles and
365      * the comparator returns an int. */
366     if (gpa->dist > gpb->dist)
367         return 1;
368     else if (gpa->dist == gpb->dist)
369         return 0;
370     else
371         return -1;
372 }
373 
374 static int sort_gp_desc(const void *a, const void *b) {
375     return -sort_gp_asc(a, b);
376 }
377 
378 /* ====================================================================
379  * Commands
380  * ==================================================================== */
381 
382 /* GEOADD key long lat name [long2 lat2 name2 ... longN latN nameN] */
383 void geoaddCommand(client *c) {
384     /* Check arguments number for sanity. */
385     if ((c->argc - 2) % 3 != 0) {
386         /* Need an odd number of arguments if we got this far... */
387         addReplyError(c, "syntax error. Try GEOADD key [x1] [y1] [name1] "
388                          "[x2] [y2] [name2] ... ");
389         return;
390     }
391 
392     int elements = (c->argc - 2) / 3;
393     int argc = 2+elements*2; /* ZADD key score ele ... */
394     robj **argv = zcalloc(argc*sizeof(robj*));
395     argv[0] = createRawStringObject("zadd",4);
396     argv[1] = c->argv[1]; /* key */
397     incrRefCount(argv[1]);
398 
399     /* Create the argument vector to call ZADD in order to add all
400      * the score,value pairs to the requested zset, where score is actually
401      * an encoded version of lat,long. */
402     int i;
403     for (i = 0; i < elements; i++) {
404         double xy[2];
405 
406         if (extractLongLatOrReply(c, (c->argv+2)+(i*3),xy) == C_ERR) {
407             for (i = 0; i < argc; i++)
408                 if (argv[i]) decrRefCount(argv[i]);
409             zfree(argv);
410             return;
411         }
412 
413         /* Turn the coordinates into the score of the element. */
414         GeoHashBits hash;
415         geohashEncodeWGS84(xy[0], xy[1], GEO_STEP_MAX, &hash);
416         GeoHashFix52Bits bits = geohashAlign52Bits(hash);
417         robj *score = createObject(OBJ_STRING, sdsfromlonglong(bits));
418         robj *val = c->argv[2 + i * 3 + 2];
419         argv[2+i*2] = score;
420         argv[3+i*2] = val;
421         incrRefCount(val);
422     }
423 
424     /* Finally call ZADD that will do the work for us. */
425     replaceClientCommandVector(c,argc,argv);
426     zaddCommand(c);
427 }
428 
429 #define SORT_NONE 0
430 #define SORT_ASC 1
431 #define SORT_DESC 2
432 
433 #define RADIUS_COORDS 1
434 #define RADIUS_MEMBER 2
435 
436 /* GEORADIUS key x y radius unit [WITHDIST] [WITHHASH] [WITHCOORD] [ASC|DESC]
437  *                               [COUNT count] [STORE key] [STOREDIST key]
438  * GEORADIUSBYMEMBER key member radius unit ... options ... */
439 void georadiusGeneric(client *c, int type) {
440     robj *key = c->argv[1];
441     robj *storekey = NULL;
442     int storedist = 0; /* 0 for STORE, 1 for STOREDIST. */
443 
444     /* Look up the requested zset */
445     robj *zobj = NULL;
446     if ((zobj = lookupKeyReadOrReply(c, key, shared.emptymultibulk)) == NULL ||
447         checkType(c, zobj, OBJ_ZSET)) {
448         return;
449     }
450 
451     /* Find long/lat to use for radius search based on inquiry type */
452     int base_args;
453     double xy[2] = { 0 };
454     if (type == RADIUS_COORDS) {
455         base_args = 6;
456         if (extractLongLatOrReply(c, c->argv + 2, xy) == C_ERR)
457             return;
458     } else if (type == RADIUS_MEMBER) {
459         base_args = 5;
460         robj *member = c->argv[2];
461         if (longLatFromMember(zobj, member, xy) == C_ERR) {
462             addReplyError(c, "could not decode requested zset member");
463             return;
464         }
465     } else {
466         addReplyError(c, "unknown georadius search type");
467         return;
468     }
469 
470     /* Extract radius and units from arguments */
471     double radius_meters = 0, conversion = 1;
472     if ((radius_meters = extractDistanceOrReply(c, c->argv + base_args - 2,
473                                                 &conversion)) < 0) {
474         return;
475     }
476 
477     /* Discover and populate all optional parameters. */
478     int withdist = 0, withhash = 0, withcoords = 0;
479     int sort = SORT_NONE;
480     long long count = 0;
481     if (c->argc > base_args) {
482         int remaining = c->argc - base_args;
483         for (int i = 0; i < remaining; i++) {
484             char *arg = c->argv[base_args + i]->ptr;
485             if (!strcasecmp(arg, "withdist")) {
486                 withdist = 1;
487             } else if (!strcasecmp(arg, "withhash")) {
488                 withhash = 1;
489             } else if (!strcasecmp(arg, "withcoord")) {
490                 withcoords = 1;
491             } else if (!strcasecmp(arg, "asc")) {
492                 sort = SORT_ASC;
493             } else if (!strcasecmp(arg, "desc")) {
494                 sort = SORT_DESC;
495             } else if (!strcasecmp(arg, "count") && (i+1) < remaining) {
496                 if (getLongLongFromObjectOrReply(c, c->argv[base_args+i+1],
497                     &count, NULL) != C_OK) return;
498                 if (count <= 0) {
499                     addReplyError(c,"COUNT must be > 0");
500                     return;
501                 }
502                 i++;
503             } else if (!strcasecmp(arg, "store") && (i+1) < remaining) {
504                 storekey = c->argv[base_args+i+1];
505                 storedist = 0;
506                 i++;
507             } else if (!strcasecmp(arg, "storedist") && (i+1) < remaining) {
508                 storekey = c->argv[base_args+i+1];
509                 storedist = 1;
510                 i++;
511             } else {
512                 addReply(c, shared.syntaxerr);
513                 return;
514             }
515         }
516     }
517 
518     /* Trap options not compatible with STORE and STOREDIST. */
519     if (storekey && (withdist || withhash || withcoords)) {
520         addReplyError(c,
521             "STORE option in GEORADIUS is not compatible with "
522             "WITHDIST, WITHHASH and WITHCOORDS options");
523         return;
524     }
525 
526     /* COUNT without ordering does not make much sense, force ASC
527      * ordering if COUNT was specified but no sorting was requested. */
528     if (count != 0 && sort == SORT_NONE) sort = SORT_ASC;
529 
530     /* Get all neighbor geohash boxes for our radius search */
531     GeoHashRadius georadius =
532         geohashGetAreasByRadiusWGS84(xy[0], xy[1], radius_meters);
533 
534     /* Search the zset for all matching points */
535     geoArray *ga = geoArrayCreate();
536     membersOfAllNeighbors(zobj, georadius, xy[0], xy[1], radius_meters, ga);
537 
538     /* If no matching results, the user gets an empty reply. */
539     if (ga->used == 0 && storekey == NULL) {
540         addReply(c, shared.emptymultibulk);
541         geoArrayFree(ga);
542         return;
543     }
544 
545     long result_length = ga->used;
546     long returned_items = (count == 0 || result_length < count) ?
547                           result_length : count;
548     long option_length = 0;
549 
550     /* Process [optional] requested sorting */
551     if (sort == SORT_ASC) {
552         qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_asc);
553     } else if (sort == SORT_DESC) {
554         qsort(ga->array, result_length, sizeof(geoPoint), sort_gp_desc);
555     }
556 
557     if (storekey == NULL) {
558         /* No target key, return results to user. */
559 
560         /* Our options are self-contained nested multibulk replies, so we
561          * only need to track how many of those nested replies we return. */
562         if (withdist)
563             option_length++;
564 
565         if (withcoords)
566             option_length++;
567 
568         if (withhash)
569             option_length++;
570 
571         /* The multibulk len we send is exactly result_length. The result is
572          * either all strings of just zset members  *or* a nested multi-bulk
573          * reply containing the zset member string _and_ all the additional
574          * options the user enabled for this request. */
575         addReplyMultiBulkLen(c, returned_items);
576 
577         /* Finally send results back to the caller */
578         int i;
579         for (i = 0; i < returned_items; i++) {
580             geoPoint *gp = ga->array+i;
581             gp->dist /= conversion; /* Fix according to unit. */
582 
583             /* If we have options in option_length, return each sub-result
584              * as a nested multi-bulk.  Add 1 to account for result value
585              * itself. */
586             if (option_length)
587                 addReplyMultiBulkLen(c, option_length + 1);
588 
589             addReplyBulkSds(c,gp->member);
590             gp->member = NULL;
591 
592             if (withdist)
593                 addReplyDoubleDistance(c, gp->dist);
594 
595             if (withhash)
596                 addReplyLongLong(c, gp->score);
597 
598             if (withcoords) {
599                 addReplyMultiBulkLen(c, 2);
600                 addReplyHumanLongDouble(c, gp->longitude);
601                 addReplyHumanLongDouble(c, gp->latitude);
602             }
603         }
604     } else {
605         /* Target key, create a sorted set with the results. */
606         robj *zobj;
607         zset *zs;
608         int i;
609         size_t maxelelen = 0;
610 
611         if (returned_items) {
612             zobj = createZsetObject();
613             zs = zobj->ptr;
614         }
615 
616         for (i = 0; i < returned_items; i++) {
617             zskiplistNode *znode;
618             geoPoint *gp = ga->array+i;
619             gp->dist /= conversion; /* Fix according to unit. */
620             double score = storedist ? gp->dist : gp->score;
621             size_t elelen = sdslen(gp->member);
622             robj *ele = createObject(OBJ_STRING,gp->member);
623 
624             if (maxelelen < elelen) maxelelen = elelen;
625             incrRefCount(ele); /* Set refcount to 2 since we reference the
626                                   object both in the skiplist and dict. */
627             znode = zslInsert(zs->zsl,score,ele);
628             serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
629             gp->member = NULL;
630         }
631 
632         if (returned_items) {
633             zsetConvertToZiplistIfNeeded(zobj,maxelelen);
634             setKey(c->db,storekey,zobj);
635             decrRefCount(zobj);
636             notifyKeyspaceEvent(NOTIFY_LIST,"georadiusstore",storekey,
637                                 c->db->id);
638             server.dirty += returned_items;
639         } else if (dbDelete(c->db,storekey)) {
640             signalModifiedKey(c->db,storekey);
641             notifyKeyspaceEvent(NOTIFY_GENERIC,"del",storekey,c->db->id);
642             server.dirty++;
643         }
644         addReplyLongLong(c, returned_items);
645     }
646     geoArrayFree(ga);
647 }
648 
649 /* GEORADIUS wrapper function. */
650 void georadiusCommand(client *c) {
651     georadiusGeneric(c, RADIUS_COORDS);
652 }
653 
654 /* GEORADIUSBYMEMBER wrapper function. */
655 void georadiusByMemberCommand(client *c) {
656     georadiusGeneric(c, RADIUS_MEMBER);
657 }
658 
659 /* GEOHASH key ele1 ele2 ... eleN
660  *
661  * Returns an array with an 11 characters geohash representation of the
662  * position of the specified elements. */
663 void geohashCommand(client *c) {
664     char *geoalphabet= "0123456789bcdefghjkmnpqrstuvwxyz";
665     int j;
666 
667     /* Look up the requested zset */
668     robj *zobj = NULL;
669     if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
670         == NULL || checkType(c, zobj, OBJ_ZSET)) return;
671 
672     /* Geohash elements one after the other, using a null bulk reply for
673      * missing elements. */
674     addReplyMultiBulkLen(c,c->argc-2);
675     for (j = 2; j < c->argc; j++) {
676         double score;
677         if (zsetScore(zobj, c->argv[j], &score) == C_ERR) {
678             addReply(c,shared.nullbulk);
679         } else {
680             /* The internal format we use for geocoding is a bit different
681              * than the standard, since we use as initial latitude range
682              * -85,85, while the normal geohashing algorithm uses -90,90.
683              * So we have to decode our position and re-encode using the
684              * standard ranges in order to output a valid geohash string. */
685 
686             /* Decode... */
687             double xy[2];
688             if (!decodeGeohash(score,xy)) {
689                 addReply(c,shared.nullbulk);
690                 continue;
691             }
692 
693             /* Re-encode */
694             GeoHashRange r[2];
695             GeoHashBits hash;
696             r[0].min = -180;
697             r[0].max = 180;
698             r[1].min = -90;
699             r[1].max = 90;
700             geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
701 
702             char buf[12];
703             int i;
704             for (i = 0; i < 11; i++) {
705                 int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
706                 buf[i] = geoalphabet[idx];
707             }
708             buf[11] = '\0';
709             addReplyBulkCBuffer(c,buf,11);
710         }
711     }
712 }
713 
714 /* GEOPOS key ele1 ele2 ... eleN
715  *
716  * Returns an array of two-items arrays representing the x,y position of each
717  * element specified in the arguments. For missing elements NULL is returned. */
718 void geoposCommand(client *c) {
719     int j;
720 
721     /* Look up the requested zset */
722     robj *zobj = NULL;
723     if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptymultibulk))
724         == NULL || checkType(c, zobj, OBJ_ZSET)) return;
725 
726     /* Report elements one after the other, using a null bulk reply for
727      * missing elements. */
728     addReplyMultiBulkLen(c,c->argc-2);
729     for (j = 2; j < c->argc; j++) {
730         double score;
731         if (zsetScore(zobj, c->argv[j], &score) == C_ERR) {
732             addReply(c,shared.nullmultibulk);
733         } else {
734             /* Decode... */
735             double xy[2];
736             if (!decodeGeohash(score,xy)) {
737                 addReply(c,shared.nullmultibulk);
738                 continue;
739             }
740             addReplyMultiBulkLen(c,2);
741             addReplyHumanLongDouble(c,xy[0]);
742             addReplyHumanLongDouble(c,xy[1]);
743         }
744     }
745 }
746 
747 /* GEODIST key ele1 ele2 [unit]
748  *
749  * Return the distance, in meters by default, otherwise accordig to "unit",
750  * between points ele1 and ele2. If one or more elements are missing NULL
751  * is returned. */
752 void geodistCommand(client *c) {
753     double to_meter = 1;
754 
755     /* Check if there is the unit to extract, otherwise assume meters. */
756     if (c->argc == 5) {
757         to_meter = extractUnitOrReply(c,c->argv[4]);
758         if (to_meter < 0) return;
759     } else if (c->argc > 5) {
760         addReply(c,shared.syntaxerr);
761         return;
762     }
763 
764     /* Look up the requested zset */
765     robj *zobj = NULL;
766     if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.emptybulk))
767         == NULL || checkType(c, zobj, OBJ_ZSET)) return;
768 
769     /* Get the scores. We need both otherwise NULL is returned. */
770     double score1, score2, xyxy[4];
771     if (zsetScore(zobj, c->argv[2], &score1) == C_ERR ||
772         zsetScore(zobj, c->argv[3], &score2) == C_ERR)
773     {
774         addReply(c,shared.nullbulk);
775         return;
776     }
777 
778     /* Decode & compute the distance. */
779     if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
780         addReply(c,shared.nullbulk);
781     else
782         addReplyDoubleDistance(c,
783             geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
784 }
785