Browse Source

Merge branch 'master' of https://github.com/pelias/api into pelias-intersections

pull/1058/head
Alex Loyko 7 years ago
parent
commit
f6f5f47f03
  1. 11
      .travis.yml
  2. 12
      README.md
  3. 17
      circle.yml
  4. 77
      controller/coarse_reverse.js
  5. 1
      controller/libpostal.js
  6. 2
      controller/place.js
  7. 111
      helper/geojsonify.js
  8. 42
      helper/geojsonify_meta_data.js
  9. 76
      helper/geojsonify_place_details.js
  10. 4
      helper/placeTypes.js
  11. 15
      helper/type_mapping.js
  12. 13
      index.js
  13. 7
      middleware/confidenceScore.js
  14. 3
      middleware/cors.js
  15. 3
      middleware/geocodeJSON.js
  16. 36
      middleware/interpolate.js
  17. 35
      package.json
  18. 14
      query/structured_geocoding.js
  19. 12
      sanitizer/_geo_common.js
  20. 21
      sanitizer/_geonames_deprecation.js
  21. 9
      sanitizer/_text_addressit.js
  22. 10
      service/configurations/PointInPolygon.js
  23. 1
      test/ciao/CORS/headers_GET.coffee
  24. 1
      test/ciao/CORS/headers_OPTIONS.coffee
  25. 157
      test/unit/controller/coarse_reverse.js
  26. 4
      test/unit/controller/libpostal.js
  27. 4
      test/unit/controller/place.js
  28. 4
      test/unit/controller/predicates/is_coarse_reverse.js
  29. 2
      test/unit/controller/search_with_ids.js
  30. 39
      test/unit/fixture/reverse_null_island.js
  31. 39
      test/unit/fixture/reverse_standard.js
  32. 49
      test/unit/fixture/reverse_with_boundary_country.js
  33. 41
      test/unit/fixture/reverse_with_layer_filtering.js
  34. 41
      test/unit/fixture/reverse_with_layer_filtering_non_coarse_subset.js
  35. 46
      test/unit/fixture/reverse_with_source_filtering.js
  36. 71
      test/unit/fixture/search_linguistic_viewport.js
  37. 71
      test/unit/fixture/search_linguistic_viewport_min_diagonal.js
  38. 59
      test/unit/fixture/structured_geocoding/boundary_country.json
  39. 859
      test/unit/fixture/structured_geocoding/fallback.json
  40. 62
      test/unit/fixture/structured_geocoding/linguistic_bbox.json
  41. 65
      test/unit/fixture/structured_geocoding/linguistic_focus.json
  42. 76
      test/unit/fixture/structured_geocoding/linguistic_focus_bbox.json
  43. 65
      test/unit/fixture/structured_geocoding/linguistic_focus_null_island.json
  44. 51
      test/unit/fixture/structured_geocoding/linguistic_only.json
  45. 51
      test/unit/fixture/structured_geocoding/linguistic_viewport.json
  46. 51
      test/unit/fixture/structured_geocoding/linguistic_viewport_min_diagonal.json
  47. 60
      test/unit/fixture/structured_geocoding/postalcode_only.js
  48. 52
      test/unit/fixture/structured_geocoding/with_source_filtering.json
  49. 910
      test/unit/helper/geojsonify.js
  50. 590
      test/unit/helper/geojsonify_place_details.js
  51. 5
      test/unit/helper/type_mapping.js
  52. 32
      test/unit/middleware/confidenceScore.js
  53. 298
      test/unit/middleware/interpolate.js
  54. 7
      test/unit/query/MockQuery.js
  55. 573
      test/unit/query/reverse.js
  56. 48
      test/unit/query/search.js
  57. 663
      test/unit/query/structured_geocoding.js
  58. 1
      test/unit/run.js
  59. 19
      test/unit/sanitizer/_geo_common.js
  60. 46
      test/unit/sanitizer/_geonames_deprecation.js
  61. 16
      test/unit/sanitizer/_layers.js
  62. 56
      test/unit/service/configurations/PointInPolygon.js

11
.travis.yml

@ -9,20 +9,15 @@ matrix:
fast_finish: true
env:
global:
- CXX=g++-4.8
- BUILD_LEADER_ID=2
script: npm run travis
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
before_install:
- npm i -g npm@^3.0.0
before_script:
- npm prune
after_success:
- npm run semantic-release
- npm install -g npx
- npx -p node@8 npm run semantic-release
branches:
except:
- /^v\d+\.\d+\.\d+$/

12
README.md

@ -44,7 +44,7 @@ The API recognizes the following properties under the top-level `api` key in you
|`indexName`|*no*|*pelias*|name of the Elasticsearch index to be used when building queries|
|`relativeScores`|*no*|true|if set to true, confidence scores will be normalized, realistically at this point setting this to false is not tested or desirable
|`accessLog`|*no*||name of the format to use for access logs; may be any one of the [predefined values](https://github.com/expressjs/morgan#predefined-formats) in the `morgan` package. Defaults to `"common"`; if set to `false`, or an otherwise falsy value, disables access-logging entirely.|
|`services`|*no*||service definitions for [point-in-polygon](https://github.com/pelias/pip-service) and [placholder](https://github.com/pelias/placeholder) services. If missing (which is not recommended), the point-in-polygon and placeholder services will not be called.|
|`services`|*no*||service definitions for [point-in-polygon](https://github.com/pelias/pip-service), and [placeholder](https://github.com/pelias/placeholder), and [interpolation](https://github.com/pelias/interpolation) services. If missing (which is not recommended), the services will not be called.|
|`defaultParameters.focus.point.lon` <br> `defaultParameters.focus.point.lat`|no | |default coordinates for focus point
Example configuration file would look something like this:
@ -73,6 +73,10 @@ Example configuration file would look something like this:
},
"placeholder": {
"url": "http://myplaceholderservice.com:5000"
},
"interpolation": {
"url": "http://myinterpolationservice.com:3000",
"timeout": 2500
}
}
"defaultParameters": {
@ -80,12 +84,6 @@ Example configuration file would look something like this:
"focus.point.lon": 21.212121
}
},
"interpolation": {
"client": {
"adapter": "http",
"host": "internal-pelias-interpolation-dev-130430937.us-east-1.elb.amazonaws.com"
}
},
"logger": {
"level": "debug"
}

17
circle.yml

@ -1,17 +0,0 @@
machine:
ruby:
version: 2.1.2
node:
version: 0.12.2
deployment:
dev:
branch: master
commands:
- git clone git@github.com:mapzen/pelias-deploy.git && cd pelias-deploy && bundle install
- cd pelias-deploy && bundle exec rake deploy:api dev
prod_build:
branch: staging
commands:
- git clone git@github.com:mapzen/pelias-deploy.git && cd pelias-deploy && bundle install
- cd pelias-deploy && bundle exec rake deploy:api prod_build

77
controller/coarse_reverse.js

