const Request = require('./Request')
const defines = require('./defines')
const BoundingBox = require('boundingbox')
const overpassOutOptions = require('./overpassOutOptions')
const RequestGetMembers = require('./RequestGetMembers')
const isGeoJSON = require('./isGeoJSON')
/**
* A get request (request list of map features by id)
* @extends Request
*/
class RequestGet extends Request {
/**
* @param {OverpassFrontend} overpass
* @param {data} data
*/
constructor (overpass, data) {
super(overpass, data)
this.type = 'get'
if (typeof this.ids === 'string') {
this.ids = [this.ids]
} else {
this.ids = this.ids.concat()
}
if (typeof this.options.properties === 'undefined') {
this.options.properties = defines.DEFAULT
}
for (let i = 0; i < this.ids.length; i++) {
if (this.ids[i] in this.overpass.cacheElements && this.overpass.cacheElements[this.ids[i]] === false) {
delete this.overpass.cacheElements[this.ids[i]]
}
}
if (this.options.bounds) {
if (isGeoJSON(this.options.bounds)) {
this.geojsonBounds = this.options.bounds
}
this.options.bounds = new BoundingBox(this.options.bounds)
} else if (this.options.bbox) {
this.options.bounds = new BoundingBox(this.options.bbox)
delete this.options.bbox
console.error('OverpassFrontend.get(): option "bbox" is deprecated, use "bounds" instead')
}
// option 'split' not available for get requests -> use effort instead
delete this.options.split
this.done = {}
if ('members' in this.options) {
RequestGetMembers(this)
}
}
_effortForId (id) {
if (!id) {
return null
}
const type = id.substr(0, 1)
switch (type) {
case 'n':
return this.overpass.options.effortNode
case 'w':
return this.overpass.options.effortWay
case 'r':
return this.overpass.options.effortRelation
}
}
/**
* how much effort can a call to this request use
* @return {Request#minMaxEffortResult} - minimum and maximum effort
*/
minMaxEffort () {
const todo = this.ids.filter(x => x)
if (todo.length === 0) {
return { minEffort: 0, maxEffort: 0 }
}
const minEffort = Math.min.apply(this, todo.map(id => this._effortForId(id)))
const maxEffort = todo.map(id => this._effortForId(id)).reduce((a, b) => a + b)
return { minEffort, maxEffort }
}
/**
* check if there are any map features which can be returned right now
*/
preprocess () {
this.allFound = true
for (let i = 0; i < this.ids.length; i++) {
const id = this.ids[i]
if (id === null) {
continue
}
if (id in this.overpass.cacheElements) {
const ob = this.overpass.cacheElements[id]
let ready = true
// Feature does not exists!
if (ob.missingObject) {
this.featureCallback(null, null, i)
this.ids[i] = null
continue
}
// for bounds option, if object is (partly) loaded, but outside call
// featureCallback with 'false'
if (this.options.bounds) {
const intersects = this.geojsonBounds ? ob.intersects(this.geojsonBounds) : ob.intersects(this.options.bounds)
if (intersects === 0 || (!ob.bounds && ob.properties | defines.BBOX)) {
this.featureCallback(null, false, i)
this.ids[i] = null
continue
}
}
// not fully loaded
if ((ob !== false && ob !== null) && (this.options.properties & ob.properties) !== this.options.properties) {
ready = false
}
// if sort is set in options maybe defer calling featureCallback
if (ready) {
this.receiveObject(ob)
this.featureCallback(null, ob, i)
this.ids[i] = null
continue
}
} else {
// Illegal ID
if (id !== null && !id.match(/^[nwr][0-9]+$/)) {
this.featureCallback(null, null, i)
this.ids[i] = null
continue
}
}
this.allFound = false
}
}
/**
* compile the query
* @param {OverpassFrontend#Context} context - Current context
* @return {Request#SubRequest} - the compiled query
*/
_compileQuery (context) {
let query = ''
let nodeQuery = ''
let wayQuery = ''
let relationQuery = ''
let BBoxQuery = ''
let effort = 0
let outOptions
if (this.options.bounds) {
BBoxQuery = '(' + this.options.bounds.toLatLonString() + ')'
}
for (let i = 0; i < this.ids.length; i++) {
const id = this.ids[i]
outOptions = overpassOutOptions(this.options)
if (effort > context.maxEffort) {
break
}
if (id === null) {
continue
}
// don't load objects multiple times in same context
if (id in context.todo) {
continue
}
if (this.options.bounds) {
// check if we already know the bounds of the element; if yes, don't try
// to load object if it does not intersect bounds
if (id in this.overpass.cacheElements && (this.overpass.cacheElements[id].properties & defines.BBOX)) {
if (!this.overpass.cacheElements[id].intersects(this.options.bounds)) {
continue
}
}
}
switch (id.substr(0, 1)) {
case 'n':
nodeQuery += 'node(' + id.substr(1) + ');\n'
effort += this.overpass.options.effortNode
break
case 'w':
wayQuery += 'way(' + id.substr(1) + ');\n'
effort += this.overpass.options.effortWay
break
case 'r':
relationQuery += 'relation(' + id.substr(1) + ');\n'
effort += this.overpass.options.effortRelation
break
}
context.todo[id] = true
}
if (nodeQuery !== '') {
query += '((' + nodeQuery + ');)->.n;\n'
if (BBoxQuery) {
query += '(node.n; - node.n' + BBoxQuery + '->.n;);\nout ids bb qt;\n'
}
}
if (wayQuery !== '') {
query += '((' + wayQuery + ');)->.w;\n'
if (BBoxQuery) {
query += '(way.w; - way.w' + BBoxQuery + '->.w;);\nout ids bb qt;\n'
}
}
if (relationQuery !== '') {
query += '((' + relationQuery + ');)->.r;\n'
if (BBoxQuery) {
query += '(relation.r; - relation.r' + BBoxQuery + '->.r;);\nout ids bb qt;\n'
}
}
const requestParts = []
if (BBoxQuery && (nodeQuery !== '' || wayQuery !== '' || relationQuery !== '')) {
// additional separator to separate objects outside bbox from inside bbox
query += 'out count;\n'
requestParts.push({
properties: defines.BBOX,
bounds: this.options.bounds,
boundsNoMatch: true
})
}
if (nodeQuery !== '') {
query += '.n out ' + outOptions + ';\n'
}
if (wayQuery !== '') {
query += '.w out ' + outOptions + ';\n'
}
if (relationQuery !== '') {
query += '.r out ' + outOptions + ';\n'
}
if (query) {
requestParts.push({
properties: this.options.properties,
receiveObject: this.receiveObject.bind(this),
checkFeatureCallback: this.checkFeatureCallback.bind(this),
featureCallback: this._featureCallback.bind(this, this.featureCallback)
})
}
const subRequest = {
query,
effort: effort,
request: this,
parts: requestParts
}
return subRequest
}
checkFeatureCallback (ob) {
if (this.geojsonBounds && ob.intersects(this.geojsonBounds) === 0) {
return false
}
return true
}
_featureCallback (fun, err, ob) {
const indexes = []
let p
while ((p = this.ids.indexOf(ob.id)) !== -1) {
this.ids[p] = null
indexes.push(p)
}
if (this.options.bounds && !ob.intersects(this.options.bounds)) {
indexes.forEach(p => fun(null, false, p))
return
}
indexes.forEach(p => fun(null, ob, p))
}
needLoad () {
this.preprocess()
return this.allFound
}
mayFinish () {
return this.allFound
}
}
module.exports = RequestGet