mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-24 00:59:02 -05:00
0b93139048
This commit simplifies the implementation in a few ways: 1. zsetScore implementation improved a bit and moved into t_zset.c where is now also used to implement the ZSCORE command. 2. Range extraction from the sorted set remains a separated implementation from the one in t_zset.c, but was hyper-specialized in order to avoid accumulating results into a list and remove the ones outside the radius. 3. A new type is introduced: geoArray, which can accumulate geoPoint structures in a vector with power of two expansion policy. This is useful since we have to call qsort() against it before returning the result to the user. 4. As a result of 1, 2, 3, the two files zset.c and zset.h are now removed, including the function to merge two lists (now handled with functions that can add elements to existing geoArray arrays) and the machinery used in order to pass zset results. 5. geoPoint structure simplified because of the general code structure simplification, so we no longer need to take references to objects. 6. Not counting the JSON removal the refactoring removes 200 lines of code for the same functionalities, with a simpler to read implementation. 7. GEORADIUS is now 2.5 times faster testing with 10k elements and a radius resulting in 124 elements returned. However this is mostly a side effect of the refactoring and simplification. More speed gains can be achieved by trying to optimize the code.
258 lines
8.1 KiB
C
258 lines
8.1 KiB
C
/*
|
|
* Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
|
|
* Copyright (c) 2014, Matt Stancliff <matt@genges.com>.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Redis nor the names of its contributors may be used
|
|
* to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#include "geohash.h"
|
|
|
|
/**
|
|
* Hashing works like this:
|
|
* Divide the world into 4 buckets. Label each one as such:
|
|
* -----------------
|
|
* | | |
|
|
* | | |
|
|
* | 0,1 | 1,1 |
|
|
* -----------------
|
|
* | | |
|
|
* | | |
|
|
* | 0,0 | 1,0 |
|
|
* -----------------
|
|
*/
|
|
|
|
void geohashGetCoordRange(GeoHashRange *lat_range, GeoHashRange *long_range) {
|
|
/* These are constraints from EPSG:900913 / EPSG:3785 / OSGEO:41001 */
|
|
/* We can't geocode at the north/south pole. */
|
|
lat_range->max = 85.05112878;
|
|
lat_range->min = -85.05112878;
|
|
long_range->max = 180.0;
|
|
long_range->min = -180.0;
|
|
}
|
|
|
|
int geohashEncode(GeoHashRange *lat_range, GeoHashRange *long_range,
|
|
double latitude, double longitude, uint8_t step,
|
|
GeoHashBits *hash) {
|
|
uint8_t i;
|
|
|
|
if (NULL == hash || step > 32 || step == 0 || RANGEPISZERO(lat_range) ||
|
|
RANGEPISZERO(long_range)) {
|
|
return 0;
|
|
}
|
|
|
|
hash->bits = 0;
|
|
hash->step = step;
|
|
|
|
if (latitude < lat_range->min || latitude > lat_range->max ||
|
|
longitude < long_range->min || longitude > long_range->max) {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < step; i++) {
|
|
uint8_t lat_bit, long_bit;
|
|
|
|
if (lat_range->max - latitude >= latitude - lat_range->min) {
|
|
lat_bit = 0;
|
|
lat_range->max = (lat_range->max + lat_range->min) / 2;
|
|
} else {
|
|
lat_bit = 1;
|
|
lat_range->min = (lat_range->max + lat_range->min) / 2;
|
|
}
|
|
if (long_range->max - longitude >= longitude - long_range->min) {
|
|
long_bit = 0;
|
|
long_range->max = (long_range->max + long_range->min) / 2;
|
|
} else {
|
|
long_bit = 1;
|
|
long_range->min = (long_range->max + long_range->min) / 2;
|
|
}
|
|
|
|
hash->bits <<= 1;
|
|
hash->bits += long_bit;
|
|
hash->bits <<= 1;
|
|
hash->bits += lat_bit;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int geohashEncodeType(double latitude, double longitude, uint8_t step, GeoHashBits *hash) {
|
|
GeoHashRange r[2] = { { 0 } };
|
|
geohashGetCoordRange(&r[0], &r[1]);
|
|
return geohashEncode(&r[0], &r[1], latitude, longitude, step, hash);
|
|
}
|
|
|
|
int geohashEncodeWGS84(double latitude, double longitude, uint8_t step,
|
|
GeoHashBits *hash) {
|
|
return geohashEncodeType(latitude, longitude, step, hash);
|
|
}
|
|
|
|
static inline uint8_t get_bit(uint64_t bits, uint8_t pos) {
|
|
return (bits >> pos) & 0x01;
|
|
}
|
|
|
|
int geohashDecode(const GeoHashRange lat_range, const GeoHashRange long_range,
|
|
const GeoHashBits hash, GeoHashArea *area) {
|
|
uint8_t i;
|
|
|
|
if (HASHISZERO(hash) || NULL == area || RANGEISZERO(lat_range) ||
|
|
RANGEISZERO(long_range)) {
|
|
return 0;
|
|
}
|
|
|
|
area->hash = hash;
|
|
area->latitude.min = lat_range.min;
|
|
area->latitude.max = lat_range.max;
|
|
area->longitude.min = long_range.min;
|
|
area->longitude.max = long_range.max;
|
|
|
|
for (i = 0; i < hash.step; i++) {
|
|
uint8_t lat_bit, long_bit;
|
|
|
|
long_bit = get_bit(hash.bits, (hash.step - i) * 2 - 1);
|
|
lat_bit = get_bit(hash.bits, (hash.step - i) * 2 - 2);
|
|
|
|
if (lat_bit == 0) {
|
|
area->latitude.max = (area->latitude.max + area->latitude.min) / 2;
|
|
} else {
|
|
area->latitude.min = (area->latitude.max + area->latitude.min) / 2;
|
|
}
|
|
|
|
if (long_bit == 0) {
|
|
area->longitude.max =
|
|
(area->longitude.max + area->longitude.min) / 2;
|
|
} else {
|
|
area->longitude.min =
|
|
(area->longitude.max + area->longitude.min) / 2;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int geohashDecodeType(const GeoHashBits hash, GeoHashArea *area) {
|
|
GeoHashRange r[2] = { { 0 } };
|
|
geohashGetCoordRange(&r[0], &r[1]);
|
|
return geohashDecode(r[0], r[1], hash, area);
|
|
}
|
|
|
|
int geohashDecodeWGS84(const GeoHashBits hash, GeoHashArea *area) {
|
|
return geohashDecodeType(hash, area);
|
|
}
|
|
|
|
int geohashDecodeAreaToLatLong(const GeoHashArea *area, double *latlong) {
|
|
double y, x;
|
|
|
|
if (!latlong) return 0;
|
|
|
|
y = (area->latitude.min + area->latitude.max) / 2;
|
|
x = (area->longitude.min + area->longitude.max) / 2;
|
|
|
|
latlong[0] = y;
|
|
latlong[1] = x;
|
|
return 1;
|
|
}
|
|
|
|
int geohashDecodeToLatLongType(const GeoHashBits hash, double *latlong) {
|
|
GeoHashArea area = { { 0 } };
|
|
if (!latlong || !geohashDecodeType(hash, &area))
|
|
return 0;
|
|
return geohashDecodeAreaToLatLong(&area, latlong);
|
|
}
|
|
|
|
int geohashDecodeToLatLongWGS84(const GeoHashBits hash, double *latlong) {
|
|
return geohashDecodeToLatLongType(hash, latlong);
|
|
}
|
|
|
|
static void geohash_move_x(GeoHashBits *hash, int8_t d) {
|
|
if (d == 0)
|
|
return;
|
|
|
|
uint64_t x = hash->bits & 0xaaaaaaaaaaaaaaaaLL;
|
|
uint64_t y = hash->bits & 0x5555555555555555LL;
|
|
|
|
uint64_t zz = 0x5555555555555555LL >> (64 - hash->step * 2);
|
|
|
|
if (d > 0) {
|
|
x = x + (zz + 1);
|
|
} else {
|
|
x = x | zz;
|
|
x = x - (zz + 1);
|
|
}
|
|
|
|
x &= (0xaaaaaaaaaaaaaaaaLL >> (64 - hash->step * 2));
|
|
hash->bits = (x | y);
|
|
}
|
|
|
|
static void geohash_move_y(GeoHashBits *hash, int8_t d) {
|
|
if (d == 0)
|
|
return;
|
|
|
|
uint64_t x = hash->bits & 0xaaaaaaaaaaaaaaaaLL;
|
|
uint64_t y = hash->bits & 0x5555555555555555LL;
|
|
|
|
uint64_t zz = 0xaaaaaaaaaaaaaaaaLL >> (64 - hash->step * 2);
|
|
if (d > 0) {
|
|
y = y + (zz + 1);
|
|
} else {
|
|
y = y | zz;
|
|
y = y - (zz + 1);
|
|
}
|
|
y &= (0x5555555555555555LL >> (64 - hash->step * 2));
|
|
hash->bits = (x | y);
|
|
}
|
|
|
|
void geohashNeighbors(const GeoHashBits *hash, GeoHashNeighbors *neighbors) {
|
|
neighbors->east = *hash;
|
|
neighbors->west = *hash;
|
|
neighbors->north = *hash;
|
|
neighbors->south = *hash;
|
|
neighbors->south_east = *hash;
|
|
neighbors->south_west = *hash;
|
|
neighbors->north_east = *hash;
|
|
neighbors->north_west = *hash;
|
|
|
|
geohash_move_x(&neighbors->east, 1);
|
|
geohash_move_y(&neighbors->east, 0);
|
|
|
|
geohash_move_x(&neighbors->west, -1);
|
|
geohash_move_y(&neighbors->west, 0);
|
|
|
|
geohash_move_x(&neighbors->south, 0);
|
|
geohash_move_y(&neighbors->south, -1);
|
|
|
|
geohash_move_x(&neighbors->north, 0);
|
|
geohash_move_y(&neighbors->north, 1);
|
|
|
|
geohash_move_x(&neighbors->north_west, -1);
|
|
geohash_move_y(&neighbors->north_west, 1);
|
|
|
|
geohash_move_x(&neighbors->north_east, 1);
|
|
geohash_move_y(&neighbors->north_east, 1);
|
|
|
|
geohash_move_x(&neighbors->south_east, 1);
|
|
geohash_move_y(&neighbors->south_east, -1);
|
|
|
|
geohash_move_x(&neighbors->south_west, -1);
|
|
geohash_move_y(&neighbors->south_west, -1);
|
|
}
|