Skip to content

Instantly share code, notes, and snippets.

@benmj
Created August 29, 2013 16:38
Show Gist options
  • Save benmj/6380466 to your computer and use it in GitHub Desktop.
Save benmj/6380466 to your computer and use it in GitHub Desktop.
An AngularJS Service for intelligently geocoding addresses using Google's API. Makes use of localStorage (via the ngStorage package) to avoid unnecessary trips to the server. Queries Google's API synchronously to avoid `google.maps.GeocoderStatus.OVER_QUERY_LIMIT`
/*global angular: true, google: true, _ : true */
'use strict';
angular.module('geocoder', ['ngStorage']).factory('Geocoder', function ($localStorage, $q, $timeout) {
var locations = $localStorage.locations ? JSON.parse($localStorage.locations) : {};
var queue = [];
// Amount of time (in milliseconds) to pause between each trip to the
// Geocoding API, which places limits on frequency.
var queryPause = 250;
/**
* executeNext() - execute the next function in the queue.
* If a result is returned, fulfill the promise.
* If we get an error, reject the promise (with message).
* If we receive OVER_QUERY_LIMIT, increase interval and try again.
*/
var executeNext = function () {
var task = queue[0],
geocoder = new google.maps.Geocoder();
geocoder.geocode({ address : task.address }, function (result, status) {
if (status === google.maps.GeocoderStatus.OK) {
var latLng = {
lat: result[0].geometry.location.lat(),
lng: result[0].geometry.location.lng()
};
queue.shift();
locations[task.address] = latLng;
$localStorage.locations = JSON.stringify(locations);
task.d.resolve(latLng);
if (queue.length) {
$timeout(executeNext, queryPause);
}
} else if (status === google.maps.GeocoderStatus.ZERO_RESULTS) {
queue.shift();
task.d.reject({
type: 'zero',
message: 'Zero results for geocoding address ' + task.address
});
} else if (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
queryPause += 250;
$timeout(executeNext, queryPause);
} else if (status === google.maps.GeocoderStatus.REQUEST_DENIED) {
queue.shift();
task.d.reject({
type: 'denied',
message: 'Request denied for geocoding address ' + task.address
});
} else if (status === google.maps.GeocoderStatus.INVALID_REQUEST) {
queue.shift();
task.d.reject({
type: 'invalid',
message: 'Invalid request for geocoding address ' + task.address
});
}
});
};
return {
latLngForAddress : function (address) {
var d = $q.defer();
if (_.has(locations, address)) {
$timeout(function () {
d.resolve(locations[address]);
});
} else {
queue.push({
address: address,
d: d
});
if (queue.length === 1) {
executeNext();
}
}
return d.promise;
}
};
});
@avaliani
Copy link

avaliani commented Apr 9, 2014

BUG REPORT: Additionally, executeNext() is not being called if the response type is zero results, request denied or invalid result.

I've forked the gist to make the fixes. I've also simplified the query pause logic (didn't like the query pause timeout creeping upwards and I wanted to be more aggressive with retries). Link below:

https://gist.github.com/avaliani/10214857

@david-meza
Copy link

Thank you for this gist. I took the liberty of forking it and modifying it to work with the angular-google-maps library.

Here's my gist if anyone wants to use it.

@saurabhudaniya200
Copy link

_.has(locations, address) can be replaced with Object.keys(locations).indexOf(address) > -1

This can remove the _ dependency

@marlowBlackwood
Copy link

@benmj thanks for posting this gist, and @david-meza, thanks for modifying it to work with angular-google-maps. I'm using angular-local-storage in my project, so I made a fork that works with it instead of ngStorage.

Here's the modified version

@vimal-aequalisys
Copy link

Great service, When I try to integrate, I get the below error.
TypeError: Cannot read property 'geocode' of undefined
Could you please suggest me a fix?

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment