Need a mapping solution for your application? Why not try out Virtual Earth. They just released the ability to do with Web Services on top of their normal offering.
Shortly I’ll integrate this in with a mobile client, however I’m sharing the code for a Win32 client to help out early adopters. You can download my source for this over at Peace Love Code. The solution has both VB and C#. All you need to do is get an account for the account information needed.
Where to start? Lets knock on the door.
With most web services, you need an account. Here is the process for Virtual Earth:
- Go to https://mappoint-css.live.com/mwssignup and sign up with a Windows Live ID
- You’ll receive an email, click the link to confirm your sign up.
- Get a cup of coffee to wait for another email.
- You’ll receive another email with additional infromation. The big one is the URL for the VE Platform Customer Service Site (VEP CSS)
https://mappoint-css.live.com/CSCV3/ go there. - Set up your password.
Account secure, now on to Visual Studio. We’ll have to add in the web service for the token. If you don’t know how to do this, here are the steps in Visual Studio 2008.
- Go to Project->Add Web Reference
- Use https://staging.common.virtualearth.net/find-30/common.asmx as your URL.
- You’ll be prompted for a login and password, use your VE credentials for this. This is not your Live ID.
- Click Ok
Now that we have the Common Service, we’ll look at the code to get the token from the service. First we’ll have to make a reference to the web service by having a using / import statement. And we’ll create a Authentication class. Since I’ll be using a Win32 client, I’ll have to go about a different way about getting my IP.
C#:
public class Authentication
{
public static string strVEWSToken;
public static string Authenticate()
{
// for web pages
// Page.Request.UserHostAddress
var ipToken = "127.0.0.1";
var ips = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
foreach (var ip in ips)
{
if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
continue;
// got a valid IPv4 address
ipToken = ip.ToString();
break;
}
return Authenticate(ipToken);
}
public static string Authenticate(string strIP)
{
var commonService = new CommonService
{
Url = "https://staging.common.virtualearth.net/find-30/common.asmx",
Credentials = new NetworkCredential("SomeNumber", "SomePassword")
};
// Create the TokenSpecification object to pass to GetClientToken.
var tokenSpec = new TokenSpecification
{
// Use the Page object to retrieve the end-client’s IPAddress.
ClientIPAddress = strIP,
// The maximum allowable token duration is 480 minutes (8 hours).
// The minimum allowable duration is 15 minutes.
TokenValidityDurationMinutes = 480
};
// Now get a token from the Virtual Earth Platform Token service.
strVEWSToken = commonService.GetClientToken(tokenSpec);
return strVEWSToken;
}
}
vb.net:
Public Class Authentication
Public Shared strVEWSToken As String
Public Shared Function Authenticate() As String
' for web pages
' Page.Request.UserHostAddress
Dim ipToken As String = "127.0.0.1"
Dim ips As var = Dns.GetHostEntry(Dns.GetHostName()).AddressList
For Each ip As IPAddress In ips
If ip.AddressFamily <> System.Net.Sockets.AddressFamily.InterNetwork Then
Continue For
End If
' got a valid IPv4 address
ipToken = ip.ToString()
Exit For
Next
Return Authenticate(ipToken)
End Function
Public Shared Function Authenticate(ByVal strIP As String) As String
Dim commonService As New CommonService()
commonService.Url = "https://staging.common.virtualearth.net/find-30/common.asmx"
commonService.Credentials = New NetworkCredential("SomeNumbers", "YourPassword")
' Create the TokenSpecification object to pass to GetClientToken.
Dim tokenSpec As New TokenSpecification()
' Use the Page object to retrieve the end-client’s IPAddress.
tokenSpec.ClientIPAddress = strIP
' The maximum allowable token duration is 480 minutes (8 hours).
' The minimum allowable duration is 15 minutes.
tokenSpec.TokenValidityDurationMinutes = 480
' Now get a token from the Virtual Earth Platform Token service.
strVEWSToken = commonService.GetClientToken(tokenSpec)
Return strVEWSToken
End Function
End Class
You have a token? Come in, Come in!
With a token, now we’ll be able to some querying. For this example, we’ll just fetch imagery, however you can get geographical data, routing and searching data following a similar route taken in this example. To find out where to get these WDSLs and more about it, MSDN has a full list along with a detailed break down.
For this example, we’ll add a service reference to https://staging.dev.virtualearth.net/webservices/v1/imageryservice/imageryservice.svc. You can either go Project->Add Service Reference or Add Web Reference. I used a service reference for this example. I also named it ImageryService as I’m extremely creative.
This example’s primary goal is to show how to get bird’s eye data back. For that, we’ll have to use GetImageryMetadata. If you just want overhead data with or without roads overlaid, you can use GetMapUri instead.
A quick UX design.
I have crazy user interface skills (typically me calling up Jeff for a pow-wow). I have a quick and dirty interface here for getting some needed information.
- With Bird’s eye, you can shift your orientation.
- Setting what view you want.
- What zoom level, 1 to 21.
- What is your device, mobile or desktop
You’ll note I left off latitude and longitude, that is this was just a demo application, I wanted to get up and running quick so I’ve hardcoded my latitude and longitude to Forest Park in St. Louis, MO.
To populate the combo boxes, we’ll use a quick trick. We’ll use Enum.GetValues to set the data source of the combo boxes. To fetch the data, we’ll use SelectedValue on the combo box and cast to the needed data type.
C#:
cmbView.DataSource = Enum.GetValues(typeof(MapStyle));
cmbDeviceType.DataSource = Enum.GetValues(typeof(DeviceType));
vb.net:
cmbView.DataSource = [Enum].GetValues(GetType(MapStyle))
cmbDeviceType.DataSource = [Enum].GetValues(GetType(DeviceType))
Making the secret knock for images
After getting what the user wanted, we need to do a request.
C#:
var imageryService = new ImageryServiceClient();
var request = new ImageryMetadataRequest
{
Style = mapStyle,
Options = new ImageryMetadataOptions
{
Location = new Location {Latitude = 38.637593, Longitude = (-90.270843)},
ZoomLevel = zoomLevel,
Heading = new Heading {Orientation = orientation}
},
Credentials = new Credentials {Token = token},
UserProfile = new UserProfile {DeviceType = deviceType}
};
var metaDataReturn = imageryService.GetImageryMetadata(request);
vb.net:
Dim imageryService As New ImageryServiceClient()
Dim request As New ImageryMetadataRequest()
request.Style = mapStyle
request.Options = New ImageryMetadataOptions()
request.Options.Location = New Location()
request.Options.Location.Latitude = 38.637593
request.Options.Location.Longitude = (-90.270843)
request.Options.ZoomLevel = zoomLevel
request.Options.Heading = New Heading()
request.Options.Heading.Orientation = orientation
request.Credentials = New Credentials()
request.Credentials.Token = token
request.UserProfile = New UserProfile()
request.UserProfile.DeviceType = deviceType
Dim metaDataReturn As ImageryMetadataResponse = imageryService.GetImageryMetadata(request)
Data Fetched but we need a full out URI
We have our data but depending on the source, we’ll have to do some additional transformation. Depending on if you do a overhead or bird eye view, you’ll have to manipulate the URI to include subdomain, culture, and tile id. Once again, we’re assuming a fat client application. For a website, we’d grab the culture information differently just like we did with the IP.
C#:
private static string createBirdEyeUri(string imageUri, int tileId, string[] subdomain, string token)
{
//{tileId}, {subdomain}, {token}
if (subdomain.Length > 0)
imageUri = imageUri.Replace("{tileId}", tileId.ToString()).
Replace("{subdomain}", subdomain[0]).
Replace("{token}", token);
return imageUri;
}
private static string createOverheadUri(string imageUri, string token)
{
//{culture}, {token}
imageUri = imageUri.Replace("{culture}", System.Threading.Thread.CurrentThread.CurrentCulture.IetfLanguageTag).
Replace("{token}", token);
return imageUri;
}
vb.net:
Private Shared Function createBirdEyeUri(ByVal imageUri As String, ByVal tileId As Integer, ByVal subdomain As String(), ByVal token As String) As String
'{tileId}, {subdomain}, {token}
If subdomain.Length > 0 Then
imageUri = imageUri.Replace("{tileId}", tileId.ToString()).Replace("{subdomain}", subdomain(0)).Replace("{token}", token)
End If
Return imageUri
End Function
Private Shared Function createOverheadUri(ByVal imageUri As String, ByVal token As String) As String
'{culture}, {token}
imageUri = imageUri.Replace("{culture}", System.Threading.Thread.CurrentThread.CurrentCulture.IetfLanguageTag).Replace("{token}", token)
Return imageUri
End Function
Laying down the tiles, don’t forget the grout
For this example, we’ll do just bird eye as it is slightly more complex. We’ll have to create a tile layout and programmatically add in the tiles since we don’t know how many will be there.
c#:
var result = (ImageryMetadataBirdseyeResult)metaDataReturn.Results[0];
var pics = new PictureBox[result.TilesX * result.TilesY];
int currentTop = 0;
int currentLeft = 0;
for (int y = 0; y < result.TilesY; y++)
{
for (int x = 0; x < result.TilesX; x++)
{
int currentIndex = y * result.TilesX + x;
pics[currentIndex] = new PictureBox
{
Top = currentTop,
Left = currentLeft,
Width = metaDataReturn.Results[0].ImageSize.Width,
Height = metaDataReturn.Results[0].ImageSize.Height,
ImageLocation =
createBirdEyeUri(result.ImageUri, currentIndex, result.ImageUriSubdomains, token)
};
currentLeft += metaDataReturn.Results[0].ImageSize.Width;
}
currentTop += metaDataReturn.Results[0].ImageSize.Height;
currentLeft = 0;
}
gbImagery.Controls.AddRange(pics);
vb.net:
Dim result As ImageryMetadataBirdseyeResult = DirectCast(metaDataReturn.Results(0), ImageryMetadataBirdseyeResult)
Dim pics As PictureBox() = New PictureBox(result.TilesX * result.TilesY) {}
Dim currentTop As Integer = 0
Dim currentLeft As Integer = 0
Dim y As Integer = 0
While y < result.TilesY
Dim x As Integer = 0
While x < result.TilesX
Dim currentIndex As Integer = y * result.TilesX + x
pics(currentIndex) = New PictureBox()
pics(currentIndex).Top = currentTop
pics(currentIndex).Left = currentLeft
pics(currentIndex).Width = metaDataReturn.Results(0).ImageSize.Width
pics(currentIndex).Height = metaDataReturn.Results(0).ImageSize.Height
pics(currentIndex).ImageLocation = createBirdEyeUri(result.ImageUri, currentIndex, result.ImageUriSubdomains, token)
currentLeft += metaDataReturn.Results(0).ImageSize.Width
System.Math.Max(System.Threading.Interlocked.Increment(x),x - 1)
End While
currentTop += metaDataReturn.Results(0).ImageSize.Height
currentLeft = 0
System.Math.Max(System.Threading.Interlocked.Increment(y),y - 1)
End While
gbImagery.Controls.AddRange(pics)
Go Do’s!
For more information and examples about Virtual Earth and their services, visit the Virtual Earth blog on MSDN. You have the ability to play with geographical, routing, and searching data on top of just the extreme high resolution earth data.
You can download my source for this over at Peace Love Code. The solution has both VB and C#. All you need to do is get an account for the account information needed.