I don't believe I've written this up anywhere and it might be useful to refer to in the future... I did a rather extensive page in vb aspnet webforms that included mapping several markers on a google map. Several lessons learned throughout. Here is the mature code...
I was told to use a ListView control, and they wanted paging, so I hooked up the DataPager control to that, but they wanted the map to show everything in the list, not just the displayed page, so I just included a separate hidden listview, with my required data elements (all of them), no paging. The data is a list of cars, each with a zip code as the sole location data. I was to use this limited info to create markers for each car on the map. Oh and the data was in a foreign language :) Everything else about the List should be standard here...
Added to the bottom of the html in this case...
<script language="javascript" type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIsomething&sensor=false"></script>
<!-- deploy this script AFTER the maps api-->
<script language="javascript" src="../_scripts/google-maps-3-vs-1-0.js" type="text/javascript"></script>
<script language="javascript" type="text/javascript">
var map;
var cars;
var infowindow;
var rollovers = [];
var latlngprocessed = [];
function init() {
var mapOptions = {
center: new google.maps.LatLng(51.55, 4.28), // default center
zoom: 7,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map($("#CVMap")[0], mapOptions);
cars = getAllCars();
$(cars).each(function () {
if (!latLngAlreadyProcessed(this)) {
// it's likely there exists more than one car at a particular latlng
var allmatches = getAllMatches(this);
var image = new google.maps.MarkerImage('../_css/img/org_dwn_arrow.png',
new google.maps.Size(17, 19),
new google.maps.Point(0, 0),
new google.maps.Point(0, 19));
var shadow = new google.maps.MarkerImage('../_css/img/org_dwn_arrow.png',
new google.maps.Size(40, 19),
new google.maps.Point(0, 0),
new google.maps.Point(0, 19));
var shape = { coord: [1, 1, 1, 17, 19, 17, 19, 1], type: 'poly' };
var marker = new google.maps.Marker({
map: map,
position: getGoogleLatLng(this),
shadow: shadow,
icon: image,
shape: shape,
title: allmatches.length > 1 ? (allmatches.length) + ' cars' : this.titlelink.text
});
centerMap(allmatches);
createInfoWindow();
var $rollovercontent = $('<div class="carrollover" id="CarRollover"><h1></h1><ul></ul></div>');
$.each(allmatches, function (idx, val) {
var $item = $('<li></li>').html($(val.titlelink).clone())
, $title = $('h1', $rollovercontent);
if ($title.text() == '') {
$title.text('Cars in ' + val.location);
}
$('ul', $rollovercontent).append($item);
});
// closure and a separate array of rollovercontent needed here, because there is only one infowindow per map
var i = rollovers.length; // get before push so we have index for closure below
rollovers.push(getStringFromJqueryObject($rollovercontent));
google.maps.event.addListener(marker, 'mouseover', (function (mark, idx) {
return function () {
infowindow.setContent(rollovers[idx]);
infowindow.open(map, mark);
$('#CarRollover').parent().css('overflow-x', 'hidden');
setTimeout(function () {
$fix = $('#CarRollover').parent().parent();
$fix.css({'top' : '28px'});
}, 200);
};
})(marker, i));
}
});
}
The closure is nothing more than creating distinct function instances on the fly for each rollover, since google maps only have one InfoWindow, we need to replace the content of it with that particular marker's info. Also we merge info so a marker with several cars sharing the same zip code would show as a list of links.
The rest is just some standard helper jQuery-fu functions.
function centerMap(allmatches) { // to first in resultslist
if ($.grep(allmatches, function (v) { return v.index == 0; }).length > 0) {
map.setCenter(getGoogleLatLng(allmatches[0]));
}
}
function createInfoWindow() { // if not already created (google says only one per map)
if (!infowindow) {
infowindow = new google.maps.InfoWindow({
maxWidth: 400
});
}
}
function getAllCars() {
var titlelinks = $("#hiddenformap .nameformap");
var descriptions = $("#hiddenformap .shortdescriptionformap").map(function () { return $(this).text(); }).get();
var locations = $(".hiddenlocationformap").map(function () { return $(this).text(); }).get();
var latlngs = $(".hiddenlatlngformap").map(function () { return $(this).text(); }).get();
var result = [];
$.each(latlngs, function (idx, val) {
if (val != '') {
result.push({
'index': idx,
'location': locations[idx],
'latlng': val,
'titlelink': titlelinks[idx],
'description': descriptions[idx] });
}
});
return result;
}
function getAllMatches(car) {
return $.merge($.grep(cars, function (c) { return car.latlng == c.latlng && c != car; }), [car]);
}
function getGoogleLatLng(car) {
return new google.maps.LatLng(car.latlng.split(",")[0], car.latlng.split(",")[1]);
}
function getStringFromJqueryObject(obj) {
return $('<div>').append($(obj).clone()).html();
}
function latLngAlreadyProcessed(car) {
var result = $.grep(latlngprocessed, function (ll) {
return ll == car.latlng;
}).length > 0;
if (result == false) { latlngprocessed.push(car.latlng); }
return result;
}
One other thing to mention was that I was querying google for latitude and longitude and storing it server side, so as not to pound their geolocation service, as they requested in terms of service and by applying a few different limits. So rather than sending zipcode (if you read the code above notice it should not send zip) it sends latlng which it already has. Here is the server side code for google's geo service (note my XML library calls, which I now replace with 3.5 xml literals [yay] since I just found out about them)...
Private Function GetGoogleLatLng(ByVal c As Car, ByVal trynumber As Int16) As String
Dim url As String = String.Format("http://maps.googleapis.com/maps/api/geocode/xml?sensor=false&address={0}", HttpUtility.HtmlEncode(j.FunctionLocationPostalCode))
Dim x As XDocument = XDocument.Load(url)
Dim s As XElement = x.Descendants("status")(0)
If s Is Nothing Then
'Logging.Log.
Return String.Empty
End If
If s.Value = "OVER_QUERY_LIMIT" And trynumber < 4 Then
System.Threading.Thread.Sleep(500) ' this sucks but what else can I do, actually it works well with google
trynumber += 1
Return GetGoogleLatLng(c, trynumber)
End If
Dim e As XElement = x.Descendants("geometry").Elements("location")(0)
If Not e Is Nothing Then
Return String.Format("{0},{1}", Xml.GetField(e, "lat", 25), Xml.GetField(e, "lng", 25))
Else : Return String.Empty
End If
End Function
BTW PS...
Want intellisense for your google. namespace?
I added this to the top of my usercontrol above
<%--
<% #if (false) %>
<script src="../_scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<% #endif %>
--%>