const Request = require('./Request')
const overpassOutOptions = require('./overpassOutOptions')
const defines = require('./defines')
const RequestBBoxMembers = require('./RequestBBoxMembers')
const Filter = require('./Filter')
const boundsToLokiQuery = require('./boundsToLokiQuery')
const boundsIsFullWorld = require('./boundsIsFullWorld')
/**
* A BBox request
* @extends Request
*/
class RequestBBox extends Request {
/**
* @param {OverpassFrontend} overpass
* @param {object} options
*/
constructor (overpass, data) {
super(overpass, data)
this.type = 'BBoxQuery'
if (typeof this.options.properties === 'undefined') {
this.options.properties = defines.DEFAULT
}
this.options.properties |= defines.BBOX
this.options.minEffort = this.options.minEffort || 256
// make sure the request ends with ';'
if (!this.query.match(/;\s*$/)) {
this.query += ';'
}
if (!('noCacheQuery' in this.options) || !this.options.noCacheQuery) {
try {
if (this.options.filter) {
this.filterQuery = new Filter({ and: [this.query, this.options.filter] })
this.query = this.filterQuery.toQl()
} else {
this.filterQuery = new Filter(this.query)
}
} catch (err) {
return this.finish(err)
}
this.lokiQuery = this.filterQuery.toLokijs()
this.lokiQueryNeedMatch = !!this.lokiQuery.needMatch
delete this.lokiQuery.needMatch
if (!boundsIsFullWorld(this.bounds)) {
this.lokiQuery = { $and: [this.lokiQuery, boundsToLokiQuery(this.bbox, this.overpass)] }
}
const cacheFilter = new Filter({ and: [this.filterQuery, new Filter('nwr(properties:' + this.options.properties + ')')] })
this.options.properties = cacheFilter.properties()
this.cacheDescriptors = cacheFilter.cacheDescriptors().map(cacheDescriptors => {
return {
cache: this.overpass.bboxQueryCache.get(cacheDescriptors.id),
cacheDescriptors
}
})
}
this.loadFinish = false
if ('members' in this.options) {
RequestBBoxMembers(this)
}
}
/**
* check if there are any map features which can be returned right now
*/
preprocess () {
let items = []
if (this.lokiQuery) {
items = this.overpass.db.find(this.lokiQuery)
}
for (let i = 0; i < items.length; i++) {
if (this.options.limit && this.count >= this.options.limit) {
this.loadFinish = true
return
}
const id = items[i].id
if (!(id in this.overpass.cacheElements)) {
continue
}
const ob = this.overpass.cacheElements[id]
if (id in this.doneFeatures) {
continue
}
// maybe we need an additional check
if (this.lokiQueryNeedMatch && !this.filterQuery.match(ob)) {
continue
}
// also check the object directly if it intersects the bbox - if possible
if (ob.intersects(this.bounds) < 2) {
continue
}
if ((this.options.properties & ob.properties) === this.options.properties) {
this.receiveObject(ob)
this.featureCallback(null, ob)
}
}
if (this.options.limit && this.count >= this.options.limit) {
this.loadFinish = true
}
}
/**
* shall this Request be included in the current call?
* @param {OverpassFrontend#Context} context - Current context
* @return {boolean|int[]} - yes|no - or [ minEffort, maxEffort ]
*/
willInclude (context) {
if (this.loadFinish) {
return false
}
if (context.bbox && context.bbox.toLatLonString() !== this.bbox.toLatLonString()) {
return false
}
context.bbox = this.bbox
for (const i in context.requests) {
const request = context.requests[i]
if (request instanceof RequestBBox && request.query === this.query) {
return false
}
}
return true
}
/**
* how much effort can a call to this request use
* @return {Request#minMaxEffortResult} - minimum and maximum effort
*/
minMaxEffort () {
if (this.loadFinish) {
return { minEffort: 0, maxEffort: 0 }
}
let minEffort = this.options.minEffort
let maxEffort = null
if (this.options.limit) {
maxEffort = (this.options.limit - this.count) * this.overpass.options.effortBBoxFeature
minEffort = Math.min(minEffort, maxEffort)
}
return { minEffort, maxEffort }
}
/**
* compile the query
* @param {OverpassFrontend#Context} context - Current context
* @return {Request#SubRequest|false} - the compiled query or false if the bbox does not match
*/
_compileQuery (context) {
if (this.loadFinish || (context.bbox && context.bbox.toLatLonString() !== this.bbox.toLatLonString())) {
return {
query: '',
request: this,
parts: [],
effort: 0
}
}
const efforts = this.minMaxEffort()
let effortAvailable = Math.max(context.maxEffort, efforts.minEffort)
if (efforts.maxEffort) {
effortAvailable = Math.min(effortAvailable, efforts.maxEffort)
}
// if the context already has a bbox and it differs from this, we can't add
// ours
let query = this.query.substr(0, this.query.length - 1) + '->.result;\n'
let queryRemoveDoneFeatures = ''
let countRemoveDoneFeatures = 0
for (const id in this.doneFeatures) {
const ob = this.doneFeatures[id]
if (countRemoveDoneFeatures % 1000 === 999) {
query += '(' + queryRemoveDoneFeatures + ')->.done;\n'
queryRemoveDoneFeatures = '.done;'
}
queryRemoveDoneFeatures += ob.type + '(' + ob.osm_id + ');'
countRemoveDoneFeatures++
}
if (countRemoveDoneFeatures) {
query += '(' + queryRemoveDoneFeatures + ')->.done;\n'
query += '(.result; - .done;)->.result;\n'
}
if (!('split' in this.options)) {
this.options.effortSplit = Math.ceil(effortAvailable / this.overpass.options.effortBBoxFeature)
}
query += '.result out ' + overpassOutOptions(this.options) + ';'
const subRequest = {
query,
request: this,
parts: [
{
properties: this.options.properties,
receiveObject: this.receiveObject.bind(this),
checkFeatureCallback: this.checkFeatureCallback.bind(this),
featureCallback: this.featureCallback
}
],
effort: this.options.split ? this.options.split * this.overpass.options.effortBBoxFeature : effortAvailable
}
return subRequest
}
/**
* receive an object from OverpassFronted -> enter to cache, return to caller
* @param {OverpassObject} ob - Object which has been received
* @param {Request#SubRequest} subRequest - sub request which is being handled right now
* @param {int} partIndex - Which part of the subRequest is being received
*/
receiveObject (ob) {
super.receiveObject(ob)
this.doneFeatures[ob.id] = ob
}
checkFeatureCallback (ob) {
if (this.bounds && ob.intersects(this.bounds) === 0) {
return false
}
return true
}
/**
* the current subrequest is finished -> update caches, check whether request is finished
* @param {Request#SubRequest} subRequest - the current sub request
*/
finishSubRequest (subRequest) {
super.finishSubRequest(subRequest)
if (('effortSplit' in this.options && this.options.effortSplit > subRequest.parts[0].count) ||
(this.options.split > subRequest.parts[0].count)) {
this.loadFinish = true
this.cacheDescriptors && this.cacheDescriptors.forEach(cache => {
cache.cache.add(this.bbox, cache.cacheDescriptors)
})
}
if (this.options.limit && this.options.limit <= this.count) {
this.loadFinish = true
}
}
/**
* check if we need to call Overpass API. Maybe whole area is cached anyway?
* @return {boolean} - true, if we need to call Overpass API
*/
needLoad () {
if (this.loadFinish) {
return false
}
return !this.cacheDescriptors || !this.cacheDescriptors.every(cache => {
return cache.cache.check(this.bbox, cache.cacheDescriptors)
})
}
mayFinish () {
return !this.needLoad()
}
}
module.exports = RequestBBox