mirror of https://github.com/pelias/api.git
Julian Simioni
9 years ago
116 changed files with 2969 additions and 1821 deletions
@ -1,6 +1,23 @@
|
||||
sudo: false |
||||
language: node_js |
||||
script: "npm run unit" |
||||
node_js: |
||||
- "0.10" |
||||
- "0.12" |
||||
sudo: false |
||||
- 0.10 |
||||
- 0.12 |
||||
- 4.4 |
||||
- 5.8 |
||||
matrix: |
||||
allow_failures: |
||||
- node_js: 4.4 |
||||
- node_js: 5.8 |
||||
env: |
||||
global: |
||||
- CXX=g++-4.8 |
||||
matrix: |
||||
- TEST_SUITE=unit |
||||
script: "npm run $TEST_SUITE" |
||||
addons: |
||||
apt: |
||||
sources: |
||||
- ubuntu-toolchain-r-test |
||||
packages: |
||||
- g++-4.8 |
||||
|
@ -1,9 +0,0 @@
|
||||
|
||||
#### Search: |
||||
|
||||
- Nearest Museums: http://pelias.mapzen.com/search?lat=51.533&lon=-0.0652&input=museum&size=40 |
||||
- Nearest Hotels: http://pelias.mapzen.com/search?lat=51.533&lon=-0.0652&input=hotel&size=40 |
||||
|
||||
#### Autocomplete: |
||||
|
||||
- Local Neighborhoods: http://pelias.mapzen.com/suggest/nearby?lat=40.7259&lon=-73.9806&input=e&layers=neighborhood&size=40 |
@ -0,0 +1,9 @@
|
||||
#!/bin/bash -ex |
||||
|
||||
rm -r docs || true |
||||
|
||||
curl -s http://localhost:3100/v1 > /dev/null || die "Pelias server does not appear to be running \ |
||||
on http://localhost:3100, run npm start in another window before generating docs." |
||||
|
||||
cd test/ciao |
||||
node ../../node_modules/ciao/bin/ciao -c ../ciao.json . -d ../../docs |
@ -1,45 +0,0 @@
|
||||
|
||||
var _ = require('lodash'), |
||||
peliasSchema = require('pelias-schema'), |
||||
peliasLogger = require( 'pelias-logger' ).get( 'api' ); |
||||
|
||||
var ADMIN_FIELDS = [ |
||||
'admin0', |
||||
'admin1', |
||||
'admin1_abbr', |
||||
'admin2', |
||||
'local_admin', |
||||
'locality', |
||||
'neighborhood', |
||||
'address.zip' |
||||
]; |
||||
|
||||
/** |
||||
* Get all admin fields that were expected and also found in schema |
||||
* |
||||
* @param {Object} [schema] optional: for testing only |
||||
* @param {Array} [expectedFields] optional: for testing only |
||||
* @param {Object} [logger] optional: for testing only |
||||
* @returns {Array.<string>} |
||||
*/ |
||||
function getAvailableAdminFields(schema, expectedFields, logger) { |
||||
|
||||
schema = schema || peliasSchema; |
||||
expectedFields = expectedFields || ADMIN_FIELDS; |
||||
logger = logger || peliasLogger; |
||||
|
||||
var actualFields = Object.keys(schema.mappings._default_.properties); |
||||
|
||||
// check if expected fields are actually in current schema
|
||||
var available = expectedFields.filter(function (field) { |
||||
return _.contains( actualFields, field ); |
||||
}); |
||||
|
||||
if (available.length === 0) { |
||||
logger.error('helper/adminFields: no expected admin fields found in schema'); |
||||
} |
||||
|
||||
return available; |
||||
} |
||||
|
||||
module.exports = getAvailableAdminFields; |
@ -1,35 +1,74 @@
|
||||
|
||||
var _ = require('lodash'), |
||||
check = require('check-types'), |
||||
schemas = require('./labelSchema.json'); |
||||
schemas = require('./labelSchema'); |
||||
|
||||
module.exports = function( record ){ |
||||
var schema = getSchema(record.country_a); |
||||
|
||||
var labelParts = getInitialLabel(record); |
||||
|
||||
var labelParts = [ record.name.default ]; |
||||
for (var key in schema) { |
||||
var valueFunction = schema[key]; |
||||
|
||||
var schema = schemas.default; |
||||
|
||||
if (record.country_a && record.country_a.length && schemas[record.country_a]) { |
||||
schema = schemas[record.country_a]; |
||||
labelParts = valueFunction(record, labelParts); |
||||
} |
||||
|
||||
var buildOutput = function(parts, schemaArr, record) { |
||||
for (var i=0; i<schemaArr.length; i++) { |
||||
var fieldValue = record[schemaArr[i]]; |
||||
if (check.unemptyString(fieldValue) && !_.contains(parts, fieldValue)) { |
||||
parts.push( fieldValue ); |
||||
return parts; |
||||
} |
||||
} |
||||
return parts; |
||||
}; |
||||
|
||||
for (var key in schema) { |
||||
labelParts = buildOutput(labelParts, schema[key], record); |
||||
// NOTE: while it may seem odd to call `uniq` on the list of label parts,
|
||||
// the effect is quite subtle. Take, for instance, a result for "Lancaster, PA"
|
||||
// the pseudo-object is:
|
||||
// {
|
||||
// 'name': 'Lancaster',
|
||||
// 'locality': 'Lancaster',
|
||||
// 'region_a': 'PA',
|
||||
// 'country_a': 'USA'
|
||||
// }
|
||||
//
|
||||
// the code up to this point generates the label:
|
||||
// `Lancaster, Lancaster, PA, USA`
|
||||
//
|
||||
// then the `unique` call reduces this to:
|
||||
// `Lancaster, PA, USA`
|
||||
//
|
||||
// this code doesn't have the same effect in the case of a venue or address
|
||||
// where the `name` field would contain the address or name of a point-of-interest
|
||||
//
|
||||
// Also see https://github.com/pelias/api/issues/429 for other ways that this is bad
|
||||
//
|
||||
// de-dupe, join, trim
|
||||
return _.uniq( labelParts ).join(', ').trim(); |
||||
|
||||
}; |
||||
|
||||
function getSchema(country_a) { |
||||
if (country_a && country_a.length && schemas[country_a]) { |
||||
return schemas[country_a]; |
||||
} |
||||
|
||||
return schemas.default; |
||||
|
||||
} |
||||
|
||||
// helper function that sets a default label for non-US/CA regions
|
||||
// this is a very special case
|
||||
function getInitialLabel(record) { |
||||
if (isRegion(record.layer) && |
||||
isGeonamesOrWhosOnFirst(record.source) && |
||||
isUSAOrCAN(record.country_a)) { |
||||
return []; |
||||
} |
||||
|
||||
// de-dupe outputs
|
||||
labelParts = _.unique( labelParts ); |
||||
return [record.name]; |
||||
|
||||
} |
||||
|
||||
function isRegion(layer) { |
||||
return 'region' === layer; |
||||
} |
||||
|
||||
function isUSAOrCAN(country_a) { |
||||
return 'USA' === country_a || 'CAN' === country_a; |
||||
} |
||||
|
||||
function isGeonamesOrWhosOnFirst(source) { |
||||
return 'geonames' === source || 'whosonfirst' === source; |
||||
|
||||
return labelParts.join(', ').trim(); |
||||
}; |
||||
} |
||||
|
@ -0,0 +1,66 @@
|
||||
var _ = require('lodash'), |
||||
check = require('check-types'); |
||||
|
||||
module.exports = { |
||||
'USA': { |
||||
'local': getFirstProperty(['localadmin', 'locality', 'neighbourhood', 'county']), |
||||
'regional': getUsState, |
||||
'country': getFirstProperty(['country_a']) |
||||
}, |
||||
'GBR': { |
||||
'local': getFirstProperty(['neighbourhood', 'county', 'localadmin', 'locality', 'macroregion', 'region']), |
||||
'regional': getFirstProperty(['county','country','region']) |
||||
}, |
||||
'SGP': { |
||||
'local': getFirstProperty(['neighbourhood', 'region', 'county', 'localadmin', 'locality']), |
||||
'regional': getFirstProperty(['county','country','region']) |
||||
}, |
||||
'SWE': { |
||||
'local': getFirstProperty(['neighbourhood', 'region', 'county', 'localadmin', 'locality']), |
||||
'regional': getFirstProperty(['country']) |
||||
}, |
||||
'default': { |
||||
'local': getFirstProperty(['localadmin', 'locality', 'neighbourhood', 'county', 'macroregion', 'region']), |
||||
'regional': getFirstProperty(['country']) |
||||
} |
||||
}; |
||||
|
||||
// find the first field of record that has a non-empty value that's not already in labelParts
|
||||
function getFirstProperty(fields) { |
||||
return function(record, labelParts) { |
||||
for (var i = 0; i < fields.length; i++) { |
||||
var fieldValue = record[fields[i]]; |
||||
|
||||
if (check.nonEmptyString(fieldValue) && !_.includes(labelParts, fieldValue)) { |
||||
labelParts.push( fieldValue ); |
||||
return labelParts; |
||||
} |
||||
|
||||
} |
||||
|
||||
return labelParts; |
||||
|
||||
}; |
||||
|
||||
} |
||||
|
||||
// this function is exclusively used for figuring out which field to use for US States
|
||||
// 1. if a US state is the most granular bit of info entered, the label should contain
|
||||
// the full state name, eg: Pennsylvania, USA
|
||||
// 2. otherwise, the state abbreviation should be used, eg: Lancaster, PA, USA
|
||||
// 3. if for some reason the abbreviation isn't available, use the full state name
|
||||
function getUsState(record, labelParts) { |
||||
if ('region' === record.layer && record.region) { |
||||
// add full state name when state is the most granular piece of info
|
||||
labelParts.push(record.region); |
||||
} else if (record.region_a) { |
||||
// otherwise just add the region code when available
|
||||
labelParts.push(record.region_a); |
||||
} else if (record.region) { |
||||
// add the full name when there's no region code available ()
|
||||
labelParts.push(record.region); |
||||
} |
||||
|
||||
return labelParts; |
||||
|
||||
} |
@ -1,22 +0,0 @@
|
||||
{ |
||||
"USA": { |
||||
"local": ["localadmin", "locality", "neighbourhood", "county"], |
||||
"regional": ["region_a", "region", "country"] |
||||
}, |
||||
"GBR": { |
||||
"local": ["neighbourhood", "county", "localadmin", "locality", "region"], |
||||
"regional": ["county","country","region"] |
||||
}, |
||||
"SGP": { |
||||
"local": ["neighbourhood", "region", "county", "localadmin", "locality"], |
||||
"regional": ["county","country","region"] |
||||
}, |
||||
"SWE": { |
||||
"local": ["neighbourhood", "region", "county", "localadmin", "locality"], |
||||
"regional": ["country"] |
||||
}, |
||||
"default": { |
||||
"local": ["localadmin", "locality", "neighbourhood", "county", "region"], |
||||
"regional": ["country"] |
||||
} |
||||
} |
@ -1,87 +1,81 @@
|
||||
var extend = require('extend'), |
||||
_ = require('lodash'); |
||||
|
||||
var TYPE_TO_SOURCE = { |
||||
'geoname': 'gn', |
||||
'osmnode': 'osm', |
||||
'osmway': 'osm', |
||||
'admin0': 'qs', |
||||
'admin1': 'qs', |
||||
'admin2': 'qs', |
||||
'neighborhood': 'qs', |
||||
'locality': 'qs', |
||||
'local_admin': 'qs', |
||||
'osmaddress': 'osm', |
||||
'openaddresses': 'oa' |
||||
}; |
||||
function addStandardTargetsToAliases(standard, aliases) { |
||||
var combined = _.extend({}, aliases); |
||||
standard.forEach(function(target) { |
||||
if (combined[target] === undefined) { |
||||
combined[target] = [target]; |
||||
} |
||||
}); |
||||
|
||||
return combined; |
||||
} |
||||
|
||||
/* |
||||
* This doesn't include alias layers such as coarse |
||||
* Sources |
||||
*/ |
||||
var TYPE_TO_LAYER = { |
||||
'geoname': 'venue', |
||||
'osmnode': 'venue', |
||||
'osmway': 'venue', |
||||
'admin0': 'country', |
||||
'admin1': 'region', |
||||
'admin2': 'county', |
||||
'neighborhood': 'neighbourhood', |
||||
'locality': 'locality', |
||||
'local_admin': 'localadmin', |
||||
'osmaddress': 'address', |
||||
'openaddresses': 'address' |
||||
}; |
||||
|
||||
var SOURCE_TO_TYPE = { |
||||
'gn' : ['geoname'], |
||||
'geonames' : ['geoname'], |
||||
'oa' : ['openaddresses'], |
||||
'openaddresses' : ['openaddresses'], |
||||
'qs' : ['admin0', 'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin'], |
||||
'quattroshapes' : ['admin0', 'admin1', 'admin2', 'neighborhood', 'locality', 'local_admin'], |
||||
'osm' : ['osmaddress', 'osmnode', 'osmway'], |
||||
'openstreetmap' : ['osmaddress', 'osmnode', 'osmway'] |
||||
// a list of all sources
|
||||
var SOURCES = ['openstreetmap', 'openaddresses', 'geonames', 'whosonfirst']; |
||||
|
||||
/* |
||||
* A list of alternate names for sources, mostly used to save typing |
||||
*/ |
||||
var SOURCE_ALIASES = { |
||||
'osm': ['openstreetmap'], |
||||
'oa': ['openaddresses'], |
||||
'gn': ['geonames'], |
||||
'wof': ['whosonfirst'] |
||||
}; |
||||
|
||||
/** |
||||
* This does not included alias layers, those are built separately |
||||
/* |
||||
* Create an object that contains all sources or aliases. The key is the source or alias, |
||||
* the value is either that source, or the canonical name for that alias if it's an alias. |
||||
*/ |
||||
var LAYER_TO_TYPE = { |
||||
'venue': ['geoname','osmnode','osmway'], |
||||
'address': ['osmaddress','openaddresses'], |
||||
'country': ['admin0'], |
||||
'region': ['admin1'], |
||||
'county': ['admin2'], |
||||
'locality': ['locality'], |
||||
'localadmin': ['local_admin'], |
||||
'neighbourhood': ['neighborhood'] |
||||
var SOURCE_MAPPING = addStandardTargetsToAliases(SOURCES, SOURCE_ALIASES); |
||||
|
||||
/* |
||||
* Layers |
||||
*/ |
||||
|
||||
/* |
||||
* A list of all layers in each source. This is used for convenience elswhere |
||||
* and to determine when a combination of source and layer parameters is |
||||
* not going to match any records and will return no results. |
||||
*/ |
||||
var LAYERS_BY_SOURCE = { |
||||
openstreetmap: [ 'address', 'venue' ], |
||||
openaddresses: [ 'address' ], |
||||
geonames: [ 'country', 'region', 'county', 'locality', 'venue' ], |
||||
whosonfirst: [ 'continent', 'macrocountry', 'country', 'dependency', 'region', |
||||
'locality', 'localadmin', 'county', 'macrohood', 'neighbourhood', 'microhood', 'disputed'] |
||||
}; |
||||
|
||||
/* |
||||
* A list of layer aliases that can be used to support specific use cases |
||||
* (like coarse geocoding) * or work around the fact that different sources |
||||
* may have layers that mean the same thing but have a different name |
||||
*/ |
||||
var LAYER_ALIASES = { |
||||
'coarse': ['admin0','admin1','admin2','neighborhood','locality','local_admin'] |
||||
'coarse': LAYERS_BY_SOURCE.whosonfirst |
||||
}; |
||||
|
||||
var LAYER_WITH_ALIASES_TO_TYPE = extend({}, LAYER_ALIASES, LAYER_TO_TYPE); |
||||
// create a list of all layers by combining each entry from LAYERS_BY_SOURCE
|
||||
var LAYERS = _.uniq(Object.keys(LAYERS_BY_SOURCE).reduce(function(acc, key) { |
||||
return acc.concat(LAYERS_BY_SOURCE[key]); |
||||
}, [])); |
||||
|
||||
/* |
||||
* derive the list of types, sources, and layers from above mappings |
||||
* Create the an object that has a key for each possible layer or alias, |
||||
* and returns either that layer, or all the layers in the alias |
||||
*/ |
||||
var TYPES = Object.keys(TYPE_TO_SOURCE); |
||||
var SOURCES = Object.keys(SOURCE_TO_TYPE); |
||||
var LAYERS = Object.keys(LAYER_TO_TYPE); |
||||
|
||||
var sourceAndLayerToType = function sourceAndLayerToType(source, layer) { |
||||
return _.intersection(SOURCE_TO_TYPE[source], LAYER_WITH_ALIASES_TO_TYPE[layer]); |
||||
}; |
||||
var LAYER_MAPPING = addStandardTargetsToAliases(LAYERS, LAYER_ALIASES); |
||||
|
||||
module.exports = { |
||||
types: TYPES, |
||||
sources: SOURCES, |
||||
layers: LAYERS, |
||||
type_to_source: TYPE_TO_SOURCE, |
||||
type_to_layer: TYPE_TO_LAYER, |
||||
source_to_type: SOURCE_TO_TYPE, |
||||
layer_to_type: LAYER_TO_TYPE, |
||||
layer_with_aliases_to_type: LAYER_WITH_ALIASES_TO_TYPE, |
||||
source_and_layer_to_type: sourceAndLayerToType |
||||
source_mapping: SOURCE_MAPPING, |
||||
layer_mapping: LAYER_MAPPING, |
||||
layers_by_source: LAYERS_BY_SOURCE |
||||
}; |
||||
|
@ -1,43 +0,0 @@
|
||||
var type_mapping = require( '../helper/type_mapping' ); |
||||
var _ = require('lodash'); |
||||
|
||||
/** |
||||
* Different parts of the code express "preferences" for which Elasticsearch types are going to be searched |
||||
* This method decides how to combine all the preferences. |
||||
* |
||||
* @param {Array} clean_types |
||||
* @returns {Array} |
||||
*/ |
||||
module.exports = function calculate_types(clean_types) { |
||||
//Check that at least one preference of types is defined
|
||||
if (!clean_types || !(clean_types.from_layers || clean_types.from_sources || clean_types.from_text_parser)) { |
||||
throw new Error('clean_types should not be null or undefined'); |
||||
} |
||||
|
||||
/* the layers and source parameters are cumulative: |
||||
* perform a set intersection of their specified types |
||||
*/ |
||||
if (clean_types.from_layers || clean_types.from_sources) { |
||||
var types = type_mapping.types; |
||||
|
||||
if (clean_types.from_layers) { |
||||
types = _.intersection(types, clean_types.from_layers); |
||||
} |
||||
|
||||
if (clean_types.from_sources) { |
||||
types = _.intersection(types, clean_types.from_sources); |
||||
} |
||||
|
||||
return types; |
||||
} |
||||
|
||||
/* |
||||
* Type restrictions requested by the address parser should only be used |
||||
* if both the source and layers parameters are empty, so do this last |
||||
*/ |
||||
if (clean_types.from_text_parser) { |
||||
return clean_types.from_text_parser; |
||||
} |
||||
|
||||
throw new Error('no types specified'); |
||||
}; |
@ -1,46 +0,0 @@
|
||||
var types_helper = require( '../helper/types' ); |
||||
|
||||
/** |
||||
* Validate the types specified to be searched. |
||||
* |
||||
* Elasticsearch interprets an empty array of types as "search anything" rather |
||||
* than "search nothing", so in the case of an empty array, return an error |
||||
* message instead of searching at all. |
||||
*/ |
||||
function middleware(req, res, next) { |
||||
req.clean = req.clean || {}; |
||||
|
||||
if (req.clean.hasOwnProperty('types')) { |
||||
|
||||
try { |
||||
var types = types_helper(req.clean.types); |
||||
|
||||
if ((types instanceof Array) && types.length === 0) { |
||||
var err = 'You have specified both the `sources` and `layers` ' + |
||||
'parameters in a combination that will return no results.'; |
||||
req.errors.push( err ); |
||||
} |
||||
|
||||
else { |
||||
req.clean.type = types; |
||||
} |
||||
|
||||
} |
||||
|
||||
// @todo: refactor this flow, it is confusing as `types_helper()` can throw
|
||||
// with an error "clean_types should not be null or undefined" which is
|
||||
// not returned to the user yet the return value CAN trigger a user error.
|
||||
// I would have liked to throw for BOTH cases and then handle the users errors
|
||||
// inside the 'catch' but this is not possible.
|
||||
// also: why are we deleting things from $clean?
|
||||
catch (err) { |
||||
// this means there were no types specified
|
||||
delete req.clean.types; |
||||
} |
||||
|
||||
} |
||||
|
||||
next(); |
||||
} |
||||
|
||||
module.exports = middleware; |
@ -0,0 +1,38 @@
|
||||
var logger = require('pelias-logger').get('api'); |
||||
|
||||
/** |
||||
* Parses the bounding box property in docs, if one is found |
||||
*/ |
||||
|
||||
function setup() { |
||||
return function (req, res, next) { |
||||
// do nothing if no result data set
|
||||
if (!res || !res.data) { |
||||
return next(); |
||||
} |
||||
|
||||
res.data = res.data.map(parseBBox); |
||||
|
||||
next(); |
||||
}; |
||||
} |
||||
|
||||
/* |
||||
* Parse the bbox property and form an object |
||||
*/ |
||||
function parseBBox(place) { |
||||
|
||||
if (place && place.bounding_box) { |
||||
try { |
||||
place.bounding_box = JSON.parse(place.bounding_box); |
||||
} |
||||
catch (err) { |
||||
logger.error('Invalid bounding_box json string:', place); |
||||
delete place.bounding_box; |
||||
} |
||||
} |
||||
|
||||
return place; |
||||
} |
||||
|
||||
module.exports = setup; |
@ -1,46 +0,0 @@
|
||||
|
||||
var _ = require('lodash'), |
||||
check = require('check-types'), |
||||
sources_map = require( '../query/sources' ); |
||||
|
||||
var ALL_SOURCES = Object.keys(sources_map), |
||||
ALL_SOURCES_JOINED = ALL_SOURCES.join(','); |
||||
|
||||
function sanitize( raw, clean ) { |
||||
|
||||
// error & warning messages
|
||||
var messages = { errors: [], warnings: [] }; |
||||
|
||||
// init clean.types (if not already init)
|
||||
clean.types = clean.types || {}; |
||||
|
||||
// default case (no layers specified in GET params)
|
||||
// don't even set the from_layers key in this case
|
||||
if( check.unemptyString( raw.source ) ){ |
||||
|
||||
var sources = raw.source.split(','); |
||||
|
||||
var invalid_sources = sources.filter(function(source) { |
||||
return !_.contains( ALL_SOURCES, source ); |
||||
}); |
||||
|
||||
if( invalid_sources.length > 0 ){ |
||||
invalid_sources.forEach( function( invalid ){ |
||||
messages.errors.push('\'' + invalid + '\' is an invalid source parameter. Valid options: ' + ALL_SOURCES_JOINED); |
||||
}); |
||||
} |
||||
|
||||
else { |
||||
var types = sources.reduce(function(acc, source) { |
||||
return acc.concat(sources_map[source]); |
||||
}, []); |
||||
|
||||
clean.types.from_source = types; |
||||
} |
||||
|
||||
} |
||||
|
||||
return messages; |
||||
} |
||||
|
||||
module.exports = sanitize; |
@ -0,0 +1,37 @@
|
||||
var _ = require( 'lodash' ); |
||||
var type_mapping = require( '../helper/type_mapping' ); |
||||
|
||||
/* |
||||
* This sanitiser depends on clean.layers and clean.sources |
||||
* so it has to be run after those sanitisers have been run |
||||
*/ |
||||
function sanitize( raw, clean ){ |
||||
var messages = { errors: [], warnings: [] }; |
||||
|
||||
var possible_errors = []; |
||||
var at_least_one_valid_combination = false; |
||||
|
||||
if (clean.layers && clean.sources) { |
||||
clean.sources.forEach(function(source) { |
||||
var layers_for_source = type_mapping.layers_by_source[source]; |
||||
clean.layers.forEach(function(layer) { |
||||
if (_.includes(layers_for_source, layer)) { |
||||
at_least_one_valid_combination = true; |
||||
} else { |
||||
var message = 'You have specified both the `sources` and `layers` ' + |
||||
'parameters in a combination that will return no results: the ' + |
||||
source + ' source has nothing in the ' + layer + ' layer'; |
||||
possible_errors.push(message); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
if (!at_least_one_valid_combination) { |
||||
messages.errors = possible_errors; |
||||
} |
||||
} |
||||
|
||||
return messages; |
||||
} |
||||
|
||||
module.exports = sanitize; |
@ -1,84 +0,0 @@
|
||||
var adminFields = require('../../../helper/adminFields'); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.interface = function(test, common) { |
||||
test('validate fields', function(t) { |
||||
t.assert(adminFields instanceof Function, 'adminFields is a function'); |
||||
t.assert(adminFields() instanceof Array, 'adminFields() returns an array'); |
||||
t.assert(adminFields().length > 0, 'adminFields array is not empty'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.lookupExistance = function(test, common) { |
||||
test('all expected fields in schema', function(t) { |
||||
|
||||
var expectedFields = [ |
||||
'one', |
||||
'two', |
||||
'three', |
||||
'four' |
||||
]; |
||||
var schema = { mappings: { _default_: { properties: {} } } }; |
||||
|
||||
// inject all expected fields into schema mock
|
||||
expectedFields.forEach(function (field) { |
||||
schema.mappings._default_.properties[field] = {}; |
||||
}); |
||||
|
||||
var res = adminFields(schema, expectedFields); |
||||
|
||||
t.deepEquals(res, expectedFields, 'all expected fields are returned'); |
||||
t.end(); |
||||
}); |
||||
|
||||
test('some expected fields in schema', function(t) { |
||||
|
||||
var expectedFields = [ |
||||
'one', |
||||
'two', |
||||
'three', |
||||
'four' |
||||
]; |
||||
var schema = { mappings: { _default_: { properties: {} } } }; |
||||
|
||||
// inject only some of the expected fields into schema mock
|
||||
expectedFields.slice(0, 3).forEach(function (field) { |
||||
schema.mappings._default_.properties[field] = {}; |
||||
}); |
||||
|
||||
var res = adminFields(schema, expectedFields); |
||||
|
||||
t.deepEquals(res, expectedFields.slice(0, 3), 'only matching expected fields are returned'); |
||||
t.end(); |
||||
}); |
||||
|
||||
test('no expected fields in schema', function(t) { |
||||
|
||||
var schema = { mappings: { _default_: { properties: { foo: {} } } } }; |
||||
|
||||
var logErrorCalled = false; |
||||
var logger = { |
||||
error: function () { |
||||
logErrorCalled = true; |
||||
}}; |
||||
|
||||
var res = adminFields(schema, undefined, logger); |
||||
|
||||
t.deepEquals(res, [], 'no admin fields found'); |
||||
t.assert(logErrorCalled, 'log error called'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.all = function (tape, common) { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape('adminFields: ' + name, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
@ -0,0 +1,87 @@
|
||||
|
||||
var generator = require('../../../helper/labelGenerator'); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.interface = function(test, common) { |
||||
test('interface', function(t) { |
||||
t.equal(typeof generator, 'function', 'valid function'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// GBR street address
|
||||
module.exports.tests.one_main_street_uk = function(test, common) { |
||||
test('one main street uk', function(t) { |
||||
var doc = { |
||||
'name': '1 Main St', |
||||
'housenumber': '1', |
||||
'street': 'Main St', |
||||
'postalcode': 'BT77 0BG', |
||||
'country_a': 'GBR', |
||||
'country': 'United Kingdom', |
||||
'region': 'Dungannon' |
||||
}; |
||||
t.equal(generator(doc),'1 Main St, Dungannon, United Kingdom'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// GBR venue
|
||||
module.exports.tests.hackney_city_farm = function(test, common) { |
||||
test('hackney city farm', function(t) { |
||||
var doc = { |
||||
'name': 'Hackney City Farm', |
||||
'country_a': 'GBR', |
||||
'country': 'United Kingdom', |
||||
'region': 'Hackney', |
||||
'county': 'Greater London', |
||||
'locality': 'London', |
||||
'neighbourhood': 'Haggerston' |
||||
}; |
||||
t.equal(generator(doc),'Hackney City Farm, Haggerston, Greater London'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// GBR country
|
||||
module.exports.tests.wales = function(test, common) { |
||||
test('wales', function(t) { |
||||
var doc = { |
||||
'name': 'Wales', |
||||
'country_a': 'GBR', |
||||
'country': 'United Kingdom', |
||||
'region': 'Wales' |
||||
}; |
||||
t.equal(generator(doc),'Wales, United Kingdom'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// GBR macroregion
|
||||
module.exports.tests.macroregion_trumps_region = function(test, common) { |
||||
test('macroregion should trump region when none of neighbourhood, county, localadmin, locality are available', function(t) { |
||||
var doc = { |
||||
'name': 'Name', |
||||
'country_a': 'GBR', |
||||
'country': 'Country Name', |
||||
'macroregion': 'Macroregion Name', |
||||
'region': 'Region Name' |
||||
}; |
||||
|
||||
t.equal(generator(doc), 'Name, Macroregion Name, Country Name'); |
||||
t.end(); |
||||
|
||||
}); |
||||
}; |
||||
|
||||
module.exports.all = function (tape, common) { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape('label generator (GBR): ' + name, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
@ -0,0 +1,51 @@
|
||||
|
||||
var generator = require('../../../helper/labelGenerator'); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.interface = function(test, common) { |
||||
test('interface', function(t) { |
||||
t.equal(typeof generator, 'function', 'valid function'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// SGP region
|
||||
module.exports.tests.north_west_singapore = function(test, common) { |
||||
test('north west singapore', function(t) { |
||||
var doc = { |
||||
'name': 'North West', |
||||
'country_a': 'SGP', |
||||
'country': 'Singapore', |
||||
'region': 'North West' |
||||
}; |
||||
t.equal(generator(doc),'North West, Singapore'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// SGP venue
|
||||
module.exports.tests.singapore_mcdonalds = function(test, common) { |
||||
test('singapore_mcdonalds', function(t) { |
||||
var doc = { |
||||
'name': 'McDonald\'s', |
||||
'country_a': 'SGP', |
||||
'country': 'Singapore', |
||||
'region': 'Central Singapore', |
||||
'locality': 'Singapore' |
||||
}; |
||||
t.equal(generator(doc),'McDonald\'s, Central Singapore, Singapore'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.all = function (tape, common) { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape('label generator: ' + name, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
@ -0,0 +1,53 @@
|
||||
|
||||
var generator = require('../../../helper/labelGenerator'); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.interface = function(test, common) { |
||||
test('interface', function(t) { |
||||
t.equal(typeof generator, 'function', 'valid function'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// SWE city
|
||||
module.exports.tests.skane1 = function(test, common) { |
||||
test('skåne 1', function(t) { |
||||
var doc = { |
||||
'name': 'Malmö', |
||||
'country_a': 'SWE', |
||||
'country': 'Sweden', |
||||
'region': 'Skåne', |
||||
'county': 'Malmö' |
||||
}; |
||||
t.equal(generator(doc),'Malmö, Skåne, Sweden'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// SWE city
|
||||
module.exports.tests.skane2 = function(test, common) { |
||||
test('skåne 2', function(t) { |
||||
var doc = { |
||||
'name': 'Malmö', |
||||
'country_a': 'SWE', |
||||
'country': 'Sweden', |
||||
'region': 'Skåne', |
||||
'county': 'Malmö', |
||||
'locality': 'Malmö' |
||||
}; |
||||
t.equal(generator(doc),'Malmö, Skåne, Sweden'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.all = function (tape, common) { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape('label generator: ' + name, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
@ -0,0 +1,229 @@
|
||||
var generator = require('../../../helper/labelGenerator'); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.interface = function(test, common) { |
||||
test('interface', function(t) { |
||||
t.equal(typeof generator, 'function', 'valid function'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.localadmin = function(test, common) { |
||||
test('localadmin should trump locality, neighbourhood, and county', function(t) { |
||||
var doc = { |
||||
'name': 'Default Name', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'Region Name', |
||||
'region_a': 'Region Abbr', |
||||
'county': 'County Name', |
||||
'localadmin': 'LocalAdmin Name', |
||||
'locality': 'Locality Name', |
||||
'neighbourhood': 'Neighbourhood Name' |
||||
}; |
||||
t.equal(generator(doc),'Default Name, LocalAdmin Name, Region Abbr, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.locality = function(test, common) { |
||||
test('locality should trump neighbourhood and county when localadmin not available', function(t) { |
||||
var doc = { |
||||
'name': 'Default Name', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'Region Name', |
||||
'region_a': 'Region Abbr', |
||||
'county': 'County Name', |
||||
'locality': 'Locality Name', |
||||
'neighbourhood': 'Neighbourhood Name' |
||||
}; |
||||
t.equal(generator(doc),'Default Name, Locality Name, Region Abbr, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.neighbourhood = function(test, common) { |
||||
test('neighbourhood should trump county when neither localadmin nor locality', function(t) { |
||||
var doc = { |
||||
'name': 'Default Name', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'Region Name', |
||||
'region_a': 'Region Abbr', |
||||
'county': 'County Name', |
||||
'neighbourhood': 'Neighbourhood Name' |
||||
}; |
||||
t.equal(generator(doc),'Default Name, Neighbourhood Name, Region Abbr, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.county = function(test, common) { |
||||
test('county should be used when localadmin, locality, and neighbourhood are not available', function(t) { |
||||
var doc = { |
||||
'name': 'Default Name', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'Region Name', |
||||
'region_a': 'Region Abbr', |
||||
'county': 'County Name' |
||||
}; |
||||
t.equal(generator(doc),'Default Name, County Name, Region Abbr, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.region = function(test, common) { |
||||
test('region should be used when region_a is not available', function(t) { |
||||
var doc = { |
||||
'name': 'Default Name', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'Region Name' |
||||
}; |
||||
t.equal(generator(doc),'Default Name, Region Name, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// USA geonames state
|
||||
module.exports.tests.region_geonames = function(test, common) { |
||||
test('default name should not be prepended when source=geonames and layer=region', function(t) { |
||||
var doc = { |
||||
'name': 'Region Name', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'Region Name', |
||||
'region_a': 'Region Abbr', |
||||
'source': 'geonames', |
||||
'layer': 'region' |
||||
}; |
||||
t.equal(generator(doc),'Region Name, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// USA whosonfirst state
|
||||
module.exports.tests.region_whosonfirst = function(test, common) { |
||||
test('default name should not be prepended when source=whosonfirst and layer=region', function(t) { |
||||
var doc = { |
||||
'name': 'Region Name', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'Region Name', |
||||
'region_a': 'Region Abbr', |
||||
'source': 'whosonfirst', |
||||
'layer': 'region' |
||||
}; |
||||
t.equal(generator(doc),'Region Name, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// USA non-geonames/whosonfirst state
|
||||
module.exports.tests.region_other_source = function(test, common) { |
||||
test('default name should be prepended when layer=region and source is not whosonfirst or geonames', function(t) { |
||||
var doc = { |
||||
'name': 'Default Name', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'Region Name', |
||||
'region_a': 'Region Abbr', |
||||
'source': 'not geonames or whosonfirst', |
||||
'layer': 'region' |
||||
}; |
||||
t.equal(generator(doc),'Default Name, Region Name, USA',generator(doc)); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// major USA city
|
||||
module.exports.tests.san_francisco = function(test, common) { |
||||
test('san francisco', function(t) { |
||||
var doc = { |
||||
'name': 'San Francisco', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'California', |
||||
'region_a': 'CA', |
||||
'county': 'San Francisco County', |
||||
'locality': 'San Francisco' |
||||
}; |
||||
t.equal(generator(doc),'San Francisco, San Francisco County, CA, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// USA venue
|
||||
module.exports.tests.nyc_office = function(test, common) { |
||||
test('30 West 26th Street', function(t) { |
||||
var doc = { |
||||
'name': '30 West 26th Street', |
||||
'housenumber': '30', |
||||
'street': 'West 26th Street', |
||||
'postalcode': '10010', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'New York', |
||||
'region_a': 'NY', |
||||
'county': 'New York County', |
||||
'localadmin': 'Manhattan', |
||||
'locality': 'New York', |
||||
'neighbourhood': 'Flatiron District' |
||||
}; |
||||
t.equal(generator(doc),'30 West 26th Street, Manhattan, NY, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// USA NYC eatery
|
||||
module.exports.tests.nyc_bakery = function(test, common) { |
||||
test('New York Bakery', function(t) { |
||||
var doc = { |
||||
'name': 'New York Bakery', |
||||
'housenumber': '51 W', |
||||
'street': '29th', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'New York', |
||||
'region_a': 'NY', |
||||
'county': 'New York County', |
||||
'localadmin': 'Manhattan', |
||||
'locality': 'New York', |
||||
'neighbourhood': 'Koreatown' |
||||
}; |
||||
t.equal(generator(doc),'New York Bakery, Manhattan, NY, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
// USA SFC building
|
||||
module.exports.tests.ferry_building = function(test, common) { |
||||
test('Ferry Building', function(t) { |
||||
var doc = { |
||||
'name': 'Ferry Building', |
||||
'country_a': 'USA', |
||||
'country': 'United States', |
||||
'region': 'California', |
||||
'region_a': 'CA', |
||||
'county': 'San Francisco County', |
||||
'locality': 'San Francisco', |
||||
'neighbourhood': 'Financial District' |
||||
}; |
||||
t.equal(generator(doc),'Ferry Building, San Francisco, CA, USA'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.all = function (tape, common) { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape('label generator: ' + name, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
@ -1,92 +0,0 @@
|
||||
var types = require('../../../helper/types'); |
||||
|
||||
module.exports.tests = {}; |
||||
|
||||
module.exports.tests.no_cleaned_types = function(test, common) { |
||||
test('no cleaned types', function(t) { |
||||
function testIt() { |
||||
types({}); |
||||
} |
||||
|
||||
t.throws(testIt, /clean_types should not be null or undefined/, 'no input should result in exception'); |
||||
|
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.address_parser = function(test, common) { |
||||
test('address parser specifies only admin layers', function(t) { |
||||
var cleaned_types = { |
||||
from_text_parser: ['admin0'] // simplified return value from address parser
|
||||
}; |
||||
var actual = types(cleaned_types); |
||||
var expected = ['admin0']; // simplified expected value for all admin layers
|
||||
t.deepEqual(actual, expected, 'only layers specified by address parser returned'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.layers_parameter = function(test, common) { |
||||
test('layers parameter specifies only some layers', function(t) { |
||||
var cleaned_types = { |
||||
from_layers: ['geoname'] |
||||
}; |
||||
var actual = types(cleaned_types); |
||||
var expected = ['geoname']; |
||||
t.deepEqual(actual, expected, 'only types specified by layers parameter returned'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.layers_parameter_and_address_parser = function(test, common) { |
||||
test('layers parameter and address parser present', function(t) { |
||||
var cleaned_types = { |
||||
from_layers: ['geoname'], |
||||
from_text_parser: ['admin0'] // simplified return value from address parse
|
||||
}; |
||||
var actual = types(cleaned_types); |
||||
var expected = ['geoname']; |
||||
t.deepEqual(actual, expected, 'layers parameter overrides address parser completely'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.source_parameter = function(test, common) { |
||||
test('source parameter specified', function(t) { |
||||
var cleaned_types = { |
||||
from_sources: ['openaddresses'] |
||||
}; |
||||
|
||||
var actual = types(cleaned_types); |
||||
|
||||
var expected = ['openaddresses']; |
||||
t.deepEqual(actual, expected, 'type parameter set to types specified by source'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.tests.source_and_layers_parameters = function(test, common) { |
||||
test('source and layers parameter both specified', function(t) { |
||||
var cleaned_types = { |
||||
from_sources: ['openaddresses'], |
||||
from_layers: ['osmaddress', 'openaddresses'] |
||||
}; |
||||
|
||||
var actual = types(cleaned_types); |
||||
|
||||
var expected = ['openaddresses']; |
||||
t.deepEqual(actual, expected, 'type set to intersection of source and layer types'); |
||||
t.end(); |
||||
}); |
||||
}; |
||||
|
||||
module.exports.all = function (tape, common) { |
||||
|
||||
function test(name, testFunction) { |
||||
return tape('types: ' + name, testFunction); |
||||
} |
||||
|
||||
for( var testCase in module.exports.tests ){ |
||||
module.exports.tests[testCase](test, common); |
||||
} |
||||
}; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue