Source: Filter.js

  1. const turf = require('./turf')
  2. const strsearch2regexp = require('strsearch2regexp')
  3. const filterJoin = require('./filterJoin')
  4. const OverpassFrontend = require('./defines')
  5. const qlFunctions = require('./qlFunctions/__index__')
  6. const parseString = require('./parseString')
  7. const parseParentheses = require('./parseParentheses')
  8. const qlFunction = require('./qlFunctions/qlFunction')
  9. function qlesc (str) {
  10. return '"' + str.replace(/"/g, '\\"') + '"'
  11. }
  12. function compile (part, options = {}) {
  13. if (Array.isArray(part)) {
  14. return part.map(compile).join('')
  15. }
  16. if (part.or) {
  17. return { or: part.or.map(compile) }
  18. }
  19. const keyRegexp = (part.keyRegexp ? '~' : '')
  20. if (part instanceof qlFunction) {
  21. return part.toString(options)
  22. }
  23. if (part.type) {
  24. return part.type
  25. }
  26. switch (part.op) {
  27. case 'has_key':
  28. if (part.keyRegexp === 'i') {
  29. return '[~' + qlesc(part.key) + '~".",i]'
  30. } else if (keyRegexp) {
  31. return '[~' + qlesc(part.key) + '~"."]'
  32. } else {
  33. return '[' + keyRegexp + qlesc(part.key) + ']'
  34. }
  35. case 'not_exists':
  36. return '[!' + qlesc(part.key) + ']'
  37. case '=':
  38. case '!=':
  39. case '~':
  40. case '!~':
  41. return '[' + keyRegexp + qlesc(part.key) + part.op + qlesc(part.value) + ']'
  42. case '~i':
  43. case '!~i':
  44. return '[' + keyRegexp + qlesc(part.key) + part.op.substr(0, part.op.length - 1) + qlesc(part.value) + ',i]'
  45. case 'has':
  46. return '[' + keyRegexp + qlesc(part.key) + '~' + qlesc('^(.*;|)' + part.value + '(|;.*)$') + ']'
  47. case 'strsearch':
  48. return '[' + keyRegexp + qlesc(part.key) + '~' + qlesc(strsearch2regexp(part.value)) + ',i]'
  49. default:
  50. throw new Error('unknown operator' + JSON.stringify(part))
  51. }
  52. }
  53. function test (ob, part) {
  54. if (Array.isArray(part)) {
  55. return part.every(part => test(ob, part))
  56. }
  57. if (part.type) {
  58. return ob.type === part.type
  59. }
  60. if (part.or) {
  61. return part.or.some(part => test(ob, part))
  62. }
  63. if (part.and) {
  64. return part.and.every(part => test(ob, part))
  65. }
  66. if (part.keyRegexp) {
  67. let regex
  68. if (part.value) {
  69. regex = new RegExp(part.value, part.op.match(/i$/) ? 'i' : '')
  70. }
  71. const keyRegex = new RegExp(part.key, part.keyRegexp === 'i' ? 'i' : '')
  72. for (const k in ob.tags) {
  73. if (k.match(keyRegex)) {
  74. if (regex) {
  75. if (ob.tags[k].match(regex)) {
  76. return true
  77. }
  78. } else {
  79. return true
  80. }
  81. }
  82. }
  83. return false
  84. }
  85. if (part instanceof qlFunction) {
  86. return part.test(ob)
  87. }
  88. switch (part.op) {
  89. case 'has_key':
  90. return ob.tags && (part.key in ob.tags)
  91. case 'not_exists':
  92. return ob.tags && (!(part.key in ob.tags))
  93. case '=':
  94. return ob.tags && (part.key in ob.tags) && (ob.tags[part.key] === part.value)
  95. case '!=':
  96. return ob.tags && (!(part.key in ob.tags) || (ob.tags[part.key] !== part.value))
  97. case '~':
  98. return ob.tags && (part.key in ob.tags) && (ob.tags[part.key].match(part.value))
  99. case '!~':
  100. return ob.tags && (!(part.key in ob.tags) || (!ob.tags[part.key].match(part.value)))
  101. case '~i':
  102. return ob.tags && (part.key in ob.tags) && (ob.tags[part.key].match(new RegExp(part.value, 'i')))
  103. case '!~i':
  104. return ob.tags && (!(part.key in ob.tags) || !ob.tags[part.key].match(new RegExp(part.value, 'i')))
  105. case 'has':
  106. return ob.tags && (part.key in ob.tags) && (ob.tags[part.key].split(/;/).indexOf(part.value) !== -1)
  107. case 'strsearch':
  108. return ob.tags && (part.key in ob.tags) && (ob.tags[part.key].match(new RegExp(strsearch2regexp(part.value), 'i')))
  109. default:
  110. console.log('match: unknown operator in filter', part)
  111. return false
  112. }
  113. }
  114. function parse (def, rek = 0) {
  115. const script = []
  116. let current = []
  117. let mode = 0
  118. let key
  119. let value
  120. let op
  121. let m
  122. let keyRegexp = false
  123. let notExists = null
  124. while (true) {
  125. // console.log('rek' + rek, 'mode' + mode + '|', def.substr(0, 20), '->', script, 'next:', current)
  126. if (mode === 0) {
  127. if (def.match(/^\s*$/)) {
  128. return [rek === 0 && script.length === 1 ? script[0] : script, def]
  129. }
  130. keyRegexp = false
  131. m = def.match(/^\s*(node|way|relation|rel|nwr|\()/)
  132. if (m && m[1] === '(') {
  133. def = def.slice(m[0].length)
  134. let parts
  135. [parts, def] = parse(def, rek + 1)
  136. mode = 1
  137. script.push({ or: parts })
  138. current = []
  139. } else if (m) {
  140. if (m[1] === 'rel') {
  141. current.push({ type: 'relation' })
  142. } else if (m[1] === 'nwr') {
  143. // nothing
  144. } else {
  145. current.push({ type: m[1] })
  146. }
  147. mode = 10
  148. def = def.slice(m[0].length)
  149. } else {
  150. throw new Error("Can't parse query, expected type of object (e.g. 'node'): " + def)
  151. }
  152. } else if (mode === 1) {
  153. m = def.match(/^\s*\)\s*;?\s*/)
  154. if (m) {
  155. if (rek === 0) {
  156. return [script.length === 1 ? script[0] : script, def]
  157. } else {
  158. def = def.slice(m[0].length)
  159. return [script, def]
  160. }
  161. } else {
  162. mode = 0
  163. }
  164. } else if (mode === 10) {
  165. const m = def.match(/^\s*(\[|\(|;)/)
  166. if (m && m[1] === '[') {
  167. def = def.slice(m[0].length)
  168. mode = 11
  169. } else if (m && m[1] === '(') {
  170. def = def.slice(m[0].length - 1)
  171. mode = 20
  172. } else if (m && m[1] === ';') {
  173. def = def.slice(m[0].length)
  174. script.push(current)
  175. current = []
  176. notExists = null
  177. mode = 1
  178. } else if (!m && def.match(/^\s*$/)) {
  179. if (current.length) {
  180. script.push(current)
  181. }
  182. return [rek === 0 && script.length === 1 ? script[0] : script, '']
  183. } else {
  184. throw new Error("Can't parse query, expected '[' or ';': " + def)
  185. }
  186. } else if (mode === 11) {
  187. m = def.match(/^(\s*)(([~!])\s*)?([a-zA-Z0-9_]+|"|')/)
  188. if (m && m[2]) {
  189. if (m[3] === '~') {
  190. keyRegexp = true
  191. } else if (m[3] === '!') {
  192. notExists = true
  193. }
  194. }
  195. if (m && (m[4] === '"' || m[4] === "'")) {
  196. def = def.slice(m[1].length + (m[2] || '').length)
  197. const x = parseString(def)
  198. key = x[0]
  199. def = x[1]
  200. mode = 12
  201. } else if (m) {
  202. key = m[4]
  203. def = def.slice(m[0].length)
  204. mode = 12
  205. } else {
  206. throw new Error("Can't parse query, expected key: " + def)
  207. }
  208. } else if (mode === 12) {
  209. m = def.match(/^\s*(=|!=|~|!~|\^|]|%)/)
  210. if (m && m[1] === ']') {
  211. const entry = { key, op: 'has_key' }
  212. if (keyRegexp) {
  213. entry.keyRegexp = true
  214. }
  215. if (notExists) {
  216. entry.op = 'not_exists'
  217. }
  218. current.push(entry)
  219. def = def.slice(m[0].length)
  220. notExists = null
  221. mode = 10
  222. } else if (m) {
  223. if (notExists) {
  224. throw new Error("Can't parse query, expected ']': " + def)
  225. }
  226. op = m[1] === '^' ? 'has' : m[1] === '%' ? 'strsearch' : m[1]
  227. mode = 13
  228. def = def.slice(m[0].length)
  229. } else {
  230. throw new Error("Can't parse query, expected operator or ']': " + def)
  231. }
  232. } else if (mode === 13) {
  233. m = def.match(/^(\s*)([a-zA-Z0-9_]+|"|')/)
  234. if (m && (m[2] === '"' || m[2] === "'")) {
  235. def = def.slice(m[1].length)
  236. const x = parseString(def)
  237. value = x[0]
  238. def = x[1]
  239. mode = 14
  240. } else if (m) {
  241. value = m[2]
  242. def = def.slice(m[0].length)
  243. mode = 14
  244. } else {
  245. throw new Error("Can't parse query, expected value: " + def)
  246. }
  247. } else if (mode === 14) {
  248. m = def.match(/^\s*(,i)?\]/)
  249. if (m) {
  250. if (m[1] === ',i') {
  251. if (op === '~' || op === '!~') {
  252. op += 'i'
  253. } else {
  254. throw new Error("Can't parse query, expected ']': " + def)
  255. }
  256. }
  257. const entry = { key, op }
  258. if (value === '.') {
  259. entry.op = 'has_key'
  260. } else {
  261. entry.value = value
  262. }
  263. if (keyRegexp) {
  264. entry.keyRegexp = op.match(/^!?~i$/) ? 'i' : true
  265. }
  266. current.push(entry)
  267. mode = 10
  268. def = def.slice(m[0].length)
  269. } else {
  270. throw new Error("Can't parse query, expected ']': " + def)
  271. }
  272. } else if (mode === 20) {
  273. const r = parseParentheses(def)
  274. def = r[1]
  275. const mId = r[0].match(/^\s*(\d+)\s*$/)
  276. const mBbox = r[0].match(/^((\s*-?\d+(.\d+)?\s*,){3}\s*-?\d+(.\d+)?\s*)$/)
  277. const m = r[0].match(/^\s*(\w+)\s*:\s*(.*)\s*$/)
  278. /* eslint-disable new-cap */
  279. if (mId) {
  280. current.push(new qlFunctions.id(mId[1]))
  281. mode = 10
  282. } else if (mBbox) {
  283. current.push(new qlFunctions.bbox(mBbox[1]))
  284. mode = 10
  285. } else if (m) {
  286. const fun = m[1]
  287. if (!qlFunctions[fun]) {
  288. throw new Error('Unsupported filter function: ' + fun)
  289. }
  290. current.push(new qlFunctions[fun](m[2]))
  291. mode = 10
  292. } else {
  293. throw new Error("Can't parse query, expected id, bbox or function: " + def)
  294. }
  295. /* eslint-enable new-cap */
  296. }
  297. }
  298. }
  299. function check (def) {
  300. if (typeof def === 'string') {
  301. const result = parse(def)
  302. if (result[1].trim()) {
  303. throw new Error("Can't parse query, trailing characters: " + result[1])
  304. }
  305. return result[0]
  306. } else if (def === null) {
  307. return
  308. } else if (typeof def === 'object' && def instanceof Filter) {
  309. def = def.def
  310. } else if (Array.isArray(def)) {
  311. def = def.map(d => check(d))
  312. }
  313. if (def.and) {
  314. def.and = def.and.map(p => check(p))
  315. }
  316. if (def.or) {
  317. def.or = def.or.map(p => check(p))
  318. } else if (def.fun && !(def instanceof qlFunction)) {
  319. def = new qlFunctions[def.fun](def.value)
  320. }
  321. return def
  322. }
  323. /**
  324. * A Filter into OSM data. A simplified version of <a href='https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL'>Overpass QL</a>.
  325. *
  326. * <p>Either a single query (e.g. <tt>node[amenity=restaurant];</tt>) or a combined query (e.g. <tt>(node[amenity=restaurant];way[amenity=restaurant];);</tt>).<br>
  327. * A single query statement consists of a type (e.g. 'node', 'way', 'relation', 'nwr' (node, way or relation)) and optional filters:<ul>
  328. * <li>(Not) Equals (=, !=): <tt>[amenity=restaurant]</tt> or <tt>["amenity"="restaurant"]</tt> resp. <tt>["amenity"!="restaurant"]</tt>.
  329. * <li>Regular Expression: <tt>[amenity~"^(restaurant|cafe)$"]</tt> resp. negated: <tt>[amenity!~"^(restaurant|cafe)$"]</tt>
  330. * <li>Key regular expression: <tt>[~"cycleway"~"left"]</tt> (key has to match cycleway and its value match left)
  331. * <li>Key (not) exists: <tt>[amenity]</tt> or <tt>["amenity"]</tt> resp. <tt>[!amenity]</tt>
  332. * <li>Array search: <tt>[cuisine^kebap]</tt>: search for cuisine tags which exactly include 'kebap' (semicolon-separated values, e.g. <tt>cuisine=kebap;pizza</tt>).
  333. * <li>String search: <tt>[name%cafe]</tt>: search for name tags which are similar to cafe, e.g. "café". (see https://github.com/plepe/strsearch2regexp for details).
  334. * </ul>
  335. * More advanced queries are not supported.</p>
  336. *
  337. * @param {string|object} query
  338. */
  339. class Filter {
  340. uniqId () {
  341. this._uniqId = (this._uniqId || 0) + 1
  342. return this._uniqId
  343. }
  344. constructor (def) {
  345. if (!def) {
  346. this.def = []
  347. return
  348. }
  349. this.def = check(def)
  350. }
  351. /**
  352. * Check if an object matches this filter
  353. * @param {OverpassNode|OverpassWay|OverpassRelation} ob an object from Overpass API
  354. * @return {boolean}
  355. */
  356. match (ob, def) {
  357. if (!def) {
  358. def = this.def
  359. }
  360. if (Array.isArray(def) && Array.isArray(def[0])) {
  361. // script with several statements detected. only use the last one, as previous statements
  362. // can't have an effect on the last statement yet.
  363. def = def[def.length - 1]
  364. }
  365. if (def.or) {
  366. return def.or.some(part => this.match(ob, part))
  367. }
  368. if (def.and) {
  369. return def.and.every(test.bind(this, ob))
  370. }
  371. return def.filter(test.bind(this, ob)).length === def.length
  372. }
  373. /**
  374. * Convert query to a string representation
  375. * @return {string}
  376. */
  377. toString (def) {
  378. return this.toQl({ toString: true }, def)
  379. }
  380. /**
  381. * Convert query to Overpass QL
  382. * @param {object} [options] Additional options
  383. * @param {string} [options.inputSet=''] Specify input set (e.g.'.foo').
  384. * @param {string} [options.outputSet=''] Specify output set (e.g.'.foo').
  385. * @return {string}
  386. */
  387. toQl (options = {}, def) {
  388. if (!def) {
  389. def = this.def
  390. }
  391. if (Array.isArray(def) && Array.isArray(def[0])) {
  392. return def.map(d => this.toQl(options, d)).join('')
  393. }
  394. if (def.or) {
  395. return '(' + def.or.map(part => {
  396. const subOptions = JSON.parse(JSON.stringify(options))
  397. subOptions.inputSet = options.inputSet
  398. subOptions.outputSet = ''
  399. return this.toQl(subOptions, part)
  400. }).join('') + ')' + (options.outputSet ? '->' + options.outputSet : '') + ';'
  401. }
  402. if (def.and) {
  403. const first = def.and[0]
  404. const last = def.and[def.and.length - 1]
  405. const others = def.and.concat().slice(1, def.and.length - 1)
  406. const set = '.x' + this.uniqId()
  407. const subOptions1 = JSON.parse(JSON.stringify(options))
  408. const subOptions2 = JSON.parse(JSON.stringify(options))
  409. const subOptions3 = JSON.parse(JSON.stringify(options))
  410. subOptions1.outputSet = set
  411. subOptions2.inputSet = set
  412. subOptions2.outputSet = set
  413. subOptions3.inputSet = set
  414. return this.toQl(subOptions1, first) +
  415. others.map(part => this.toQl(subOptions2, part)).join('') +
  416. this.toQl(subOptions3, last)
  417. }
  418. if (!options.inputSet) {
  419. options.inputSet = ''
  420. }
  421. if (!options.outputSet) {
  422. options.outputSet = ''
  423. }
  424. const parts = def.filter(part => part.type)
  425. let types
  426. switch (parts.length) {
  427. case 0:
  428. types = ['nwr']
  429. break
  430. case 1:
  431. types = [parts[0].type]
  432. break
  433. default:
  434. throw new Error('Filter: only one type query allowed!')
  435. }
  436. const queries = filterJoin(def
  437. .filter(part => !part.type)
  438. .map(part => compile(part, options)))
  439. let result
  440. if (queries.length > 1) {
  441. result = '(' + queries.map(q => types.map(type => type + options.inputSet + q).join(';')).join(';') + ';)'
  442. } else if (types.length === 1) {
  443. result = types[0] + options.inputSet + queries[0]
  444. } else {
  445. result = '(' + types.map(type => type + options.inputSet + queries[0]).join(';') + ';)'
  446. }
  447. return result + (options.outputSet ? '->' + options.outputSet : '') + ';'
  448. }
  449. /**
  450. * Convert query to LokiJS query for local database. If the property 'needMatch' is set on the returned object, an additional match() should be executed for each returned object, as the query can't be fully compiled (and the 'needMatch' property removed).
  451. * @param {object} [options] Additional options
  452. * @return {object}
  453. */
  454. toLokijs (options = {}, def) {
  455. if (!def) {
  456. def = this.def
  457. }
  458. if (Array.isArray(def) && Array.isArray(def[0])) {
  459. // script with several statements detected. only compile the last one, as previous statements
  460. // can't have an effect on the last statement yet.
  461. def = def[def.length - 1]
  462. }
  463. if (def.or) {
  464. let needMatch = false
  465. const r = {
  466. $or:
  467. def.or.map(part => {
  468. const r = this.toLokijs(options, part)
  469. if (r.needMatch) {
  470. needMatch = true
  471. }
  472. delete r.needMatch
  473. return r
  474. })
  475. }
  476. // if the $or has elements that are always true, remove whole $or
  477. if (r.$or.filter(p => Object.keys(p).length === 0).length > 0) {
  478. delete r.$or
  479. }
  480. if (needMatch) {
  481. r.needMatch = true
  482. }
  483. return r
  484. }
  485. if (def.and) {
  486. let needMatch = false
  487. const r = {
  488. $and:
  489. def.and
  490. .map(part => {
  491. const r = this.toLokijs(options, part)
  492. if (r.needMatch) {
  493. needMatch = true
  494. }
  495. delete r.needMatch
  496. return r
  497. })
  498. .filter(part => Object.keys(part).length)
  499. }
  500. if (needMatch) {
  501. r.needMatch = true
  502. }
  503. return r
  504. }
  505. const query = {}
  506. let orQueries = []
  507. if (!Array.isArray(def)) {
  508. def = [def]
  509. }
  510. def.forEach(filter => {
  511. let k, v
  512. if (filter.keyRegexp) {
  513. k = 'needMatch'
  514. v = true
  515. // can't query for key regexp, skip
  516. } else if (filter instanceof qlFunction) {
  517. const d = filter.compileLokiJS()
  518. if (d.needMatch) {
  519. query.needMatch = true
  520. }
  521. delete d.needMatch
  522. if (Object.keys(d).length) {
  523. if (query.$and) {
  524. query.$and.push(d)
  525. } else {
  526. query.$and = [d]
  527. }
  528. }
  529. } else if (filter.op === '=') {
  530. k = 'tags.' + filter.key
  531. v = { $eq: filter.value }
  532. } else if (filter.op === '!=') {
  533. k = 'tags.' + filter.key
  534. v = { $ne: filter.value }
  535. } else if (filter.op === 'has_key') {
  536. k = 'tags.' + filter.key
  537. v = { $exists: true }
  538. } else if (filter.op === 'not_exists') {
  539. k = 'tags.' + filter.key
  540. v = { $exists: false }
  541. } else if (filter.op === 'has') {
  542. k = 'tags.' + filter.key
  543. v = { $regex: '^(.*;|)' + filter.value + '(|;.*)$' }
  544. } else if ((filter.op === '~') || (filter.op === '~i')) {
  545. k = 'tags.' + filter.key
  546. v = { $regex: new RegExp(filter.value, (filter.op === '~i' ? 'i' : '')) }
  547. } else if ((filter.op === '!~') || (filter.op === '!~i')) {
  548. k = 'tags.' + filter.key
  549. v = { $not: { $regex: new RegExp(filter.value, (filter.op === '!~i' ? 'i' : '')) } }
  550. } else if (filter.op === 'strsearch') {
  551. k = 'tags.' + filter.key
  552. v = { $regex: new RegExp(strsearch2regexp(filter.value), 'i') }
  553. } else if (filter.type) {
  554. k = 'type'
  555. v = { $eq: filter.type }
  556. } else if (filter.or) {
  557. orQueries.push(filter.or.map(p => {
  558. const r = this.toLokijs(options, p)
  559. if (r.needMatch) {
  560. query.needMatch = true
  561. delete r.needMatch
  562. }
  563. return r
  564. }))
  565. } else {
  566. console.log('unknown filter', filter)
  567. }
  568. if (k && v) {
  569. if (k === 'needMatch') {
  570. query.needMatch = true
  571. } else if (k in query) {
  572. if (!('$and' in query[k])) {
  573. query[k] = { $and: [query[k]] }
  574. }
  575. query[k].$and.push(v)
  576. } else {
  577. query[k] = v
  578. }
  579. }
  580. })
  581. orQueries = orQueries.filter(q => Object.keys(q).length)
  582. if (orQueries.length === 1) {
  583. query.$or = orQueries[0]
  584. } else if (orQueries.length > 1) {
  585. query.$and = orQueries.map(q => { return { $or: q } })
  586. }
  587. return query
  588. }
  589. cacheDescriptors () {
  590. let result
  591. if (Array.isArray(this.def) && Array.isArray(this.def[0])) {
  592. // script with several statements detected. only compile the last one, as previous statements
  593. // can't have an effect on the last statement yet.
  594. result = this._caches(this.def[this.def.length - 1])
  595. } else {
  596. result = this._caches(this.def)
  597. }
  598. result.forEach(entry => {
  599. entry.id = (entry.type || 'nwr') + entry.filters + '(properties:' + entry.properties + ')'
  600. delete entry.type
  601. delete entry.filters
  602. delete entry.properties
  603. })
  604. return result
  605. }
  606. _caches (def) {
  607. let options = [{ filters: '', properties: 0 }]
  608. if (def.or) {
  609. let result = []
  610. def.or.forEach(e => {
  611. const r = this._caches(e)
  612. if (Array.isArray(r)) {
  613. result = result.concat(r)
  614. } else {
  615. result.push(r)
  616. }
  617. })
  618. return result
  619. }
  620. if (!Array.isArray(def)) {
  621. def = [def]
  622. }
  623. def.forEach(part => {
  624. if (part.type) {
  625. options = options.map(o => {
  626. if (o.type && o.type !== part.type) {
  627. o.type = '-'
  628. } else {
  629. o.type = part.type
  630. }
  631. return o
  632. })
  633. } else if (part.op) {
  634. options = options.map(o => {
  635. o.filters += compile(part)
  636. o.properties |= OverpassFrontend.TAGS
  637. return o
  638. })
  639. } else if (part instanceof qlFunction) {
  640. part.cacheDescriptors(options)
  641. } else if (part.or) {
  642. const result = []
  643. part.or.forEach(e => {
  644. const r = this._caches(e)
  645. options.forEach(o => {
  646. r.forEach(r1 => {
  647. result.push(this._cacheMerge(o, r1))
  648. })
  649. })
  650. })
  651. options = result
  652. } else if (part.and) {
  653. let result = options
  654. part.and.forEach(e => {
  655. const current = result
  656. result = []
  657. const r = this._caches(e)
  658. r.forEach(r1 => {
  659. current.forEach(c => {
  660. result.push(this._cacheMerge(c, r1))
  661. })
  662. })
  663. })
  664. options = result
  665. } else {
  666. throw new Error('caches(): invalid entry')
  667. }
  668. })
  669. return options
  670. }
  671. _cacheMerge (a, b) {
  672. const r = {}
  673. for (const k in a) {
  674. r[k] = a[k]
  675. }
  676. r.filters += b.filters
  677. r.properties |= b.properties
  678. if (b.type) {
  679. if (a.type && a.type !== b.type) {
  680. r.type = '-'
  681. } else {
  682. r.type = b.type
  683. }
  684. }
  685. if (b.ids) {
  686. r.ids = b.ids
  687. if (a.ids) {
  688. r.ids = b.ids.filter(n => a.ids.includes(n))
  689. }
  690. }
  691. if (b.invalid) {
  692. r.invalid = true
  693. }
  694. if (b.bounds && a.bounds) {
  695. const mergeBounds = turf.intersect(a.bounds, b.bounds)
  696. if (mergeBounds) {
  697. r.bounds = mergeBounds.geometry
  698. } else {
  699. r.invalid = true
  700. delete r.bounds
  701. }
  702. } else if (b.bounds) {
  703. r.bounds = b.bounds
  704. }
  705. return r
  706. }
  707. /**
  708. * compare this filter with an other filter.
  709. * @param Filter other the other filter.
  710. * @return boolean true, if the current filter is equal other or a super-set of other.
  711. */
  712. isSupersetOf (other) {
  713. return this._isSupersetOf(this.def, other.def)
  714. }
  715. _isSupersetOf (def, otherDef) {
  716. if (def.or) {
  717. return def.or.some(d => this._isSupersetOf(d, otherDef))
  718. }
  719. if (def.and) {
  720. return def.and.every(d => this._isSupersetOf(d, otherDef))
  721. }
  722. if (otherDef.or) {
  723. return otherDef.or.every(d => this._isSupersetOf(def, d))
  724. }
  725. if (otherDef.and) {
  726. return otherDef.and.some(d => this._isSupersetOf(def, d))
  727. }
  728. // search for something, where otherPart is not equal or subset
  729. return !def.some(part => {
  730. return !otherDef.some(otherPart => {
  731. if (part.type && otherPart.type) {
  732. return part.type === otherPart.type
  733. }
  734. if (compile(otherPart, { toString: true }) === compile(part, { toString: true })) {
  735. return true
  736. }
  737. if (['~', '~i'].includes(part.op) && otherPart.op === '=' && part.key === otherPart.key && otherPart.value.match(RegExp(part.value, part.op === '~i' ? 'i' : ''))) {
  738. return true
  739. }
  740. if (['~', '~i'].includes(part.op) && part.keyRegexp && otherPart.op === '=' && otherPart.key.match(RegExp(part.key, part.keyRegexp === 'i' ? 'i' : '')) && otherPart.value.match(RegExp(part.value, part.op === '~i' ? 'i' : ''))) {
  741. return true
  742. }
  743. if (part.op === 'has_key' && otherPart.op && !['!=', '!~', '!~i', 'not_exists'].includes(otherPart.op) && part.key === otherPart.key) {
  744. return true
  745. }
  746. if (part.op === 'has_key' && part.keyRegexp && otherPart.op && !['!=', '!~', '!~i', 'not_exists'].includes(otherPart.op) && otherPart.key.match(RegExp(part.key, part.keyRegexp === 'i' ? 'i' : ''))) {
  747. return true
  748. }
  749. if (part instanceof qlFunction && otherPart instanceof qlFunction && part.isSupersetOf(otherPart)) {
  750. return true
  751. }
  752. return false
  753. })
  754. })
  755. }
  756. /**
  757. * @returns {number} properties which are required for this filter
  758. */
  759. properties () {
  760. let result
  761. if (Array.isArray(this.def) && Array.isArray(this.def[0])) {
  762. // script with several statements detected. only compile the last one, as previous statements
  763. // can't have an effect on the last statement yet.
  764. result = this._caches(this.def[this.def.length - 1])
  765. } else {
  766. result = this._caches(this.def)
  767. }
  768. return result.reduce((current, entry) => current | entry.properties, 0)
  769. }
  770. }
  771. module.exports = Filter