Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filtering on addendum namespaces #1643

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
54 changes: 34 additions & 20 deletions helper/geojsonify.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ const codec = require('pelias-model').codec;
const field = require('./fieldValue');
const decode_gid = require('./decode_gid');
const iso3166 = require('./iso3166');
const peliasConfig = require('pelias-config').generate();

function geojsonifyPlaces( params, docs ){
function geojsonifyPlaces(params, docs) {

// flatten & expand data for geojson conversion
const geodata = docs
Expand All @@ -28,8 +29,8 @@ function geojsonifyPlaces( params, docs ){
const extentPoints = extractExtentPoints(geodata);

// convert to geojson
const geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] });
const geojsonExtentPoints = GeoJSON.parse( extentPoints, { Point: ['lat', 'lng'] });
const geojson = GeoJSON.parse(geodata, { Point: ['lat', 'lng'] });
const geojsonExtentPoints = GeoJSON.parse(extentPoints, { Point: ['lat', 'lng'] });

// to insert the bbox property at the top level of each feature, it must be done separately after
// initial geojson construction is finished
Expand Down Expand Up @@ -72,16 +73,26 @@ function geojsonifyPlace(params, place) {
// add addendum data if available
// note: this should be the last assigned property, for aesthetic reasons.
if (_.has(place, 'addendum')) {
let addendum = {};
for(let namespace in place.addendum){
try {
addendum[namespace] = codec.decode(place.addendum[namespace]);
} catch( e ){
logger.warn(`doc ${doc.gid} failed to decode addendum namespace ${namespace}`);
const configuredAddendumNamespaces = peliasConfig.get('addendum_namespaces');
let nonConfiguredNamespaces = {};
let configuredNamespaces = {};
for (const namespace in place.addendum) {
const isConfigured = configuredAddendumNamespaces[namespace];
if (isConfigured) {
configuredNamespaces[namespace] = place.addendum[namespace];
} else {
try {
nonConfiguredNamespaces[namespace] = codec.decode(place.addendum[namespace]);
} catch (e) {
logger.warn(`doc ${doc.gid} failed to decode addendum namespace ${namespace}`);
}
}
}
if( Object.keys(addendum).length ){
doc.addendum = addendum;
if (Object.keys(nonConfiguredNamespaces).length) {
doc.addendum = nonConfiguredNamespaces;
}
for (let namespace in configuredNamespaces) {
doc[namespace] = configuredNamespaces[namespace];
}
}

Expand Down Expand Up @@ -133,8 +144,7 @@ function extractExtentPoints(geodata) {
lat: place.bounding_box.max_lat
});

}
else {
} else {
// otherwise, use the point for the extent
extentPoints.push({
lng: place.lng,
Expand All @@ -158,13 +168,13 @@ function computeBBox(geojson, geojsonExtentPoints) {
// @note: extent() sometimes throws Errors for unusual data
// eg: https://github.com/pelias/pelias/issues/84
try {
var bbox = extent( geojsonExtentPoints );
if( !!bbox ){
const bbox = extent(geojsonExtentPoints);
if (!!bbox) {
geojson.bbox = bbox;
}
} catch( e ){
logger.error( 'bbox error', e.message, e.stack );
logger.error( 'geojson', geojsonExtentPoints );
} catch (e) {
logger.error('bbox error', e.message, e.stack);
logger.error('geojson', geojsonExtentPoints);
}
}

Expand All @@ -176,11 +186,15 @@ function computeBBox(geojson, geojsonExtentPoints) {
function addISO3166PropsPerFeature(geojson) {
geojson.features.forEach(feature => {
let code = _.get(feature, 'properties.country_a') || _.get(feature, 'properties.dependency_a') || '';
if (!_.isString(code) || _.isEmpty(code)){ return; }
if (!_.isString(code) || _.isEmpty(code)) {
return;
}

let info = iso3166.info(code);
let alpha2 = _.get(info, 'alpha2');
if (!_.isString(alpha2) || _.size(alpha2) !== 2) { return; }
if (!_.isString(alpha2) || _.size(alpha2) !== 2) {
return;
}

_.set(feature, 'properties.country_code', alpha2);
});
Expand Down
34 changes: 24 additions & 10 deletions query/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ const toSingleField = require('./view/helper').toSingleField;

// additional views (these may be merged in to pelias/query at a later date)
var views = {
custom_boosts: require('./view/boost_sources_and_layers'),
ngrams_strict: require('./view/ngrams_strict'),
ngrams_last_token_only: require('./view/ngrams_last_token_only'),
ngrams_last_token_only_multi: require('./view/ngrams_last_token_only_multi'),
admin_multi_match_first: require('./view/admin_multi_match_first'),
admin_multi_match_last: require('./view/admin_multi_match_last'),
phrase_first_tokens_only: require('./view/phrase_first_tokens_only'),
boost_exact_matches: require('./view/boost_exact_matches'),
custom_boosts: require('./view/boost_sources_and_layers'),
ngrams_strict: require('./view/ngrams_strict'),
ngrams_last_token_only: require('./view/ngrams_last_token_only'),
ngrams_last_token_only_multi: require('./view/ngrams_last_token_only_multi'),
admin_multi_match_first: require('./view/admin_multi_match_first'),
admin_multi_match_last: require('./view/admin_multi_match_last'),
phrase_first_tokens_only: require('./view/phrase_first_tokens_only'),
boost_exact_matches: require('./view/boost_exact_matches'),
max_character_count_layer_filter: require('./view/max_character_count_layer_filter'),
focus_point_filter: require('./view/focus_point_distance_filter')
focus_point_filter: require('./view/focus_point_distance_filter'),
addendum_namespace_filter: require('./view/addendum_namespace_filter')
};

// add abbrevations for the fields pelias/parser is able to detect.
Expand Down Expand Up @@ -65,6 +66,11 @@ query.filter( peliasQuery.view.categories );
query.filter( peliasQuery.view.boundary_gid );
query.filter( views.focus_point_filter );

const configuredAddendumNamespaces = config.get('addendum_namespaces');
Object.keys(configuredAddendumNamespaces).forEach(namespace => {
query.filter( views.addendum_namespace_filter(namespace, configuredAddendumNamespaces[namespace].type) );
});

// --------------------------------

/**
Expand All @@ -75,6 +81,14 @@ function generateQuery( clean ){

const vs = new peliasQuery.Vars( defaults );

//addendum
const configuredAddendumNamespaces = config.get('addendum_namespaces');
Object.keys(configuredAddendumNamespaces)
.filter(namespace => clean[namespace])
.forEach(namespace => {
vs.var( namespace, clean[namespace] );
});

// sources
if( _.isArray(clean.sources) && !_.isEmpty(clean.sources) ){
vs.var( 'sources', clean.sources );
Expand Down Expand Up @@ -104,7 +118,7 @@ function generateQuery( clean ){
vs.var( 'input:name', clean.text );

// if the tokenizer has run then we set 'input:name' to as the combination of the
// 'complete' tokens with the 'incomplete' tokens, the resuting array differs
// 'complete' tokens with the 'incomplete' tokens, the resulting array differs
// slightly from the 'input:name:tokens' array as some tokens might have been
// removed in the process; such as single grams which are not present in then
// ngrams index.
Expand Down
17 changes: 16 additions & 1 deletion query/reverse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const _ = require('lodash');
const peliasQuery = require('pelias-query');
const defaults = require('./reverse_defaults');
const config = require('pelias-config').generate();
const addendum_namespace_filter = require('./view/addendum_namespace_filter');

//------------------------------
// reverse geocode query
Expand All @@ -21,12 +23,25 @@ query.filter( peliasQuery.view.categories );
query.filter( peliasQuery.view.boundary_country );
query.filter( peliasQuery.view.boundary_gid );

const configuredAddendumNamespaces = config.get('addendum_namespaces');
Object.keys(configuredAddendumNamespaces).forEach(namespace => {
query.filter( addendum_namespace_filter(namespace, configuredAddendumNamespaces[namespace].type) );
});

// --------------------------------

function generateQuery( clean ){

const vs = new peliasQuery.Vars( defaults );

//addendum
const configuredAddendumNamespaces = config.get('addendum_namespaces');
Object.keys(configuredAddendumNamespaces)
.filter(namespace => clean[namespace])
.forEach(namespace => {
vs.var( namespace, clean[namespace] );
});

// set size
if( clean.querySize ){
vs.var( 'size', clean.querySize);
Expand Down Expand Up @@ -55,7 +70,7 @@ function generateQuery( clean ){
// bounding circle
// note: the sanitizers will take care of the case
// where point.lan/point.lon are provided in the
// absense of boundary.circle.lat/boundary.circle.lon
// absence of boundary.circle.lat/boundary.circle.lon
if( _.isFinite(clean['boundary.circle.lat']) &&
_.isFinite(clean['boundary.circle.lon']) ){

Expand Down
22 changes: 17 additions & 5 deletions query/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const _ = require('lodash');
const peliasQuery = require('pelias-query');
const defaults = require('./search_defaults');
const textParser = require('./text_parser');
const config = require('pelias-config').generate();
const addendum_namespace_filter = require('./view/addendum_namespace_filter');

//------------------------------
// general-purpose search query
Expand All @@ -22,6 +24,12 @@ fallbackQuery.filter( peliasQuery.view.sources );
fallbackQuery.filter( peliasQuery.view.layers );
fallbackQuery.filter( peliasQuery.view.categories );
fallbackQuery.filter( peliasQuery.view.boundary_gid );

const configuredAddendumNamespaces = config.get('addendum_namespaces');
Object.keys(configuredAddendumNamespaces).forEach(namespace => {
fallbackQuery.filter( addendum_namespace_filter(namespace, configuredAddendumNamespaces[namespace].type) );
});

// --------------------------------

/**
Expand All @@ -36,6 +44,14 @@ function generateQuery( clean ){
// input text
vs.var( 'input:name', clean.text );

//addendum
const configuredAddendumNamespaces = config.get('addendum_namespaces');
Object.keys(configuredAddendumNamespaces)
.filter(namespace => clean[namespace])
.forEach(namespace => {
vs.var( namespace, clean[namespace] );
});

// sources
if( _.isArray(clean.sources) && !_.isEmpty(clean.sources) ) {
vs.var( 'sources', clean.sources);
Expand Down Expand Up @@ -113,11 +129,7 @@ function generateQuery( clean ){
textParser( clean.parsed_text, vs );
}

var q = getQuery(vs);

//console.log(JSON.stringify(q, null, 2));

return q;
return getQuery(vs);
}

function getQuery(vs) {
Expand Down
21 changes: 16 additions & 5 deletions query/structured_geocoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const _ = require('lodash');
const peliasQuery = require('pelias-query');
const defaults = require('./search_defaults');
const textParser = require('./text_parser');
const config = require('pelias-config').generate();
const addendum_namespace_filter = require('./view/addendum_namespace_filter');

//------------------------------
// general-purpose search query
Expand All @@ -22,6 +24,11 @@ structuredQuery.filter( peliasQuery.view.sources );
structuredQuery.filter( peliasQuery.view.layers );
structuredQuery.filter( peliasQuery.view.categories );
structuredQuery.filter( peliasQuery.view.boundary_gid );

const configuredAddendumNamespaces = config.get('addendum_namespaces');
Object.keys(configuredAddendumNamespaces).forEach(namespace => {
structuredQuery.filter( addendum_namespace_filter(namespace, configuredAddendumNamespaces[namespace].type) );
});
// --------------------------------

/**
Expand All @@ -35,6 +42,14 @@ function generateQuery( clean ){
// input text
vs.var( 'input:name', clean.text );

//addendum
const configuredAddendumNamespaces = config.get('addendum_namespaces');
Object.keys(configuredAddendumNamespaces)
.filter(namespace => clean[namespace])
.forEach(namespace => {
vs.var( namespace, clean[namespace] );
});

// sources
vs.var( 'sources', clean.sources);

Expand Down Expand Up @@ -103,11 +118,7 @@ function generateQuery( clean ){
textParser( clean.parsed_text, vs );
}

const q = getQuery(vs);

// console.log(JSON.stringify(q.body, null, 2));

return q;
return getQuery(vs);
}

function getQuery(vs) {
Expand Down
22 changes: 22 additions & 0 deletions query/view/addendum_namespace_filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module.exports = function (addendumParameter, type) {
return function (vs) {
if (!vs.isset(addendumParameter)) {
return null;
}

switch (type) {
case 'array':
return {
terms: {
[`addendum.${addendumParameter}`]: vs.var(addendumParameter)
}
};
default:
return {
term: {
[`addendum.${addendumParameter}`]: vs.var(addendumParameter)
}
};
}
};
};
65 changes: 65 additions & 0 deletions sanitizer/_addendum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const _ = require('lodash');
const peliasConfig = require('pelias-config').generate();

const nonEmptyString = (v) => _.isString(v) && !_.isEmpty(v);

function _sanitize(raw, clean) {

// error & warning messages
const messages = { errors: [], warnings: [] };

// target input params
const configuredAddendumNamespaces = peliasConfig.get('addendum_namespaces');

Object.keys(raw)
.filter(namespace => configuredAddendumNamespaces.hasOwnProperty(namespace))
.forEach(namespace => {

if (!nonEmptyString(raw[namespace])) {
messages.errors.push(namespace + ' should be a non empty string');
} else {
const rawValue = raw[namespace].trim();
const validationType = configuredAddendumNamespaces[namespace].type;
switch (validationType.toLowerCase()) {
case 'array':
const valuesArray = rawValue.split(',').filter(nonEmptyString);
if (_.isArray(valuesArray) && _.isEmpty(valuesArray)) {
messages.errors.push(namespace + ' should not be empty');
} else {
clean[namespace] = valuesArray;
}
break;

case 'string':
clean[namespace] = rawValue;
break;

case 'number':
if (isNaN(rawValue)) {
messages.errors.push(namespace + ': Invalid parameter type, expecting: ' + validationType + ', got NaN: ' + rawValue);
} else {
clean[namespace] = Number(rawValue);
}
break;

case 'boolean':
if ('true' !== rawValue && 'false' !== rawValue) {
messages.errors.push(namespace + ': Invalid parameter type, expecting: ' + validationType + ', got: ' + rawValue);
} else {
clean[namespace] = 'true' === rawValue;
}
break;

default:
messages.errors.push(namespace + ': Unsupported configured type ' + validationType + ', ' +
'valid types are array, string, number and boolean');
}
}
});

return messages;
}

module.exports = () => ({
sanitize: _sanitize,
});
Loading