Skip to content

Commit

Permalink
Merge pull request #51 from HSLdevcom/sync
Browse files Browse the repository at this point in the history
Update pelias api
  • Loading branch information
siren authored Sep 8, 2017
2 parents 4d47921 + f27e2d5 commit 293a840
Show file tree
Hide file tree
Showing 206 changed files with 15,603 additions and 4,242 deletions.
1 change: 0 additions & 1 deletion .lgtm

This file was deleted.

42 changes: 11 additions & 31 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,12 @@
sudo: false
language: node_js
cache:
directories:
- node_modules
notifications:
email: false
node_js:
- 4
- 6
matrix:
fast_finish: true
allow_failures:
env:
global:
- CXX=g++-4.8
script: "npm run travis"
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
before_install:
- npm i -g npm@^2.0.0
before_script:
- npm prune
after_success:
- npm run semantic-release
sudo: required
branches:
except:
- /^v\d+\.\d+\.\d+$/
only:
- master
- /201[7-9][0-1][0-9][0-3][0-9]/

services: docker
install: true

language: node_js

script: ./travis-build.sh
13 changes: 11 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,29 @@ RUN mkdir -p /mnt/data/libpostal

RUN git clone https://github.com/openvenues/libpostal \
&& cd libpostal \
&& git checkout tags/v0.3.4 \
&& git checkout tags/v1.0.0 \
&& ./bootstrap.sh \
&& ./configure --datadir=/mnt/data/libpostal \
&& make \
&& make install \
&& ldconfig

# use our extended query module until it gets merged upstream
# use our extended query module
ENV QUERY=/opt/pelias/query
WORKDIR ${QUERY}
RUN git clone --single-branch https://github.com/HSLdevcom/query.git \
&& cd query \
&& npm install \
&& npm link

# use our text-analyzer
ENV TEXT_ANALYZER=/opt/pelias/text-analyzer
WORKDIR ${TEXT_ANALYZER}
RUN git clone --single-branch https://github.com/HSLdevcom/pelias-text-analyzer.git \
&& cd pelias-text-analyzer \
&& npm install \
&& npm link

# Where the app is built and run inside the docker fs
ENV WORK=/opt/pelias/api

Expand All @@ -40,6 +48,7 @@ ADD . ${WORK}
# Build and set permissions for arbitrary non-root user
RUN npm install \
&& npm link pelias-query \
&& npm link pelias-text-analyzer \
&& npm test \
&& chmod -R a+rwX .

Expand Down
58 changes: 55 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,61 @@ The API ships with several convenience commands (runnable via `npm`):
## pelias-config
The API recognizes the following properties under the top-level `api` key in your `pelias.json` config file:

* `accessLog`: (*optional*) The 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.
|parameter|required|default|description|
|---|---|---|---|
|`host`|*yes*||specifies the url under which the http service is to run|
|`textAnalyzer`|*no*|*addressit*|can be either `libpostal` or `addressit` however will soon be **deprecated** and only `libpostal` will be supported going forward|
|`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.|
|`defaultParameters.focus.point.lon` <br> `defaultParameters.focus.point.lat`|no | |default coordinates for focus point

Example configuration file would look something like this:

```
{
"esclient": {
"keepAlive": true,
"requestTimeout": "1200000",
"hosts": [
{
"protocol": "http",
"host": "somesemachine.elb.amazonaws.com",
"port": 9200
}
]
},
"api": {
"host": "localhost:3100/v1/",
"indexName": "foobar",
"relativeScores": true,
"textAnalyzer": "libpostal",
"services": {
"pip": {
"url": "http://mypipservice.com:3000"
},
"placeholder": {
"url": "http://myplaceholderservice.com:5000"
}
}
"defaultParameters": {
"focus.point.lat": 12.121212,
"focus.point.lon": 21.212121
}
},
"interpolation": {
"client": {
"adapter": "http",
"host": "internal-pelias-interpolation-dev-130430937.us-east-1.elb.amazonaws.com"
}
},
"logger": {
"level": "debug"
}
}
```


## Contributing

Expand Down
3 changes: 0 additions & 3 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ app.use( require('./middleware/jsonp') );

/** ----------------------- routes ----------------------- **/

var legacy = require('./routes/legacy');
legacy.addRoutes(app, peliasConfig.api);

var v1 = require('./routes/v1');
v1.addRoutes(app, peliasConfig);

Expand Down
154 changes: 154 additions & 0 deletions controller/coarse_reverse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
const logger = require('pelias-logger').get('coarse_reverse');
const _ = require('lodash');
const Document = require('pelias-model').Document;

// 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'
];

// 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;

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);
});

// 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;

}

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();
}

// 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);

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();
}

// log how many results there were
logger.info(`[controller:coarse_reverse][queryType:pip][result_count:${_.size(results)}]`);

// 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)) {
res.data.push(synthesizeDoc(applicable_results));
}

return next();

});

}

return controller;

}

module.exports = setup;
31 changes: 31 additions & 0 deletions controller/libpostal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const text_analyzer = require('pelias-text-analyzer');
const _ = require('lodash');
const iso3166 = require('iso3166-1');

function setup(should_execute) {
function controller( req, res, next ){
// bail early if req/res don't pass conditions for execution
if (!should_execute(req, res)) {
return next();
}

// parse text with query parser
const parsed_text = text_analyzer.parse(req.clean.text);

if (parsed_text !== undefined) {
// if a known ISO2 country was parsed, convert it to ISO3
if (_.has(parsed_text, 'country') && iso3166.is2(_.toUpper(parsed_text.country))) {
parsed_text.country = iso3166.to3(_.toUpper(parsed_text.country));
}

req.clean.parsed_text = parsed_text;
}

return next();

}

return controller;
}

module.exports = setup;
Loading

0 comments on commit 293a840

Please sign in to comment.