Wednesday, 6 December 2017

Visualforce: Showing nearest account locations on google map

Visualforce: Showing nearest account locations on google map

Few days back i got a requirement from my client that salesperson need a app in salesforce which allows them to do following.

Requirements:
  • When they are onsite to visit an account or prospect this salesforce app will tell them other nearby(get current location as they will be using this app from mobile) accounts location on google map.This will help them to visit more than 1 account in a day.
  • Allow them to enter a dynamic address and on the bases of that address find out nearby accounts.
Problem:
  • In order to get current/dynamic location latitude and longitude to show on map we have to use any API which will provide us latitude and longitude values in return of address.
  • All the account in the system should have their latitude and longitude values saved in a field in order to show them on map.
  • Client do not want to pay for any API.
Solution:

  • Many of you know about data integration rule. This feature is released by Salesforce few releases back. You can get a better idea of this feature from this link.
  • This will help us to get lat and lon values of all accounts resit in our database. We will use javascript to get lat and lon values of current location and dynamic entered address.
Enough of talk lets do some code :)

Visualforce page:

<apex:page docType="html-5.0" sidebar="false" Controller="accountMappingController" showHeader="true" id="page">  
    <apex:pagemessages id="message"/>
<apex:stylesheet value="{!URLFOR($Resource.bootstrap, '/dist/css/bootstrap.min.css')}"/>
<apex:includeScript value="/support/console/34.0/integration.js"/>
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>



  <script> var previousOnload = window.onload; window.onload = function() { if (previousOnload) { previousOnload(); } setUserLocation(); } </script> 

<!-- Javascript to get the user's current location and set appropriate URL call-->
<script type="text/javascript">

    
      function geocodeAddress() {
        var street = document.getElementById('page:MyId:Pb:street1').value;
        var city = document.getElementById('page:MyId:Pb:city1').value;
        var state = document.getElementById('page:MyId:Pb:state1').value;
        var country = document.getElementById('page:MyId:Pb:country1').value;
        var address = street + ' ' + city + ' ' +state + ' ' +country;
        var geocoder = new google.maps.Geocoder();
        geocoder.geocode({'address': address}, function(results, status) {
          if (status === 'OK') {
            
            var array = results[0].geometry.location.toString();
            array = array.replace(/\(|\)/g,'').split(",");
            document.getElementById("page:MyId:Pb:Lat").value = array[0];
            document.getElementById("page:MyId:Pb:Long").value = array[1];
            NewCurrentLocation();
          } else {
            alert('Geocode was not successful for the following reason: ' + status);
          }
        });
}

    

    function setUserLocation() {
        if("{!street}" && "{!city}" && "{!state}"){
        }
        else{
            if (navigator.geolocation) { 
                navigator.geolocation.getCurrentPosition(function(loc){
                    var latlon = loc.coords.latitude + "," + loc.coords.longitude;
                    var el = document.querySelector("input.currentPosition");
                    el.value = latlon;
                    generateMap();
                });
            }
            
            var gURL;
            var el2 = document.querySelector("input.googleURL");
            
            if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
                gURL = "comgooglemaps://?"; // if User device is mobile, call Google Maps mobile app url
            }
            else {
                gURL = "https://www.google.com/maps?"; // else, call Google Maps for Desktop url
            } 
            
            el2.value =  gURL;
        }
    }
    

</script>

<div class="bs" id="firstdiv">

    <div class="row">        
        <div class="col-xs-12"> <!--Block of code used to show accounts coming from controller on page  -->
            <apex:outputPanel rendered="{!IF(accounts.size==0,false,true)}" id="sectionsPanel" >              
                <apex:map width="100%" height="500px" center="{!userLocation}">   
                    <apex:repeat value="{!accounts}" var="a">
                        <apex:mapMarker icon="http://maps.google.com/intl/en_us/mapfiles/ms/micons/red-dot.png" id="accountMapMarker"
                        position="{latitude: {!a.BillingLatitude}, longitude: {!a.BillingLongitude}}">
                            
                            <apex:mapInfoWindow >
                                <apex:outputPanel layout="block" style="font-weight: bold;  font-size: 14px;">
                                    <apex:outputText >{!a.Name}</apex:outputText>
                                </apex:outputPanel>                              
                                <a href="tel:{!a.Phone}">{!a.Phone}</a>                                                             
                                <apex:outputPanel layout="block">
                                    <apex:outputText ><b>Account Type</b> {!a.Type} </apex:outputText>
                                </apex:outputPanel>
                                
                                <apex:outputPanel layout="block">
                                    <apex:outputText >{!a.BillingStreet}, {!a.BillingCity}, {!a.BillingState}</apex:outputText>
                                </apex:outputPanel>
                                <button type="button" onclick="window.open('{!googleURL}daddr={!a.BillingLatitude},{!a.BillingLongitude}&saddr={!currentPosition}'); return false;" >Get Directions</button>
                                
                               
                            </apex:mapInfoWindow>
                        </apex:mapMarker>
                    </apex:repeat>
             
                       <apex:mapMarker id="UserLocationMapMarker" position="{!currentPosition}" icon="http://maps.google.com/mapfiles/ms/micons/green-dot.png" > 
                       
                        <apex:mapInfoWindow >
                            <apex:outputPanel layout="block" style="font-weight: bold;  font-size: 14px;">
                                <apex:outputText >Current Location</apex:outputText>
                            </apex:outputPanel>
                            <apex:outputPanel layout="block">
                                <apex:outputText >Search Google Maps<BR/>for "current Location"<BR/>in your area</apex:outputText>
                            </apex:outputPanel>
                            <button type="button" onclick="window.open('{!googleURL}q=Garage+Doors&center={!currentPosition}&zoom=20'); return false;">Get Directions</button>
                            
                        </apex:mapInfoWindow>
                    </apex:mapMarker>  
                </apex:map>
            </apex:outputPanel>
        </div>
    </div>
