IP Address Geolocation with Force.com
If you have a website knowing where your visitors are located is a powerful feature. For starters it makes your website appear to have super powers if it automatically knows where the user is from. You can do all sorts of cool stalker stuff, and by cool stalker stuff, I mean customizing site content based on the visitors location.
There are several different ways to figure out where a visitor is located, each with their pros and cons. The two main ways are IP address tracing and HTML5 geolocation. There is a great guide on developer.force.com about HTML5 geolocation that you should check out. HTML5 is all the rage these days but both methods have their advantages. In my testing HTML 5 was more accurate but it requires input from the user. The user must explicitly grant permission for the browser to find their location. Depending on the use case this may or may not work. IP address tracing on the other hand requires no input from the user and can be done completely behind the scenes unobtrusive to the user. The down side is that it is not as accurate. Another issue to consider is that some users (think your parents) will not want to click a button that gives away the location because they may think the internet is full of scams and the website will use this information to track them down and steal their identity….just sayin. So these are the two primary methods and choosing one really depends on the type of application you are building.
In this post I am going to show IP address geolocation. The code is a little long as usually try to keep the demos short and easy to understand but this is actually a really good example. It shows calling out to a web service, using cookies, and merging Apex variables in JavaScript so they can be used with Google maps.
To get a users location based on IP address I am using this webservice, http://ipinfodb.com/. It is free but limited at two queries per second. This means you could make 172,800 calls per day which is probably plenty but if not there are pay services out there. To reduce the number of request we can also store the user location in cookies. This is good for two reasons. First, we reduce the calls to the webservice. Second, if we don’t have to make a callout the page load faster.
EDIT: So if the map doesn’t load that means your IP address wasn’t found in the database which isn’t too surprising considering it’s a free service.
Click here to view the page.
Page:
<apex:page controller="IPLocation" showHeader="true" cache="false"> <apex:includeScript value="http://maps.google.com/maps/api/js?sensor=true"/> <script type="text/javascript"> window.onload = function(){ loadMap(); } //Javascript function that loads the map. Notice I can simply use the normal Visualforce merge fields for lat and long. function loadMap() { var latlng = new google.maps.LatLng({!latitude}, {!longitude}); var myOptions = { zoom: 11, center: latlng, mapTypeId: google.maps.MapTypeId.ROADMAP }; var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); var marker = new google.maps.Marker({ position: latlng, title:"IP Location" }); // To add the marker to the map, call setMap(); marker.setMap(map); } </script> <span style="font-size: 1.4em"> <strong>Related Blog Post:</strong> <a href="http://www.tehnrd.com/ip-address-geolocation-with-force-com/">IP Address Geolocation with Force.com</a><br/><br/> </span> <div id="map_canvas" style="height:500px"></div><br/> <strong>Values returned from web service:</strong> <apex:dataTable value="{!dataList}" var="d" width="300px"> <apex:column headerValue="Name" value="{!d.Name}"/> <apex:column headerValue="Value" value="{!d.Site}"/> </apex:dataTable><br/> If there are no values listed above the location was retrieved from the cookie and the web service was never called. To see web service values clear your cookies and reload this page. Make sure to click the refresh button, not place cursor in URL and hit enter.<br/><br/> <strong>XML respons:</strong> <br/><br/> {!xmlResponse} </apex:page>
Class:
public class IPLocation { String IP_LOOKUP_URL = 'http://ipinfodb.com/ip_query.php?&timezone=true&ip='; public String latitude {get; set;} public String longitude {get; set;} public List<Account> dataList {get; set;} public String xmlResponse {get; set;} public IPLocation(){ findLocation(); } public void findLocation(){ //Get the users IP address String ipAddress = ApexPages.currentPage().getHeaders().get('X-Salesforce-SIP'); Boolean lookupLocation = true; /*First check to see if user has existing cookie with current IP address. Rather than create 3 seperate cookies for IP, lat, and long I put these all in one cookie comma seperated: "245.547.54.45,45.4575,-124.4575" There is probably no advantage, it may even make this more complicated but it at least demos a different way to use cookies*/ //First check to see if cookie exists Map<String,Cookie> cookies = ApexPages.currentPage().getCookies(); if(cookies.size() > 0){ if(cookies.get('ipLocation') != null){ List<String> cookieValues = cookies.get('ipLocation').getValue().split(','); //List will contain [IP Address, Latitude, Longitude] /*Check to see if user has the same IP address as the cookie. If same there is no need to look up the location again*/ if(ipAddress == cookieValues[0]){ latitude = cookieValues[1]; longitude = cookieValues[2]; lookupLocation = false; } } } /*If there were no cookies found or the current user's IP address did not match that of the cookie we need to look up location of users IP address */ if(lookupLocation == true){ //Build HTTP request //Instantiate a new http object Http h = new Http(); //Instantiate a new HTTP request, specify the method (GET) as well as the endpoint HttpRequest req = new HttpRequest(); req.setEndpoint(IP_LOOKUP_URL + ipAddress); req.setMethod('GET'); /*We are also setting a timeout, if we are doing this callout on page load we don't want this to prevent page from loading if IP server is down*/ req.setTimeout(3000); //Send the request / call webservice HttpResponse res; Boolean calloutSuccess = true; try{ res = h.send(req); }catch(Exception e){ //Ghetto error handling but better than nothing calloutSuccess = false; } if(calloutSuccess == true){ //Inspect the response, use the XmlStreamReader so we don't have to parse response xmlResponse = res.getBody(); XmlStreamReader reader = res.getXmlStreamReader(); Map<String,String> responseValues = new Map<String,String>(); try{ while(reader.hasNext()) { System.debug('Event Type:' + reader.getEventType()); if(reader.getEventType() == XmlTag.START_ELEMENT) { String key = reader.getLocalName(); reader.next(); if(reader.getEventType() == XmlTag.CHARACTERS){ responseValues.put(key,reader.getText()); } } reader.next(); } }catch(exception e){ system.debug(e.getMessage()); } //Set the latitude and longitude latitude = responseValues.get('Latitude');// '47.482877'; longitude = responseValues.get('Longitude');// '-122.217066'; /*Next we want to create a cookies so we dont have to make unecessary callouts in the future*/ List<Cookie> newCookies = new List<Cookie>(); newCookies.add(new Cookie('ipLocation',ipAddress+','+latitude+','+longitude,null,15552000,false)); ApexPages.currentPage().setCookies(newCookies); //Populate dataList, simply to output webservice values on the page dataList = new List<Account>(); for(String s : responseValues.keySet()){ Account a = new Account(); a.Name = s; a.Site = responseValues.get(s); dataList.add(a); } } } } }

Cool