If you follow my site, you must know that I am a big fan of the Advanced Custom Fields plugin. It is super useful and packed with great features. Previously, I showed how to create and edit posts from the front-end with Advanced custom fields plugin, which has been very useful for many of this website visitors. In this post, I am going to explain how to create a Google Map with multiple dynamic markers using the Advanced Custom Fields plugin for WordPress.

Please Note

You will need a valid Google Maps API key with billing activated to display the map correctly.

The Scenario

Suppose you are running a business that has multiple outlets throughout the country. You want to showcase all the locations so users can see where they are. Meanwhile, you have some special outlets which have greater inventory so you want to highlight them. There are some other stores that are upcoming and not open to public yet.

So, you end up with 3 (three) type of outlets

  1. Regular Outlets
  2. Special Outlets
  3. Upcoming Outlets

Our plan is to show these three type of outlets with different markers on a map so they appear nice and easy to differentiate.

Initial Setup

For the outlets, we are going to create a custom post type called “Locations”. This is easy to create with a plugin called “Custom Post Types UI”. Or you can add the following code to functions.php file.

<?php

/**
 * Post Type: Locations
 */

function cpt_location() {

	$labels = array(
		"name" => __( "Locations", "text_domain" ),
		"singular_name" => __( "Location", "text_domain" ),
	);

	$args = array(
		"label" => __( "Locations", "text_domain" ),
		"labels" => $labels,
		"description" => "",
		"public" => true,
		"publicly_queryable" => true,
		"show_ui" => true,
		"delete_with_user" => false,
		"show_in_rest" => true,
		"rest_base" => "",
		"rest_controller_class" => "WP_REST_Posts_Controller",
		"has_archive" => false,
		"show_in_menu" => true,
		"show_in_nav_menus" => true,
		"exclude_from_search" => false,
		"capability_type" => "post",
		"map_meta_cap" => true,
		"hierarchical" => false,
		"rewrite" => array( "slug" => "location", "with_front" => true ),
		"query_var" => true,
		"supports" => array( "title" ),
	);

	register_post_type( "location", $args );
}

add_action( 'init', 'cpt_location' );

Once we have created the custom post type, you will see a new “Locations” link in your Dashboard.

Now we are going to create a field group with the Advanced Custom Fields (ACF) plugin. I created the fields like the following image. If you need the exact copy, you can download here.

Now, if you head over to add a new location. You will see something like the following image.

The error message you see is showing because we did not add a Google Maps API yet. Due to recent changes by Google, we require an account with activated billing information to be able to get a Google Maps API key. Without the billing, you will not be able to use it properly.

Getting Google Maps API

  • Head over to https://cloud.google.com/maps-platform/
  • Click “Get Started”
  • Check Maps & Places then click “Continue”
  • Select or Create a Project
  • Enable billing for the project you selected or created. You will be asked to “Create Billing Account” if you don’t already have billing information added.
  • Once your billing is all set, go to API & Services > Credentials
  • Click “Create Credentials” > API key and copy the API key.
  • You may set “HTTP referrers” restriction. (optional)
  • API Library and enable the following API
    • Maps JavaScript API
    • Geocoding API
    • Places API
  • You will need to make sure there is no quota limitation in these API’s. So, go to each of their pages (Maps, Geocoding, Places) and then to the “Quotas” tab and check if all limits are appropriate.
  • Now your Google Maps API key is ready to be used.

Setting Google Maps API

Now that we have the Google Maps API key, we need to add to our website. You can simply add the following code to functions.php file to set the Google Maps API key. Don’t forget to add the API key in the mentioned position as indicated!

function my_acf_init() {
	acf_update_setting('google_api_key', 'YOUR_GOOGLE_MAPS_API_HERE');
}
add_action('acf/init', 'my_acf_init');

With this added, our map should work fine in the back end. So, we will go ahead and add the location posts we need.

Enqueue Scripts

Now we need to enqueue two scripts. First the Google Maps JavaScript API and then a custom script that we can find at Advanced Custom Fields documentation page for Google Maps Field.

First, create a JavaScript file named gmaps.js in your child-theme folder. If you are going to do this without a child theme (not recommended), you will need to replace get_stylesheet_directory_uri() with get_template_directory_uri() in the following code.

Add the following code to your functions.php file. Replace “YOUR_GOOGLE_MAPS_API_HERE” portion with your API key.