</div> 

<apex:form id="MyId">
    <apex:outputText rendered="{!IF(accounts.size==0,true,false)}"><b>Loading Account Map...</b></apex:outputText>
    
    <apex:actionFunction action="{!findbyNewCurrentLocation}" name="NewCurrentLocation"/>
    
    <apex:actionFunction action="{!redirectByLocation}" name="generateMap" rerender="page"/> 
    <apex:input size="30" id="currentPosition" styleClass="currentPosition"  style="visibility: hidden;"  value="{!currentPosition}" />
    <apex:input size="30" id="resultsAvailable" styleClass="resultsAvailable" style="visibility: hidden;"   value="{!resultsAvailable}" />
   
    <apex:input size="1" id="googleURL" styleClass="googleURL" style="visibility: hidden;" value="{!googleURL}" /> 
    
    
  
    <apex:pageBlock title="Change Current Location" id="Pb">
            <table width="60%" align="center">
                <tr><td>
                    <b><apex:outputLabel value="Street " for="street" style="width:5%" /></b></td><td>
                    <apex:inputtext value="{!street}" id="street1" style="width:65%"/></td><td>
                    <b><apex:outputLabel value="City"  for="city" style="width:10%"/></b></td><td>
                    <apex:inputtext value="{!city}" id="city1" style="width:55%"/></td><td>
                    <b><apex:outputLabel value="State"  for="state" style="width:10%"/></b></td><td>
                    <apex:inputtext value="{!state}" id="state1" style="width:55%"/></td><td>
                    <b><apex:outputLabel value="Country"  for="country" style="width:10%"/></b></td><td>
                    <apex:inputtext value="{!country}" id="country1" style="width:55%"/></td>
                    </tr>
            </table>
  <apex:inputHidden id="Long" value="{!CusLong}" />
  <apex:inputHidden id="Lat" value="{!CusLat}" />
 
        <apex:commandButton value="Change Current Location" styleClass="buttonStyle" onclick = "geocodeAddress();"
                style="font-weight: bold;" rerender="page,MyId,message" status="loadingStatus" id="submit"/>
    </apex:pageBlock>
<apex:actionstatus id="loadingStatus">
          <apex:facet name="start">
              <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; opacity: 0.25; z-index: 1000; background-color: black;">
                  &nbsp;
              </div>
              <div style="position: fixed; left: 0; top: 0; bottom: 0; right: 0; z-index: 1001; margin: 15% 50%">
                  <div style="display: inline-block; padding: 2px; background-color: #fff; width: 125px;">
                      <img src="/img/loading.gif" style="float: left; margin: 8px;" />
                      <span style="display: inline-block; padding: 10px 0px;">Please Wait...</span>
                  </div>
              </div>
          </apex:facet>
      </apex:actionstatus>
</apex:form>

</apex:page>
Note: Billing address is used to show location on map. This works on SF1 too.

Controller Code:

