UNPKG

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.

287 lines (240 loc) 8.25 kB
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