Tuesday 12 March 2013


The Google Maps API is a free service used to calculate distance and travel times between locations. In this article I will walk through the logic necessary to build a simple, reusable class to calculate distances within Salesforce.com
Step 1: Create a new Apex Class called googleMaps

public class googleMaps {

}

Now that we have a custom object to instantiate, let's add some public variables. These variables will be used within the class, but also will be the way we quickly pull results from a Salesforce.com page.

We will create the following variables
duration: Example: 3 hours 51 mins
travelTime: Example: 231 (minutes)
distance: Example: 235 (miles)

Step 2: Create public variables

 public String duration {get;set;}
 public Integer travelTime {get;set;}
 public Decimal distance {get;set;}

The variables have been created, but have not been populated with any values.

Google Maps API can return the results in XML or JSON format. In the code below, we will request the results to be returned in a JSON format.

Step 3: Create a method to get JSON results from the Google Maps API.

 public String getJsonResults(
   String address1,
   String address2) {
  
  HttpRequest req = new HttpRequest();
  Http http = new Http();
  
  req.setMethod('GET');
  
  String url = 'https://maps.googleapis.com/maps/api/distancematrix/json'
   + '?origins=' + address1
   + '&destinations=' + address2
   + '&mode=driving'
   + '&sensor=false'
   + '&language=en'
   + '&units=imperial';
   
  req.setEndPoint(url);
  
  HTTPResponse resp = http.send(req);
  
  return jsonResults;
 }

The results from the Google API call will look similar to the code below:

Sample uses the following URL:

https://maps.googleapis.com/maps/api/distancematrix/json?origins=Orlando,+FL&destinations=Miami,+FL&mode=driving&sensor=false&language=en&units=imperial

{
  "status": "OK",
  "origin_addresses": [ "Orlando, FL, USA" ],
  "destination_addresses": [ "Miami, FL, USA" ],
  "rows": [ {
    "elements": [ {
      "status": "OK",
      "duration": {
        "value": 13880,
        "text": "3 hours 51 mins"
      },
      "distance": {
        "value": 377876,
        "text": "235 mi"
      }
    } ]
  } ]
}

Note: At the time this article was written, my research indicated no official JSON parser is currently built into Salesforce.com. Since the data returned is minimal, we will build a custom parser to extract the data we need.

Step 4: Create a method to format the jsonResults to be parsed.

 public String formatJsonResults(String jsonResults) {
  jsonResults = jsonResults.replace('{', ', ');
  jsonResults = jsonResults.replace('}', ', ');
  jsonResults = jsonResults.replace('[', ', ');
  jsonResults = jsonResults.replace(']', ', ');
  jsonResults = jsonResults.replace('"', ''); 
 }

Step 5: Create additional methods to parse the data and populate the variables created in step 2

 public void updateJsonSections(
  String jsonResults) {
  
  List<String> jsonSections = jsonResults.split(', ');
  
  for (Integer i = 0; i < jsonSections.size(); i++) {
   jsonSections[i] = jsonSections[i].trim();
   
   if (jsonSections[i].contains('duration:')) {
    duration = parseDuration(jsonSections[i + 2]);
    travelTime = parseTravelTime(duration);
   }
   
   if (jsonSections[i].contains('distance:')) {
    distance = parseDistance(jsonSections[i + 2]);
   }
  }
 }

 public Decimal parseDistance(String value) {
  value = value.replace('text: ', '');
  value = value.replace(' mi', '');
  value = value.replace(' ft', '');
  value = value.replace(',', '');
  value = value.trim();
  
  return Decimal.valueOf(value);
 }
 
 public String parseDuration(String value) {
  value = value.replace('text: ', '');
  
  return value;
 }
 
 public Integer parseTravelTime(String value) {
 
  Integer tmpMinutes = 0;
 
  List<String> durationNodes = value.split(' ');
  String prevDurationNode = '';
  
  for (String durationNode : durationNodes) {
   if (durationNode == 'day' || durationNode == 'days') {
    tmpMinutes += Integer.valueOf(prevDurationNode) * 1440;
   }
   if (durationNode == 'hour' || durationNode == 'hours') {
    tmpMinutes += Integer.valueOf(prevDurationNode) * 60;
   }
   if (durationNode == 'min' || durationNode == 'mins') {
    tmpMinutes += Integer.valueOf(prevDurationNode);
   }
   
   prevDurationNode = durationNode;
  }
 
  return tmpMinutes; 
 }

