geocoder.js.html 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>JSDoc: Source: geocoder.js</title>
  6. <script src="scripts/prettify/prettify.js"> </script>
  7. <script src="scripts/prettify/lang-css.js"> </script>
  8. <!--[if lt IE 9]>
  9. <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  10. <![endif]-->
  11. <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
  12. <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
  13. </head>
  14. <body>
  15. <div id="main">
  16. <h1 class="page-title">Source: geocoder.js</h1>
  17. <section>
  18. <article>
  19. <pre class="prettyprint source linenums"><code>/**
  20. * @fileoverview Local reverse geocoder based on GeoNames data.
  21. * @author Thomas Steiner (tomac@google.com)
  22. * @license Apache 2.0
  23. *
  24. * @param {(object|object[])} points One single or an array of
  25. * latitude/longitude pairs
  26. * @param {integer} maxResults The maximum number of results to return
  27. * @callback callback The callback function with the results
  28. *
  29. * @returns {object[]} An array of GeoNames-based geocode results
  30. *
  31. * @example
  32. * // With just one point
  33. * var point = {latitude: 42.083333, longitude: 3.1};
  34. * geocoder.lookUp(point, 1, function(err, res) {
  35. * console.log(JSON.stringify(res, null, 2));
  36. * });
  37. *
  38. * // In batch mode with many points
  39. * var points = [
  40. * {latitude: 42.083333, longitude: 3.1},
  41. * {latitude: 48.466667, longitude: 9.133333}
  42. * ];
  43. * geocoder.lookUp(points, 1, function(err, res) {
  44. * console.log(JSON.stringify(res, null, 2));
  45. * });
  46. */
  47. 'use strict';
  48. var debug = require('debug')('local-reverse-geocoder');
  49. var fs = require('fs');
  50. var path = require('path');
  51. var parse = require('csv-parse');
  52. var kdTree = require('kdt');
  53. var request = require('request');
  54. var unzip = require('node-unzip-2');
  55. var async = require('async');
  56. var readline = require('readline');
  57. // All data from http://download.geonames.org/export/dump/
  58. var GEONAMES_URL = 'http://download.geonames.org/export/dump/';
  59. var CITIES_FILE = 'cities1000';
  60. var ADMIN_1_CODES_FILE = 'admin1CodesASCII';
  61. var ADMIN_2_CODES_FILE = 'admin2Codes';
  62. var ALL_COUNTRIES_FILE = 'allCountries';
  63. var ALTERNATE_NAMES_FILE = 'alternateNames';
  64. /* jshint maxlen: false */
  65. var GEONAMES_COLUMNS = [
  66. 'geoNameId', // integer id of record in geonames database
  67. 'name', // name of geographical point (utf8) varchar(200)
  68. 'asciiName', // name of geographical point in plain ascii characters, varchar(200)
  69. 'alternateNames', // alternatenames, comma separated, ascii names automatically transliterated, convenience attribute from alternatename table, varchar(10000)
  70. 'latitude', // latitude in decimal degrees (wgs84)
  71. 'longitude', // longitude in decimal degrees (wgs84)
  72. 'featureClass', // see http://www.geonames.org/export/codes.html, char(1)
  73. 'featureCode', // see http://www.geonames.org/export/codes.html, varchar(10)
  74. 'countryCode', // ISO-3166 2-letter country code, 2 characters
  75. 'cc2', // alternate country codes, comma separated, ISO-3166 2-letter country code, 60 characters
  76. 'admin1Code', // fipscode (subject to change to iso code), see exceptions below, see file admin1Codes.txt for display names of this code; varchar(20)
  77. 'admin2Code', // code for the second administrative division, a county in the US, see file admin2Codes.txt; varchar(80)
  78. 'admin3Code', // code for third level administrative division, varchar(20)
  79. 'admin4Code', // code for fourth level administrative division, varchar(20)
  80. 'population', // bigint (8 byte int)
  81. 'elevation', // in meters, integer
  82. 'dem', // digital elevation model, srtm3 or gtopo30, average elevation 3''x3'' (ca 90mx90m) or 30''x30'' (ca 900mx900m) area in meters, integer. srtm processed by cgiar/ciat.
  83. 'timezone', // the timezone id (see file timeZone.txt) varchar(40)
  84. 'modificationDate', // date of last modification in yyyy-MM-dd format
  85. ];
  86. /* jshint maxlen: 80 */
  87. var GEONAMES_ADMIN_CODES_COLUMNS = [
  88. 'concatenatedCodes',
  89. 'name',
  90. 'asciiName',
  91. 'geoNameId'
  92. ];
  93. /* jshint maxlen: false */
  94. var GEONAMES_ALTERNATE_NAMES_COLUMNS = [
  95. 'alternateNameId', // the id of this alternate name, int
  96. 'geoNameId', // geonameId referring to id in table 'geoname', int
  97. 'isoLanguage', // iso 639 language code 2- or 3-characters; 4-characters 'post' for postal codes and 'iata','icao' and faac for airport codes, fr_1793 for French Revolution name
  98. 'alternateNames', // alternate name or name variant, varchar(200)
  99. 'isPreferrredName', // '1', if this alternate name is an official/preferred name
  100. 'isShortName', // '1', if this is a short name like 'California' for 'State of California'
  101. 'isColloquial', // '1', if this alternate name is a colloquial or slang term
  102. 'isHistoric' // '1', if this alternate name is historic and was used in the past
  103. ];
  104. /* jshint maxlen: 80 */
  105. var GEONAMES_DUMP = __dirname + '/geonames_dump';
  106. var geocoder = {
  107. _kdTree: null,
  108. _admin1Codes: null,
  109. _admin2Codes: null,
  110. _admin3Codes: null,
  111. _admin4Codes: null,
  112. _alternateNames: null,
  113. // Distance function taken from
  114. // http://www.movable-type.co.uk/scripts/latlong.html
  115. _distanceFunc: function distance(x, y) {
  116. var toRadians = function(num) {
  117. return num * Math.PI / 180;
  118. };
  119. var lat1 = x.latitude;
  120. var lon1 = x.longitude;
  121. var lat2 = y.latitude;
  122. var lon2 = y.longitude;
  123. var R = 6371; // km
  124. var φ1 = toRadians(lat1);
  125. var φ2 = toRadians(lat2);
  126. var Δφ = toRadians(lat2 - lat1);
  127. var Δλ = toRadians(lon2 - lon1);
  128. var a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
  129. Math.cos(φ1) * Math.cos(φ2) *
  130. Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
  131. var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  132. return R * c;
  133. },
  134. _getGeoNamesAlternateNamesData: function(callback) {
  135. var now = (new Date()).toISOString().substr(0, 10);
  136. // Use timestamped alternate names file OR bare alternate names file
  137. var timestampedFilename = GEONAMES_DUMP + '/alternate_names/' +
  138. ALTERNATE_NAMES_FILE + '_' + now + '.txt';
  139. if (fs.existsSync(timestampedFilename)) {
  140. debug('Using cached GeoNames alternate names data from ' +
  141. timestampedFilename);
  142. return callback(null, timestampedFilename);
  143. }
  144. var filename = GEONAMES_DUMP + '/alternate_names/' + ALTERNATE_NAMES_FILE +
  145. '.txt';
  146. if (fs.existsSync(filename)) {
  147. debug('Using cached GeoNames alternate names data from ' +
  148. filename);
  149. return callback(null, filename);
  150. }
  151. debug('Getting GeoNames alternate names data from ' +
  152. GEONAMES_URL + ALTERNATE_NAMES_FILE + '.zip (this may take a while)');
  153. var options = {
  154. url: GEONAMES_URL + ALTERNATE_NAMES_FILE + '.zip',
  155. encoding: null
  156. };
  157. request.get(options, function(err, response, body) {
  158. if (err || response.statusCode !== 200) {
  159. return callback('Error downloading GeoNames alternate names data' +
  160. (err ? ': ' + err : ''));
  161. }
  162. debug('Received zipped GeoNames alternate names data');
  163. // Store a dump locally
  164. if (!fs.existsSync(GEONAMES_DUMP + '/alternate_names')) {
  165. fs.mkdirSync(GEONAMES_DUMP + '/alternate_names');
  166. }
  167. var zipFilename = GEONAMES_DUMP + '/alternate_names/' +
  168. ALTERNATE_NAMES_FILE + '_' + now + '.zip';
  169. try {
  170. fs.writeFileSync(zipFilename, body);
  171. fs.createReadStream(zipFilename)
  172. .pipe(unzip.Extract({path: GEONAMES_DUMP + '/alternate_names'}))
  173. .on('error', function(e) {
  174. console.error(e);
  175. })
  176. .on('close', function() {
  177. fs.renameSync(filename, timestampedFilename);
  178. fs.unlinkSync(GEONAMES_DUMP + '/alternate_names/' +
  179. ALTERNATE_NAMES_FILE + '_' + now + '.zip');
  180. debug('Unzipped GeoNames alternate names data');
  181. // Housekeeping, remove old files
  182. var currentFileName = path.basename(timestampedFilename);
  183. fs.readdirSync(GEONAMES_DUMP + '/alternate_names').forEach(
  184. function(file) {
  185. if (file !== currentFileName) {
  186. fs.unlinkSync(GEONAMES_DUMP + '/alternate_names/' + file);
  187. }
  188. });
  189. return callback(null, timestampedFilename);
  190. });
  191. } catch (e) {
  192. debug('Warning: ' + e);
  193. return callback(null, timestampedFilename);
  194. }
  195. });
  196. },
  197. _parseGeoNamesAlternateNamesCsv: function(pathToCsv, callback) {
  198. var that = this;
  199. that._alternateNames = {};
  200. var lineReader = readline.createInterface({
  201. input: fs.createReadStream(pathToCsv)
  202. });
  203. lineReader.on('line', function(line) {
  204. line = line.split('\t');
  205. const [
  206. _,
  207. geoNameId,
  208. isoLanguage,
  209. altName,
  210. isPreferredName,
  211. isShortName,
  212. isColloquial,
  213. isHistoric
  214. ] = line;
  215. if (isoLanguage === '') {
  216. // consider data without country code as invalid
  217. return;
  218. }
  219. if (!that._alternateNames[geoNameId]) {
  220. that._alternateNames[geoNameId] = {};
  221. }
  222. that._alternateNames[geoNameId][isoLanguage] = {
  223. altName,
  224. isPreferredName: Boolean(isPreferredName),
  225. isShortName: Boolean(isShortName),
  226. isColloquial: Boolean(isColloquial),
  227. isHistoric: Boolean(isHistoric)
  228. };
  229. });
  230. lineReader.on('close', function() {
  231. return callback();
  232. });
  233. },
  234. _getGeoNamesAdmin1CodesData: function(callback) {
  235. var now = (new Date()).toISOString().substr(0, 10);
  236. var timestampedFilename = GEONAMES_DUMP + '/admin1_codes/' +
  237. ADMIN_1_CODES_FILE + '_' + now + '.txt';
  238. if (fs.existsSync(timestampedFilename)) {
  239. debug('Using cached GeoNames admin 1 codes data from ' +
  240. timestampedFilename);
  241. return callback(null, timestampedFilename);
  242. }
  243. var filename = GEONAMES_DUMP + '/admin1_codes/' + ADMIN_1_CODES_FILE +
  244. '.txt';
  245. if (fs.existsSync(filename)) {
  246. debug('Using cached GeoNames admin 1 codes data from ' +
  247. filename);
  248. return callback(null, filename);
  249. }
  250. debug('Getting GeoNames admin 1 codes data from ' +
  251. GEONAMES_URL + ADMIN_1_CODES_FILE + '.txt (this may take a while)');
  252. var url = GEONAMES_URL + ADMIN_1_CODES_FILE + '.txt';
  253. request.get(url, function(err, response, body) {
  254. if (err || response.statusCode !== 200) {
  255. return callback('Error downloading GeoNames admin 1 codes data' +
  256. (err ? ': ' + err : ''));
  257. }
  258. // Store a dump locally
  259. if (!fs.existsSync(GEONAMES_DUMP + '/admin1_codes')) {
  260. fs.mkdirSync(GEONAMES_DUMP + '/admin1_codes');
  261. }
  262. try {
  263. fs.writeFileSync(timestampedFilename, body);
  264. // Housekeeping, remove old files
  265. var currentFileName = path.basename(timestampedFilename);
  266. fs.readdirSync(GEONAMES_DUMP + '/admin1_codes').forEach(function(file) {
  267. if (file !== currentFileName) {
  268. fs.unlinkSync(GEONAMES_DUMP + '/admin1_codes/' + file);
  269. }
  270. });
  271. } catch (e) {
  272. throw(e);
  273. }
  274. return callback(null, timestampedFilename);
  275. });
  276. },
  277. _parseGeoNamesAdmin1CodesCsv: function(pathToCsv, callback) {
  278. var that = this;
  279. var lenI = GEONAMES_ADMIN_CODES_COLUMNS.length;
  280. that._admin1Codes = {};
  281. var lineReader = readline.createInterface({
  282. input: fs.createReadStream(pathToCsv)
  283. });
  284. lineReader.on('line', function(line) {
  285. line = line.split('\t');
  286. for (var i = 0; i &lt; lenI; i++) {
  287. var value = line[i] || null;
  288. if (i === 0) {
  289. that._admin1Codes[value] = {};
  290. } else {
  291. that._admin1Codes[line[0]][GEONAMES_ADMIN_CODES_COLUMNS[i]] = value;
  292. }
  293. }
  294. });
  295. lineReader.on('close', function() {
  296. return callback();
  297. });
  298. },
  299. _getGeoNamesAdmin2CodesData: function(callback) {
  300. var now = (new Date()).toISOString().substr(0, 10);
  301. var timestampedFilename = GEONAMES_DUMP + '/admin2_codes/' +
  302. ADMIN_2_CODES_FILE + '_' + now + '.txt';
  303. if (fs.existsSync(timestampedFilename)) {
  304. debug('Using cached GeoNames admin 2 codes data from ' +
  305. timestampedFilename);
  306. return callback(null, timestampedFilename);
  307. }
  308. var filename = GEONAMES_DUMP + '/admin2_codes/' + ADMIN_2_CODES_FILE +
  309. '.txt';
  310. if (fs.existsSync(filename)) {
  311. debug('Using cached GeoNames admin 2 codes data from ' +
  312. filename);
  313. return callback(null, filename);
  314. }
  315. debug('Getting GeoNames admin 2 codes data from ' +
  316. GEONAMES_URL + ADMIN_2_CODES_FILE + '.txt (this may take a while)');
  317. var url = GEONAMES_URL + ADMIN_2_CODES_FILE + '.txt';
  318. request.get(url, function(err, response, body) {
  319. if (err || response.statusCode !== 200) {
  320. return callback('Error downloading GeoNames admin 2 codes data' +
  321. (err ? ': ' + err : ''));
  322. }
  323. // Store a dump locally
  324. if (!fs.existsSync(GEONAMES_DUMP + '/admin2_codes')) {
  325. fs.mkdirSync(GEONAMES_DUMP + '/admin2_codes');
  326. }
  327. try {
  328. fs.writeFileSync(timestampedFilename, body);
  329. // Housekeeping, remove old files
  330. var currentFileName = path.basename(timestampedFilename);
  331. fs.readdirSync(GEONAMES_DUMP + '/admin2_codes').forEach(function(file) {
  332. if (file !== currentFileName) {
  333. fs.unlinkSync(GEONAMES_DUMP + '/admin2_codes/' + file);
  334. }
  335. });
  336. } catch (e) {
  337. throw(e);
  338. }
  339. return callback(null, timestampedFilename);
  340. });
  341. },
  342. _parseGeoNamesAdmin2CodesCsv: function(pathToCsv, callback) {
  343. var that = this;
  344. var lenI = GEONAMES_ADMIN_CODES_COLUMNS.length;
  345. that._admin2Codes = {};
  346. var lineReader = readline.createInterface({
  347. input: fs.createReadStream(pathToCsv)
  348. });
  349. lineReader.on('line', function(line) {
  350. line = line.split('\t');
  351. for (var i = 0; i &lt; lenI; i++) {
  352. var value = line[i] || null;
  353. if (i === 0) {
  354. that._admin2Codes[value] = {};
  355. } else {
  356. that._admin2Codes[line[0]][GEONAMES_ADMIN_CODES_COLUMNS[i]] = value;
  357. }
  358. }
  359. });
  360. lineReader.on('close', function() {
  361. return callback();
  362. });
  363. },
  364. _getGeoNamesCitiesData: function(callback) {
  365. var now = (new Date()).toISOString().substr(0, 10);
  366. // Use timestamped cities file OR bare cities file
  367. var timestampedFilename = GEONAMES_DUMP + '/cities/' + CITIES_FILE + '_' +
  368. now + '.txt';
  369. if (fs.existsSync(timestampedFilename)) {
  370. debug('Using cached GeoNames cities data from ' +
  371. timestampedFilename);
  372. return callback(null, timestampedFilename);
  373. }
  374. var filename = GEONAMES_DUMP + '/cities/' + CITIES_FILE + '.txt';
  375. if (fs.existsSync(filename)) {
  376. debug('Using cached GeoNames cities data from ' +
  377. filename);
  378. return callback(null, filename);
  379. }
  380. debug('Getting GeoNames cities data from ' + GEONAMES_URL +
  381. CITIES_FILE + '.zip (this may take a while)');
  382. var options = {
  383. url: GEONAMES_URL + CITIES_FILE + '.zip',
  384. encoding: null
  385. };
  386. request.get(options, function(err, response, body) {
  387. if (err || response.statusCode !== 200) {
  388. return callback('Error downloading GeoNames cities data' +
  389. (err ? ': ' + err : ''));
  390. }
  391. debug('Received zipped GeoNames cities data');
  392. // Store a dump locally
  393. if (!fs.existsSync(GEONAMES_DUMP + '/cities')) {
  394. fs.mkdirSync(GEONAMES_DUMP + '/cities');
  395. }
  396. var zipFilename = GEONAMES_DUMP + '/cities/' + CITIES_FILE + '_' + now +
  397. '.zip';
  398. try {
  399. fs.writeFileSync(zipFilename, body);
  400. fs.createReadStream(zipFilename)
  401. .pipe(unzip.Extract({path: GEONAMES_DUMP + '/cities'}))
  402. .on('close', function() {
  403. fs.renameSync(filename, timestampedFilename);
  404. fs.unlinkSync(GEONAMES_DUMP + '/cities/' + CITIES_FILE + '_' + now +
  405. '.zip');
  406. debug('Unzipped GeoNames cities data');
  407. // Housekeeping, remove old files
  408. var currentFileName = path.basename(timestampedFilename);
  409. fs.readdirSync(GEONAMES_DUMP + '/cities').forEach(function(file) {
  410. if (file !== currentFileName) {
  411. fs.unlinkSync(GEONAMES_DUMP + '/cities/' + file);
  412. }
  413. });
  414. return callback(null, timestampedFilename);
  415. });
  416. } catch (e) {
  417. debug('Warning: ' + e);
  418. return callback(null, timestampedFilename);
  419. }
  420. });
  421. },
  422. _parseGeoNamesCitiesCsv: function(pathToCsv, callback) {
  423. debug('Started parsing cities.txt (this may take a ' +
  424. 'while)');
  425. var data = [];
  426. var lenI = GEONAMES_COLUMNS.length;
  427. var that = this;
  428. var content = fs.readFileSync(pathToCsv);
  429. parse(content, {delimiter: '\t', quote: ''}, function(err, lines) {
  430. if (err) {
  431. return callback(err);
  432. }
  433. lines.forEach(function(line) {
  434. var lineObj = {};
  435. for (var i = 0; i &lt; lenI; i++) {
  436. var column = line[i] || null;
  437. lineObj[GEONAMES_COLUMNS[i]] = column;
  438. }
  439. data.push(lineObj);
  440. });
  441. debug('Finished parsing cities.txt');
  442. debug('Started building cities k-d tree (this may take ' +
  443. 'a while)');
  444. var dimensions = [
  445. 'latitude',
  446. 'longitude'
  447. ];
  448. that._kdTree = kdTree.createKdTree(data, that._distanceFunc, dimensions);
  449. debug('Finished building cities k-d tree');
  450. return callback();
  451. });
  452. },
  453. _getGeoNamesAllCountriesData: function(callback) {
  454. var now = (new Date()).toISOString().substr(0, 10);
  455. var timestampedFilename = GEONAMES_DUMP + '/all_countries/' +
  456. ALL_COUNTRIES_FILE + '_' + now + '.txt';
  457. if (fs.existsSync(timestampedFilename)) {
  458. debug('Using cached GeoNames all countries data from ' +
  459. timestampedFilename);
  460. return callback(null, timestampedFilename);
  461. }
  462. var filename = GEONAMES_DUMP + '/all_countries/' + ALL_COUNTRIES_FILE +
  463. '.txt';
  464. if (fs.existsSync(filename)) {
  465. debug('Using cached GeoNames all countries data from ' +
  466. filename);
  467. return callback(null, filename);
  468. }
  469. debug('Getting GeoNames all countries data from ' +
  470. GEONAMES_URL + ALL_COUNTRIES_FILE + '.zip (this may take a while)');
  471. var options = {
  472. url: GEONAMES_URL + ALL_COUNTRIES_FILE + '.zip',
  473. encoding: null
  474. };
  475. request.get(options, function(err, response, body) {
  476. if (err || response.statusCode !== 200) {
  477. return callback('Error downloading GeoNames all countries data' +
  478. (err ? ': ' + err : ''));
  479. }
  480. debug('Received zipped GeoNames all countries data');
  481. // Store a dump locally
  482. if (!fs.existsSync(GEONAMES_DUMP + '/all_countries')) {
  483. fs.mkdirSync(GEONAMES_DUMP + '/all_countries');
  484. }
  485. var zipFilename = GEONAMES_DUMP + '/all_countries/' + ALL_COUNTRIES_FILE +
  486. '_' + now + '.zip';
  487. try {
  488. fs.writeFileSync(zipFilename, body);
  489. fs.createReadStream(zipFilename)
  490. .pipe(unzip.Extract({path: GEONAMES_DUMP + '/all_countries'}))
  491. .on('close', function() {
  492. fs.renameSync(filename, timestampedFilename);
  493. fs.unlinkSync(GEONAMES_DUMP + '/all_countries/' +
  494. ALL_COUNTRIES_FILE + '_' + now + '.zip');
  495. debug('Unzipped GeoNames all countries data');
  496. // Housekeeping, remove old files
  497. var currentFileName = path.basename(timestampedFilename);
  498. var directory = GEONAMES_DUMP + '/all_countries';
  499. fs.readdirSync(directory).forEach(function(file) {
  500. if (file !== currentFileName) {
  501. fs.unlinkSync(GEONAMES_DUMP + '/all_countries/' + file);
  502. }
  503. });
  504. return callback(null, timestampedFilename);
  505. });
  506. } catch (e) {
  507. debug('Warning: ' + e);
  508. return callback(null, timestampedFilename);
  509. }
  510. });
  511. },
  512. _parseGeoNamesAllCountriesCsv: function(pathToCsv, callback) {
  513. debug('Started parsing all countries.txt (this may take ' +
  514. 'a while)');
  515. var lenI = GEONAMES_COLUMNS.length;
  516. var that = this;
  517. // Indexes
  518. var featureCodeIndex = GEONAMES_COLUMNS.indexOf('featureCode');
  519. var countryCodeIndex = GEONAMES_COLUMNS.indexOf('countryCode');
  520. var admin1CodeIndex = GEONAMES_COLUMNS.indexOf('admin1Code');
  521. var admin2CodeIndex = GEONAMES_COLUMNS.indexOf('admin2Code');
  522. var admin3CodeIndex = GEONAMES_COLUMNS.indexOf('admin3Code');
  523. var admin4CodeIndex = GEONAMES_COLUMNS.indexOf('admin4Code');
  524. var nameIndex = GEONAMES_COLUMNS.indexOf('name');
  525. var asciiNameIndex = GEONAMES_COLUMNS.indexOf('asciiName');
  526. var geoNameIdIndex = GEONAMES_COLUMNS.indexOf('geoNameId');
  527. var counter = 0;
  528. that._admin3Codes = {};
  529. that._admin4Codes = {};
  530. var lineReader = readline.createInterface({
  531. input: fs.createReadStream(pathToCsv)
  532. });
  533. lineReader.on('line', function(line) {
  534. line = line.split('\t');
  535. var featureCode = line[featureCodeIndex];
  536. if ((featureCode === 'ADM3') || (featureCode === 'ADM4')) {
  537. var lineObj = {
  538. name: line[nameIndex],
  539. asciiName: line[asciiNameIndex],
  540. geoNameId: line[geoNameIdIndex]
  541. };
  542. var key = line[countryCodeIndex] + '.' + line[admin1CodeIndex] + '.' +
  543. line[admin2CodeIndex] + '.' + line[admin3CodeIndex];
  544. if (featureCode === 'ADM3') {
  545. that._admin3Codes[key] = lineObj;
  546. } else if (featureCode === 'ADM4') {
  547. that._admin4Codes[key + '.' + line[admin4CodeIndex]] = lineObj;
  548. }
  549. }
  550. if (counter % 100000 === 0) {
  551. debug('Parsing progress all countries ' + counter);
  552. }
  553. counter++;
  554. });
  555. lineReader.on('close', function() {
  556. debug('Finished parsing all countries.txt');
  557. return callback();
  558. });
  559. },
  560. init: function(options, callback) {
  561. options = options || {};
  562. if (options.dumpDirectory) {
  563. GEONAMES_DUMP = options.dumpDirectory;
  564. }
  565. options.load = options.load || {};
  566. if (options.load.admin1 === undefined) {
  567. options.load.admin1 = true;
  568. }
  569. if (options.load.admin2 === undefined) {
  570. options.load.admin2 = true;
  571. }
  572. if (options.load.admin3And4 === undefined) {
  573. options.load.admin3And4 = true;
  574. }
  575. if (options.load.alternateNames === undefined) {
  576. options.load.alternateNames = true;
  577. }
  578. debug('Initializing local reverse geocoder using dump ' +
  579. 'directory: ' + GEONAMES_DUMP);
  580. // Create local cache folder
  581. if (!fs.existsSync(GEONAMES_DUMP)) {
  582. fs.mkdirSync(GEONAMES_DUMP);
  583. }
  584. var that = this;
  585. async.parallel([
  586. // Get GeoNames cities
  587. function(waterfallCallback) {
  588. async.waterfall([
  589. that._getGeoNamesCitiesData.bind(that),
  590. that._parseGeoNamesCitiesCsv.bind(that)
  591. ], function() {
  592. return waterfallCallback();
  593. });
  594. },
  595. // Get GeoNames admin 1 codes
  596. function(waterfallCallback) {
  597. if (options.load.admin1) {
  598. async.waterfall([
  599. that._getGeoNamesAdmin1CodesData.bind(that),
  600. that._parseGeoNamesAdmin1CodesCsv.bind(that)
  601. ], function() {
  602. return waterfallCallback();
  603. });
  604. } else {
  605. return setImmediate(waterfallCallback);
  606. }
  607. },
  608. // Get GeoNames admin 2 codes
  609. function(waterfallCallback) {
  610. if (options.load.admin2) {
  611. async.waterfall([
  612. that._getGeoNamesAdmin2CodesData.bind(that),
  613. that._parseGeoNamesAdmin2CodesCsv.bind(that)
  614. ], function() {
  615. return waterfallCallback();
  616. });
  617. } else {
  618. return setImmediate(waterfallCallback);
  619. }
  620. },
  621. // Get GeoNames all countries
  622. function(waterfallCallback) {
  623. if (options.load.admin3And4) {
  624. async.waterfall([
  625. that._getGeoNamesAllCountriesData.bind(that),
  626. that._parseGeoNamesAllCountriesCsv.bind(that)
  627. ], function() {
  628. return waterfallCallback();
  629. });
  630. } else {
  631. return setImmediate(waterfallCallback);
  632. }
  633. },
  634. // Get GeoNames alternate names
  635. function(waterfallCallback) {
  636. if (options.load.alternateNames) {
  637. async.waterfall([
  638. that._getGeoNamesAlternateNamesData.bind(that),
  639. that._parseGeoNamesAlternateNamesCsv.bind(that)
  640. ], function() {
  641. return waterfallCallback();
  642. });
  643. } else {
  644. return setImmediate(waterfallCallback);
  645. }
  646. }
  647. ],
  648. // Main callback
  649. function(err) {
  650. if (err) {
  651. throw(err);
  652. }
  653. return callback();
  654. });
  655. },
  656. lookUp: function(points, arg2, arg3) {
  657. var callback;
  658. var maxResults;
  659. if (arguments.length === 2) {
  660. maxResults = 1;
  661. callback = arg2;
  662. } else {
  663. maxResults = arg2;
  664. callback = arg3;
  665. }
  666. this._lookUp(points, maxResults, function(err, results) {
  667. return callback(null, results);
  668. });
  669. },
  670. _lookUp: function(points, maxResults, callback) {
  671. var that = this;
  672. // If not yet initialied, then initialize
  673. if (!this._kdTree) {
  674. return this.init({}, function() {
  675. return that.lookUp(points, maxResults, callback);
  676. });
  677. }
  678. // Make sure we have an array of points
  679. if (!Array.isArray(points)) {
  680. points = [points];
  681. }
  682. var functions = [];
  683. points.forEach(function(point, i) {
  684. point = {
  685. latitude: parseFloat(point.latitude),
  686. longitude: parseFloat(point.longitude)
  687. };
  688. debug('Look-up request for point ' +
  689. JSON.stringify(point));
  690. functions[i] = function(innerCallback) {
  691. var result = that._kdTree.nearest(point, maxResults);
  692. result.reverse();
  693. for (var j = 0, lenJ = result.length; j &lt; lenJ; j++) {
  694. if (result &amp;&amp; result[j] &amp;&amp; result[j][0]) {
  695. var countryCode = result[j][0].countryCode || '';
  696. var geoNameId = result[j][0].geoNameId || '';
  697. var admin1Code;
  698. var admin2Code;
  699. var admin3Code;
  700. var admin4Code;
  701. // Look-up of admin 1 code
  702. if (that._admin1Codes) {
  703. admin1Code = result[j][0].admin1Code || '';
  704. var admin1CodeKey = countryCode + '.' + admin1Code;
  705. result[j][0].admin1Code = that._admin1Codes[admin1CodeKey] ||
  706. result[j][0].admin1Code;
  707. }
  708. // Look-up of admin 2 code
  709. if (that._admin2Codes) {
  710. admin2Code = result[j][0].admin2Code || '';
  711. var admin2CodeKey = countryCode + '.' + admin1Code + '.' +
  712. admin2Code;
  713. result[j][0].admin2Code = that._admin2Codes[admin2CodeKey] ||
  714. result[j][0].admin2Code;
  715. }
  716. // Look-up of admin 3 code
  717. if (that._admin3Codes) {
  718. admin3Code = result[j][0].admin3Code || '';
  719. var admin3CodeKey = countryCode + '.' + admin1Code + '.' +
  720. admin2Code + '.' + admin3Code;
  721. result[j][0].admin3Code = that._admin3Codes[admin3CodeKey] ||
  722. result[j][0].admin3Code;
  723. }
  724. // Look-up of admin 4 code
  725. if (that._admin4Codes) {
  726. admin4Code = result[j][0].admin4Code || '';
  727. var admin4CodeKey = countryCode + '.' + admin1Code + '.' +
  728. admin2Code + '.' + admin3Code + '.' + admin4Code;
  729. result[j][0].admin4Code = that._admin4Codes[admin4CodeKey] ||
  730. result[j][0].admin4Code;
  731. }
  732. // Look-up of alternate name
  733. if (that._alternateNames) {
  734. result[j][0].alternateName = that._alternateNames[geoNameId] ||
  735. result[j][0].alternateName;
  736. }
  737. // Pull in the k-d tree distance in the main object
  738. result[j][0].distance = result[j][1];
  739. // Simplify the output by not returning an array
  740. result[j] = result[j][0];
  741. }
  742. }
  743. debug('Found result(s) for point ' +
  744. JSON.stringify(point) + result.map(function(subResult, i) {
  745. return '\n (' + (++i) + ') {"geoNameId":"' +
  746. subResult.geoNameId + '",' + '"name":"' + subResult.name +
  747. '"}';
  748. }));
  749. return innerCallback(null, result);
  750. };
  751. });
  752. async.series(
  753. functions,
  754. function(err, results) {
  755. debug('Delivering joint results');
  756. return callback(null, results);
  757. });
  758. }
  759. };
  760. module.exports = geocoder;
  761. </code></pre>
  762. </article>
  763. </section>
  764. </div>
  765. <nav>
  766. <h2><a href="index.html">Home</a></h2><h3><a href="global.html">Global</a></h3>
  767. </nav>
  768. <br class="clear">
  769. <footer>
  770. Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a> on Sat Sep 07 2019 23:12:47 GMT+0300 (Moscow Standard Time)
  771. </footer>
  772. <script> prettyPrint(); </script>
  773. <script src="scripts/linenumber.js"> </script>
  774. </body>
  775. </html>