Source: RequestBBox.js

  1. const Request = require('./Request')
  2. const overpassOutOptions = require('./overpassOutOptions')
  3. const defines = require('./defines')
  4. const RequestBBoxMembers = require('./RequestBBoxMembers')
  5. const Filter = require('./Filter')
  6. const boundsToLokiQuery = require('./boundsToLokiQuery')
  7. const boundsIsFullWorld = require('./boundsIsFullWorld')
  8. /**
  9. * A BBox request
  10. * @extends Request
  11. */
  12. class RequestBBox extends Request {
  13. /**
  14. * @param {OverpassFrontend} overpass
  15. * @param {object} options
  16. */
  17. constructor (overpass, data) {
  18. super(overpass, data)
  19. this.type = 'BBoxQuery'
  20. if (typeof this.options.properties === 'undefined') {
  21. this.options.properties = defines.DEFAULT
  22. }
  23. this.options.properties |= defines.BBOX
  24. this.options.minEffort = this.options.minEffort || 256
  25. // make sure the request ends with ';'
  26. if (!this.query.match(/;\s*$/)) {
  27. this.query += ';'
  28. }
  29. if (!('noCacheQuery' in this.options) || !this.options.noCacheQuery) {
  30. try {
  31. if (this.options.filter) {
  32. this.filterQuery = new Filter({ and: [this.query, this.options.filter] })
  33. this.query = this.filterQuery.toQl()
  34. } else {
  35. this.filterQuery = new Filter(this.query)
  36. }
  37. } catch (err) {
  38. return this.finish(err)
  39. }
  40. this.lokiQuery = this.filterQuery.toLokijs()
  41. this.lokiQueryNeedMatch = !!this.lokiQuery.needMatch
  42. delete this.lokiQuery.needMatch
  43. if (!boundsIsFullWorld(this.bounds)) {
  44. this.lokiQuery = { $and: [this.lokiQuery, boundsToLokiQuery(this.bbox, this.overpass)] }
  45. }
  46. const cacheFilter = new Filter({ and: [this.filterQuery, new Filter('nwr(properties:' + this.options.properties + ')')] })
  47. this.options.properties = cacheFilter.properties()
  48. this.cacheDescriptors = cacheFilter.cacheDescriptors().map(cacheDescriptors => {
  49. return {
  50. cache: this.overpass.bboxQueryCache.get(cacheDescriptors.id),
  51. cacheDescriptors
  52. }
  53. })
  54. }
  55. this.loadFinish = false
  56. if ('members' in this.options) {
  57. RequestBBoxMembers(this)
  58. }
  59. }
  60. /**
  61. * check if there are any map features which can be returned right now
  62. */
  63. preprocess () {
  64. let items = []
  65. if (this.lokiQuery) {
  66. items = this.overpass.db.find(this.lokiQuery)
  67. }
  68. for (let i = 0; i < items.length; i++) {
  69. if (this.options.limit && this.count >= this.options.limit) {
  70. this.loadFinish = true
  71. return
  72. }
  73. const id = items[i].id
  74. if (!(id in this.overpass.cacheElements)) {
  75. continue
  76. }
  77. const ob = this.overpass.cacheElements[id]
  78. if (id in this.doneFeatures) {
  79. continue
  80. }
  81. // maybe we need an additional check
  82. if (this.lokiQueryNeedMatch && !this.filterQuery.match(ob)) {
  83. continue
  84. }
  85. // also check the object directly if it intersects the bbox - if possible
  86. if (ob.intersects(this.bounds) < 2) {
  87. continue
  88. }
  89. if ((this.options.properties & ob.properties) === this.options.properties) {
  90. this.receiveObject(ob)
  91. this.featureCallback(null, ob)
  92. }
  93. }
  94. if (this.options.limit && this.count >= this.options.limit) {
  95. this.loadFinish = true
  96. }
  97. }
  98. /**
  99. * shall this Request be included in the current call?
  100. * @param {OverpassFrontend#Context} context - Current context
  101. * @return {boolean|int[]} - yes|no - or [ minEffort, maxEffort ]
  102. */
  103. willInclude (context) {
  104. if (this.loadFinish) {
  105. return false
  106. }
  107. if (context.bbox && context.bbox.toLatLonString() !== this.bbox.toLatLonString()) {
  108. return false
  109. }
  110. context.bbox = this.bbox
  111. for (const i in context.requests) {
  112. const request = context.requests[i]
  113. if (request instanceof RequestBBox && request.query === this.query) {
  114. return false
  115. }
  116. }
  117. return true
  118. }
  119. /**
  120. * how much effort can a call to this request use
  121. * @return {Request#minMaxEffortResult} - minimum and maximum effort
  122. */
  123. minMaxEffort () {
  124. if (this.loadFinish) {
  125. return { minEffort: 0, maxEffort: 0 }
  126. }
  127. let minEffort = this.options.minEffort
  128. let maxEffort = null
  129. if (this.options.limit) {
  130. maxEffort = (this.options.limit - this.count) * this.overpass.options.effortBBoxFeature
  131. minEffort = Math.min(minEffort, maxEffort)
  132. }
  133. return { minEffort, maxEffort }
  134. }
  135. /**
  136. * compile the query
  137. * @param {OverpassFrontend#Context} context - Current context
  138. * @return {Request#SubRequest|false} - the compiled query or false if the bbox does not match
  139. */
  140. _compileQuery (context) {
  141. if (this.loadFinish || (context.bbox && context.bbox.toLatLonString() !== this.bbox.toLatLonString())) {
  142. return {
  143. query: '',
  144. request: this,
  145. parts: [],
  146. effort: 0
  147. }
  148. }
  149. const efforts = this.minMaxEffort()
  150. let effortAvailable = Math.max(context.maxEffort, efforts.minEffort)
  151. if (efforts.maxEffort) {
  152. effortAvailable = Math.min(effortAvailable, efforts.maxEffort)
  153. }
  154. // if the context already has a bbox and it differs from this, we can't add
  155. // ours
  156. let query = this.query.substr(0, this.query.length - 1) + '->.result;\n'
  157. let queryRemoveDoneFeatures = ''
  158. let countRemoveDoneFeatures = 0
  159. for (const id in this.doneFeatures) {
  160. const ob = this.doneFeatures[id]
  161. if (countRemoveDoneFeatures % 1000 === 999) {
  162. query += '(' + queryRemoveDoneFeatures + ')->.done;\n'
  163. queryRemoveDoneFeatures = '.done;'
  164. }
  165. queryRemoveDoneFeatures += ob.type + '(' + ob.osm_id + ');'
  166. countRemoveDoneFeatures++
  167. }
  168. if (countRemoveDoneFeatures) {
  169. query += '(' + queryRemoveDoneFeatures + ')->.done;\n'
  170. query += '(.result; - .done;)->.result;\n'
  171. }
  172. if (!('split' in this.options)) {
  173. this.options.effortSplit = Math.ceil(effortAvailable / this.overpass.options.effortBBoxFeature)
  174. }
  175. query += '.result out ' + overpassOutOptions(this.options) + ';'
  176. const subRequest = {
  177. query,
  178. request: this,
  179. parts: [
  180. {
  181. properties: this.options.properties,
  182. receiveObject: this.receiveObject.bind(this),
  183. checkFeatureCallback: this.checkFeatureCallback.bind(this),
  184. featureCallback: this.featureCallback
  185. }
  186. ],
  187. effort: this.options.split ? this.options.split * this.overpass.options.effortBBoxFeature : effortAvailable
  188. }
  189. return subRequest
  190. }
  191. /**
  192. * receive an object from OverpassFronted -> enter to cache, return to caller
  193. * @param {OverpassObject} ob - Object which has been received
  194. * @param {Request#SubRequest} subRequest - sub request which is being handled right now
  195. * @param {int} partIndex - Which part of the subRequest is being received
  196. */
  197. receiveObject (ob) {
  198. super.receiveObject(ob)
  199. this.doneFeatures[ob.id] = ob
  200. }
  201. checkFeatureCallback (ob) {
  202. if (this.bounds && ob.intersects(this.bounds) === 0) {
  203. return false
  204. }
  205. return true
  206. }
  207. /**
  208. * the current subrequest is finished -> update caches, check whether request is finished
  209. * @param {Request#SubRequest} subRequest - the current sub request
  210. */
  211. finishSubRequest (subRequest) {
  212. super.finishSubRequest(subRequest)
  213. if (('effortSplit' in this.options && this.options.effortSplit > subRequest.parts[0].count) ||
  214. (this.options.split > subRequest.parts[0].count)) {
  215. this.loadFinish = true
  216. this.cacheDescriptors && this.cacheDescriptors.forEach(cache => {
  217. cache.cache.add(this.bbox, cache.cacheDescriptors)
  218. })
  219. }
  220. if (this.options.limit && this.options.limit <= this.count) {
  221. this.loadFinish = true
  222. }
  223. }
  224. /**
  225. * check if we need to call Overpass API. Maybe whole area is cached anyway?
  226. * @return {boolean} - true, if we need to call Overpass API
  227. */
  228. needLoad () {
  229. if (this.loadFinish) {
  230. return false
  231. }
  232. return !this.cacheDescriptors || !this.cacheDescriptors.every(cache => {
  233. return cache.cache.check(this.bbox, cache.cacheDescriptors)
  234. })
  235. }
  236. mayFinish () {
  237. return !this.needLoad()
  238. }
  239. }
  240. module.exports = RequestBBox