allBlogsList

Coveo Search widget mapped to google map for searching based on location and distance

Google earlier used to allow usage of its search api free of cost but not no more. 

1- So, the first and foremost step is to get your own google api key, with permissions at least for "Geocodong API" and "Maps Javascript API".

2- Create a copy of the file "...\Website\Views\Coveo\SearchView.cshtml" and name it appropriately. In my example I named it as "CoveoGoogleSearchView.cshtml". This will be the file we will customize now to incorporate google map based search.

3- Open the file "CoveoGoogleSearchView.cshtml".

4- Create a duplicate of "/sitecore/layout/Renderings/Coveo/Coveo Search View" item. For my example, I have named it as "Coveo Google Search View".

5- Right on the top, add the following:

@{
var location = Request.QueryString.HasKeys() && Request.QueryString["loc"] != null ? Request.QueryString["loc"] : "null";
var _distance = Request.QueryString.HasKeys() && Request.QueryString["dis"] != null ? Request.QueryString["dis"] : "null";

    int distance = 0;
if (_distance != "null")
{
distance = int.Parse(_distance);
}

    var query = Request.QueryString.HasKeys() && Request.QueryString["query"] != null ? Request.QueryString["query"] : "null";

    string _googleAPIKey = Sitecore.Configuration.Settings.GetSetting("GoogleAPIKey", "NULL");
}

As you can see, I am getting the location and distance along with the actual "query" paramters from the query string. I am also getting the "googleapikey" from the config file.

6- You can then replace the usage of SearchBox component with your own section as below:

           <div class="coveo-results-column">

                <div>
<div>
Location
<input id="location" type="text" placeholder="Enter location zip/city here" />
</div>
<div>
Distance (in miles)
<input id="distance" type="text" placeholder="Enter distance in miles" />
</div>
<div>
<input id="query" type="text" placeholder="Enter search query" />
</div>

                </div>

                    <button id="btn">Search</button>
</div>

7- Right after this, add the following <div> that would contain the google map:

              <div id="map_populate" style="width:350px; height:200px; border: 5px solid #5E5454;"></div>

8- In your coveo result template rows, you can add this new row to show the result's distance in miles, from the location specified in "location" search box above:
<tr>
<th class='CoveoCaption'>Distance</th>
<td class='CoveoCaption'>
{{=(raw.distance/1609.344).toFixed(2)}} miles
</td>
</tr>
Note the quick calculation here where I have converting the distance into miles from default meters.

9- Anywhere in this file, add the following to use the google map api:

          <script src='@string.Format("https://maps.googleapis.com/maps/api/js?v=3&key={0}", _googleAPIKey)'></script>

Make sure your site uses "https" as otherwise the api will fail.

10- In a new javascript block, declare the global variables to store the longitude and latitude values calculated from the given "location" value above.

        var refLat = null;
var refLng = null;

11- In the beginning of script block "Coveo.$(function () {", add the "buildingQuery" event handler to modify search parameters:

            $('#@Model.Id').on("buildingQuery", function (e, args) {

                    if (refLat && refLng) {
$('.CoveoResultList').show();
$('.coveo-results-header').show();
$('.CoveoPager').show();
$('.CoveoResultsPerPage').show();

                        var distance = document.getElementById("distance").value; // Value in miles
distance = distance * 1609.344; // value in metres
var geoQuery = "$qf(function:'dist(@(Model.ToCoveoFieldName("latitude")),@(Model.ToCoveoFieldName("longitude"))," + refLat + "," + refLng + ")', fieldName: 'distance') @@distance<" + distance;
if (args && args.queryBuilder) {
args.queryBuilder.advancedExpression.add(geoQuery);
}
}
else {
$('.CoveoResultList').hide();
$('.coveo-results-header').hide();
$('.CoveoPager').hide();
$('.CoveoResultsPerPage').hide();
}

    args.queryBuilder.advancedExpression.add("@(Model.ToCoveoFieldName("[query field]")) = \"" + document.getElementById("query").value + "\"");
});

Here we are making sure longiture and latitude values are available and only then show the results block. 
(a) We are first reading the "distance" from the search box section (step 4) above, 
(b) converting it to meters (as google api works on meters), 
(c) using the query function we are generating a dynamic coveo field called "distance" betweeen the queried result item's longitude and latitude and longitude ("refLng") and latitude ("refLat") computed from the "location" value specified above in the search box section (step 4). This query function then makes sure the computed dynamic field "distance"  is within the value of "distance" provided above in the search box section (step 4).
(d) We are then finally adding the actual "query" field in the generated query expression.