function google_maps_scripts() {
	if (!is_admin()) {
		wp_register_script('googlemapsapi', 'https://maps.googleapis.com/maps/api/js?key=YOUR_GOOGLE_MAPS_API_HERE&callback', array(), '', false);
	    wp_enqueue_script('googlemapsapi');
	    
	    wp_register_script('gmaps-init', get_stylesheet_directory_uri().'/gmaps.js', array(), '', false);
	    wp_enqueue_script('gmaps-init');
	}	
}

We are loading these scripts only in the front end as we don’t need them in the backend. Hense, using the if statement with is_admin() condition

Now we are going to add the following code to the gmaps.js file we created. This portion of code is slightly modified from the exisiting code provided in Advanced Custom Fields documentation here.

(function($) {

/*
*  new_map
*
*  This function will render a Google Map onto the selected jQuery element
*
*  @type	function
*  @date	8/11/2013
*  @since	4.3.0
*
*  @param	$el (jQuery element)
*  @return	n/a
*/

function new_map( $el ) {
	
	// var
	var $markers = $el.find('.marker');
	
	
	// vars
	var args = {
		zoom		: 16,
		center		: new google.maps.LatLng(0, 0),
		mapTypeId	: google.maps.MapTypeId.ROADMAP
	};
	
	
	// create map	        	
	var map = new google.maps.Map( $el[0], args);
	
	
	// add a markers reference
	map.markers = [];
	
	
	// add markers
	$markers.each(function(){
		
    	add_marker( $(this), map );
		
	});
	
	
	// center map
	center_map( map );
	
	
	// return
	return map;
	
}

/*
*  add_marker
*
*  This function will add a marker to the selected Google Map
*
*  @type	function
*  @date	8/11/2013
*  @since	4.3.0
*
*  @param	$marker (jQuery element)
*  @param	map (Google Map object)
*  @return	n/a
*/

function add_marker( $marker, map ) { 
	// var
	var latlng = new google.maps.LatLng( $marker.attr('data-lat'), $marker.attr('data-lng') );
    var icon = $marker.attr('data-img');
	// create marker
	var marker = new google.maps.Marker({
		position	: latlng,
		map			: map,
		icon        : icon
	});

	// add to array
	map.markers.push( marker );
	
	// if marker contains HTML, add it to an infoWindow
	if( $marker.html() )
	{
		// create info window
		var infowindow = new google.maps.InfoWindow({
			content		: $marker.html()
		});

		// show info window when marker is clicked
		google.maps.event.addListener(marker, 'click', function() {          

			infowindow.open( map, marker );

		});
	}

}

/*
*  center_map
*
*  This function will center the map, showing all markers attached to this map
*
*  @type	function
*  @date	8/11/2013
*  @since	4.3.0
*
*  @param	map (Google Map object)
*  @return	n/a
*/

function center_map( map ) {

	// vars
	var bounds = new google.maps.LatLngBounds();

	// loop through all markers and create bounds
	$.each( map.markers, function( i, marker ){

		var latlng = new google.maps.LatLng( marker.position.lat(), marker.position.lng() );

		bounds.extend( latlng );

	});

	// only 1 marker?
	if( map.markers.length == 1 )
	{
		// set center of map
	    map.setCenter( bounds.getCenter() );
	    map.setZoom( 16 );
	}
	else
	{
		// fit to bounds
		map.fitBounds( bounds );
	}

}

/*
*  document ready
*
*  This function will render each map when the document is ready (page has loaded)
*
*  @type	function
*  @date	8/11/2013
*  @since	5.0.0
*
*  @param	n/a
*  @return	n/a
*/
// global var
var map = null;

$(document).ready(function(){

	$('.acf-map').each(function(){

		// create map
		map = new_map( $(this) );

	});
 
		    //zoom
			google.maps.event.addListener( map, 'zoom_changed', function( e ) {
	        
        	var zoom = map.getZoom();   
			
             if(zoom!= 5)           
             {
			var bounds = map.getBounds();
			
               myLatLngss = [];
              	$.each( map.markers, function( i, marker ){			
			var myLatLng = new google.maps.LatLng(marker.position.lat(), marker.position.lng() );	
						
			if(bounds.contains(myLatLng)===true) {						
             		 myLatLngss.push( myLatLng );
					}
					else {
						   
					}
			});
               if(myLatLngss.length > 0)
               { 
                 document.cookie = "coordn="+myLatLngss;
                 $("#customzm").load(location.href + " #customzm");                 
               } 
            } 
			       
	       });
   google.maps.event.addListener(map, 'dragend', function() {
   //alert('map dragged');
   var bounds = map.getBounds();
			
                  myLatLngss = [];
              	$.each( map.markers, function( i, marker ){

			var myLatLng = new google.maps.LatLng(marker.position.lat(), marker.position.lng() );	
					
			if(bounds.contains(myLatLng)===true) {						
             		 myLatLngss.push( myLatLng );
					}
					else {
   
					}
           if(myLatLngss.length > 0)
               {
                 document.cookie = "coordn="+myLatLngss;
                 $("#customzm").load(location.href + " #customzm");                 
               }
			});
   
 } );
  			

});

})(jQuery);

