Skip to main content

SharePoint Ninja

Go Search
SharePoint Ninja
  

TechElixir > SharePoint Ninja > Posts > Multiple pushpins via Locations with Virtual Earth and SharePoint
Multiple pushpins via Locations with Virtual Earth and SharePoint

Also available on the Kindle

Putting a bunch of pushpins on a Virtual Earth map based on addresses presents some interesting problems. It is relatively straight forward if you have a set of longitudes and latitudes but most mortals don't know that off the top of their heads. I was presented with the problem of putting all the pushpins of a set of people based upon data from a SharePoint list. Luckily I was able to get it down starting with a post from Tom Resing. His blog can be found at http://tomresing.com/blog/default.aspx. Well, let's get down to brass tax and explain how to accomplish this goal as shown below.

Step 1. Create the SharePoint List with address information

Create a customized list with the fields of Address, City, State and Zip Code. I renamed the field Title to Name in order to represent the names of the people. Once the list has been created, you will need to populate it with some data. It doesn't matter what you name the list, but it will need the fields listed for the XSL to work. Otherwise, you will need to make the appropriate changes. The list fields are shown below.

Step 2: Create an aspx page.

Create a web part page on your site. It doesn't really matter which page you choose.

Step 3: Add a Data View Web Part

You need to add a DVWP based on the list you created in step 1. You will need to open the site in SharePoint Designer 2010 and then open the aspx page you created in step 2 as shown below.

Step 4: Create an XSL page for the DVWP

We will use XSL to convert the data from SharePoint into pushpins on a map from provided from Virtual Earth. The following is the link to the javascript from Virtual Earth that is used for this example. I used version 6.3.

http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3

This link is put into the xsl that is shown below. You will need to put this xsl somewhere on your SharePoint server or somewhere accessible from the page. I recommend putting it into a SharePoint library and turning versioning on. That way if you mess it up, then you can roll it back. If you accidentally choose to let SharePoint Designer update the code, it can potentially change it.

<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ddwrt2="urn:frontpage:internal">
<xsl:output method="html" indent="no"/>

<xsl:param name="ListID">f2a35021-9f2e-4d89-9ae3-6cf83a59ae9c</xsl:param>
<xsl:param name="ID">1</xsl:param>

<xsl:template match="/" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" ddwrt:ghost="">
<xsl:call-template name="dvt_1"/>
</xsl:template>
<xsl:template name="dvt_1">
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row" />
<xsl:call-template name="dvt_1.body">

        <xsl:with-param name="Rows" select="$Rows" />

    </xsl:call-template>
</xsl:template>
<xsl:template name="dvt_1.body">
<xsl:param name="Rows" />

<!-- Link to the Virtual Earth Map control JS file -->
<div id='myMap' style="position: relative; width: 650px; height: 600px;"></div>

<script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.3">
</script>
<script type="text/javascript">
_spBodyOnLoadFunctionNames.push(&quot;GetMap&quot;);    

var map=null;
var sAddress = [];
var sTitle;
var sDescription = [];
var iCount = 0;
var iCurrent = 0;

function GetMap()
{

if(map == null)
{
map = new VEMap('myMap');
map.LoadMap(null, 8, VEMapStyle.Road, false, VEMapMode.Mode2D, true, 1);
}

var i=0;
iCurrent=0;
for(i=0;i!=iCount;i++)
{

map.Find(null, sAddress[i], null, null, 0, 1, true, true, true, true, PlacesCallBack);
}


PlacesCallBack(shape, find, places, moreresults, error)
{
    if(places == null)
    {
        return;
    }
var home = places[0]; //first found places
var location = home.LatLong;
var newShape = new VEShape(VEShapeType.Pushpin, location);
newShape.SetDescription(places[0].Name);
sTitle = sDescription[iCurrent];
newShape.SetTitle(sTitle);
map.AddShape(newShape);
map.SetZoomLevel(10);
iCurrent++;    
}
</script>
<xsl:for-each select="$Rows">

        <xsl:call-template name="dvt_1.rowview" />

</xsl:for-each>

</xsl:template>

    <xsl:template name="dvt_1.rowview">

<script type="text/javascript">

var description = &quot;<xsl:value-of select="@Title" />&quot;;

var sID = &quot;<xsl:value-of select="@ID" />&quot;;
var s = &quot;<xsl:value-of select="@Address" />&quot;;
s = s + ' ';
s = s + &quot;<xsl:value-of select="@City" />&quot;;
s = s + ', ';
s = s + &quot;<xsl:value-of select="@State" />&quot;;
s = s + ' ';
s = s + &quot;<xsl:value-of select="@Zip_x0020_Code" />&quot;;
sAddress[iCount]= s;

         <![CDATA[

description = '<a href=http://site/Lists/addresses/display.aspx?ID=' + sID + '>' + description + '</a>';

]]>

sDescription[iCount] = description;
iCount++;
</script>
</xsl:template>
</xsl:stylesheet>

You can download this at maps.xml (you will need to modify the href to be specific to your site.)

Step 6: Assigning the XSLLink

In SharePoint Designer you need to highlight the DVWP and view the properties. You will need to put the link to the XSL file in the XSLLink property.

 

Step 7. Adding columns

You may need to add columns depending on what the default view is. Make sure you have all the columns necessary to provide the complete address (address, city, state, and zip code) as well as the Name/Title and the ID. You do this by clicking on the DVMP and then clicking on Options and then clicking on Add/Remove columns as shown below.

You will be presented with a dialog box. Make sure all the required columns are in the right hand column as shown below. More columns won't hurt anything and you may want them if you are going to include additional information in the description.

 

Step 8. Saving the ASPX page.

When you save the ASPX file in SharePoint Designer it will ask you to save the file. You will want to choose Set Action and set it to pull from the existing file and not overwrite as shown below.

Now you can preview the file and you should see a pushpin on the map for every address.

Breaking it down

Javascript in XSL

The javascript in the XSL shown above is broken into two parts. Everything in the first part will happen once. So, this is where you want to put your globals to keep track of things like how many list items there are and arrays of addresses. You need these globals because the map doesn't get initialized until after the form is displayed. The second part of the javascript gets fired every time a row is retrieved by the XSL. This is where the arrays are populated.

Calling code after the form loads

This is called with the javascript function _spBodyOnLoadFunctionNames.push(&quot;GetMap&quot;);

_spBodyOnLoadFunctionNames is a special function that takes the name of a javascript function and calls it after the form loads. In this case it calls the function GetMap.

Map.Find

This is the function that can take an address and finds possible locations. Part of the function is a callback function that retrieves the list of possible locations based upon the address. The most likely address is first. In this scenario the callback function is where we add the pushpins based upon the most likely location. In this example the callback function is named PlacesCallBack but you can name it whatever you want.

CDATA

In this example I put a link in the description that involves using the anchor tag (<a></a>). Since this piece of javascript is inside the XSL the parser thinks that anything starting with a < must be part of the XSL. Therefore, any javascript inside the XSL must be surrounded by the CDATA tag which is used in the following way

<![CDATA[ javascript goes here ]]>

Callback Function

The callback function is where the pushpins are added. It is also the place where the zoom is set. That is because the call back function operates asynchronously. In other words the code after map.Find fires before the callback function finishes. If the incrementing of the globals happened outside of the callback function you would get unpredictable results.

Comments

There are no comments yet for this post.