That's it. Now it is very easy to call this from any custom controller or apex trigger.

Step 6: Instantiate the object and extract the data.

  googleMaps gm = new googleMaps(
   account1Address,
   account2Address);

  distance = gm.distance;
  duration = gm.duration;
  travelTime = gm.travelTime;

That's it. The complete source code can be found below.

public class googleMaps {

 public String duration {get;set;}
 public Integer travelTime {get;set;}
 public Decimal distance {get;set;}

 public googleMaps(
   String address1,
   String address2) {
    
  String jsonResults = getJsonResults(address1, address2);
  jsonResults = formatJsonResults(jsonResults);
  updateJsonSections(jsonResults);
 }

 public String getJsonResults(
   String address1,
   String address2) {
  
  HttpRequest req = new HttpRequest();
  Http http = new Http();
  
  req.setMethod('GET');
  
  String url = 'https://maps.googleapis.com/maps/api/distancematrix/json'
   + '?origins=' + address1
   + '&destinations=' + address2
   + '&mode=driving'
   + '&sensor=false'
   + '&language=en'
   + '&units=imperial';
   
  req.setEndPoint(url);
  
  HTTPResponse resp = http.send(req);
  
  String jsonResults = resp.getBody().replace('\n', '');

  return jsonResults;
 }
 
 public String formatJsonResults(String value) {
  
  value = value.replace('{', ', ');
  value = value.replace('}', ', ');
  value = value.replace('[', ', ');
  value = value.replace(']', ', ');
  value = value.replace('"', '');
  
  return value; 
 }
 
 public void updateJsonSections(
  String jsonResults) {
  
  List<String> jsonSections = jsonResults.split(', ');
  
  for (Integer i = 0; i < jsonSections.size(); i++) {
   jsonSections[i] = jsonSections[i].trim();
   
   if (jsonSections[i].contains('duration:')) {
    duration = parseDuration(jsonSections[i + 2]);
    travelTime = parseTravelTime(duration);
   }
   
   if (jsonSections[i].contains('distance:')) {
    distance = parseDistance(jsonSections[i + 2]);
   }
  }
 }

 public Decimal parseDistance(String value) {
  value = value.replace('text: ', '');
  value = value.replace(' mi', '');
  value = value.replace(' ft', '');
  value = value.replace(',', '');
  value = value.trim();
  
  return Decimal.valueOf(value);
 }
 
 public String parseDuration(String value) {
  value = value.replace('text: ', '');
  
  return value;
 }
 
 public Integer parseTravelTime(String value) {
 
  Integer tmpMinutes = 0;
 
  List<String> durationNodes = value.split(' ');
  String prevDurationNode = '';
  
  for (String durationNode : durationNodes) {
   if (durationNode == 'day' || durationNode == 'days') {
    tmpMinutes += Integer.valueOf(prevDurationNode) * 1440;
   }
   if (durationNode == 'hour' || durationNode == 'hours') {
    tmpMinutes += Integer.valueOf(prevDurationNode) * 60;
   }
   if (durationNode == 'min' || durationNode == 'mins') {
    tmpMinutes += Integer.valueOf(prevDurationNode);
   }
   
   prevDurationNode = durationNode;
  }
 
  return tmpMinutes; 
 }

}

public class test_ctrl_GoogleMaps_Accounts {

 /* Constructor */
 public test_ctrl_GoogleMaps_Accounts() {
  updateAccounts();
 }

 /* Accounts */
 public List<Account> accounts = new List<Account>();
 
 public List<Account> getAccounts() {
  return accounts;
 }
 
