mirror of https://github.com/pelias/api.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
5.5 KiB
171 lines
5.5 KiB
const logger = require('pelias-logger').get('coarse_reverse'); |
|
const _ = require('lodash'); |
|
const Document = require('pelias-model').Document; |
|
const Debug = require('../helper/debug'); |
|
const debugLog = new Debug('controller:coarse_reverse'); |
|
|
|
// do not change order, other functionality depends on most-to-least granular order |
|
const coarse_granularities = [ |
|
'neighbourhood', |
|
'borough', |
|
'locality', |
|
'localadmin', |
|
'county', |
|
'macrocounty', |
|
'region', |
|
'macroregion', |
|
'dependency', |
|
'country', |
|
'empire', |
|
'continent', |
|
'ocean', |
|
'marinearea' |
|
]; |
|
|
|
// remove non-coarse layers and return what's left (or all if empty) |
|
function getEffectiveLayers(requested_layers) { |
|
// remove non-coarse layers |
|
const non_coarse_layers_removed = _.without(requested_layers, 'venue', 'address', 'street'); |
|
|
|
// if resulting array is empty, use all coarse granularities |
|
if (_.isEmpty(non_coarse_layers_removed)) { |
|
return coarse_granularities; |
|
} |
|
|
|
// otherwise use requested layers with non-coarse layers removed |
|
return non_coarse_layers_removed; |
|
|
|
} |
|
|
|
// drop from coarse_granularities until there's one that was requested |
|
// this depends on coarse_granularities being ordered |
|
function getApplicableRequestedLayers(requested_layers) { |
|
return _.dropWhile(coarse_granularities, (coarse_granularity) => { |
|
return !_.includes(requested_layers, coarse_granularity); |
|
}); |
|
} |
|
|
|
// removing non-coarse layers could leave effective_layers empty, so it's |
|
// important to check for empty layers here |
|
function hasResultsAtRequestedLayers(results, requested_layers) { |
|
return !_.isEmpty(_.intersection(_.keys(results), requested_layers)); |
|
} |
|
|
|
// get the most granular layer from the results by taking the head of the intersection |
|
// of coarse_granularities (which are ordered) and the result layers |
|
// ['neighbourhood', 'borough', 'locality'] - ['locality', 'borough'] = 'borough' |
|
// this depends on coarse_granularities being ordered |
|
function getMostGranularLayerOfResult(result_layers) { |
|
return _.head(_.intersection(coarse_granularities, result_layers)); |
|
} |
|
|
|
// create a model.Document from what's left, using the most granular |
|
// result available as the starting point |
|
function synthesizeDoc(results) { |
|
// find the most granular layer to use as the document layer |
|
const most_granular_layer = getMostGranularLayerOfResult(_.keys(results)); |
|
const id = results[most_granular_layer][0].id; |
|
|
|
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 || undefined); |
|
}); |
|
|
|
// 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] |
|
} |
|
}); |
|
|
|
} |
|
|
|
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) { |
|
function controller(req, res, next) { |
|
// do not run controller when a request validation error has occurred |
|
if (!should_execute(req, res)) { |
|
return next(); |
|
} |
|
const initialTime = debugLog.beginTimer(req); |
|
// return a warning to the caller that boundary.circle.radius will be ignored |
|
if (!_.isUndefined(req.clean['boundary.circle.radius'])) { |
|
req.warnings.push('boundary.circle.radius is not applicable for coarse reverse'); |
|
} |
|
|
|
// because coarse reverse is called when non-coarse reverse didn't return |
|
// anything, treat requested layers as if it didn't contain non-coarse layers |
|
const effective_layers = getEffectiveLayers(req.clean.layers); |
|
debugLog.push(req, {effective_layers: effective_layers}); |
|
|
|
const centroid = { |
|
lat: req.clean['point.lat'], |
|
lon: req.clean['point.lon'] |
|
}; |
|
|
|
service(req, (err, results) => { |
|
// if there's an error, log it and bail |
|
if (err) { |
|
logger.info(`[controller:coarse_reverse][error]`); |
|
logger.error(err); |
|
return next(); |
|
} |
|
|
|
// now keep everything from the response that is equal to or less granular |
|
// than the most granular layer requested. that is, if effective_layers=['county'], |
|
// remove neighbourhoods, boroughs, localities, localadmins |
|
const applicable_results = _.pick(results, getApplicableRequestedLayers(effective_layers)); |
|
|
|
res.meta = {}; |
|
res.data = []; |
|
|
|
// if there's a result at the requested layer(s), synthesize a doc from results |
|
if (hasResultsAtRequestedLayers(applicable_results, effective_layers)) { |
|
const doc = synthesizeDoc(applicable_results); |
|
if (doc){ |
|
res.data.push(doc); |
|
} |
|
} |
|
debugLog.stopTimer(req, initialTime); |
|
return next(); |
|
|
|
}); |
|
|
|
} |
|
|
|
return controller; |
|
|
|
} |
|
|
|
module.exports = setup;
|
|
|