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