 public void updateAccounts() {
  accounts =
   [SELECT Id, Name, ShippingStreet, ShippingCity,
    ShippingState, ShippingPostalCode, ShippingCountry
    FROM Account
    WHERE ShippingStreet <> ''
    LIMIT 100];
 }
 
 /* Account Variables */
 public String account1Id {get;set;}
 public Account account1 {get;set;}
 public String account1Address {get;set;}
 
 public String account2Id {get;set;}
 public Account account2 {get;set;}
 public String account2Address {get;set;}
 
 public void updateAccountVariables() {
  for (Account a : accounts) {
   if (a.Id == account1Id) {
    account1 = a;
   }
   if (a.Id == account2Id) {
    account2 = a;
   }
  }
 }
 
 public void updateAccountAddresses() {
  account1Address = EncodingUtil.urlEncode(
   + account1.ShippingStreet + ' '
   + account1.ShippingCity + ', '
   + account1.ShippingState + ' '
   + account1.ShippingPostalCode + ' '
   + account1.ShippingCountry,
   'UTF-8');
   
  account2Address = EncodingUtil.urlEncode( 
   + account2.ShippingStreet + ' '
   + account2.ShippingCity + ', '
   + account2.ShippingState + ' '
   + account2.ShippingPostalCode + ' '
   + account2.ShippingCountry,
   'UTF-8');
 }
 
 /* Account Options */
 public List<selectOption> getAccountOptions() {
  
  List<selectOption> options = new List<selectOption>();
  
  for (Account a : accounts) {
   options.add(new selectOption(a.Id, a.Name));
  }
  
  return options;
 }
 
 /* Button Action */
 public void btnCalculateDistance_Click() {
  
  updateAccountVariables();
  updateAccountAddresses();
  
  googleMaps gm = new googleMaps(
   account1Address,
   account2Address);

  distance = gm.distance;
  duration = gm.duration;
  travelTime = gm.travelTime;
 }
 
 /* Results */
 public Decimal distance {get;set;}
 public String duration {get;set;}
 public Integer travelTime {get;set;}

}

<apex:page controller="test_ctrl_GoogleMaps_Accounts">
    
    <apex:form >
    
        <apex:pageBlock title="Google Maps">
        
            <apex:pageBlockSection columns="1">
            
                <apex:pageBlockSectionItem >
                
                    <apex:outputText value="Account #1" />
                    <apex:selectList value="{!account1Id}" size="1">
                        <apex:selectOptions value="{!accountOptions}" />
                    </apex:selectList>
                
                </apex:pageBlockSectionItem>
            
                <apex:pageBlockSectionItem >
                
                    <apex:outputText value="Account #2" />
                    <apex:selectList value="{!account2Id}" size="1">
                        <apex:selectOptions value="{!accountOptions}" />
                    </apex:selectList>
                
                </apex:pageBlockSectionItem>
            
            </apex:pageBlockSection>
            
            <apex:pageBlockButtons location="bottom">
             
             <apex:commandButton id="btnCalculateDistance"
              value="Calculate Distance" action="{!btnCalculateDistance_Click}"
              rerender="pbResults" />
             
            </apex:pageBlockButtons>
        
        </apex:pageBlock>
        
        <apex:pageBlock id="pbResults" title="Results">
        
         <apex:pageBlockSection columns="1">
          
          <apex:pageBlockSectionItem >
          
           <apex:outputText value="Distance (miles)" />
           <apex:outputText value="{!distance}" />
          
          </apex:pageBlockSectionItem>
          
          <apex:pageBlockSectionItem >
          
           <apex:outputText value="Duration" />
           <apex:outputText value="{!duration}" />
          
          </apex:pageBlockSectionItem>
          
          <apex:pageBlockSectionItem >
          
           <apex:outputText value="Travel Time (minutes)" />
           <apex:outputText value="{!travelTime}" />
          
          </apex:pageBlockSectionItem>
          
         </apex:pageBlockSection>
        
        </apex:pageBlock>
    
    </apex:form>
    
</apex:page>

0 comments:

Post a Comment

    Links