osm-poi-db
Version:
Extract POIs from OSM PBFs and put them into LevelDB, ready for nearby query
192 lines (166 loc) • 5.45 kB
JavaScript
var async = require('async');
var parseOSM = require('osm-pbf-parser');
var GeoHash = require('geohash').GeoHash;
var leveldown = require('leveldown');
var db = leveldown('./osm', {
writeBufferSize: 64 * 1024 * 1024
});
var Transform = require('stream').Transform;
var Writable = require('stream').Writable;
var util = require('util');
var INTERESTING = [
"amenity", "emergency", "historic",
"leisure", "public_transport", "shop",
"sport", "tourism", "craft",
"office", "addr:street", "addr:housenumber"
];
var CONCURRENCY = 32;
util.inherits(Expander, Transform);
function Expander() {
Transform.call(this, { objectMode: true });
this._readableState.highWaterMark = CONCURRENCY;
this._writableState.highWaterMark = 1;
}
Expander.prototype._transform = function(values, encoding, callback) {
async.eachSeries(values, function(value, cb) {
if (value.type === 'way') {
this.expandWay(value, function(err) {
this.push(value);
cb(err);
}.bind(this));
} else {
this.push(value);
setImmediate(cb);
}
}.bind(this), callback);
};
Expander.prototype.expandWay = function(way, callback) {
if (way.refs && way.refs.length > 0) {
var lon = 0, lat = 0, len = 0;
// var start = Date.now();
async.eachLimit(way.refs, CONCURRENCY, function(id, cb) {
db.get("p:" + id, function(err, data) {
if (data) {
var geohash = GeoHash.decodeGeoHash(data.toString());
lon += geohash.longitude[2];
lat += geohash.latitude[2];
len++;
cb();
} else if (err && err.notFound) {
/* Ignore :-/ */
console.log("get p:" + id, "notFound");
cb();
} else {
/* Ignore :-/ */
console.log("get p:" + id, err, data);
cb();
}
});
}, function(err) {
// var stop = Date.now();
if (len > 0) {
way.lon = lon / len;
way.lat = lat / len;
// console.log("looked up way", way.lon, "/", way.lat, "from", len, "in", stop - start, "ms");
} else {
console.log("No location for", way, err);
}
callback(err, way);
});
} else {
console.log("Cannot expand way", way);
callback(null, way);
}
};
util.inherits(ToDB, Transform);
function ToDB() {
Transform.call(this, { objectMode: true, highWaterMark: 0 });
this.stats = {};
}
ToDB.prototype._transform = function(value, encoding, callback) {
if (!this.stats.hasOwnProperty(value.type)) {
this.stats[value.type] = 0;
}
this.stats[value.type]++;
if (value.lat && value.lon) {
var geohash = GeoHash.encodeGeoHash(value.lat, value.lon);
this.push({
type: 'put',
key: "p:" + value.id,
value: geohash
});
var isInteresting = INTERESTING.some(function(field) {
return value.tags.hasOwnProperty(field);
});
if (isInteresting) {
delete value.info;
var key = "geo:" + geohash + ":" + value.id;
this.push({
type: 'put',
key: key,
value: JSON.stringify(value)
});
}
}
callback();
};
ToDB.prototype._flush = function(callback) {
console.log("Processed", this.stats);
callback();
};
util.inherits(BatchBuffer, Transform);
function BatchBuffer(batchSize) {
Transform.call(this, { objectMode: true });
this._readableState.highWaterMark = 1;
this._writableState.highWaterMark = batchSize;
this.batchSize = batchSize;
this.buffer = [];
}
BatchBuffer.prototype._transform = function(chunk, encoding, callback) {
this.buffer.push(chunk);
this.canFlush(false);
callback();
};
BatchBuffer.prototype._flush = function(callback) {
this.canFlush(true);
callback();
};
BatchBuffer.prototype.canFlush = function(force) {
while((force && this.buffer.length > 0) || this.buffer.length >= this.batchSize) {
var chunks = this.buffer.slice(0, this.batchSize);
this.buffer = this.buffer.slice(this.batchSize);
this.push(chunks);
}
};
util.inherits(BatchWriter, Writable);
function BatchWriter() {
Writable.call(this, { objectMode: true, highWaterMark: 2 });
this.count = 0;
this.oldCount = 0;
}
BatchWriter.prototype._write = function(chunk, encoding, callback) {
db.batch(chunk, callback);
this.count += chunk.length;
if (!this.statsTimeout) {
this.statsTimeout = setTimeout(function() {
this.statsTimeout = null;
console.log("Writing", Math.floor((this.count - this.oldCount) / 3), "entries/s");
this.oldCount = this.count;
}.bind(this), 3 * 1000);
}
};
db.open(function(err) {
if (err) {
console.log("open", err);
process.exit(1);
}
process.stdin
.pipe(parseOSM({ parseInfo: false }))
.pipe(new Expander())
.pipe(new ToDB())
.pipe(new BatchBuffer(CONCURRENCY))
.pipe(new BatchWriter())
.on('end', function() {
db.close();
});
});