pelias-openstreetmap 8.3.0 → 8.3.2

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.
@@ -6,12 +6,12 @@ jobs:
6
6
  strategy:
7
7
  matrix:
8
8
  os:
9
- - ubuntu-22.04
9
+ - ubuntu-24.04
10
10
  node-version: [ 20.x, 22.x, 24.x ]
11
11
  steps:
12
- - uses: actions/checkout@v4
12
+ - uses: actions/checkout@v6
13
13
  - name: 'Install node.js ${{ matrix.node-version }}'
14
- uses: actions/setup-node@v4
14
+ uses: actions/setup-node@v6
15
15
  with:
16
16
  node-version: '${{ matrix.node-version }}'
17
17
  - name: Run unit tests
@@ -6,19 +6,21 @@ jobs:
6
6
  npm-publish:
7
7
  needs: unit-tests
8
8
  if: github.ref == 'refs/heads/master' && needs.unit-tests.result == 'success'
9
- runs-on: ubuntu-22.04
9
+ runs-on: ubuntu-24.04
10
+ permissions:
11
+ id-token: write
12
+ contents: write
10
13
  steps:
11
- - uses: actions/checkout@v4
14
+ - uses: actions/checkout@v6
12
15
  - name: Install Node.js
13
- uses: actions/setup-node@v4
16
+ uses: actions/setup-node@v6
14
17
  with:
15
- node-version: 20.x
18
+ node-version: 22.x
16
19
  - name: Run semantic-release
17
20
  env:
18
- GH_TOKEN: ${{ secrets.GH_SEMANTIC_RELEASE_TOKEN }}
19
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
21
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20
22
  run: >
21
- if [[ -n "$GH_TOKEN" && -n "$NPM_TOKEN" ]]; then
23
+ if [[ "${{ github.repository_owner }}" == "pelias" ]]; then
22
24
  curl "https://raw.githubusercontent.com/pelias/ci-tools/master/semantic-release.sh" | bash -
23
25
  fi
24
26
  build-docker-images:
@@ -26,9 +28,9 @@ jobs:
26
28
  # note: github actions won't run a job if you don't call one of the status check functions, so `always()` is called since it evalutes to `true`
27
29
  if: ${{ always() && needs.unit-tests.result == 'success' && (needs.npm-publish.result == 'success' || needs.npm-publish.result == 'skipped') }}
28
30
  needs: [unit-tests, npm-publish]
29
- runs-on: ubuntu-22.04
31
+ runs-on: ubuntu-24.04
30
32
  steps:
31
- - uses: actions/checkout@v4
33
+ - uses: actions/checkout@v6
32
34
  - name: Build Docker images
33
35
  env:
34
36
  DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
package/README.md CHANGED
@@ -94,6 +94,24 @@ The OSM importer will look for a file with a name matching this value in the con
94
94
 
95
95
  If downloading from a remote URL, the filename must match the value in `sourceURL`.
96
96
 
