|
|
|
|
|
|
|
var queries = require('geopipes-elasticsearch-backend').queries,
|
|
|
|
sort = require('../query/sort'),
|
|
|
|
adminFields = require('../helper/adminFields')(),
|
|
|
|
addressWeights = require('../helper/address_weights');
|
|
|
|
|
|
|
|
|
|
|
|
function generate( params ){
|
|
|
|
var centroid = null;
|
|
|
|
|
|
|
|
if ( params.lat && params.lon ){
|
|
|
|
centroid = {
|
|
|
|
lat: params.lat,
|
|
|
|
lon: params.lon
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
var query = queries.distance( centroid, { size: params.size } );
|
|
|
|
var input = params.input;
|
|
|
|
|
|
|
|
if (params.bbox) {
|
|
|
|
query = queries.bbox ( centroid, { size: params.size, bbox: params.bbox } );
|
|
|
|
}
|
|
|
|
|
|
|
|
query.query.filtered.query = {
|
|
|
|
'bool': {
|
|
|
|
'must': [],
|
|
|
|
'should': []
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (params.parsed_input) {
|
|
|
|
addParsedMatch(query, input, params.parsed_input);
|
|
|
|
|
|
|
|
// update input
|
|
|
|
if (params.parsed_input.number && params.parsed_input.street) {
|
|
|
|
input = params.parsed_input.number + ' ' + params.parsed_input.street;
|
|
|
|
} else if (params.parsed_input.admin_parts) {
|
|
|
|
input = params.parsed_input.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add search condition to distance query
|
|
|
|
query.query.filtered.query.bool.must.push({
|
|
|
|
'match': {
|
|
|
|
'name.default': input
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// add phrase matching query
|
|
|
|
// note: this is required for shingle/phrase matching
|
|
|
|
query.query.filtered.query.bool.should.push({
|
|
|
|
'match': {
|
|
|
|
'phrase.default': input
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
query.sort = query.sort.concat( sort( params ) );
|
|
|
|
|
|
|
|
return query;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Traverse the parsed input object, containing all the address parts detected in query string.
|
|
|
|
* Add matches to query for each identifiable component.
|
|
|
|
*
|
|
|
|
* @param {Object} query
|
|
|
|
* @param {string} defaultInput
|
|
|
|
* @param {Object} parsedInput
|
|
|
|
*/
|
|
|
|
function addParsedMatch(query, defaultInput, parsedInput) {
|
|
|
|
query.query.filtered.query.bool.should = query.query.filtered.query.bool.should || [];
|
|
|
|
|
|
|
|
// copy expected admin fields so we can remove them as we parse the address
|
|
|
|
var unmatchedAdminFields = adminFields.slice();
|
|
|
|
|
|
|
|
// address
|
|
|
|
// number, street, postalcode
|
|
|
|
addMatch(query, unmatchedAdminFields, 'address.number', parsedInput.number, addressWeights.number);
|
|
|
|
addMatch(query, unmatchedAdminFields, 'address.street', parsedInput.street, addressWeights.street);
|
|
|
|
addMatch(query, unmatchedAdminFields, 'address.zip', parsedInput.postalcode, addressWeights.zip);
|
|
|
|
|
|
|
|
// city
|
|
|
|
// admin2, locality, local_admin, neighborhood
|
|
|
|
addMatch(query, unmatchedAdminFields, 'admin2', parsedInput.city, addressWeights.admin2);
|
|
|
|
|
|
|
|
// state
|
|
|
|
// admin1, admin1_abbr
|
|
|
|
addMatch(query, unmatchedAdminFields, 'admin1_abbr', parsedInput.state, addressWeights.admin1_abbr);
|
|
|
|
|
|
|
|
// country
|
|
|
|
// admin0, alpha3
|
|
|
|
addMatch(query, unmatchedAdminFields, 'alpha3', parsedInput.country, addressWeights.alpha3);
|
|
|
|
|
|
|
|
addUnmatchedAdminFieldsToQuery(query, unmatchedAdminFields, parsedInput, defaultInput);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check for additional admin fields in the parsed input, and if any was found
|
|
|
|
* combine into single string and match against all unmatched admin fields.
|
|
|
|
*
|
|
|
|
* @param {Object} query
|
|
|
|
* @param {Array} unmatchedAdminFields
|
|
|
|
* @param {Object} parsedInput
|
|
|
|
* @param {string} defaultInput
|
|
|
|
*/
|
|
|
|
function addUnmatchedAdminFieldsToQuery(query, unmatchedAdminFields, parsedInput, defaultInput) {
|
|
|
|
if (unmatchedAdminFields.length === 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var leftovers = [];
|
|
|
|
|
|
|
|
if (parsedInput.admin_parts) {
|
|
|
|
leftovers.push(parsedInput.admin_parts);
|
|
|
|
}
|
|
|
|
else if (parsedInput.regions) {
|
|
|
|
leftovers.push(parsedInput.regions);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (leftovers.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there are additional regions/admin_parts found
|
|
|
|
if (leftovers !== defaultInput) {
|
|
|
|
unmatchedAdminFields.forEach(function (key) {
|
|
|
|
// combine all the leftover parts into one string
|
|
|
|
addMatch(query, [], key, leftovers.join(' '));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add key:value match to query. Apply boost if specified.
|
|
|
|
*
|
|
|
|
* @param {Object} query
|
|
|
|
* @param {Array} unmatched
|
|
|
|
* @param {string} key
|
|
|
|
* @param {string|number|undefined} value
|
|
|
|
* @param {number|undefined} [boost] optional
|
|
|
|
*/
|
|
|
|
function addMatch(query, unmatched, key, value, boost) { // jshint ignore:line
|
|
|
|
if (typeof value === 'undefined') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
var match = {};
|
|
|
|
|
|
|
|
if (boost) {
|
|
|
|
match[key] = {
|
|
|
|
query: value,
|
|
|
|
boost: boost
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
match[key] = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
query.query.filtered.query.bool.should.push({ 'match': match });
|
|
|
|
|
|
|
|
removeFromUnmatched(unmatched, key);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If key is found in unmatched list, remove it from the array
|
|
|
|
*
|
|
|
|
* @param {Array} unmatched
|
|
|
|
* @param {string} key
|
|
|
|
*/
|
|
|
|
function removeFromUnmatched(unmatched, key) {
|
|
|
|
var index = unmatched.indexOf(key);
|
|
|
|
if (index !== -1) {
|
|
|
|
unmatched.splice(index, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = generate;
|