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