Create Shortcode

We are now going to create a shortcode to show the map. This will be very useful as we will be able to use it almost anywhere. So, add the following code to your functions.php file.

// Locations Map Shortcode - [locations_map]

function locations_map (){
    
    $args = array(
        'post_type' => 'location',
        'posts_per_page' => -1,
    );
    
    $locations_query = new WP_QUERY($args);
    
    if ( $locations_query->have_posts() ) {
    
    ob_start(); ?>

    <div class="acf-map" style="overflow: hidden; position: relative;">

    	<?php while ( $locations_query->have_posts() ) {
    		$locations_query->the_post(); 
    		$address = get_field('address');
    		$title = get_the_title();
    		$outlet_type = get_field('outlet_type');
    		$phone = get_field('phone');
                        
            if ($outlet_type == 'regular') {
                $type_icon = get_stylesheet_directory_uri().'/images/green-marker.png';
                $outlet_text = 'Regular Outlet';
            } elseif ($outlet_type == 'special') {
                $type_icon = get_stylesheet_directory_uri().'/images/red-marker.png';    
            	$outlet_text = 'Special Outlet';
            } else {
            	$type_icon = get_stylesheet_directory_uri().'/images/gray-marker.png';
            	$outlet_text = 'Upcoming Outlet';
            }

    		?>
    		
    		<div class="marker" data-lat="<?php echo $address['lat']; ?>" data-lng="<?php echo $address['lng']; ?>" data-img="<?php echo $type_icon; ?>">
    		    <div class="inside-marker">
    			    <h5><?php echo $title; ?></h5>
    			    <?php
    			        echo $outlet_text.'<br>';   
    			        if($phone) {
    			         echo 'Phone: '.$phone;       
    			        }
    			    ?>
    			</div>
    		</div>
    		
    <?php } ?>
    </div>
    
    <?php wp_reset_postdata(); 
    
    }
    
    return ob_get_clean();
        
}

add_shortcode( 'locations_map', 'locations_map' );

In this code portion, we doing a query to get all the posts from location custom post type. Then we are adding data from the posts inside the loop. The div acf-map is going to wrap the map with all the markers.

The variable $type_icon holds info about the marker image that is used in gmaps.js file. This part helps us display dynamic markers based on values.

We will need to upload 3 image files called red-marker.png, green-marker.png and gray-marker.png. These will need to be uploaded to the images folder in our child theme as indicated in our code. For non-child theme users, you will need to replace get_stylesheet_directory_uri() with get_template_directory_uri() as done previously.

CSS and Animation

We need to add the following CSS code in our theme (or plugin if you use any). Most themes allow adding Custom CSS in Appearance > Customize > Additional CSS. You may tweak the code to your preference.

.acf-map {
    width: 100%;
    height: 700px;
    border: #ccc solid 1px;
    margin: 20px 0;
}

Finally, use the shortcode [locations_map] to a page or post and check your beautiful map!

The Output

Dynamic Markers in Google Maps with Advanced Custom Fields

Troubleshooting

This is a fairly complex project and it is very normal to get into errors. The most issues will be from Google Maps API. Google Cloud Console gets changed every now and then and you might see the steps are different but they should be very similar.

Watermarked “for development purposes only” error will most likely be shown if:

  • You did not use the API correctly
  • Your account is not billing enabled
  • A self-imposed quota/limit is set in your API
  • Your billing is no longer valid
  • Conflicting plugins/themes
  • JavaScript errors

Hopefully you will not run into any of these. Good Luck!

Liked the article? Please don't forget to share!