public with sharing class accountMappingController {

    public Map<String, Double> userLocation { get; private set; }  
    
    public List<Account> accounts { get; set; } 
    public List<Account> prospects { get; private set; }
    public List<Account> suspects { get; private set; }
    public String googleURL { get; set; }
    public String street{ get; set; }
    public String city{ get; set; }
    public String state{ get; set; }
    public String country {get;set;}
    public string CusLat {get;set;}
    public string CusLong {get;set;}
    
    public accountMappingController (){
        Apexpages.currentPage().getHeaders().put('X-UA-Compatible', 'IE=10');
        
        
    }
    
    public String currentPosition {
        get {
            if(String.isBlank(street) && String.isBlank(city) && String.isBlank(state)){
                if (String.isBlank(currentPosition)) {
                    currentPosition = '0,0';
                }
            }
            return currentPosition;
        }
        set;
    }
    
    public Boolean resultsAvailable {
        get {
            if(accounts == Null) {
                return false;
            }
            return true;
        }
        
        set;
    }
    

    public PageReference findNearby() {
        String lat, lon;
        
        System.debug('Inside findNearby, Position='+street);
        
        List<String> latlon;
      
        latlon = currentPosition.split(',');
       
        lat = latlon[0].trim();
        lon = latlon[1].trim(); 
        
        userLocation = new Map<String, Double>{
            'latitude' =>  decimal.valueOf(lat),
            'longitude' =>  decimal.valueOf(lon)
        };
        
        accounts = new List<Account>(); 

        
        //Limited to 99 Results, Update LIMIT code Lines to change results ratio
        
         // SOQL query to get Accounts
        String queryString1 = 
            'SELECT Id, Name, Type, Phone, BillingLatitude,BillingLongitude,BillingStreet, BillingCity, BillingState ' +
            'FROM Account ' +
            'WHERE BillingLatitude != null AND BillingLongitude != null ' + 
            'ORDER BY DISTANCE(BillingAddress, GEOLOCATION('+lat+','+lon+'), \'mi\') NULLS LAST ' +
            'LIMIT 60';

            
        // run query
        accounts = database.Query(queryString1);
        
        if (0 == accounts.size()) 
            System.debug('No results. Query: ' + queryString1);
         
                 
        return null;
    }
    
    //This method is a copy of findNearby() method with the only difference that current
    //location is calculated on the basis of street, city, and state field on the UI
    public PageReference findbyNewCurrentLocation() {
        try{
            String lat, lon;
                      
            accounts = new List<Account>(); 

            currentPosition = CusLat +','+ CusLong;
            lat = CusLat.trim();
            lon = CusLong.trim();

            userLocation = new Map<String, Double>{
                'latitude' =>  decimal.valueOf(lat),
                'longitude' =>  decimal.valueOf(lon)
            };
           
       
            //Limited to 99 Results, Update LIMIT code Lines to change results ratio
            
             // SOQL query to get Accounts
            String queryString1 = 
                  'SELECT Id, Name, Type, Phone,BillingLatitude,BillingLongitude,BillingStreet, BillingCity, BillingState '+ 
                'FROM Account ' +
                'WHERE BillingLatitude !=null AND BillingLongitude !=null ' + 
                'ORDER BY DISTANCE(BillingAddress, GEOLOCATION('+lat+','+lon+'), \'mi\') NULLS LAST ' + 
                'LIMIT 60'; 
  
            // run query
            accounts = database.Query(queryString1);
            
            
            if (0 == accounts.size()) 
                System.debug('No results. Query: ' + queryString1); 
                    
            return null;
        }
        catch(exception e){ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, Label.ERR_Location_Not_found)); system.debug('Error'+ e);return null;}
        
   
        
    }
    
    //returns location data on the condition of whether street field is empty or not on UI
    public PageReference redirectByLocation(){
     
        
        //if(street !='' && city != '' && state != ''){
        if(!String.IsBlank(street) && !String.IsBlank(city) && !String.IsBlank(state) && !String.IsBlank(country)){
            return findbyNewCurrentLocation();
        }
        else{
            return findNearby();
        }
    }
    
    
    
}
Note: Label is used for showing error message.

Page View:



1 comment:

  1. Our Sales persons are always on the road. When they visit a customer (Account), they like to find out is there another customer (Account) nearby where they can visit for maximum utilization of their time. I know this scenario can be automated using the Geolocation feature. I have a formula field on the account object Location_Address__c
    Formula is ShippingStreet + ' ' + ShippingCity + ' '+ ShippingState + ' '+ShippingPostalCode

    On the Account object I have the Shipping Latitude and Shipping Longitude fields on my page layout which gives me the latitude and longitude.

    My requirement is to have a Get NearBy” button on the detail page of Account record or have something similiar which can fethch the nearby accounts.
    When the user will click on this record, we will display a list of nearby Accounts sorted by distance from the current account. The Salesperson can then decide which customers he will like to visit.
    I know we can use SQL query but I am not sure how to populate the nearest accounts.

    I have already activated the Data Integration Rules for account object. I havea activated Geocodes for Account Shipping Address and Geocodes for Account Billing Address for account object.

    ReplyDelete