Rendering Bing Maps on Windows 7 using Windows Web Services and Direct2D – Part I

The Bing Maps Web Services provide a wide array of methods that can be accessed programmatically. If you want to build desktop applications using native code, you can use Windows Web Services (WWSAPI) to access the Bing Maps Web Services. This series of three articles demonstrates using the Imagery service of the Bing Web Services using WWSAPI and then displaying the map images using Direct2D.

  1. In the first article, we shall see how to use Windows Web Services and obtain a login token from Bing Maps Web Services.
  2. In the second part, we will perform an address lookup using the Geocode web service and fetch imagery metadata from the Imagery Web Service.
  3. In the final part, we will then download image data using the native Concurrency runtime and render the map data using Direct2D.

Requirements

  1. Microsoft Visual Studio 2008/2010
  2. Microsoft Windows 7 SDK
  3. Bing Maps for Enterprise developer credentials. You can sign up for a free developer account on the Bing Maps Developer Account Request (https://mappoint-css.live.com/mwssignup/) site.

Creating the Project

The project is a standard Win32 Project developed using native C++

  1. Open Visual Studio
  2. Select New and then Project
  3. From the list of project types, choose Win32 and then Win32 project.
  4. Name your project BingMapsSample and then click OK.
  5. In the Win32 Application Wizard page, choose the application type “Windows Application” and then under the Additional Options, choose “Empty Project”.

Setting up the client Token

The Bing Maps Web Services requires that you have a client token to make requests. In our sample, we will retrieve the token at application startup. This token can only be obtained by making a web request to the Bing Maps token service.

Retrieving and referencing the Bing Maps Token Service
  • The Bing Maps token service is located at https://staging.common.virtualearth.net/find-30/common.asmx and the WSDL can be obtained by navigating to https://staging.common.virtualearth.net/find-30/common.asmx?wsdl.
  • You will be prompted to login with your Bing Maps developer credentials.
  • After you login, choose File->Save As and download the wsdl script into a folder of your choice. In this sample, we shall download this into the BingMapsSample solution directory and name it as “common.wsdl”.
  • Before we generate the client stub functions from the downloaded WSDL, we need to make a few changes. These changes are meant to make writing code easier and less error prone later on.
  • Make sure the following policy element is present in wsdl:definitions in the common.wsdl file.
 <wsp:Policy wsu:Id="DigestAuth_policy">
    <wsp:ExactlyOne>
      <wsp:All>
        <http:DigestAuthentication xmlns:http="https://schemas.microsoft.com/ws/06/2004/policy/http" />
      </wsp:All>
    </wsp:ExactlyOne>
</wsp:Policy>
  • add these two prefix definitions to wsdl:definitions: xmlns:wsp="https://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu="https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd
  • Then add this element to the wsdl:binding element:

<wsp:PolicyReference URI="#DigestAuth_policy" />

  • Open a new Windows SDK command Shell by navigating to Start->All Programs->Microsoft Windows SDK v7.0->CMD Shell.
  • Navigate to the BingMapsSample solution folder where the “common.wsdl” file is located.
  • Execute the following command: Wsutil /wsdl:common.wsdl
  • Wsutil is a utility that ships as part of the Windows 7 SDK. It is used to generate ‘C’ style wrapper functions and structs from WSDL definitions. The /wsdl option specifies the name and location of the WSDL file.
  • Wsutil parses the common.wsdl file and produces 2 output files, named common.wsdl.h and common.wsdl.c. These files contain the wrapper functions and structs that we will use to make calls to the Bing Maps token service.

Requesting a Token

In order to retrieve a token, you need to prepare a Token Specification structure. This contains

  • Client IP address. This field is used for tracking purposes only.
  • The expiration length of the token.

To retrieve a token:

  1. Add the common.wsdl.h and common.wsdl.c files to the BingMapsSample solution.
  2. Create a new C++ source file. Save it as CommonToken.cpp
  3. In CommonToken.cpp, add a include reference to webservices.h, followed by a reference to common.wsdl.h
  4. Create a variable to type WCHAR. We shall name it commonToken.
  5. Create a new method called ObtainToken. This method executes a call to request a token and then set it to the commonToken variable.
    HRESULT hr = NOERROR;
   WS_ERROR* error = NULL;
   WS_HEAP* heap = NULL;
   WS_SERVICE_PROXY* proxy = NULL;

   WS_HTTP_HEADER_AUTH_BINDING_TEMPLATE bindingTemplate = {};
   WS_STRING_WINDOWS_INTEGRATED_AUTH_CREDENTIAL cred = {{WS_STRING_WINDOWS_INTEGRATED_AUTH_CREDENTIAL_TYPE}};
    
   cred.username.chars = szUserName;
   cred.username.length = (ULONG)wcslen(cred.username.chars);
   cred.password.chars = szPassword;
   cred.password.length = (ULONG)wcslen(cred.password.chars);

   bindingTemplate.httpHeaderAuthSecurityBinding.clientCredential = &cred.credential;
    
   WS_ENDPOINT_ADDRESS address = {};
    
   WS_STRING url = WS_STRING_VALUE(L"https://staging.common.virtualearth.net/find-30/common.asmx");    
   address.url = url;
            
   TokenSpecification tokenSpec = {};
   tokenSpec.ClientIPAddress = L"Current Computer IP address";

   tokenSpec.TokenValidityDurationMinutes = 300;
   WCHAR* tokenResult[MAX_PATH] = {0};
    
    // Create an error object for storing rich error information
    hr = WsCreateError(
        NULL, 
        0, 
        &error);
    if (FAILED(hr))
    {
        goto Exit;
    }
    
    // Create a heap to store deserialized data
    hr = WsCreateHeap(
        /*maxSize*/ 2048, 
        /*trimSize*/ 512, 
        NULL, 
        0, 
        &heap, 
        error);
    if (FAILED(hr))
    {
        goto Exit;
    }
    
    // Create the proxy
    
    hr = CommonServiceSoap_CreateServiceProxy(
            &bindingTemplate,
            NULL,
           0,
            &proxy,
            error);

    if (FAILED(hr))
    {
        goto Exit;
    }
    
    hr = WsOpenServiceProxy(
        proxy, 
        &address, 
        NULL, 
        error);
    if (FAILED(hr))
    {
        goto Exit;
    }

    hr = CommonServiceSoap_GetClientToken(
         proxy,
         &tokenSpec,
         tokenResult,
         heap,
         NULL,
         0,
         NULL,
         error);
        
    if (SUCCEEDED(hr))
    {
        wcscpy(commonToken_, *tokenResult);
     }
    if (FAILED(hr))
    {
        goto Exit;
    }   

                      
Exit:
    if (FAILED(hr))
    {
        // Print out the error
        PrintError(hr, error);
    }
    if (proxy != NULL)
    {
        WsCloseServiceProxy(
            proxy, 
            NULL, 
            NULL);
    
        WsFreeServiceProxy(
            proxy);
    }
    
    
    if (heap != NULL)
    {
        WsFreeHeap(heap);
    }
    if (error != NULL)
    {
        WsFreeError(error);
    }
  • The PrintError function helps us obtain rich error information that is supported and provided by the Windows Web Services API.
 // Print out rich error info
void PrintError(HRESULT errorCode, WS_ERROR* error)
{
    wprintf(L"Failure: errorCode=0x%lx\n", errorCode);

    if (errorCode == E_INVALIDARG || errorCode == WS_E_INVALID_OPERATION)
    {
        // Correct use of the APIs should never generate these errors
        wprintf(L"The error was due to an invalid use of an API.  This is likely due to a bug in the program.\n");
        DebugBreak();
    }

    HRESULT hr = NOERROR;
    if (error != NULL)
    {
        ULONG errorCount;
        hr = WsGetErrorProperty(error, WS_ERROR_PROPERTY_STRING_COUNT, &errorCount, sizeof(errorCount));
        if (FAILED(hr))
        {
            goto Exit;
        }
        for (ULONG i = 0; i < errorCount; i++)
        {
            WS_STRING string;
            hr = WsGetErrorString(error, i, &string);
            if (FAILED(hr))
            {
                goto Exit;
            }
            wprintf(L"%.*s\n", string.length, string.chars);
        }
    }
Exit:
    if (FAILED(hr))
    {
        wprintf(L"Could not get error string (errorCode=0x%lx)\n", hr);
    }
}
  • Finally add the program entry point, WinMain. Inside WinMain, call the ObtainToken method. The code in WinMain looks as follows
 /******************************************************************
*                                                                 *
*  WinMain                                                        *
*                                                                 *
*  Application entrypoint                                         *
*                                                                 *
******************************************************************/

int WINAPI wWinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPWSTR pszCmdLine,
    int nCmdShow)

{
       WCHAR commonToken[MAX_PATH] = {0};
       ObtainToken();
}

That’s it!! This sample piece of code allows you as a developer to query the Bing Maps Web Services and obtain a login token. In our next part, we shall see how to access the geocode web service and perform an address lookup using Windows Web Services.