Skip to content

Commit 8105431

Browse files
committed
geoip-lite#41 Reverse geocode feature request
1 parent 78d9161 commit 8105431

File tree

4 files changed

+240
-2
lines changed

4 files changed

+240
-2
lines changed

lib/geohash.js

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
var geohash = module.exports = {};
2+
3+
// geohash.js
4+
// Geohash library for Javascript
5+
// (c) 2008 David Troy
6+
// Distributed under the MIT License
7+
8+
BITS = [16, 8, 4, 2, 1];
9+
10+
BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz";
11+
NEIGHBORS = { right : { even : "bc01fg45238967deuvhjyznpkmstqrwx" },
12+
left : { even : "238967debc01fg45kmstqrwxuvhjyznp" },
13+
top : { even : "p0r21436x8zb9dcf5h7kjnmqesgutwvy" },
14+
bottom : { even : "14365h7k9dcfesgujnmqp0r2twvyx8zb" } };
15+
BORDERS = { right : { even : "bcfguvyz" },
16+
left : { even : "0145hjnp" },
17+
top : { even : "prxz" },
18+
bottom : { even : "028b" } };
19+
20+
NEIGHBORS.bottom.odd = NEIGHBORS.left.even;
21+
NEIGHBORS.top.odd = NEIGHBORS.right.even;
22+
NEIGHBORS.left.odd = NEIGHBORS.bottom.even;
23+
NEIGHBORS.right.odd = NEIGHBORS.top.even;
24+
25+
BORDERS.bottom.odd = BORDERS.left.even;
26+
BORDERS.top.odd = BORDERS.right.even;
27+
BORDERS.left.odd = BORDERS.bottom.even;
28+
BORDERS.right.odd = BORDERS.top.even;
29+
30+
geohash.refine_interval = function(interval, cd, mask) {
31+
if(cd & mask)
32+
interval[0] = (interval[0] + interval[1])/2;
33+
else
34+
interval[1] = (interval[0] + interval[1])/2;
35+
}
36+
37+
geohash.calculateAdjacent = function(srcHash, dir) {
38+
srcHash = srcHash.toLowerCase();
39+
var lastChr = srcHash.charAt(srcHash.length-1);
40+
var type = (srcHash.length % 2) ? 'odd' : 'even';
41+
var base = srcHash.substring(0,srcHash.length-1);
42+
if (BORDERS[dir][type].indexOf(lastChr)!=-1)
43+
base = geohash.calculateAdjacent(base, dir);
44+
return base + BASE32[NEIGHBORS[dir][type].indexOf(lastChr)];
45+
}
46+
47+
geohash.decodeGeoHash = function(hash) {
48+
var is_even = 1;
49+
var lat = []; var lon = [];
50+
lat[0] = -90.0; lat[1] = 90.0;
51+
lon[0] = -180.0; lon[1] = 180.0;
52+
lat_err = 90.0; lon_err = 180.0;
53+
54+
for (i=0; i<hash.length; i++) {
55+
c = hash[i];
56+
cd = BASE32.indexOf(c);
57+
for (j=0; j<5; j++) {
58+
mask = BITS[j];
59+
if (is_even) {
60+
lon_err /= 2;
61+
geohash.refine_interval(lon, cd, mask);
62+
} else {
63+
lat_err /= 2;
64+
geohash.refine_interval(lat, cd, mask);
65+
}
66+
is_even = !is_even;
67+
}
68+
}
69+
lat[2] = (lat[0] + lat[1])/2;
70+
lon[2] = (lon[0] + lon[1])/2;
71+
72+
return { latitude: lat, longitude: lon};
73+
}
74+
75+
geohash.encodeGeoHash = function(latitude, longitude) {
76+
var is_even=1;
77+
var i=0;
78+
var lat = []; var lon = [];
79+
var bit=0;
80+
var ch=0;
81+
var precision = 12;
82+
hash = "";
83+
84+
lat[0] = -90.0; lat[1] = 90.0;
85+
lon[0] = -180.0; lon[1] = 180.0;
86+
87+
while (hash.length < precision) {
88+
if (is_even) {
89+
mid = (lon[0] + lon[1]) / 2;
90+
if (longitude > mid) {
91+
ch |= BITS[bit];
92+
lon[0] = mid;
93+
} else
94+
lon[1] = mid;
95+
} else {
96+
mid = (lat[0] + lat[1]) / 2;
97+
if (latitude > mid) {
98+
ch |= BITS[bit];
99+
lat[0] = mid;
100+
} else
101+
lat[1] = mid;
102+
}
103+
104+
is_even = !is_even;
105+
if (bit < 4)
106+
bit++;
107+
else {
108+
hash += BASE32[ch];
109+
bit = 0;
110+
ch = 0;
111+
}
112+
}
113+
return hash;
114+
}

lib/geoip.js

+106
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
var fs = require('fs');
22
var net = require('net');
33
var path = require('path');
4+
var Triejs = require('triejs');
45

56
fs.existsSync = fs.existsSync || path.existsSync;
67

78
var utils = require('./utils');
9+
var geohash = require('./geohash');
810
var fsWatcher = require('./fsWatcher');
911
var async = require('async');
1012

@@ -51,6 +53,8 @@ var cache6 = {
5153
recordSize: 58
5254
};
5355

56+
var locationTrie = null;
57+
5458
var RECORD_SIZE = 10;
5559
var RECORD_SIZE6 = 34
5660

