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
JavaScript
;
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;