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