UNPKG

node-geocoder

Version:

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

245 lines (244 loc) 8.42 kB
'use strict'; var net = require('net'); /** * Constructor * @param {Object} httpAdapter Http Adapter * @param {Object} options Options (language, client_id, client_secret) */ var AGOLGeocoder = function AGOLGeocoder(httpAdapter, options) { if (!httpAdapter || httpAdapter == 'undefined') { throw new Error('ArcGis Online Geocoder requires a httpAdapter to be defined'); } if (!options || options == 'undefined') { options = {}; } if (!options.client_id || options.client_id == 'undefined') { options.client_id = null; } if (!options.client_secret || options.client_secret == 'undefined') { options.client_secret = null; } if (!options.client_secret || !options.client_id) { throw new Error('You must specify the client_id and the client_secret'); } this.options = options; this.httpAdapter = httpAdapter; this.cache = {}; }; AGOLGeocoder.prototype._authEndpoint = 'https://www.arcgis.com/sharing/oauth2/token'; AGOLGeocoder.prototype._endpoint = 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find'; AGOLGeocoder.prototype._reverseEndpoint = 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode'; //Cached vars AGOLGeocoder.prototype._cachedToken = { 'now': function () { return (new Date()).getTime(); }, 'put': function (token, experation, cache) { cache.token = token; //Shave 30 secs off experation to ensure that we expire slightly before the actual expiration cache.tokenExp = this.now() + (experation - 30); }, 'get': function (cache) { if (!cache) { return null; } if (this.now() <= cache.tokenExp) { return cache.token; } else { return null; } } }; AGOLGeocoder.prototype._getToken = function (callback) { var _this = this; if (_this._cachedToken.get(_this.cache) !== null) { callback(_this._cachedToken.get()); return; } var params = { 'grant_type': 'client_credentials', 'client_id': _this.options.client_id, 'client_secret': _this.options.client_secret }; _this.httpAdapter.get(_this._authEndpoint, params, function (err, result) { if (err) { return callback(err); } else { if (typeof result === 'string') { result = JSON.parse(result); } var tokenExpiration = (new Date()).getTime() + result.expires_in; var token = result.access_token; _this._cachedToken.put(token, tokenExpiration, _this.cache); callback(false, token); } }); }; /** * Geocode * @param {String} value Value to geocode (Address) * @param {Function} callback Callback method */ AGOLGeocoder.prototype.geocode = function (value, callback) { var _this = this; if (net.isIP(value)) { throw new Error('The AGOL geocoder does not support IP addresses'); } if (value instanceof Array) { //As defined in http://resources.arcgis.com/en/help/arcgis-rest-api/#/Batch_geocoding/02r300000003000000/ throw new Error('An ArcGIS Online organizational account is required to use the batch geocoding functionality'); } var execute = function (value, token, callback) { var params = { 'token': token, 'f': 'json', 'text': value, 'outFields': 'AddNum,StPreDir,StName,StType,City,Postal,Region,Country' }; _this.httpAdapter.get(_this._endpoint, params, function (err, result) { if (typeof result === 'string') { result = JSON.parse(result); } if (err) { return callback(err); } else { //This is to work around ESRI's habit of returning 200 OK for failures such as lack of authentication if (result.error) { callback(result.error); return null; } var results = []; for (var i = 0; i < result.locations.length; i++) { results.push(_this._formatResult(result.locations[i])); } results.raw = result; callback(false, results); } }); }; this._getToken(function (err, token) { if (err) { return callback(err); } else { execute(value, token, callback); } }); }; AGOLGeocoder.prototype._formatResult = function (result) { if (result.address) { return { 'latitude': result.location.y, 'longitude': result.location.x, 'country': result.address.CountryCode, 'city': result.address.City, 'state': result.address.Region, 'zipcode': result.address.Postal, 'countryCode': result.address.CountryCode, 'address': result.address.Address, 'neighborhood': result.address.Neighborhood, 'loc_name': result.address.Loc_name }; } var country = null; var countryCode = null; var city = null; var state = null; var stateCode = null; var zipcode = null; var streetPreDir = null; var streetType = null; var streetName = null; var streetNumber = null; var attributes = result.feature.attributes; for (var property in attributes) { if (attributes.hasOwnProperty(property)) { if (property == 'City') { city = attributes[property]; } if (property == 'Postal') { zipcode = attributes[property]; } if (property == 'Region') { state = attributes[property]; } if (property == 'StPreDir') { streetPreDir = attributes[property]; } if (property == 'AddNum') { streetNumber = attributes[property]; } if (property == 'StName') { streetName = attributes[property]; } if (property == 'StType') { streetType = attributes[property]; } if (property == 'Country') { countryCode = attributes[property]; country = attributes[property]; } } } return { 'latitude': result.feature.geometry.y, 'longitude': result.feature.geometry.x, 'country': country, 'city': city, 'state': state, 'stateCode': stateCode, 'zipcode': zipcode, 'streetName': streetPreDir + ' ' + streetName + ' ' + streetType, 'streetNumber': streetNumber, 'countryCode': countryCode }; }; /** * Reverse geocoding * @param {lat:<number>,lon:<number>} lat: Latitude, lon: Longitude * @param {function} callback Callback method */ AGOLGeocoder.prototype.reverse = function (query, callback) { var lat = query.lat; var long = query.lon; var _this = this; var execute = function (lat, long, token, callback) { var params = { 'token': token, 'f': 'json', 'location': long + ',' + lat, 'outFields': 'AddrNum,StPreDir,StName,StType,City,Postal,Region,Country' }; _this.httpAdapter.get(_this._reverseEndpoint, params, function (err, result) { if (typeof result === 'string') { result = JSON.parse(result); } if (err) { return callback(err); } else { //This is to work around ESRI's habit of returning 200 OK for failures such as lack of authentication if (result.error) { callback(result.error, { raw: result }); return null; } var results = []; results.push(_this._formatResult(result)); results.raw = result; callback(false, results); } }); }; this._getToken(function (err, token) { if (err) { return callback(err); } else { execute(lat, long, token, callback); } }); }; module.exports = AGOLGeocoder;