UNPKG

12.8 kBJavaScriptView Raw
1// This query is used for component geocodes, where the individual fields have
2// been specified by the user and therefore doesn't need to be parsed. It functions
3// much like FallbackQuery but with (currently) one notable exceptions:
4//
5// - if locality is available but borough isn't, query borough layer with locality value
6// - boosts are hardcoded
7
8var _ = require('lodash');
9var baseQuery = require('./baseQuery');
10
11function Layout(){
12 this._score = [];
13 this._filter = [];
14}
15
16Layout.prototype.score = function( view ){
17 this._score.push( view );
18 return this;
19};
20
21Layout.prototype.filter = function( view ){
22 this._filter.push( view );
23 return this;
24};
25
26function addPrimary(value, layer, fields) {
27 // base primary query should match on layer and one of the admin fields via
28 // multi_match
29 var o = {
30 bool: {
31 _name: 'fallback.' + layer,
32 must: [
33 {
34 multi_match: {
35 query: value,
36 type: 'phrase',
37 fields: fields
38 }
39 }
40 ],
41 filter: {
42 term: {
43 layer: layer
44 }
45 }
46 }
47 };
48
49 return o;
50
51}
52
53// Secondary matches are for less granular administrative areas that have been
54// specified. For example, in "Socorro, NM", "Socorro" is primary, whereas
55// "NM" is secondary.
56function addSecondary(value, fields) {
57 return {
58 multi_match: {
59 query: value,
60 type: 'phrase',
61 fields: fields
62 }
63 };
64
65}
66
67// add the postal code if supplied
68function addSecPostCode(vs, o) {
69 // add postcode if specified
70 if (vs.isset('input:postcode')) {
71 o.bool.should.push({
72 match_phrase: {
73 'address_parts.zip': {
74 query: vs.var('input:postcode').toString()
75 }
76 }
77 });
78 }
79}
80
81function addSecNeighbourhood(vs, o) {
82 // add neighbourhood if specified
83 if (vs.isset('input:neighbourhood')) {
84 o.bool.must.push(addSecondary(
85 vs.var('input:neighbourhood').toString(),
86 [
87 'parent.neighbourhood',
88 'parent.neighbourhood_a'
89 ]
90 ));
91 }
92}
93
94function addSecBorough(vs, o) {
95 // add borough if specified
96 if (vs.isset('input:borough')) {
97 o.bool.must.push(addSecondary(
98 vs.var('input:borough').toString(),
99 [
100 'parent.borough',
101 'parent.borough_a'
102 ]
103 ));
104 }
105}
106
107function addSecLocality(vs, o) {
108 // add locality if specified
109 if (vs.isset('input:locality')) {
110 o.bool.must.push(addSecondary(
111 vs.var('input:locality').toString(),
112 [
113 'parent.locality',
114 'parent.locality_a',
115 'parent.localadmin',
116 'parent.localadmin_a'
117 ]
118 ));
119 }
120}
121
122function addSecCounty(vs, o) {
123 // add county if specified
124 if (vs.isset('input:county')) {
125 o.bool.must.push(addSecondary(
126 vs.var('input:county').toString(),
127 [
128 'parent.county',
129 'parent.county_a',
130 'parent.macrocounty',
131 'parent.macrocounty_a'
132 ]
133 ));
134 }
135}
136
137function addSecRegion(vs, o) {
138 // add region if specified
139 if (vs.isset('input:region')) {
140 o.bool.must.push(addSecondary(
141 vs.var('input:region').toString(),
142 [
143 'parent.region',
144 'parent.region_a',
145 'parent.macroregion',
146 'parent.macroregion_a'
147 ]
148 ));
149 }
150}
151
152function addSecCountry(vs, o) {
153 // add country if specified
154 if (vs.isset('input:country')) {
155 o.bool.must.push(addSecondary(
156 vs.var('input:country').toString(),
157 [
158 'parent.country',
159 'parent.country_a',
160 'parent.dependency',
161 'parent.dependency_a'
162 ]
163 ));
164 }
165}
166
167
168function addQuery(vs) {
169 var o = addPrimary(
170 vs.var('input:query').toString(),
171 'venue',
172 [
173 'phrase.default',
174 'category'
175 ],
176 false
177 );
178
179 addSecNeighbourhood(vs, o);
180 addSecBorough(vs, o);
181 addSecLocality(vs, o);
182 addSecCounty(vs, o);
183 addSecRegion(vs, o);
184 addSecCountry(vs, o);
185
186 return o;
187
188}
189
190function addUnitAndHouseNumberAndStreet(vs) {
191 var o = {
192 bool: {
193 _name: 'fallback.address',
194 must: [
195 {
196 match_phrase: {
197 'address_parts.unit': {
198 query: vs.var('input:unit').toString()
199 }
200 }
201 },
202 {
203 match_phrase: {
204 'address_parts.number': {
205 query: vs.var('input:housenumber').toString()
206 }
207 }
208 },
209 {
210 match_phrase: {
211 'address_parts.street': {
212 query: vs.var('input:street').toString()
213 }
214 }
215 }
216 ],
217 should: [],
218 filter: {
219 term: {
220 layer: 'address'
221 }
222 }
223 }
224 };
225
226 if (vs.isset('boost:address')) {
227 o.bool.boost = vs.var('boost:address');
228 }
229
230 addSecPostCode(vs, o);
231 addSecNeighbourhood(vs, o);
232 addSecBorough(vs, o);
233 addSecLocality(vs, o);
234 addSecCounty(vs, o);
235 addSecRegion(vs, o);
236 addSecCountry(vs, o);
237
238 return o;
239
240}
241
242function addHouseNumber(vs) {
243 var o = {
244 bool: {
245 _name: 'fallback.housenumber',
246 must: [
247 {
248 match_phrase: {
249 'address_parts.number': {
250 query: vs.var('input:housenumber').toString()
251 }
252 }
253 }
254 ],
255 should: [],
256 filter: {
257 term: {
258 layer: 'address'
259 }
260 }
261 }
262 };
263
264 if (vs.isset('boost:address')) {
265 o.bool.boost = vs.var('boost:address');
266 }
267
268 addSecPostCode(vs, o);
269 addSecNeighbourhood(vs, o);
270 addSecBorough(vs, o);
271 addSecLocality(vs, o);
272 addSecCounty(vs, o);
273 addSecRegion(vs, o);
274 addSecCountry(vs, o);
275
276 return o;
277}
278
279function addHouseNumberAndStreet(vs) {
280 var o = {
281 bool: {
282 _name: 'fallback.address',
283 must: [
284 {
285 match_phrase: {
286 'address_parts.number': {
287 query: vs.var('input:housenumber').toString()
288 }
289 }
290 },
291 {
292 match_phrase: {
293 'address_parts.street': {
294 query: vs.var('input:street').toString()
295 }
296 }
297 }
298 ],
299 should: [],
300 filter: {
301 term: {
302 layer: 'address'
303 }
304 }
305 }
306 };
307
308 if (vs.isset('boost:address')) {
309 o.bool.boost = vs.var('boost:address');
310 }
311
312 addSecPostCode(vs, o);
313 addSecNeighbourhood(vs, o);
314 addSecBorough(vs, o);
315 addSecLocality(vs, o);
316 addSecCounty(vs, o);
317 addSecRegion(vs, o);
318 addSecCountry(vs, o);
319
320 return o;
321
322}
323
324function addStreet(vs) {
325 var o = {
326 bool: {
327 _name: 'fallback.street',
328 must: [
329 {
330 match_phrase: {
331 'address_parts.street': {
332 query: vs.var('input:street').toString()
333 }
334 }
335 }
336 ],
337 should: [],
338 filter: {
339 term: {
340 layer: 'street'
341 }
342 }
343 }
344 };
345
346 if (vs.isset('boost:street')) {
347 o.bool.boost = vs.var('boost:street');
348 }
349
350 addSecPostCode(vs, o);
351 addSecNeighbourhood(vs, o);
352 addSecBorough(vs, o);
353 addSecLocality(vs, o);
354 addSecCounty(vs, o);
355 addSecRegion(vs, o);
356 addSecCountry(vs, o);
357
358 return o;
359
360}
361
362function addNeighbourhood(vs) {
363 var o = addPrimary(
364 vs.var('input:neighbourhood').toString(),
365 'neighbourhood',
366 [
367 'parent.neighbourhood',
368 'parent.neighbourhood_a'
369 ],
370 false
371 );
372
373 addSecBorough(vs, o);
374 addSecLocality(vs, o);
375 addSecCounty(vs, o);
376 addSecRegion(vs, o);
377 addSecCountry(vs, o);
378
379 return o;
380
381}
382
383function addBorough(vs) {
384 var o = addPrimary(
385 vs.var('input:borough').toString(),
386 'borough',
387 [
388 'parent.borough',
389 'parent.borough_a'
390 ],
391 false
392 );
393
394 addSecLocality(vs, o);
395 addSecCounty(vs, o);
396 addSecRegion(vs, o);
397 addSecCountry(vs, o);
398
399 return o;
400
401}
402
403function addLocalityAsBorough(vs) {
404 var o = addPrimary(vs.var('input:locality').toString(),
405 'borough', ['parent.borough', 'parent.borough_a'], false);
406
407 addSecCounty(vs, o);
408 addSecRegion(vs, o);
409 addSecCountry(vs, o);
410
411 return o;
412
413}
414
415function addLocality(vs) {
416 var o = addPrimary(vs.var('input:locality').toString(),
417 'locality', ['parent.locality', 'parent.locality_a'], false);
418
419 addSecCounty(vs, o);
420 addSecRegion(vs, o);
421 addSecCountry(vs, o);
422
423 return o;
424
425}
426
427function addLocalAdmin(vs) {
428 var o = addPrimary(vs.var('input:locality').toString(),
429 'localadmin', ['parent.localadmin', 'parent.localadmin_a'], false);
430
431 addSecCounty(vs, o);
432 addSecRegion(vs, o);
433 addSecCountry(vs, o);
434
435 return o;
436
437}
438
439function addCounty(vs) {
440 var o = addPrimary(
441 vs.var('input:county').toString(),
442 'county',
443 [
444 'parent.county',
445 'parent.county_a'
446 ],
447 false
448 );
449
450 addSecRegion(vs, o);
451 addSecCountry(vs, o);
452
453 return o;
454
455}
456
457function addMacroCounty(vs) {
458 var o = addPrimary(
459 vs.var('input:county').toString(),
460 'macrocounty',
461 [
462 'parent.macrocounty',
463 'parent.macrocounty_a'
464 ],
465 false
466 );
467
468 addSecRegion(vs, o);
469 addSecCountry(vs, o);
470
471 return o;
472
473}
474
475function addRegion(vs) {
476 var o = addPrimary(
477 vs.var('input:region').toString(),
478 'region',
479 [
480 'parent.region',
481 'parent.region_a'
482 ],
483 true
484 );
485
486 addSecCountry(vs, o);
487
488 return o;
489
490}
491
492function addMacroRegion(vs) {
493 var o = addPrimary(
494 vs.var('input:region').toString(),
495 'macroregion',
496 [
497 'parent.macroregion',
498 'parent.macroregion_a'
499 ],
500 true
501 );
502
503 addSecCountry(vs, o);
504
505 return o;
506
507}
508
509function addDependency(vs) {
510 var o = addPrimary(
511 vs.var('input:country').toString(),
512 'dependency',
513 [
514 'parent.dependency',
515 'parent.dependency_a'
516 ],
517 true
518 );
519
520 return o;
521
522}
523
524function addCountry(vs) {
525 var o = addPrimary(
526 vs.var('input:country').toString(),
527 'country',
528 [
529 'parent.country',
530 'parent.country_a'
531 ],
532 true
533 );
534
535 return o;
536
537}
538
539function addPostCode(vs) {
540 var o = addPrimary(
541 vs.var('input:postcode').toString(),
542 'postalcode',
543 [
544 'parent.postalcode'
545 ],
546 false
547 );
548
549 // same position in hierarchy as borough according to WOF
550 // https://github.com/whosonfirst/whosonfirst-placetypes#here-is-a-pretty-picture
551 addSecLocality(vs, o);
552 addSecCounty(vs, o);
553 addSecRegion(vs, o);
554 addSecCountry(vs, o);
555
556 return o;
557
558}
559
560
561Layout.prototype.render = function( vs ){
562 var q = Layout.base( vs );
563
564 var funcScoreShould = q.query.function_score.query.bool.should;
565
566 if (vs.isset('input:query')) {
567 funcScoreShould.push(addQuery(vs));
568 }
569 if (vs.isset('input:street')) {
570 if (vs.isset('input:unit') && vs.isset('input:housenumber')) {
571 funcScoreShould.push(addUnitAndHouseNumberAndStreet(vs));
572 } else if (vs.isset('input:housenumber')) {
573 funcScoreShould.push(addHouseNumberAndStreet(vs));
574 }
575 funcScoreShould.push(addStreet(vs));
576 } else if (vs.isset('input:housenumber')) {
577 funcScoreShould.push(addHouseNumber(vs));
578 }
579
580 if (vs.isset('input:postcode')) {
581 funcScoreShould.push(addPostCode(vs));
582 }
583 if (vs.isset('input:neighbourhood')) {
584 funcScoreShould.push(addNeighbourhood(vs));
585 }
586 if (vs.isset('input:borough')) {
587 funcScoreShould.push(addBorough(vs));
588 }
589 if (vs.isset('input:locality')) {
590 if (!vs.isset('input:borough')) {
591 funcScoreShould.push(addLocalityAsBorough(vs));
592 }
593 funcScoreShould.push(addLocality(vs));
594 funcScoreShould.push(addLocalAdmin(vs));
595 }
596 if (vs.isset('input:county')) {
597 funcScoreShould.push(addCounty(vs));
598 funcScoreShould.push(addMacroCounty(vs));
599 }
600 if (vs.isset('input:region')) {
601 funcScoreShould.push(addRegion(vs));
602 funcScoreShould.push(addMacroRegion(vs));
603 }
604 if (vs.isset('input:country')) {
605 funcScoreShould.push(addDependency(vs));
606 funcScoreShould.push(addCountry(vs));
607 }
608
609 // handle scoring views under 'query' section (both 'must' & 'should')
610 if( this._score.length ){
611 this._score.forEach( function( view ){
612 var rendered = view( vs );
613 if( rendered ){
614 q.query.function_score.functions.push( rendered );
615 }
616 });
617 }
618
619 // handle filter views under 'filter' section (only 'must' is allowed here)
620 if( this._filter.length ){
621 this._filter.forEach( function( view ){
622 var rendered = view( vs );
623 if( rendered ){
624 if( !q.query.function_score.query.bool.hasOwnProperty( 'filter' ) ){
625 q.query.function_score.query.bool.filter = {
626 bool: {
627 must: []
628 }
629 };
630 }
631 q.query.function_score.query.bool.filter.bool.must.push( rendered );
632 }
633 });
634 }
635
636 return q;
637};
638
639Layout.base = function( vs ){
640 var baseQueryCopy = _.cloneDeep(baseQuery);
641
642 baseQueryCopy.size = vs.var('size');
643 baseQueryCopy.track_scores = vs.var('track_scores');
644
645 return baseQueryCopy;
646
647};
648
649module.exports = Layout;