Creating the Server Application
With the Pocket PC application completed, it is now time to build the server application. Using Visual Studio 2005, create a new Windows application and name it C:\MapServer. Populate the default Form1 with the following controls (see also
Figure 7):
- GroupBox controls
- WebBrowser controls
- Label controls
- TextBox controls
- Button controls
 | |
Figure 7. Populate the default Form1 with the various controls as shown. |
Using Virtual Earth to Display Maps
To display a map of locations returned by the Pocket PC clients, I will use Microsoft's Virtual Earth, a map and search system comprising maps, aerial images, business directories, etc. Using VE, you can search for businesses, addresses, as well as ask for directions. You can access VE at http://local.live.com/.
Microsoft provides a map control that allows developers to embed VE maps into their own applications. You can use it to build a custom solution using VE mapping services. The VE Map control is made up of a JavaScript page and a cascading style sheet. The VE Map control is hosted at http://dev.virtualearth.net/mapcontrol/v3/mapcontrol.js. The CSS is located at http://local.live.com/css/MapControl.css.
The first step to using Virtual Earth is to create an HTML file to contain all the necessary JavaScript functions to interact with the VE map.
Tip: To get detailed help and the references to the various methods in the Virtual Map control, download the Virtual Earth SDK. As of this writing, Microsoft Virtual Earth is in version 3.0. |
Add an HTML page to the project (right-click on project name in Solution Explorer and then select Add > New Item… Select HTML Page). Name the HTML page VirtualEarth.html.
Populate the HTML page with the following:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>My Virtual Earth</title>
<link href="http://local.live.com/css/MapControl.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/v3/mapcontrol.js"></script>
<script type="text/javascript">
var map = null;
//---Go to a particular location on the map---
function goto_map_position(lat, lng)
{
map.PanToLatLong(new VELatLong(lat,lng));
}
//---Load the Map---
function loadMap()
{
container = document.getElementById("VirtualEarthMap");
container.style.width = 488;
container.style.height = 469;
//---instantiate the VE map---
map = new VEMap("VirtualEarthMap")
map.LoadMap(new VELatLong(38.898748, -77.037684), 12 ,'r' , false);
}
</script>
</head>
<body onload="loadMap()" style="margin: 0px">
<div id="VirtualEarthMap">
</div>
</body>
</html>
The VirtualEarth.html page contains a reference to the VE Map control (.js) as well as the CSS file (.css). It also contains two JavaScript functions:
- goto_map_position()Goes to a particular location on the map based on the latitude and longitude specified.
- loadMap()Loads the map with the various parameters such as size of the map, zoom level, latitude and longitude, etc. It also attaches event handlers to the various events so that when certain events happen, the appropriate event handler is fired.
You would also need to set the Copy to Output Directory property of VirtualEarth.html to "Copy if newer" (via the Property window) so that the HTML page is deployed during runtime.
Using Sockets for Communication
While you can use Web services to pass the positional information from the Pocket PC to the server, doing so will take up a significant amount of bandwidth. This is because Web services uses XML SOAP packets for information exchange and this XML padding consumes considerable bandwidth consider that all we want to send is latitude and longitude (usually less then 20 bytes) information. Clearly, using Web services over a GPRS connection is not a feasible option (and an expensive one at that). For this reason, I have decided to program sockets directly to pass data to the server.
In Solution Explorer, add a new Class item to the project (right-click on project name and select Add > New Item…. Select the Class item) and name it as WMClient.vb. Populate the file as follows:
Imports System.Net.Sockets
Public Class WMClient
Public Event LatLng( _
ByVal ID As Integer, _
ByVal lat As Double, _
ByVal lng As Double)
Private _client As TcpClient
'---used for sending/receiving data---
Private data() As Byte
Public Sub New(ByVal client As TcpClient)
_client = client
'---start reading data from the client in a separate thread---
ReDim data(_client.ReceiveBufferSize)
_client.GetStream.BeginRead(data, 0, _
CInt(_client.ReceiveBufferSize), _
AddressOf ReceiveMessage, Nothing)
End Sub
Public Sub ReceiveMessage(ByVal ar As IAsyncResult)
'---read from client---
Dim bytesRead As Integer
Try
SyncLock _client.GetStream
bytesRead = _client.GetStream.EndRead(ar)
End SyncLock
'---client has disconnected---
If bytesRead < 1 Then
Exit Sub
Else
'---get the message sent---
Dim messageReceived As String = _
System.Text.Encoding.ASCII. _
GetString(data, 0, bytesRead)
Dim field() As String = messageReceived.Split(":")
'---raise an event to pass back the lat and lng---
RaiseEvent LatLng( _
field(0), _
Convert.ToDouble(field(1)), _
Convert.ToDouble(field(2)))
End If
'---continue reading from client---
SyncLock _client.GetStream
_client.GetStream.BeginRead(data, 0, _
CInt(_client.ReceiveBufferSize), _
AddressOf ReceiveMessage, Nothing)
End SyncLock
Catch ex As Exception
Console.WriteLine(ex.ToString)
End Try
End Sub
End Class
Basically, the WMClient class does the following:
- Contains a public event named LatLng. This event is raised when it receives data sent from Windows Mobile clients.
- Creates a constructor that begins to read incoming data from the connected client
- Processes incoming data from the ReceiveMessage() subroutine. The LatLng event is raised so that the data can be passed to the calling function (Form1) to update the map.
Coding the Server
Back to Form1, and we are now ready to code the main logic for the server. Switching to the code-behind of Form1, first import the following namespaces:
Imports System.Net.Sockets
Imports System.Threading
To programmatically interact with the map displayed within the WebBrowser control, you need to mark the Form1 class as COM-visible using the ComVisibleAttribute class:
<System.Runtime.InteropServices.ComVisibleAttribute(True)> _
Public Class Form1
Next, declare a member variable WMClient to store incoming connections:
<System.Runtime.InteropServices.ComVisibleAttribute(True)> _
Public Class Form1
Dim WithEvents user As WMClient
Next, define the
ListenForConnections() subroutine so that the server can continually listen for incoming connections:
Private Sub ListenForConnections ()
Const portNo As Integer = 3456
Dim localAdd As System.Net.IPAddress = _
System.Net.IPAddress.Parse("10.0.1.4")
Dim listener As New TcpListener(localAdd, portNo)
listener.Start()
While True
user = New WMClient(listener.AcceptTcpClient)
End While
End Sub
Author's Note: I am assuming the IP address of the server is 10.0.1.4. Replace this with the IP address of your server. Also, I have chosen the port 3456 to use for the communication. |
In the Form1_Load event, you will read the content of the VirtualEarth.html file and load it into the two WebBrowser controls:
Private Sub Form1_Load( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Dim fileContents As String
fileContents = My.Computer.FileSystem.ReadAllText( _
Application.StartupPath & "\VirtualEarth.html")
WebBrowser1.DocumentText = fileContents
WebBrowser1.ObjectForScripting = Me
WebBrowser2.DocumentText = fileContents
WebBrowser2.ObjectForScripting = Me
Dim t1 As New Thread(AddressOf ListenForConnections)
t1.Start()
End Sub
Also, note that I am using a separate thread to invoke the
ListenForConnections() subroutine. This is because the
ListenForConnections() subroutine is an infinite loop and using a separate thread to invoke it will prevent the UI of the application from freezing.
To handle events raised by the WMClient class, define the LatLng subroutine as the event handler. In this event, you will receive the ID, latitude, and longitude that were sent by the client. This information will be used to update the map, which is serviced by a delegate (you cannot directly update Windows controls in a separate thread):
Private Sub LatLng( _
ByVal ID As Integer, _
ByVal lat As Double, _
ByVal lng As Double) _
Handles user.LatLng
'---display map at specific location---
Me.BeginInvoke(New _
mydelegate(AddressOf GotoLocation), _
New Object() {ID, lat, lng})
End Sub
The
GotoLocation() subroutine updates the appropriate TextBox as well as the WebBrowser controls. To update the map to display the specified location, you need to invoke the
"goto_map_position" JavaScript function that you defined earlier in the HTML file:
 | |
Figure 8. This figure shows what the server will look like when it is started during testing. |
Private Delegate Sub mydelegate( _
ByVal ID As Integer, _
ByVal lat As Double, _
ByVal lng As Double)
Private Sub GotoLocation( _
ByVal ID As Integer, _
ByVal lat As Double, _
ByVal lng As Double)
Console.WriteLine(lat & ":" & lng)
Dim param() As Object = New Object() {lat, lng}
If ID = 1 Then
txtLatitude_1.Text = lat
txtLongitude_1.Text = lng
WebBrowser1.Document.InvokeScript( _
"goto_map_position", param)
ElseIf ID = 2 Then
txtLatitude_2.Text = lat
txtLongitude_2.Text = lng
WebBrowser2.Document.InvokeScript( _
"goto_map_position", param)
End If
End Sub
That's it! You can now test your server by pressing F5.
Figure 8 shows what the server will look like when it is started.
But that's not the end of the story. You now need to modify the Pocket PC client application so that it will send the latitude and longitude information to the server.