function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
KitagawaSan805KitagawaSan805 

Geocoding Trigger help

I am working on a trigger that will populate longitude and latitude fields on the account object. I have a start on it, but I am getting an error:

"Error:Apex trigger AccountBeforeUpsert caused an unexpected exception, contact your administrator: AccountBeforeUpsert: System.LimitException: Too many script statements: 200001"

 

Class: 

public with sharing class GeoUtilities {

  @future(callout=true)
  public static void updateAccounts(List<Id> accountIds)
  {
  // get the custom city, state and country fields from account
    List<account> accounts = [select Id, Name, BillingStreet, BillingCity, BillingState, BillingCountry
      from Account where Id in :accountIds ];
    for(Account theAccount: accounts) { 
      List<string> address = new List<string>();
      address.add(theAccount.BillingStreet);
      address.add(theAccount.BillingCity);
      address.add(theAccount.BillingState);
      address.add(theAccount.BillingCountry);
      String[] coordinates = GeoUtilities.getCoordinates(address);
      if(coordinates != null)
      {
        Decimal pos1 = Decimal.valueOf(coordinates[0]);
        theAccount.Latitude__c = pos1;
        Decimal pos2 = Decimal.valueOf(coordinates[1]);
        theAccount.Longitude__c = pos2;
        system.debug(Logginglevel.ERROR,'GeoUtilities coordinates ' + pos1 + ' ' + pos2 );    
      }
      else
      {
        system.debug(Logginglevel.ERROR,'GeoUtilities no coordinates!!! for address' );          
      }
    }
    update accounts;  
  }

/* 
Input list of address parts: street,  city,  state,  country. 
Output: list of coordinates: latitude, longitude
*/ 
public static String[] getCoordinates(String[] addressParts)
{ 
  String[] Coordinates; 
  String address = '';
  boolean needComma = false;

  if(address.length() == 0)
  {
    system.debug(Logginglevel.ERROR,
      'GeoUtilities getCoordinates no address provided. Return null');    
    return null; 
  }
   
  String url = 'http://maps.google.com/maps/geo?';
  url += 'q=' + address; 
  url += '&output=csv'; 
  
  system.debug(Logginglevel.ERROR,'GeoUtilities getCoordinates url: ' + url);    
  
  Http h = new Http(); 
  HttpRequest req = new HttpRequest();
  
  req.setHeader('Content-type', 'application/x-www-form-urlencoded'); 
  req.setHeader('Content-length', '0'); 
  req.setEndpoint(url); 
  req.setMethod('POST');
  String responseBody;
  if (!Test.isRunningTest()){ 
  // Methods defined as TestMethod do not support Web service callouts
    HttpResponse res = h.send(req); 
    responseBody = res.getBody();
  }
  else {
    // dummy data
    responseBody = '200,4,48.5,-123.67';
  } 
  String[] responseParts = responseBody.split(',',0); 
  // the response has four parts: 
  // status code, quality code, latitude and longitude
  Coordinates = new String[2];
  Coordinates[0] = responseParts[2];
  Coordinates[1] = responseParts[3];
    
  return Coordinates; 
}   
  
  static testMethod void  testGetGeo()
  {
    String[] addressParts;
    String[] coordinates;
    
    addressParts = new List<string>();
    addressParts.add('');
    addressParts.add('San Diego');
    addressParts.add('CA');
    addressParts.add('USA');
    
    coordinates = GeoUtilities.getCoordinates(addressParts);
    System.assert(coordinates != null);
    System.assertEquals(2, coordinates.size()); 
  }
}

 

Trigger: 

trigger AccountBeforeUpsert on Account (before insert, before update) {
  system.debug(Logginglevel.ERROR,'AccountBeforeUpsert  geo updates');    

  List<Id> accountsToUpdate = new List<Id>();
  for(Account theAccount: trigger.new)
  {
    boolean needsUpdate = false;
    if(Trigger.isInsert)
    {
      needsUpdate = true;
    }
    else {
    Account beforeUpdate = System.Trigger.oldMap.get(theAccount.Id);
    needsUpdate = (
      (beforeUpdate.BillingStreet != theAccount.BillingStreet) ||
      (beforeUpdate.BillingCity != theAccount.BillingCity) ||
      (beforeUpdate.BillingState != theAccount.BillingState) ||
      (beforeUpdate.BillingCountry != theAccount.BillingCountry)
      );
    }
    if(needsUpdate)
    {
      accountsToUpdate.add(theAccount.Id);
    }
  }
  if(accountsToUpdate.size() >0 )
  {
    Integer cnt = 0;
    Integer i = 0;
    Integer x = accountsToUpdate.size();
    x = (x>100? 100: x);
    while(i < x){
      List<account> smallList = new List<account>();
      for(Integer k = 0; k == x;)
          break;
      }
      GeoUtilities.updateAccounts(accountsToUpdate);
    }
  }

 Any help with this would be greatly appreciated. Credit for much of the code goes to Bryan at https://bryandf11.wordpress.com/2011/08/22/geocoding-in-apex-triggers-callouts-and-future-methods/

 

Thanks!

Best Answer chosen by Admin (Salesforce Developers) 
colemabcolemab

Please see my updated geocoder class here.  It contains a link to the orignal article in case you want background.

All Answers

colemabcolemab

Please see my updated geocoder class here.  It contains a link to the orignal article in case you want background.

This was selected as the best answer
KitagawaSan805KitagawaSan805

Thanks!!! 

 

The only question I have is on the last part about scheduling it to run 10x per hour. I see the code for it, (below), but where do I put that? 

 

 ScheduleGoogleGeoCodeUpdater  g = new ScheduleGoogleGeoCodeUpdater ();
String sch = '0 0 * * * ?';
system.schedule('GoogleGeoCodeUpdater at top of the hour', sch, g);

 Thanks again for all your help! 

colemabcolemab

The code you quoted must be executed anonymously.  You can do this in the eclipse IDE or in the developer console.

 

Here and here are some links about doing this in the IDE.  It should be fairly straight forward in the developer console.

 

Also, you can check your scheduled jobs under: Your Name->Setup->Admin Setup->Monitoring->Scheduled Jobs.

 

Once you have initialized the geolocations for your system, you will want to only schedule this once per hour.  That way you can save your other scheduled job slots (there is a maximum) for use elsewhere.