We’ve already looked at how to geoCode addresses using various APIs. Now lets look at using Google Maps to put markers on a Map using an Excel worksheet as input. As usual you can find the completed example in the googleMapping.xlsm download. Before running this please check the parameter worksheet and change the filename value  for the place that the application will generate a web page to something relevant for your machine. Look for the  filename in the Marker html section of the Parameter Worksheet.


Putting markers on a map for our ‘Customer Sheet’

If you’ve been following along with the geoCoding examples you will already be familiar with our test data, which we’ve now already geocoded from input addresses of various levels of accuracy. You can also customize the markers and add heatmaps and circles – see Overlaying circles and heatmaps

The objective of this section is to plot this sheet on a map, with a marker for each row. In addition we are also going to have a popup box that will show the customer name and the full address. The final map using our test data looks like this. 


Parameters

Like many of the examples on this site, we are using parameters to drive the behavior of the code. Many of the parameters used in the geoCoding examples are used, but we have a couple of new sections in the parameter worksheet.  4 pieces of data are needed for placing markers on the map are given under the ‘Markers’ parameter block. This instructs the application which columns in the table contain these values.

Preparing the Content

The title Marker Parameter specifies the column containing the value you want shown in reaction to a mouse hover over the map marker. In this case, we want to show the customer name. The content marker parameter  specifies the column containing the value you want to show in the pop-up box in reaction to a click on the map marker. In this case, we want to create a description of the customer and his address, and we can use html to construct it if necessary. You cab see that I’ve added a content field in our example application that looks like the below, but you can of course put whatever you want in there. A blank content field will mean that you don’t need a pop up box for that marker.

="<b>" & B2 & "</b><br>" & SUBSTITUTE(L2,",","<br>") & "<br>"

which translates to

<b>Acme</b><br>1600 Amphitheatre Pkwy<br> Mountain View<br> CA 94043<br> USA<br>

Customizing the generated web page

The application creates , and displays, a web page in the place you specify in the filename parameter of the Marker Html section of the Parameter WorkSheet.


The other parameters in this section contain the Code  needed to generate this web page.  Headermain and functions are largely  javaScript and are responsible for interpreting the Excel data and plotting it on the map.  You can change these if you want of course, but only if you want to change the functionality of the application. The body section however is likely going to need to be customized to design whatever web page you want to create. Right now it just looks like this

Quick Links
Excel and Google Mapping Integration
<a href="http://ramblings.mcpher.com">
ramblings.mcpher.com</a>

As long as you retain the  <body onload=”initialize()”> (load up the map when the page loads) and <div id=”map_canvas”></div>  (the place on the web page to contain the map), you can tailor this body parameter as as much as you like.  

Compare Google Maps to others

I’ve implemented this exact same thing on other APIs as below.  Take a look to compare. My recommendation is to stick with Google Maps.

The generated html file

