Referring to part 1 and 2 (previous posts), today let's discuss how a fully dynamic store locator's are shown in Google Maps.
If you didn't read,
Refer Part 1
Here.
Refer Part 2
Here.
Step 1. Create a Google Map Developer Account and obtain a key. (See Part 1 for details)
Step 2. Add references to your page.
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
type="text/javascript"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" />
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB7Mg6XsXVsRdEOIA3sAM0CmVq03R_Y9kc"
type="text/javascript"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js" type="text/javascript"></script>
Step 3. Add HTML for map canvas and store list. (Bootstrap is used )
<div id="wrapper">
<div id="google_map">
<div id="map_canvas" style="min-height: 768px;">
</div>
</div>
<div>
<div id="over_map" class="postcodeSearch">
<div class="row">
<div class="col-lg-12">
<div class="PCHeader">
Trouvez Votre Hyperburo
</div>
</div>
</div>
<hr />
<div class="row">
<div class="col-lg-9 geolocate-me">
<i class="fa fa-chevron-right" aria-hidden="true"></i>Par géolocalisation</div>
<div class="col-lg-3 text-right">
<button class="btn btn-danger btn-sm" type="button" title="Geolocate me" onclick="geolocateme();">
<i class="fa fa-crosshairs" aria-hidden="true"></i>
</button>
</div>
</div>
<hr class="hr-devide" />
<div class="row">
<div class="col-lg-12">
<i class="fa fa-chevron-right" aria-hidden="true"></i>Par département ou code postal
</div>
</div>
<hr class="hr-devide" />
<div class="row">
<div class="col-lg-12">
<div class="input-group">
<input id="txtadPostCode" class="form-control" type="text"
placeholder="Ex: 750001"></input>
<span class="input-group-btn">
<button id="btnGoToPostCode" type="button" class="btn btn-danger" onclick="GoToPostCode();">
<i class="fa fa-hand-o-right" aria-hidden="true"></i>
</button>
</span>
</div>
</div>
</div>
</div>
</div>
<hr class="hr-devide" />
<div id="over_map_corp" class="postcodeSearch">
<div id="divCorpList" class="divCorpList">
</div>
</div>
</div>
Step 4. Add a class that has all the properties of the store locations including address details.
public class CorporateLocation
{
public int idCorporate { get; set; }
public string lbCorporate { get; set; }
public int idAddress { get; set; }
public string coMapCoordinate { get; set; }
public string Latitude { get; set; }
public string Longitude { get; set; }
public string adLine1 { get; set; }
public string adLine2 { get; set; }
public string adLine3 { get; set; }
public string adRegion { get; set; }
public string adPostCode { get; set; }
public string coCountry { get; set; }
public string lbCountry { get; set; }
public string adPhone { get; set; }
public string adFax { get; set; }
public string lbDomain { get; set; }
}
coMapCoordinate has the latitude and longitude together separated by a comma.
Ex: 6.926610,79.896203
Step 5. Create your procedure that returns the list of store location result.
CREATE PROCEDURE dbo.CorporateLocationSel (
@LatitudeFrom vcMax,
@LatitudeTo vcMax,
@LongitudeFrom vcMax,
@LongitudeTo vcMax,
@adPostCode vcPostCode = NULL)
AS
.........
If the postcode is sent to the procedure, it should look the store location table that matches to the postcode and return the list.
Else you need to filter the store locations based on latitude and longitude. For that you need to do some calculations based on user's current geolocation. Do it inside your stored procedure and return the list. For example, see the below query.
SELECT
idCorporate,
lbCorporate,
idAddress,
coMapCoordinate,
Latitude,
Longitude,
adLine1,
adLine2,
adLine3,
adRegion,
adPostCode,
coCountry,
lbCountry,
adPhone,
adFax,
lbDomain
FROM (SELECT
c.idCorporate,
c.lbCorporate,
ad.idAddress,
ad.coMapCoordinate,
SUBSTRING(ad.coMapCoordinate, 1, CASE CHARINDEX(',', ad.coMapCoordinate)
WHEN 0 THEN LEN(ad.coMapCoordinate)
ELSE CHARINDEX(',', ad.coMapCoordinate) - 1
END) AS 'Latitude',
SUBSTRING(ad.coMapCoordinate, CASE CHARINDEX(',', ad.coMapCoordinate)
WHEN 0 THEN LEN(ad.coMapCoordinate) + 1
ELSE CHARINDEX(',', ad.coMapCoordinate) + 1
END, 1000) AS 'Longitude',
ISNULL(ad.adLine1, '') AS adLine1,
ISNULL(ad.adLine2, '') AS adLine2,
ISNULL(ad.adLine3, '') AS adLine3,
ad.adCity,
ISNULL(ad.adRegion, '') AS adRegion,
ad.adPostCode,
ad.coCountry,
cn.lbCountry,
ISNULL(c.adPhone, '') AS adPhone,
ISNULL(c.adFax, '') AS adFax,
ISNULL(dns.lbDomain, '') AS lbDomain
FROM dbo.cstCorporate AS c
INNER JOIN dbo.gblAddress AS ad
ON c.idInvoiceAddress = ad.idAddress
JOIN dbo.gblCountry cn
ON ad.coCountry = cn.coCountry
LEFT JOIN dbo.cmcDns dns
ON dns.idCorporateLocal = c.idCorporate) Address
WHERE Latitude BETWEEN @LatitudeFrom AND @LatitudeTo
AND Longitude BETWEEN @LongitudeFrom AND @LongitudeTo
The @LatitudeFrom, @LatitudeTo, @LongitudeFrom, @LongitudeTo are 4 parameters that we used with min and max values of latitude and longitude sent from web method that contains the user's current location.
Always better to keep a 1 degree difference between min and max values of latitude and longitude where the diameter of the area could equal to 111 kms. (Each degree of latitude is
approximately 69 miles (111 kilometers) apart.)
Step 6. Add a web method that returns a serialized list of store location objects.
[WebMethod]
public static string GetLocations(decimal Latitude, decimal Longitude, string adPostCode)
{
var clList = new cbCorporateLocation().SelStoreLocation(Latitude, Longitude, adPostCode);
JavaScriptSerializer Serializer = new JavaScriptSerializer();
return Serializer.Serialize(clList);
}
Step 7. In your Business Logic Layer, you can define the min and max latitude / longitude values.
public List<CorporateLocation> SelCorporateLocation(decimal Latitude, decimal Longitude, string adPostCode)
{
decimal LaUpper = Latitude + new decimal(0.5);
decimal LaLower = Latitude - new decimal(0.5);
decimal LoUpper = Longitude + new decimal(0.5);
decimal LoLower = Longitude - new decimal(0.5);
if (adPostCode == "0")
adPostCode = null;
......... continue your method ...
Step 8. Now you have the list of stores and you need to show them on Google Map. For that you have a bit of JavaScript works to do. Here I customize the map window for a custom theme. Look at the below JS functions.
<script type="text/javascript">
//common function : get query string parameter values
function getParameterByName(name, url) {
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
//common function : if the returned url does not have http; add it here
function addhttp(url) {
if (!/^(f|ht)tps?:\/\//i.test(url)) {
url = "http://" + url;
}
return url;
}
// use JQuery ready function
$(document).ready(function () {
$("#over_map").draggable(); //make the post code search box draggable
$("#over_map_corp").draggable(); //make the store list box draggable
$("#over_map").hide(); //hide the post code search box until map is loaded
$("#over_map_corp").hide(); //hide the store list box until map is loaded
var adPostCode = getParameterByName('adPostCode'); //get url parameter [postcode]
$("#txtadPostCode").val(adPostCode); //append to textbox
getLocation(adPostCode);
});
// get locations and draw them on map
function GetLocations(Latitude, Longitude, adPostCode) {
var objPara = {
Latitude: Latitude,
Longitude: Longitude,
adPostCode: adPostCode
};
$.ajax({
type: "POST",
url: "Default.aspx/GetLocations",
data: JSON.stringify(objPara),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (msg) {
var ret = JSON.parse(msg.d);
var LaFrom = 0;
var LaTo = 0;
var LoFrom = 0;
var LoTo = 0;
//this each will get the median values of latitude and longitude of returned values
$.each(ret, function (i, val) {
if (LaFrom == 0)
LaFrom = val.Latitude;
else {
if (val.Latitude < LaFrom)
LaFrom = val.Latitude;
}
if (LaTo == 0)
LaTo = val.Latitude;
else {
if (val.Latitude > LaTo)
LaTo = val.Latitude;
}
if (LoFrom == 0)
LoFrom = val.Longitude;
else {
if (val.Longitude < LoFrom)
LoFrom = val.Longitude;
}
if (LoTo == 0)
LoTo = val.Longitude;
else {
if (val.Longitude > LoTo)
LoTo = val.Longitude;
}
});
var la = (parseFloat(LaFrom) + parseFloat(LaTo)) / 2;
var lo = (parseFloat(LoFrom) + parseFloat(LoTo)) / 2;
if (la != 0 && lo != 0) {
Latitude = la;
Longitude = lo;
}
// map with ceter values and zoom
var map = new google.maps.Map(document.getElementById('map_canvas'),
{
zoom: 10,
center: new google.maps.LatLng(Latitude, Longitude),
mapTypeId: google.maps.MapTypeId.ROADMAP
});
//if post code is sent, change the zoom value
if (!adPostCode)
map.setZoom(10);
else
map.setZoom(8);
var infowindow = new google.maps.InfoWindow({ pixelOffset: new google.maps.Size(-10, 10) });
var marker, i;
var corpList = "";
$.each(ret, function (i, val) {
marker = new google.maps.Marker(
{
position: new google.maps.LatLng(val.Latitude, val.Longitude),
map: map,
icon: '2.png' //custom icon
});
var address = '';
if (val.adLine1.length > 0)
address += val.adLine1;
if (val.adLine2.length > 0)
address += val.adLine2;
if (val.adLine3.length > 0)
address += val.adLine3;
var postcode = '';
if (val.adPostCode.length > 0)
postcode += '<br/>' + val.adPostCode;
var country = '';
if (val.lbCountry.length > 0)
country += '<br/>' + val.lbCountry;
var phone = '';
if (val.adPhone.length > 0)
phone += '<br/> Phone : ' + val.adPhone;
var fax = '';
if (val.adFax.length > 0)
fax += '<br/> Fax : ' + val.adFax;
var lbDomain = '';
if (val.lbDomain.length > 0)
lbDomain += '<div class="iw-link" onclick="fnGoToResellerSite(\'' + val.lbDomain + '\')" title=' + val.lbDomain + '> Voir la fiche </div>';
var divId = "div" + val.idCorporate;
corpList += '<div class="row" id=' + divId + ' ><div class="col-lg-12"><div class="panel panel-red"><div class="panel-heading"><i class="fa fa-map-marker" aria-hidden="true"></i> ' + capitalizeFirstLetter(val.lbCorporate) + '</div><div class="panel-body">' + capitalizeFirstLetter(address) + postcode + capitalizeFirstLetter(country) + '</div></div></div></div>';
//the infowindow content to show the store location when clicked
var contentString = '<div id="iw_content"><div id="iw-container">' +
'<div class="iw-title">' + val.lbCorporate + '</div>' +
'<div class="iw-content">' +
'<div class="iw-address">' + address + postcode + country + '</div>' +
'<div class="iw-contact">' + phone + fax + '</div>' +
lbDomain +
'</div>' +
'</div></div>';
// add click event to map
google.maps.event.addListener(marker, 'click', (function (marker, i) {
return function () {
infowindow.setContent(contentString);
infowindow.pixelOffset = new google.maps.Size(-10, 10);
infowindow.open(map, marker);
//DRAWING THE InfoWindow
// Reference to the DIV that wraps the bottom of infowindow
var iwOuter = $('.gm-style-iw');
var iwBackground = iwOuter.prev();
// Removes background shadow DIV
iwBackground.children(':nth-child(2)').css({ 'display': 'none' });
// Removes white background DIV
iwBackground.children(':nth-child(4)').css({ 'display': 'none' });
// Moves the infowindow 115px to the right.
iwOuter.parent().parent().css({ left: '115px' });
// Moves the shadow of the arrow 76px to the left margin.
iwBackground.children(':nth-child(1)').attr('style', function (i, s) { return s + 'left: 76px !important;' });
// Moves the arrow 76px to the left margin.
iwBackground.children(':nth-child(3)').attr('style', function (i, s) { return s + 'left: 76px !important;' });
// Changes the desired tail shadow color.
iwBackground.children(':nth-child(3)').find('div').children().css({ 'box-shadow': 'rgba(72, 181, 233, 0.6) 0px 1px 6px', 'z-index': '1' });
// Reference to the div that groups the close button elements.
var iwCloseBtn = iwOuter.next();
// Apply the desired effect to the close button
iwCloseBtn.css({ opacity: '1', right: '38px', top: '3px', border: '7px solid #d01320', 'border-radius': '13px' });
// The API automatically applies 0.7 opacity to the button after the mouseout event. This function reverses this event to the desired value.
iwCloseBtn.mouseout(function () {
$(this).css({ opacity: '1' });
});
//setting arrow color
iwOuter.parent().children(':nth-child(1)').children(':nth-child(3)').children(':nth-child(1)').children(':nth-child(1)').css({ 'border-color': '#d01320', 'border-style': 'solid', 'border-width': '0 0 0 1px' });
iwOuter.parent().children(':nth-child(1)').children(':nth-child(3)').children(':nth-child(2)').children(':nth-child(1)').css({ 'border-color': '#d01320', 'border-style': 'solid', 'border-width': '0 1px 0 0' });
map.setCenter(marker.getPosition());
}
})(marker, i));
});
//binding store list here
$('#divCorpList').html('');
$('#divCorpList').append(corpList);
$.each(ret, function (i, val) {
var address = '';
if (val.adLine1.length > 0)
address += val.adLine1;
if (val.adLine2.length > 0)
address += val.adLine2;
if (val.adLine3.length > 0)
address += val.adLine3;
var postcode = '';
if (val.adPostCode.length > 0)
postcode += '<br/>' + val.adPostCode;
var country = '';
if (val.lbCountry.length > 0)
country += '<br/>' + val.lbCountry;
var phone = '';
if (val.adPhone.length > 0)
phone += '<br/> Phone : ' + val.adPhone;
var fax = '';
if (val.adFax.length > 0)
fax += '<br/> Fax : ' + val.adFax;
var lbDomain = '';
if (val.lbDomain.length > 0)
lbDomain += '<div class="iw-link" onclick="fnGoToResellerSite(\'' + val.lbDomain + '\')" title=' + val.lbDomain + '> Voir la fiche </div>';
var contentString = '<div id="iw_content"><div id="iw-container">' +
'<div class="iw-title">' + val.lbCorporate + '</div>' +
'<div class="iw-content">' +
'<div class="iw-address">' + address + postcode + country + '</div>' +
'<div class="iw-contact">' + phone + fax + '</div>' +
lbDomain +
'</div>' +
'</div></div>';
var divId = "div" + val.idCorporate;
var geocoder = new google.maps.Geocoder;
var elem = document.getElementById(divId);
if (elem) {
elem.addEventListener('click', function () {
infowindow.setContent(contentString);
infowindow.pixelOffset = new google.maps.Size(-115, 10);
geocodeLatLng(geocoder, map, infowindow, val.coMapCoordinate, contentString);
});
}
});
$("#over_map").fadeIn(3000);
$("#over_map_corp").fadeIn(3000);
},
error: function (err) {
alert(err.responseText);
}
});
}
//if user click on a single store from the list shown in the list, this method is popping up the address of the given point
function geocodeLatLng(geocoder, map, infowindow, latlng, contentString) {
var latlngStr = latlng.split(',', 2);
var latlng = { lat: parseFloat(latlngStr[0]), lng: parseFloat(latlngStr[1]) };
geocoder.geocode({ 'location': latlng }, function (results, status) {
if (status === 'OK') {
if (results[1]) {
map.setZoom(16);
var marker = new google.maps.Marker({
position: latlng,
map: map,
icon: '2.png'
});
map.setCenter(marker.getPosition());
infowindow.open(map, marker);
var iwOuter = $('.gm-style-iw');
var iwBackground = iwOuter.prev();
iwBackground.children(':nth-child(2)').css({ 'display': 'none' });
iwBackground.children(':nth-child(4)').css({ 'display': 'none' });
iwOuter.parent().parent().css({ left: '115px' });
iwBackground.children(':nth-child(1)').attr('style', function (i, s) { return s + 'left: 76px !important;' });
iwBackground.children(':nth-child(3)').attr('style', function (i, s) { return s + 'left: 76px !important;' });
iwBackground.children(':nth-child(3)').find('div').children().css({ 'box-shadow': 'rgba(72, 181, 233, 0.6) 0px 1px 6px', 'z-index': '1' });
var iwCloseBtn = iwOuter.next();
iwCloseBtn.css({ opacity: '1', right: '38px', top: '3px', border: '7px solid #d01320', 'border-radius': '13px' });
iwCloseBtn.mouseout(function () {
$(this).css({ opacity: '1' });
});
iwOuter.parent().children(':nth-child(1)').children(':nth-child(3)').children(':nth-child(1)').children(':nth-child(1)').css({ 'border-color': '#d01320', 'border-style': 'solid', 'border-width': '0 0 0 1px' });
iwOuter.parent().children(':nth-child(1)').children(':nth-child(3)').children(':nth-child(2)').children(':nth-child(1)').css({ 'border-color': '#d01320', 'border-style': 'solid', 'border-width': '0 1px 0 0' });
google.maps.event.addListener(marker, 'click', (function (marker) {
return function () {
infowindow.setContent(contentString);
infowindow.pixelOffset = new google.maps.Size(-10, 10);
infowindow.open(map, marker);
var iwOuter = $('.gm-style-iw');
var iwBackground = iwOuter.prev();
iwBackground.children(':nth-child(2)').css({ 'display': 'none' });
iwBackground.children(':nth-child(4)').css({ 'display': 'none' });
iwOuter.parent().parent().css({ left: '115px' });
iwBackground.children(':nth-child(1)').attr('style', function (i, s) { return s + 'left: 76px !important;' });
iwBackground.children(':nth-child(3)').attr('style', function (i, s) { return s + 'left: 76px !important;' });
iwBackground.children(':nth-child(3)').find('div').children().css({ 'box-shadow': 'rgba(72, 181, 233, 0.6) 0px 1px 6px', 'z-index': '1' });
var iwCloseBtn = iwOuter.next();
iwCloseBtn.css({ opacity: '1', right: '38px', top: '3px', border: '7px solid #d01320', 'border-radius': '13px' });
iwCloseBtn.mouseout(function () {
$(this).css({ opacity: '1' });
});
map.setCenter(marker.getPosition());
iwOuter.parent().children(':nth-child(1)').children(':nth-child(3)').children(':nth-child(1)').children(':nth-child(1)').css({ 'border-color': '#d01320', 'border-style': 'solid', 'border-width': '0 0 0 1px' });
iwOuter.parent().children(':nth-child(1)').children(':nth-child(3)').children(':nth-child(2)').children(':nth-child(1)').css({ 'border-color': '#d01320', 'border-style': 'solid', 'border-width': '0 1px 0 0' });
}
})(marker));
} else {
GetLocations(48.864716, 2.349014, 0);
}
} else {
GetLocations(48.864716, 2.349014, 0);
}
});
}
//common function : capitalize first letter of the words in a string
function capitalizeFirstLetter(string) {
return string.toLowerCase().replace(/\b[a-z]/g, function (letter) {
return letter.toUpperCase();
});
}
//common function : go to each store website
function fnGoToResellerSite(lbDomain) {
lbDomain = addhttp(lbDomain);
window.open(lbDomain, '_blank');
}
//common function : get geolocation of the user and load the store list
function getLocation(adPostCode) {
if (adPostCode && adPostCode.length > 0) {
GetLocations(0, 0, adPostCode);
}
else {
if (navigator.geolocation) {
var timeoutVal = 10 * 1000 * 1000;
navigator.geolocation.getCurrentPosition(showPosition, showDefault, { enableHighAccuracy: true, timeout: timeoutVal, maximumAge: 0 });
} else {
GetLocations(48.864716, 2.349014, 0);
}
}
}
function showPosition(position) {
GetLocations(position.coords.latitude, position.coords.longitude, 0);
}
function showDefault(error) {
GetLocations(48.864716, 2.349014, 0);
}
function geolocateme() {
getLocation('');
}
//common function : search by postcode
function GoToPostCode() {
var txtadPostCode = $("#txtadPostCode").val();
if (txtadPostCode)
getLocation(txtadPostCode);
}
</script>
Step 9. Run your project. :)
Leave a comment if you need any help..
And try this, it will be fun.
Happy coding.. :)