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.

312 lines (261 loc) 8.18 kB
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