@@ -201,6 +205,64 @@ function lookup6(ip) {
201205
} while(1);
202206
}
203207

208+
function info(lat, lon) {
209+
var precision = 4; //found this value optimal
210+
211+
var hash = geohash.encodeGeoHash(lat, lon);
212+
213+
var boundary = hash.substring(0, precision);
214+
215+
var searches = [boundary, geohash.calculateAdjacent(boundary, 'top'),
216+
geohash.calculateAdjacent(boundary, 'bottom'),
217+
geohash.calculateAdjacent(boundary, 'right'),
218+
geohash.calculateAdjacent(boundary, 'left')];
219+
220+
var results = [];
221+
222+
for (var i = 0; i < searches.length; i++) {
223+
var result = locationTrie.find(searches[i]);
224+
225+
if(result != undefined) {
226+
results = results.concat(result);
227+
}
228+
}
229+
230+
var found = null;
231+
var foundDistance = 0;
232+
233+
if(results != undefined) {
234+
for(var i = 0; i < results.length; i++) {
235+
if(found == null) {
236+
found = results[i];
237+
foundDistance = utils.distance(lat, lon, found.ll[0], found.ll[1]);
238+
} else {
239+
var f = results[i];
240+
var d = utils.distance(lat, lon, f.ll[0], f.ll[1]);
241+
242+
if(d < foundDistance) {
243+
found = f;
244+
foundDistance = d;
245+
}
246+
}
247+
}
248+
249+
}
250+
251+
if(!found) {
252+
return null;
253+
}
254+
255+
var result = {
256+
country: found.country,
257+
city: found.city,
258+
region: found.region,
259+
ll: found.ll,
260+
distance: foundDistance,
261+
};
262+
263+
return result;
264+
}
265+
204266
function preload(callback) {
205267
var datFile;
206268
var datSize;
@@ -425,9 +487,49 @@ function preload6(callback) {
425487
}
426488
}
427489

490+
function preloadLocations(callback) {
491+
var locBuffer = cache4.locationBuffer;
492+
493+
var locRecordSize = cache4.locationRecordSize;
494+
var line;
495+
496+
locationTrie = new Triejs();
497+
498+
var locNumber = locBuffer.length / locRecordSize;
499+
500+
for(var locId = 0; locId < locNumber; locId++) {
501+
var ll = [locBuffer.readInt32BE((locId * locRecordSize) + 4) / 10000, locBuffer.readInt32BE((locId * locRecordSize) + 8) / 10000];
502+
503+
var hash = geohash.encodeGeoHash(ll[0], ll[1])
504+
505+
if(locationTrie.find(hash) == undefined) {
506+
var country = locBuffer.toString('utf8', (locId * locRecordSize) + 0, (locId * locRecordSize) + 2).replace(/\u0000.*/, '');
507+
var region = locBuffer.toString('utf8', (locId * locRecordSize) + 2, (locId * locRecordSize) + 4).replace(/\u0000.*/, '');
508+
var city = locBuffer.toString('utf8', (locId * locRecordSize) + 12, (locId * locRecordSize) + locRecordSize).replace(/\u0000.*/, '');
509+
510+
var l = {
511+
country: country,
512+
region: region,
513+
city: city,
514+
ll: ll,
515+
};
516+
517+
locationTrie.add(hash, l);
518+
}
519+
}
520+
521+
if (typeof arguments[0] === 'function') {
522+
//nothing to be done for now
523+
}
524+
}
525+
428526
module.exports = {
429527
cmp: utils.cmp,
430528

529+
info: function(lat, lon) {
530+
return info(lat, lon);
531+
},
532+
431533
lookup: function(ip) {
432534
if (!ip) {
433535
return null;
@@ -465,6 +567,9 @@ module.exports = {
465567
},
466568
function (cb) {
467569
preload6(cb);
570+
},
571+
function (cb) {
572+
preloadLocations(cb);
468573
}
469574
]);
470575
});
@@ -478,6 +583,7 @@ module.exports = {
478583

479584
preload();
480585
preload6();
586+
preloadLocations();
481587

482588
//lookup4 = gen_lookup('geoip-country.dat', 4);
483589
//lookup6 = gen_lookup('geoip-country6.dat', 16);

lib/utils.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,21 @@ utils.ntoa6 = function(n) {
9595
a = a.replace(/:$/, ']').replace(/:0+/g, ':').replace(/::+/, '::');
9696

9797
return a;
98-
};
98+
};
99+
100+
utils.toRad = function(v) {
101+
return v * Math.PI / 180;
102+
}
103+
104+
utils.distance = function(lat1, lon1, lat2, lon2) {
105+
var R = 6371; // km
106+
var dLat = utils.toRad(lat2-lat1);
107+
var dLon = utils.toRad(lon2-lon1);
108+
var lat1 = utils.toRad(lat1);
109+
var lat2 = utils.toRad(lat2);
110+
111+
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
112+
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
113+
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
114+
return R * c;
115+
}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"iconv-lite": "~0.2.11",
2222
"lazy": "~1.0.11",
2323
"rimraf": "~2.0.2",
24-
"unzip": "~0.0.4"
24+
"unzip": "~0.0.4",
25+
"triejs": "~0.1.5"
2526
},
2627
"devDependencies": {
2728
"nodeunit": "~0.7.4"

0 commit comments

Comments
 (0)