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;
}
};
});
@wiedikerli
Copy link

f (_.has(locations, address)) {

Is there a way to do this without _?

@wiedikerli
Copy link

and could you provide a example for the usage?

@mwawrusch
Copy link

Great service, but does not survive minification. Can you change your gist like so:

angular.module('geocoder', ['ngStorage']).factory('Geocoder', ['$localStorage', '$q', '$timeout', function ($localStorage, $q, $timeout) {

and

} ]);

at the last line

How to use (in Coffeescript):

$scope.geocode = (address) ->
  p = Geocoder.latLngForAddress address

  fnSuccess = (latLng) ->
    $scope.data.latitude = latLng.lat
    $scope.data.longitude = latLng.lng

  fnError= () ->
    # not found somewhere.
    console.log "ERROR"
  p.then fnSuccess,fnError

@suras
Copy link

suras commented Dec 31, 2013

thanks for the gist
similar way for getting latlng with location api and reverse geocoding
https://gist.github.com/suras/8196789

@TylerL-uxai
Copy link

I'd like to see an example of this is used. It looks really promising!

@avaliani
Copy link

avaliani commented Apr 8, 2014

Thanks for sharing. Very helpful.

BUG REPORT: you are missing a call to "$rootScope.$apply()" at line 62. When the resultCallback for "geocoder.geocode( resultCallback )" is invoked it is not part of the angular $digest cycle. Therefore, you need to explicitly call "$rootScope.$apply()" (and of course include $rootScope in your dependencies).

Once you do this then your code works like a charm!

Thank you.

@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