From 1b4b401a667c0a136e95255c5b54b76cae970c80 Mon Sep 17 00:00:00 2001 From: Diana Shkolnikov Date: Tue, 1 Sep 2015 00:25:12 -0400 Subject: [PATCH] More property renaming/mapping * Rename `layer` to `type` and `source` * Rename `address` block and merge properties to main `geocoding` namespace --- helper/geojsonify.js | 147 ++++++++++++++++++--------- helper/outputGenerator.js | 4 +- helper/outputSchema.json | 12 +-- middleware/renamePlacenames.js | 19 +++- test/unit/helper/geojsonify.js | 169 ++++++++++++++++--------------- test/unit/helper/outputSchema.js | 6 +- 6 files changed, 216 insertions(+), 141 deletions(-) diff --git a/helper/geojsonify.js b/helper/geojsonify.js index d40ab157..757a1e1b 100644 --- a/helper/geojsonify.js +++ b/helper/geojsonify.js @@ -5,70 +5,113 @@ var GeoJSON = require('geojson'), // Properties to be copied when details=true var DETAILS_PROPS = [ - 'alpha3', - 'admin0', - 'admin1', - 'admin1_abbr', - 'admin2', - 'local_admin', - 'locality', - 'neighborhood', + 'housenumber', + 'street', 'category', - 'address', + 'postalcode', 'country_a', 'country', 'region', 'region_a', 'county', + 'localadmin', + 'locality', 'neighbourhood' ]; - -function search( docs, params ){ +var META_MAP = { + 'geoname': { type: '???', source: 'gn' }, // TODO: not sure how to map. will need to use categories? + 'osmnode': { type: 'venue', source: 'osm' }, + 'osmway': { type: 'venue', source: 'osm' }, + 'admin0': { type: 'country', source: 'qs' }, + 'admin1': { type: 'region', source: 'qs' }, + 'admin2': { type: 'county', source: 'qs' }, + 'neighborhood': { type: 'neighbourhood', source: 'qs' }, + 'locality': { type: 'locality', source: 'qs' }, + 'local_admin': { type: 'local_admin', source: 'qs' }, + 'osmaddress': { type: 'address', source: 'osm' }, + 'openaddresses': { type: 'address', source: 'oa' } +}; + +function geojsonifyPlaces( docs, params ){ var details = params ? params.details : {}; details = details === true || details === 1; // flatten & expand data for geojson conversion - var geodata = docs.map( function( doc ) { + var geodata = docs + .map(geojsonifyPlace.bind(null, details)) + .filter( function( doc ){ + return !!doc; + }); - // something went very wrong - if( !doc || !doc.hasOwnProperty( 'center_point' ) ) { - return warning(); - } + // convert to geojson + var geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] }); - var output = {}; + // bounding box calculations + computeBBox(geojson); - // provide metadata to consumer - output.id = doc._id; - output.layer = doc._type; + return geojson; +} - // map center_point - output.lat = parseFloat( doc.center_point.lat ); - output.lng = parseFloat( doc.center_point.lon ); +function geojsonifyPlace(details, place) { - if (details) { - // map name - if( !doc.name || !doc.name.default ) { return warning(); } - output.name = doc.name.default; + // something went very wrong + if( !place || !place.hasOwnProperty( 'center_point' ) ) { + return warning('No doc or center_point property'); + } - copyProperties( doc, DETAILS_PROPS, output ); - } + var geocoding = {}; - // generate region-specific text string - output.text = outputGenerator( doc ); + addMetaData(place, geocoding); + addDetails(details, place, geocoding); + addLabel(place, geocoding); - return output; + var output = {}; - // filter-out invalid entries - }).filter( function( doc ){ - return doc; - }); + output.geocoding = geocoding; + // map center_point for GeoJSON to work properly + // these should not show up in the final feature properties + output.lat = parseFloat(place.center_point.lat); + output.lng = parseFloat(place.center_point.lon); - // convert to geojson - var geojson = GeoJSON.parse( geodata, { Point: ['lat', 'lng'] }); + return output; +} - // bounding box calculations +/** + * Add details properties when needed + * + * @param {boolean} details + * @param {object} src + * @param {object} dst + */ +function addDetails(details, src, dst) { + if (details) { + // map name + if( !src.name || !src.name.default ) { return warning(src); } + dst.name = src.name.default; + + copyProperties(src, DETAILS_PROPS, dst); + } +} + +/** + * Add region-specific label string + * + * @param {object} src + * @param {object} dst + */ +function addLabel(src, dst) { + dst.label = outputGenerator(src); +} + +/** + * Compute bbox that encompasses all features in the result set. + * Set bbox property on the geojson object. + * + * @param {object} geojson + */ +function computeBBox(geojson) { // @note: extent() sometimes throws Errors for unusual data // eg: https://github.com/pelias/pelias/issues/84 try { @@ -80,8 +123,6 @@ function search( docs, params ){ console.error( 'bbox error', e.message, e.stack ); console.error( 'geojson', JSON.stringify( geojson, null, 2 ) ); } - - return geojson; } /** @@ -90,16 +131,30 @@ function search( docs, params ){ * * @param {object} source * @param {[]} props - * @param {object} dest + * @param {object} dst */ -function copyProperties( source, props, dest ) { +function copyProperties( source, props, dst ) { props.forEach( function ( prop ) { if ( source.hasOwnProperty( prop ) ) { - dest[prop] = source[prop]; + dst[prop] = source[prop]; } }); } +/** + * Determine and set place id, type, and source + * + * @param {object} src + * @param {object} dst + */ +function addMetaData(src, dst) { + // lookup mapping, or set both values to _type if not found + var meta = META_MAP[src._type] || { type: src._type, source: src._type }; + + dst.id = src._id; + dst.type = meta.type; + dst.source = meta.source; +} /** * emit a warning if the doc format is invalid @@ -107,9 +162,9 @@ function copyProperties( source, props, dest ) { * @note: if you see this error, fix it ASAP! */ function warning( doc ) { - console.error( 'error: invalid doc', __filename, doc ); + console.error( 'error: invalid doc', __filename, doc); return false; // remove offending doc from results } -module.exports.search = search; \ No newline at end of file +module.exports.search = geojsonifyPlaces; \ No newline at end of file diff --git a/helper/outputGenerator.js b/helper/outputGenerator.js index 53ae1fb1..d8b47f0d 100644 --- a/helper/outputGenerator.js +++ b/helper/outputGenerator.js @@ -7,8 +7,8 @@ module.exports = function( record ){ var schema = schemas.default; - if (record.alpha3 && record.alpha3.length && schemas[record.alpha3]) { - schema = schemas[record.alpha3]; + if (record.country_a && record.country_a.length && schemas[record.country_a]) { + schema = schemas[record.country_a]; } var buildOutput = function(parts, schemaArr, record) { diff --git a/helper/outputSchema.json b/helper/outputSchema.json index f7a7540e..560d12e9 100644 --- a/helper/outputSchema.json +++ b/helper/outputSchema.json @@ -1,14 +1,14 @@ { "USA": { - "local": ["local_admin", "locality", "neighborhood", "admin2"], - "regional": ["admin1_abbr", "admin1", "admin0"] + "local": ["localadmin", "locality", "neighbourhood", "county"], + "regional": ["region_a", "region", "country"] }, "GBR": { - "local": ["neighborhood", "admin2", "local_admin", "locality"], - "regional": ["admin2","admin0","admin1"] + "local": ["neighbourhood", "county", "localadmin", "locality"], + "regional": ["county","country","region"] }, "default": { - "local": ["local_admin", "locality", "neighborhood", "admin2"], - "regional": ["admin1_abbr", "admin1", "admin0"] + "local": ["localadmin", "locality", "neighbourhood", "county"], + "regional": ["region_a", "region", "country"] } } \ No newline at end of file diff --git a/middleware/renamePlacenames.js b/middleware/renamePlacenames.js index 1eff4026..9e24fc8f 100644 --- a/middleware/renamePlacenames.js +++ b/middleware/renamePlacenames.js @@ -1,3 +1,5 @@ +var extend = require('extend'); + /** - P is a preferred English name - Q is a preferred name (in other languages) @@ -11,21 +13,23 @@ */ // config mapping of old names to new ones var NAME_MAP = { + 'number': 'housenumber', + 'zip': 'postalcode', 'alpha3': 'country_a', 'admin0': 'country', 'admin1': 'region', 'admin1_abbr': 'region_a', 'admin2': 'county', - // TODO: what does "local_admin" map to in WOF??? + 'local_admin': 'localadmin', 'neighborhood': 'neighbourhood' }; function setup() { - return mapPlacenames; + return renamePlacenames; } -function mapPlacenames(req, res, next) { +function renamePlacenames(req, res, next) { // do nothing if no result data set if (!req.results.data) { @@ -41,7 +45,12 @@ function mapPlacenames(req, res, next) { function renameProperties(place) { var newPlace = {}; Object.keys(place).forEach(function (property) { - renameProperty(place, newPlace, property); + if (property === 'address') { + extend(newPlace, renameProperties(place[property])); + } + else { + renameProperty(place, newPlace, property); + } }); return newPlace; } @@ -51,7 +60,7 @@ function renameProperty(oldObj, newObj, property) { return; } - newObj[ (NAME_MAP[property] || property) ] = oldObj[property]; + newObj[(NAME_MAP[property] || property)] = oldObj[property]; } module.exports = setup; diff --git a/test/unit/helper/geojsonify.js b/test/unit/helper/geojsonify.js index 4f026439..fdd255fe 100644 --- a/test/unit/helper/geojsonify.js +++ b/test/unit/helper/geojsonify.js @@ -48,20 +48,17 @@ module.exports.tests.search = function(test, common) { 'name': { 'default': '\'Round Midnight Jazz and Blues Bar' }, - 'type': 'node', - 'address': { - 'number': '13', - 'street': 'Liverpool Road', - 'zip': 'N1 0RW' - }, - 'alpha3': 'GBR', - 'admin0': 'United Kingdom', - 'admin1': 'Islington', - 'admin1_abbr': 'ISL', - 'admin2': 'Angel', - 'local_admin': 'test1', + 'housenumber': '13', + 'street': 'Liverpool Road', + 'postalcode': 'N1 0RW', + 'country_a': 'GBR', + 'country': 'United Kingdom', + 'region': 'Islington', + 'region_a': 'ISL', + 'county': 'Angel', + 'localadmin': 'test1', 'locality': 'test2', - 'neighborhood': 'test3', + 'neighbourhood': 'test3', 'suggest': { 'input': [ '\'round midnight jazz and blues bar' @@ -76,7 +73,6 @@ module.exports.tests.search = function(test, common) { { '_id': 'id2', '_type': 'type2', - 'type': 'way', 'name': { 'default': 'Blues Cafe' }, @@ -84,14 +80,14 @@ module.exports.tests.search = function(test, common) { 'lat': '51.517806', 'lon': '-0.101795' }, - 'alpha3': 'GBR', - 'admin0': 'United Kingdom', - 'admin1': 'City And County Of The City Of London', - 'admin1_abbr': 'COL', - 'admin2': 'Smithfield', - 'local_admin': 'test1', + 'country_a': 'GBR', + 'country': 'United Kingdom', + 'region': 'City And County Of The City Of London', + 'region_a': 'COL', + 'county': 'Smithfield', + 'localadmin': 'test1', 'locality': 'test2', - 'neighborhood': 'test3', + 'neighbourhood': 'test3', 'suggest': { 'input': [ 'blues cafe' @@ -102,7 +98,6 @@ module.exports.tests.search = function(test, common) { { '_id': '34633854', '_type': 'osmway', - 'type': 'osmway', 'name': { 'default': 'Empire State Building' }, @@ -110,14 +105,14 @@ module.exports.tests.search = function(test, common) { 'lat': '40.748432', 'lon': '-73.985656' }, - 'alpha3': 'USA', - 'admin0': 'United States', - 'admin1': 'New York', - 'admin1_abbr': 'NY', - 'admin2': 'New York', - 'local_admin': 'Manhattan', + 'country_a': 'USA', + 'country': 'United States', + 'region': 'New York', + 'region_a': 'NY', + 'county': 'New York', + 'localadmin': 'Manhattan', 'locality': 'New York', - 'neighborhood': 'Koreatown', + 'neighbourhood': 'Koreatown', 'suggest': { 'input': [ 'empire state building' @@ -145,23 +140,24 @@ module.exports.tests.search = function(test, common) { ] }, 'properties': { - 'id': 'id1', - 'layer': 'type1', - 'text': '\'Round Midnight Jazz and Blues Bar, test3, Angel', - 'name': '\'Round Midnight Jazz and Blues Bar', - 'alpha3': 'GBR', - 'admin0': 'United Kingdom', - 'admin1': 'Islington', - 'admin1_abbr': 'ISL', - 'admin2': 'Angel', - 'local_admin': 'test1', - 'locality': 'test2', - 'neighborhood': 'test3', - 'category': [ 'food', 'nightlife' ], - 'address': { - 'number': '13', + 'geocoding': { + 'id': 'id1', + 'type': 'type1', + 'source': 'type1', + 'label': '\'Round Midnight Jazz and Blues Bar, test3, Angel', + 'name': '\'Round Midnight Jazz and Blues Bar', + 'country_a': 'GBR', + 'country': 'United Kingdom', + 'region': 'Islington', + 'region_a': 'ISL', + 'county': 'Angel', + 'localadmin': 'test1', + 'locality': 'test2', + 'neighbourhood': 'test3', + 'category': ['food', 'nightlife'], + 'housenumber': '13', 'street': 'Liverpool Road', - 'zip': 'N1 0RW' + 'postalcode': 'N1 0RW' } } }, @@ -175,18 +171,21 @@ module.exports.tests.search = function(test, common) { ] }, 'properties': { - 'id': 'id2', - 'layer': 'type2', - 'text': 'Blues Cafe, test3, Smithfield', - 'name': 'Blues Cafe', - 'alpha3': 'GBR', - 'admin0': 'United Kingdom', - 'admin1': 'City And County Of The City Of London', - 'admin1_abbr': 'COL', - 'admin2': 'Smithfield', - 'local_admin': 'test1', - 'locality': 'test2', - 'neighborhood': 'test3' + 'geocoding': { + 'id': 'id2', + 'type': 'type2', + 'source': 'type2', + 'label': 'Blues Cafe, test3, Smithfield', + 'name': 'Blues Cafe', + 'country_a': 'GBR', + 'country': 'United Kingdom', + 'region': 'City And County Of The City Of London', + 'region_a': 'COL', + 'county': 'Smithfield', + 'localadmin': 'test1', + 'locality': 'test2', + 'neighbourhood': 'test3' + } } }, { @@ -199,19 +198,22 @@ module.exports.tests.search = function(test, common) { ] }, 'properties': { - 'id': '34633854', - 'layer': 'osmway', - 'text': 'Empire State Building, Manhattan, NY', - 'name': 'Empire State Building', - 'alpha3': 'USA', - 'admin0': 'United States', - 'admin1': 'New York', - 'admin1_abbr': 'NY', - 'admin2': 'New York', - 'local_admin': 'Manhattan', - 'locality': 'New York', - 'neighborhood': 'Koreatown', - 'category': [ 'tourism', 'transport' ] + 'geocoding': { + 'id': '34633854', + 'type': 'venue', + 'source': 'osm', + 'label': 'Empire State Building, Manhattan, NY', + 'name': 'Empire State Building', + 'country_a': 'USA', + 'country': 'United States', + 'region': 'New York', + 'region_a': 'NY', + 'county': 'New York', + 'localadmin': 'Manhattan', + 'locality': 'New York', + 'neighbourhood': 'Koreatown', + 'category': ['tourism', 'transport'] + } } } ] @@ -247,9 +249,12 @@ module.exports.tests.search = function(test, common) { ] }, 'properties': { - 'id': 'id1', - 'layer': 'type1', - 'text': '\'Round Midnight Jazz and Blues Bar, test3, Angel' + 'geocoding': { + 'id': 'id1', + 'type': 'type1', + 'source': 'type1', + 'label': '\'Round Midnight Jazz and Blues Bar, test3, Angel' + } } }, { @@ -262,9 +267,12 @@ module.exports.tests.search = function(test, common) { ] }, 'properties': { - 'id': 'id2', - 'layer': 'type2', - 'text': 'Blues Cafe, test3, Smithfield' + 'geocoding': { + 'id': 'id2', + 'type': 'type2', + 'source': 'type2', + 'label': 'Blues Cafe, test3, Smithfield' + } } }, { @@ -277,9 +285,12 @@ module.exports.tests.search = function(test, common) { ] }, 'properties': { - 'id': '34633854', - 'layer': 'osmway', - 'text': 'Empire State Building, Manhattan, NY' + 'geocoding': { + 'id': '34633854', + 'type': 'venue', + 'source': 'osm', + 'label': 'Empire State Building, Manhattan, NY' + } } } ] diff --git a/test/unit/helper/outputSchema.js b/test/unit/helper/outputSchema.js index fb2b72af..cefd08da 100644 --- a/test/unit/helper/outputSchema.js +++ b/test/unit/helper/outputSchema.js @@ -13,10 +13,10 @@ module.exports.tests.interface = function(test, common) { }; module.exports.tests.valid = function(test, common) { - var valid_keys = ['local_admin', 'locality', 'neighborhood', 'admin2', 'admin1_abbr', 'admin1', 'admin0']; + var valid_keys = ['localadmin', 'locality', 'neighbourhood', 'county', 'region_a', 'region', 'country']; var default_schema = { - local: ['local_admin', 'locality', 'neighborhood', 'admin2'], - regional: ['admin1_abbr', 'admin1', 'admin0'] + local: ['localadmin', 'locality', 'neighbourhood', 'county'], + regional: ['region_a', 'region', 'country'] }; var isValid = function(keys, schema) {