Skip to content

Instantly share code, notes, and snippets.

@avaliani
Forked from benmj/geocoder-service.js
Last active February 3, 2022 16:13
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save avaliani/10214857 to your computer and use it in GitHub Desktop.
Save avaliani/10214857 to your computer and use it in GitHub Desktop.
This is a fork of @benmj 's Angular JS Geocoder service. It fixes the following bugs (1) rootscope apply missing, (2) executeNext() not being called if the response type is 'zero results', 'request denied' or 'invalid result', and (3) it simplifies the query pause logic (no increasing pause times and tasks are rejected if we are still over quota…
/*
* 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`.
*
* @author: benmj
* @author: amir.valiani
*
* Original source: https://gist.github.com/benmj/6380466
*/
/*global angular: true, google: true, _ : true */
'use strict';
angular.module('geocoder', ['ngStorage']).factory('Geocoder', function ($localStorage, $q, $timeout, $rootScope) {
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 QUERY_PAUSE= 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 parsedResult = {
lat: result[0].geometry.location.lat(),
lng: result[0].geometry.location.lng(),
formattedAddress: result[0].formatted_address
};
locations[task.address] = parsedResult;
$localStorage.locations = JSON.stringify(locations);
queue.shift();
task.d.resolve(parsedResult);
} 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) {
if (task.executedAfterPause) {
queue.shift();
task.d.reject({
type: 'busy',
message: 'Geocoding server is busy can not process address ' + task.address
});
}
} else if (status === google.maps.GeocoderStatus.REQUEST_DENIED) {
queue.shift();
task.d.reject({
type: 'denied',
message: 'Request denied for geocoding address ' + task.address
});
} else {
queue.shift();
task.d.reject({
type: 'invalid',
message: 'Invalid request for geocoding: status=' + status + ', address=' + task.address
});
}
if (queue.length) {
if (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
var nextTask = queue[0];
nextTask.executedAfterPause = true;
$timeout(executeNext, QUERY_PAUSE);
} else {
$timeout(executeNext, 0);
}
}
if (!$rootScope.$$phase) { $rootScope.$apply(); }
});
};
return {
geocodeAddress : function (address) {
var d = $q.defer();
if (_.has(locations, address)) {
d.resolve(locations[address]);
} else {
queue.push({
address: address,
d: d
});
if (queue.length === 1) {
executeNext();
}
}
return d.promise;
}
};
});
@midori-ao
Copy link

Hi there! Is it possible to save the coordinates returned from the promise into a global variable so that it can be manipulated (such as emitting it with sockets) and used in other controllers?

@avaliani
Copy link
Author

@alexzkazu - That could be done. I think the angular way is to have a variable in the factory vs. a true globa (in short just initialize locations to {} instead of leveraging localStorage)l. But of course it's up to you how you leverage this code. Note that the result has the coordinates (lat and lng fields).

@amaanr
Copy link

amaanr commented Jul 24, 2015

@avaliani do you have a code example of this working?

@frmi
Copy link

frmi commented Sep 24, 2015

@avaliani, This sample is untested but i guess you could do something like:

angular.module('myApp', ['geocoder'])
  .controller('geoCtrl', function ($scope,Geocoder ) {
      Geocoder.geocodeAddress("your address").then(function(latlng){
        console.log(latlng)
      });
});
´´´

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