12- Create the "querySuccess" handler:

            Coveo.$('#@Model.Id').on("querySuccess", function (e, args) {
args.results.results.forEach(function (result) {
if (result.raw) {
getmap(result.raw.@(Model.ToCoveoFieldName("city", false)));
}
});

                if (args.results.results.length != 0) {
$('.CoveoResultList').show();
$('.coveo-results-header').show();
$('.CoveoPager').show();
$('.CoveoResultsPerPage').show();

                }
else {
$('.CoveoResultList').hide();
$('.coveo-results-header').hide();
$('.CoveoPager').hide();
$('.CoveoResultsPerPage').hide();
}
});

Here we are traversing through the results and calling a function to mark the result locations in google map.

13- Lets now add the click handler for "search" button in the search box section (step 4):

            var button = document.getElementById('btn');

            button.addEventListener("click", function () {
var location = document.getElementById('location').value;
var distance = document.getElementById('distance').value;
var query = document.getElementById('query').value;

                    if (location.length != 0 && distance.length != 0 && query.length != 0) {

                        var href = "/CoveoGoogleSearch" + "?query=" + query + "&dis=" + distance + "&loc=" + location;

                        window.location.href = href;

                    }
});

Here we are reading the parameters from search box section (step 4) and then re-loading the search page item, in my case I named it as "CoveoGoogleSearch", passing the search parameters.

14- Define this function that calls the callback function once it is able to get the longitude and latitude given the address:

        function getLatitudeLongitude(callback, address) {
if (address!="NULL") {
// Initialize the Geocoder
geocoder = new google.maps.Geocoder();
if (geocoder) {
geocoder.geocode({
'address': address
}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
callback(results[0]);
}
});
}
}
else {
callback(address);
}
}

15- This is the callback function that is called above in step 12:

        function showResult(result) {
if (result!="NULL") {
refLat = result.geometry.location.lat();
refLng = result.geometry.location.lng();
initialize(refLat, refLng);
}

            Coveo.$('#@Model.Id').coveo('executeQuery');
}

This calls the google api function to initialize geocoder and then triggers the query.

16- Add the below function:

        function initLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
x.innerHTML = "Geolocation is not supported by this browser.";
}

        }

        function showPosition(position) {
initialize(position.coords.latitude, position.coords.longitude);
}

"initLocation" is the base function called to show the user's current location in the map, even before they enter the "location" parameter in search box section.

17- This is the function that is called to initialize and plot the user's current location on the google map:

        function initialize(lat, long) {
mapcode = new google.maps.Geocoder();
var lnt = new google.maps.LatLng(lat, long);
var diagChoice = {
zoom: 9,
center: lnt,
diagId: google.maps.MapTypeId.ROADMAP
}
diag = new google.maps.Map(document.getElementById('map_populate'), diagChoice);
}

18- This is the function to plot the result locations (step 10) on google map:

        function getmap(address) {
mapcode.geocode({ 'address': address }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
diag.setCenter(results[0].geometry.location);
var hint = new google.maps.Marker({
map: diag,
position: results[0].geometry.location
});
} else {
alert('Location Not Tracked. ' + status);
}
});
}

19- The final script block that is used to start the entire chain of functions, once it is able to read the location, distance and query from the query strings.

        $(document).ready(function () {

            var location = "@location";
var distance = @distance;
var query = "@query";

            if ((('@_googleAPIKey' != 'NULL' && location != "null" && distance != 0)) && query != "null") {
if (location != "null") {
$('#location').val(location);
}
if (distance != 0) {
$('#distance').val(distance);
}
if (query != "null") {
$('#query').val(query);
}

                var address = document.getElementById('location').value;
getLatitudeLongitude(showResult, address);
}
else {
initLocation();
}

        });

20- The entire code is here.

21- Create a copy of "/sitecore/layout/Renderings/Coveo/Coveo Search View", call this say "Coveo Google Search View", and use this new file we just created "CoveoGoogleSearchView.cshtml" as its file.

22- Create an item out of default "/sitecore/templates/CoveoModule/Search/Coveo Search Page MVC".

23- In the presentation setting of this item, replace usage of default "coveo search view" with your new component "Coveo Google Search View" as in step 18.

24- You are now ready to test your new search page.