fast-timezone
Version:
Fast timezone lookup service.
148 lines (139 loc) • 4.67 kB
JavaScript
var fs = require('fs')
var https = require('follow-redirects').https
var path = require('path')
var async = require('async')
var mkdirp = require('mkdirp')
var rimraf = require('rimraf')
var yazl = require('yazl')
var yauzl = require('yauzl')
var indexGeoJSON = require('./geo-index')
var TARGET_INDEX_PERCENT = 0.5
var dlFile = __dirname + '/../downloads/timezones.zip'
var tzFile = __dirname + '/../downloads/timezones.json'
var downloadLatest = function (callback) {
console.log('Downloading geojson')
async.auto({
getLatestUrl: function (cb) {
https.get(
{
headers: { 'user-agent': 'node-geo-tz' },
host: 'api.github.com',
path: '/repos/evansiroky/timezone-boundary-builder/releases/latest'
},
function (res) {
var data = ''
res.on('data', function (chunk) {
data += chunk
})
res.on('end', function () {
data = JSON.parse(data)
for (var i = 0; i < data.assets.length; i++) {
data.assets[i].browser_download_url.indexOf('timezones.geojson') > -1
return cb(null, data.assets[i].browser_download_url)
}
cb('geojson not found')
})
}
).on('error', cb)
},
rm: function (cb) {
rimraf(dlFile, cb)
},
mkdir: ['rm', function (results, cb) {
mkdirp(__dirname + '/../downloads', cb)
}],
dl: ['mkdir', 'getLatestUrl', function (results, cb) {
https.get({
headers: { 'user-agent': 'node-geo-tz' },
host: 'github.com',
path: results.getLatestUrl.replace('https://github.com', '')
}, function (response) {
var file = fs.createWriteStream(dlFile)
response.pipe(file)
file.on('finish', function () {
file.close(cb)
})
}).on('error', cb)
}]
}, callback)
}
var zipDir = function (zipFile, filePath, zipPath, nextDir, callback) {
var curPath = path.join(filePath, nextDir)
fs.readdir(curPath, function (err, files) {
if (err) return callback(err)
async.each(files, function (file, cb) {
var curFile = path.join(curPath, file)
var curZipFile = path.join(zipPath, file)
fs.stat(curFile, function (err, stats) {
if (err) return cb(err)
if (stats.isDirectory()) {
zipDir(zipFile, curPath, curZipFile, file, cb)
} else {
zipFile.addFile(curFile, curZipFile)
cb()
}
})
}, callback)
})
}
module.exports = function (cfg, callback) {
if (!callback) {
if (typeof cfg === 'function') {
callback = cfg
cfg = {}
} else {
callback = function () {}
}
}
var dataDir = cfg.dataDir || 'data'
var resolvedDataDir = cfg.dataDir ? __dirname + '/../' + cfg.dataDir : __dirname + '/../data'
async.auto({
// download latest geojson data
downloadData: function (cb) {
downloadLatest(cb)
},
deleteIndexFoldersAndFiles: ['downloadData', function (results, cb) {
async.each(['a', 'b', 'c', 'd', 'index.json'], function (fileOrFolder, eachCb) {
rimraf(resolvedDataDir + '/' + fileOrFolder, eachCb)
}, cb)
}],
unzipGeoJson: ['downloadData', function (results, cb) {
yauzl.open(dlFile, { lazyEntries: true }, function (err, zipfile) {
if (err) { return cb(err) }
zipfile.readEntry()
zipfile.on('entry', function (entry) {
if (/\/$/.test(entry.fileName)) {
// directory, keep reading
zipfile.readEntry()
} else {
// assuming a json file
zipfile.openReadStream(entry,
function (err, readStream) {
if (err) { return cb(err) }
readStream.pipe(fs.createWriteStream(tzFile))
readStream.on('end', function () {
zipfile.readEntry()
})
}
)
}
})
zipfile.on('end', function (err) {
cb(err)
})
})
}],
createIndex: ['deleteIndexFoldersAndFiles', 'unzipGeoJson', function (results, cb) {
indexGeoJSON(require(tzFile), resolvedDataDir, TARGET_INDEX_PERCENT, cb)
}],
zipData: ['createIndex', function (results, cb) {
var zipfile = new yazl.ZipFile()
// recursively add all files in data directory to zip file
zipDir(zipfile, path.resolve(__dirname + '/../'), dataDir, dataDir, function (err) {
if (err) return cb(err)
zipfile.outputStream.pipe(fs.createWriteStream(resolvedDataDir + '.zip')).on('close', cb)
zipfile.end()
})
}]
}, callback)
}