UNPKG

node-geocoder

Version:

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

274 lines (232 loc) 7.4 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;