pelias-openstreetmap 5.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +2 -0
- package/.editorconfig +9 -0
- package/.github/workflows/push.yml +55 -0
- package/.jshintignore +6 -0
- package/.jshintrc +22 -0
- package/Dockerfile +23 -0
- package/LICENSE +21 -0
- package/README.md +184 -0
- package/bin/download +3 -0
- package/bin/start +3 -0
- package/bin/test +7 -0
- package/config/category_map.js +360 -0
- package/config/features.js +52 -0
- package/config/localized_name_keys.js +44 -0
- package/config/popularity_map.js +163 -0
- package/index.js +12 -0
- package/package.json +77 -0
- package/schema/address_karlsruhe.js +25 -0
- package/schema/address_naptan.js +17 -0
- package/schema/address_osm.js +17 -0
- package/schema/address_tiger.js +18 -0
- package/schema/name_osm.js +51 -0
- package/schema.js +27 -0
- package/stream/addendum_mapper.js +71 -0
- package/stream/address_extractor.js +131 -0
- package/stream/category_mapper.js +77 -0
- package/stream/document_constructor.js +76 -0
- package/stream/importPipeline.js +36 -0
- package/stream/multiple_pbfs.js +25 -0
- package/stream/pbf.js +75 -0
- package/stream/popularity_mapper.js +80 -0
- package/stream/stats.js +47 -0
- package/stream/stringify.js +9 -0
- package/stream/tag_mapper.js +168 -0
- package/util/download_data.js +58 -0
- package/util/sendPassthroughStream.js +8 -0
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pelias-openstreetmap",
|
|
3
|
+
"version": "5.13.0",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": ">=10.0.0"
|
|
6
|
+
},
|
|
7
|
+
"author": "pelias",
|
|
8
|
+
"description": "Pelias openstreetmap utilities",
|
|
9
|
+
"homepage": "https://github.com/mapzen/pelias-openstreetmap",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"main": "index.js",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"download": "./bin/download",
|
|
14
|
+
"coverage": "node_modules/.bin/istanbul cover test/run.js",
|
|
15
|
+
"end-to-end": "export NODE_ENV=test && node test/end-to-end.js;",
|
|
16
|
+
"lint": "jshint .",
|
|
17
|
+
"start": "./bin/start",
|
|
18
|
+
"test": "npm run units",
|
|
19
|
+
"ci": "node ./test/ci-config.js && npm test && npm run end-to-end",
|
|
20
|
+
"units": "./bin/test",
|
|
21
|
+
"validate": "npm ls"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/mapzen/pelias-openstreetmap.git"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"pelias",
|
|
29
|
+
"stream",
|
|
30
|
+
"openstreetmap",
|
|
31
|
+
"elasticsearch"
|
|
32
|
+
],
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/mapzen/pelias-openstreetmap/issues"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"async": "^3.0.1",
|
|
38
|
+
"combined-stream": "^1.0.5",
|
|
39
|
+
"extend": "^3.0.0",
|
|
40
|
+
"is-object": "^1.0.1",
|
|
41
|
+
"iso-639-3": "^1.0.0",
|
|
42
|
+
"joi": "^14.0.0",
|
|
43
|
+
"lodash": "^4.17.4",
|
|
44
|
+
"pbf2json": "^6.9.0",
|
|
45
|
+
"pelias-blacklist-stream": "^1.0.0",
|
|
46
|
+
"pelias-config": "^4.12.0",
|
|
47
|
+
"pelias-dbclient": "^2.13.0",
|
|
48
|
+
"pelias-logger": "^1.2.1",
|
|
49
|
+
"pelias-model": "^9.1.1",
|
|
50
|
+
"pelias-wof-admin-lookup": "^7.4.1",
|
|
51
|
+
"through2": "^3.0.0",
|
|
52
|
+
"through2-sink": "^1.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"colors": "^1.1.2",
|
|
56
|
+
"deep-diff": "^1.0.0",
|
|
57
|
+
"istanbul": "^0.4.3",
|
|
58
|
+
"jshint": "^2.10.3",
|
|
59
|
+
"naivedb": "^1.0.7",
|
|
60
|
+
"pelias-mock-logger": "^1.0.1",
|
|
61
|
+
"precommit-hook": "^3.0.0",
|
|
62
|
+
"proxyquire": "^2.0.0",
|
|
63
|
+
"stream-mock": "^2.0.3",
|
|
64
|
+
"taginfo": "^1.0.1",
|
|
65
|
+
"tap-spec": "^5.0.0",
|
|
66
|
+
"tape": "^5.0.0"
|
|
67
|
+
},
|
|
68
|
+
"pre-commit": [
|
|
69
|
+
"lint",
|
|
70
|
+
"validate",
|
|
71
|
+
"test"
|
|
72
|
+
],
|
|
73
|
+
"release": {
|
|
74
|
+
"branch": "master",
|
|
75
|
+
"success": []
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
Attempt to map OSM address tags to the Pelias address schema.
|
|
4
|
+
|
|
5
|
+
On the left is the OSM tag name, on the right is corresponding
|
|
6
|
+
doc.address_parts key for which it should be mapped to.
|
|
7
|
+
|
|
8
|
+
eg. tags['addr:street'] -> doc.address_parts['street']
|
|
9
|
+
|
|
10
|
+
@ref: http://wiki.openstreetmap.org/wiki/Karlsruhe_Schema
|
|
11
|
+
**/
|
|
12
|
+
|
|
13
|
+
var KARLSRUHE_SCHEMA = {
|
|
14
|
+
'addr:housename': 'name',
|
|
15
|
+
'addr:housenumber': 'number',
|
|
16
|
+
'addr:street': 'street',
|
|
17
|
+
'addr:postcode': 'zip'
|
|
18
|
+
|
|
19
|
+
// @ref: https://github.com/pelias/model/pull/13
|
|
20
|
+
// 'addr:state': 'state',
|
|
21
|
+
// 'addr:city': 'city',
|
|
22
|
+
// 'addr:country': 'country'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = KARLSRUHE_SCHEMA;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
Attempt to map OSM address tags to the Pelias address schema.
|
|
4
|
+
|
|
5
|
+
On the left is the OSM tag name, on the right is corresponding
|
|
6
|
+
doc.address key for which it should be mapped to.
|
|
7
|
+
|
|
8
|
+
eg. tags['naptan:Street'] -> doc.address_parts['street']
|
|
9
|
+
|
|
10
|
+
@ref: http://wiki.openstreetmap.org/wiki/NaPTAN
|
|
11
|
+
**/
|
|
12
|
+
|
|
13
|
+
var NAPTAN_SCHEMA = {
|
|
14
|
+
'naptan:Street': 'street'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = NAPTAN_SCHEMA;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
Attempt to map OSM address tags to the Pelias address schema.
|
|
4
|
+
|
|
5
|
+
On the left is the OSM tag name, on the right is corresponding
|
|
6
|
+
doc.address key for which it should be mapped to.
|
|
7
|
+
|
|
8
|
+
eg. tags['postal_code'] -> doc.address_parts['zip']
|
|
9
|
+
|
|
10
|
+
@ref: http://wiki.openstreetmap.org/wiki/Key:postal_code
|
|
11
|
+
**/
|
|
12
|
+
|
|
13
|
+
var OSM_SCHEMA = {
|
|
14
|
+
'postal_code': 'zip'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
module.exports = OSM_SCHEMA;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
Attempt to map OSM TIGER tags to the Pelias address schema.
|
|
4
|
+
|
|
5
|
+
On the left is the OSM tag name, on the right is corresponding
|
|
6
|
+
doc.address key for which it should be mapped to.
|
|
7
|
+
|
|
8
|
+
eg. tags['tiger:zip_left'] -> doc.address_parts['zip']
|
|
9
|
+
|
|
10
|
+
@ref: http://wiki.openstreetmap.org/wiki/TIGER_to_OSM_Attribute_Map
|
|
11
|
+
**/
|
|
12
|
+
|
|
13
|
+
var TIGER_SCHEMA = {
|
|
14
|
+
'tiger:zip_left': 'zip',
|
|
15
|
+
'tiger:zip_right': 'zip'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
module.exports = TIGER_SCHEMA;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
Attempt to map OSM name tags to the Pelias naming schema.
|
|
4
|
+
|
|
5
|
+
On the left is the OSM tag name, on the right is corresponding
|
|
6
|
+
doc.name key for which it should be mapped to.
|
|
7
|
+
|
|
8
|
+
eg. tags['alt_name'] -> doc.name['alternative']
|
|
9
|
+
|
|
10
|
+
The value 'default' is special, it defines the name used by default
|
|
11
|
+
for elasticsearch queries, it has the unique ability to specify multiple
|
|
12
|
+
keys with a value of 'default'.
|
|
13
|
+
|
|
14
|
+
When multiple keys have the value 'default' then they are considered
|
|
15
|
+
as aliases of the default field.
|
|
16
|
+
|
|
17
|
+
The '_primary' property defined below defines which of those aliases
|
|
18
|
+
is considered the 'primary default name' for label generation.
|
|
19
|
+
|
|
20
|
+
No values other than 'default' should be specified more than once.
|
|
21
|
+
|
|
22
|
+
@ref: http://wiki.openstreetmap.org/wiki/Key:name
|
|
23
|
+
@ref: http://wiki.openstreetmap.org/wiki/Names
|
|
24
|
+
**/
|
|
25
|
+
|
|
26
|
+
var OSM_NAMING_SCHEMA = {
|
|
27
|
+
'name': 'default',
|
|
28
|
+
'loc_name': 'default',
|
|
29
|
+
'alt_name': 'default',
|
|
30
|
+
'short_name': 'default',
|
|
31
|
+
|
|
32
|
+
// note: these aliases are currently disabled because they are not being used when querying
|
|
33
|
+
// 'int_name': 'international',
|
|
34
|
+
// 'nat_name': 'national',
|
|
35
|
+
// 'official_name': 'official',
|
|
36
|
+
// 'old_name': 'old',
|
|
37
|
+
// 'reg_name': 'regional',
|
|
38
|
+
// 'sorting_name': 'sorting'
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// this property is considered the 'primary name'
|
|
42
|
+
// for label generation, the others are considered
|
|
43
|
+
// secondary or 'aliases'.
|
|
44
|
+
Object.defineProperty(OSM_NAMING_SCHEMA, '_primary', {
|
|
45
|
+
value: 'name',
|
|
46
|
+
enumerable: false,
|
|
47
|
+
configurable: false,
|
|
48
|
+
writable: false
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
module.exports = OSM_NAMING_SCHEMA;
|
package/schema.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Joi = require('joi');
|
|
4
|
+
|
|
5
|
+
// Schema Configuration
|
|
6
|
+
// datapath: string (required)
|
|
7
|
+
// leveldbpath: string (required)
|
|
8
|
+
// import: array of objects containing filename (optional)
|
|
9
|
+
// importVenues: boolean (optional)
|
|
10
|
+
// download: array of objects containing sourceURL (optional)
|
|
11
|
+
// deduplicate: boolean (optional)
|
|
12
|
+
module.exports = Joi.object().keys({
|
|
13
|
+
imports: Joi.object().keys({
|
|
14
|
+
openstreetmap: Joi.object().keys({
|
|
15
|
+
datapath: Joi.string(),
|
|
16
|
+
leveldbpath: Joi.string(),
|
|
17
|
+
import: Joi.array().items(Joi.object().keys({
|
|
18
|
+
filename: Joi.string(),
|
|
19
|
+
importVenues: Joi.boolean().default(true).truthy('yes').falsy('no').insensitive(true)
|
|
20
|
+
}).requiredKeys('filename').unknown(true)),
|
|
21
|
+
download: Joi.array().items(Joi.object().keys({
|
|
22
|
+
sourceURL: Joi.string()
|
|
23
|
+
}).requiredKeys('sourceURL').unknown(true)),
|
|
24
|
+
deduplicate: Joi.boolean()
|
|
25
|
+
}).requiredKeys('datapath', 'leveldbpath', 'import').unknown(true)
|
|
26
|
+
}).requiredKeys('openstreetmap').unknown(true)
|
|
27
|
+
}).requiredKeys('imports').unknown(true);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
The addendum mapper is responsible for adding interesting metadata
|
|
3
|
+
as an 'addendum' to the record.
|
|
4
|
+
|
|
5
|
+
@see: https://github.com/pelias/api/pull/1255
|
|
6
|
+
**/
|
|
7
|
+
|
|
8
|
+
const through = require('through2');
|
|
9
|
+
const peliasLogger = require('pelias-logger').get('openstreetmap');
|
|
10
|
+
const whitelist = [
|
|
11
|
+
'wheelchair', // Wheelchair accessibility
|
|
12
|
+
'iata', // IATA airport codes
|
|
13
|
+
'icao', // ICAO airport codes
|
|
14
|
+
'wikidata', // Wikidata concordance
|
|
15
|
+
'wikipedia', // Wikipedia concordance
|
|
16
|
+
'operator', // Operator name
|
|
17
|
+
'brand', // Brand name
|
|
18
|
+
'website', // Website URL
|
|
19
|
+
'phone', // Telephone number
|
|
20
|
+
'opening_hours', // Opening hours
|
|
21
|
+
|
|
22
|
+
// COVID-19
|
|
23
|
+
'opening_hours:covid19',
|
|
24
|
+
'delivery:covid19',
|
|
25
|
+
'safety:mask:covid19'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
module.exports = function(){
|
|
29
|
+
|
|
30
|
+
return through.obj(( doc, enc, next ) => {
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
|
|
34
|
+
// skip records with no tags
|
|
35
|
+
let tags = doc.getMeta('tags');
|
|
36
|
+
if( !tags ){
|
|
37
|
+
return next( null, doc );
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let addendum = {};
|
|
41
|
+
|
|
42
|
+
// iterate over mapping
|
|
43
|
+
whitelist.forEach(key => {
|
|
44
|
+
|
|
45
|
+
// check each whitelist key against document tags
|
|
46
|
+
if( !tags.hasOwnProperty( key ) ){ return; }
|
|
47
|
+
|
|
48
|
+
// set addendum key
|
|
49
|
+
let value = tags[key].trim();
|
|
50
|
+
if( !!value.length ){
|
|
51
|
+
addendum[ key ] = value;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// set document addendum
|
|
56
|
+
if( !!Object.keys( addendum ).length ){
|
|
57
|
+
doc.setAddendum( 'osm', addendum );
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
catch( e ){
|
|
62
|
+
peliasLogger.error( 'addendum_mapper error' );
|
|
63
|
+
peliasLogger.error( e.stack );
|
|
64
|
+
peliasLogger.error( JSON.stringify( doc, null, 2 ) );
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return next( null, doc );
|
|
68
|
+
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
The address extractor is responsible for cloning documents where a valid address
|
|
4
|
+
data exists.
|
|
5
|
+
|
|
6
|
+
The hasValidAddress() function examines the address property which was populated
|
|
7
|
+
earier in the pipeline by the osm.tag.mapper stream and therefore MUST come after
|
|
8
|
+
that stream in the pipeline or it will fail to find any address information.
|
|
9
|
+
|
|
10
|
+
There are a few different outcomes for this stream depending on the data contained
|
|
11
|
+
in each individual document, the result can be, 0, 1 or 2 documents being emitted.
|
|
12
|
+
|
|
13
|
+
In the case of the document missing a valid doc.name.default string then it is not
|
|
14
|
+
considered to be a point-of-interest in it's own right, it will be discarded.
|
|
15
|
+
|
|
16
|
+
In the case where the document contains BOTH a valid house number & street name we
|
|
17
|
+
consider this record to be an address in it's own right and we clone that record,
|
|
18
|
+
duplicating the data across to the new doc instance while adjusting it's id and layer.
|
|
19
|
+
|
|
20
|
+
In a rare case it is possible that the record contains neither a valid name nor a valid
|
|
21
|
+
address. If this case in encountered then the parser should be modified so these records
|
|
22
|
+
are no longer passed down the pipeline; as they will simply be discarded because they are
|
|
23
|
+
not searchable.
|
|
24
|
+
**/
|
|
25
|
+
|
|
26
|
+
var through = require('through2');
|
|
27
|
+
var isObject = require('is-object');
|
|
28
|
+
var extend = require('extend');
|
|
29
|
+
var peliasLogger = require( 'pelias-logger' ).get( 'openstreetmap' );
|
|
30
|
+
var Document = require('pelias-model').Document;
|
|
31
|
+
|
|
32
|
+
function hasValidAddress( doc ){
|
|
33
|
+
if( !isObject( doc ) ){ return false; }
|
|
34
|
+
if( !isObject( doc.address_parts ) ){ return false; }
|
|
35
|
+
if( 'string' !== typeof doc.address_parts.number ){ return false; }
|
|
36
|
+
if( 'string' !== typeof doc.address_parts.street ){ return false; }
|
|
37
|
+
if( !doc.address_parts.number.length ){ return false; }
|
|
38
|
+
if( !doc.address_parts.street.length ){ return false; }
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = function(){
|
|
43
|
+
|
|
44
|
+
var stream = through.obj( function( doc, enc, next ) {
|
|
45
|
+
var isNamedPoi = !!doc.getName('default');
|
|
46
|
+
var isAddress = hasValidAddress( doc );
|
|
47
|
+
|
|
48
|
+
// accept semi-colon delimited house numbers
|
|
49
|
+
// ref: https://github.com/pelias/openstreetmap/issues/21
|
|
50
|
+
var streetNumbers = (doc.getAddress('number') || '').split(';').map(Function.prototype.call, String.prototype.trim);
|
|
51
|
+
|
|
52
|
+
// create a new record for street addresses
|
|
53
|
+
if( isAddress ){
|
|
54
|
+
var record;
|
|
55
|
+
|
|
56
|
+
streetNumbers.forEach( function( streetno, i ){
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
var newid = [ doc.getSourceId() ];
|
|
60
|
+
if( i > 0 ){
|
|
61
|
+
newid.push( i );
|
|
62
|
+
peliasLogger.debug('[address_extractor] found multiple house numbers: ', streetNumbers);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// copy data to new document
|
|
66
|
+
record = new Document( 'openstreetmap', 'address', newid.join('/') )
|
|
67
|
+
.setName( 'default', streetno + ' ' + doc.address_parts.street )
|
|
68
|
+
.setCentroid( doc.getCentroid() );
|
|
69
|
+
|
|
70
|
+
// copy all address properties
|
|
71
|
+
setProperties( record, doc );
|
|
72
|
+
|
|
73
|
+
// ensure that only a single address number is used
|
|
74
|
+
record.setAddress('number', streetno);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
catch( e ){
|
|
78
|
+
peliasLogger.error( 'address_extractor error' );
|
|
79
|
+
peliasLogger.error( e.stack );
|
|
80
|
+
peliasLogger.error( JSON.stringify( doc, null, 2 ) );
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if( record !== undefined ){
|
|
84
|
+
// copy meta data (but maintain the id assigned above)
|
|
85
|
+
record._meta = extend( true, {}, doc._meta, { id: record.getId() } );
|
|
86
|
+
this.push( record );
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
peliasLogger.error( '[address_extractor] failed to push address downstream' );
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}, this);
|
|
93
|
+
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// forward doc downstream if it's a POI in its own right
|
|
97
|
+
// note: this MUST be below the address push()
|
|
98
|
+
if( isNamedPoi ){
|
|
99
|
+
|
|
100
|
+
// in the case of multiple house numbers, only use the first one for the venue
|
|
101
|
+
if (streetNumbers.length > 1) {
|
|
102
|
+
doc.setAddress('number', streetNumbers[0]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.push( doc );
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return next();
|
|
109
|
+
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// catch stream errors
|
|
113
|
+
stream.on( 'error', peliasLogger.error.bind( peliasLogger, __filename ) );
|
|
114
|
+
|
|
115
|
+
return stream;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// properties to map from the osm record to the pelias doc
|
|
119
|
+
var addrProps = [ 'name', 'number', 'street', 'zip' ];
|
|
120
|
+
|
|
121
|
+
// call document setters and ignore non-fatal errors
|
|
122
|
+
function setProperties( record, doc ){
|
|
123
|
+
addrProps.forEach( function ( prop ){
|
|
124
|
+
try {
|
|
125
|
+
record.setAddress( prop, doc.getAddress( prop ) );
|
|
126
|
+
} catch ( ex ) {}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// export for testing
|
|
131
|
+
module.exports.hasValidAddress = hasValidAddress;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
The category mapper is responsible for checking each documents tags
|
|
4
|
+
against a predefined taxonomy mapping file.
|
|
5
|
+
|
|
6
|
+
For each openstreetmap 'feature' that matches the mapping file we
|
|
7
|
+
create a new document 'category' by calling `doc.addCategory( category );`.
|
|
8
|
+
|
|
9
|
+
This idea is to classify places-of-interest in order to facilitate the
|
|
10
|
+
building of industry and interest specific geocoders.
|
|
11
|
+
|
|
12
|
+
@see: https://github.com/pelias/pelias/wiki/Taxonomy-v1
|
|
13
|
+
@see: ./config/category_map.js
|
|
14
|
+
**/
|
|
15
|
+
|
|
16
|
+
const _ = require('lodash');
|
|
17
|
+
const through = require('through2');
|
|
18
|
+
const peliasLogger = require('pelias-logger').get('openstreetmap');
|
|
19
|
+
|
|
20
|
+
module.exports = function( mapping ){
|
|
21
|
+
|
|
22
|
+
return through.obj( function( doc, enc, next ){
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
|
|
26
|
+
// do not categorize addresses
|
|
27
|
+
if( doc.getLayer().match('address') ){
|
|
28
|
+
return next( null, doc );
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// skip records with no tags
|
|
32
|
+
var tags = doc.getMeta('tags');
|
|
33
|
+
if( !tags ){
|
|
34
|
+
return next( null, doc );
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// iterate over mapping
|
|
38
|
+
_.each(mapping, (osmTagValues, osmTagKey) => {
|
|
39
|
+
|
|
40
|
+
// check each mapping key against document tags
|
|
41
|
+
if( !tags.hasOwnProperty( osmTagKey ) ){ return; }
|
|
42
|
+
|
|
43
|
+
// handle mapping wildcards
|
|
44
|
+
if( osmTagValues.hasOwnProperty('*') ){
|
|
45
|
+
addCategories( doc, osmTagValues['*'] );
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// handle regular features
|
|
49
|
+
_.each(osmTagValues, (categories, osmTagValue) => {
|
|
50
|
+
if( '*' === osmTagValue ){ return; }
|
|
51
|
+
if( tags[osmTagKey] === osmTagValue ){
|
|
52
|
+
addCategories( doc, categories );
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
catch( e ){
|
|
59
|
+
peliasLogger.error( 'category_mapper error' );
|
|
60
|
+
peliasLogger.error( e.stack );
|
|
61
|
+
peliasLogger.error( JSON.stringify( doc, null, 2 ) );
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return next( null, doc );
|
|
65
|
+
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function addCategories( doc, categories ){
|
|
71
|
+
if( !Array.isArray( categories ) ){
|
|
72
|
+
categories = [categories];
|
|
73
|
+
}
|
|
74
|
+
categories.forEach( function( category ){
|
|
75
|
+
doc.addCategory( category );
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
The document constructor is responsible for mapping input data from the parser
|
|
4
|
+
in to model.Document() objects which the rest of the pipeline expect to consume.
|
|
5
|
+
**/
|
|
6
|
+
|
|
7
|
+
const through = require('through2');
|
|
8
|
+
const Document = require('pelias-model').Document;
|
|
9
|
+
const peliasLogger = require( 'pelias-logger' ).get( 'openstreetmap' );
|
|
10
|
+
const _ = require('lodash');
|
|
11
|
+
|
|
12
|
+
module.exports = function(){
|
|
13
|
+
|
|
14
|
+
var stream = through.obj( function( item, enc, next ) {
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
if (!item.type || ! item.id) {
|
|
18
|
+
throw new Error('doc without valid id or type');
|
|
19
|
+
}
|
|
20
|
+
var uniqueId = [ item.type, item.id ].join('/');
|
|
21
|
+
|
|
22
|
+
// we need to assume it will be a venue and later if it turns out to be an address it will get changed
|
|
23
|
+
var doc = new Document( 'openstreetmap', 'venue', uniqueId );
|
|
24
|
+
|
|
25
|
+
// Set latitude / longitude
|
|
26
|
+
if( item.hasOwnProperty('lat') && item.hasOwnProperty('lon') ){
|
|
27
|
+
doc.setCentroid({
|
|
28
|
+
lat: item.lat,
|
|
29
|
+
lon: item.lon
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Set latitude / longitude (for ways where the centroid has been precomputed)
|
|
34
|
+
else if( item.hasOwnProperty('centroid') ){
|
|
35
|
+
if( item.centroid.hasOwnProperty('lat') && item.centroid.hasOwnProperty('lon') ){
|
|
36
|
+
doc.setCentroid({
|
|
37
|
+
lat: item.centroid.lat,
|
|
38
|
+
lon: item.centroid.lon
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// set bounding box
|
|
44
|
+
if( _.isPlainObject(item.bounds) ){
|
|
45
|
+
doc.setBoundingBox({
|
|
46
|
+
upperLeft: {
|
|
47
|
+
lat: parseFloat(item.bounds.n),
|
|
48
|
+
lon: parseFloat(item.bounds.w)
|
|
49
|
+
},
|
|
50
|
+
lowerRight: {
|
|
51
|
+
lat: parseFloat(item.bounds.s),
|
|
52
|
+
lon: parseFloat(item.bounds.e)
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Store osm tags as a property inside _meta
|
|
58
|
+
doc.setMeta( 'tags', item.tags || {} );
|
|
59
|
+
|
|
60
|
+
// Push instance of Document downstream
|
|
61
|
+
this.push( doc );
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
catch( e ){
|
|
65
|
+
peliasLogger.error( 'error constructing document model', e.stack );
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return next();
|
|
69
|
+
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// catch stream errors
|
|
73
|
+
stream.on( 'error', peliasLogger.error.bind( peliasLogger, __filename ) );
|
|
74
|
+
|
|
75
|
+
return stream;
|
|
76
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
var categoryDefaults = require('../config/category_map');
|
|
2
|
+
|
|
3
|
+
var streams = {};
|
|
4
|
+
|
|
5
|
+
streams.config = {
|
|
6
|
+
categoryDefaults: categoryDefaults
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
streams.pbfParser = require('./multiple_pbfs').create;
|
|
10
|
+
streams.docConstructor = require('./document_constructor');
|
|
11
|
+
streams.blacklistStream = require('pelias-blacklist-stream');
|
|
12
|
+
streams.tagMapper = require('./tag_mapper');
|
|
13
|
+
streams.adminLookup = require('pelias-wof-admin-lookup').create;
|
|
14
|
+
streams.addressExtractor = require('./address_extractor');
|
|
15
|
+
streams.categoryMapper = require('./category_mapper');
|
|
16
|
+
streams.addendumMapper = require('./addendum_mapper');
|
|
17
|
+
streams.popularityMapper = require('./popularity_mapper');
|
|
18
|
+
streams.dbMapper = require('pelias-model').createDocumentMapperStream;
|
|
19
|
+
streams.elasticsearch = require('pelias-dbclient');
|
|
20
|
+
|
|
21
|
+
// default import pipeline
|
|
22
|
+
streams.import = function(){
|
|
23
|
+
streams.pbfParser()
|
|
24
|
+
.pipe( streams.docConstructor() )
|
|
25
|
+
.pipe( streams.tagMapper() )
|
|
26
|
+
.pipe( streams.addressExtractor() )
|
|
27
|
+
.pipe( streams.blacklistStream() )
|
|
28
|
+
.pipe( streams.categoryMapper( categoryDefaults ) )
|
|
29
|
+
.pipe( streams.addendumMapper() )
|
|
30
|
+
.pipe( streams.popularityMapper() )
|
|
31
|
+
.pipe( streams.adminLookup() )
|
|
32
|
+
.pipe( streams.dbMapper() )
|
|
33
|
+
.pipe( streams.elasticsearch({name: 'openstreetmap'}) );
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
module.exports = streams;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
var combinedStream = require('combined-stream');
|
|
2
|
+
var pbf = require('./pbf');
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var logger = require('pelias-logger').get('openstreetmap');
|
|
5
|
+
|
|
6
|
+
function createCombinedStream(){
|
|
7
|
+
var fullStream = combinedStream.create();
|
|
8
|
+
var defaultPath= require('pelias-config').generate().imports.openstreetmap;
|
|
9
|
+
|
|
10
|
+
defaultPath.import.forEach(function( importObject){
|
|
11
|
+
var conf = {
|
|
12
|
+
file: path.join(defaultPath.datapath, importObject.filename),
|
|
13
|
+
leveldb: defaultPath.leveldbpath,
|
|
14
|
+
importVenues: importObject.importVenues
|
|
15
|
+
};
|
|
16
|
+
fullStream.append(function(next){
|
|
17
|
+
logger.info('Creating read stream for: ' + conf.file);
|
|
18
|
+
next(pbf.parser(conf));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return fullStream;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports.create = createCombinedStream;
|