From 6d9c6a6fe7849f031ba179276b211de2c2a35328 Mon Sep 17 00:00:00 2001 From: Al Date: Tue, 3 Mar 2015 18:51:49 -0500 Subject: [PATCH] [utils] geohash --- src/geohash/geohash.c | 293 ++++++++++++++++++++++++++++++++++++++++++ src/geohash/geohash.h | 77 +++++++++++ 2 files changed, 370 insertions(+) create mode 100644 src/geohash/geohash.c create mode 100644 src/geohash/geohash.h diff --git a/src/geohash/geohash.c b/src/geohash/geohash.c new file mode 100644 index 00000000..7fed5952 --- /dev/null +++ b/src/geohash/geohash.c @@ -0,0 +1,293 @@ +/* + * geohash.c + * libgeohash + * + * Created by Derek Smith on 10/6/09. + * Copyright (c) 2010, SimpleGeo + * 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 the SimpleGeo 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 HOLDER 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" + +#include +#include +#include + +#define MAX_LAT 90.0 +#define MIN_LAT -90.0 + +#define MAX_LONG 180.0 +#define MIN_LONG -180.0 + +#define NORTH 0 +#define EAST 1 +#define SOUTH 2 +#define WEST 3 + +#define LENGTH_OF_DEGREE 111100 // meters + +typedef struct IntervalStruct { + + double high; + double low; + +} Interval; + + +/* Normal 32 characer map used for geohashing */ +static char char_map[32] = "0123456789bcdefghjkmnpqrstuvwxyz"; + +/* + * The follow character maps were created by Dave Troy and used in his Javascript Geohashing + * library. http://github.com/davetroy/geohash-js + */ +static char *even_neighbors[] = {"p0r21436x8zb9dcf5h7kjnmqesgutwvy", + "bc01fg45238967deuvhjyznpkmstqrwx", + "14365h7k9dcfesgujnmqp0r2twvyx8zb", + "238967debc01fg45kmstqrwxuvhjyznp" + }; + +static char *odd_neighbors[] = {"bc01fg45238967deuvhjyznpkmstqrwx", + "p0r21436x8zb9dcf5h7kjnmqesgutwvy", + "238967debc01fg45kmstqrwxuvhjyznp", + "14365h7k9dcfesgujnmqp0r2twvyx8zb" + }; + +static char *even_borders[] = {"prxz", "bcfguvyz", "028b", "0145hjnp"}; +static char *odd_borders[] = {"bcfguvyz", "prxz", "0145hjnp", "028b"}; + +unsigned int index_for_char(char c, char *string) { + + int index = -1; + int string_amount = strlen(string); + int i; + for(i = 0; i < string_amount; i++) { + + if(c == string[i]) { + + index = i; + break; + } + + } + + return index; +} + +char* get_neighbor(char *hash, int direction) { + + int hash_length = strlen(hash); + + char last_char = hash[hash_length - 1]; + + int is_odd = hash_length % 2; + char **border = is_odd ? odd_borders : even_borders; + char **neighbor = is_odd ? odd_neighbors : even_neighbors; + + char *base = malloc(sizeof(char) * 1); + base[0] = '\0'; + strncat(base, hash, hash_length - 1); + + if(index_for_char(last_char, border[direction]) != -1) + base = get_neighbor(base, direction); + + int neighbor_index = index_for_char(last_char, neighbor[direction]); + last_char = char_map[neighbor_index]; + + char *last_hash = malloc(sizeof(char) * 2); + last_hash[0] = last_char; + last_hash[1] = '\0'; + strcat(base, last_hash); + free(last_hash); + + return base; +} + +char* geohash_encode(double lat, double lng, int precision) { + + if(precision < 1 || precision > 12) + precision = 6; + + char* hash = NULL; + + if(lat <= 90.0 && lat >= -90.0 && lng <= 180.0 && lng >= -180.0) { + + hash = (char*)malloc(sizeof(char) * (precision + 1)); + hash[precision] = '\0'; + + precision *= 5.0; + + Interval lat_interval = {MAX_LAT, MIN_LAT}; + Interval lng_interval = {MAX_LONG, MIN_LONG}; + + Interval *interval; + double coord, mid; + int is_even = 1; + unsigned int hashChar = 0; + int i; + for(i = 1; i <= precision; i++) { + + if(is_even) { + + interval = &lng_interval; + coord = lng; + + } else { + + interval = &lat_interval; + coord = lat; + } + + mid = (interval->low + interval->high) / 2.0; + hashChar = hashChar << 1; + + if(coord > mid) { + + interval->low = mid; + hashChar |= 0x01; + + } else + interval->high = mid; + + if(!(i % 5)) { + + hash[(i - 1) / 5] = char_map[hashChar]; + hashChar = 0; + + } + + is_even = !is_even; + } + + + } + + return hash; +} + +GeoCoord geohash_decode(char *hash) { + + GeoCoord coordinate = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + + if(hash) { + + int char_amount = strlen(hash); + + if(char_amount) { + + unsigned int char_mapIndex; + Interval lat_interval = {MAX_LAT, MIN_LAT}; + Interval lng_interval = {MAX_LONG, MIN_LONG}; + Interval *interval; + + int is_even = 1; + double delta; + int i, j; + for(i = 0; i < char_amount; i++) { + + char_mapIndex = index_for_char(hash[i], (char*)char_map); + + if(char_mapIndex < 0) + break; + + // Interpret the last 5 bits of the integer + for(j = 0; j < 5; j++) { + + interval = is_even ? &lng_interval : &lat_interval; + + delta = (interval->high - interval->low) / 2.0; + + if((char_mapIndex << j) & 0x0010) + interval->low += delta; + else + interval->high -= delta; + + is_even = !is_even; + } + + } + + coordinate.latitude = lat_interval.high - ((lat_interval.high - lat_interval.low) / 2.0); + coordinate.longitude = lng_interval.high - ((lng_interval.high - lng_interval.low) / 2.0); + + coordinate.north = lat_interval.high; + coordinate.east = lng_interval.high; + coordinate.south = lat_interval.low; + coordinate.west = lng_interval.low; + } + } + + return coordinate; +} + + +char** geohash_neighbors(char *hash) { + + char** neighbors = NULL; + + if(hash) { + + // N, NE, E, SE, S, SW, W, NW + neighbors = (char**)malloc(sizeof(char*) * 8); + + neighbors[0] = get_neighbor(hash, NORTH); + neighbors[1] = get_neighbor(neighbors[0], EAST); + neighbors[2] = get_neighbor(hash, EAST); + neighbors[3] = get_neighbor(neighbors[2], SOUTH); + neighbors[4] = get_neighbor(hash, SOUTH); + neighbors[5] = get_neighbor(neighbors[4], WEST); + neighbors[6] = get_neighbor(hash, WEST); + neighbors[7] = get_neighbor(neighbors[6], NORTH); + + } + + return neighbors; +} + +GeoBoxDimension geohash_dimensions_for_precision(int precision) { + + GeoBoxDimension dimensions = {0.0, 0.0}; + + if(precision > 0) { + + int lat_times_to_cut = precision * 5 / 2; + int lng_times_to_cut = precision * 5 / 2 + (precision % 2 ? 1 : 0); + + double width = 360.0; + double height = 180.0; + + int i; + for(i = 0; i < lat_times_to_cut; i++) + height /= 2.0; + + for(i = 0; i < lng_times_to_cut; i++) + width /= 2.0; + + dimensions.width = width; + dimensions.height = height; + + } + + return dimensions; +} diff --git a/src/geohash/geohash.h b/src/geohash/geohash.h new file mode 100644 index 00000000..bb81039a --- /dev/null +++ b/src/geohash/geohash.h @@ -0,0 +1,77 @@ +/* + * geohash.h + * libgeohash + * + * Created by Derek Smith on 10/6/09. + * Copyright (c) 2010, SimpleGeo + * 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 the SimpleGeo 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 HOLDER 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. + */ + +// Metric in meters +typedef struct GeoBoxDimensionStruct { + + double height; + double width; + +} GeoBoxDimension; + +typedef struct GeoCoordStruct { + + double latitude; + double longitude; + + double north; + double east; + double south; + double west; + + GeoBoxDimension dimension; + +} GeoCoord; + +/* + * Creates a the hash at the specified precision. If precision is set to 0. + * or less than it defaults to 12. + */ +extern char* geohash_encode(double lat, double lng, int precision); + +/* + * Returns the latitude and longitude used to create the hash along with + * the bounding box for the encoded coordinate. + */ +extern GeoCoord geohash_decode(char* hash); + +/* + * Return an array of geohashes that represent the neighbors of the passed + * in value. The neighbors are indexed as followed: + * + * N, NE, E, SE, S, SW, W, NW + * 0, 1, 2, 3, 4, 5, 6, 7 + */ +extern char** geohash_neighbors(char* hash); + +/* + * Returns the width and height of a precision value. + */ +extern GeoBoxDimension geohash_dimensions_for_precision(int precision); \ No newline at end of file