Google Maps API Integrations for Geolocation, Directions, & Redrawing Pins based on User Search
/**
* Get HTML5 Geolocation values for Latitude, Longitude & State values
*/
var userLat = bridgeChildGetCookie( 'bridgeChildLatitude' ),
userLng = bridgeChildGetCookie( 'bridgeChildLongitude' ),
userState = bridgeChildGetCookie( 'bridgeChildState' );
/**
* Get Retailers Object from localized variable
*/
var retailerPins = [];
/**
* Map initialization function.
*/
var geocoder;
var map;
function initMap() {
geocoder = new google.maps.Geocoder();
var mapOptions = {
center: {
lat: parseFloat( userLat ),
lng: parseFloat( userLng )
},
scrollwheel: false,
mapTypeControl: false,
streetViewControl: false,
zoom: 7,
styles: [
{
"featureType": "landscape.natural",
"elementType": "geometry.fill",
"stylers": [
{
"visibility": "on"
},
{
"color": "#e0efef"
}
]
},
{
"featureType": "poi",
"elementType": "geometry.fill",
"stylers": [
{
"visibility": "on"
},
{
"hue": "#1900ff"
},
{
"color": "#c0e8e8"
}
]
},
{
"featureType": "poi",
"elementType": "labels.text",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "poi",
"elementType": "labels.icon",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "road",
"elementType": "geometry",
"stylers": [
{
"lightness": 100
},
{
"visibility": "simplified"
}
]
},
{
"featureType": "road",
"elementType": "labels",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "transit.line",
"elementType": "geometry",
"stylers": [
{
"visibility": "on"
},
{
"lightness": 700
}
]
},
{
"featureType": "transit.line",
"elementType": "labels.text",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "transit.line",
"elementType": "labels.text.fill",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "transit.line",
"elementType": "labels.text.stroke",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "transit.line",
"elementType": "labels.icon",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "transit.station",
"elementType": "labels.text",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "transit.station",
"elementType": "labels.icon",
"stylers": [
{
"visibility": "off"
}
]
},
{
"featureType": "water",
"elementType": "all",
"stylers": [
{
"color": "#7dcdcd"
}
]
}
]
};
map = new google.maps.Map(document.getElementById('map-container'), mapOptions);
/**
* Set the map marker pins if we have retailerPins available.
*/
if ( typeof retailerPins !== 'undefined' ) {
setMarkers(map);
}
directionsDisplay = new google.maps.DirectionsRenderer();
directionsDisplay.setMap(map);
/**
* Add an event listener for geocode search if submitGeocode element is present.
*/
if( document.getElementById( 'submitGeocode' ) ) {
document.getElementById('submitGeocode').addEventListener('click', function() {
geocodeAddress();
});
}
}
/**
* Set markers on the map function.
*
* @param map
* @param retailerList
*/
function setMarkers(map, retailerList) {
if ( typeof retailerList === 'undefined' ) {
retailerList = bridgeChildGeoVars['bridgeChildRetailers'];
}
/**
* URI of the active theme.
*/
var iconURLPrefix = bridgeChildGeoVars['bridgeChildThemeURI'];
var icons = {
'bedbreakfastlodging': iconURLPrefix + '/assets/images/icon-lodging.png',
'restaurantcoffee-shopbakery': iconURLPrefix + '/assets/images/icon-coffee.png',
'other-retailer': iconURLPrefix + '/assets/images/icon-retailer.png',
'college-bookstoreeducational': iconURLPrefix + '/assets/images/icon-education.png',
'golf-courses': iconURLPrefix + '/assets/images/icon-golf.png',
'military': iconURLPrefix + '/assets/images/icon-military.png',
'museum': iconURLPrefix + '/assets/images/icon-museum.png',
'state-or-national-parks': iconURLPrefix + '/assets/images/icon-park.png'
};
jQuery('.retailer-list-container').html('');
for (var i = 0; i < retailerList.length; i++) {
var retailer = retailerList[i];
var retailerSlug;
if ( typeof retailer.industry !== 'boolean' ) {
retailerSlug = retailer.industry[0]['slug'];
} else {
retailerSlug = 'other-retailer';
}
// Add Markers to Map
var marker = new google.maps.Marker({
position: {
lat: parseFloat(retailer['lat']),
lng: parseFloat(retailer['lng'])
},
map: map,
title: retailer['post_title'],
content: retailer['address']['address'],
lat: retailer['lat'],
lng: retailer['lng'],
address: retailer['a_address'],
city: retailer['a_city'],
state: retailer['a_state'],
icon: icons[retailerSlug]
});
retailerPins.push(marker);
var infowindow = new google.maps.InfoWindow();
google.maps.event.addListener(marker, 'click', function() {
infowindow.setContent(
'<div class="map-info-wrap">' +
'<div class="map-info-title">' + this.title + '</div>' +
'<div class="map-info-content">' + this.content + '</div>' +
'<div class="map-links">' +
'<a data-address="' + this.address + ', ' + this.city + ', ' + this.state + '" onclick="setDirectionEnd(this);">Directions To</a>' +
'<a data-lat="' + this.lat + '" data-lng="' + this.lng + '" onclick="zoomToPin(this)">Zoom</a>' +
'</div>' +
'</div>'
);
infowindow.open(map, this);
});
(function($){
var listContainer = $('.retailer-list-container');
listContainer.append(
'<div class="retailer">' +
'<div class="retailer-icon"><img src="' + icons[retailerSlug] + '"/></div>' +
'<div class="retailer-title">' + retailer.post_title + '</div>' +
'<div class="retailer-location">' + retailer.a_city + ', ' + retailer.a_state + '</div>' +
'<a href="' + retailer.a_website + '">Website</a>' +
'</div>'
);
})(jQuery);
}
}
/**
* Set map boundaries to contain all pins
*/
function setMapBoundaries() {
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < retailerPins.length; i++) {
bounds.extend(retailerPins[i].getPosition());
}
map.fitBounds(bounds);
if ( retailerPins.length <= 1 ) {
var listener = google.maps.event.addListener(map, 'idle', function(){
if (map.getZoom() > 11 ) {
map.setZoom(11);
}
google.maps.event.removeListener(listener);
});
}
}
/**
* Function to clear all the markers on the map
*/
function clearMarkers() {
for (var i = 0; i < retailerPins.length; i++) {
retailerPins[i].setMap(null);
}
retailerPins = [];
}
/**
* Retrieve retailers by the "state" taxonomy.
*
* @param state
*/
function getRetailersByState( state ) {
var get_retailers_by_state = {
"action" : "get_retailers_by_state",
"state" : state
};
return get_retailers_by_state;
}
/**
* Retrieve retailers by the "industry" taxonomy.
*
* @param industry
*/
function getRetailersByIndustry( industry ) {
var get_retailers_by_industry = {
"action" : "get_retailers_by_industry",
"industry" : industry
};
return get_retailers_by_industry;
}
/**
* Retrieve retailers by both the "industry" and "state" taxonomy.
*
* @param {string} industry
* @param {string} state
*/
function getRetailersByIndustryAndState( industry, state ) {
var get_retailers_by_industry_and_state = {
"action" : "get_retailers_by_industry_and_state",
"industry" : industry,
"state" : state
};
return get_retailers_by_industry_and_state;
}
/**
* Get initial set of pins based on users location
*/
function getInitialLocationPins() {
var userStateCode = userState.toLowerCase();
getRetailersByState( userStateCode );
}
/**
* Geocode address for dealer search function.
*/
var stateSearch,
stateSearchValue,
industrySearch,
industrySearchValue,
searchRadius,
radiusCircle,
mileRadius;
function geocodeAddress() {
stateSearch = document.getElementById('search-by-state');
stateSearchValue = stateSearch[stateSearch.selectedIndex].value;
industrySearch = document.getElementById('search-by-industry');
industrySearchValue = industrySearch[industrySearch.selectedIndex].value;
searchRadius = document.getElementById('search-radius').value;
var searchParameters;
if ( stateSearchValue !== 'all' && industrySearchValue !== 'all' ) {
searchParameters = getRetailersByIndustryAndState( industrySearchValue, stateSearchValue );
} else if ( stateSearchValue !== 'all' && industrySearchValue === 'all' ) {
searchParameters = getRetailersByState( stateSearchValue );
} else if ( industrySearchValue !== 'all' && stateSearchValue === 'all' ) {
searchParameters = getRetailersByIndustry( industrySearchValue );
}
jQuery.ajax(
{
url : bridgeChildRetailerEndpoint + JSON.stringify( searchParameters ),
beforeSend: function() {
// loading gif while search function is being run
jQuery('#loading-animation').fadeIn();
},
complete: function() {
// stop the loading gif
jQuery('#loading-animation').fadeOut();
},
type : 'GET'
}
)
.done(
function( data, textStatus, jqXHR ) {
console.log( "HTTP Request Succeded: " + jqXHR.status );
console.log( data );
if ( data.success === true ) {
clearMarkers();
var retailerList = data['data'];
setMarkers(map, retailerList);
setMapBoundaries();
jQuery('.error-alert').fadeOut();
} else {
jQuery('#search-error-alert').fadeIn();
}
}
)
.fail(
function( jqXHR, textStatus, errorThrown ) {
console.log( "HTTP Request Failed" );
}
);
/**
* Geocode map on search function
*/
geocoder.geocode( {'address': stateSearchValue}, function(results, status) {
if ( status == google.maps.GeocoderStatus.OK ) {
clearMarkers();
getRetailersByState( stateSearchValue );
radiusCircle = new google.maps.Circle();
if ( searchRadius === '' || searchRadius === null || searchRadius === undefined || searchRadius === 0 ) {
// If search radius isn't set, it defaults to 200 miles
mileRadius = 200 * 1069;
} else {
mileRadius = searchRadius * 1069;
}
radiusCircle.setRadius(mileRadius);
radiusCircle.setCenter(results[0].geometry.location);
map.setCenter(results[0].geometry.location);
map.fitBounds(radiusCircle.getBounds());
} else {
alert( status );
}
});
}
/**
* Set end address as destination in directions form
*
* @param address
*/
function setDirectionEnd( address ) {
var endAddress = jQuery(address).attr('data-address'),
endValue = jQuery('#endDirection');
endValue.attr('value', endAddress);
jQuery('#directions-form').fadeIn();
}
/**
* Zoom to Pin
*
* @param location
*/
function zoomToPin( location ) {
var pinLat = jQuery(location).attr('data-lat'),
pinLng = jQuery(location).attr('data-lng');
console.log('Lat: ' + pinLat + ' Lng: ' + pinLng);
map.setZoom(15);
map.setCenter({lat: parseFloat(pinLat), lng: parseFloat(pinLng) });
}
/**
* Set active Travel Mode for calculating directions
*/
(function($){
$('#directions-form .select-travel-mode').click(function(e) {
e.preventDefault();
$(this).addClass('active');
$(this).siblings().removeClass('active');
var directionsList = $('#directions-list-container');
var clickedItem = jQuery(this).attr('id');
directionsList.find('#' + clickedItem).addClass('active');
directionsList.find('#' + clickedItem).siblings().removeClass('active');
});
$('#directions-list-container .select-travel-mode').click(function(e) {
e.preventDefault();
$(this).addClass('active');
$(this).siblings().removeClass('active');
var directionsForm = $('#directions-form');
var clickedItem = jQuery(this).attr('id');
directionsForm.find('#' + clickedItem).addClass('active');
directionsForm.find('#' + clickedItem).siblings().removeClass('active');
calcRoute();
});
})(jQuery);
/**
* Directions initialization function.
*/
var directionsDisplay = new google.maps.DirectionsRenderer();
var directionsService = new google.maps.DirectionsService();
function calcRoute() {
// Get user selected travel mode
var activeMode = jQuery('#travel-modes').find('.active').attr('id');
var selectedTravelMode;
if ( activeMode === 'travel-mode-transit' ) {
selectedTravelMode = 'TRANSIT';
}
else if ( activeMode === 'travel-mode-walking' ) {
selectedTravelMode = 'WALKING';
}
else if ( activeMode === 'travel-mode-bicycle' ) {
selectedTravelMode = 'BICYCLING';
}
else {
selectedTravelMode = 'DRIVING';
}
var start = document.getElementById('startDirection').value;
var end = document.getElementById('endDirection').value;
var request = {
origin: start,
destination: end,
provideRouteAlternatives: true,
travelMode: selectedTravelMode
};
directionsService.route(request, function(result, status) {
if( status == 'OK' ) {
jQuery('#directions-list-container').fadeIn();
clearMarkers();
directionsDisplay.setDirections(result);
jQuery('.error-alert').fadeOut();
} else {
alert('Directions Request Error ' + status);
jQuery('#directions-error-alert').fadeIn();
}
});
directionsDisplay.setPanel(document.getElementById('directions-list'));
}
/**
* Close Directions Panel, Clear Directions Route, & Redraw Pins
*/
function closeDirections() {
jQuery('#directions-form').fadeOut();
jQuery('#directions-list-container').fadeOut();
directionsDisplay.setDirections({routes: []});
map.setZoom(7);
map.setCenter({
lat: parseFloat( userLat ),
lng: parseFloat( userLng )
});
jQuery('.error-alert').fadeOut();
setMarkers(map);
}
/**
* If the map-canvas element exists, initialize the map.
*/
if( document.getElementById( 'map-container' ) ) {
google.maps.event.addDomListener(window, 'load', initMap);
}
/**
* JS and functions related to geolocation and retailer map search.
*
* This file is intended to be our own geo init, will likely use Geolocator.js V2 to provide user coords.
* There are 2 functions, bridgeChildSetCookie, and bridgeChildGetCookie, for setting a cookie for the location coords.
* If these functions are not flexible enough, consider using a library like https://github.com/js-cookie/js-cookie.
* If you have an array or object that you need to store in the cookie (like lat/lng coords) JSON encode before setting.
* If the value for the cookie contains disallowed chars, URI encode before setting.
*
* @see bridgeChildSetCookie
*
* @link https://github.com/onury/geolocator
* @link https://onury.github.io/geolocator/?api=geolocator
*/
/**
* The endpoint for geolocation and retailer map search.
*
* Note: bridgeChildGeoVars.bridgeChildGeoEndPoint is provided by WordPress PHP as a variable on the page,
* do not hardcode, as it could change.
*
* API endpoint allowed action parameters documented in bridge_child_geo_endpoint.
* Required inputs for each action available by inspecting the function signatures of the functions in the case statements of bridge_child_geo_endpoint.
*
* Example usage :
* var get_retailers_by_state = {
* "action" : "get_retailers_by_state",
* "state" : "mn"
* };
* jQuery.ajax(
* {
* url : bridgeChildRetailerEndpoint + JSON.stringify( get_retailers_by_state ),
* type : 'GET'
* }
* )
* .done(
* function ( data, textStatus, jqXHR ) {
* console.log( "HTTP Request Succeeded: " + jqXHR.status );
* console.log( data );
* }
* )
* .fail(
* function ( jqXHR, textStatus, errorThrown ) {
* console.log( "HTTP Request Failed" );
* }
* );
*
* @type {string}
*/
var bridgeChildRetailerEndpoint = bridgeChildGeoVars.bridgeChildGeoEndPoint;
/**
* The Google API Key to use.
*
* Note: bridgeChildGeoVars.bridgeChildGoogleKey is provided by WordPress PHP as a variable on the page,
* do not hardcode, as it could change.
*
* @type {string}
*/
var bridgeChildGoogleAPIKey = bridgeChildGeoVars.bridgeChildGoogleKey;
/**
* Configuration object for Geolocator.
*/
geolocator.config(
{
"language" : "en",
"google" : {
"version" : "3",
"key" : bridgeChildGoogleAPIKey
}
}
);
/**
* Function(s) to run on window load.
*/
window.onload = function () {
/**
* Options to pass to geolocator.locate function.
*
* @link https://onury.github.io/geolocator/?api=geolocator#geolocator.locate
*
* @type {{enableHighAccuracy: boolean, timeout: number, maximumAge: number, desiredAccuracy: number, fallbackToIP: boolean, addressLookup: boolean, timezone: boolean}}
*/
var geolocatorOptions = {
enableHighAccuracy : false,
timeout : 6000,
maximumAge : 60000,
desiredAccuracy : 1609,
fallbackToIP : true,
addressLookup : false,
timezone : false
};
/**
* Perform location lookup.
* This can return an error if user does not allow HTML5 geolocation, and IP fallback fails.
* If that happens, we may need a plan to deal with such a situation.
* This is asynchronous, so plan accordingly.
*/
geolocator.locate(
geolocatorOptions,
function ( locateError, location ) {
if ( locateError ) {
/**
* Error or no response. Use fallback behavior.
*/
return console.log( locateError );
}
/**
* Success, we can glean lat/lng from this.
* We can set a cookie so we don't need to perform the lookup on every page load.
* We can pass the response along as args to PHP requests that require lat/lng data.
* Reminder : Cookies can store maximum 4KB.
*/
console.log( location );
/**
* Set cookie for users latitude value
*/
bridgeChildSetCookie(
'bridgeChildLatitude',
encodeURI(
JSON.stringify(
location.coords.latitude
)
),
1
);
/**
* Set cookie for users longitude value
*/
bridgeChildSetCookie(
'bridgeChildLongitude',
encodeURI(
JSON.stringify(
location.coords.longitude
)
),
1
);
/**
* Set cookie for users state code
*/
bridgeChildSetCookie(
'bridgeChildState',
encodeURI(
location.address.stateCode
),
1
);
}
);
};
/**
* Set a cookie.
*
* Example usage :
* bridgeChildSetCookie(
* 'bridgeChildPosition',
* encodeURI(
* JSON.stringify(
* bridgeChildCoordinates
* )
* ),
* 1
* );
*
* @param {string} cookieName The name for the cookie.
* @param {string} cookieValue The value for the cookie.
* @param {number} cookieExpiration The expiration value for the cookie in days.
*/
function bridgeChildSetCookie( cookieName, cookieValue, cookieExpiration ) {
/**
* @type {Date}
*/
var d;
/**
* @type {string}
*/
var expires;
d = new Date();
d.setTime(
d.getTime() + (
cookieExpiration * 24 * 60 * 60 * 1000
)
);
expires = 'expires=' + d.toUTCString();
document.cookie = cookieName + '=' + cookieValue + '; ' + expires + '; path=/';
}
/**
* Get the value of a cookie.
*
* Example usage:
* var positionCookie = bridgeChildGetCookie( 'bridgeChildPosition' );
*
* @param {string} cookieName The name of the cookie.
*
* @returns {string} The value of the cookie or empty string.
*/
function bridgeChildGetCookie( cookieName ) {
/**
* The name of the cookie we are looking for, appended with an equals character
* because cookies are actually strings of key=value pairs.
*
* @type {string}
*/
var name = cookieName + '=';
/**
* Array of cookie key=value strings.
*
* document.cookie is a string containing a semicolon-separated list of all cookies (i.e. key=value pairs).
* We split that string into an array on the semicolon.
*
* @type {Array}
*/
var cookieArray = document.cookie.split( ';' );
/**
* A loop counter.
*
* @type {number}
*/
var counter = 0;
/**
* The current cookie string in the loop.
*
* @type {string}
*/
var currentCookie = '';
while ( counter < cookieArray.length ) {
currentCookie = cookieArray[ counter ];
while ( ' ' === currentCookie.charAt( 0 ) ) {
currentCookie = currentCookie.substring( 1 );
}
if ( 0 === currentCookie.indexOf( name ) ) {
return currentCookie.substring( name.length, currentCookie.length );
}
counter++;
}
/**
* Cookie not found, empty, or mangled.
*/
return '';
}