97
+ #### `imports.openstreetmap.layers`
98
+
99
+ This is an object you can use to define your own layers based on OSM tags. For example, if you wanted a layer `coffee` for just coffee shops you could set:
100
+
101
+ ```
102
+ {
103
+ "imports": {
104
+ "openstreetmap": {
105
+ "layers": {
106
+ "coffee": {
107
+ "tags": [ "amenity~cafe+name" ]
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
97
115
  #### `imports.openstreetmap.leveldbpath`
98
116
 
99
117
  This is the directory where temporary files will be stored in order to
@@ -106,6 +124,8 @@ Defaults to `tmp`.
106
124
 
107
125
  By default, the OSM importer imports both venue records and addresses. If set to false, only address records will be imported.
108
126
 
127
+ You can also disable venue import by setting `imports.openstreetmap.layers.venue` to an empty value.
128
+
109
129
  #### `imports.openstreetmap.removeDisusedVenues`
110
130
 
111
131
  If set to boolean `true`, `venue`s with popularity below `0` (as determined by OSM tags) will be
@@ -2,52 +2,60 @@
2
2
  /**
3
3
  default list of tags to extract from the pbf file when running
4
4
  imports. @see: https://github.com/pelias/pbf2json for more info.
5
- **/
6
5
 
7
- // default tags imported
8
- const tags = [
9
- 'addr:housenumber+addr:street',
10
- 'addr:housenumber+addr:place' // @ref https://github.com/pelias/pelias/issues/787#issuecomment-477137803
11
- ];
6
+ addressTags: extracted so address_extractor can assign the address layer downstream.
7
+ these are not a layer themselves — address classification is structural,
8
+ not tag-based.
12
9
 
13
- // tags corresponding to venues
14
- const venue_tags = [
15
- 'amenity+name',
16
- 'building+name',
17
- 'shop+name',
18
- 'office+name',
19
- 'public_transport+name',
20
- 'cuisine+name',
21
- 'railway~station+name',
22
- 'railway~tram_stop+name',
23
- 'railway~halt+name',
24
- 'railway~subway_entrance+name',
25
- 'railway~train_station_entrance+name',
26
- 'highway~pedestrian+area~yes+name',
27
- 'place~square+name',
28
- 'sport+name',
29
- 'natural+name',
30
- 'tourism+name',
31
- 'leisure+name',
32
- 'historic+name',
33
- 'man_made+name',
34
- 'landuse+name',
35
- 'waterway+name',
36
- 'aerialway+name',
37
- 'craft+name',
38
- 'military+name',
39
- 'aeroway~terminal+name',
40
- 'aeroway~aerodrome+name',
41
- 'aeroway~helipad+name',
42
- 'aeroway~airstrip+name',
43
- 'aeroway~heliport+name',
44
- 'aeroway~areodrome+name',
45
- 'aeroway~spaceport+name',
46
- 'aeroway~landing_strip+name',
47
- 'aeroway~airfield+name',
48
- 'aeroway~airport+name',
49
- 'brand+name',
50
- 'healthcare+name'
51
- ];
10
+ layers: keyed by output layer name. order determines classifier priority (first match wins).
11
+ add custom layers here (e.g. river, protected_area) to extend the importer.
12
+ **/
52
13
 
53
- module.exports = {tags,venue_tags};
14
+ module.exports = {
15
+ addressTags: [
16
+ 'addr:housenumber+addr:street',
17
+ 'addr:housenumber+addr:place' // @ref https://github.com/pelias/pelias/issues/787#issuecomment-477137803
18
+ ],
19
+ layers: {
20
+ venue: {
21
+ tags: [
22
+ 'amenity+name',
23
+ 'building+name',
24
+ 'shop+name',
25
+ 'office+name',
26
+ 'public_transport+name',
27
+ 'cuisine+name',
28
+ 'railway~station+name',
29
+ 'railway~tram_stop+name',
30
+ 'railway~halt+name',
31
+ 'railway~subway_entrance+name',
32
+ 'railway~train_station_entrance+name',
33
+ 'highway~pedestrian+area~yes+name',
34
+ 'place~square+name',
35
+ 'sport+name',
36
+ 'natural+name',
37
+ 'tourism+name',
38
+ 'leisure+name',
39
+ 'historic+name',
40
+ 'man_made+name',
41
+ 'landuse+name',
42
+ 'waterway+name',
43
+ 'aerialway+name',
44
+ 'craft+name',
45
+ 'military+name',
46
+ 'aeroway~terminal+name',
47
+ 'aeroway~aerodrome+name',
48
+ 'aeroway~helipad+name',
49
+ 'aeroway~airstrip+name',
50
+ 'aeroway~heliport+name',
51
+ 'aeroway~areodrome+name',
52
+ 'aeroway~spaceport+name',
53
+ 'aeroway~landing_strip+name',
54
+ 'aeroway~airfield+name',
55
+ 'aeroway~airport+name',
56
+ 'brand+name',
57
+ 'healthcare+name'
58
+ ]
59
+ }
60
+ }
61
+ };
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pelias-openstreetmap",
3
- "version": "8.3.0",
3
+ "version": "8.3.2",
4
4
  "engines": {
5
5
  "node": ">=10.0.0"
6
6
  },
7
7
  "author": "pelias",
8
8
  "description": "Pelias openstreetmap utilities",
9
- "homepage": "https://github.com/mapzen/pelias-openstreetmap",
9
+ "homepage": "https://github.com/pelias/openstreetmap",
10
10
  "license": "MIT",
11
11
  "main": "index.js",
12
12
  "scripts": {
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "https://github.com/mapzen/pelias-openstreetmap.git"
25
+ "url": "https://github.com/pelias/openstreetmap.git"
26
26
  },
27
27
  "keywords": [
28
28
  "pelias",
@@ -31,7 +31,7 @@
31
31
  "elasticsearch"
32
32
  ],
33
33
  "bugs": {
34
- "url": "https://github.com/mapzen/pelias-openstreetmap/issues"
34
+ "url": "https://github.com/pelias/openstreetmap/issues"
35
35
  },
36
36
  "dependencies": {
37
37
  "async": "^3.0.1",
package/schema.js CHANGED
@@ -21,7 +21,14 @@ module.exports = Joi.object().keys({
21
21
  download: Joi.array().items(Joi.object().keys({
22
22
  sourceURL: Joi.string()
23
23
  }).requiredKeys('sourceURL').unknown(true)),
24
- deduplicate: Joi.boolean()
24
+ deduplicate: Joi.boolean(),
25
+ addressTags: Joi.array().items(Joi.string()),
26
+ layers: Joi.object().pattern(
27
+ Joi.string(),
28
+ Joi.object().keys({
29
+ tags: Joi.array().items(Joi.string())
30
+ })
31
+ )
25
32
  }).requiredKeys('datapath', 'leveldbpath', 'import').unknown(true)
26
33
  }).requiredKeys('openstreetmap').unknown(true)
27
34
  }).requiredKeys('imports').unknown(true);
@@ -8,6 +8,11 @@ const through = require('through2');
8
8
  const Document = require('pelias-model').Document;
9
9
  const peliasLogger = require( 'pelias-logger' ).get( 'openstreetmap' );
10
10
  const _ = require('lodash');
11
+ const settings = require('pelias-config').generate(require('../schema'));
12
+ const resolveFeatures = require('../util/resolveFeatures');
13
+ const { classify } = require('../util/layerClassifier');
14
+
15
+ const { layers } = resolveFeatures(settings);
11
16
 
12
17
  module.exports = function(){
13
18
 
@@ -19,8 +24,10 @@ module.exports = function(){
19
24
  }
20
25
  var uniqueId = [ item.type, item.id ].join('/');
21
26
 
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 );
27
+ // classify the layer from OSM tags; address_extractor assigns the address
28
+ // layer downstream the classifier only covers named layers (venue, etc.)
29
+ var layer = classify(item.tags || {}, layers);
30
+ var doc = new Document( 'openstreetmap', layer, uniqueId );
24
31
 
25
32
  // Set latitude / longitude
26
33
  if( item.hasOwnProperty('lat') && item.hasOwnProperty('lon') ){
@@ -13,6 +13,7 @@ streams.tagMapper = require('./tag_mapper');
13
13
  streams.addressesWithoutStreet = require('./addresses_without_street');
14
14
  streams.adminLookup = require('pelias-wof-admin-lookup').create;
15
15
  streams.addressExtractor = require('./address_extractor');
16
+ streams.venueFilter = require('./venueFilter');
16
17
  streams.categoryMapper = require('./category_mapper');
17
18
  streams.addendumMapper = require('./addendum_mapper');
18
19
  streams.popularityMapper = require('./popularity_mapper');
@@ -26,6 +27,7 @@ streams.import = function(){
26
27
  .pipe( streams.addressesWithoutStreet() )
27
28
  .pipe( streams.tagMapper() )
28
29
  .pipe( streams.addressExtractor() )
30
+ .pipe( streams.venueFilter() )
29
31
  .pipe( streams.blacklistStream() )
30
32
  .pipe( streams.categoryMapper( categoryDefaults ) )
31
33
  .pipe( streams.addendumMapper() )
package/stream/pbf.js CHANGED
@@ -7,7 +7,7 @@
7
7
  var fs = require('fs'),
8
8
  pbf2json = require('pbf2json'),
9
9
  settings = require('pelias-config').generate(require('../schema')),
10
- features = require('../config/features'),
10
+ resolveFeatures = require('../util/resolveFeatures'),
11
11
  path = require('path');
12
12
 
13
13
  // Create a new pbf parser stream
@@ -46,11 +46,11 @@ function config(opts){
46
46
  if(!opts.tags){
47
47
  // check if we import venues
48
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
- }
49
+ const features = resolveFeatures(settings);
50
+ const layerTags = opts.importVenues === false
51
+ ? []
52
+ : Object.values(features.layers).flatMap(l => l.tags ?? []);
53
+ opts.tags = features.addressTags.concat(layerTags);
54
54
  }
55
55
  return opts;
56
56
  }
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * When importVenues is false, drop any record that is not in the address layer.
5
+ * This prevents named POIs that also have address tags from leaking through as
6
+ * venue records when venue import is disabled.
7
+ *
8
+ * Note: the preferred way to control which layers are imported is now to set
9
+ * `imports.openstreetmap.layers` in pelias.json. Setting `importVenues: false`
10
+ * is equivalent to `layers: {}` and is retained for backward compatibility.
11
+ */
12
+
13
+ const through = require('through2');
14
+ const settings = require('pelias-config').generate(require('../schema'));
15
+
16
+ module.exports = function() {
17
+ const importVenues = settings.imports.openstreetmap.import[0].importVenues;
18
+
19
+ // passthrough when venues are enabled (default)
20
+ if (importVenues !== false) {
21
+ return through.obj();
22
+ }
23
+
24
+ return through.obj(function(doc, enc, next) {
25
+ if (doc.getLayer() === 'address') {
26
+ this.push(doc);
27
+ }
28
+ next();
29
+ });
30
+ };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Test a single pbf2json tag condition against an OSM tags object.
5
+ * Supports 'key' (key must exist) or 'key~value' (key must equal value).
6
+ */
7
+ function conditionMatchesTags(condition, tags) {
8
+ const tildeIdx = condition.indexOf('~');
9
+ if (tildeIdx === -1) {
10
+ return Object.prototype.hasOwnProperty.call(tags, condition);
11
+ }
12
+ const key = condition.slice(0, tildeIdx);
13
+ const value = condition.slice(tildeIdx + 1);
14
+ return tags[key] === value;
15
+ }
16
+
17
+ /**
18
+ * Test whether a full pbf2json pattern string matches a tags object.
19
+ * Pattern conditions are separated by '+' and all must match (AND logic).
20
+ */
21
+ function patternMatchesTags(pattern, tags) {
22
+ return pattern.split('+').every(cond => conditionMatchesTags(cond, tags));
23
+ }
24
+
25
+ /**
26
+ * Classify an OSM tags object against the features config.
27
+ * Iterates layers in declaration order and returns the first matching layer.
28
+ * Falls back to 'venue' if no patterns match.
29
+ *
30
+ * @param {Object} tags - OSM tags from a pbf2json record
31
+ * @param {Object} features - the features.js keyed-by-layer export
32
+ * @returns {string} layer name
33
+ */
34
+ function classify(tags, features) {
35
+ for (const [layer, config] of Object.entries(features)) {
36
+ if (Array.isArray(config.tags) && config.tags.some(p => patternMatchesTags(p, tags))) {
37
+ return layer;
38
+ }
39
+ }
40
+ return 'venue';
41
+ }
42
+
43
+ module.exports = { patternMatchesTags, classify };
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const defaults = require('../config/features');
4
+
5
+ /**
6
+ * Resolve the effective features config by merging pelias.json overrides
7
+ * with the defaults from config/features.js.
8
+ *
9
+ * If `imports.openstreetmap.layers` is set in pelias.json it fully replaces
10
+ * the default layer set. Same for `addressTags`. This lets operators add
11
+ * custom layers (e.g. protected_area) or restrict which tags are extracted
12
+ * without touching the source code.
13
+ *
14
+ * @param {Object} settings - result of pelias-config generate()
15
+ * @returns {{ addressTags: string[], layers: Object }}
16
+ */
17
+ function resolveFeatures(settings) {
18
+ const osmConfig = (settings && settings.imports && settings.imports.openstreetmap) || {};
19
+ return {
20
+ addressTags: osmConfig.addressTags || defaults.addressTags,
21
+ layers: osmConfig.layers || defaults.layers
22
+ };
23
+ }
24
+
25
+ module.exports = resolveFeatures;