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/stream/pbf.js ADDED
@@ -0,0 +1,75 @@
1
+
2
+ /**
3
+ The pbf parser is responsible for configuring and executing a pbf parser and
4
+ returning a valid readable stream.
5
+ **/
6
+
7
+ var fs = require('fs'),
8
+ pbf2json = require('pbf2json'),
9
+ settings = require('pelias-config').generate(require('../schema')),
10
+ features = require('../config/features'),
11
+ path = require('path');
12
+
13
+ // Create a new pbf parser stream
14
+ function createPbfStream(opts){
15
+
16
+ var conf = config(opts);
17
+
18
+ validatePath( conf.file, 'failed to stat pbf file: ' + conf.file );
19
+ validatePath( conf.leveldb, 'failed to stat leveldb path: ' + conf.leveldb );
20
+ validateTags( conf.tags );
21
+
22
+ return pbf2json.createReadStream(conf);
23
+ }
24
+
25
+ // Generate configuration options for pbf2json, apply default
26
+ // configurations where not explicitly specified.
27
+ function config(opts){
28
+ if (!opts){
29
+ opts = {};
30
+ }
31
+
32
+ // Use datapath setting from your config file
33
+ // @see: github://pelias/config for more info.
34
+ if(!opts.file){
35
+ var filename = settings.imports.openstreetmap.import[0].filename;
36
+ opts.file = path.join(settings.imports.openstreetmap.datapath, filename);
37
+ }
38
+
39
+ // Use leveldb setting from your config file
40
+ // @see: github://pelias/config for more info.
41
+ if(!opts.leveldb){
42
+ opts.leveldb = settings.imports.openstreetmap.leveldbpath;
43
+ }
44
+
45
+ // Use default parser tags
46
+ if(!opts.tags){
47
+ // check if we import venues
48
+ opts.importVenues = settings.imports.openstreetmap.import[0].importVenues;
49
+ if(opts.importVenues){
50
+ opts.tags = features.tags.concat(features.venue_tags);
51
+ } else {
52
+ opts.tags = features.tags;
53
+ }
54
+ }
55
+ return opts;
56
+ }
57
+
58
+ // Check path exists
59
+ function validatePath( path, message ){
60
+ try {
61
+ fs.statSync( path );
62
+ } catch( e ){
63
+ throw new Error( message );
64
+ }
65
+ }
66
+
67
+ // Validate the tag list
68
+ function validateTags( tags ){
69
+ if( !Array.isArray(tags) || !tags.length ) {
70
+ throw new Error( 'invalid tags' );
71
+ }
72
+ }
73
+
74
+ module.exports.config = config;
75
+ module.exports.parser = createPbfStream;
@@ -0,0 +1,80 @@
1
+ /**
2
+ The popularity mapper is responsible for generating a 'popularity'
3
+ value by inspecting OSM tags.
4
+
5
+ Disused and abandoned places are given a strong negative score.
6
+ If the popularity score is less than zero then the document is discarded.
7
+
8
+ Feel free to make changes to this mapping file!
9
+ **/
10
+
11
+ const _ = require('lodash');
12
+ const through = require('through2');
13
+ const peliasLogger = require('pelias-logger').get('openstreetmap');
14
+ const peliasConfig = require('pelias-config').generate();
15
+ const mapping = require('../config/popularity_map');
16
+
17
+ module.exports = function(){
18
+
19
+ return through.obj(( doc, enc, next ) => {
20
+
21
+ try {
22
+
23
+ // only map venues
24
+ if( !['venue', 'address'].includes(doc.getLayer()) ){
25
+ return next(null, doc);
26
+ }
27
+
28
+ // skip records with no tags
29
+ let tags = doc.getMeta('tags');
30
+ if( !tags ){
31
+ return next( null, doc );
32
+ }
33
+
34
+ // default popularity
35
+ let popularity = doc.getPopularity() || 0;
36
+
37
+ // apply scores from config
38
+ _.each(mapping, (osmTagScores, osmTagKey) => {
39
+ if( tags.hasOwnProperty( osmTagKey ) ){
40
+ // global score for the tag
41
+ if( osmTagScores._score ){
42
+ popularity += osmTagScores._score;
43
+ }
44
+ // individual scores for specific values
45
+ _.each(osmTagScores, (osmSubTagScores, osmTagValue) => {
46
+ if( osmTagValue === '_score' ){ return; }
47
+ if( !osmSubTagScores._score ){ return; }
48
+ if (_.get(tags, osmTagKey, '').trim().toLowerCase() === osmTagValue ){
49
+ popularity += osmSubTagScores._score;
50
+ }
51
+ });
52
+ }
53
+ });
54
+
55
+ // addresses with a popularity score GTE 10000 receieve
56
+ // a popularity of 1000, all others get a popularity of 0.
57
+ if ( doc.getLayer() === 'address' ){
58
+ popularity = (popularity >= 10000) ? 1000 : 0;
59
+ }
60
+
61
+ // set document popularity if it is greater than zero
62
+ if( popularity > 0 ){ doc.setPopularity( popularity ); }
63
+
64
+ // discard places with a negative popularity
65
+ else if( popularity < 0 && peliasConfig.get('imports.openstreetmap.removeDisusedVenues') === true ){
66
+ peliasLogger.warn(`removing record ${doc.getGid()} (${doc.getName('default')}) with popularity ${popularity}`);
67
+ return next();
68
+ }
69
+ }
70
+
71
+ catch( e ){
72
+ peliasLogger.error( 'popularity_mapper error' );
73
+ peliasLogger.error( e.stack );
74
+ peliasLogger.error( JSON.stringify( doc, null, 2 ) );
75
+ }
76
+
77
+ return next( null, doc );
78
+ });
79
+
80
+ };
@@ -0,0 +1,47 @@
1
+
2
+ /**
3
+ The stats module is useful for recording stats while developing new streams.
4
+
5
+ @usage:
6
+
7
+ tap.pipe( stream1 )
8
+ .pipe( stats.proxy('stream1 -> stream2') )
9
+ .pipe( stream2 )
10
+ .pipe( stats.proxy('stream2 -> stream3') )
11
+ .pipe( stream3 )
12
+ .pipe( stats.proxy('stream3 -> sink') )
13
+ .pipe( sink )
14
+
15
+ you can then get throughput statistics in your terminal to identify bottlenecks:
16
+
17
+ var peliasLogger = require( 'pelias-logger' ).get( 'openstreetmap' );
18
+ setInterval( peliasLogger.log.bind( peliasLogger, stats.metrics ), 1000 );
19
+ **/
20
+
21
+ var through = require('through2');
22
+
23
+ function Stats(){
24
+ this.reset();
25
+ }
26
+
27
+ Stats.prototype.reset = function(){
28
+ this.metrics = {};
29
+ };
30
+
31
+ Stats.prototype.proxy = function( title ){
32
+ if( !this.metrics.hasOwnProperty( title ) ){
33
+ this.metrics[ title ] = 0;
34
+ }
35
+
36
+ var self = this;
37
+
38
+ var proxy = through.obj( function( item, enc, next ) {
39
+ self.metrics[ title ]++;
40
+ this.push( item );
41
+ return next();
42
+ });
43
+
44
+ return proxy;
45
+ };
46
+
47
+ module.exports = new Stats();
@@ -0,0 +1,9 @@
1
+ var through = require('through2');
2
+
3
+ // convenience function for converting object streams back to strings
4
+ var stringify = through.obj( function( data, enc, next ){
5
+ this.push( JSON.stringify( data, null, 2 ), 'utf-8' );
6
+ next();
7
+ });
8
+
9
+ module.exports = stringify;
@@ -0,0 +1,168 @@
1
+
2
+ /**
3
+ The tag mapper is responsible for mapping OSM tag information in to the
4
+ document model, using a variety of different schemas found in /schema/*.
5
+ **/
6
+
7
+ const _ = require('lodash');
8
+ const through = require('through2');
9
+ const peliasLogger = require('pelias-logger').get('openstreetmap');
10
+
11
+ var LOCALIZED_NAME_KEYS = require('../config/localized_name_keys');
12
+ var NAME_SCHEMA = require('../schema/name_osm');
13
+ var ADDRESS_SCHEMA = _.merge( {},
14
+ require('../schema/address_tiger'),
15
+ require('../schema/address_osm'),
16
+ require('../schema/address_naptan'),
17
+ require('../schema/address_karlsruhe')
18
+ );
19
+
20
+ module.exports = function(){
21
+
22
+ var stream = through.obj( function( doc, enc, next ) {
23
+
24
+ try {
25
+
26
+ // skip records with no tags
27
+ var tags = doc.getMeta('tags');
28
+ if( !tags ){
29
+ return next( null, doc );
30
+ }
31
+
32
+ // Unfortunately we need to iterate over every tag,
33
+ // so we only do the iteration once to save CPU.
34
+ _.each(tags, (value, key) => {
35
+
36
+ // Map localized names which begin with 'name:'
37
+ // @ref: http://wiki.openstreetmap.org/wiki/Namespace#Language_code_suffix
38
+ var suffix = getNameSuffix( key );
39
+ if( suffix ){
40
+ var val1 = trim( value );
41
+ if( val1 ){
42
+ doc.setName( suffix, val1 );
43
+ }
44
+ }
45
+
46
+ // Map name data from our name mapping schema
47
+ else if( _.has(NAME_SCHEMA, key) ){
48
+ var val2 = trim( value );
49
+ if( val2 ){
50
+ if( key === NAME_SCHEMA._primary ){
51
+ doc.setName( NAME_SCHEMA[key], val2 );
52
+ } else if ( 'default' === NAME_SCHEMA[key] ) {
53
+ doc.setNameAlias( NAME_SCHEMA[key], val2 );
54
+ } else {
55
+ doc.setName( NAME_SCHEMA[key], val2 );
56
+ }
57
+ }
58
+ }
59
+
60
+ // Map address data from our address mapping schema
61
+ else if( _.has(ADDRESS_SCHEMA, key) ){
62
+ var val3 = trim( value );
63
+ if( val3 ){
64
+ let label = ADDRESS_SCHEMA[key];
65
+ doc.setAddress(label, normalizeAddressField(label, val3));
66
+ }
67
+ }
68
+ });
69
+
70
+ // Handle the case where no default name was set but there were
71
+ // other names which we could use as the default.
72
+ if( !doc.getName('default') ){
73
+
74
+ var defaultName =
75
+ _.get(tags, 'official_name') ||
76
+ _.get(tags, 'int_name') ||
77
+ _.get(tags, 'nat_name') ||
78
+ _.get(tags, 'reg_name') ||
79
+ doc.getName('en');
80
+
81
+ // use one of the preferred name tags listed above
82
+ if ( defaultName ){
83
+ doc.setName('default', defaultName);
84
+ }
85
+
86
+ // else try to use an available two-letter language name tag
87
+ else {
88
+ var keys = Object.keys(doc.name).filter(n => n.length === 2);
89
+
90
+ // unambiguous (there is only a single two-letter name tag)
91
+ if ( keys.length === 1 ){
92
+ doc.setName('default', doc.getName(keys[0]));
93
+ }
94
+
95
+ // note we do not handle ambiguous cases where the record contains >1
96
+ // two-letter name tags.
97
+ // see: https://github.com/pelias/openstreetmap/pull/498
98
+ }
99
+ }
100
+
101
+ // Import airport codes as aliases
102
+ if( tags.hasOwnProperty('aerodrome') || tags.hasOwnProperty('aeroway') ){
103
+ if( tags.hasOwnProperty('iata') ){
104
+ var iata = trim( tags.iata );
105
+ if( iata ){
106
+ doc.setNameAlias( 'default', iata );
107
+ doc.setNameAlias( 'default', `${iata} Airport` );
108
+ }
109
+ }
110
+ }
111
+ }
112
+
113
+ catch( e ){
114
+ peliasLogger.error( 'tag_mapper error' );
115
+ peliasLogger.error( e.stack );
116
+ peliasLogger.error( JSON.stringify( doc, null, 2 ) );
117
+ }
118
+
119
+ return next( null, doc );
120
+
121
+ });
122
+
123
+ // catch stream errors
124
+ stream.on( 'error', peliasLogger.error.bind( peliasLogger, __filename ) );
125
+
126
+ return stream;
127
+ };
128
+
129
+ // Clean string of leading/trailing junk chars
130
+ function trim( str ){
131
+ return _.trim( str, '#$%^*<>-=_{};:",./?\t\n\' ' );
132
+ }
133
+
134
+ // extract name suffix, eg for 'name:EN' return 'en'
135
+ // if not valid, return false.
136
+ function getNameSuffix( tag ){
137
+
138
+ if( tag.length < 6 || tag.substr(0,5) !== 'name:' ){
139
+ return false;
140
+ }
141
+
142
+ // normalize suffix
143
+ var suffix = tag.substr(5).toLowerCase();
144
+
145
+ // check the suffix is in the localized key list
146
+ if( LOCALIZED_NAME_KEYS.indexOf(suffix) === -1 ){
147
+ return false;
148
+ }
149
+
150
+ return suffix;
151
+ }
152
+
153
+ // apply some very basic normalization
154
+ // note: in the future we should consider doing something more advanced like:
155
+ // https://github.com/pelias/openaddresses/pull/477
156
+ function normalizeAddressField(key, value) {
157
+ if (key === 'street') {
158
+
159
+ // contract English diagonals
160
+ value = value
161
+ .replace(/\b(northwest)\b/i, 'NW')
162
+ .replace(/\b(northeast)\b/i, 'NE')
163
+ .replace(/\b(southwest)\b/i, 'SW')
164
+ .replace(/\b(southeast)\b/i, 'SE');
165
+ }
166
+
167
+ return value;
168
+ }
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const async = require('async');
5
+ const child_process = require('child_process');
6
+ const fs = require('fs');
7
+ const config = require( 'pelias-config' ).generate(require('../schema'));
8
+ const logger = require('pelias-logger').get('openstreetmap-download');
9
+
10
+ if (require.main === module) {
11
+ download((err) => {
12
+ if (err) {
13
+ logger.error('Failed to download data', err);
14
+ process.exit(1);
15
+ }
16
+ logger.info('All done!');
17
+ });
18
+ }
19
+
20
+ function download(callback) {
21
+ let sources;
22
+
23
+ // if no download sources are specified, default to the planet file
24
+ if (_.isEmpty(config.imports.openstreetmap.download)) {
25
+ sources = [
26
+ 'https://planet.openstreetmap.org/pbf/planet-latest.osm.pbf'
27
+ ];
28
+ }
29
+ else {
30
+ sources = _.map(config.imports.openstreetmap.download, (source) => source.sourceURL);
31
+ }
32
+
33
+ logger.info(`Downloading sources: ${sources}`);
34
+
35
+ fs.mkdir(config.imports.openstreetmap.datapath, { recursive: true }, (err) => {
36
+ if (err) {
37
+ logger.error(`error making directory ${config.imports.openstreetmap.datapath}`, err);
38
+ return callback(err);
39
+ }
40
+
41
+ async.forEach(
42
+ sources,
43
+ (source, next) => {
44
+ downloadSource(config, source, next);
45
+ },
46
+ callback
47
+ );
48
+ });
49
+ }
50
+
51
+ function downloadSource(config, sourceUrl, callback) {
52
+ const targetDir = config.imports.openstreetmap.datapath;
53
+
54
+ logger.debug(`downloading ${sourceUrl} to ${targetDir}`);
55
+ child_process.exec(`cd ${targetDir} && { curl -L -X GET --silent --fail --remote-name ${sourceUrl}; }`, callback);
56
+ }
57
+
58
+ module.exports = download;
@@ -0,0 +1,8 @@
1
+ var through = require('through2');
2
+
3
+ function sendPassthroughStream() {
4
+ return through.obj(function (doc, enc, next) {
5
+ next(null, doc);
6
+ });
7
+ }
8
+ module.exports = sendPassthroughStream;