overpass-frontend
Version:
A JavaScript (NodeJS/Browser) library to easily access data from OpenStreetMap via Overpass API or from an OSM File. The objects can directly be used with LeafletJS or exported to GeoJSON. Data will be cached in the browser memory.
217 lines (172 loc) • 5.92 kB
JavaScript
const defines = require('./defines')
const overpassOutOptions = require('./overpassOutOptions')
const each = require('lodash/forEach')
const map = require('lodash/map')
const keys = require('lodash/keys')
const BoundingBox = require('boundingbox')
const SortedCallbacks = require('./SortedCallbacks')
const isGeoJSON = require('./isGeoJSON')
const Request = require('./Request')
class RequestBBoxMembers extends Request {
constructor (request) {
super(request.overpass, {})
this.master = request
this.options = this.master.options
this.options.properties |= defines.MEMBERS
this.options.memberProperties = this.options.memberProperties || defines.DEFAULT
this.options.memberProperties |= defines.BBOX
if (this.options.memberBounds) {
if (isGeoJSON(this.options.memberBounds)) {
this.bounds = this.options.memberBounds
} else {
this.bounds = new BoundingBox(this.options.memberBounds)
}
}
this.master._compileQuery = this._compileQuery.bind(this, this.master._compileQuery)
this.master.needLoad = this.needLoad.bind(this, this.master.needLoad)
this.master.mayFinish = this.mayFinish.bind(this, this.master.mayFinish)
this.master.preprocess = this.preprocess.bind(this, this.master.preprocess)
this.master.willInclude = this.willInclude.bind(this, this.master.willInclude)
this.master.minMaxEffort = this.minMaxEffort.bind(this, this.master.minMaxEffort)
this.master.finishSubRequest = this.finishSubRequest.bind(this, this.master.finishSubRequest)
this.master.featureCallback = this.receiveMasterObject.bind(this, this.master.featureCallback)
this.doneFeatures = {}
this.relations = {}
this.currentRelations = []
this.todo = {}
this.loadFinish = true
const callbacks = new SortedCallbacks(this.options, this.options.memberCallback, this.finalCallback)
this.options.memberCallback = callbacks.next.bind(callbacks)
this.finalCallback = callbacks.final.bind(callbacks)
}
willInclude (fun, context) {
const result = fun.call(this.master, context)
if (!this.loadFinish) {
return true
}
return result
}
minMaxEffort (fun) {
let { minEffort, maxEffort } = fun.call(this.master)
if (!this.loadFinish) {
minEffort += 64
maxEffort = null
}
return { minEffort, maxEffort }
}
preprocess (fun) {
fun.call(this.master)
this.todo = {}
each(this.relations, ob => {
each(ob.members, member => {
if (!(member.id in this.doneFeatures)) {
this.todo[member.id] = undefined
}
})
})
each(this.todo, (value, id) => {
if (id in this.overpass.cacheElements) {
const ob = this.overpass.cacheElements[id]
if (this.bounds && !ob.intersects(this.bounds)) {
return
}
if ((this.options.memberProperties & ob.properties) === this.options.memberProperties) {
this.receiveObject(ob)
this.options.memberCallback(null, ob)
}
}
})
}
_compileQuery (fun, context) {
const subRequest = fun.call(this.master, context)
if (keys(this.relations).length === 0) {
return subRequest
}
let query = '(\n'
query += map(this.relations, ob => {
if (ob.type === 'relation') {
return 'relation(' + ob.osm_id + ');\n'
}
return ''
}).join('')
query += ')->.result;'
this.currentRelations = keys(this.relations)
let BBoxString = ''
if (this.bounds) {
BBoxString = '(' + new BoundingBox(this.bounds).toLatLonString() + ')'
}
query += '(\n' +
' node(r.result)' + BBoxString + ';\n' +
' way(r.result)' + BBoxString + ';\n' +
' relation(r.result)' + BBoxString + ';\n' +
')->.resultMembers;\n'
let queryRemoveDoneFeatures = ''
let countRemoveDoneFeatures = 0
for (const id in this.doneFeatures) {
const ob = this.doneFeatures[id]
if (countRemoveDoneFeatures % 1000 === 999) {
query += '(' + queryRemoveDoneFeatures + ')->.doneMembers;\n'
queryRemoveDoneFeatures = '.doneMembers;'
}
queryRemoveDoneFeatures += ob.type + '(' + ob.osm_id + ');'
countRemoveDoneFeatures++
}
if (countRemoveDoneFeatures) {
query += '(' + queryRemoveDoneFeatures + ')->.doneMembers;\n'
query += '(.resultMembers; - .doneMembers;)->.resultMembers;\n'
}
this.part = {
properties: this.options.memberProperties,
receiveObject: this.receiveObject.bind(this),
checkFeatureCallback: this.checkFeatureCallback.bind(this),
featureCallback: this.options.memberCallback,
count: 0
}
query += '.resultMembers out ' + overpassOutOptions(this.part) + ';'
this.loadFinish = true
if (subRequest.parts.length) {
subRequest.query += '\nout count;\n'
}
subRequest.query += query
subRequest.parts.push(this.part)
return subRequest
}
receiveMasterObject (fun, err, result, index) {
this.relations[result.id] = result
this.loadFinish = false
fun(err, result, index)
}
receiveObject (ob) {
super.receiveObject(ob)
this.doneFeatures[ob.id] = ob
}
checkFeatureCallback (ob) {
if (this.bounds && ob.intersects(this.bounds) === 0) {
return false
}
return true
}
finishSubRequest (fun, subRequest) {
fun.call(this.master, subRequest)
if (keys(this.relations).length !== this.currentRelations.length) {
this.loadFinish = false
}
}
needLoad (fun) {
const result = fun.call(this.master)
if (result === true) {
return true
}
return !this.loadFinish
}
mayFinish (fun) {
const result = fun.call(this.master)
if (result === false) {
return false
}
return this.loadFinish
}
}
module.exports = function (request) {
return new RequestBBoxMembers(request)
}