Appirio's Tech Blog

Saturday, August 9, 2008

Google Geocoding from Visualforce

Kyle
Roche


Mashups are becoming a common part of most implementations. Replacing legacy applications are sometimes phased into adoption by creating a mashup in Salesforce.com of the current application(s) and slowly replacing components as they are reconstructed using native Salesforce. Google Maps Mashups are among the most popular. However, we found most of the examples were either using the AJAX toolkit or were hard coding the Lattitude / Longitude coordinates. In this example, we'll demonstrate how to use Google's Geocoding API to geocode an Account address from with your Visualforce controller. The key difference is that this example geocodes the address using server side scripting.

Start off by creating a new APEX Class called GoogleGeocodeExtension. This will be our controller extension. Remember, controller extensions have constructors that take an argument of controller for which they are extending. In this example, we'll be extending the standard controller for the Account object. Make sure your class looks like the following.

public class GoogleGeocodeExtension {
private final Account acct;

public GoogleGeocodeExtension (ApexPages.StandardController stdController) {
this.acct = (Account)stdController.getRecord();
}
}

Google's Geocoding API can be accessed via server side scripting. You can choose different output formats like XML, CSV, JSON (default). In our case, we'll keep things simple and return the results in CSV format. Add the following property to your APEX class.

public string[] Coordinates
{
get
{
if (Coordinates == null)
{
Account myAccount = [select name,billingstreet,billingcity,billingstate,billingpostalcode from Account where id=:acct.id];
String url = 'http://maps.google.com/maps/geo?';
url += 'q=' + EncodingUtil.urlEncode(myAccount.BillingStreet,'UTF-8') + ',' + EncodingUtil.urlEncode(myAccount.BillingCity,'UTF-8') + ',' + myAccount.BillingState;
url += '&output=csv&key=yourgooglemapkeyhere';

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');

HttpResponse res = h.send(req);
String responseBody = res.getBody();
Coordinates = responseBody.split(',',0);
}
return Coordinates;
}
set;
}

This APEX property queries the billing address for the Account record and passes it to the Google Geocoding API. Because spaces and other special characters can appear in addresses and city names we need to use the urlEncode() method to properly format these strings.

We chose to use the CSV format on our response. So, we simply need to split the string by the comma delimiter so we can access each field individually. To keep things simple you can add the following two properties to your controller extension.

public string CoordinateLat { get { return Coordinates[2]; } }
public string CoordinateLong { get { return Coordinates[3]; } }

Like any other APEX property in a controller or extension you can access these in your Visualforce page using the standard binding syntax {!CoordinateLat}.

9 comments:

  1. Kyle,
    I tried to implement your solution but am getting a different response from the HTTP request. My output is from Coordinates [0]-[4]:

    '{"name":"800 S. Lexington Ave"' '
    ' '"Status":{"code":200' '
    ' '"request":"geocode"}' '
    ' '"Placemark":[{"id":"p1"'

    Any ideas? Thanks

    FYI: Your controller extension is misnamed.

    ReplyDelete
  2. Hi Brian, thanks for the feedback. I fixed the controller name in the example. Did you change the output type to CSV? Feel free to post your code if you'd like me to check it out.

    Thanks again!
    Kyle

    ReplyDelete
  3. public class GoogleGeocodeExtension {
    private final Account acct;
    String responseBody;
    String[] c;

    public GoogleGeocodeExtension(ApexPages.StandardController stdController) {
    this.acct = (Account)stdController.getRecord();

    }

    public string[] Coordinates {
    get {

    if (Coordinates == null) {
    Account a = [SELECT name, billingstreet, billingcity, billingstate, billingcountry FROM Account WHERE id=:acct.id];

    if (a.billingstreet==null || a.billingcity==null || a.billingcountry==null) {
    String message ='Account address could not be found';
    Coordinates[0]='0';
    Coordinates[1]='0';
    Coordinates[2]='0';
    Coordinates[3]='0';
    }
    else {
    String url = 'http://maps.google.com/maps/geo?';
    url += 'q=' + EncodingUtil.urlEncode(a.billingstreet,'UTF-8');
    url += ' ' + EncodingUtil.urlEncode(a.billingcity,'UTF-8');
    url += ' ' + EncodingUtil.urlEncode(a.billingcountry,'UTF-8');
    url += '&output=csv&key=ABQIAAAAn-_wL1b8pbIpJfusL0LMmRQ72da4xWLg1N8Ce02Xdu0VxpZsuRQ204RfeXqfJttrBEVklwfOQn2xNA';

    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');

    HttpResponse res = h.send(req);
    responseBody = res.getBody();
    Coordinates = responseBody.split(',',0);

    }

    }
    else {

    }
    return Coordinates;
    }
    set;
    }

    public string Coordinate0 { get { return Coordinates[0]; } }
    public string Coordinate1 { get { return Coordinates[1]; } }
    public string Coordinate2 { get { return Coordinates[2]; } }
    public string Coordinate3 { get { return Coordinates[3]; } }


    }

    ReplyDelete
  4. Thanks again Kyle. I really appreciate you providing your code as I would prefer a VF solution versus an Javascript.

    My code is almost identical to yours. It appears the HTTP response has a different format than expected.

    I "split" the response string and found "coordinates:[-76.323456,12.098766]" around the 12th and 13th indices.

    From what I've read elsewhere, your format for the response string is what I should expect, but it isn't for some reason.

    Thanks again.

    ReplyDelete
  5. Hi Brian, I did notice one difference... when you are building the string 'url' it appears you are forgetting the commas. If that doesn't work, please post your Visualforce Page and I'll look at the whole thing.

    Thanks,
    Kyle

    ReplyDelete
  6. Kyle,
    You were right! I do get some extra quotes but will get rid of them. Thanks again.

    '200' '
    ' '6' '
    ' '36.090405' '
    ' '-79.437504'

    ReplyDelete
  7. I also use ViaMichelin for Geocoding. Coding can be in these formats http / xml / soap / wsdl

    Geocoding , reverse Geocoding is available for logistic, fleet management companies for address verification.

    I would not use google, map 24 multimap etc as they kacl the coverage, quality and cstomisation of ViaMichelin business mapping UK for Geolocation and mapping solutions. mark was my contact in the London office and offered me a really cost effective look up service with was eay to intergrate.

    I opened up a free trial account and have never looked back !

    ReplyDelete
  8. Thanks for posting this Kyle. It was very helpful.

    @Brian - you may want to request your api key to get pulled from your comments.

    Jason

    ReplyDelete
  9. how do you get the response..I have a class created already but dont know the method to get reponse... are you creating some separate fields for response in accounts object??

    ReplyDelete

 
2006-2012 Appirio Inc. All rights reserved.
Appirio.com | Support | Resource Center | Contact | Careers | Privacy Policy