├── .gitmodules ├── README.md ├── rockspecs ├── geo-1.0.0-1.rockspec ├── geo-1.1.0-1.rockspec └── geo-scm-1.rockspec ├── src ├── geo.c ├── geohash.c └── quadkeys.c └── test └── geohash_try.lua /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/lauxhlib"] 2 | path = deps/lauxhlib 3 | url = https://github.com/mah0x211/lauxhlib.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-geo 2 | 3 | geo location util. 4 | 5 | ## Installation 6 | 7 | ```sh 8 | luarocks install geo --from=http://mah0x211.github.io/rocks/ 9 | ``` 10 | 11 | ## API 12 | 13 | ### Encode 14 | 15 | #### hash, err = geo.encode( lat:number, lon:number, precision:uint ) 16 | 17 | returns the geohash encoded string from specified arguments. 18 | 19 | ```lua 20 | local geo = require('geo'); 21 | local precision = 16; 22 | local lat = 35.673343; 23 | local lon = 139.710388; 24 | local hash = geo.encode( lat, lon, precision ); 25 | 26 | print( hash ); -- 'xn76gnjurcpggseg' 27 | ``` 28 | 29 | **Parameters** 30 | 31 | - `lat`: number - the latitude value range must be the `-90` to `90`. 32 | - `lon`: number - the longitude value range must be the `-180` to `180`. 33 | - `precision`: uint - the hash string length range must be the `1` to `16`. 34 | 35 | **Returns** 36 | 37 | 1. `hash`: string. 38 | 2. `err`: string. 39 | 40 | 41 | ### Decode 42 | 43 | #### tbl, err = geo.decode( geohash:string ) 44 | 45 | returns table that included of latitude and longitude values from the geohash string. 46 | 47 | ```lua 48 | local inspect = require('util').inspect; 49 | local geo = require('geo'); 50 | local hash = 'xn76gnjurcpggseg'; 51 | local latlon = geo.decode( hash ); 52 | 53 | print( inspect( latlon ) ); 54 | --[[ 55 | { 56 | lat = 35.673342999935, 57 | lon = 139.71038800008 58 | } 59 | --]] 60 | ``` 61 | 62 | **Parameters** 63 | 64 | - `geohash`: string - geohash encoded string. 65 | 66 | **Returns** 67 | 68 | 1. `latlon`: table - `{ lat = latitude:number, lon = longitude:number }`. 69 | 2. `err`: string. 70 | -------------------------------------------------------------------------------- /rockspecs/geo-1.0.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "geo" 2 | version = "1.0.0-1" 3 | source = { 4 | url = "git://github.com/mah0x211/lua-geo.git", 5 | tag = "v1.0.0" 6 | } 7 | description = { 8 | summary = "geo location util", 9 | homepage = "https://github.com/mah0x211/lua-geo", 10 | license = "MIT/X11", 11 | maintainer = "Masatoshi Teruya" 12 | } 13 | dependencies = { 14 | "lua >= 5.1" 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | geo = "geo.c" 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /rockspecs/geo-1.1.0-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "geo" 2 | version = "1.1.0-1" 3 | source = { 4 | url = "git://github.com/mah0x211/lua-geo.git", 5 | tag = "v1.1.0" 6 | } 7 | description = { 8 | summary = "geo location util", 9 | homepage = "https://github.com/mah0x211/lua-geo", 10 | license = "MIT/X11", 11 | maintainer = "Masatoshi Teruya" 12 | } 13 | dependencies = { 14 | "lua >= 5.1" 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | geo = "geo.c" 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /rockspecs/geo-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "geo" 2 | version = "scm-1" 3 | source = { 4 | url = "git://github.com/mah0x211/lua-geo.git" 5 | } 6 | description = { 7 | summary = "geo location util", 8 | homepage = "https://github.com/mah0x211/lua-geo", 9 | license = "MIT/X11", 10 | maintainer = "Masatoshi Teruya" 11 | } 12 | dependencies = { 13 | "lua >= 5.1" 14 | } 15 | build = { 16 | type = "builtin", 17 | modules = { 18 | geo = "src/geo.c", 19 | ["geo.geohash"] = { 20 | incdirs = { "deps/lauxhlib" }, 21 | sources = { "src/geohash.c" } 22 | }, 23 | ["geo.quadkeys"] = { 24 | incdirs = { "deps/lauxhlib" }, 25 | sources = { "src/quadkeys.c" } 26 | }, 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/geo.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Masatoshi Teruya 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | * 23 | * range of latitude : -90 - 90 24 | * range of longitude : -180 - 180 25 | * 26 | * DMS(Degrees Minutes Seconds): hh:mm:ss.sss 27 | * range of latitude : hh: -89 - 89 28 | * range of longitude : hh: -179 - 179 29 | * latitude/longitude : mm: 0 - 59 30 | * : ss.sss: 0 - 59.999 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "lauxlib.h" 39 | #include "lualib.h" 40 | #include "lua.h" 41 | 42 | // helper macros for lua_State 43 | #define lstate_fn2tbl(L,k,v) do{ \ 44 | lua_pushstring(L,k); \ 45 | lua_pushcfunction(L,v); \ 46 | lua_rawset(L,-3); \ 47 | }while(0) 48 | 49 | #define lstate_num2tbl(L,k,v) do{ \ 50 | lua_pushstring(L,k); \ 51 | lua_pushnumber(L,v); \ 52 | lua_rawset(L,-3); \ 53 | }while(0) 54 | 55 | 56 | 57 | #define GEO_MAX_HASH_LEN 16 58 | #define GEO_MAX_PRECISION_RANGE 16 59 | #define GEO_IS_PRECISION_RANGE(p) ( p > 0 && p < 17 ) 60 | #define GEO_IS_LAT_RANGE(l) ( l >= -90 && l <= 90 ) 61 | #define GEO_IS_LON_RANGE(l) ( l >= -180 && l <= 180 ) 62 | #define GEO_IS_LATLON_RANGE(la,lo) \ 63 | ( GEO_IS_LAT_RANGE(la) && GEO_IS_LON_RANGE( la ) ) 64 | 65 | // semi-major axis of ellipse 66 | #define GEO_WGS84MAJOR 6378137.0 67 | // semi-minor axis of ellipse 68 | #define GEO_WGS84MINOR 6356752.314245 69 | 70 | // eccentricity 71 | // ( GEO_WGS84MAJOR^2 - GEO_WGS84MINOR^2 ) / GEO_WGS84MAJOR^2 = 0.006694379990197 72 | #define GEO_ECCENTRICITY 0.006694379990197 73 | 74 | // meridian numerator 75 | // = GEO_WGS84MAJOR * ( 1 - GEO_ECCENTRICITY ) 76 | // = 6378137.0 * 1- 0.006694379990197 77 | // = 6335439.327292464877011 78 | #define GEO_MERIDIAN_NUM 6335439.327292464877011 79 | 80 | // lat_ave = ( org_lat_rad + dst_lat_rad ) / 2; 81 | // W = sqrt( 1 - GEO_ECCENTRICITY * lat_ave_sin^2 ) 82 | // meridian curvature: GEO_MERIDIAN_NUM / pow( W, 3 ) 83 | #define GEO_MERIDIAN(w) (GEO_MERIDIAN_NUM/pow(w,3)) 84 | // prime vertical: GEO_WGS84MAJOR / W 85 | #define GEO_PRIME_VERT(w) (GEO_WGS84MAJOR/w) 86 | 87 | static const double GEO_RAD = M_PI/180; 88 | static const double GEO_DEG = 180/M_PI; 89 | static const double GEO_PI2 = M_PI*2; 90 | 91 | #define GEO_DEG2RAD(d) (d*GEO_RAD) 92 | #define GEO_RAD2DEG(r) (r/GEO_DEG) 93 | 94 | static const uint8_t GEO_BITMASK[5] = { 16, 8, 4, 2, 1 }; 95 | 96 | 97 | typedef struct { 98 | double lat; 99 | double lon; 100 | double lat_rad; 101 | double lon_rad; 102 | double lat_sin; 103 | double lat_cos; 104 | double lon_sin; 105 | double lon_cos; 106 | } geo_t; 107 | 108 | 109 | typedef struct { 110 | double lat; 111 | double lon; 112 | double lat_rad; 113 | double lon_rad; 114 | double dist; 115 | double dist_sin; 116 | double dist_cos; 117 | double angle; 118 | double angle_rad; 119 | geo_t *pivot; 120 | } geodest_t; 121 | 122 | 123 | static int geo_init( geo_t *geo, double lat, double lon, int with_math ) 124 | { 125 | if( GEO_IS_LATLON_RANGE( lat, lon ) ) 126 | { 127 | geo->lat = lat; 128 | geo->lon = lon; 129 | geo->lat_rad = GEO_DEG2RAD(lat); 130 | geo->lon_rad = GEO_DEG2RAD(lon); 131 | if( with_math ){ 132 | geo->lat_sin = sin( geo->lat_rad ); 133 | geo->lat_cos = cos( geo->lat_rad ); 134 | geo->lon_sin = sin( geo->lon_rad ); 135 | geo->lon_cos = cos( geo->lon_rad ); 136 | } 137 | return 0; 138 | } 139 | 140 | errno = EINVAL; 141 | return -1; 142 | } 143 | 144 | static int geo_init_by_tokyo( geo_t *geo, double lat, double lon, int with_math ) 145 | { 146 | return geo_init( geo, 147 | lon - lat * 0.000046038 - lon * 0.000083043 + 0.010040, 148 | lat - lat * 0.00010695 + lon * 0.000017464 + 0.0046017, 149 | with_math ); 150 | } 151 | 152 | // merter 153 | static double geo_get_distance( geo_t *from, geo_t *dest ) 154 | { 155 | double lat_ave = ( from->lat_rad + dest->lat_rad ) / 2; 156 | double W = sqrt( 1 - GEO_ECCENTRICITY * pow( sin( lat_ave ), 2 ) ); 157 | 158 | return sqrt( pow( ( from->lat_rad - dest->lat_rad ) * GEO_MERIDIAN(W), 2 ) + 159 | pow( ( from->lon_rad - dest->lon_rad ) * GEO_PRIME_VERT(W) * 160 | cos( lat_ave ), 2 ) ); 161 | } 162 | 163 | static void geo_dest_update( geodest_t *dest ) 164 | { 165 | double platc_ds = dest->pivot->lat_cos * dest->dist_sin; 166 | 167 | dest->lat_rad = asin( dest->pivot->lat_sin * dest->dist_cos + 168 | platc_ds * cos( dest->angle_rad ) ); 169 | dest->lon_rad = fmod( dest->pivot->lon_rad + 170 | atan2( platc_ds * sin( dest->angle_rad ), 171 | dest->dist_cos - 172 | pow( dest->pivot->lat_sin, 2 ) ) + 173 | M_PI, GEO_PI2 ) - M_PI; 174 | 175 | dest->lat = dest->lat_rad * GEO_DEG; 176 | dest->lon = dest->lon_rad * GEO_DEG; 177 | } 178 | 179 | static void geo_set_angle( geodest_t *dest, double angle, int update ) 180 | { 181 | dest->angle = angle; 182 | dest->angle_rad = angle * GEO_RAD; 183 | 184 | if( update ){ 185 | geo_dest_update( dest ); 186 | } 187 | } 188 | 189 | static void geo_set_distance( geodest_t *dest, double dist, int update ) 190 | { 191 | dest->dist = dist / GEO_WGS84MAJOR; 192 | dest->dist_sin = sin( dest->dist ); 193 | dest->dist_cos = cos( dest->dist ); 194 | 195 | if( update ){ 196 | geo_dest_update( dest ); 197 | } 198 | } 199 | 200 | static void geo_get_dest( geodest_t *dest, geo_t *from, double dist, double angle ) 201 | { 202 | dest->pivot = from; 203 | geo_set_distance( dest, dist, 0 ); 204 | geo_set_angle( dest, angle, 0 ); 205 | geo_dest_update( dest ); 206 | } 207 | 208 | 209 | static char *geo_hash_encode( char *hash, double lat, double lon, uint8_t precision ) 210 | { 211 | if( !GEO_IS_PRECISION_RANGE( precision ) || 212 | !GEO_IS_LATLON_RANGE( lat, lon ) ){ 213 | errno = EINVAL; 214 | return NULL; 215 | } 216 | else 217 | { 218 | static const char geobase32[] = "0123456789bcdefghjkmnpqrstuvwxyz"; 219 | double latlon[2][2] = { { -90.0, 90.0 }, { -180.0, 180.0 } }; 220 | double latlon_org[2] = { lat, lon }; 221 | int is_lon = 1; 222 | double mid; 223 | int bit = 0; 224 | uint8_t idx = 0; 225 | int i = 0; 226 | 227 | // precision: 1 - 16 228 | while( i < precision ) 229 | { 230 | mid = ( latlon[is_lon][0] + latlon[is_lon][1] ) / 2; 231 | if( latlon_org[is_lon] >= mid ){ 232 | idx |= GEO_BITMASK[bit]; 233 | latlon[is_lon][0] = mid; 234 | } 235 | else{ 236 | latlon[is_lon][1] = mid; 237 | } 238 | is_lon = !is_lon; 239 | 240 | if( bit < 4 ){ 241 | bit++; 242 | } 243 | else { 244 | hash[i++] = geobase32[idx]; 245 | bit = 0; 246 | idx = 0; 247 | } 248 | } 249 | hash[i] = '\0'; 250 | } 251 | 252 | return hash; 253 | } 254 | 255 | 256 | static int geo_hash_decode( const char *hash, size_t len, double *lat, double *lon ) 257 | { 258 | if( GEO_IS_PRECISION_RANGE( len ) ) 259 | { 260 | static const unsigned char geohash32code[256] = { 261 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 262 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 263 | 0, 0, 0, 0, 264 | // 0 1 2 3 4 5 6 7 8 9 265 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 266 | 0, 0, 0, 0, 0, 0, 0, 0, 267 | // B C D E F G H J K M N P Q R S 268 | 11, 12, 13, 14, 15, 16, 17, 0, 18, 19, 0, 20, 21, 0, 22, 23, 24, 25, 269 | // T U V W X Y Z 270 | 26, 27, 28, 29, 30, 31, 32, 271 | 0 272 | }; 273 | const unsigned char *code = (const unsigned char*)hash; 274 | double latlon[2][2] = { { -90.0, 90.0 }, { -180.0, 180.0 } }; 275 | uint8_t c; 276 | int is_lon = 1; 277 | int mask = 0; 278 | size_t i, j; 279 | 280 | for( i = 0; i < len; i++ ) 281 | { 282 | // to uppercase 283 | c = ( code[i] > '`' && code[i] < '{' ) ? code[i] - 32 : code[i]; 284 | c = geohash32code[c]; 285 | // invalid charcode 286 | if( !c ){ 287 | errno = EINVAL; 288 | return -1; 289 | } 290 | // decode 291 | for( j = 0; j < 5; j++ ){ 292 | mask = ( c - 1 ) & GEO_BITMASK[j]; 293 | latlon[is_lon][!mask] = ( latlon[is_lon][0] + latlon[is_lon][1] ) / 2; 294 | is_lon = !is_lon; 295 | } 296 | } 297 | 298 | *lat = ( latlon[0][0] + latlon[0][1] ) / 2; 299 | *lon = ( latlon[1][0] + latlon[1][1] ) / 2; 300 | 301 | return 0; 302 | } 303 | else { 304 | errno = EOVERFLOW; 305 | } 306 | 307 | return -1; 308 | } 309 | 310 | 311 | // MARK: lua binding 312 | static int encode_lua( lua_State *L ) 313 | { 314 | int rc = 0; 315 | double lat = luaL_checknumber( L, 1 ); 316 | double lon = luaL_checknumber( L, 2 ); 317 | uint8_t precision = (uint8_t)luaL_checknumber( L, 3 ); 318 | char hash[GEO_MAX_HASH_LEN+1] = {0}; 319 | 320 | // encode 321 | if( geo_hash_encode( hash, lat, lon, precision ) ){ 322 | lua_pushstring( L, hash ); 323 | return 1; 324 | } 325 | 326 | // got error 327 | lua_pushnil( L ); 328 | lua_pushstring( L, strerror( errno ) ); 329 | 330 | return 2; 331 | } 332 | 333 | 334 | static int decode_lua( lua_State *L ) 335 | { 336 | size_t len = 0; 337 | const char *hash = luaL_checklstring( L, 1, &len ); 338 | double lat = 0; 339 | double lon = 0; 340 | 341 | // decode 342 | if( geo_hash_decode( hash, len, &lat, &lon ) == 0 ){ 343 | lua_createtable( L, 0, 2 ); 344 | lstate_num2tbl( L, "lat", lat ); 345 | lstate_num2tbl( L, "lon", lon ); 346 | return 1; 347 | } 348 | 349 | // got error 350 | lua_pushnil( L ); 351 | lua_pushstring( L, strerror( errno ) ); 352 | 353 | return 2; 354 | } 355 | 356 | 357 | LUALIB_API int luaopen_geo( lua_State *L ) 358 | { 359 | static struct luaL_Reg method[] = { 360 | { "encode", encode_lua }, 361 | { "decode", decode_lua }, 362 | { NULL, NULL } 363 | }; 364 | struct luaL_Reg *ptr = method; 365 | 366 | lua_newtable( L ); 367 | while( ptr->name ){ 368 | lstate_fn2tbl( L, ptr->name, ptr->func ); 369 | ptr++; 370 | } 371 | 372 | return 1; 373 | } 374 | 375 | -------------------------------------------------------------------------------- /src/geohash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013-2017 Masatoshi Teruya 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | * 23 | * range of latitude : -90 - 90 24 | * range of longitude : -180 - 180 25 | * 26 | * DMS(Degrees Minutes Seconds): hh:mm:ss.sss 27 | * range of latitude : hh: -89 - 89 28 | * range of longitude : hh: -179 - 179 29 | * latitude/longitude : mm: 0 - 59 30 | * : ss.sss: 0 - 59.999 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "lauxhlib.h" 39 | 40 | 41 | #define GEO_MAX_HASH_LEN 16 42 | #define GEO_IS_PRECISION_RANGE(p) ( p > 0 && p < 17 ) 43 | #define GEO_IS_LAT_RANGE(l) ( l >= -90 && l <= 90 ) 44 | #define GEO_IS_LON_RANGE(l) ( l >= -180 && l <= 180 ) 45 | #define GEO_IS_LATLON_RANGE(la,lo) \ 46 | ( GEO_IS_LAT_RANGE(la) && GEO_IS_LON_RANGE( la ) ) 47 | 48 | 49 | static const uint8_t GEO_BITMASK[5] = { 16, 8, 4, 2, 1 }; 50 | 51 | 52 | static char *geo_hash_encode( char *hash, double lat, double lon, uint8_t precision ) 53 | { 54 | if( !GEO_IS_PRECISION_RANGE( precision ) || 55 | !GEO_IS_LATLON_RANGE( lat, lon ) ){ 56 | errno = EINVAL; 57 | return NULL; 58 | } 59 | else 60 | { 61 | static const char geobase32[] = "0123456789bcdefghjkmnpqrstuvwxyz"; 62 | double latlon[2][2] = { { -90.0, 90.0 }, { -180.0, 180.0 } }; 63 | double latlon_org[2] = { lat, lon }; 64 | int is_lon = 1; 65 | double mid; 66 | int bit = 0; 67 | uint8_t idx = 0; 68 | int i = 0; 69 | 70 | // precision: 1 - 16 71 | while( i < precision ) 72 | { 73 | mid = ( latlon[is_lon][0] + latlon[is_lon][1] ) / 2; 74 | if( latlon_org[is_lon] >= mid ){ 75 | idx |= GEO_BITMASK[bit]; 76 | latlon[is_lon][0] = mid; 77 | } 78 | else{ 79 | latlon[is_lon][1] = mid; 80 | } 81 | is_lon = !is_lon; 82 | 83 | if( bit < 4 ){ 84 | bit++; 85 | } 86 | else { 87 | hash[i++] = geobase32[idx]; 88 | bit = 0; 89 | idx = 0; 90 | } 91 | } 92 | hash[i] = '\0'; 93 | } 94 | 95 | return hash; 96 | } 97 | 98 | 99 | static int geo_hash_decode( const char *hash, size_t len, double *lat, double *lon ) 100 | { 101 | if( GEO_IS_PRECISION_RANGE( len ) ) 102 | { 103 | static const unsigned char geohash32code[256] = { 104 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106 | 0, 0, 0, 0, 107 | // 0 1 2 3 4 5 6 7 8 9 108 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 109 | 0, 0, 0, 0, 0, 0, 0, 0, 110 | // B C D E F G H J K M N P Q R S 111 | 11, 12, 13, 14, 15, 16, 17, 0, 18, 19, 0, 20, 21, 0, 22, 23, 24, 25, 112 | // T U V W X Y Z 113 | 26, 27, 28, 29, 30, 31, 32, 114 | 0 115 | }; 116 | const unsigned char *code = (const unsigned char*)hash; 117 | double latlon[2][2] = { { -90.0, 90.0 }, { -180.0, 180.0 } }; 118 | uint8_t c; 119 | int is_lon = 1; 120 | int mask = 0; 121 | size_t i, j; 122 | 123 | for( i = 0; i < len; i++ ) 124 | { 125 | // to uppercase 126 | c = ( code[i] > '`' && code[i] < '{' ) ? code[i] - 32 : code[i]; 127 | c = geohash32code[c]; 128 | // invalid charcode 129 | if( !c ){ 130 | errno = EINVAL; 131 | return -1; 132 | } 133 | // decode 134 | for( j = 0; j < 5; j++ ){ 135 | mask = ( c - 1 ) & GEO_BITMASK[j]; 136 | latlon[is_lon][!mask] = ( latlon[is_lon][0] + latlon[is_lon][1] ) / 2; 137 | is_lon = !is_lon; 138 | } 139 | } 140 | 141 | *lat = ( latlon[0][0] + latlon[0][1] ) / 2; 142 | *lon = ( latlon[1][0] + latlon[1][1] ) / 2; 143 | 144 | return 0; 145 | } 146 | else { 147 | errno = EOVERFLOW; 148 | } 149 | 150 | return -1; 151 | } 152 | 153 | 154 | // MARK: lua binding 155 | static int encode_lua( lua_State *L ) 156 | { 157 | int rc = 0; 158 | double lat = luaL_checknumber( L, 1 ); 159 | double lon = luaL_checknumber( L, 2 ); 160 | uint8_t precision = (uint8_t)luaL_checknumber( L, 3 ); 161 | char hash[GEO_MAX_HASH_LEN+1] = {0}; 162 | 163 | // encode 164 | if( geo_hash_encode( hash, lat, lon, precision ) ){ 165 | lua_pushstring( L, hash ); 166 | return 1; 167 | } 168 | 169 | // got error 170 | lua_pushnil( L ); 171 | lua_pushstring( L, strerror( errno ) ); 172 | 173 | return 2; 174 | } 175 | 176 | 177 | static int decode_lua( lua_State *L ) 178 | { 179 | size_t len = 0; 180 | const char *hash = luaL_checklstring( L, 1, &len ); 181 | double lat = 0; 182 | double lon = 0; 183 | 184 | // decode 185 | if( geo_hash_decode( hash, len, &lat, &lon ) == 0 ){ 186 | lua_pushnumber( L, lat ); 187 | lua_pushnumber( L, lon ); 188 | return 2; 189 | } 190 | 191 | // got error 192 | lua_pushnil( L ); 193 | lua_pushnil( L ); 194 | lua_pushstring( L, strerror( errno ) ); 195 | 196 | return 3; 197 | } 198 | 199 | 200 | LUALIB_API int luaopen_geo_geohash( lua_State *L ) 201 | { 202 | lua_createtable( L, 0, 2 ); 203 | lauxh_pushfn2tbl( L, "encode", encode_lua ); 204 | lauxh_pushfn2tbl( L, "decode", decode_lua ); 205 | 206 | return 1; 207 | } 208 | 209 | -------------------------------------------------------------------------------- /src/quadkeys.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 Masatoshi Teruya 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | * 22 | * src/quadkeys.c 23 | * lua-geo 24 | * Created by Masatoshi Teruya on 17/11/20. 25 | * 26 | */ 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | // lua 33 | #include "lauxhlib.h" 34 | 35 | 36 | #define LATITUDE_MIN -85.05112878 37 | #define LATITUDE_MAX 85.05112878 38 | #define LONGITUDE_MIN -180 39 | #define LONGITUDE_MAX 180 40 | 41 | 42 | // Clips a number to the specified minimum and maximum values. 43 | // n: The number to clip. 44 | // minValue: Minimum allowable value. 45 | // maxValue: Maximum allowable value. 46 | // returns: The clipped value. 47 | static inline double getclip( double n, double minValue, double maxValue ) 48 | { 49 | return fmin( fmax( n, minValue ), maxValue ); 50 | } 51 | 52 | 53 | // Determines the map width and height (in pixels) at a specified level 54 | // of detail. 55 | // lv: Level of detail, from 1 (lowest detail) to 23 (highest detail). 56 | // returns: The map width and height in pixels. 57 | static inline unsigned int getmapsize( int lv ) 58 | { 59 | return 256 << lv; 60 | } 61 | 62 | 63 | // Converts a point from latitude/longitude WGS-84 coordinates (in degrees) 64 | // into pixel XY coordinates at a specified level of detail. 65 | // lat: latitude of the point, in degrees. 66 | // lon: longitude of the point, in degrees. 67 | // lv: from 1 (lowest detail) to 23 (highest detail) 68 | // px: Output parameter receiving the X coordinate in pixels. 69 | // py: Output parameter receiving the Y coordinate in pixels. 70 | static void latlon2pixel( double lat, double lon, int lv, int *px, int *py ) 71 | { 72 | lat = getclip( lat, LATITUDE_MIN, LATITUDE_MAX ); 73 | lon = getclip( lon, LONGITUDE_MIN, LONGITUDE_MAX ); 74 | 75 | double x = ( lon + 180) / 360; 76 | double sinlat = sin( lat * M_PI / 180 ); 77 | double y = 0.5 - log( ( 1 + sinlat ) / ( 1 - sinlat ) ) / ( 4 * M_PI ); 78 | unsigned int mapsize = getmapsize( lv ); 79 | 80 | *px = (int)getclip( x * mapsize + 0.5, 0, mapsize - 1 ); 81 | *py = (int)getclip( y * mapsize + 0.5, 0, mapsize - 1 ); 82 | } 83 | 84 | 85 | // Converts a pixel from pixel XY coordinates at a specified level of detail 86 | // into latitude/longitude WGS-84 coordinates (in degrees). 87 | // px: X coordinate of the point, in pixels. 88 | // py: Y coordinates of the point, in pixels. 89 | // lv: Level of detail, from 1 (lowest detail) to 23 (highest detail). 90 | // lat: Output parameter receiving the latitude in degrees. 91 | // lon: Output parameter receiving the longitude in degrees. 92 | static void pixel2latlon( int px, int py, int lv, double *lat, double *lon ) 93 | { 94 | double mapSize = getmapsize( lv ); 95 | double x = ( getclip( px, 0, mapSize - 1 ) / mapSize ) - 0.5; 96 | double y = 0.5 - ( getclip( py, 0, mapSize - 1 ) / mapSize ); 97 | 98 | *lat = 90 - 360 * atan( exp( -y * 2 * M_PI ) ) / M_PI; 99 | *lon = 360 * x; 100 | } 101 | 102 | 103 | // Converts pixel XY coordinates into tile XY coordinates of the tile containing 104 | // the specified pixel. 105 | // px: Pixel X coordinate. 106 | // py: Pixel Y coordinate. 107 | // tx: Output parameter receiving the tile X coordinate. 108 | // ty: Output parameter receiving the tile Y coordinate. 109 | static void pixel2tile( int px, int py, int *tx, int *ty ) 110 | { 111 | *tx = px / 256; 112 | *ty = py / 256; 113 | } 114 | 115 | 116 | // Converts tile XY coordinates into pixel XY coordinates of the upper-left pixel 117 | // of the specified tile. 118 | // tx: Tile X coordinate. 119 | // ty: Tile Y coordinate. 120 | // px: Output parameter receiving the pixel X coordinate. 121 | // py: Output parameter receiving the pixel Y coordinate. 122 | static void tile2pixel( int tx, int ty, int *px, int *py ) 123 | { 124 | *px = tx * 256; 125 | *py = ty * 256; 126 | } 127 | 128 | 129 | // Converts tile XY coordinates into a QuadKey at a specified level of detail. 130 | // tx: Tile X coordinate. 131 | // ty: Tile Y coordinate. 132 | // lv: Level of detail, from 1 (lowest detail) to 23 (highest detail). 133 | // returns: A string containing the QuadKey. 134 | static int tile2quadkey( char *quadkeys, int tx, int ty, int lv ) 135 | { 136 | int i = lv; 137 | char digit = 0; 138 | int mask = 0; 139 | int len = 0; 140 | 141 | for(; i > 0; i-- ) 142 | { 143 | digit = '0'; 144 | mask = 1 << ( i - 1 ); 145 | if( ( tx & mask ) != 0 ){ 146 | digit++; 147 | } 148 | if( ( ty & mask ) != 0 ){ 149 | digit += 2; 150 | } 151 | 152 | quadkeys[len++] = digit; 153 | } 154 | 155 | return len; 156 | } 157 | 158 | 159 | static int encode_lua( lua_State *L ) 160 | { 161 | lua_Number lat = lauxh_checknumber( L, 1 ); 162 | lua_Number lon = lauxh_checknumber( L, 2 ); 163 | lua_Integer lv = lauxh_optinteger( L, 3, 23 ); 164 | char quadkey[23] = {0}; 165 | int len = 0; 166 | int px = 0; 167 | int py = 0; 168 | int tx = 0; 169 | int ty = 0; 170 | 171 | lauxh_argcheck( 172 | L, lv >= 1 && lv <= 23, 3, "1-23 expected, got an out of range value" 173 | ); 174 | 175 | // Converts a point from latitude/longitude WGS-84 coordinates (in degrees) 176 | latlon2pixel( lat, lon, lv, &px, &py ); 177 | // Converts pixel XY coordinates into tile XY coordinates of the tile 178 | // containing the specified pixel. 179 | pixel2tile( px, py, &tx, &ty ); 180 | // Converts tile XY coordinates into a QuadKey at a specified level of detail. 181 | len = tile2quadkey( quadkey, tx, ty, lv ); 182 | lua_pushlstring( L, quadkey, len ); 183 | 184 | return 1; 185 | } 186 | 187 | 188 | static int encode2tile_lua( lua_State *L ) 189 | { 190 | lua_Number lat = lauxh_checknumber( L, 1 ); 191 | lua_Number lon = lauxh_checknumber( L, 2 ); 192 | lua_Integer lv = lauxh_optinteger( L, 3, 23 ); 193 | int len = 0; 194 | int px = 0; 195 | int py = 0; 196 | int tx = 0; 197 | int ty = 0; 198 | 199 | lauxh_argcheck( 200 | L, lv >= 1 && lv <= 23, 3, "1-23 expected, got an out of range value" 201 | ); 202 | 203 | latlon2pixel( lat, lon, lv, &px, &py ); 204 | pixel2tile( px, py, &tx, &ty ); 205 | lua_pushnumber( L, tx ); 206 | lua_pushnumber( L, ty ); 207 | 208 | return 2; 209 | } 210 | 211 | 212 | // Converts a QuadKey into tile XY coordinates. 213 | // quadKey: QuadKey of the tile. 214 | // lv: Output parameter receiving the level of detail. 215 | // tx: Output parameter receiving the tile X coordinate. 216 | // ty: Output parameter receiving the tile Y coordinate. 217 | static int quadkey2tile( const char *quadKey, int lv, int *tx, int *ty ) 218 | { 219 | int i = lv; 220 | int mask = 0; 221 | 222 | *tx = *ty = 0; 223 | for(; i > 0; i-- ) 224 | { 225 | mask = 1 << ( i - 1 ); 226 | switch( quadKey[lv - i] ) 227 | { 228 | case '0': 229 | break; 230 | 231 | case '1': 232 | *tx |= mask; 233 | break; 234 | 235 | case '2': 236 | *ty |= mask; 237 | break; 238 | 239 | case '3': 240 | *tx |= mask; 241 | *ty |= mask; 242 | break; 243 | 244 | // Invalid QuadKey digit sequence 245 | default: 246 | return -1; 247 | } 248 | } 249 | 250 | return 0; 251 | } 252 | 253 | 254 | static int decode_lua( lua_State *L ) 255 | { 256 | size_t len = 0; 257 | const char *quadkeys = lauxh_checklstring( L, 1, &len ); 258 | int tx = 0; 259 | int ty = 0; 260 | 261 | lauxh_argcheck( 262 | L, len >= 1 && len <= 23, 1, 263 | "length between 1 and 23 expected, got an out of range value" 264 | ); 265 | 266 | if( quadkey2tile( quadkeys, (int)len, &tx, &ty ) == 0 ){ 267 | int px = 0; 268 | int py = 0; 269 | double lat = 0; 270 | double lon = 0; 271 | 272 | // Converts tile XY coordinates into pixel XY coordinates of the upper-left 273 | // pixel of the specified tile. 274 | tile2pixel( tx, ty, &px, &py ); 275 | // Converts a pixel from pixel XY coordinates at a specified level of detail 276 | // into latitude/longitude WGS-84 coordinates (in degrees). 277 | pixel2latlon( px, py, (int)len, &lat, &lon ); 278 | lua_pushnumber( L, lat ); 279 | lua_pushnumber( L, lon ); 280 | return 2; 281 | } 282 | 283 | // got error 284 | lua_pushnil( L ); 285 | lua_pushnil( L ); 286 | // invalid quadkey digit sequence 287 | lua_pushstring( L, strerror( EILSEQ ) ); 288 | 289 | return 3; 290 | } 291 | 292 | 293 | static int decode2tile_lua( lua_State *L ) 294 | { 295 | size_t len = 0; 296 | const char *quadkeys = lauxh_checklstring( L, 1, &len ); 297 | int x = 0; 298 | int y = 0; 299 | 300 | lauxh_argcheck( 301 | L, len >= 1 && len <= 23, 1, 302 | "length between 1 and 23 expected, got an out of range value" 303 | ); 304 | 305 | if( quadkey2tile( quadkeys, (int)len, &x, &y ) == 0 ){ 306 | lua_pushnumber( L, x ); 307 | lua_pushnumber( L, y ); 308 | return 2; 309 | } 310 | 311 | // got error 312 | lua_pushnil( L ); 313 | lua_pushnil( L ); 314 | // invalid quadkey digit sequence 315 | lua_pushstring( L, strerror( EILSEQ ) ); 316 | return 3; 317 | } 318 | 319 | 320 | static int tile2latlon_lua( lua_State *L ) 321 | { 322 | int x = lauxh_checkinteger( L, 1 ); 323 | int y = lauxh_checkinteger( L, 2 ); 324 | int lv = lauxh_checkinteger( L, 3 ); 325 | int px = 0; 326 | int py = 0; 327 | double lat = 0; 328 | double lon = 0; 329 | 330 | lauxh_argcheck( 331 | L, lv >= 1 && lv <= 23, 3, "1-23 expected, got an out of range value" 332 | ); 333 | 334 | tile2pixel( x, y, &px, &py ); 335 | pixel2latlon( px, py, lv, &lat, &lon ); 336 | lua_pushnumber( L, lat ); 337 | lua_pushnumber( L, lon ); 338 | 339 | return 2; 340 | } 341 | 342 | 343 | static int tile2key_lua( lua_State *L ) 344 | { 345 | int tx = lauxh_checkinteger( L, 1 ); 346 | int ty = lauxh_checkinteger( L, 2 ); 347 | lua_Integer lv = lauxh_optinteger( L, 3, 23 ); 348 | char quadkey[23] = {0}; 349 | int len = 0; 350 | 351 | lauxh_argcheck( 352 | L, lv >= 1 && lv <= 23, 3, "1-23 expected, got an out of range value" 353 | ); 354 | 355 | len = tile2quadkey( quadkey, tx, ty, lv ); 356 | lua_pushlstring( L, quadkey, len ); 357 | 358 | return 1; 359 | } 360 | 361 | LUALIB_API int luaopen_geo_quadkeys( lua_State *L ) 362 | { 363 | lua_createtable( L, 0, 3 ); 364 | lauxh_pushfn2tbl( L, "encode", encode_lua ); 365 | lauxh_pushfn2tbl( L, "encode2tile", encode2tile_lua ); 366 | lauxh_pushfn2tbl( L, "decode", decode_lua ); 367 | lauxh_pushfn2tbl( L, "decode2tile", decode2tile_lua ); 368 | lauxh_pushfn2tbl( L, "tile2latlon", tile2latlon_lua ); 369 | lauxh_pushfn2tbl( L, "tile2key", tile2key_lua ); 370 | 371 | return 1; 372 | } 373 | -------------------------------------------------------------------------------- /test/geohash_try.lua: -------------------------------------------------------------------------------- 1 | local floor = math.floor; 2 | local ceil = math.ceil; 3 | local geo = require('geo.geohash'); 4 | local precision = 16; 5 | local incr = 0.1; 6 | local LAT_MAX = 90.0; 7 | local LON_MAX = 180.0; 8 | local lat, lon, hash, dlat, dlon, err; 9 | 10 | for lat = -LAT_MAX, LAT_MAX, incr do 11 | for lon = -LON_MAX, LON_MAX, incr do 12 | hash = ifNil( geo.encode( lat, lon, precision ) ); 13 | dlat, dlon, err = geo.decode( hash ); 14 | ifNil( dlat, err ); 15 | -- resolve round-off error 16 | ifNotEqual( floor( dlat * 1000000 + .5 ), ceil( lat * 1000000 ) ); 17 | ifNotEqual( floor( dlon * 1000000 + .5 ), ceil( lon * 1000000 ) ); 18 | end 19 | end 20 | --------------------------------------------------------------------------------