mirror of https://github.com/pelias/api.git
Julian Simioni
9 years ago
36 changed files with 939 additions and 154 deletions
@ -0,0 +1,99 @@
|
||||
var logger = require('pelias-logger').get('api'); |
||||
var _ = require('lodash'); |
||||
|
||||
function setup() { |
||||
return dedupeResults; |
||||
} |
||||
|
||||
function dedupeResults(req, res, next) { |
||||
// do nothing if no result data set
|
||||
if (_.isUndefined(req.clean) || _.isUndefined(res) || _.isUndefined(res.data)) { |
||||
return next(); |
||||
} |
||||
|
||||
// loop through data items and only copy unique items to uniqueResults
|
||||
var uniqueResults = []; |
||||
|
||||
_.some(res.data, function (hit) { |
||||
if (uniqueResults.length === 0 || _.every(uniqueResults, isDifferent.bind(null, hit)) ) { |
||||
uniqueResults.push(hit); |
||||
} |
||||
else { |
||||
logger.info('[dupe]', { query: req.clean.text, hit: hit.name.default }); |
||||
} |
||||
|
||||
// stop looping when requested size has been reached in uniqueResults
|
||||
return req.clean.size <= uniqueResults.length; |
||||
}); |
||||
|
||||
res.data = uniqueResults; |
||||
|
||||
next(); |
||||
} |
||||
|
||||
/** |
||||
* @param {object} item1 |
||||
* @param {object} item2 |
||||
* @returns {boolean} |
||||
* @throws {Error} |
||||
*/ |
||||
function isDifferent(item1, item2) { |
||||
try { |
||||
propMatch(item1, item2, 'admin1_abbr'); |
||||
propMatch(item1, item2, 'alpha3'); |
||||
|
||||
if (item1.hasOwnProperty('name') && item2.hasOwnProperty('name')) { |
||||
propMatch(item1.name, item2.name, 'default'); |
||||
} |
||||
else { |
||||
propMatch(item1, item2, 'name'); |
||||
} |
||||
|
||||
if (item1.hasOwnProperty('address') && item2.hasOwnProperty('address')) { |
||||
propMatch(item1.address, item2.address, 'number'); |
||||
propMatch(item1.address, item2.address, 'street'); |
||||
propMatch(item1.address, item2.address, 'zip'); |
||||
} |
||||
else if (item1.address !== item2.address) { |
||||
throw new Error('different'); |
||||
} |
||||
} |
||||
catch (err) { |
||||
if (err.message === 'different') { |
||||
return true; |
||||
} |
||||
throw err; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Throw exception if properties are different |
||||
* |
||||
* @param {object} item1 |
||||
* @param {object} item2 |
||||
* @param {string} prop |
||||
* @throws {Error} |
||||
*/ |
||||
function propMatch(item1, item2, prop) { |
||||
if (normalizeString(item1[prop]) !== normalizeString(item2[prop])) { |
||||
throw new Error('different'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Remove punctuation and lowercase |
||||
* |
||||
* @param {string} str |
||||
* @returns {string} |
||||
*/ |
||||
function normalizeString(str) { |
||||
if (!str) { |
||||
return ''; |
||||
} |
||||
return str.toLowerCase().split(/[ ,-]+/).join(' '); |
||||
} |
||||
|
||||
|
||||
module.exports = setup; |
@ -0,0 +1,39 @@
|
||||
|
||||
function setup() { |
||||
return applyLocalNamingConventions; |
||||
} |
||||
|
||||
function applyLocalNamingConventions(req, res, next) { |
||||
|
||||
// do nothing if no result data set
|
||||
if (!res || !res.data) { |
||||
return next(); |
||||
} |
||||
|
||||
// loop through data items and flip relevant number/street
|
||||
res.data.filter(function(place){ |
||||
// only relevant for German addresses
|
||||
if( 'DEU' !== place.alpha3 ){ return false; } |
||||
if( !place.hasOwnProperty('address') ){ return false; } |
||||
if( !place.address.hasOwnProperty('number') ){ return false; } |
||||
if( !place.address.hasOwnProperty('street') ){ return false; } |
||||
return true; |
||||
}) |
||||
.forEach( flipNumberAndStreet ); |
||||
|
||||
next(); |
||||
} |
||||
|
||||
// DE address should have the housenumber and street name flipped
|
||||
// eg. '101 Grolmanstraße' -> 'Grolmanstraße 101'
|
||||
function flipNumberAndStreet(place) { |
||||
var standard = ( place.address.number + ' ' + place.address.street ), |
||||
flipped = ( place.address.street + ' ' + place.address.number ); |
||||
|
||||
// flip street name and housenumber
|
||||
if( place.name.default === standard ){ |
||||
place.name.default = flipped; |
||||
} |
||||
} |
||||
|
||||
module.exports = setup; |
@ -0,0 +1,35 @@
|
||||
var _ = require('lodash'); |
||||
|
||||
var SIZE_PADDING = 2; |
||||
|
||||
/** |
||||
* Utility for calculating query result size |
||||
* incorporating padding for dedupe process |
||||
*/ |
||||
function setup() { |
||||
return function setQuerySize(req, res, next) { |
||||
if (_.isUndefined(req.clean) || _.isUndefined(req.clean.size)) { |
||||
return next(); |
||||
} |
||||
|
||||
req.clean.querySize = calculateSize(req.clean.size); |
||||
next(); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Add padding or set to 1 |
||||
* |
||||
* @param {number} cleanSize |
||||
* @returns {number} |
||||
*/ |
||||
function calculateSize(cleanSize) { |
||||
switch (cleanSize || 1) { |
||||
case 1: |
||||
return 1; |
||||
default: |
||||
return cleanSize * SIZE_PADDING; |
||||
} |
||||
} |
||||
|
||||
module.exports = setup; |
@ -0,0 +1,61 @@
|
||||
module.exports = [ |
||||
{ |
||||
'id': 'foobar', |
||||
'gid': 'osm:venue:foobar', |
||||
'layer': 'venue', |
||||
'source': 'osm', |
||||
'name': { |
||||
'default': '万里长城万里长城' |
||||
}, |
||||
'country_a': 'CHN', |
||||
'country': 'China', |
||||
'region': 'Beijing', |
||||
'confidence': 0.733 |
||||
}, |
||||
{ |
||||
'id': '185883777', |
||||
'gid': 'osm:venue:185883777', |
||||
'layer': 'venue', |
||||
'source': 'osm', |
||||
'name': { |
||||
'default': '万里长城' |
||||
}, |
||||
'country_a': 'CHN', |
||||
'country': 'China', |
||||
'region': 'Beijing', |
||||
'confidence': 0.733 |
||||
}, |
||||
{ |
||||
'id': '1877602615', |
||||
'gid': 'osm:venue:1877602615', |
||||
'layer': 'venue', |
||||
'source': 'osm', |
||||
'name': { |
||||
'default': '万里花' |
||||
}, |
||||
'country_a': 'JPN', |
||||
'country': 'Japan', |
||||
'region': 'Tokyo', |
||||
'county': '豊島区', |
||||
'locality': 'Tokyo', |
||||
'neighbourhood': '2丁目', |
||||
'confidence': 0.646 |
||||
}, |
||||
{ |
||||
'id': '231404818', |
||||
'gid': 'osm:venue:231404818', |
||||
'layer': 'venue', |
||||
'source': 'osm', |
||||
'name': { |
||||
'default': '万里加油站' |
||||
}, |
||||
'address': { |
||||
'street': 'S308', |
||||
'postalcode': '312044' |
||||
}, |
||||
'country_a': 'CHN', |
||||
'country': 'China', |
||||
'region': 'Zhejiang', |
||||
'confidence': 0.646 |
||||
} |
||||
]; |
@ -0,0 +1,273 @@
|
||||
module.exports = [ |
||||
{ |
||||
'center_point': { |
||||
'lon': -76.207456, |
||||
'lat': 40.039265 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'East Lampeter', |
||||
'admin1_abbr': 'PA', |
||||
'name': { |
||||
'default': 'East Lampeter High School' |
||||
}, |
||||
'admin1': 'Pennsylvania', |
||||
'locality': 'Smoketown', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Lancaster County', |
||||
'admin0': 'United States', |
||||
'neighborhood': 'Greenland', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '357321757', |
||||
'_type': 'osmnode', |
||||
'_score': 1.2367082, |
||||
'confidence': 0.879 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -76.207456, |
||||
'lat': 40.039265 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'East Lampeter', |
||||
'admin1_abbr': 'PA', |
||||
'name': { |
||||
'default': 'East Lampeter, High-School' |
||||
}, |
||||
'admin1': 'Pennsylvania', |
||||
'locality': 'Smoketown', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Lancaster County', |
||||
'admin0': 'United States', |
||||
'neighborhood': 'Greenland', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '357321757', |
||||
'_type': 'osmnode', |
||||
'_score': 1.2367082, |
||||
'confidence': 0.879 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -76.23246, |
||||
'lat': 39.99288 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'West Lampeter', |
||||
'admin1_abbr': 'PA', |
||||
'name': { |
||||
'default': 'Lampeter-Strasburg High School' |
||||
}, |
||||
'admin1': 'Pennsylvania', |
||||
'locality': 'Lampeter', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Lancaster County', |
||||
'admin0': 'United States', |
||||
'neighborhood': 'Wheatland Mills', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '4559068', |
||||
'_type': 'geoname', |
||||
'_score': 1.2367082, |
||||
'confidence': 0.879 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -76.20746, |
||||
'lat': 40.03927 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'East Lampeter', |
||||
'admin1_abbr': 'PA', |
||||
'name': { |
||||
'default': 'East Lampeter High School' |
||||
}, |
||||
'admin1': 'Pennsylvania', |
||||
'locality': 'Smoketown', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Lancaster County', |
||||
'admin0': 'United States', |
||||
'neighborhood': 'Greenland', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '5187980', |
||||
'_type': 'geoname', |
||||
'_score': 1.2367082, |
||||
'confidence': 0.879 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -76.232457, |
||||
'lat': 39.992877 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'West Lampeter', |
||||
'admin1_abbr': 'PA', |
||||
'name': { |
||||
'default': 'Lampeter-Strasburg High School' |
||||
}, |
||||
'admin1': 'Pennsylvania', |
||||
'locality': 'Lampeter', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Lancaster County', |
||||
'admin0': 'United States', |
||||
'neighborhood': 'Wheatland Mills', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '357294404', |
||||
'_type': 'osmnode', |
||||
'_score': 1.2367082, |
||||
'confidence': 0.879 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -76.207456, |
||||
'lat': 40.038987 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'East Lampeter', |
||||
'admin1_abbr': 'PA', |
||||
'name': { |
||||
'default': 'East Lampeter School' |
||||
}, |
||||
'admin1': 'Pennsylvania', |
||||
'locality': 'Smoketown', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Lancaster County', |
||||
'admin0': 'United States', |
||||
'neighborhood': 'Greenland', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '357283977', |
||||
'_type': 'osmnode', |
||||
'_score': 1.1036991, |
||||
'confidence': 0.664 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -76.20746, |
||||
'lat': 40.03899 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'East Lampeter', |
||||
'admin1_abbr': 'PA', |
||||
'name': { |
||||
'default': 'East Lampeter School' |
||||
}, |
||||
'admin1': 'Pennsylvania', |
||||
'locality': 'Smoketown', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Lancaster County', |
||||
'admin0': 'United States', |
||||
'neighborhood': 'Greenland', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '5187966', |
||||
'_type': 'geoname', |
||||
'_score': 1.1036991, |
||||
'confidence': 0.664 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -94.167445, |
||||
'lat': 38.762788 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'Polk', |
||||
'admin1_abbr': 'MO', |
||||
'name': { |
||||
'default': 'Strasburg School' |
||||
}, |
||||
'admin1': 'Missouri', |
||||
'locality': 'Strasburg', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Cass County', |
||||
'admin0': 'United States', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '358058986', |
||||
'_type': 'osmnode', |
||||
'_score': 1.0492544, |
||||
'confidence': 0.658 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -78.36317, |
||||
'lat': 38.98445 |
||||
}, |
||||
'address': {}, |
||||
'admin1_abbr': 'VA', |
||||
'name': { |
||||
'default': 'Strasburg High School' |
||||
}, |
||||
'admin1': 'Virginia', |
||||
'locality': 'Strasburg', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Shenandoah County', |
||||
'admin0': 'United States', |
||||
'neighborhood': 'Strasburg Junction', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '4787978', |
||||
'_type': 'geoname', |
||||
'_score': 0.9724125, |
||||
'confidence': 0.649 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -100.16516, |
||||
'lat': 46.13427 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'Strasburg', |
||||
'admin1_abbr': 'ND', |
||||
'name': { |
||||
'default': 'Strasburg High School' |
||||
}, |
||||
'admin1': 'North Dakota', |
||||
'locality': 'Strasburg', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Emmons County', |
||||
'admin0': 'United States', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '9683163', |
||||
'_type': 'geoname', |
||||
'_score': 0.9724125, |
||||
'confidence': 0.649 |
||||
}, |
||||
{ |
||||
'center_point': { |
||||
'lon': -81.532392, |
||||
'lat': 40.597578 |
||||
}, |
||||
'address': {}, |
||||
'local_admin': 'Franklin', |
||||
'admin1_abbr': 'OH', |
||||
'name': { |
||||
'default': 'Strasburg High School' |
||||
}, |
||||
'admin1': 'Ohio', |
||||
'locality': 'Strasburg', |
||||
'alpha3': 'USA', |
||||
'admin2': 'Tuscarawas County', |
||||
'admin0': 'United States', |
||||
'category': [ |
||||
'education' |
||||
], |
||||
'_id': '356646971', |
||||
'_type': 'osmway', |
||||
'_score': 0.9724125, |
||||
'confidence': 0.649 |
||||
} |
||||
]; |
@ -0,0 +1,67 @@
|
||||
|
||||
var calcSize = require('../../../middleware/sizeCalculator.js')(); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.interface = function(test, common) { |
||||
test('interface', function(t) { |
||||
t.equal(typeof calcSize, 'function', 'valid function'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.valid = function(test, common) { |
||||
var req = { clean: {} }; |
||||
function setup(val) { |
||||
if (isNaN(val)) { |
||||
delete req.clean.size; |
||||
} |
||||
else { |
||||
req.clean.size = val; |
||||
} |
||||
delete req.clean.querySize; |
||||
} |
||||
|
||||
test('size=0', function (t) { |
||||
setup(0); |
||||
calcSize(req, {}, function () { |
||||
t.equal(req.clean.querySize, 1); |
||||
t.end(); |
||||
}); |
||||
}); |
||||
|
||||
test('size=1', function (t) { |
||||
setup(1); |
||||
calcSize(req, {}, function () { |
||||
t.equal(req.clean.querySize, 1); |
||||
t.end(); |
||||
}); |
||||
}); |
||||
|
||||
test('size=10', function (t) { |
||||
setup(10); |
||||
calcSize(req, {}, function () { |
||||
t.equal(req.clean.querySize, 20); |
||||
t.end(); |
||||
}); |
||||
}); |
||||
|
||||
test('no size', function (t) { |
||||
setup(); |
||||
calcSize(req, {}, function () { |
||||
t.equal(req.clean.hasOwnProperty('querySize'), false); |
||||
t.end(); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.all = function (tape, common) { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape('sizeCalculator: ' + name, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
@ -0,0 +1,70 @@
|
||||
var data = require('../fixture/dedupe_elasticsearch_results'); |
||||
var nonAsciiData = require('../fixture/dedupe_elasticsearch_nonascii_results'); |
||||
var dedupe = require('../../../middleware/dedupe')(); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.dedupe = function(test, common) { |
||||
test('filter out duplicates', function(t) { |
||||
var req = { |
||||
clean: { |
||||
text: 'lampeter strasburg high school', |
||||
size: 100 |
||||
} |
||||
}; |
||||
var res = { |
||||
data: data |
||||
}; |
||||
|
||||
var expectedCount = 7; |
||||
dedupe(req, res, function () { |
||||
t.equal(res.data.length, expectedCount, 'results have fewer items than before'); |
||||
t.end(); |
||||
}); |
||||
}); |
||||
|
||||
test('handle non-ascii gracefully', function(t) { |
||||
var req = { |
||||
clean: { |
||||
size: 100 |
||||
} |
||||
}; |
||||
var res = { |
||||
data: nonAsciiData |
||||
}; |
||||
|
||||
var expectedCount = 4; |
||||
dedupe(req, res, function () { |
||||
t.equal(res.data.length, expectedCount, 'none were removed'); |
||||
t.end(); |
||||
}); |
||||
}); |
||||
|
||||
test('truncate results based on specified size', function(t) { |
||||
var req = { |
||||
clean: { |
||||
text: 'lampeter strasburg high school', |
||||
size: 3 |
||||
} |
||||
}; |
||||
var res = { |
||||
data: data |
||||
}; |
||||
|
||||
dedupe(req, res, function () { |
||||
t.equal(res.data.length, req.clean.size, 'results have fewer items than before'); |
||||
t.end(); |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.all = function (tape, common) { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape('[middleware] dedupe: ' + name, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
@ -0,0 +1,71 @@
|
||||
var localNamingConventions = require('../../../middleware/localNamingConventions'); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
// ref: https://github.com/pelias/pelias/issues/141
|
||||
module.exports.tests.flipNumberAndStreet = function(test, common) { |
||||
|
||||
var ukAddress = { |
||||
'_id': 'test1', |
||||
'_type': 'test', |
||||
'name': { 'default': '1 Main St' }, |
||||
'center_point': { 'lon': -7.131521, 'lat': 54.428866 }, |
||||
'address': { |
||||
'zip': 'BT77 0BG', |
||||
'number': '1', |
||||
'street': 'Main St' |
||||
}, |
||||
'admin1': 'Dungannon', |
||||
'alpha3': 'GBR', |
||||
'admin0': 'United Kingdom' |
||||
}; |
||||
|
||||
var deAddress = { |
||||
'_id': 'test2', |
||||
'_type': 'test', |
||||
'name': { 'default': '23 Grolmanstraße' }, |
||||
'center_point': { 'lon': 13.321487, 'lat': 52.506781 }, |
||||
'address': { |
||||
'zip': '10623', |
||||
'number': '23', |
||||
'street': 'Grolmanstraße' |
||||
}, |
||||
'admin1': 'Berlin', |
||||
'locality': 'Berlin', |
||||
'alpha3': 'DEU', |
||||
'admin2': 'Berlin', |
||||
'admin0': 'Germany', |
||||
'neighborhood': 'Hansaviertel' |
||||
}; |
||||
|
||||
var req = {}, |
||||
res = { data: [ ukAddress, deAddress ] }, |
||||
middleware = localNamingConventions(); |
||||
|
||||
test('flipNumberAndStreet', function(t) { |
||||
|
||||
middleware( req, res, function next(){ |
||||
|
||||
// GBR address should be a noop
|
||||
t.equal( res.data[0].name.default, '1 Main St', 'standard name' ); |
||||
|
||||
// DEU address should have the housenumber and street name flipped
|
||||
// eg. '101 Grolmanstraße' -> 'Grolmanstraße 101'
|
||||
t.equal( res.data[1].name.default, 'Grolmanstraße 23', 'flipped name' ); |
||||
|
||||
t.end(); |
||||
}); |
||||
}); |
||||
|
||||
}; |
||||
|
||||
module.exports.all = function (tape, common) { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape('[middleware] localNamingConventions: ' + name, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
Loading…
Reference in new issue