(For more resources related to this topic, see here.)
Obtaining the Google API key
First, you need to obtain an API key for the Google Geocoding API:
- Visit https://code.google.com/apis/console and sign in with your Google account (assuming you already have one).
- Click on the Create Project button.
- Enter My Salesforce Account Project for the Project name.
- Accept the default value for the Project ID.
- Click on Create.
- Click on APIs & auth from the left-hand navigation bar.
- Set the Geocoding API to ON.
- Select Credentials and click on CREATE NEW KEY.
- Click on the Browser Key button.
- Click on Create to generate the key. Make a note of the API key.
Adding a Salesforce remote site
Now, we need to add a Salesforce remote site for the Google Maps API:
- Navigate to Setup | Security Controls | Remote Site Settings.
- Click on the New Remote Site button.
- Enter Google_Maps_API for the Remote Site Name.
- Enter https://maps.googleapis.com for the Remote Site URL.
- Ensure that the Active checkbox is checked.
- Click on Save.
- Your remote site detail should resemble the following screenshot:
Adding the Location custom field to Account
Next, we need to add a Location field to the Account object:
- Navigate to Setup | Customize | Accounts | Fields.
- Click on the New button in the Custom Fields & Relationships section.
- Select Geolocation for the Data Type. Click on Next.
- Enter Location for the Field Label. The Field Name should also default to Location.
- Select Decimal for the Latitude and Longitude Display Notation.
- Enter 7 for the Decimal Places. Click on Next.
- Click on Next to accept the defaults for Field-Level Security.
- Click on Save to add the field to all account related page layouts.
Adding the Apex Utility Class
Next, we need an Apex utility class to geocode an address using the Google Geocoding API:
- Navigate to Setup | Develop | Apex Classes.
- All of the Apex classes for your organization will be displayed. Click on Developer Console.
- Navigate to File | New | Apex Class.
- Enter AccountGeocodeAddress for the Class Name and click on OK.
- Enter the following code into the Apex Code Editor in your Developer Console window:
// static variable to determine if geocoding has already occurred private static Boolean geocodingCalled = false; // wrapper method to prevent calling future methods from an existing future context public static void DoAddressGeocode(id accountId) { if (geocodingCalled || System.isFuture()) { System.debug(LoggingLevel.WARN, '***Address Geocoding Future Method Already Called - Aborting...'); return; } // if not being called from future context, geocode the address geocodingCalled = true; geocodeAddress(accountId); }
- The AccountGeocodeAddress method and public static variable geocodingCalled protect us from a potential error where a future method may be called from within a future method that is already executing. If this isn’t the case, we call the geocodeAddress method that is defined next. Enter the following code into the Apex Code Editor in your Developer Console window:
// we need a future method to call Google Geocoding API from Salesforce @future (callout=true) static private void geocodeAddress(id accountId) { // Key for Google Maps Geocoding API String geocodingKey = '[Your API Key here]'; // get the passed in address Account geoAccount = [SELECT BillingStreet, BillingCity, BillingState, BillingCountry, BillingPostalCode FROM Account WHERE id = :accountId]; // check that we have enough information to geocode the address if ((geoAccount.BillingStreet == null) || (geoAccount.BillingCity == null)) { System.debug(LoggingLevel.WARN, 'Insufficient Data to Geocode Address'); return; } // create a string for the address to pass to Google Geocoding API String geoAddress = ''; if (geoAccount.BillingStreet != null) geoAddress += geoAccount.BillingStreet + ', '; if (geoAccount.BillingCity != null) geoAddress += geoAccount.BillingCity + ', '; if (geoAccount.BillingState != null) geoAddress += geoAccount.BillingState + ', '; if (geoAccount.BillingCountry != null) geoAddress += geoAccount.BillingCountry + ', '; if (geoAccount.BillingPostalCode != null) geoAddress += geoAccount.BillingPostalCode; // encode the string so we can pass it as part of URL geoAddress = EncodingUtil.urlEncode(geoAddress, 'UTF-8'); // build and make the callout to the Geocoding API Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://maps.googleapis.com/maps/api/geocode/json?address=' + geoAddress + '&key=' + geocodingKey + '&sensor=false'); request.setMethod('GET'); request.setTimeout(60000); try { // make the http callout HttpResponse response = http.send(request); // parse JSON to extract co-ordinates JSONParser responseParser = JSON.createParser(response.getBody()); // initialize co-ordinates double latitude = null; double longitude = null; while (responseParser.nextToken() != null) { if ((responseParser.getCurrentToken() == JSONToken.FIELD_NAME) && (responseParser.getText() == 'location')) { responseParser.nextToken(); while (responseParser.nextToken() != JSONToken.END_OBJECT) { String locationText = responseParser.getText(); responseParser.nextToken(); if (locationText == 'lat') latitude = responseParser.getDoubleValue(); else if (locationText == 'lng') longitude = responseParser.getDoubleValue(); } } } // update co-ordinates on address if we get them back if (latitude != null) { geoAccount.Location__Latitude__s = latitude; geoAccount.Location__Longitude__s = longitude; update geoAccount; } } catch (Exception e) { System.debug(LoggingLevel.ERROR, 'Error Geocoding Address - ' + e.getMessage()); } }
- Insert your Google API key in the following line of code:
String geocodingKey = '[Your API Key here]';
- Navigate to File | Save.
Adding the Apex Trigger
Finally, we need to implement an Apex trigger class to geocode the Billing Address when an Account is added or updated
- Navigate to Setup | Develop | Apex Triggers.
- All of the Apex triggers for your organization will be displayed. Click on Developer Console.
- Navigate to File | New | Apex Trigger in the Developer Console.
- Enter geocodeAccountAddress in the Name field.
- Select Account in the Objects dropdown list and click on Submit.
- Enter the following code into the Apex Code Editor in your Developer Console window:
trigger geocodeAccountAddress on Account (after insert, after update) { // bulkify trigger in case of multiple accounts for (Account account : trigger.new) { // check if Billing Address has been updated Boolean addressChangedFlag = false; if (Trigger.isUpdate) { Account oldAccount = Trigger.oldMap.get(account.Id); if ((account.BillingStreet != oldAccount.BillingStreet) || (account.BillingCity != oldAccount.BillingStreet) || (account.BillingCountry != oldAccount.BillingCountry) || (account.BillingPostalCode != oldAccount.BillingPostalCode)) { addressChangedFlag = true; System.debug(LoggingLevel.DEBUG, '***Address changed for - ' + oldAccount.Name); } } // if address is null or has been changed, geocode it if ((account.Location__Latitude__s == null) || (addressChangedFlag == true)) { System.debug(LoggingLevel.DEBUG, '***Geocoding Account - ' + account.Name); AccountGeocodeAddress.DoAddressGeocode(account.id); } } }
- Navigate to File | Save.
The after insert / after update account trigger itself is relatively simple. If the Location field is blank, or the Billing Address has been updated, a call is made to the AccountGeocodeAddress.DoAddressGeocode method to geocode the address against the Google Maps Geocoding API.
Summary
Congratulations, you have now completed the Geolocation trigger for your Salesforce Account Object. With this, we can calculate distances between two objects in Salesforce or search for accounts/contacts within a certain radius.
Resources for Article:
Further resources on this subject:
- Learning to Fly with Force.com [Article]
- Salesforce CRM Functions [Article]
- Force.com: Data Management [Article]
Hello
I tried your tuto in my sandbox,
But I’ve some error. I thinks it’s because of return lines into your code. Like “//”
And I don’t have a name of geocodeAddress in point 6.
Could you send me the 3 classes by txt file?
Kind Regards
Benoit
public class AccountGeocodeAddress {
// static variable to determine if geocoding has already occurred
private static Boolean geocodingCalled = false;
// wrapper method to prevent calling future methods from an existing future context
public static void DoAddressGeocode (id accountId){
system.debug(‘Called’);
if (geocodingCalled || System.isFuture()) {
System.debug(LoggingLevel.WARN,’***Address Geocoding Future Method Already Called – Aborting…’);
return;
}
// if not being called from future context, geocode the address
geocodingCalled = true;
geocodeAddress(accountId);
}
// we need a future method to call Google Geocoding API from Salesforce
@future (callout=true)
static private void geocodeAddress(id accountId){
// Key for Google Maps Geocoding API
String geocodingKey = ‘Your API Key Value’;
// get the passed in address
Account geoAccount = [SELECT BillingStreet, BillingCity, BillingState, BillingCountry,
BillingPostalCode,Locations__Latitude__s,Locations__Longitude__s
FROM
Account
WHERE
id = :accountId];
// check that we have enough information to geocode the address
if((geoAccount.BillingStreet == null) || (geoAccount.BillingCity == null)) {
System.debug(LoggingLevel.WARN,
‘Insufficient Data to Geocode Address’);
return;
}
// create a string for the address to pass to Google Geocoding API
String geoAddress = ”;
if(geoAccount.BillingStreet != null)
geoAddress += geoAccount.BillingStreet + ‘, ‘;
if(geoAccount.BillingCity != null)
geoAddress+= geoAccount.BillingCity + ‘, ‘;
if(geoAccount.BillingState != null)
geoAddress+= geoAccount.BillingState + ‘, ‘;
if(geoAccount.BillingCountry != null)
geoAddress+= geoAccount.BillingCountry + ‘, ‘;
if(geoAccount.BillingPostalCode != null)
geoAddress+= geoAccount.BillingPostalCode;
// encode the string so we can pass it as part of URL
geoAddress = EncodingUtil.urlEncode(geoAddress, ‘UTF-8’);
// build and make the callout to the Geocoding API
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint(‘https://maps.googleapis.com/maps/api/geocode/json?address=’+geoAddress + ‘&key=’ + geocodingKey+ ‘&sensor=false’);
request.setMethod(‘GET’);
request.setTimeout(60000);
//try {
// make the http callout
HttpResponse response = http.send(request);
// parse JSON to extract co-ordinates
JSONParser responseParser = JSON.createParser(response.getBody());
// initialize co-ordinates
System.debug(‘JSONParser$$’+responseParser);
double latitude = null;
double longitude = null;
while (responseParser.nextToken() != null) {
System.debug(‘currentToken!!’+responseParser.getCurrentToken());
System.debug(‘NextToken!!’+responseParser.nextToken());
System.debug(‘text@@’+responseParser.getText());
System.debug(‘JSOn!!’+JSONToken.FIELD_NAME +’###’+JSONToken.END_OBJECT);
if((responseParser.getCurrentToken() == JSONToken.FIELD_NAME)) {
responseParser.nextToken();
while(responseParser.nextToken() != JSONToken.END_OBJECT) {
String locationText = responseParser.getText();
responseParser.nextToken();
if (locationText == ‘lat’)
latitude = responseParser.getDoubleValue();
else if (locationText == ‘lng’)
longitude = responseParser.getDoubleValue();
}
}
}
// update co-ordinates on address if we get them back
if(latitude != null) {
System.debug(‘!!’+latitude);
System.debug(‘@@’+longitude);
geoAccount.Locations__Latitude__s
= latitude;
geoAccount.Locations__Longitude__s
= longitude;
update geoAccount;
}
/* } catch
(Exception e) {
System.debug(LoggingLevel.ERROR, ‘Error Geocoding Address – ‘ + e.getMessage());
}*/
}
}