Experience your
30 day trial
now!
GET STARTED
I'd like to introduce Marco Amoedo Martinez, a CRM MVP from Spain and our guest blogger today.
Mapping technologies can be very useful for CRM tools. We can position customers on a map and obtain valuable information about where they are, about where we have more market share, and so on.
Microsoft provides several mapping technologies that could help us add these functionalities to our CRM. We have MapPoint, MapPoint Web services and Live Maps. While the first ones have a license cost or service fee, because they are more powerful and have more value-add functionalities, the last one is free and “simple” to use. So, Live Maps looks like a good starting point for adding maps to our CRM.
A good place to start using maps could be the account form. We will try to add a new tab with a map indicating us the location of our customer. To achieve this we will use an IFrame based customization of Microsoft Dynamics CRM.
Step One: Creating a Web page with the map
The first step is creating a new Web page with all the things needed in order to show a map using Live Maps: importing the jscript control of Live Maps, setting the div tag which will contain the map, and adding jscript code to load the map. The html looks like this:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title></title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <script type="text/jscript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=5" mce_src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=5"></script> <script type="text/jscript"> var map = null; function GetMap() { map = new VEMap('myMap'); map.LoadMap(); } </script> </head> <body onload="GetMap();"> <div id='myMap' style="position:relative; width:400px; height:400px;"></div> </body> </html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/jscript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=5" mce_src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=5"></script>
<script type="text/jscript">
var map = null;
function GetMap()
{
map = new VEMap('myMap');
map.LoadMap();
}
</script>
</head>
<body onload="GetMap();">
<div id='myMap' style="position:relative; width:400px; height:400px;"></div>
</body>
</html>
More detailed information on the Live Maps SDK.
This Web page will return a generic map, not a map showing up our customer location which is our goal. So, suppose that we have a variable with the customer address. Now we can center the map on the customer address using functions provided by the map. The script code will look like this.
var map = null; function GetMap() { var address = "Emilio Gonzalez Lopez, 15011 A Coruña (A Coruña), Spain"; map = new VEMap('myMap'); map.LoadMap(); map.Find(null,address); }
var address = "Emilio Gonzalez Lopez, 15011 A Coruña (A Coruña), Spain";
map.Find(null,address);
Now we have a map centered on customer’s address. But this map doesn’t reflect the exact customer location. Therefore, we will add some more code to place a pushpin in the map over the customer location using an overload of the Find function. This overload will make a call to other function when the find operation completes, and this new function will add the pushpin to the map based on the results of the find call.
var map = null; function GetMap() { var address = "Emilio Gonzalez Lopez, 15011 A Coruña (A Coruña), Spain"; map = new VEMap('myMap'); map.LoadMap(); map.Find(null,address,null,null,0,10,true,true,true,true,FindCallBack) } function FindCallBack(shapeLayer, results, positions, moreResults, e) { if(positions != null && positions.length > 0) { latlong = new VELatLong(positions[0].LatLong.Latitude,positions[0].LatLong.Longitude); customerPushPin = new VEShape(VEShapeType.Pushpin,latlong); map.AddShape(customerPushPin); } }
map.Find(null,address,null,null,0,10,true,true,true,true,FindCallBack)
function FindCallBack(shapeLayer, results, positions, moreResults, e)
if(positions != null && positions.length > 0)
latlong = new VELatLong(positions[0].LatLong.Latitude,positions[0].LatLong.Longitude);
customerPushPin = new VEShape(VEShapeType.Pushpin,latlong);
map.AddShape(customerPushPin);
Well, we now have a map that shows up the customer location based on the customer address. It would be fine if we could store the latitude and longitude of customer position instead of always try to find it using the address. As we do before, suppose that we have a latitude and longitude variables for storing customer position. Now, the jscript code will check if we already have customer’s latitude and longitude, and place the pushpin, or if it has to find them using customer’s address as we do previously. Also, we will add some code to store the latitude and longitude if we did not already have them. The script now looks like this:
var map = null; var latitude = 43.37490; var longitude = -8.43178; var address = "Emilio Gonzalez Lopez, 15011 A Coruña (A Coruña), Spain"; function GetMap() { map = new VEMap('myMap'); map.LoadMap(); if (latitude != null && longitude != null) { PlacePushPin(latitude,longitude); } else { map.Find(null,address,null,null,0,10,true,true,true,true,FindCallBack); } } function FindCallBack(shapeLayer, results, positions, moreResults, e) { if(positions != null && positions.length > 0) { PlacePushPin(positions[0].LatLong.Latitude,positions[0].LatLong.Longitude); latitude = positions[0].LatLong.Latitude; longitude = positions[0].LatLong.Longitude; } } function PlacePushPin(lat, lon) { latlong = new VELatLong(lat,lon); customerPushPin = new VEShape(VEShapeType.Pushpin,latlong); map.AddShape(customerPushPin); map.SetCenterAndZoom(latlong,15); }
var latitude = 43.37490;
var longitude = -8.43178;
if (latitude != null && longitude != null)
PlacePushPin(latitude,longitude);
else
map.Find(null,address,null,null,0,10,true,true,true,true,FindCallBack);
PlacePushPin(positions[0].LatLong.Latitude,positions[0].LatLong.Longitude);
latitude = positions[0].LatLong.Latitude;
longitude = positions[0].LatLong.Longitude;
function PlacePushPin(lat, lon)
latlong = new VELatLong(lat,lon);
map.SetCenterAndZoom(latlong,15);
Now we have a web page that can show up the location of a customer based on his address or his latitude and longitude. We could add more functionality to this page, like for instance to allow the user to move the pushpin, give driving directions, etc. But before doing this lets proceed to integrate this Web page into Microsoft Dynamics CRM 3.0.
Step Two: Integrate Maps on CRM
As I said before, we will add the Web page to CRM’s customer form using an IFrame. But before doing this, we have to “wire” the script of the map with the CRM data. It’s quite a simple work; we only have to get/set values from/to CRM fields using jscript for latitude, longitude and address (see the CRM SDK page about this). The code will look like this:
var map = null; var latitude = null; var longitude = null; var address = ""; function GetMap() { // Try to get lat&long from the CRM latitude = parent.document.forms[0].all.address1_latitude.DataValue; longitude = parent.document.forms[0].all.address1_longitude.DataValue; //Get adress from CRM var addressLine1 = parent.document.forms[0].all.address1_line1.DataValue; var postalCode = parent.document.forms[0].all.address1_postalcode.DataValue; var city = parent.document.forms[0].all.address1_city.DataValue; var stateOrProvince = parent.document.forms[0].all.address1_stateorprovince.DataValue; var country = parent.document.forms[0].all.address1_country.DataValue; //Compose customer address address = addressLine1+", "+postalCode+" "+city+" ("+stateOrProvince+"), "+country; map = new VEMap('myMap'); map.LoadMap(); if (latitude != null && longitude != null) { PlacePushPin(latitude,longitude); } else { map.Find(null,address,null,null,0,10,true,true,true,true,FindCallBack); } } function FindCallBack(shapeLayer, results, positions, moreResults, e) { if(positions != null && positions.length > 0) { PlacePushPin(positions[0].LatLong.Latitude,positions[0].LatLong.Longitude); latitude = positions[0].LatLong.Latitude; longitude = positions[0].LatLong.Longitude; parent.document.forms[0].all.address1_latitude.DataValue = latitude; parent.document.forms[0].all.address1_longitude.DataValue = longitude; } } function PlacePushPin(lat, lon) { latlong = new VELatLong(lat,lon); customerPushPin = new VEShape(VEShapeType.Pushpin,latlong); map.AddShape(customerPushPin); map.SetCenterAndZoom(latlong,15); }
var latitude = null;
var longitude = null;
var address = "";
// Try to get lat&long from the CRM
latitude = parent.document.forms[0].all.address1_latitude.DataValue;
longitude = parent.document.forms[0].all.address1_longitude.DataValue;
//Get adress from CRM
var addressLine1 = parent.document.forms[0].all.address1_line1.DataValue;
var postalCode = parent.document.forms[0].all.address1_postalcode.DataValue;
var city = parent.document.forms[0].all.address1_city.DataValue;
var stateOrProvince = parent.document.forms[0].all.address1_stateorprovince.DataValue;
var country = parent.document.forms[0].all.address1_country.DataValue;
//Compose customer address
address = addressLine1+", "+postalCode+" "+city+" ("+stateOrProvince+"), "+country;
parent.document.forms[0].all.address1_latitude.DataValue = latitude;
parent.document.forms[0].all.address1_longitude.DataValue = longitude;
Notice that the GetMap method now retrieves the value of CRM fields for address, latitude and longitude. Also, in the FindCallBack method we update CRM fields for storing latitude and longitude.
Before continuing, we must place the Web page file in a virtual directory within the IIS web server that contains CRM. The best option is to create a new virtual directory under CRM’s site and place the Web page on it (i. e.: crm.plainconcepts.com/livemaps/), since adding it directly to CRM’s Web site is not supported.
Now we have the Web page ready to be integrated with CRM. So let’s go customize the account form with the IFrame and fields to storing latitude and longitude.
Only one more customization is left for our solution to works correctly. The attributes address1_latitude and addres1_longitude must be modified to accept the value range used for storing coordinates at Live Maps (Latitude: -90 to 90, Longitude: -180 to 180).
A precision of 5 is not very accurate. Live maps works with 14 decimals (a double precision floating point), but for our purpose is sufficient since we will lose only some meters. This lost of precision could be mitigated if we make some kind of transformation between CRM and Live Map data.
Conclusion
As we have shown here, adding a map to the CRM is not so complex. The possibilities of this kind of integration are enormous and it could add a great value to our CRM. I leave you a more complete example of this integration with Live Maps. This example also supports moving pushpins and giving driving directions, and it could be illustrative on how to customize live maps. I hope it be useful to you.
Marco Amoedo Martinez
Some answers to the comments below:
-------
Mike: Make sure that your are using map.find() method passing the correct address as an argument. Also, try to put the same address string in maps.live.com and see what happens. Virtual Earth is still in enhancement for address outside the US, in Spain there are also some address that are not find correctly.
Serge: I am trying to figure what is failing but I cannot find the problem. Give more details of the error, and I will try to help you. About showing several accounts on the same map, you could achieve this using a multi-selection button on account’s grid. Take a look to this example on the SDK http://msdn2.microsoft.com/en-us/library/bb267367.aspx
Andrew: The Web Page is the responsible for getting the data from CRM Form when is loaded. If you give a look to the Onload event of the HTML page showed on the IFrame you will see that there is some jscript code that retrieves the data from the CRM Form.
Paul: There is a line of Jscript code on the HTML page that composes the string of the address to search on the map. Try to modify this line to only use the postal code and the country.
Great article, just wanted to point out that Virtual Earth is free for development purposes but will require licensing for production systems.
http://www.microsoft.com/virtualearth/faq.mspx
==========================
John O'Donnell
Microsoft CRM MVP
http://www.crowecrm.com
Las dos últimas semanas he estado bastante atareado, ya casi están aquí las vacaciones y hay que dejar
Marco: Great post! Thanks for all the detail and hard work. Can't wait to test it all out.
-Matt Wittemann
I hate to make negative comments on the heel of such a well written article and example of Marco's stellar development skills. But let me ask a simple question to the community and the microsoft Program and Product mangers that read this blog.
Why is a mapping solution not built into the product? There was a basic one in v 1.2. It was at least something you could choose during install, or turn on and off in the isv.config file, it help demo the product, and it showed the power of a web application. And actually my guys actuall used it.
It is goofey that a customer has to hire a developer to write an unsupported application to work with CRM to generate what has been shown here to be a relatively simple process. I know that John O'Donnell and his team can do this, so can my team, sure we have visual studio and developers, and write code. But I don't think that Joe sales manager, Rather Gale Sales Manager, and her part time IT guy can make this work. It is just too fricken complicated for 90% of the people running this product to put a Map point tab on an account that draws a map from thier office to the customers address.
To do this you need:
1) Have to Own Visual Studio
2) Have to sign up for a map point account, which ain't simple.
3) Have to pour thru 57 blog articles to get find the code.
4) Test
5) Recompile
6) Call Support
7) Hang up
4) etc......
Get the drift, complicated! We are putting customers thur 3 hours of developer time, a design document, an engagment letter with their partner, a pilot group to approve the design, a rework, and a sign off meeting, which will come to about 3,000 bucks billing and lost productivity to put a web map on the form of a web based application that points to a web service that is run by the company that they bought the CRM app from. It should not be this complicated. Kind of reminds me of that "Microsoft iPod" marketing video. Just overly complicated.
Just one man's late night "rage against the machine"
Have to agree. This is something that should already be integrated into CRM out-of-the-box. Dynamics GP has had it for quite some time and we do have customers that both use and require this type of functionality as part of their Contact Management application. We are going to "attempt" to follow this article and see if we can get it working in a Development environment while tracking time, effort and grunts of frustration. We are not a Development house and do not intend to be but if it is as simple as stated, that would indeed be sweet.
Addendum: a couple of non-developers and we were able to get this working in under an hour. I am really impressed. Still would be "nice" to have it come with the shipped product, but our customers will really see value in this feature. Thank you for the article and the lesson.
Hi there, I'm trying to use your script with Live Maps. I downloaded the file from http://geeks.ms/files/folders/24756/download.aspx however, the README has a blank page on the end. Is that where the JSCRIPT should be or should I be using the jscript that is being used above?
Thanks
John: You are right; I have misunderstood the TOU. You can use Live Maps free of charge for any public application (with a limit of 100,000 transactions a day), but for private/intranet applications (like this example) an agreement should be signed with Microsoft. http://dev.live.com/terms/virtualearthoverview.aspx
Matt: Thank you for the comments. Your file explorer solution is also great and I am waiting to download the new Demo VPC to test it. Good luck at your new job :)
Pmarius: I understand your opinion; I also think that there are hundreds of customizations that could come “out of the box”. But I am sure that there is no time for all, and they have to be prioritized. Also, have in mind that adding maps to CRM has a lot of things to take account of: What mapping technology to use? How we can customize it? What functionalities to include? It is not so easy. I think that the great value of Dynamics CRM is the highly extensible CRM platform that permits us to give our customers the functionalities that they need. I prefer powerful and easy customization capabilities to a full featured CRM with less extensibility, but perhaps my opinion is impartial since I am a software engineer and a part of my work is to customize CRM :)
Jseiden: Thank you for the comments. I am very happy that you found this post useful and easy to understand.
STL: The readme is ok. The blank page at the end of the readme file should not contain anything. The script code is at the HTML pages that you should put in your IIS Server as indicated at the readme, and then you use them within an Iframe on the CRM.
Anyone try to get this to work on a laptop running the MSCRM laptop client? I get an onload error Access Denied, and a full map of the US is displayed. It works great on the same laptop through the MSCRM web access and also on any outlook desktop client. It doesn't work on a laptop client.
Any help would be much appreciated as this is a pretty cool integration.
Great tip and I've been trying to make it work in the UK, however I believe the address format being passed to live maps is not being understood. For my needs we only need to pass the post code, I've tried stripping parts out of the code but it's still picking the full address up.
Can anyone shed some light ?!
Can't wait to get this functionality working !
The address seems to be searching first in the US, is there a way to default the map to the UK as it does when you visit live.maps.com.
I'm hoping this will correct the address issues we are having as they are resolving to the USA first. (IE if we enter a postcode it finds a location in the us, but if we do the same in live.maps.com it finds the correct postal code in the uk)..
PS Absolutly excellent article and use for the ever adaptable CRM. - Well done.
Hi Marco. I also followed the instructions to a T but can't seem to make it work either. I feel like I'm missing something crutial. Is there code in the OnLoad pae that I'm missing? What causes the web page in my iFrame to make the call to CRM getting the address data, transfer it to the map webpage and then return the map with the CRM Account's coordinates?
I'm also having trouble getting the maps to work. I followed the instructions, but nothing comes up in the maps section.
Marco,
I tried your code and it worked fine. The only thing doesn't work is print option. When report previewed, Map doesn't show customer's address and I see the error stating:
var lat = parent.document.forms[0].all.address1_latitude.DataValue;
Value is Null or Object not set...
Can you help please?
Also, how we can preview multiple contacts locations on the map?
This is fabulous! Thanks for the info. Everything works great.