@ -15,7 +15,11 @@ const coarse_granularities = [
'region',
'macroregion',
'dependency',
'country'
'country',
'empire',
'continent',
'ocean',
'marinearea'
];
// remove non-coarse layers and return what's left (or all if empty)
@ -62,40 +66,50 @@ function synthesizeDoc(results) {
const most_granular_layer = getMostGranularLayerOfResult(_.keys(results));
const id = results[most_granular_layer][0].id;
const doc = new Document('whosonfirst', most_granular_layer, id.toString());
doc.setName('default', results[most_granular_layer][0].name);
try {
const doc = new Document('whosonfirst', most_granular_layer, id.toString());
doc.setName('default', results[most_granular_layer][0].name);
// assign the administrative hierarchy
_.keys(results).forEach((layer) => {
doc.addParent(layer, results[layer][0].name, results[layer][0].id.toString(), results[layer][0].abbr);
});
// assign the administrative hierarchy
_.keys(results).forEach((layer) => {
doc.addParent(layer, results[layer][0].name, results[layer][0].id.toString(), results[layer][0].abbr || undefined);
});
// set centroid if available
if (_.has(results[most_granular_layer][0], 'centroid')) {
doc.setCentroid( results[most_granular_layer][0].centroid );
}
// set centroid if available
if (_.has(results[most_granular_layer][0], 'centroid')) {
doc.setCentroid( results[most_granular_layer][0].centroid );
}
// set bounding box if available
if (_.has(results[most_granular_layer][0], 'bounding_box')) {
const parsed_bounding_box = results[most_granular_layer][0].bounding_box.split(',').map(parseFloat);
doc.setBoundingBox({
upperLeft: {
lat: parsed_bounding_box[3],
lon: parsed_bounding_box[0]
},
lowerRight: {
lat: parsed_bounding_box[1],
lon: parsed_bounding_box[2]
}
});
// set bounding box if available
if (_.has(results[most_granular_layer][0], 'bounding_box')) {
const parsed_bounding_box = results[most_granular_layer][0].bounding_box.split(',').map(parseFloat);
doc.setBoundingBox({
upperLeft: {
lat: parsed_bounding_box[3],
lon: parsed_bounding_box[0]
},
lowerRight: {
lat: parsed_bounding_box[1],
lon: parsed_bounding_box[2]
}
});
}
}
const esDoc = doc.toESDocument();
esDoc.data._id = esDoc._id;
esDoc.data._type = esDoc._type;
return esDoc.data;
const esDoc = doc.toESDocument();
esDoc.data._id = esDoc._id;
esDoc.data._type = esDoc._type;
return esDoc.data;
} catch( e ) {
// an error occurred when generating a new Document
logger.info(`[controller:coarse_reverse][error]`);
logger.error(e);
logger.info(results);
return null;
}
}
function setup(service, should_execute) {
@ -141,7 +155,10 @@ function setup(service, should_execute) {
// if there's a result at the requested layer(s), synthesize a doc from results
if (hasResultsAtRequestedLayers(applicable_results, effective_layers)) {
res.data.push(synthesizeDoc(applicable_results));
const doc = synthesizeDoc(applicable_results);
if (doc){
res.data.push(doc);
}
}
debugLog.stopTimer(req, initialTime);
return next();

1
controller/libpostal.js

@ -20,6 +20,7 @@ function setup(should_execute) {
parsed_text.country = iso3166.to3(_.toUpper(parsed_text.country));
}
req.clean.parser = 'libpostal';
req.clean.parsed_text = parsed_text;
debugLog.push(req, {parsed_text: req.clean.parsed_text});
}

2
controller/place.js

@ -38,7 +38,7 @@ function setup( apiConfig, esclient ){
const cmd = req.clean.ids.map( function(id) {
return {
_index: apiConfig.indexName,
_type: id.layers,
_type: id.layer,
_id: id.id
};
});

111
helper/geojsonify.js

@ -1,28 +1,32 @@
var GeoJSON = require('geojson');
var extent = require('@mapbox/geojson-extent');
var logger = require('pelias-logger').get('api');
var type_mapping = require('./type_mapping');
var _ = require('lodash');
var addDetails = require('./geojsonify_place_details');
var addMetaData = require('./geojsonify_meta_data');
const GeoJSON = require('geojson');
const extent = require('@mapbox/geojson-extent');
const logger = require('pelias-logger').get('geojsonify');
const collectDetails = require('./geojsonify_place_details');
const _ = require('lodash');
const Document = require('pelias-model').Document;
function geojsonifyPlaces( params, docs ){
// flatten & expand data for geojson conversion
var geodata = docs
.map(geojsonifyPlace.bind(null, params))
.filter( function( doc ){
return !!doc;
});
const geodata = docs
.filter(doc => {
if (!_.has(doc, 'center_point')) {
logger.warn('No doc or center_point property');
return false;
} else {
return true;
}
})
.map(geojsonifyPlace.bind(null, params));
// get all the bounding_box corners as well as single points
// to be used for computing the overall bounding_box for the FeatureCollection
var extentPoints = extractExtentPoints(geodata);
const extentPoints = extractExtentPoints(geodata);
// convert to geojson
var geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] });
var 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
@ -35,36 +39,29 @@ function geojsonifyPlaces( params, docs ){
}
function geojsonifyPlace(params, place) {
// something went very wrong
if( !place || !place.hasOwnProperty( 'center_point' ) ) {
return warning('No doc or center_point property');
// setup the base doc
const doc = {
id: place._id,
gid: new Document(place.source, place.layer, place._id).getGid(),
layer: place.layer,
source: place.source,
source_id: place.source_id,
bounding_box: place.bounding_box,
lat: parseFloat(place.center_point.lat),
lng: parseFloat(place.center_point.lon)
};
// assign name, logging a warning if it doesn't exist
if (_.has(place, 'name.default')) {
doc.name = place.name.default;
} else {
logger.warn(`doc ${doc.gid} does not contain name.default`);
}
var output = {};
addMetaData(place, output);
addName(place, output);
addDetails(params, place, output);
// map center_point for GeoJSON to work properly
// these should not show up in the final feature properties
output.lat = parseFloat(place.center_point.lat);
output.lng = parseFloat(place.center_point.lon);
// assign all the details info into the doc
Object.assign(doc, collectDetails(params, place));
return output;
}
/**
* Validate and add name property
*
* @param {object} src
* @param {object} dst
*/
function addName(src, dst) {
// map name
if( !src.name || !src.name.default ) { return warning(src); }
dst.name = src.name.default;
return doc;
}
/**
@ -73,12 +70,7 @@ function addName(src, dst) {
* @param {object} geojson
*/
function addBBoxPerFeature(geojson) {
geojson.features.forEach(function (feature) {
if (!feature.properties.hasOwnProperty('bounding_box')) {
return;
}
geojson.features.forEach(feature => {
if (feature.properties.bounding_box) {
feature.bbox = [
feature.properties.bounding_box.min_lon,
@ -101,8 +93,8 @@ function addBBoxPerFeature(geojson) {
* @returns {Array}
*/
function extractExtentPoints(geodata) {
var extentPoints = [];
geodata.forEach(function (place) {
return geodata.reduce((extentPoints, place) => {
// if there's a bounding_box, use the LL/UR for the extent
if (place.bounding_box) {
extentPoints.push({
lng: place.bounding_box.min_lon,
@ -112,16 +104,20 @@ function extractExtentPoints(geodata) {
lng: place.bounding_box.max_lon,
lat: place.bounding_box.max_lat
});
}
else {
// otherwise, use the point for the extent
extentPoints.push({
lng: place.lng,
lat: place.lat
});
}
});
return extentPoints;
}, []);
return extentPoints;
}
/**
@ -144,15 +140,4 @@ function computeBBox(geojson, geojsonExtentPoints) {
}
}
/**
* emit a warning if the doc format is invalid
*
* @note: if you see this error, fix it ASAP!
*/
function warning( doc ) {
console.error( 'error: invalid doc', __filename, doc);
return false; // remove offending doc from results
}
module.exports = geojsonifyPlaces;

42
helper/geojsonify_meta_data.js

@ -1,42 +0,0 @@
var Document = require('pelias-model').Document;
/**
* Determine and set place id, type, and source
*
* @param {object} src
* @param {object} dst
*/
function addMetaData(src, dst) {
dst.id = src._id;
dst.gid = makeGid(src);
dst.layer = lookupLayer(src);
dst.source = lookupSource(src);
dst.source_id = lookupSourceId(src);
if (src.hasOwnProperty('bounding_box')) {
dst.bounding_box = src.bounding_box;
}
}
/**
* Create a gid from a document
*
* @param {object} src
*/
function makeGid(src) {
var doc = new Document(lookupSource(src), lookupLayer(src), src._id);
return doc.getGid();
}
function lookupSource(src) {
return src.source;
}
function lookupSourceId(src) {
return src.source_id;
}
function lookupLayer(src) {
return src.layer;
}
module.exports = addMetaData;

76
helper/geojsonify_place_details.js

@ -1,9 +1,11 @@
var _ = require('lodash');
'use strict';
const _ = require('lodash');
// Properties to be copied
// If a property is identified as a single string, assume it should be presented as a string in response
// If something other than string is desired, use the following structure: { name: 'category', type: 'array' }
var DETAILS_PROPS = [
const DETAILS_PROPS = [
{ name: 'housenumber', type: 'string' },
{ name: 'street', type: 'string' },
{ name: 'street1', type: 'string' },
@ -43,68 +45,71 @@ var DETAILS_PROPS = [
{ name: 'borough_a', type: 'string' },
{ name: 'neighbourhood', type: 'string' },
{ name: 'neighbourhood_gid', type: 'string' },
{ name: 'continent', type: 'string' },
{ name: 'continent_gid', type: 'string' },
{ name: 'continent_a', type: 'string' },
{ name: 'empire', type: 'string', condition: _.negate(hasCountry) },
{ name: 'empire_gid', type: 'string', condition: _.negate(hasCountry) },
{ name: 'empire_a', type: 'string', condition: _.negate(hasCountry) },
{ name: 'ocean', type: 'string' },
{ name: 'ocean_gid', type: 'string' },
{ name: 'ocean_a', type: 'string' },
{ name: 'marinearea', type: 'string' },
{ name: 'marinearea_gid', type: 'string' },
{ name: 'marinearea_a', type: 'string' },
{ name: 'bounding_box', type: 'default' },
{ name: 'label', type: 'string' },
{ name: 'category', type: 'array', condition: checkCategoryParam }
];
function checkCategoryParam(params) {
return _.isObject(params) && params.hasOwnProperty('categories');
// returns true IFF source a country_gid property
function hasCountry(params, source) {
return source.hasOwnProperty('country_gid');
}
/**
* Add details properties
*
* @param {object} params clean query params
* @param {object} src
* @param {object} dst
*/
function addDetails(params, src, dst) {
copyProperties(params, src, DETAILS_PROPS, dst);
function checkCategoryParam(params) {
return _.isObject(params) && params.hasOwnProperty('categories');
}
/**
* Copy specified properties from source to dest.
* Collect the specified properties from source into an object and return it
* Ignore missing properties.
*
* @param {object} params clean query params
* @param {object} source
* @param {[]} props
* @param {object} dst
*/
function copyProperties( params, source, props, dst ) {
props.forEach( function ( prop ) {
// if condition isn't met, just return without setting the property
if (_.isFunction(prop.condition) && !prop.condition(params)) {
return;
function collectProperties( params, source ) {
return DETAILS_PROPS.reduce((result, prop) => {
// if condition isn't met, don't set the property
if (_.isFunction(prop.condition) && !prop.condition(params, source)) {
return result;
}
var property = {
name: prop.name || prop,
type: prop.type || 'default'
};
var value = null;
if ( source.hasOwnProperty( property.name ) ) {
if ( source.hasOwnProperty( prop.name ) ) {
let value = null;
switch (property.type) {
switch (prop.type) {
case 'string':
value = getStringValue(source[property.name]);
value = getStringValue(source[prop.name]);
break;
case 'array':
value = getArrayValue(source[property.name]);
value = getArrayValue(source[prop.name]);
break;
// default behavior is to copy property exactly as is
default:
value = source[property.name];
value = source[prop.name];
}
if (_.isNumber(value) || (value && !_.isEmpty(value))) {
dst[property.name] = value;
result[prop.name] = value;
}
}
});
return result;
}, {});
}
function getStringValue(property) {
@ -125,7 +130,6 @@ function getStringValue(property) {
return _.toString(property);
}
function getArrayValue(property) {
// isEmpty check works for all types of values: strings, arrays, objects
if (_.isEmpty(property)) {
@ -139,4 +143,4 @@ function getArrayValue(property) {
return [property];
}
module.exports = addDetails;
module.exports = collectProperties;

4
helper/placeTypes.js

@ -1,4 +1,8 @@
module.exports = [
'ocean',
'marinearea',
'continent',
'empire',
'country',
'dependency',
'macroregion',

15
helper/type_mapping.js

@ -1,5 +1,4 @@
var extend = require('extend'),
_ = require('lodash');
const _ = require('lodash');
function addStandardTargetsToAliases(standard, aliases) {
var combined = _.extend({}, aliases);
@ -49,9 +48,10 @@ var LAYERS_BY_SOURCE = {
openaddresses: [ 'address' ],
geonames: [ 'country','macroregion', 'region', 'county','localadmin',
'locality','borough', 'neighbourhood', 'venue' ],
whosonfirst: [ 'continent', 'country', 'dependency', 'macroregion', 'region',
whosonfirst: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region',
'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough',
'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode']
'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode',
'continent', 'ocean', 'marinearea']
};
/*
@ -60,9 +60,10 @@ var LAYERS_BY_SOURCE = {
* may have layers that mean the same thing but have a different name
*/
var LAYER_ALIASES = {
'coarse': [ 'continent', 'country', 'dependency', 'macroregion', 'region',
'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough',
'neighbourhood', 'microhood', 'disputed', 'postalcode' ]
'coarse': [ 'continent', 'empire', 'country', 'dependency', 'macroregion',
'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood',
'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode',
'continent', 'ocean', 'marinearea']
};
// create a list of all layers by combining each entry from LAYERS_BY_SOURCE

13
index.js

@ -1,7 +1,10 @@
var app = require('./app'),
port = ( process.env.PORT || 3100 );
const app = require('./app'),
port = ( process.env.PORT || 3100 ),
host = ( process.env.HOST || undefined );
/** run server on the default setup (single core) **/
console.log( 'pelias is now running on port ' + port );
app.listen( port );
const server = app.listen( port, host, () => {
// ask server for the actual address and port its listening on
const listenAddress = server.address();
console.log( `pelias is now running on ${listenAddress.address}:${listenAddress.port}` );
});

7
middleware/confidenceScore.js

@ -94,7 +94,8 @@ function checkForDealBreakers(req, hit) {
return false;
}
if (check.assigned(req.clean.parsed_text.state) && hit.parent.region_a && req.clean.parsed_text.state !== hit.parent.region_a[0]) {
if (check.assigned(req.clean.parsed_text.state) && check.assigned(hit.parent) &&
hit.parent.region_a && req.clean.parsed_text.state !== hit.parent.region_a[0]) {
logger.debug('[confidence][deal-breaker]: state !== region_a');
return true;
}
@ -220,8 +221,8 @@ function checkAddress(text, hit) {
res += propMatch(text.number, (hit.address_parts ? hit.address_parts.number : null), false);
res += propMatch(text.street, (hit.address_parts ? hit.address_parts.street : null), false);
res += propMatch(text.postalcode, (hit.address_parts ? hit.address_parts.zip: null), true);
res += propMatch(text.state, (hit.parent.region_a ? hit.parent.region_a[0] : null), true);
res += propMatch(text.country, (hit.parent.country_a ? hit.parent.country_a[0] :null), true);
res += propMatch(text.state, ((hit.parent && hit.parent.region_a) ? hit.parent.region_a[0] : null), true);
res += propMatch(text.country, ((hit.parent && hit.parent.country_a) ? hit.parent.country_a[0] :null), true);
res /= checkCount;
}

3
middleware/cors.js

@ -3,8 +3,7 @@ function middleware(req, res, next){
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.header('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
res.header('Access-Control-Allow-Credentials', true);
next();
}
module.exports = middleware;
module.exports = middleware;

3
middleware/geocodeJSON.js

@ -1,5 +1,4 @@
var url = require('url');
var extend = require('extend');
var geojsonify = require('../helper/geojsonify');
var _ = require('lodash');
@ -74,7 +73,7 @@ function convertToGeocodeJSON(req, res, next, opts) {
res.body.geocoding.timestamp = new Date().getTime();
// convert docs to geojson and merge with geocoding block
extend(res.body, geojsonify(req.clean, res.data || []));
_.extend(res.body, geojsonify(req.clean, res.data || []));
next();
}

36
middleware/interpolate.js

@ -21,15 +21,34 @@ example response from interpolation web service:
}
**/
// The interpolation middleware layer uses async.map to iterate over the results
// since the interpolation service only operates on single inputs. The problem
// with async.map is that if a single error is returned then the entire batch
// exits early. This function wraps the service call to intercept the error so
// that async.map never returns an error.
function error_intercepting_service(service, req) {
return (street_result, next) => {
service(req, street_result, (err, interpolation_result) => {
if (err) {
logger.error(`[middleware:interpolation] ${_.defaultTo(err.message, err)}`);
// now that the error has been caught and reported, act as if there was no error
return next(null, null);
}
// no error occurred, so pass along the result
return next(null, interpolation_result);
});
};
}
function setup(service, should_execute) {
return function controller(req, res, next) {
// bail early if the service shouldn't execute
if (!should_execute(req, res)) {
return next();
}
// bind the service to the req which doesn't change
const req_bound_service = _.partial(service, req);
// only interpolate the street-layer results
// save this off into a separate array so that when docs are annotated
// after the interpolate results are returned, no complicated bookkeeping is needed
@ -37,12 +56,13 @@ function setup(service, should_execute) {
// perform interpolations asynchronously for all relevant hits
const start = (new Date()).getTime();
async.map(street_results, req_bound_service, (err, interpolation_results) => {
if (err) {
logger.error(`[middleware:interpolation] ${_.defaultTo(err.message, err)}`);
return next();
}
logger.info(`[interpolation] [street_results] count=${street_results.length}`);
// call the interpolation service asynchronously on every street result
async.map(street_results, error_intercepting_service(service, req), (err, interpolation_results) => {
// iterate the interpolation results, mapping back into the source results
interpolation_results.forEach((interpolation_result, idx) => {
const source_result = street_results[idx];

35
package.json

@ -7,7 +7,7 @@
"license": "MIT",
"main": "index.js",
"scripts": {
"audit": "npm shrinkwrap; node node_modules/nsp/bin/nspCLI.js audit-shrinkwrap; rm npm-shrinkwrap.json;",
"audit": "npm shrinkwrap; node node_modules/nsp/bin/nsp check; rm npm-shrinkwrap.json;",
"ciao": "node node_modules/ciao/bin/ciao -c test/ciao.json test/ciao",
"coverage": "node_modules/.bin/istanbul cover test/unit/run.js",
"docs": "./bin/generate-docs",
@ -19,7 +19,8 @@
"validate": "npm ls",
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"config": "node -e \"console.log(JSON.stringify(require( 'pelias-config' ).generate(require('./schema')), null, 2))\"",
"check-dependencies": "node_modules/.bin/npm-check --production --ignore pelias-interpolation"
"check-dependencies": "node_modules/.bin/npm-check --production --ignore pelias-interpolation",
"prune": "npm prune"
},
"repository": {
"type": "git",
@ -43,27 +44,26 @@
"elasticsearch": "^13.0.0",
"elasticsearch-exceptions": "0.0.4",
"express": "^4.8.8",
"extend": "^3.0.1",
"geojson": "^0.5.0",
"@mapbox/geojson-extent": "^0.3.1",
"geolib": "^2.0.18",
"iso-639-3": "^1.0.0",
"iso3166-1": "^0.3.0",
"joi": "^11.0.1",
"joi": "^12.0.0",
"locale": "^0.1.0",
"lodash": "^4.17.4",
"markdown": "0.5.0",
"morgan": "^1.8.2",
"pelias-categories": "1.2.0",
"pelias-config": "2.12.0",
"pelias-labels": "1.6.0",
"pelias-logger": "0.2.0",
"pelias-microservice-wrapper": "1.2.0",
"pelias-model": "5.0.1",
"pelias-query": "9.1.0",
"pelias-sorting": "1.0.1",
"pelias-text-analyzer": "1.9.1",
"predicates": "^1.0.1",
"pelias-config": "2.13.0",
"pelias-labels": "1.7.0",
"pelias-logger": "0.3.0",
"pelias-microservice-wrapper": "1.3.0",
"pelias-model": "5.2.0",
"pelias-query": "9.1.1",
"pelias-sorting": "1.1.0",
"pelias-text-analyzer": "1.10.2",
"predicates": "^2.0.0",
"retry": "^0.10.1",
"stats-lite": "^2.0.4",
"through2": "^2.0.3"
@ -74,12 +74,12 @@
"istanbul": "^0.4.2",
"jshint": "^2.5.6",
"npm-check": "git://github.com/orangejulius/npm-check.git#disable-update-check",
"nsp": "^2.2.0",
"pelias-mock-logger": "1.1.1",
"nsp": "^3.0.0",
"pelias-mock-logger": "1.2.0",
"precommit-hook": "^3.0.0",
"proxyquire": "^1.7.10",
"semantic-release": "^7.0.1",
"source-map": "^0.5.6",
"semantic-release": "^8.0.0",
"source-map": "^0.6.0",
"tap-dot": "1.0.5",
"tape": "^4.5.1",
"tmp": "0.0.33",
@ -87,6 +87,7 @@
},
"pre-commit": [
"lint",
"prune",
"validate",
"test",
"check-dependencies"

14
query/structured_geocoding.js

@ -1,12 +1,12 @@
var peliasQuery = require('pelias-query'),
defaults = require('./search_defaults'),
textParser = require('./text_parser'),
check = require('check-types');
const peliasQuery = require('pelias-query'),
defaults = require('./search_defaults'),
textParser = require('./text_parser'),
check = require('check-types');
//------------------------------
// general-purpose search query
//------------------------------
var structuredQuery = new peliasQuery.layout.StructuredFallbackQuery();
const structuredQuery = new peliasQuery.layout.StructuredFallbackQuery();
// scoring boost
structuredQuery.score( peliasQuery.view.focus_only_function( peliasQuery.view.phrase ) );
@ -29,7 +29,7 @@ structuredQuery.filter( peliasQuery.view.categories );
**/
function generateQuery( clean ){
var vs = new peliasQuery.Vars( defaults );
const vs = new peliasQuery.Vars( defaults );
// input text
vs.var( 'input:name', clean.text );
@ -95,7 +95,7 @@ function generateQuery( clean ){
textParser( clean.parsed_text, vs );
}
var q = getQuery(vs);
const q = getQuery(vs);
// console.log(JSON.stringify(q.body, null, 2));

12
sanitizer/_geo_common.js

@ -39,6 +39,18 @@ function sanitize_rect( key_prefix, clean, raw, bbox_is_required ) {
properties.forEach(function(prop) {
sanitize_coord(prop, clean, raw, true);
});
var min_lat = parseFloat( raw[key_prefix + '.' + 'min_lat'] );
var max_lat = parseFloat( raw[key_prefix + '.' + 'max_lat'] );
if (min_lat > max_lat) {
throw new Error( util.format( 'min_lat is larger than max_lat in \'%s\'', key_prefix ) );
}
var min_lon = parseFloat( raw[key_prefix + '.' + 'min_lon'] );
var max_lon = parseFloat( raw[key_prefix + '.' + 'max_lon'] );
if (min_lon > max_lon) {
throw new Error( util.format( 'min_lon is larger than max_lon in \'%s\'', key_prefix ) );
}
}
/**

21
sanitizer/_geonames_deprecation.js

@ -1,21 +1,32 @@
const _ = require('lodash');
/**
with the release of coarse reverse as a separate thing and ES reverse only
handling venues, addresses, and streets, geonames make no sense in the reverse context
* Now that we have the pip-service, we have stopped supporting returning Geonames for coarse reverse.
*
* However, until the `/nearby` endpoint is finalized, we still want to support Geonames for
* _non-coarse_ reverse.
**/
const coarse_reverse_message ='coarse /reverse does not support geonames. See https://github.com/pelias/pelias/issues/675 for more info';
function _sanitize( raw, clean, opts ) {
// error & warning messages
const messages = { errors: [], warnings: [] };
// return taking no action unless this is a coarse-only reverse request
const non_coarse_layers = ['address', 'street', 'venue'];
const is_coarse_reverse = !_.isEmpty(clean.layers) &&
_.isEmpty(_.intersection(clean.layers, non_coarse_layers));
if (!is_coarse_reverse) {
return messages;
}
if (_.isEqual(clean.sources, ['geonames']) || _.isEqual(clean.sources, ['gn'])) {
messages.errors.push('/reverse does not support geonames');
messages.errors.push(coarse_reverse_message);
} else if (_.includes(clean.sources, 'geonames') || _.includes(clean.sources, 'gn')) {
clean.sources = _.without(clean.sources, 'geonames', 'gn');
messages.warnings.push('/reverse does not support geonames');
messages.warnings.push(coarse_reverse_message);
}
return messages;

9
sanitizer/_text_addressit.js

@ -1,6 +1,5 @@
var check = require('check-types');
var parser = require('addressit');
var extend = require('extend');
var _ = require('lodash');
var logger = require('pelias-logger').get('api');
@ -82,8 +81,8 @@ function parse(query) {
var addressWithAdminParts = getAdminPartsBySplittingOnDelim(queryParts);
var addressWithAddressParts= getAddressParts(queryParts.join(DELIM + ' '));
var parsedAddress = extend(addressWithAdminParts,
addressWithAddressParts);
// combine the 2 objects
_.extend(addressWithAdminParts, addressWithAddressParts);
var address_parts = [ 'name',
'number',
@ -99,8 +98,8 @@ function parse(query) {
var parsed_text = {};
address_parts.forEach(function(part){
if (parsedAddress[part]) {
parsed_text[part] = parsedAddress[part];
if (addressWithAdminParts[part]) {
parsed_text[part] = addressWithAdminParts[part];
}
});

10
service/configurations/PointInPolygon.js

@ -11,6 +11,16 @@ class PointInPolygon extends ServiceConfiguration {
super('pip', o);
}
getParameters(req) {
if (_.has(req, 'clean.layers')) {
return {
layers: _.join(req.clean.layers, ',')
};
}
return {};
}
getUrl(req) {
// use resolve to eliminate possibility of duplicate /'s in URL
return url.resolve(this.baseUrl, `${req.clean['point.lon']}/${req.clean['point.lat']}`);

1
test/ciao/CORS/headers_GET.coffee

@ -6,4 +6,3 @@ path: '/'
response.should.have.header 'Access-Control-Allow-Origin','*'
response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS'
response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type'
response.should.have.header 'Access-Control-Allow-Credentials','true'

1
test/ciao/CORS/headers_OPTIONS.coffee

@ -7,4 +7,3 @@ method: 'OPTIONS'
response.should.have.header 'Access-Control-Allow-Origin','*'
response.should.have.header 'Access-Control-Allow-Methods','GET, OPTIONS'
response.should.have.header 'Access-Control-Allow-Headers','X-Requested-With,content-type'
response.should.have.header 'Access-Control-Allow-Credentials','true'

157
test/unit/controller/coarse_reverse.js

@ -210,6 +210,22 @@ module.exports.tests.success_conditions = (test, common) => {
country: [
{ id: 100, name: 'country name', abbr: 'xyz'},
{ id: 101, name: 'country name 2'}
],
empire: [
{ id: 110, name: 'empire name', abbr: 'empire abbr'},
{ id: 111, name: 'empire name 2'}
],
continent: [
{ id: 120, name: 'continent name', abbr: 'continent abbr'},
{ id: 121, name: 'continent name 2'}
],
ocean: [
{ id: 130, name: 'ocean name', abbr: 'ocean abbr'},
{ id: 131, name: 'ocean name 2'}
],
marinearea: [
{ id: 140, name: 'marinearea name', abbr: 'marinearea abbr'},
{ id: 141, name: 'marinearea name 2'}
]
};
@ -282,7 +298,19 @@ module.exports.tests.success_conditions = (test, common) => {
dependency_a: ['dependency abbr'],
country: ['country name'],
country_id: ['100'],
country_a: ['xyz']
country_a: ['xyz'],
empire: ['empire name'],
empire_id: ['110'],
empire_a: ['empire abbr'],
continent: ['continent name'],
continent_id: ['120'],
continent_a: ['continent abbr'],
ocean: ['ocean name'],
ocean_id: ['130'],
ocean_a: ['ocean abbr'],
marinearea: ['marinearea name'],
marinearea_id: ['140'],
marinearea_a: ['marinearea abbr'],
},
center_point: {
lat: 12.121212,
@ -822,6 +850,22 @@ module.exports.tests.failure_conditions = (test, common) => {
country: [
{ id: 100, name: 'country name', abbr: 'xyz'},
{ id: 101, name: 'country name 2'}
],
empire: [
{ id: 110, name: 'empire name', abbr: 'empire abbr'},
{ id: 111, name: 'empire name 2'}
],
continent: [
{ id: 120, name: 'continent name', abbr: 'continent abbr'},
{ id: 121, name: 'continent name 2'}
],
ocean: [
{ id: 130, name: 'ocean name', abbr: 'ocean abbr'},
{ id: 131, name: 'ocean name 2'}
],
marinearea: [
{ id: 140, name: 'marinearea name', abbr: 'marinearea abbr'},
{ id: 141, name: 'marinearea name 2'}
]
};
@ -860,6 +904,117 @@ module.exports.tests.failure_conditions = (test, common) => {
});
test('service returns 0 length name', (t) => {
t.plan(6);
const service = (req, callback) => {
t.deepEquals(req, { clean: { layers: ['neighbourhood'] } } );
const results = {
neighbourhood: [
{ id: 20, name: '' }
]
};
callback(undefined, results);
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../controller/coarse_reverse', {
'pelias-logger': logger
})(service, _.constant(true));
const req = {
clean: {
layers: ['neighbourhood']
}
};
const res = { };
// verify that next was called
const next = () => {
t.pass('next() was called');
};
controller(req, res, next);
const expected = {
meta: {},
data: []
};
t.deepEquals(res, expected);
// logger messages
t.true(logger.hasMessages('info'), '[controller:coarse_reverse][error]');
t.true(logger.hasMessages('error'), 'invalid document type, expecting: truthy, got: ');
t.true(logger.hasMessages('info'), '{ neighbourhood: [ { id: 20, name: \'\' } ] }');
t.end();
});
test('service returns 0 length abbr', (t) => {
t.plan(4);
const service = (req, callback) => {
t.deepEquals(req, { clean: { layers: ['neighbourhood'] } } );
const results = {
neighbourhood: [
{ id: 20, name: 'Example', abbr: '' }
]
};
callback(undefined, results);
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../controller/coarse_reverse', {
'pelias-logger': logger
})(service, _.constant(true));
const req = {
clean: {
layers: ['neighbourhood']
}
};
const res = { };
// verify that next was called
const next = () => {
t.pass('next() was called');
};
controller(req, res, next);
const expected = {
meta: {},
data: [{
name: { default: 'Example' },
phrase: { default: 'Example' },
parent: {
neighbourhood: [ 'Example' ],
neighbourhood_id: [ '20' ],
neighbourhood_a: [ null ]
},
source: 'whosonfirst',
layer: 'neighbourhood',
source_id: '20',
_id: '20',
_type: 'neighbourhood'
}]
};
t.deepEquals(res, expected);
t.notOk(logger.hasErrorMessages());
t.end();
});
};
module.exports.all = (tape, common) => {

4
test/unit/controller/libpostal.js

@ -149,6 +149,7 @@ module.exports.tests.parse_is_called = (test, common) => {
controller(req, res, () => {
t.deepEquals(req, {
clean: {
parser: 'libpostal',
parsed_text: 'replacement parsed_text'
}
});
@ -184,6 +185,7 @@ module.exports.tests.iso2_conversion = (test, common) => {
controller(req, res, () => {
t.deepEquals(req, {
clean: {
parser: 'libpostal',
parsed_text: {
locality: 'this is the locality'
}
@ -223,6 +225,7 @@ module.exports.tests.iso2_conversion = (test, common) => {
controller(req, res, () => {
t.deepEquals(req, {
clean: {
parser: 'libpostal',
parsed_text: {
country: 'unknown country code'
}
@ -265,6 +268,7 @@ module.exports.tests.iso2_conversion = (test, common) => {
controller(req, res, () => {
t.deepEquals(req, {
clean: {
parser: 'libpostal',
parsed_text: {
country: 'ISO3 COUNTRY CODE'
}

4
test/unit/controller/place.js

@ -51,11 +51,11 @@ module.exports.tests.success = (test, common) => {
ids: [
{
id: 'id1',
layers: 'layer1'
layer: 'layer1'
},
{
id: 'id2',
layers: 'layer2'
layer: 'layer2'
}
]
},

4
test/unit/controller/predicates/is_coarse_reverse.js

@ -17,7 +17,9 @@ const coarse_layers = [
'borough',
'neighbourhood',
'microhood',
'disputed'
'disputed',
'ocean',
'marinearea'
];
module.exports.tests = {};

2
test/unit/controller/search_with_ids.js

@ -388,7 +388,7 @@ module.exports.tests.service_errors = (test, common) => {
const next = () => {
t.deepEqual(logger.getInfoMessages(), [
'[req]',
'[req] endpoint=undefined {}',
'request timed out on attempt 1, retrying',
'request timed out on attempt 2, retrying',
'request timed out on attempt 3, retrying'

39
test/unit/fixture/reverse_null_island.js

@ -1,39 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 0,
'lon': 0
}
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 0,
'lon': 0
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

39
test/unit/fixture/reverse_standard.js

@ -1,39 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

49
test/unit/fixture/reverse_with_boundary_country.js

@ -1,49 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'must': [
{
'match': {
'parent.country_a': {
'analyzer': 'standard',
'query': 'ABC'
}
}
}
],
'filter': [{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

41
test/unit/fixture/reverse_with_layer_filtering.js

@ -1,41 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [
{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}
]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

41
test/unit/fixture/reverse_with_layer_filtering_non_coarse_subset.js

@ -1,41 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [
{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'layer': ['venue', 'street']
}
}
]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

46
test/unit/fixture/reverse_with_source_filtering.js

@ -1,46 +0,0 @@
var vs = require('../../../query/reverse_defaults');
module.exports = {
'query': {
'bool': {
'filter': [
{
'geo_distance': {
'distance': '3km',
'distance_type': 'plane',
'optimize_bbox': 'indexed',
'center_point': {
'lat': 29.49136,
'lon': -82.50622
}
}
},
{
'terms': {
'source': ['test']
}
},
{
'terms': {
'layer': ['venue', 'address', 'street']
}
}
]
}
},
'sort': [
'_score',
{
'_geo_distance': {
'center_point': {
'lat': 29.49136,
'lon': -82.50622
},
'order': 'asc',
'distance_type': 'plane'
}
}
],
'size': vs.size,
'track_scores': true
};

71
test/unit/fixture/search_linguistic_viewport.js

@ -1,71 +0,0 @@
module.exports = {
'query': {
'function_score': {
'query': {
'bool': {
'minimum_should_match': 1,
'should': [
{
'bool': {
'_name': 'fallback.street',
'boost': 5,
'must': [
{
'match_phrase': {
'address_parts.street': 'street value'
}
}
],
'should': [],
'filter': {
'term': {
'layer': 'street'
}
}
}
}
],
'filter': {
'bool': {
'must': [
{
'terms': {
'layer': [
'test'
]
}
}
]
}
}
}
},
'max_boost': 20,
'functions': [
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'popularity',
'missing': 1
},
'weight': 1
},
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'population',
'missing': 1
},
'weight': 2
}
],
'score_mode': 'avg',
'boost_mode': 'multiply'
}
},
'size': 10,
'track_scores': true,
'sort': [
'_score'
]
};

71
test/unit/fixture/search_linguistic_viewport_min_diagonal.js

@ -1,71 +0,0 @@
module.exports = {
'query': {
'function_score': {
'query': {
'bool': {
'minimum_should_match': 1,
'should': [
{
'bool': {
'_name': 'fallback.street',
'boost': 5,
'must': [
{
'match_phrase': {
'address_parts.street': 'street value'
}
}
],
'should': [],
'filter': {
'term': {
'layer': 'street'
}
}
}
}
],
'filter': {
'bool': {
'must': [
{
'terms': {
'layer': [
'test'
]
}
}
]
}
}
}
},
'max_boost': 20,
'functions': [
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'popularity',
'missing': 1
},
'weight': 1
},
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'population',
'missing': 1
},
'weight': 2
}
],
'score_mode': 'avg',
'boost_mode': 'multiply'
}
},
'size': 10,
'track_scores': true,
'sort': [
'_score'
]
};

59
test/unit/fixture/structured_geocoding/boundary_country.json

@ -1,59 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"match": {
"parent.country_a": {
"analyzer": "standard",
"query": "ABC"
}
}
},
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

859
test/unit/fixture/structured_geocoding/fallback.json

@ -1,859 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [
{
"bool": {
"_name": "fallback.venue",
"must": [
{
"multi_match": {
"query": "query value",
"type": "phrase",
"fields": [
"phrase.default",
"category"
]
}
},
{
"multi_match": {
"query": "neighbourhood value",
"type": "phrase",
"fields": [
"parent.neighbourhood",
"parent.neighbourhood_a"
]
}
},
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "venue"
}
}
}
},
{
"bool": {
"_name": "fallback.address",
"must": [
{
"match_phrase": {
"address_parts.number": "number value"
}
},
{
"match_phrase": {
"address_parts.street": "street value"
}
},
{
"multi_match": {
"query": "neighbourhood value",
"type": "phrase",
"fields": [
"parent.neighbourhood",
"parent.neighbourhood_a"
]
}
},
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"should": [
{
"match_phrase": {
"address_parts.zip": "postalcode value"
}
}
],
"filter": {
"term": {
"layer": "address"
}
},
"boost": 10
}
},
{
"bool": {
"_name": "fallback.street",
"must": [
{
"match_phrase": {
"address_parts.street": "street value"
}
},
{
"multi_match": {
"query": "neighbourhood value",
"type": "phrase",
"fields": [
"parent.neighbourhood",
"parent.neighbourhood_a"
]
}
},
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"should": [
{
"match_phrase": {
"address_parts.zip": "postalcode value"
}
}
],
"filter": {
"term": {
"layer": "street"
}
},
"boost": 5
}
},
{
"bool": {
"_name": "fallback.postalcode",
"must": [
{
"multi_match": {
"query": "postalcode value",
"type": "phrase",
"fields": [
"parent.postalcode"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "postalcode"
}
}
}
},
{
"bool": {
"_name": "fallback.neighbourhood",
"must": [
{
"multi_match": {
"query": "neighbourhood value",
"type": "phrase",
"fields": [
"parent.neighbourhood",
"parent.neighbourhood_a"
]
}
},
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "neighbourhood"
}
}
}
},
{
"bool": {
"_name": "fallback.borough",
"must": [
{
"multi_match": {
"query": "borough value",
"type": "phrase",
"fields": [
"parent.borough",
"parent.borough_a"
]
}
},
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a",
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "borough"
}
}
}
},
{
"bool": {
"_name": "fallback.locality",
"must": [
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.locality",
"parent.locality_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "locality"
}
}
}
},
{
"bool": {
"_name": "fallback.localadmin",
"must": [
{
"multi_match": {
"query": "city value",
"type": "phrase",
"fields": [
"parent.localadmin",
"parent.localadmin_a"
]
}
},
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a",
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "localadmin"
}
}
}
},
{
"bool": {
"_name": "fallback.county",
"must": [
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.county",
"parent.county_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "county"
}
}
}
},
{
"bool": {
"_name": "fallback.macrocounty",
"must": [
{
"multi_match": {
"query": "county value",
"type": "phrase",
"fields": [
"parent.macrocounty",
"parent.macrocounty_a"
]
}
},
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a",
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "macrocounty"
}
}
}
},
{
"bool": {
"_name": "fallback.region",
"must": [
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.region",
"parent.region_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "region"
}
}
}
},
{
"bool": {
"_name": "fallback.macroregion",
"must": [
{
"multi_match": {
"query": "state value",
"type": "phrase",
"fields": [
"parent.macroregion",
"parent.macroregion_a"
]
}
},
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a",
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "macroregion"
}
}
}
},
{
"bool": {
"_name": "fallback.dependency",
"must": [
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.dependency",
"parent.dependency_a"
]
}
}
],
"filter": {
"term": {
"layer": "dependency"
}
}
}
},
{
"bool": {
"_name": "fallback.country",
"must": [
{
"multi_match": {
"query": "country value",
"type": "phrase",
"fields": [
"parent.country",
"parent.country_a"
]
}
}
],
"filter": {
"term": {
"layer": "country"
}
}
}
}
]
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 20,
"track_scores": true
}

62
test/unit/fixture/structured_geocoding/linguistic_bbox.json

@ -1,62 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"geo_bounding_box": {
"type": "indexed",
"center_point": {
"top": 11.51,
"right": -61.84,
"bottom": 47.47,
"left": -103.16
}
}
},
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

65
test/unit/fixture/structured_geocoding/linguistic_focus.json

@ -1,65 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"weight": 2,
"linear": {
"center_point": {
"origin": {
"lat": 29.49136,
"lon": -82.50622
},
"offset": "0km",
"scale": "50km",
"decay": 0.5
}
}
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

76
test/unit/fixture/structured_geocoding/linguistic_focus_bbox.json

@ -1,76 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"geo_bounding_box": {
"type": "indexed",
"center_point": {
"top": 11.51,
"right": -61.84,
"bottom": 47.47,
"left": -103.16
}
}
},
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"weight": 2,
"linear": {
"center_point": {
"origin": {
"lat": 29.49136,
"lon": -82.50622
},
"offset": "0km",
"scale": "50km",
"decay": 0.5
}
}
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

65
test/unit/fixture/structured_geocoding/linguistic_focus_null_island.json

@ -1,65 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"weight": 2,
"linear": {
"center_point": {
"origin": {
"lat": 0,
"lon": 0
},
"offset": "0km",
"scale": "50km",
"decay": 0.5
}
}
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

51
test/unit/fixture/structured_geocoding/linguistic_only.json

@ -1,51 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

51
test/unit/fixture/structured_geocoding/linguistic_viewport.json

@ -1,51 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

51
test/unit/fixture/structured_geocoding/linguistic_viewport_min_diagonal.json

@ -1,51 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [],
"filter": {
"bool": {
"must": [
{
"terms": {
"layer": [
"test"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 10,
"track_scores": true
}

60
test/unit/fixture/structured_geocoding/postalcode_only.js

@ -1,60 +0,0 @@
module.exports = {
'query': {
'function_score': {
'query': {
'bool': {
'minimum_should_match': 1,
'should': [
{
'bool': {
'_name': 'fallback.postalcode',
'must': [
{
'multi_match': {
'query': 'postalcode value',
'type': 'phrase',
'fields': [
'parent.postalcode'
]
}
}
],
'filter': {
'term': {
'layer': 'postalcode'
}
}
}
}
]
}
},
'max_boost': 20,
'functions': [
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'popularity',
'missing': 1
},
'weight': 1
},
{
'field_value_factor': {
'modifier': 'log1p',
'field': 'population',
'missing': 1
},
'weight': 2
}
],
'score_mode': 'avg',
'boost_mode': 'multiply'
}
},
'size': 20,
'track_scores': true,
'sort': [
'_score'
]
};

52
test/unit/fixture/structured_geocoding/with_source_filtering.json

@ -1,52 +0,0 @@
{
"query": {
"function_score": {
"query": {
"bool": {
"minimum_should_match": 1,
"should": [
],
"filter": {
"bool": {
"must": [
{
"terms": {
"source": [
"test_source"
]
}
}
]
}
}
}
},
"max_boost": 20,
"functions": [
{
"field_value_factor": {
"modifier": "log1p",
"field": "popularity",
"missing": 1
},
"weight": 1
},
{
"field_value_factor": {
"modifier": "log1p",
"field": "population",
"missing": 1
},
"weight": 2
}
],
"score_mode": "avg",
"boost_mode": "multiply"
}
},
"sort": [
"_score"
],
"size": 20,
"track_scores": true
}

910
test/unit/helper/geojsonify.js

@ -1,5 +1,6 @@
const geojsonify = require('../../../helper/geojsonify');
var geojsonify = require('../../../helper/geojsonify');
const proxyquire = require('proxyquire').noCallThru();
module.exports.tests = {};
@ -36,433 +37,610 @@ module.exports.tests.earth = function(test, common) {
};
module.exports.tests.geojsonify = function(test, common) {
module.exports.tests.all = (test, common) => {
test('bounding_box should be calculated using points when avaiable', t => {
const input = [
{
_id: 'id 1',
source: 'source 1',
source_id: 'source_id 1',
layer: 'layer 1',
name: {
default: 'name 1',
},
center_point: {
lat: 12.121212,
lon: 21.212121
}
},
{
_id: 'id 2',
source: 'source 2',
source_id: 'source_id 2',
layer: 'layer 2',
name: {
default: 'name 2',
},
center_point: {
lat: 13.131313,
lon: 31.313131
}
}
];
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
if (source._id === 'id 1') {
return {
property1: 'property 1',
property2: 'property 2'
};
} else if (source._id === 'id 2') {
return {
property3: 'property 3',
property4: 'property 4'
};
}
}
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 21.212121, 12.121212 ]
},
properties: {
id: 'id 1',
gid: 'source 1:layer 1:id 1',
layer: 'layer 1',
source: 'source 1',
source_id: 'source_id 1',
name: 'name 1',
property1: 'property 1',
property2: 'property 2'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 31.313131, 13.131313 ]
},
properties: {
id: 'id 2',
gid: 'source 2:layer 2:id 2',
layer: 'layer 2',
source: 'source 2',
source_id: 'source_id 2',
name: 'name 2',
property3: 'property 3',
property4: 'property 4'
}
}
],
bbox: [21.212121, 12.121212, 31.313131, 13.131313]
};
t.deepEquals(actual, expected);
t.end();
test('geojsonify(doc)', function(t) {
var input = [
});
test('bounding_box should be calculated using polygons when avaiable', t => {
const input = [
{
'_id': 'id1',
'_type': 'layer1',
'source': 'source1',
'source_id': 'source_id_1',
'layer': 'layer1',
'center_point': {
'lat': 51.5337144,
'lon': -0.1069716
_id: 'id 1',
source: 'source 1',
source_id: 'source_id 1',
layer: 'layer 1',
name: {
default: 'name 1',
},
'name': {
'default': '\'Round Midnight Jazz and Blues Bar'
bounding_box: {
min_lon: 1,
min_lat: 1,
max_lon: 2,
max_lat: 2
},
'housenumber': '13',
'street': 'Liverpool Road',
'postalcode': 'N1 0RW',
'country_a': 'GBR',
'country': 'United Kingdom',
'dependency': 'dependency name',
'region': 'Islington',
'region_a': 'ISL',
'macroregion': 'England',
'county': 'Angel',
'localadmin': 'test1',
'locality': 'test2',
'neighbourhood': 'test3',
'category': [
'food',
'nightlife'
],
'label': 'label for id id1'
center_point: {
lat: 12.121212,
lon: 21.212121
}
},
{
'_id': 'id2',
'_type': 'layer2',
'source': 'source2',
'source_id': 'source_id_2',
'layer': 'layer2',
'name': {
'default': 'Blues Cafe'
_id: 'id 2',
source: 'source 2',
source_id: 'source_id 2',
layer: 'layer 2',
name: {
default: 'name 2',
},
'center_point': {
'lat': '51.517806',
'lon': '-0.101795'
bounding_box: {
min_lon: -3,
min_lat: -3,
max_lon: -1,
max_lat: -1
},
'country_a': 'GBR',
'country': 'United Kingdom',
'dependency': 'dependency name',
'region': 'City And County Of The City Of London',
'region_a': 'COL',
'macroregion': 'England',
'county': 'Smithfield',
'localadmin': 'test1',
'locality': 'test2',
'neighbourhood': 'test3',
'label': 'label for id id2'
center_point: {
lat: 13.131313,
lon: 31.313131
}
}
];
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
if (source._id === 'id 1') {
return {
property1: 'property 1',
property2: 'property 2'
};
} else if (source._id === 'id 2') {
return {
property3: 'property 3',
property4: 'property 4'
};
}
}
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 21.212121, 12.121212 ]
},
properties: {
id: 'id 1',
gid: 'source 1:layer 1:id 1',
layer: 'layer 1',
source: 'source 1',
source_id: 'source_id 1',
name: 'name 1',
property1: 'property 1',
property2: 'property 2'
},
bbox: [ 1, 1, 2, 2 ]
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 31.313131, 13.131313 ]
},
properties: {
id: 'id 2',
gid: 'source 2:layer 2:id 2',
layer: 'layer 2',
source: 'source 2',
source_id: 'source_id 2',
name: 'name 2',
property3: 'property 3',
property4: 'property 4'
},
bbox: [ -3, -3, -1, -1 ]
}
],
bbox: [ -3, -3, 2, 2 ]
};
t.deepEquals(actual, expected);
t.end();
});
test('bounding_box should be calculated using polygons AND points when avaiable', t => {
const input = [
{
_id: 'id 1',
source: 'source 1',
source_id: 'source_id 1',
layer: 'layer 1',
name: {
default: 'name 1',
},
center_point: {
lat: 12.121212,
lon: 21.212121
}
},
{
'_id': 'node:34633854',
'_type': 'venue',
'source': 'openstreetmap',
'source_id': 'source_id_3',
'layer': 'venue',
'name': {
'default': 'Empire State Building'
_id: 'id 2',
source: 'source 2',
source_id: 'source_id 2',
layer: 'layer 2',
name: {
default: 'name 2',
},
'center_point': {
'lat': '40.748432',
'lon': '-73.985656'
bounding_box: {
min_lon: -3,
min_lat: -3,
max_lon: -1,
max_lat: -1
},
'country_a': 'USA',
'country': 'United States',
'dependency': 'dependency name',
'region': 'New York',
'region_a': 'NY',
'county': 'New York',
'borough': 'Manhattan',
'locality': 'New York',
'neighbourhood': 'Koreatown',
'category': [
'tourism',
'transport'
],
'label': 'label for id node:34633854'
center_point: {
lat: 13.131313,
lon: 31.313131
}
}
];
var expected = {
'type': 'FeatureCollection',
'bbox': [ -73.985656, 40.748432, -0.101795, 51.5337144 ],
'features': [
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
if (source._id === 'id 1') {
return {
property1: 'property 1',
property2: 'property 2'
};
} else if (source._id === 'id 2') {
return {
property3: 'property 3',
property4: 'property 4'
};
}
}
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [
-0.1069716,
51.5337144
]
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 21.212121, 12.121212 ]
},
'properties': {
'id': 'id1',
'gid': 'source1:layer1:id1',
'layer': 'layer1',
'source': 'source1',
'source_id': 'source_id_1',
'name': '\'Round Midnight Jazz and Blues Bar',
'country_a': 'GBR',
'country': 'United Kingdom',
'dependency': 'dependency name',
'macroregion': 'England',
'region': 'Islington',
'region_a': 'ISL',
'county': 'Angel',
'localadmin': 'test1',
'locality': 'test2',
'neighbourhood': 'test3',
'housenumber': '13',
'street': 'Liverpool Road',
'postalcode': 'N1 0RW',
'category': [
'food',
'nightlife'
],
'label': 'label for id id1'
properties: {
id: 'id 1',
gid: 'source 1:layer 1:id 1',
layer: 'layer 1',
source: 'source 1',
source_id: 'source_id 1',
name: 'name 1',
property1: 'property 1',
property2: 'property 2'
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [
-0.101795,
51.517806
]
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 31.313131, 13.131313 ]
},
'properties': {
'id': 'id2',
'gid': 'source2:layer2:id2',
'layer': 'layer2',
'source': 'source2',
'source_id': 'source_id_2',
'name': 'Blues Cafe',
'country_a': 'GBR',
'country': 'United Kingdom',
'dependency': 'dependency name',
'macroregion': 'England',
'region': 'City And County Of The City Of London',
'region_a': 'COL',
'county': 'Smithfield',
'localadmin': 'test1',
'locality': 'test2',
'neighbourhood': 'test3',
'label': 'label for id id2'
}
properties: {
id: 'id 2',
gid: 'source 2:layer 2:id 2',
layer: 'layer 2',
source: 'source 2',
source_id: 'source_id 2',
name: 'name 2',
property3: 'property 3',
property4: 'property 4'
},
bbox: [ -3, -3, -1, -1 ]
}
],
bbox: [ -3, -3, 21.212121, 12.121212 ]
};
t.deepEquals(actual, expected);
t.end();
});
};
module.exports.tests.non_optimal_conditions = (test, common) => {
test('null/undefined places should log warnings and be ignored', t => {
const logger = require('pelias-mock-logger')();
const input = [
null,
undefined,
{
_id: 'id 1',
source: 'source 1',
source_id: 'source_id 1',
layer: 'layer 1',
name: {
default: 'name 1',
},
center_point: {
lat: 12.121212,
lon: 21.212121
}
}
];
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
if (source._id === 'id 1') {
return {
property1: 'property 1',
property2: 'property 2'
};
}
},
'pelias-logger': logger
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [
-73.985656,
40.748432
]
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 21.212121, 12.121212 ]
},
'properties': {
'id': 'node:34633854',
'gid': 'openstreetmap:venue:node:34633854',
'layer': 'venue',
'source': 'openstreetmap',
'source_id': 'source_id_3',
'name': 'Empire State Building',
'country_a': 'USA',
'country': 'United States',
'dependency': 'dependency name',
'region': 'New York',
'region_a': 'NY',
'county': 'New York',
'borough': 'Manhattan',
'locality': 'New York',
'neighbourhood': 'Koreatown',
'category': [
'tourism',
'transport'
],
'label': 'label for id node:34633854'
properties: {
id: 'id 1',
gid: 'source 1:layer 1:id 1',
layer: 'layer 1',
source: 'source 1',
source_id: 'source_id 1',
name: 'name 1',
property1: 'property 1',
property2: 'property 2'
}
}
]
],
bbox: [21.212121, 12.121212, 21.212121, 12.121212]
};
var json = geojsonify( {categories: 'foo'}, input );
t.deepEqual(json, expected, 'all docs mapped');
t.deepEquals(actual, expected);
t.ok(logger.isWarnMessage('No doc or center_point property'));
t.end();
});
test('filtering out empty items', function (t) {
var input = [
test('places w/o center_point should log warnings and be ignored', t => {
const logger = require('pelias-mock-logger')();
const input = [
{
'bounding_box': {
'min_lat': 40.6514712164,
'max_lat': 40.6737320588,
'min_lon': -73.8967895508,
'max_lon': -73.8665771484
},
'locality': [
'New York'
],
'source': 'whosonfirst',
'layer': 'neighbourhood',
'population': 173198,
'popularity': 495,
'center_point': {
'lon': -73.881319,
'lat': 40.663303
},
'name': {
'default': 'East New York'
_id: 'id 1',
source: 'source 1',
source_id: 'source_id 1',
layer: 'layer 1',
name: {
default: 'name 1',
}
},
{
_id: 'id 2',
source: 'source 2',
source_id: 'source_id 2',
layer: 'layer 2',
name: {
default: 'name 2',
},
'source_id': '85816607',
'category': ['government'],
'_id': '85816607',
'_type': 'neighbourhood',
'_score': 21.434,
'confidence': 0.888,
'country': [
'United States'
],
'country_gid': [
'85633793'
],
'country_a': [
'USA'
],
'dependency': [
'dependency name'
],
'dependency_gid': [
'dependency id'
],
'dependency_a': [
'dependency abbrevation'
],
'macroregion': [
'MacroRegion Name'
],
'macroregion_gid': [
'MacroRegion Id'
],
'macroregion_a': [
'MacroRegion Abbreviation'
],
'region': [
'New York'
],
'region_gid': [
'85688543'
],
'region_a': [
'NY'
],
'macrocounty': [
'MacroCounty Name'
],
'macrocounty_gid': [
'MacroCounty Id'
],
'macrocounty_a': [
'MacroCounty Abbreviation'
],
'county': [
'Kings County'
],
'county_gid': [
'102082361'
],
'county_a': [
null
],
'borough': [
'Brooklyn'
],
'localadmin_gid': [
'404521211'
],
'borough_a': [
null
],
'locality_gid': [
'85977539'
],
'locality_a': [
null
],
'neighbourhood': [],
'neighbourhood_gid': [],
'label': 'label for id 85816607'
center_point: {
lat: 13.131313,
lon: 31.313131
}
}
];
var expected = {
'type': 'FeatureCollection',
'bbox': [-73.8967895508, 40.6514712164, -73.8665771484, 40.6737320588],
'features': [
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
if (source._id === 'id 2') {
return {
property3: 'property 3',
property4: 'property 4'
};
}
},
'pelias-logger': logger
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{
'type': 'Feature',
'properties': {
'id': '85816607',
'gid': 'whosonfirst:neighbourhood:85816607',
'layer': 'neighbourhood',
'source': 'whosonfirst',
'source_id': '85816607',
'name': 'East New York',
'category': ['government'],
'confidence': 0.888,
'country': 'United States',
'country_gid': '85633793',
'country_a': 'USA',
'dependency': 'dependency name',
'dependency_gid': 'dependency id',
'dependency_a': 'dependency abbrevation',
'macroregion': 'MacroRegion Name',
'macroregion_gid': 'MacroRegion Id',
'macroregion_a': 'MacroRegion Abbreviation',
'region': 'New York',
'region_gid': '85688543',
'region_a': 'NY',
'macrocounty': 'MacroCounty Name',
'macrocounty_gid': 'MacroCounty Id',
'macrocounty_a': 'MacroCounty Abbreviation',
'county': 'Kings County',
'borough': 'Brooklyn',
'county_gid': '102082361',
'localadmin_gid': '404521211',
'locality': 'New York',
'locality_gid': '85977539',
'label': 'label for id 85816607'
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 31.313131, 13.131313 ]
},
'bbox': [-73.8967895508,40.6514712164,-73.8665771484,40.6737320588],
'geometry': {
'type': 'Point',
'coordinates': [
-73.881319,
40.663303
]
properties: {
id: 'id 2',
gid: 'source 2:layer 2:id 2',
layer: 'layer 2',
source: 'source 2',
source_id: 'source_id 2',
name: 'name 2',
property3: 'property 3',
property4: 'property 4'
}
}
]
],
bbox: [31.313131, 13.131313, 31.313131, 13.131313]
};
var json = geojsonify( {categories: 'foo'}, input );
t.deepEqual(json, expected, 'all wanted properties exposed');
t.deepEquals(actual, expected);
t.ok(logger.isWarnMessage('No doc or center_point property'));
t.end();
});
};
module.exports.tests.categories = function (test, common) {
test('only set category if categories filter was used', function (t) {
var input = [
test('places w/o names should log warnings and be ignored', t => {
const logger = require('pelias-mock-logger')();
const input = [
{
'_id': '85816607',
'bounding_box': {
'min_lat': 40.6514712164,
'max_lat': 40.6737320588,
'min_lon': -73.8967895508,
'max_lon': -73.8665771484
},
'source': 'whosonfirst',
'layer': 'neighbourhood',
'center_point': {
'lon': -73.881319,
'lat': 40.663303
},
'name': {
'default': 'East New York'
_id: 'id 1',
source: 'source 1',
source_id: 'source_id 1',
layer: 'layer 1',
center_point: {
lat: 12.121212,
lon: 21.212121
}
},
{
_id: 'id 2',
source: 'source 2',
source_id: 'source_id 2',
layer: 'layer 2',
name: {},
center_point: {
lat: 13.131313,
lon: 31.313131
}
},
{
_id: 'id 3',
source: 'source 3',
source_id: 'source_id 3',
layer: 'layer 3',
name: {
default: 'name 3',
},
'source_id': '85816607',
'category': ['government'],
'label': 'label for id 85816607'
center_point: {
lat: 14.141414,
lon: 41.414141
}
}
];
var expected = {
'type': 'FeatureCollection',
'bbox': [-73.8967895508, 40.6514712164, -73.8665771484, 40.6737320588],
'features': [
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
if (source._id === 'id 1') {
return {
property1: 'property 1',
property2: 'property 2'
};
} else if (source._id === 'id 2') {
return {
property3: 'property 3',
property4: 'property 4'
};
} else if (source._id === 'id 3') {
return {
property5: 'property 5',
property6: 'property 6'
};
}
},
'pelias-logger': logger
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: [
{
'type': 'Feature',
'properties': {
'id': '85816607',
'gid': 'whosonfirst:neighbourhood:85816607',
'layer': 'neighbourhood',
'source': 'whosonfirst',
'source_id': '85816607',
'name': 'East New York',
'category': ['government'],
'label': 'label for id 85816607'
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 21.212121, 12.121212 ]
},
'bbox': [-73.8967895508,40.6514712164,-73.8665771484,40.6737320588],
'geometry': {
'type': 'Point',
'coordinates': [
-73.881319,
40.663303
]
properties: {
id: 'id 1',
gid: 'source 1:layer 1:id 1',
layer: 'layer 1',
source: 'source 1',
source_id: 'source_id 1',
property1: 'property 1',
property2: 'property 2'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 31.313131, 13.131313 ]
},
properties: {
id: 'id 2',
gid: 'source 2:layer 2:id 2',
layer: 'layer 2',
source: 'source 2',
source_id: 'source_id 2',
property3: 'property 3',
property4: 'property 4'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [ 41.414141, 14.141414 ]
},
properties: {
id: 'id 3',
gid: 'source 3:layer 3:id 3',
layer: 'layer 3',
source: 'source 3',
source_id: 'source_id 3',
name: 'name 3',
property5: 'property 5',
property6: 'property 6'
}
}
]
],
bbox: [21.212121, 12.121212, 41.414141, 14.141414]
};
var json = geojsonify( {categories: 'foo'}, input );
t.deepEquals(actual, expected);
t.ok(logger.isWarnMessage('doc source 1:layer 1:id 1 does not contain name.default'));
t.ok(logger.isWarnMessage('doc source 2:layer 2:id 2 does not contain name.default'));
t.end();
t.deepEqual(json, expected, 'all wanted properties exposed');
});
test('no points', t => {
const logger = require('pelias-mock-logger')();
const input = [];
const geojsonify = proxyquire('../../../helper/geojsonify', {
'./geojsonify_place_details': (params, source, dst) => {
t.fail('should not have bee called');
},
'pelias-logger': logger
});
const actual = geojsonify({}, input);
const expected = {
type: 'FeatureCollection',
features: []
};
t.deepEquals(actual, expected);
t.end();
});
};
module.exports.all = function (tape, common) {
};
module.exports.all = (tape, common) => {
function test(name, testFunction) {
return tape('geojsonify: ' + name, testFunction);
return tape(`geojsonify: ${name}`, testFunction);
}
for( var testCase in module.exports.tests ){

590
test/unit/helper/geojsonify_place_details.js

@ -0,0 +1,590 @@
const geojsonify = require('../../../helper/geojsonify_place_details');
module.exports.tests = {};
module.exports.tests.geojsonify_place_details = (test, common) => {
test('plain old string values should be copied verbatim, replacing old values', t => {
const source = {
housenumber: 'housenumber value',
street: 'street value',
postalcode: 'postalcode value',
postalcode_gid: 'postalcode_gid value',
match_type: 'match_type value',
accuracy: 'accuracy value',
country: 'country value',
country_gid: 'country_gid value',
country_a: 'country_a value',
dependency: 'dependency value',
dependency_gid: 'dependency_gid value',
dependency_a: 'dependency_a value',
macroregion: 'macroregion value',
macroregion_gid: 'macroregion_gid value',
macroregion_a: 'macroregion_a value',
region: 'region value',
region_gid: 'region_gid value',
region_a: 'region_a value',
macrocounty: 'macrocounty value',
macrocounty_gid: 'macrocounty_gid value',
macrocounty_a: 'macrocounty_a value',
county: 'county value',
county_gid: 'county_gid value',
county_a: 'county_a value',
localadmin: 'localadmin value',
localadmin_gid: 'localadmin_gid value',
localadmin_a: 'localadmin_a value',
locality: 'locality value',
locality_gid: 'locality_gid value',
locality_a: 'locality_a value',
borough: 'borough value',
borough_gid: 'borough_gid value',
borough_a: 'borough_a value',
neighbourhood: 'neighbourhood value',
neighbourhood_gid: 'neighbourhood_gid value',
continent: 'continent value',
continent_gid: 'continent_gid value',
continent_a: 'continent_a value',
ocean: 'ocean value',
ocean_gid: 'ocean_gid value',
ocean_a: 'ocean_a value',
marinearea: 'marinearea value',
marinearea_gid: 'marinearea_gid value',
marinearea_a: 'marinearea_a value',
label: 'label value'
};
const expected = {
housenumber: 'housenumber value',
street: 'street value',
postalcode: 'postalcode value',
postalcode_gid: 'postalcode_gid value',
match_type: 'match_type value',
accuracy: 'accuracy value',
country: 'country value',
country_gid: 'country_gid value',
country_a: 'country_a value',
dependency: 'dependency value',
dependency_gid: 'dependency_gid value',
dependency_a: 'dependency_a value',
macroregion: 'macroregion value',
macroregion_gid: 'macroregion_gid value',
macroregion_a: 'macroregion_a value',
region: 'region value',
region_gid: 'region_gid value',
region_a: 'region_a value',
macrocounty: 'macrocounty value',
macrocounty_gid: 'macrocounty_gid value',
macrocounty_a: 'macrocounty_a value',
county: 'county value',
county_gid: 'county_gid value',
county_a: 'county_a value',
localadmin: 'localadmin value',
localadmin_gid: 'localadmin_gid value',
localadmin_a: 'localadmin_a value',
locality: 'locality value',
locality_gid: 'locality_gid value',
locality_a: 'locality_a value',
borough: 'borough value',
borough_gid: 'borough_gid value',
borough_a: 'borough_a value',
neighbourhood: 'neighbourhood value',
neighbourhood_gid: 'neighbourhood_gid value',
continent: 'continent value',
continent_gid: 'continent_gid value',
continent_a: 'continent_a value',
ocean: 'ocean value',
ocean_gid: 'ocean_gid value',
ocean_a: 'ocean_a value',
marinearea: 'marinearea value',
marinearea_gid: 'marinearea_gid value',
marinearea_a: 'marinearea_a value',
label: 'label value'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('\'empty\' string-type values should be output as \'\'', t => {
[ [], {}, '', 17, true, null, undefined ].forEach(empty_value => {
const source = {
housenumber: empty_value,
street: empty_value,
postalcode: empty_value,
postalcode_gid: empty_value,
match_type: empty_value,
accuracy: empty_value,
country: empty_value,
country_gid: empty_value,
country_a: empty_value,
dependency: empty_value,
dependency_gid: empty_value,
dependency_a: empty_value,
macroregion: empty_value,
macroregion_gid: empty_value,
macroregion_a: empty_value,
region: empty_value,
region_gid: empty_value,
region_a: empty_value,
macrocounty: empty_value,
macrocounty_gid: empty_value,
macrocounty_a: empty_value,
county: empty_value,
county_gid: empty_value,
county_a: empty_value,
localadmin: empty_value,
localadmin_gid: empty_value,
localadmin_a: empty_value,
locality: empty_value,
locality_gid: empty_value,
locality_a: empty_value,
borough: empty_value,
borough_gid: empty_value,
borough_a: empty_value,
neighbourhood: empty_value,
neighbourhood_gid: empty_value,
continent: empty_value,
continent_gid: empty_value,
continent_a: empty_value,
ocean: empty_value,
ocean_gid: empty_value,
ocean_a: empty_value,
marinearea: empty_value,
marinearea_gid: empty_value,
marinearea_a: empty_value,
label: empty_value
};
const expected = {};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('source arrays should be copy first value', t => {
const source = {
housenumber: ['housenumber value 1', 'housenumber value 2'],
street: ['street value 1', 'street value 2'],
postalcode: ['postalcode value 1', 'postalcode value 2'],
postalcode_gid: ['postalcode_gid value 1', 'postalcode_gid value 2'],
match_type: ['match_type value 1', 'match_type value 2'],
accuracy: ['accuracy value 1', 'accuracy value 2'],
country: ['country value 1', 'country value 2'],
country_gid: ['country_gid value 1', 'country_gid value 2'],
country_a: ['country_a value 1', 'country_a value 2'],
dependency: ['dependency value 1', 'dependency value 2'],
dependency_gid: ['dependency_gid value 1', 'dependency_gid value 2'],
dependency_a: ['dependency_a value 1', 'dependency_a value 2'],
macroregion: ['macroregion value 1', 'macroregion value 2'],
macroregion_gid: ['macroregion_gid value 1', 'macroregion_gid value 2'],
macroregion_a: ['macroregion_a value 1', 'macroregion_a value 2'],
region: ['region value 1', 'region value 2'],
region_gid: ['region_gid value 1', 'region_gid value 2'],
region_a: ['region_a value 1', 'region_a value 2'],
macrocounty: ['macrocounty value 1', 'macrocounty value 2'],
macrocounty_gid: ['macrocounty_gid value 1', 'macrocounty_gid value 2'],
macrocounty_a: ['macrocounty_a value 1', 'macrocounty_a value 2'],
county: ['county value 1', 'county value 2'],
county_gid: ['county_gid value 1', 'county_gid value 2'],
county_a: ['county_a value 1', 'county_a value 2'],
localadmin: ['localadmin value 1', 'localadmin value 2'],
localadmin_gid: ['localadmin_gid value 1', 'localadmin_gid value 2'],
localadmin_a: ['localadmin_a value 1', 'localadmin_a value 2'],
locality: ['locality value 1', 'locality value 2'],
locality_gid: ['locality_gid value 1', 'locality_gid value 2'],
locality_a: ['locality_a value 1', 'locality_a value 2'],
borough: ['borough value 1', 'borough value 2'],
borough_gid: ['borough_gid value 1', 'borough_gid value 2'],
borough_a: ['borough_a value 1', 'borough_a value 2'],
neighbourhood: ['neighbourhood value 1', 'neighbourhood value 2'],
neighbourhood_gid: ['neighbourhood_gid value 1', 'neighbourhood_gid value 2'],
continent: ['continent value 1', 'continent value 2'],
continent_gid: ['continent_gid value 1', 'continent_gid value 2'],
continent_a: ['continent_a value 1', 'continent_a value 2'],
ocean: ['ocean value 1', 'ocean value 2'],
ocean_gid: ['ocean_gid value 1', 'ocean_gid value 2'],
ocean_a: ['ocean_a value 1', 'ocean_a value 2'],
marinearea: ['marinearea value 1', 'marinearea value 2'],
marinearea_gid: ['marinearea_gid value 1', 'marinearea_gid value 2'],
marinearea_a: ['marinearea_a value 1','marinearea_a value 2'],
label: ['label value 1', 'label value 2']
};
const expected = {
housenumber: 'housenumber value 1',
street: 'street value 1',
postalcode: 'postalcode value 1',
postalcode_gid: 'postalcode_gid value 1',
match_type: 'match_type value 1',
accuracy: 'accuracy value 1',
country: 'country value 1',
country_gid: 'country_gid value 1',
country_a: 'country_a value 1',
dependency: 'dependency value 1',
dependency_gid: 'dependency_gid value 1',
dependency_a: 'dependency_a value 1',
macroregion: 'macroregion value 1',
macroregion_gid: 'macroregion_gid value 1',
macroregion_a: 'macroregion_a value 1',
region: 'region value 1',
region_gid: 'region_gid value 1',
region_a: 'region_a value 1',
macrocounty: 'macrocounty value 1',
macrocounty_gid: 'macrocounty_gid value 1',
macrocounty_a: 'macrocounty_a value 1',
county: 'county value 1',
county_gid: 'county_gid value 1',
county_a: 'county_a value 1',
localadmin: 'localadmin value 1',
localadmin_gid: 'localadmin_gid value 1',
localadmin_a: 'localadmin_a value 1',
locality: 'locality value 1',
locality_gid: 'locality_gid value 1',
locality_a: 'locality_a value 1',
borough: 'borough value 1',
borough_gid: 'borough_gid value 1',
borough_a: 'borough_a value 1',
neighbourhood: 'neighbourhood value 1',
neighbourhood_gid: 'neighbourhood_gid value 1',
continent: 'continent value 1',
continent_gid: 'continent_gid value 1',
continent_a: 'continent_a value 1',
ocean: 'ocean value 1',
ocean_gid: 'ocean_gid value 1',
ocean_a: 'ocean_a value 1',
marinearea: 'marinearea value 1',
marinearea_gid: 'marinearea_gid value 1',
marinearea_a: 'marinearea_a value 1',
label: 'label value 1'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('non-empty objects should be converted to strings', t => {
// THIS TEST SHOWS THAT THE CODE DOES NOT DO WHAT IT EXPECTED
const source = {
housenumber: { housenumber: 'housenumber value'},
street: { street: 'street value'},
postalcode: { postalcode: 'postalcode value'},
postalcode_gid: { postalcode_gid: 'postalcode_gid value'},
match_type: { match_type: 'match_type value'},
accuracy: { accuracy: 'accuracy value'},
country: { country: 'country value'},
country_gid: { country_gid: 'country_gid value'},
country_a: { country_a: 'country_a value'},
dependency: { dependency: 'dependency value'},
dependency_gid: { dependency_gid: 'dependency_gid value'},
dependency_a: { dependency_a: 'dependency_a value'},
macroregion: { macroregion: 'macroregion value'},
macroregion_gid: { macroregion_gid: 'macroregion_gid value'},
macroregion_a: { macroregion_a: 'macroregion_a value'},
region: { region: 'region value'},
region_gid: { region_gid: 'region_gid value'},
region_a: { region_a: 'region_a value'},
macrocounty: { macrocounty: 'macrocounty value'},
macrocounty_gid: { macrocounty_gid: 'macrocounty_gid value'},
macrocounty_a: { macrocounty_a: 'macrocounty_a value'},
county: { county: 'county value'},
county_gid: { county_gid: 'county_gid value'},
county_a: { county_a: 'county_a value'},
localadmin: { localadmin: 'localadmin value'},
localadmin_gid: { localadmin_gid: 'localadmin_gid value'},
localadmin_a: { localadmin_a: 'localadmin_a value'},
locality: { locality: 'locality value'},
locality_gid: { locality_gid: 'locality_gid value'},
locality_a: { locality_a: 'locality_a value'},
borough: { borough: 'borough value'},
borough_gid: { borough_gid: 'borough_gid value'},
borough_a: { borough_a: 'borough_a value'},
neighbourhood: { neighbourhood: 'neighbourhood value'},
neighbourhood_gid: { neighbourhood_gid: 'neighbourhood_gid value'},
continent: { continent: 'continent value'} ,
continent_gid: { continent: 'continent_gid value'},
continent_a: { continent: 'continent_a value'},
ocean: { ocean: 'ocean value'},
ocean_gid: { ocean_gid: 'ocean_gid value'},
ocean_a: { ocean_a: 'ocean_a value'},
marinearea: { marinearea: 'marinearea value'},
marinearea_gid: { marinearea_gid: 'marinearea_gid value'},
marinearea_a: { marinearea_a: 'marinearea_a value'},
label: { label: 'label value'}
};
const expected = {
housenumber: '[object Object]',
street: '[object Object]',
postalcode: '[object Object]',
postalcode_gid: '[object Object]',
match_type: '[object Object]',
accuracy: '[object Object]',
country: '[object Object]',
country_gid: '[object Object]',
country_a: '[object Object]',
dependency: '[object Object]',
dependency_gid: '[object Object]',
dependency_a: '[object Object]',
macroregion: '[object Object]',
macroregion_gid: '[object Object]',
macroregion_a: '[object Object]',
region: '[object Object]',
region_gid: '[object Object]',
region_a: '[object Object]',
macrocounty: '[object Object]',
macrocounty_gid: '[object Object]',
macrocounty_a: '[object Object]',
county: '[object Object]',
county_gid: '[object Object]',
county_a: '[object Object]',
localadmin: '[object Object]',
localadmin_gid: '[object Object]',
localadmin_a: '[object Object]',
locality: '[object Object]',
locality_gid: '[object Object]',
locality_a: '[object Object]',
borough: '[object Object]',
borough_gid: '[object Object]',
borough_a: '[object Object]',
neighbourhood: '[object Object]',
neighbourhood_gid: '[object Object]',
continent: '[object Object]',
continent_gid: '[object Object]',
continent_a: '[object Object]',
ocean: '[object Object]',
ocean_gid: '[object Object]',
ocean_a: '[object Object]',
marinearea: '[object Object]',
marinearea_gid: '[object Object]',
marinearea_a: '[object Object]',
label: '[object Object]'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('\'default\'-type properties should be copied without type conversion and overwrite old values', t => {
[ 'this is a string', 17.3, { a: 1 }, [1, 2, 3] ].forEach(value => {
const source = {
confidence: value,
distance: value,
bounding_box: value
};
const expected = {
confidence: value,
distance: value,
bounding_box: value
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('\'default\'-type properties that are numbers should be output as numbers', t => {
[ 17, 17.3 ].forEach(value => {
const source = {
confidence: value,
distance: value,
bounding_box: value
};
const expected = {
confidence: value,
distance: value,
bounding_box: value
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('\'empty\' values for default-type properties should not be output', t => {
[ undefined, null, true, {}, [] ].forEach(value => {
const source = {
confidence: value,
distance: value,
bounding_box: value
};
const expected = {};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('array-type properties should not be output when empty', t => {
const source = {
category: []
};
const expected = {};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('array-type properties with array values should be output as arrays', t => {
const source = {
category: [ 1, 2 ]
};
const expected = {
category: [ 1, 2 ]
};
const clean = {
categories: true
};
const actual = geojsonify(clean, source);
t.deepEqual(actual, expected);
t.end();
});
test('category property should be output when params contains \'category\' property', t => {
[ {a: 1}, 'this is a string'].forEach(value => {
const source = {
category: value
};
const expected = {
category: [ value ]
};
const clean = {
categories: true
};
const actual = geojsonify(clean, source);
t.deepEqual(actual, expected);
});
t.end();
});
test('category property should not be output when params does not contain \'category\' property', t => {
const source = {
category: [ 1, 2 ]
};
const expected = {
};
const clean = {};
const actual = geojsonify(clean, source);
t.deepEqual(actual, expected);
t.end();
});
test('category property should not be output when params is not an object', t => {
const source = {
category: [ 1, 2 ]
};
const expected = {
};
const clean = 'this is not an object';
const actual = geojsonify(clean, source);
t.deepEqual(actual, expected);
t.end();
});
};
module.exports.tests.empire_specific = (test, common) => {
test('empire* properties should not be output when country_gid property is available', t => {
const source = {
country_gid: 'country_gid value',
empire: 'empire value',
empire_gid: 'empire_gid value',
empire_a: 'empire_a value',
label: 'label value'
};
const expected = {
country_gid: 'country_gid value',
label: 'label value'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
test('empire* properties should be output when country_gid property is not available', t => {
const source = {
empire: 'empire value',
empire_gid: 'empire_gid value',
empire_a: 'empire_a value',
label: 'label value'
};
const expected = {
empire: 'empire value',
empire_gid: 'empire_gid value',
empire_a: 'empire_a value',
label: 'label value'
};
const actual = geojsonify({}, source);
t.deepEqual(actual, expected);
t.end();
});
};
module.exports.all = (tape, common) => {
function test(name, testFunction) {
return tape(`geojsonify: ${name}`, testFunction);
}
for( var testCase in module.exports.tests ){
module.exports.tests[testCase](test, common);
}
};

5
test/unit/helper/type_mapping.js

@ -12,9 +12,10 @@ module.exports.tests.interfaces = function(test, common) {
test('alias layer mapping', function(t) {
t.deepEquals(type_mapping.layer_mapping.coarse,
[ 'continent', 'country', 'dependency', 'macroregion',
[ 'continent', 'empire', 'country', 'dependency', 'macroregion',
'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood',
'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode' ]);
'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode',
'continent', 'ocean', 'marinearea']);
t.end();
});

32
test/unit/middleware/confidenceScore.js

@ -169,6 +169,38 @@ module.exports.tests.confidenceScore = function(test, common) {
t.false(res.data[0].hasOwnProperty('confidence'), 'score was not set');
t.end();
});
test('missing parent object should not throw an exception', function(t) {
var req = {
clean: {
text: '123 Main St, City, NM',
parsed_text: {
number: 123,
street: 'Main St',
state: 'NM'
}
}
};
var res = {
data: [{
_score: 10,
found: true,
value: 1,
center_point: { lat: 100.1, lon: -50.5 },
name: { default: 'test name1' },
}],
meta: {
scores: [10],
query_type: 'original'
}
};
t.doesNotThrow(() => {
confidenceScore(req, res, () => {});
});
t.equal(res.data[0].confidence, 0.28, 'score was set');
t.end();
});
};
module.exports.all = function (tape, common) {

298
test/unit/middleware/interpolate.js

@ -38,129 +38,6 @@ module.exports.tests.early_exit_conditions = (test, common) => {
};
module.exports.tests.error_conditions = (test, common) => {
test('service error string should log and not modify any results', t => {
t.plan(2);
const service = (req, res, callback) => {
callback('this is an error', {
properties: {
number: 17,
source: 'OSM',
source_id: 'openstreetmap source id',
lat: 12.121212,
lon: 21.212121
}
});
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../middleware/interpolate', {
'pelias-logger': logger
})(service, () => true);
const req = { a: 1 };
const res = {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {},
// bounding_box should be removed
bounding_box: {}
}
]
};
controller(req, res, () => {
t.ok(logger.isErrorMessage('[middleware:interpolation] this is an error'));
t.deepEquals(res, {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {},
// bounding_box should be removed
bounding_box: {}
}
]
}, 'res should not have been modified');
});
});
test('service error object should log message and not modify any results', t => {
t.plan(2);
const service = (req, res, callback) => {
callback({ message: 'this is an error' }, {
properties: {
number: 17,
source: 'OSM',
source_id: 'openstreetmap source id',
lat: 12.121212,
lon: 21.212121
}
});
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../middleware/interpolate', {
'pelias-logger': logger
})(service, () => true);
const req = { a: 1 };
const res = {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {},
// bounding_box should be removed
bounding_box: {}
}
]
};
controller(req, res, () => {
t.ok(logger.isErrorMessage('[middleware:interpolation] this is an error'));
t.deepEquals(res, {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {},
// bounding_box should be removed
bounding_box: {}
}
]
}, 'res should not have been modified');
});
});
};
module.exports.tests.success_conditions = (test, common) => {
test('undefined res should not cause errors', t => {
const service = (req, res, callback) => {
@ -182,6 +59,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, undefined, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=0/));
t.end();
});
@ -210,6 +88,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=0/));
t.deepEquals(res, {});
@ -219,7 +98,7 @@ module.exports.tests.success_conditions = (test, common) => {
});
test('interpolated results should be mapped in', t => {
test('only \'street\' layer results should attempt interpolation', t => {
const service = (req, res, callback) => {
if (res.id === 1) {
callback(null, {
@ -294,6 +173,7 @@ module.exports.tests.success_conditions = (test, common) => {
bounding_box: {}
},
{
// this is not a street result and should not attempt interpolation
id: 2,
layer: 'not street',
name: {
@ -323,6 +203,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/), 'timing should be info-logged');
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=3/), 'street count should be info-logged');
// test debug messages very vaguely to avoid brittle tests
t.ok(logger.isDebugMessage(/^\[interpolation\] \[hit\] this is req.clean.parsed_text \{.+?\}$/),
@ -398,6 +279,172 @@ module.exports.tests.success_conditions = (test, common) => {
});
test('service call returning error should not map in interpolated results for non-errors', t => {
const service = (req, res, callback) => {
if (res.id === 1) {
callback(null, {
properties: {
number: 17,
source: 'Source Abbr 1',
source_id: 'source 1 source id',
lat: 12.121212,
lon: 21.212121
}
});
} else if (res.id === 2) {
callback('id 2 produced an error string', {});
} else if (res.id === 3) {
callback({ message: 'id 3 produced an error object' }, {});
} else if (res.id === 4) {
callback(null, {
properties: {
number: 18,
source: 'Source Abbr 4',
source_id: 'source 4 source id',
lat: 13.131313,
lon: 31.313131
}
});
} else {
console.error(res.id);
t.fail(`unexpected id ${res.id}`);
}
};
const logger = require('pelias-mock-logger')();
const controller = proxyquire('../../../middleware/interpolate', {
'pelias-logger': logger,
'../helper/type_mapping': {
source_mapping: {
'source abbr 1': ['full source name 1'],
'source abbr 4': ['full source name 4']
}
}
})(service, () => true);
const req = {
clean: {
parsed_text: 'this is req.clean.parsed_text'
}
};
const res = {
data: [
{
id: 1,
layer: 'street',
name: {
default: 'street name 1'
},
address_parts: {}
},
{
id: 2,
layer: 'street',
name: {
default: 'street name 2'
},
address_parts: {}
},
{
id: 3,
layer: 'street',
name: {
default: 'street name 3'
},
address_parts: {}
},
{
id: 4,
layer: 'street',
name: {
default: 'street name 4'
},
address_parts: {}
}
]
};
controller(req, res, () => {
t.deepEquals(logger.getErrorMessages(), [
'[middleware:interpolation] id 2 produced an error string',
'[middleware:interpolation] id 3 produced an error object'
]);
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/), 'timing should be info-logged');
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=4/), 'street count should be info-logged');
// test debug messages very vaguely to avoid brittle tests
t.ok(logger.isDebugMessage(/^\[interpolation\] \[hit\] this is req.clean.parsed_text \{.+?\}$/),
'hits should be debug-logged');
t.deepEquals(res, {
data: [
{
id: 1,
layer: 'address',
match_type: 'interpolated',
name: {
default: '17 street name 1'
},
source: 'full source name 1',
source_id: 'source 1 source id',
address_parts: {
number: 17
},
center_point: {
lat: 12.121212,
lon: 21.212121
}
},
{
id: 4,
layer: 'address',
match_type: 'interpolated',
name: {
default: '18 street name 4'
},
source: 'full source name 4',
source_id: 'source 4 source id',
address_parts: {
number: 18
},
center_point: {
lat: 13.131313,
lon: 31.313131
}
},
{
id: 2,
layer: 'street',
name: {
default: 'street name 2'
},
address_parts: {}
},
{
id: 3,
layer: 'street',
name: {
default: 'street name 3'
},
address_parts: {}
}
]
}, 'hits should be mapped in and res.data sorted with addresses first and non-addresses last');
t.end();
});
});
test('interpolation result without source_id should remove all together', t => {
const service = (req, res, callback) => {
if (res.id === 1) {
@ -448,6 +495,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=1/), 'street count should be info-logged');
t.deepEquals(res, {
data: [
@ -536,6 +584,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=2/), 'street count should be info-logged');
// test debug messages very vaguely to avoid brittle tests
t.ok(logger.isDebugMessage('[interpolation] [miss] this is req.clean.parsed_text'));
@ -636,6 +685,7 @@ module.exports.tests.success_conditions = (test, common) => {
controller(req, res, () => {
t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages');
t.ok(logger.isInfoMessage(/\[interpolation\] \[took\] \d+ ms/));
t.ok(logger.isInfoMessage(/\[interpolation\] \[street_results\] count=2/), 'street count should be info-logged');
// test debug messages very vaguely to avoid brittle tests
t.ok(logger.isDebugMessage('[interpolation] [miss] this is req.clean.parsed_text'));

7
test/unit/query/MockQuery.js

@ -3,6 +3,7 @@
module.exports = class MockQuery {
constructor() {
this._score_functions = [];
this._sort_functions = [];
this._filter_functions = [];
}
@ -10,6 +11,7 @@ module.exports = class MockQuery {
return {
vs: vs,
score_functions: this._score_functions,
sort_functions: this._sort_functions,
filter_functions: this._filter_functions
};
}
@ -19,6 +21,11 @@ module.exports = class MockQuery {
return this;
}
sort(view) {
this._sort_functions.push(view);
return this;
}
filter(view) {
this._filter_functions.push(view);
return this;

573
test/unit/query/reverse.js

@ -1,190 +1,503 @@
var generate = require('../../../query/reverse');
const generate = require('../../../query/reverse');
const _ = require('lodash');
const proxyquire = require('proxyquire').noCallThru();
const MockQuery = require('./MockQuery');
const all_layers = require('../../../helper/type_mapping').layers;
// helper for canned views
const views = {
boundary_country: 'boundary_country view',
boundary_circle: 'boundary_circle view',
sources: 'sources view',
layers: 'layers view',
categories: 'categories view',
sort_distance: 'sort_distance view'
};
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) {
module.exports.tests.interface = (test, common) => {
test('valid interface', t => {
t.equal(typeof generate, 'function', 'valid function');
t.end();
});
};
module.exports.tests.query = function(test, common) {
test('valid query', function(t) {
var query = generate({
'point.lat': 29.49136,
'point.lon': -82.50622,
'boundary.circle.lat': 29.49136,
'boundary.circle.lon': -82.50622,
'boundary.circle.radius': 3
});
module.exports.tests.query = (test, common) => {
test('base no frills', t => {
const clean = {};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {
default_parameter_1: 'first default parameter',
default_parameter_2: 'second default parameter'
}
})(clean);
t.equals(query.type, 'reverse', 'query type set');
t.deepEquals(query.body.vs.var('default_parameter_1').toString(), 'first default parameter');
t.deepEquals(query.body.vs.var('default_parameter_2').toString(), 'second default parameter');
t.notOk(query.body.vs.isset('size'));
t.notOk(query.body.vs.isset('sources'));
t.notOk(query.body.vs.isset('layers'));
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.notOk(query.body.vs.isset('boundary:country'));
t.notOk(query.body.vs.isset('input:categories'));
t.deepEquals(query.body.score_functions, [
'boundary_country view'
]);
t.deepEquals(query.body.filter_functions, [
'boundary_circle view',
'sources view',
'layers view',
'categories view'
]);
t.deepEquals(query.body.sort_functions, [
'sort_distance view'
]);
t.end();
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/reverse_standard');
});
t.deepEqual(compiled.type, 'reverse', 'query type set');
t.deepEqual(compiled.body, expected, 'reverse_standard');
test('clean.querySize should set size parameter', t => {
const clean = {
querySize: 17
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('size').toString(), 17);
t.end();
});
test('valid query - null island', function(t) {
var query = generate({
'point.lat': 0,
'point.lon': 0,
'boundary.circle.lat': 0,
'boundary.circle.lon': 0,
'boundary.circle.radius': 3
});
};
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/reverse_null_island');
module.exports.tests.sources = (test, common) => {
test('non-array clean.sources should not set sources in vs', t => {
const clean = {
sources: 'this is not an array'
};
t.deepEqual(compiled.type, 'reverse', 'query type set');
t.deepEqual(compiled.body, expected, 'reverse_null_island');
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('sources'));
t.end();
});
test('valid query with radius', function(t) {
var query = generate({
'point.lat': 29.49136,
'point.lon': -82.50622,
'boundary.circle.lat': 29.49136,
'boundary.circle.lon': -82.50622,
'boundary.circle.radius': 123
});
test('empty array clean.sources should not set sources in vs', t => {
const clean = {
sources: []
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('sources'));
t.end();
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = '123km';
});
test('non-empty array clean.sources should set sources in vs', t => {
const clean = {
sources: ['source 1', 'source 2']
};
t.deepEqual(compiled.type, 'reverse', 'query type set');
t.deepEqual(compiled.body.query.bool.filter[0].geo_distance.distance, expected, 'distance set to boundary circle radius');
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('sources').toString(), ['source 1', 'source 2']);
t.end();
});
// for coarse reverse cases where boundary circle radius isn't used
test('undefined radius set to default radius', function(t) {
var query = generate({
'point.lat': 12.12121,
'point.lon': 21.21212,
'boundary.circle.lat': 12.12121,
'boundary.circle.lon': 21.21212
});
};
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = '1km';
module.exports.tests.layers = (test, common) => {
test('non-array clean.layers should not set sources in vs', t => {
const clean = {
layers: 'this is not an array'
};
t.deepEqual(compiled.type, 'reverse', 'query type set');
t.deepEqual(compiled.body.query.bool.filter[0].geo_distance.distance, expected, 'distance set to default boundary circle radius');
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('layers'));
t.end();
});
test('boundary.circle lat/lon/radius - overrides point.lat/lon when set', function(t) {
var clean = {
'point.lat': 29.49136,
'point.lon': -82.50622,
'boundary.circle.lat': 111,
'boundary.circle.lon': 333,
'boundary.circle.radius': 3
test('empty array clean.layers should not set sources in vs', t => {
const clean = {
layers: []
};
var query = generate(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
// this should not equal `point.lat` and `point.lon` as it was explitely specified
var expected = { lat: clean['boundary.circle.lat'], lon: clean['boundary.circle.lon'] };
var centroid = compiled.body.query.bool.filter[0].geo_distance.center_point;
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('layers'));
t.end();
});
test('non-empty array clean.layers should only set non-coarse layers in vs', t => {
const clean = {
layers: all_layers
};
t.deepEqual(compiled.type, 'reverse', 'query type set');
t.deepEqual(centroid, expected, 'reverse: boundary.circle/lon overrides point.lat/lon');
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('layers').toString(), ['address', 'venue', 'street']);
t.end();
});
test('size fuzz test', function(t) {
// test different sizes
var sizes = [1,4,20,undefined,null];
var expected = [1,4,20,1,1];
sizes.forEach( function( size, index ){
var query = generate({
'point.lat': 29.49136, 'point.lon': -82.50622, querySize: size
});
};
module.exports.tests.focus_point = (test, common) => {
test('numeric point.lat and non-numeric point.lon should not add focus:point:* fields', t => {
const clean = {
'point.lat': 12.121212,
'point.lon': 'this is non-numeric'
};
var compiled = JSON.parse( JSON.stringify( query ) );
t.equal( compiled.body.size, expected[index], 'valid reverse query for size: '+ size);
});
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.end();
});
test('valid boundary.country reverse search', function(t) {
var query = generate({
'point.lat': 29.49136,
'point.lon': -82.50622,
'boundary.circle.lat': 29.49136,
'boundary.circle.lon': -82.50622,
'boundary.circle.radius': 3,
'boundary.country': 'ABC'
});
test('non-numeric point.lat and numeric point.lon should not add focus:point:* fields', t => {
const clean = {
'point.lat': 'this is non-numeric',
'point.lon': 21.212121
};
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/reverse_with_boundary_country');
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.end();
t.deepEqual(compiled.type, 'reverse', 'query type set');
t.deepEqual(compiled.body, expected, 'valid reverse query with boundary.country');
});
test('numeric point.lat and point.lon should add focus:point:* fields', t => {
const clean = {
'point.lat': 12.121212,
'point.lon': 21.212121
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('focus:point:lat').toString(), 12.121212);
t.deepEquals(query.body.vs.var('focus:point:lon').toString(), 21.212121);
t.end();
});
test('valid sources filter', function(t) {
var query = generate({
'point.lat': 29.49136,
'point.lon': -82.50622,
'boundary.circle.lat': 29.49136,
'boundary.circle.lon': -82.50622,
'boundary.circle.radius': 3,
'sources': ['test']
});
};
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/reverse_with_source_filtering');
module.exports.tests.boundary_circle = (test, common) => {
test('numeric lat and non-numeric lon should not add boundary:circle:* fields', t => {
const clean = {
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 'this is non-numeric'
};
t.deepEqual(compiled.type, 'reverse', 'query type set');
t.deepEqual(compiled.body, expected, 'valid reverse query with source filtering');
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end();
});
test('valid layers filter', (t) => {
const query = generate({
'point.lat': 29.49136,
'point.lon': -82.50622,
'boundary.circle.lat': 29.49136,
'boundary.circle.lon': -82.50622,
'boundary.circle.radius': 3,
// only venue, address, and street layers should be retained
'layers': ['neighbourhood', 'venue', 'locality', 'address', 'region', 'street', 'country']
});
test('non-numeric lat and numeric lon should not add boundary:circle:* fields', t => {
const clean = {
'boundary.circle.lat': 'this is non-numeric',
'boundary.circle.lon': 21.212121
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end();
});
test('radius not supplied should default to value from reverse_defaults', t => {
const clean = {
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {
'boundary:circle:radius': 17
}
})(clean);
t.deepEquals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
t.deepEquals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.deepEquals(query.body.vs.var('boundary:circle:radius').toString(), 17);
t.end();
});
test('numeric radius supplied should be used instead of value from reverse_defaults', t => {
const clean = {
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121,
'boundary.circle.radius': 17
};
const compiled = JSON.parse( JSON.stringify( query ) );
const expected = require('../fixture/reverse_with_layer_filtering');
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {
'boundary:circle:radius': 18
}
})(clean);
t.deepEquals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
t.deepEquals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.deepEquals(query.body.vs.var('boundary:circle:radius').toString(), '17km');
t.end();
});
test('non-numeric radius supplied should not set any boundary:circle:radius', t => {
const clean = {
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121,
'boundary.circle.radius': 'this is non-numeric'
};
t.deepEqual(compiled.type, 'reverse', 'query type set');
t.deepEqual(compiled.body, expected, 'valid reverse query with source filtering');
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {
'boundary:circle:radius': 18
}
})(clean);
t.deepEquals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
t.deepEquals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.deepEquals(query.body.vs.var('boundary:circle:radius').toString(), 18);
t.end();
});
test('valid layers filter - subset of non-coarse layers', (t) => {
const query = generate({
'point.lat': 29.49136,
'point.lon': -82.50622,
'boundary.circle.lat': 29.49136,
'boundary.circle.lon': -82.50622,
'boundary.circle.radius': 3,
// only venue, address, and street layers should be retained
'layers': ['neighbourhood', 'venue', 'street', 'locality']
};
module.exports.tests.boundary_country = (test, common) => {
test('non-string boundary.country should not set boundary:country', t => {
[17, undefined, {}, [], true, null].forEach(value => {
const clean = {
'boundary.country': value
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.notOk(query.body.vs.isset('boundary:country'));
});
const compiled = JSON.parse( JSON.stringify( query ) );
const expected = require('../fixture/reverse_with_layer_filtering_non_coarse_subset');
t.end();
});
test('string boundary.country should set boundary:country', t => {
const clean = {
'boundary.country': 'boundary country value'
};
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('boundary:country').toString(), 'boundary country value');
t.end();
});
};
module.exports.tests.categories = (test, common) => {
test('categories supplied should set input:categories', t => {
const clean = {
categories: 'categories value'
};
t.deepEqual(compiled.type, 'reverse', 'query type set');
t.deepEqual(compiled.body, expected, 'valid reverse query with source filtering');
const query = proxyquire('../../../query/reverse', {
'pelias-query': {
layout: {
FilteredBooleanQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./reverse_defaults': {}
})(clean);
t.deepEquals(query.body.vs.var('input:categories').toString(), 'categories value');
t.end();
});

48
test/unit/query/search.js

@ -99,54 +99,6 @@ module.exports.tests.query = function(test, common) {
t.end();
});
test('search search + viewport', function(t) {
var clean = {
parsed_text: {
street: 'street value'
},
text: 'test', querySize: 10,
'focus.viewport.min_lat': 28.49136,
'focus.viewport.max_lat': 30.49136,
'focus.viewport.min_lon': -87.50622,
'focus.viewport.max_lon': -77.50622,
layers: ['test']
};
var query = generate(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/search_linguistic_viewport');
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_viewport');
t.end();
});
// viewport scale sizing currently disabled.
// ref: https://github.com/pelias/api/pull/388
test('search with viewport diagonal < 1km should set scale to 1km', function(t) {
var clean = {
parsed_text: {
street: 'street value'
},
text: 'test', querySize: 10,
'focus.viewport.min_lat': 28.49135,
'focus.viewport.max_lat': 28.49137,
'focus.viewport.min_lon': -87.50622,
'focus.viewport.max_lon': -87.50624,
layers: ['test']
};
var query = generate(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/search_linguistic_viewport_min_diagonal');
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'valid search query');
t.end();
});
test('search search + focus on null island', function(t) {
var clean = {
parsed_text: {

663
test/unit/query/structured_geocoding.js

@ -1,253 +1,606 @@
var generate = require('../../../query/structured_geocoding');
var fs = require('fs');
const generate = require('../../../query/structured_geocoding');
const _ = require('lodash');
const proxyquire = require('proxyquire').noCallThru();
const MockQuery = require('./MockQuery');
// helper for canned views
const views = {
focus_only_function: () => 'focus_only_function view',
popularity_only_function: 'popularity_only_function view',
population_only_function: 'population_only_function view',
boundary_country: 'boundary_country view',
boundary_circle: 'boundary_circle view',
boundary_rect: 'boundary_rect view',
sources: 'sources view',
layers: 'layers view',
categories: 'categories view'
};
module.exports.tests = {};
module.exports.tests.interface = function(test, common) {
test('valid interface', function(t) {
module.exports.tests.interface = (test, common) => {
test('valid interface', t => {
t.equal(typeof generate, 'function', 'valid function');
t.end();
});
};
module.exports.tests.query = function(test, common) {
test('valid search + focus + bbox', function(t) {
var clean = {
parsed_text: {
},
text: 'test',
querySize: 10,
'focus.point.lat': 29.49136, 'focus.point.lon': -82.50622,
'boundary.rect.min_lat': 47.47,
'boundary.rect.max_lon': -61.84,
'boundary.rect.max_lat': 11.51,
'boundary.rect.min_lon': -103.16,
layers: ['test']
module.exports.tests.query = (test, common) => {
test('base no frills', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value'
};
var query = generate(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/structured_geocoding/linguistic_focus_bbox');
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': {
default_parameter_1: 'first default parameter',
default_parameter_2: 'second default parameter'
},
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
t.equals(query.type, 'fallback', 'query type set');
t.equals(query.body.vs.var('input:name').toString(), 'text value');
t.equals(query.body.vs.var('sources').toString(), 'sources value');
t.equals(query.body.vs.var('layers').toString(), 'layers value');
t.deepEquals(query.body.vs.var('default_parameter_1').toString(), 'first default parameter');
t.deepEquals(query.body.vs.var('default_parameter_2').toString(), 'second default parameter');
t.notOk(query.body.vs.isset('size'));
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.notOk(query.body.vs.isset('boundary:rect:top'));
t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.notOk(query.body.vs.isset('boundary:country'));
t.deepEquals(query.body.score_functions, [
'focus_only_function view',
'popularity_only_function view',
'population_only_function view'
]);
t.deepEquals(query.body.filter_functions, [
'boundary_country view',
'boundary_circle view',
'boundary_rect view',
'sources view',
'layers view',
'categories view'
]);
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_focus_bbox');
t.end();
});
test('valid search + bbox', function(t) {
var clean = {
parsed_text: {
test('clean.parsed_text and vs should be passed to textParser', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
parsed_text: 'parsed_text value'
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': {
default_parameter_1: 'first default parameter',
default_parameter_2: 'second default parameter'
},
text: 'test',
querySize: 10,
'boundary.rect.min_lat': 47.47,
'boundary.rect.max_lon': -61.84,
'boundary.rect.max_lat': 11.51,
'boundary.rect.min_lon': -103.16,
layers: ['test']
'./text_parser': (parsed_text, vs) => {
vs.var('text_parser:value', 'this value populated by text_parser');
}
})(clean);
t.deepEquals(query.body.vs.var('text_parser:value').toString(), 'this value populated by text_parser');
t.end();
});
};
module.exports.tests.query_size = (test, common) => {
test('size should be set when querySize is available', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
querySize: 17
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/structured_geocoding/linguistic_bbox');
t.equals(query.body.vs.var('size').toString(), 17);
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_bbox');
t.end();
});
test('valid lingustic-only search', function(t) {
var clean = {
parsed_text: {
},
text: 'test', querySize: 10,
layers: ['test']
};
module.exports.tests.focus_point_lat_lon = (test, common) => {
test('missing focus.point.lat shouldn\'t set focus:point:lat/lon', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'focus.point.lon': 21.212121
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/structured_geocoding/linguistic_only');
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_only');
t.end();
});
test('search search + focus', function(t) {
var clean = {
parsed_text: {
},
text: 'test', querySize: 10,
'focus.point.lat': 29.49136, 'focus.point.lon': -82.50622,
layers: ['test']
test('missing focus.point.lon shouldn\'t set focus:point:lat/lon', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'focus.point.lat': 12.121212
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/structured_geocoding/linguistic_focus');
t.notOk(query.body.vs.isset('focus:point:lat'));
t.notOk(query.body.vs.isset('focus:point:lon'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_focus');
t.end();
});
test('search search + viewport', function(t) {
var clean = {
parsed_text: {
},
text: 'test', querySize: 10,
'focus.viewport.min_lat': 28.49136,
'focus.viewport.max_lat': 30.49136,
'focus.viewport.min_lon': -87.50622,
'focus.viewport.max_lon': -77.50622,
layers: ['test']
test('focus.point.lat/lon should set focus:point:lat/lon', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'focus.point.lat': 12.121212,
'focus.point.lon': 21.212121
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/structured_geocoding/linguistic_viewport');
t.equals(query.body.vs.var('focus:point:lat').toString(), 12.121212);
t.equals(query.body.vs.var('focus:point:lon').toString(), 21.212121);
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_viewport');
t.end();
});
// viewport scale sizing currently disabled.
// ref: https://github.com/pelias/api/pull/388
test('search with viewport diagonal < 1km should set scale to 1km', function(t) {
var clean = {
parsed_text: {
},
text: 'test', querySize: 10,
'focus.viewport.min_lat': 28.49135,
'focus.viewport.max_lat': 28.49137,
'focus.viewport.min_lon': -87.50622,
'focus.viewport.max_lon': -87.50624,
layers: ['test']
};
module.exports.tests.boundary_rect = (test, common) => {
test('missing boundary.rect.min_lat shouldn\'t set boundary:rect fields', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.rect.max_lat': 13.131313,
'boundary.rect.min_lon': 21.212121,
'boundary.rect.max_lon': 31.313131
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/structured_geocoding/linguistic_viewport_min_diagonal');
t.notOk(query.body.vs.isset('boundary:rect:top'));
t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'valid search query');
t.end();
});
test('search search + focus on null island', function(t) {
var clean = {
parsed_text: {
test('missing boundary.rect.max_lat shouldn\'t set boundary:rect fields', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.rect.min_lat': 12.121212,
'boundary.rect.min_lon': 21.212121,
'boundary.rect.max_lon': 31.313131
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
text: 'test', querySize: 10,
'focus.point.lat': 0, 'focus.point.lon': 0,
layers: ['test']
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
t.notOk(query.body.vs.isset('boundary:rect:top'));
t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.end();
});
test('missing boundary.rect.min_lon shouldn\'t set boundary:rect fields', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.rect.min_lat': 12.121212,
'boundary.rect.max_lat': 13.131313,
'boundary.rect.max_lon': 31.313131
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/structured_geocoding/linguistic_focus_null_island');
t.notOk(query.body.vs.isset('boundary:rect:top'));
t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search_linguistic_focus_null_island');
t.end();
});
test('parsed_text with all fields should use FallbackQuery', function(t) {
var clean = {
parsed_text: {
query: 'query value',
category: 'category value',
number: 'number value',
street: 'street value',
neighbourhood: 'neighbourhood value',
borough: 'borough value',
postalcode: 'postalcode value',
city: 'city value',
county: 'county value',
state: 'state value',
country: 'country value'
test('missing boundary.rect.max_lon shouldn\'t set boundary:rect fields', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.rect.min_lat': 12.121212,
'boundary.rect.max_lat': 13.131313,
'boundary.rect.min_lon': 21.212121
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
t.notOk(query.body.vs.isset('boundary:rect:top'));
t.notOk(query.body.vs.isset('boundary:rect:right'));
t.notOk(query.body.vs.isset('boundary:rect:bottom'));
t.notOk(query.body.vs.isset('boundary:rect:left'));
t.end();
});
test('focus.point.lat/lon should set focus:point:lat/lon', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.rect.min_lat': 12.121212,
'boundary.rect.max_lat': 13.131313,
'boundary.rect.min_lon': 21.212121,
'boundary.rect.max_lon': 31.313131
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse(JSON.stringify(query));
var expected = require('../fixture/structured_geocoding/fallback');
t.equals(query.body.vs.var('boundary:rect:top').toString(), 13.131313);
t.equals(query.body.vs.var('boundary:rect:right').toString(), 31.313131);
t.equals(query.body.vs.var('boundary:rect:bottom').toString(), 12.121212);
t.equals(query.body.vs.var('boundary:rect:left').toString(), 21.212121);
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'fallbackQuery');
t.end();
});
test('parsed_text with all fields should use FallbackQuery', function(t) {
var clean = {
parsed_text: {
postalcode: 'postalcode value'
};
module.exports.tests.boundary_circle = (test, common) => {
test('missing boundary.circle.lat shouldn\'t set boundary:circle fields', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.lon': 21.212121,
'boundary.circle.radius': 17
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end();
});
test('missing boundary.circle.lon shouldn\'t set boundary:circle fields', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.lat': 12.121212,
'boundary.circle.radius': 17
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse(JSON.stringify(query));
var expected = require('../fixture/structured_geocoding/postalcode_only');
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'structured postalcode only');
t.end();
});
test('valid boundary.country search', function(t) {
var clean = {
parsed_text: {
test('missing boundary.circle.radius should set lat/lon but not radius', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
text: 'test', querySize: 10,
layers: ['test'],
'boundary.country': 'ABC'
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
t.equals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
t.equals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end();
});
test('boundary.circle.* should set lat/lon/radius with last rounded and in kilometers', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.lat': 12.121212,
'boundary.circle.lon': 21.212121,
'boundary.circle.radius': 17.5
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/structured_geocoding/boundary_country');
t.equals(query.body.vs.var('boundary:circle:lat').toString(), 12.121212);
t.equals(query.body.vs.var('boundary:circle:lon').toString(), 21.212121);
t.equals(query.body.vs.var('boundary:circle:radius').toString(), '18km');
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search: valid boundary.country query');
t.end();
});
test('valid sources filter', function(t) {
var clean = {
parsed_text: {
test('missing boundary.circle.lat/lon but existing boundary.circle.radius should not set any', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.circle.radius': 17
};
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'text': 'test',
'sources': ['test_source']
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
t.notOk(query.body.vs.isset('boundary:circle:lat'));
t.notOk(query.body.vs.isset('boundary:circle:lon'));
t.notOk(query.body.vs.isset('boundary:circle:radius'));
t.end();
});
};
module.exports.tests.boundary_country = (test, common) => {
test('boundary.country available shoul set boundary:country', t => {
const clean = {
text: 'text value',
sources: 'sources value',
layers: 'layers value',
'boundary.country': 'boundary country value'
};
var query = generate(clean);
const query = proxyquire('../../../query/structured_geocoding', {
'pelias-query': {
layout: {
StructuredFallbackQuery: MockQuery
},
view: views,
Vars: require('pelias-query').Vars
},
'./search_defaults': { },
'./text_parser': () => {
t.fail('text_parser should not have been called');
}
})(clean);
var compiled = JSON.parse( JSON.stringify( query ) );
var expected = require('../fixture/structured_geocoding/with_source_filtering');
t.equals(query.body.vs.var('boundary:country').toString(), 'boundary country value');
t.deepEqual(compiled.type, 'fallback', 'query type set');
t.deepEqual(compiled.body, expected, 'search: valid search query with source filtering');
t.end();
});
};
module.exports.all = function (tape, common) {
module.exports.all = (tape, common) => {
function test(name, testFunction) {
return tape('structured_geocoding query ' + name, testFunction);
return tape(`structured_geocoding query ${name}`, testFunction);
}
for( var testCase in module.exports.tests ){

1
test/unit/run.js

@ -31,6 +31,7 @@ var tests = [
require('./controller/predicates/is_request_sources_only_whosonfirst'),
require('./helper/debug'),
require('./helper/diffPlaces'),
require('./helper/geojsonify_place_details'),
require('./helper/geojsonify'),
require('./helper/logging'),
require('./helper/type_mapping'),

19
test/unit/sanitizer/_geo_common.js

@ -216,8 +216,8 @@ module.exports.tests.rect = function(test, common) {
test('valid rect quad', function (t) {
var clean = {};
var params = {
'boundary.rect.min_lat': -40.659,
'boundary.rect.max_lat': -41.614,
'boundary.rect.min_lat': -41.614,
'boundary.rect.max_lat': -40.659,
'boundary.rect.min_lon': 174.612,
'boundary.rect.max_lon': 176.333
};
@ -283,6 +283,21 @@ module.exports.tests.rect = function(test, common) {
t.end();
});
test('invalid rect - max/min switched', function (t) {
var clean = {};
var params = {
'boundary.rect.max_lat': 52.2387,
'boundary.rect.max_lon': 14.1367,
'boundary.rect.min_lat': 52.7945,
'boundary.rect.min_lon': 12.6398
};
var mandatory = false;
t.throws( function() {
sanitize.sanitize_rect( 'boundary.rect', clean, params, mandatory );
});
t.end();
});
};
module.exports.tests.circle = function(test, common) {

46
test/unit/sanitizer/_geonames_deprecation.js

@ -2,6 +2,8 @@ const sanitizer = require('../../../sanitizer/_geonames_deprecation')();
module.exports.tests = {};
const coarse_reverse_message ='coarse /reverse does not support geonames. See https://github.com/pelias/pelias/issues/675 for more info';
module.exports.tests.no_warnings_or_errors_conditions = (test, common) => {
test('undefined sources should add neither warnings nor errors', (t) => {
const clean = {};
@ -27,41 +29,71 @@ module.exports.tests.no_warnings_or_errors_conditions = (test, common) => {
});
test('geonames/gn in sources with layers=venue should add neither warnings nor errors', (t) => {
const clean = {
sources: ['geonames'],
layers: ['venue']
};
const messages = sanitizer.sanitize(undefined, clean);
t.deepEquals(clean.sources, ['geonames']);
t.deepEquals(messages, { errors: [], warnings: [] });
t.end();
});
};
module.exports.tests.error_conditions = (test, common) => {
test('only geonames in sources should not modify clean.sources and add error message', (t) => {
test('only geonames in sources with layers=coarse should not modify clean.sources and add error message', (t) => {
['gn', 'geonames'].forEach((sources) => {
const clean = {
sources: [sources]
sources: [sources],
layers: ['coarse']
};
const messages = sanitizer.sanitize(undefined, clean);
t.deepEquals(clean.sources, [sources]);
t.deepEquals(messages.errors, ['/reverse does not support geonames']);
t.deepEquals(messages.errors, [ coarse_reverse_message ]);
t.deepEquals(messages.warnings, []);
});
t.end();
});
test('only geonames in sources with layers=locality should not modify clean.sources and add error message', (t) => {
['gn', 'geonames'].forEach((sources) => {
const clean = {
sources: [sources],
layers: ['locality']
};
const messages = sanitizer.sanitize(undefined, clean);
t.deepEquals(clean.sources, [sources]);
t.deepEquals(messages.errors, [ coarse_reverse_message ]);
t.deepEquals(messages.warnings, []);
});
t.end();
});
};
module.exports.tests.warning_conditions = (test, common) => {
test('only geonames in sources should not modify clean.sources and add error message', (t) => {
test('only geonames in sources and layers=coarse should not modify clean.sources and add error message', (t) => {
['gn', 'geonames'].forEach((sources) => {
const clean = {
sources: ['another source', sources, 'yet another source']
sources: ['another source', sources, 'yet another source'],
layers: ['coarse']
};
const messages = sanitizer.sanitize(undefined, clean);
t.deepEquals(clean.sources, ['another source', 'yet another source']);
t.deepEquals(messages.errors, []);
t.deepEquals(messages.warnings, ['/reverse does not support geonames']);
t.deepEquals(messages.warnings, [ coarse_reverse_message ]);
});

16
test/unit/sanitizer/_layers.js

@ -41,9 +41,10 @@ module.exports.tests.sanitize_layers = function(test, common) {
sanitizer.sanitize(raw, clean);
var admin_layers = [ 'continent', 'country', 'dependency',
'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county',
'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode' ];
var admin_layers = [ 'continent', 'empire', 'country', 'dependency', 'macroregion',
'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood',
'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', 'ocean',
'marinearea' ];
t.deepEqual(clean.layers, admin_layers, 'coarse layers set');
t.end();
@ -76,9 +77,10 @@ module.exports.tests.sanitize_layers = function(test, common) {
sanitizer.sanitize(raw, clean);
var expected_layers = [ 'continent', 'country', 'dependency',
var expected_layers = [ 'continent', 'empire', 'country', 'dependency',
'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county',
'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode' ];
'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode',
'ocean', 'marinearea'];
t.deepEqual(clean.layers, expected_layers, 'coarse + regular layers set');
t.end();
@ -112,10 +114,10 @@ module.exports.tests.sanitize_layers = function(test, common) {
sanitizer.sanitize(raw, clean);
var coarse_layers = [ 'continent',
var coarse_layers = [ 'continent', 'empire',
'country', 'dependency', 'macroregion', 'region', 'locality', 'localadmin',
'macrocounty', 'county', 'macrohood', 'borough', 'neighbourhood', 'microhood',
'disputed', 'postalcode' ];
'disputed', 'postalcode', 'ocean', 'marinearea' ];
var venue_layers = [ 'venue' ];
var expected_layers = venue_layers.concat(coarse_layers);

56
test/unit/service/configurations/PointInPolygon.js

@ -53,16 +53,62 @@ module.exports.tests.all = (test, common) => {
});
test('getParameters should return an empty object', (t) => {
test('getParameters should return an empty object when req is undefined', (t) => {
const configBlob = {
url: 'http://localhost:1234',
timeout: 17,
retries: 19
url: 'http://localhost:1234'
};
const pointInPolygon = new PointInPolygon(configBlob);
t.deepEquals(pointInPolygon.getParameters(), {});
t.deepEquals(pointInPolygon.getParameters(undefined), {});
t.end();
});
test('getParameters should return an empty object when req.clean is undefined', (t) => {
const configBlob = {
url: 'http://localhost:1234'
};
const pointInPolygon = new PointInPolygon(configBlob);
const req = {};
t.deepEquals(pointInPolygon.getParameters(req), {});
t.end();
});
test('getParameters should return an empty object when req.clean.layers is undefined', (t) => {
const configBlob = {
url: 'http://localhost:1234'
};
const pointInPolygon = new PointInPolygon(configBlob);
const req = {
clean: {}
};
t.deepEquals(pointInPolygon.getParameters(req), {});
t.end();
});
test('getParameters should return object with layers property when req.clean.layers is specified', t => {
const configBlob = {
url: 'http://localhost:1234'
};
const pointInPolygon = new PointInPolygon(configBlob);
const req = {
clean: {
layers: ['layer1', 'layer2']
}
};
t.deepEquals(pointInPolygon.getParameters(req), { layers: 'layer1,layer2'});
t.end();
});

Loading…
Cancel
Save