2015-08-14 09:40:35 -07:00
|
|
|
/*
|
|
|
|
* 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 <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
#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
|
|
|
|
|
2021-08-19 05:43:14 -07:00
|
|
|
/*
|
|
|
|
* From Elasticsearch Guide:
|
|
|
|
* The highest-precision geohash of length 12 produces cells
|
|
|
|
* that cover less than a square metre of land.
|
|
|
|
*/
|
|
|
|
#define MAX_GEOHASH_LENGTH 12
|
|
|
|
|
2015-08-14 09:40:35 -07:00
|
|
|
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;
|
|
|
|
|
2021-08-20 00:52:24 -07:00
|
|
|
size_t geohash_b_size = MAX_GEOHASH_LENGTH + 1;
|
|
|
|
char *base = malloc(sizeof(char) * geohash_b_size);
|
|
|
|
snprintf(base, geohash_b_size, "%s", hash);
|
2015-08-14 09:40:35 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
//JPM always false if(char_mapIndex < 0)
|
|
|
|
//JPM 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;
|
|
|
|
}
|