UNPKG

node-geocoder

Version:

Node Geocoder, node geocoding library, supports google maps, mapquest, open street map, tom tom, promise

313 lines (269 loc) 8.48 kB
'use strict'; var util = require('util'), net = require('net'), AbstractGeocoder = require('./abstractgeocoder'); var ValueError = require('../error/valueerror.js'); function formatGeocoderName(name) { return name.toLowerCase().replace(/geocoder$/, ''); } /** * APlaceGeocoder Constructor * @param <object> httpAdapter Http Adapter * @param <object> options Options (language, apiKey) */ var APlaceGeocoder = function (httpAdapter, options) { this.supportIPv4 = false; this.supportIPv6 = false; this.supportAddress = true; APlaceGeocoder.super_.call(this, httpAdapter); if (!this.constructor.name) { throw new Error('The Constructor must be named'); } this.name = formatGeocoderName(this.constructor.name); if (!httpAdapter || httpAdapter == 'undefined') { throw new Error(this.constructor.name + ' need an httpAdapter'); } this.httpAdapter = httpAdapter; if (options) { for (var k in options) { this.options[k] = options[k]; } } if (!this.options.language) { this.options.language = 'en'; } if (!this.options.apiKey) { throw new Error('You must specify a apiKey (see https://aplace.io/en/documentation/general/authentication)'); } if (this.options.apiKey && !httpAdapter.supportsHttps()) { throw new Error('You must use https http adapter'); } this.options = options; }; util.inherits(APlaceGeocoder, AbstractGeocoder); // APlace geocoding API endpoint APlaceGeocoder.prototype._geocoderEndpoint = 'https://api.aplace.io/api/v1.0/search'; // APlace reverse API endpoint APlaceGeocoder.prototype._reverseEndpoint = 'https://api.aplace.io/api/v1.0/pip'; /** * Reverse geocoding * @param {lat:<number>,lon:<number>} lat: Latitude, lon: Longitude * @param <function> callback Callback method */ APlaceGeocoder.prototype.reverse = function (query, callback) { return this._reverse(query, callback); }; /** * Geocode * @param <string> value Value to geocode * @param <function> callback Callback method */ APlaceGeocoder.prototype.geocode = function (value, callback) { if ( net.isIPv4(value) && (!this.supportIPv4 || this.supportIPv4 == 'undefined') ) { throw new ValueError( this.constructor.name + ' does not support geocoding IPv4' ); } if ( net.isIPv6(value) && (!this.supportIPv6 || this.supportIPv6 == 'undefined') ) { throw new ValueError( this.constructor.name + ' does not support geocoding IPv6' ); } return this._geocode(value, callback); }; /** * Batch Geocode * @param <string[]> values Valueas to geocode * @param <function> callback Callback method */ APlaceGeocoder.prototype.batchGeocode = function (values, callback) { Promise.all( values.map(value => new Promise(resolve => { this.geocode(value, (error, value) => { resolve({ error, value }); }); }) ) ) .then(data => callback(null, data)); }; APlaceGeocoder.prototype._reverse = function (query, callback) { try { const that = this; const lat = query.lat; const lon = query.lon; if (!lat || !lon) { throw new ValueError('no valid lat or lon given'); } const params = { key: this.options.apiKey, lat: lat, lon: lon }; this.httpAdapter.get(this._reverseEndpoint, params, function (err, response) { if (err) { return callback(err); } else { if (!response.session_id) { return callback( new Error( 'Status is ' + response.status + '.' + (response.error_message ? ' ' + response.error_message : '') ), { raw: response } ); } var results = []; if (response.data && response.data.length > 0) { results.push(that._formatResult(response.data[0])); } results.raw = response; callback(false, results); } }); } catch (error) { return callback(error); } } APlaceGeocoder.prototype._geocode = function (value, callback) { try { const that = this; let query; if (typeof value === 'string') { query = value; } if (typeof value === 'object') { const queryKeys = ['address', 'zip', 'city', 'country', 'countryCode']; const queryParts = []; for (const key in queryKeys) { if (value[key] && value[key].length > 0) { queryParts.push(value[key]); } } query = queryParts.join(' '); } const params = { key: this.options.apiKey, q: query.trim(), lang: this.options.language, type: 'house_number' }; const queryParams = ['type', 'lat', 'lon', 'radius', 'countries']; for (const key in queryParams) { if (value[queryParams[key]]) { params[queryParams[key]] = value[queryParams[key]]; } } const validResultsTypes = ['house_number', 'road', 'quarter', 'city', 'county', 'state', 'region', 'country']; if (params.type) { if (typeof params.type === 'string') { if (validResultsTypes.indexOf(params.type) === -1) { throw new Error('type must be one of ' + validResultsTypes.join(', ')); } } } for (const key in params) { if (!params[key]) { delete params[key]; } } this.httpAdapter.get(this._geocoderEndpoint, params, function (err, response) { if (err) { return callback(err); } else { if (!response.session_id) { return callback( new Error( 'Status is ' + response.status + '.' + (response.error_message ? ' ' + response.error_message : '') ), { raw: response } ); } var results = []; if (response.data && response.data.length > 0) { results.push(that._formatResult(response.data[0])); } results.raw = response; callback(false, results); } }); } catch (error) { return callback(error); } } APlaceGeocoder.prototype._formatResult = function (result) { let formattedAddress = result.match; if (result.match_details && result.match_details.length > 0) { formattedAddress += ', ' + result.match_details; } var extractedObj = { formattedAddress: formattedAddress || null, latitude: result.lat, longitude: result.lon, extra: {}, administrativeLevels: {} }; for (const key in result.address) { switch (key) { // Address case 'postal_code': extractedObj.zipcode = result.address.postcode; break; case 'road': extractedObj.streetName = result.address.road; break; case 'street_number': extractedObj.streetNumber = result.address.house_number; break; case 'country': extractedObj.administrativeLevels.level1long = result.address.country; extractedObj.administrativeLevels.level1short = result.address.country_code; extractedObj.extra.country = result.address.country; extractedObj.extra.countryCode = result.address.country_code; break; case 'region': extractedObj.administrativeLevels.level2long = result.address.region; extractedObj.administrativeLevels.level2short = result.address.region_code; extractedObj.extra.region = result.address.region; extractedObj.extra.regionCode = result.address.region_code; break; case 'state': extractedObj.administrativeLevels.level3long = result.address.state; extractedObj.administrativeLevels.level3short = result.address.state_code; extractedObj.extra.state = result.address.state; extractedObj.extra.stateCode = result.address.state_code; break; case 'county': extractedObj.administrativeLevels.level4long = result.address.county; extractedObj.extra.county = result.address.county; break; case 'city': extractedObj.administrativeLevels.level5long = result.address.city; break; case 'quarter': extractedObj.administrativeLevels.level6long = result.address.quarter; extractedObj.extra.quarter = result.address.quarter; extractedObj.neighbourhood = result.address.quarter; break; } } return extractedObj; }; module.exports = APlaceGeocoder;