When you execute the code for marking up the customer worksheet,  an html file is generated in the location you have specified and IE is kicked off to display it. (You may get a security warning about running local content on IE depending on your settings). The generated file from our example application looks like this. It is just the head, main, body and functions code from the Parameter sheet, plus the function mcpherDataPopulate(), which is generated at run time and contains the Excel data that will be displayed on the map. By default, the map will be displayed at zoom level 2, but you can change that with a Url parameter, for example (we use file:// instead of http:// because the file has been generated locally, but you may want to host it on a web site, or use localhost if you are running a server on your pc) file:///D:/googlechart/googleMarkerGenerated.html?zoom=5

Finally, the map will be centered around the first row in your data and will be a standard google Map. If you wanted to change that default (for example to be centered around your head office, with a satellite view), then you would need to change the code section below of the main parameter

var myOptions = {
         center: new google.maps.LatLng(mcpherData.cJobject[0].lat,
                 mcpherData.cJobject[0].lng),
        mapTypeId: google.maps.MapTypeId.ROADMAP

Generated html file contents

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
  html { height: 100% }
  body { 
 height: 100%; 
 margin: 0; 
 padding: 0 ;  
 color: Gray ;
 background-color: WhiteSmoke; 
  }
  #map_canvas { height: 100%}
.mcquicklink {
 margin-left: 20px;
 margin-right: 20px;
 display: inline;
 font-family: sans-serif;
 float: right;
}
.mccategory {
 margin-left: 20px;
 margin-right: 20px;
 display: inline;
 font-family: sans-serif;
 float: right;
}
.mcheading {
 display: inline;
 font-family: sans-serif;
}
</style>
    </head>
<body onload="initialize()">
 Quick Links
 Excel and Google Mapping Integration</b>
 <a href="https://ramblings.mcpher.com">
  ramblings.mcpher.com</a>
</body>
The Vba code
Most of the code used here is already covered in Creating Google Maps Layers from Excel Data, json, Data Manipulation Classes, and Google Visualization but here is the specific code for creating the html file from the Customer Worksheet.
' This will take the geocoded data and mark it
Option Explicit
'for more about this
' https://ramblings.mcpher.com/classes/datamanip/ses
'to contact me
' click on one of the icon above
'reuse of code
' https://ramblings.mcpher.com/reusing-code-from-this-site/
Public Sub genericMarking(paramName As String, markerHtml As String, _
                    Optional eOutput As eOutputMarkers = eOutputHtml)
    Dim dSets As cDataSets, dc As cCell, job As cJobject, fName As String
    Dim dr As cDataRow, vc As cCell, a As Variant, i As Long
    Set dSets = dSetsSetup(paramName)
    If dSets Is Nothing Then Exit Sub
    ' check that we have the required marker fields
    With dSets
        For Each dc In .dataSet(cMarkers).column("Column Name").rows
            If .dataSet(cMarkers).isCellTrue(dc.row, "required") Then
                With .dataSet(cMaster).headingRow
                    If Not .validate(True, dc.toString) Then Exit Sub
                End With
            End If
        Next dc
    End With
    ' we have it all now create a job
    Set job = New cJobject
    With job.init(Nothing)
    ' add framework/control parameters
        With .add("framework").add("control")
            For Each dr In dSets.dataSet(cControl).rows
                .add dr.value(cControl), dr.value(cControlValue)
            Next dr
        End With
        With .add("cJobject").addArray
            For Each dr In dSets.dataSet(cMaster).rows
                With .add
                    For Each dc In dSets.dataSet(cMarkers).column(cMarkers).rows
                        If Not dSets.dataSet(cMarkers).isCellTrue(dc.row, "array") Then
                            Set vc = dr.cell(dc.parent.cell("Column Name").toString)
                            If Not vc Is Nothing Then
                                .add dc.toString, vc.toString
                            End If
                        Else
                            a = Split(dc.parent.cell("Column Name").toString, ",")
                            With .add(dc.toString).addArray
                                For i = LBound(a) To UBound(a)
                                    Set vc = dr.cell(CStr(a(i)))
                                    If Not vc Is Nothing Then
                                        .add.add CStr(a(i)), vc.toString
                                    End If
                                Next i
                            End With
                        End If
                    Next dc
                End With
            Next dr
        End With
    End With
    ' now create the html file and browse to it
    fName = dSets.dataSet(markerHtml).cell("filename", "code").toString
    Select Case eOutput
        Case eOutputHtml
            If openNewHtml(fName, generateHtml(job, dSets, markerHtml)) Then
                pickABrowser dSets, fName, True
            End If
        Case eOutputKML
            If openNewHtml(fName, generateKML(job, dSets)) Then
                pickABrowser dSets, fName, True
            End If
        Case Else
            Debug.Assert False
    End Select
    dSets.tearDown
    Set dSets = Nothing
End Sub
Private Function generateHtml(job As cJobject, dSets As cDataSets, mHtml As String) As String
    Dim s1 As String
    ' the deserialized data
    s1 = "function mcpherDataPopulate() { " & vbCrLf & _
            "var mcpherData = " & job.serialize(True) & ";" & vbCrLf & _
            "return mcpherData; };"
    With dSets.dataSet(mHtml)
        generateHtml = _
        .cell("header", "code").toString & vbCrLf _
        & s1 & vbCrLf _
        & .cell("main", "code").toString & vbCrLf _
        & .cell("catfunctions", "code").toString & vbCrLf _
        & .cell("functions", "code").toString & vbCrLf _
        & .cell("body", "code").toString & vbCrLf
    End With
End Function
Private Function generateKML(job As cJobject, dSets As cDataSets) As String
    Dim s1 As String
    Dim jo As cJobject
    s1 = vbNullString
    For Each jo In job.children
        s1 = s1 & generatePlaceMark(jo)
    Next jo
    generateKML = _
         "<?xml version='1.0' encoding='UTF-8'?>" & vbLf & _
         "<kml xmlns='http://www.opengis.net/kml/2.2'>" & vbLf & _
         tag("Document", s1) & "</kml>"
End Function
Private Function generatePlaceMark(job As cJobject) As String
    ' convert jSon item to KML
    With job
        generatePlaceMark = _
            tag("Placemark", _
            tag("name", .child("title").toString) & _
            tag("description", "<!&#91;CDATA&#91;" & .child("content").toString & "&#93;&#93;>") & _
            tag("Point", _
                tag("coordinates", .child("lng").toString & "," & .child("lat").toString) & ",0"))
    End With
End Function
Private Function tag(tagName As String, Optional item As String = vbNullString) As String
    tag = "<" & tagName & ">" & vbLf & item & vbLf & "</" & tagName & ">"
End Function

The Vba code

Most of the code used here is already covered in Creating Google Maps Layers from Excel DatajsonData Manipulation Classes, and Google Visualization but here is the specific code for creating the html file from the Customer Worksheet.

' This will take the geocoded data and mark it
Option Explicit
'for more about this
' https://ramblings.mcpher.com/classes/datamanip/
'to contact me
' https://gitter.im/desktopliberation/community
' https://ramblings.mcpher.com/reusing-code-from-this-site/
Public Sub genericMarking(paramName As String, markerHtml As String, _
Optional eOutput As eOutputMarkers = eOutputHtml)
Dim dSets As cDataSets, dc As cCell, job As cJobject, fName As String
Dim dr As cDataRow, vc As cCell, a As Variant, i As Long
Set dSets = dSetsSetup(paramName)
If dSets Is Nothing Then Exit Sub
' check that we have the required marker fields
With dSets
For Each dc In .dataSet(cMarkers).column("Column Name").rows
If .dataSet(cMarkers).isCellTrue(dc.row, "required") Then
With .dataSet(cMaster).headingRow
If Not .validate(True, dc.toString) Then Exit Sub
End With
End If
Next dc
End With
' we have it all now create a job
Set job = New cJobject
With job.init(Nothing)
' add framework/control parameters
With .add("framework").add("control")
For Each dr In dSets.dataSet(cControl).rows
.add dr.value(cControl), dr.value(cControlValue)
Next dr
End With
With .add("cJobject").addArray
For Each dr In dSets.dataSet(cMaster).rows
With .add
For Each dc In dSets.dataSet(cMarkers).column(cMarkers).rows
If Not dSets.dataSet(cMarkers).isCellTrue(dc.row, "array") Then
Set vc = dr.cell(dc.parent.cell("Column Name").toString)
If Not vc Is Nothing Then
.add dc.toString, vc.toString
End If
Else
a = Split(dc.parent.cell("Column Name").toString, ",")
With .add(dc.toString).addArray
For i = LBound(a) To UBound(a)
Set vc = dr.cell(CStr(a(i)))
If Not vc Is Nothing Then
.add.add CStr(a(i)), vc.toString
End If
Next i
End With
End If
Next dc
End With
Next dr
End With
End With
' now create the html file and browse to it
fName = dSets.dataSet(markerHtml).cell("filename", "code").toString
Select Case eOutput
Case eOutputHtml
If openNewHtml(fName, generateHtml(job, dSets, markerHtml)) Then
pickABrowser dSets, fName, True
End If
Case eOutputKML
If openNewHtml(fName, generateKML(job, dSets)) Then
pickABrowser dSets, fName, True
End If
Case Else
Debug.Assert False
End Select
dSets.tearDown
Set dSets = Nothing
End Sub
Private Function generateHtml(job As cJobject, dSets As cDataSets, mHtml As String) As String
Dim s1 As String
' the deserialized data
s1 = "function mcpherDataPopulate() { " &amp; vbCrLf &amp; _
"var mcpherData = " &amp; job.serialize(True) &amp; ";" &amp; vbCrLf &amp; _
"return mcpherData; };"
With dSets.dataSet(mHtml)
generateHtml = _
.cell("header", "code").toString &amp; vbCrLf _
&amp; s1 &amp; vbCrLf _
&amp; .cell("main", "code").toString &amp; vbCrLf _
&amp; .cell("catfunctions", "code").toString &amp; vbCrLf _
&amp; .cell("functions", "code").toString &amp; vbCrLf _
&amp; .cell("body", "code").toString &amp; vbCrLf
End With
End Function
Private Function generateKML(job As cJobject, dSets As cDataSets) As String
Dim s1 As String
Dim jo As cJobject
s1 = vbNullString
For Each jo In job.children
s1 = s1 &amp; generatePlaceMark(jo)
Next jo
generateKML = _
"<!--?xml version='1.0' encoding='UTF-8'?-->" &amp; vbLf &amp; _
"" &amp; vbLf &amp; _
tag("Document", s1) &amp; ""
End Function
Private Function generatePlaceMark(job As cJobject) As String
' convert jSon item to KML
With job
generatePlaceMark = _
tag("Placemark", _
tag("name", .child("title").toString) &amp; _
tag("description", "<!&#91;CDATA&#91;" & .child("content").toString & "&#93;&#93;>") &amp; _
tag("Point", _
tag("coordinates", .child("lng").toString &amp; "," &amp; .child("lat").toString) &amp; ",0"))
End With
End Function
Private Function tag(tagName As String, Optional item As String = vbNullString) As String
tag = "&lt;" &amp; tagName &amp; "&gt;" &amp; vbLf &amp; item &amp; vbLf &amp; "&lt;/" &amp; tagName &amp; "&gt;"
End Function

Summary

Google Maps makes this kind of application – just a few years ago it would have been unheard of – just a matter of plugging a few bits and pieces together. The cDataSet class and the cJobject class together make the whole business of dealing with worksheets, setting up parameters, creating javaScript data friending and serializing to jSon very simple and open up your spreadsheets to all manner of new web service capabilities.
I look forward to your comments, questions and feedback on our forum. Please consider joining and participating our blog for the latest updates and useful Excel Ramblings discussions and code.