- View Online Demo (Click on X users online in directly underneath navigation bar)
Introduction:
I often looked at the maps provided by my web analytic software and thought how cool it would be if I could provide these types of maps for the users currently browsing my web site in real time. So my quest began to add this lush novelty item to my personal web site. I found the virtual maps javascript api and hostip.info api and with a little bit more work and research I had it.
The tutorial below describes how you can implementing a modal popup window on your web site which will display a Microsoft Virtual Earth map with pinpoints on the locations of everyone who is currently browsing your web site.
What You Will Need To Get Started:
- Microsoft Visual Studio 2008 with Ajax Controls Toolkit 3.5/3.6.
- -or- Microsoft Visual Studio 2005 with Ajax.net Framework and Ajax Controls Toolkit 2.0.
- Virtual Earth (Microsoft Live Maps) javacript API.
- hostip.info API handler.
- Singleton collection for storing session information at application level.
- Global.asax file for catching and recording session info.
Using the code
I have broken the code you will need to implement down into 5 steps below:
Step 1: A UserIpInfo singleton Collection.
This is used to store session information for each user at the application level, this is nessasary so that each user will have access to view the information of others. Alternatively web.caching or an application variable could be used in place of a Singleton collection but those would cause the objects to be serialized/deserialized when accessed. Since this is a novelty item you want to make sure the addition does not hurt the performance of your website.
''' <summary />
''' UserIPInfoList
''' </summary />
''' <remarks /></remarks />
Public Class UserIPInfoList
Inherits Generic.Dictionary(Of String, UserIpInfo)
''' <summary />
''' Instance
''' </summary />
''' <remarks />
''' The current running instance.
''' </remarks />
Private Shared m_instance As UserIPInfoList
Public Shared ReadOnly Property Instance() As UserIPInfoList
Get
' initialize if not already done
If m_instance Is Nothing Then
m_instance = New UserIPInfoList
End If
' return the initialized instance of the Singleton Class
Return m_instance
End Get
End Property
''' <summary />
''' New
''' </summary />
''' <remarks /></remarks />
Private Sub New()
MyBase.New()
End Sub
End Class
''' <summary />
''' UserIpInfo
''' </summary />
''' <remarks /></remarks />
Public Class UserIpInfo
''' <summary />
''' IPAddress
''' </summary />
''' <remarks /></remarks />
Private m_IPAddress As String
Public Property IPAddress() As String
Get
Return m_IPAddress
End Get
Set(ByVal value As String)
m_IPAddress = value
End Set
End Property
''' <summary />
''' Location
''' </summary />
''' <remarks /></remarks />
Private m_Location As String
Public Property Location() As String
Get
Return m_Location
End Get
Set(ByVal value As String)
m_Location = value
End Set
End Property
''' <summary />
''' Coordinates
''' </summary />
''' <remarks /></remarks />
Private m_Coordinates As String
Public Property Coordinates() As String
Get
Return m_Coordinates
End Get
Set(ByVal value As String)
m_Coordinates = value
End Set
End Property
''' <summary />
''' CoordinatesReversed
''' </summary />
''' <value /></value />
''' <returns /></returns />
''' <remarks /></remarks />
Public ReadOnly Property CoordinatesReversed()
Get
Dim corray As String() = m_Coordinates.Split(",")
Return corray(1) & "," & corray(0)
End Get
End Property
''' <summary />
''' New
''' </summary />
''' <param name="iPAddress" /></param />
''' <param name="location" /></param />
''' <param name="coordinates" /></param />
''' <remarks /></remarks />
Public Sub New(ByVal iPAddress As String, ByVal location As String, ByVal coordinates As String)
Me.IPAddress = iPAddress
Me.Location = location
Me.Coordinates = coordinates
End Sub
''' <summary />
''' New
''' </summary />
''' <remarks /></remarks />
Public Sub New()
End Sub
End Class
Step 2: Gathering User Information In Your Global.asax
You can use the global.asax file to gather information about your user on session start and remove it on session end. The code block below will get the users ip address, look up the location of that address and store it in your singleton collection with the session id as the key. When the user session ends it will be removed based on the unique session id.
Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs)
' Fires when the session is started
Application.Lock()
'add session ip address to list.
If Session.IsNewSession() AndAlso Not UserIPInfoList.Instance.ContainsKey(Session.SessionID) Then
Try
'make a call to hostip handler to get ip info
Dim hostIPLookupXML As New XmlDocument
hostIPLookupXML.Load("http://api.hostip.info/?ip=" & Request.UserHostAddress)
'uncomment to test.
'hostIPLookupXML.Load("http://api.hostip.info/?ip=12.215.42.19")
'add namespace
Dim nsMgr As New XmlNamespaceManager(hostIPLookupXML.NameTable)
nsMgr.AddNamespace("gml", "http://www.opengis.net/gml")
'select node
Dim Hostip As XmlNode = _
hostIPLookupXML.DocumentElement.SelectSingleNode("gml:featureMember", nsMgr).FirstChild
'check for bad results.
If Hostip IsNot Nothing Then
'parse data to local vars.
Dim locationCityState As String = Hostip.ChildNodes(0).InnerText
Dim locationCountry As String = Hostip.ChildNodes(1).InnerText
If Hostip.ChildNodes.Count > 3 AndAlso Hostip.ChildNodes(4) IsNot Nothing Then
'check that we have cooridinates.
Dim coordinates As String = Hostip.ChildNodes(4).InnerText
If coordinates <> String.Empty Then
'add user info to list.
UserIPInfoList.Instance.Add(Session.SessionID, New UserIpInfo(Request.UserHostAddress, _
locationCityState & " " & locationCountry, _
coordinates))
End If
End If
End If
Catch ex As Exception
'service is unavialable, ignore error.
End Try
End If
Application.UnLock()
End Sub
Sub Session_End(ByVal sender As Object, ByVal e As EventArgs)
' Fires when the session ends
Application.Lock()
Try
Dim ipi As New BusinessObjects.ProgrammersJournal.UserIpInfo
If UserIPInfoList.Instance.TryGetValue(Session.SessionID, ipi) Then _
UserIPInfoList.Instance.Remove(Session.SessionID)
Catch ex As Exception
'move on
End Try
Application.UnLock()
End Sub
Step 3: Placing The Map On Your Page In A Modal Popup
The next step is to simply place the map on your page in a modal pop up window. You will need to include a reference to the javascript api on your page, create a function for loading the map, create a modal pop up window and add your javascript function to the body onload event. There is plenty of information available online to customize the display of your map so I will just keep it simple here.
<!-- Add link to jscript api -->
<script src="http://dev.virtualearth.net/mapcontrol/v3/mapcontrol.js">
</script>
<!--add script to load map-->
<script type="text/javascript">
//<![CDATA[
var map = null;
function GetMap()
{
//display map
map = new VEMap('myMap');
map.LoadMap(new VELatLong(115,-150),1,'r',false);
//hide dashboard.
map.HideDashboard();
}
//]]>
</script>
<!--call load map script when page loads-->
<body onload="GetMap();">
<!--Add link button to display modal window-->
<asp:LinkButton ID="LinkButton2" runat="server" ToolTip="whats this?">
<asp:Literal ID="numonline" runat="server"></asp:Literal>
users online
</asp:LinkButton>
<!--add panel to hold map-->
<asp:Panel ID="Panel2" runat="server" Style="display: none" CssClass="modalPopup">
<asp:Panel ID="Panel4" runat="server" Style="cursor: move; background-color: #a5c863;
padding: 3px; border: solid 1px Gray; color: white; margin-bottom: 3px;">
<div>
<strong>Whos Online?</strong>
</div>
</asp:Panel>
<div id="myMap" style="position: relative; width: 400px; height: 200px;">
</div>
<br />
<asp:Button ID="OkButton" runat="server" Text="done" CssClass="loginbox" />
</asp:Panel>
<!--add modal extender-->
<cc1:ModalPopupExtender ID="ModalPopupExtender1" runat="server" TargetControlID="LinkButton2"
PopupControlID="Panel2" BackgroundCssClass="modalBackground" OkControlID="OkButton"
DropShadow="true" Y="35" PopupDragHandleControlID="Panel4" />
Step 4: Adding Pinpoints To Your Map
The next step is to loop through your singleton collection and add the pinpoints to your map. We will do this by dynamically creating the javascript in the code behind page. You will then need to call your new function from the body onload to display them, this is because the map needs to be created prior to adding the pinpoints.
'set number of users online.
Me.numonline.Text = UserIPInfoList.Instance.Keys.Count
'add pinpoints to map.
Dim sb As New StringBuilder()
Dim count As Integer = 1
sb.AppendLine("function ShowPins()")
sb.AppendLine("{")
For Each key As String In UserIPInfoList.Instance.Keys
sb.AppendLine("var pinID = " & count & ";")
sb.AppendLine("var pin = new VEPushpin(")
sb.AppendLine("pinID, ")
sb.AppendLine("new VELatLong(" & UserIPInfoList.Instance(key).CoordinatesReversed & "), ")
sb.AppendLine("null, ")
sb.AppendLine("'" & UserIPInfoList.Instance(key).IPAddress & "', ")
sb.AppendLine("'" & UserIPInfoList.Instance(key).Location & "','pinEvent', 'Century 16'")
sb.AppendLine(");")
sb.AppendLine("")
sb.AppendLine("map.AddPushpin(pin);")
count = count + 1
Next
sb.AppendLine("}")
'add script to output.
Me.Page.ClientScript.RegisterClientScriptBlock(Me.GetType(), _
"MapScript", sb.ToString(), True)
<!--call load map script when page loads-->
<body onload="GetMap();ShowPins();">
Step 5: Customizing Your CSS
There are a few customizations you will need to make to your css, the first is to control the display of to modal window as well as the pins on the map. The second is to add a style tag to your aspx page so the title windows will appear over the popup window. This needs to be added after the map has loading so it cannot be stored in your css file.
/*Modal Popup*/
.modalBackground {
background-color:Gray;
filter:alpha(opacity=50);
opacity:0.7;
}
.modalPopup {
background-color:#f9f9e5;
border-width:1px;
border-style:solid;
border-color:Gray;
padding:3px;
width:400px;
text-align:center;
}
/*Map*/
.pinEvent
{
width:10px;height:15px;
overflow:hidden;
cursor:pointer;
}
<style type="text/css">
.ero{z-index: 100002 !important;}
.ero-progressAnimation{z-index: 100002 !important;}
.VE_Message{z-index: 100002 !important;}
</style>
Points of Interest
- The hostip.info service is a free service and as a free service it performs about as good as you would expect a free service to. It seems to place any ip address it cannot determine in camarillo, ca. If you have access to a better geolocation service or database I would recomend using that, though the hostip.info service is extremely fast and performs well enough for a novelty addition to your web site.
- You will notice that I am using version 3 of the Virtual Maps javascript API. This is the only one I found to be stable with a map this small and had the fastest loading times. If you find that you are stable using one of the other versions please let me know.