Mapping, Web Services, & C++

The Microsoft MapPoint web service provides methods for looking up addresses, rendering maps, calculating routes, and a number of other useful mapping functions.


February 01, 2005
URL:http://www.drdobbs.com/mapping-web-services-c/184401910

February, 2005: Mapping, Web Services, & C++

Neil Roodyn is an independent software consultant and author who can be contacted at http://www.Roodyn.com/ or [email protected].


Signing Up for MapPoint Web Services


The Microsoft MapPoint web service [1] provides methods for looking up addresses, rendering maps, calculating routes, and a number of other useful mapping functions. I was recently asked to examine the possibility of helping a client get an application up and running on a Microsoft Smartphone, a cell phone with built-in PDA-like capabilities [2] that utilizes the MapPoint web service. The client wanted the phone screen to display a map of an address that was entered. I initially thought the .NET Compact Framework would be a good place to start, but it turned out that the client had already deployed a number of Smartphone 2002 devices, which unfortunately don't support the .NET Framework. Consequently, the only way to write applications for them is using embedded Visual C++ [3].

In this article, I describe what's involved in building client applications for the MapPoint web service in C++. To fully implement the techniques and code I present here, you must have MapPoint developer credentials. To sign up for credentials, see the accompanying text box "Signing Up for MapPoint Web Services."

Mobile devices such as the Smartphone present challenges above and beyond a normal C++-based web-service client, namely:

As an extreme programmer, I always start with a couple of spikes (experiments) to get the lay of the land. The idea is to get a thin slice of the functionality working. In this case, I wanted to create a small C++ application that calls the web service and returns some information.

Before writing any code, however, I considered whether a toolkit would help since a couple of years ago, I investigated several C++ toolkits for creating and using web services. In particular, I used RogueWave's LEIF (short for "Lightweight Enterprise Integration Framework") toolkit [4] to help a client connect a legacy C++ application through to newer .NET clients. At the same time, I also looked at gSOAP [5] and PocketSOAP [6]. However, the problem with toolkits is that they all carry some baggage. While small, the baggage is still there and embedded solutions demand the least-possible amount of baggage. Every kilobyte counts and any general solution adds a few more KBs.

Luckily, web services such as MapPoint are easy to use—essentially, they're HTTP calls that send and return text files—so I decided a toolkit would be overkill for this project. In the time it would take to learn to use the toolkit, I could have written most of the solution.

For example, the first spike I implemented was a console application that used the Win32 WinInet functions to open a connection, set up a session, and make a request. Conveniently, WinInet methods are available on the Smartphone, making life easier. Listing 1, for instance, calls the GetVersionInfo method on the web service, thereby validating that the code is working.

When you run this program, you see the output in Figure 1. The version number in the XML you have retrieved is 03.50.22.1200 and you don't need to write the code here to extract that. Again, the point of this exercise was to see how easy it is to call the authenticated MapPoint web service.

How do you know what the XML to build the SOAP request should look like? The Web Service Description Language (WSDL) file provides everything you need to work this out. This WSDL can be found at http://staging.mappoint.net/standard-30/mappoint.wsdl. The description of the GetVersionInfoSoapIn message defines the parameters that are expected in the body of the SOAP when the call is made to the operation. In this case, the parameters are described in the GetVersionInfo element (Example 1). As you can see in the WSDL, the GetVersionInfo element is empty, so no parameters need to be embedded in the SOAP. Of course, this is a simple call. Sticking to the desktop console application (thereby letting you do this without a Smartphone), I'll expand this code to find the latitude and longitude of an address using the MapPoint web service.

First you create a FindAddress method that you can call with an Internet session handle and an address. In Listing 2, I have extracted from the main method the functionality that opens the HTTP session, sets up the headers, and sends the request. I have also added a call to a method called BuildFindAddressSoapRequest.

The BuildFindAddressSoapRequest method is similar to the BuildSoap method. You need to work out what the SOAP body needs to contain. This time it is somewhat more complicated than the GetVersionInfo operation. The WSDL describes the FindAddressSoapIn, which takes a FindAddress element. The FindAddress element consists of a FindAddressSpecification. The FindAddressSpecification contains the DataSourceName (a string), an Address type, and FindOptions.

From this WSDL (see Example 2) and the help available on the MSDN MapPoint web site [1], you can create the BuildFindAddressSoapRequest method in Listing 3. In the main method, you can call the FindAddress method with an address, as in Listing 4.

When you run this application, you can see that the latitude and longitude are returned in the body of the XML SOAP envelope (Figure 2). You should probably think about extracting the latitude and longitude from the XML; see Listing 5. You can then call that method from the FindAddress method when you have read the response (Listing 6).

Granted, this is simplistic—but that's the point! If you were doing this with a SOAP toolkit such as gSOAP, LEIF, or PocketSOAP, you would get structures generated for each of the custom types exposed by the web service. For example, the FindAddressSpecification structure might look like this:

struct FindAddressSpecification 
{
TCHAR* DataSourceName;
Address* InputAddress;
FindOptions* Options;
}

As you have this structure, you would need the Address and FindOptions structures:

struct Address 
{
TCHAR* AddressLine;
TCHAR* PrimaryCity;
TCHAR* SecondaryCity;
TCHAR* Subdivision;
TCHAR* PostalCode;
TCHAR* CountryRegion;
TCHAR* FormattedAddress;
}

These structures add to the overhead mentioned earlier, and you don't need them just to get a latitude and longitude. If you are using a small subset of what is provided by a web service, then you can write simple code that gets what you want from the web service without using a toolkit. Or when you are writing a client application that uses only a small number of calls, you can get away without the toolkit. However, the toolkits are great for when you are using a large number of methods from a web service.

The requirements for the client application I am presenting here is to display a map of an address. This requires two method calls—one to get the latitude and longitude, and another to get the map.

Using the WSDL, you can work out what you need to pass in the SOAP body to get a map from a latitude and longitude. You can build this SOAP envelope in a method similar to the BuildFindAddressSoapRequest method (Listing 7). This BuildGetMapSoapRequest can be called from a method that opens the HTTP session and sends the request. Similar to the FindAddress method, this new GetMap method calls an operation on the MapPoint web service (Listing 8). Notice that the endpoint has changed from /Find-30/FindService.asmx to /Render-30/RenderService.asmx.

How did I know this was the endpoint for this operation? From the WSDL, which describes the service that supports the GetMap method:

<wsdl:service name="RouteService">
	. . . 
 <wsdl:port name="RouteServiceSoap" binding="tns:RouteServiceSoap">
  <soap:address location=
   "http://routev3.staging.mappoint.net/Route-30/RouteService.asmx" /> 
 </wsdl:port>
</wsdl:service>

You can also see that not only the endpoint has changed, but the server is also different. This means you cannot use the same Internet session for the GetMap call that you used for the FindAddress call. To call the GetMap method, you need to call InternetConnect again with the server name (Listing 9).

To get the map image (see Figure 3), you need to extract the image bits from the response you get in the GetMap method. For this experiment, I can save the image to a file. This SaveMapBitmap (Listing 10) can be called from the GetMap method.

Porting this code from the console to the Smartphone involves two other issues:

References

[1] http://msdn.microsoft.com/MapPoint.

[2] http://www.microsoft.com/windowsmobile/smartphone/default.mspx.

[3] http://www.microsoft.com/downloads/details.aspx?FamilyID=1dacdb3d-50d1-41b2-a107-fa75ae960856&displaylang=en.

[4] http://www.roguewave.com/products/leif/.

[5] http://www.cs.fsu.edu/~engelen/soap.html.

[6] http://www.pocketsoap.com/.

[7] http://blogs.msdn.com/cthota.

February, 2005: Mapping, Web Services, & C++

Example 1: WSDL describing GetVersionInfo input parameters.

<wsdl:message name="GetVersionInfoSoapIn">
 <wsdl:part name="parameters" element="tns:GetVersionInfo" />  </wsdl:message>
<s:element name="GetVersionInfo">
 <s:complexType /> 
</s:element>

February, 2005: Mapping, Web Services, & C++

Example 2: WSDL describing FindAddress input parameters.

<wsdl:message name="FindAddressSoapIn">
  <wsdl:part name="parameters" element="tns:FindAddress" /> 

<s:element name="FindAddress">
 <s:complexType>
  <s:sequence>
  <s:element minOccurs="0" maxOccurs="1" name="specification" 
                         type="tns:FindAddressSpecification" /> 
  </s:sequence>
 </s:complexType>
</s:element>

<s:complexType name="FindAddressSpecification">
 <s:sequence>
  <s:element minOccurs="0" maxOccurs="1" 
                   name="DataSourceName" type="s:string" /> 
  <s:element minOccurs="0" maxOccurs="1" 
                   name="InputAddress" type="tns:Address" /> 
  <s:element minOccurs="0" maxOccurs="1" 
                   name="Options" type="tns:FindOptions" /> 
 </s:sequence>
</s:complexType>

<s:complexType name="Address">
  <s:sequence>
  <s:element minOccurs="0" maxOccurs="1" 
                           name="AddressLine" type="s:string" /> 
  <s:element minOccurs="0" maxOccurs="1" 
                           name="PrimaryCity" type="s:string" /> 
  <s:element minOccurs="0" maxOccurs="1" 
                           name="SecondaryCity" type="s:string"/> 
  <s:element minOccurs="0" maxOccurs="1" 
                           name="Subdivision" type="s:string" /> 
  <s:element minOccurs="0" maxOccurs="1" 
                           name="PostalCode" type="s:string" /> 
  <s:element minOccurs="0" maxOccurs="1" 
                           name="CountryRegion" type="s:string" /> 
  <s:element minOccurs="0" maxOccurs="1" 
                           name="FormattedAddress" type="s:string" /> 
 </s:sequence>
</s:complexType>

<s:complexType name="FindOptions">
 <s:sequence>
  <s:element minOccurs="0" maxOccurs="1" 
                           name="Range" type="tns:FindRange" /> 
  <s:element minOccurs="1" maxOccurs="1" 
                           name="SearchContext" type="s:int" /> 
  <s:element minOccurs="1" maxOccurs="1" 
                           name="ResultMask" type="tns:FindResultMask" /> 
  <s:element minOccurs="0" maxOccurs="1" default="0.85" 
                           name="ThresholdScore" type="s:double" /> 
 </s:sequence>
</s:complexType>

<s:complexType name="FindRange">
 <s:sequence>
  <s:element minOccurs="0" maxOccurs="1" default="0" 
                           name="StartIndex" type="s:int" /> 
  <s:element minOccurs="0" maxOccurs="1" default="25" 
                           name="Count" type="s:int" /> 
 </s:sequence>
</s:complexType>
<s:simpleType name="FindResultMask">
 <s:list>
 <s:simpleType>
 <s:restriction base="s:string">
  <s:enumeration value="LatLongFlag" /> 
  <s:enumeration value="EntityFlag" /> 
  <s:enumeration value="AddressFlag" /> 
  <s:enumeration value="BestMapViewFlag" /> 
 </s:restriction>
 </s:simpleType>
 </s:list>
</s:simpleType>

February, 2005: Mapping, Web Services, & C++

Figure 1: Console output.

February, 2005: Mapping, Web Services, & C++

Figure 2: The latitude and longitude in the returned SOAP.

February, 2005: Mapping, Web Services, & C++

Figure 3: Saved map image.

February, 2005: Mapping, Web Services, & C++

Listing 1

#include <iostream>
#include <tchar.h>
#include "Windows.h"
#include "WinInet.h"

using namespace std;
void BuildSoapRequest(TCHAR* pszSoap)
{
    _tcscpy(pszSoap, _T("") );
    _tcscat(pszSoap, 
       _T("<soap:Envelope 
                     xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"
                     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" 
                     xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">") );
    _tcscat(pszSoap, _T("<soap:Header>") );
    _tcscat(pszSoap, _T("</soap:Header>") );
    _tcscat(pszSoap, _T("<soap:Body>") );
    _tcscat(pszSoap, _T("<GetVersionInfo 
                     xmlns=\ "http://s.mappoint.net/mappoint-30/\">") );
    _tcscat(pszSoap, _T("</GetVersionInfo>") );
    _tcscat(pszSoap, _T("</soap:Body>") );
    _tcscat(pszSoap, _T("</soap:Envelope>") );
}
int _tmain(int argc, _TCHAR* argv[])
{
    LPTSTR AcceptTypes[2] = {TEXT("text/xml"), NULL}; 
    DWORD dwFlags = INTERNET_FLAG_RELOAD | 
        INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_KEEP_CONNECTION; 
    HINTERNET hNet = InternetOpen("SpikeApp", INTERNET_OPEN_TYPE_PRECONFIG,
        NULL, NULL, 0);
    if (hNet)
    {
        wcout << L"Opened Internet connection and got valid handle" << endl;
        HINTERNET hSession = InternetConnect(hNet,  
                                         _T("findv3.staging.mappoint.net"),
            INTERNET_DEFAULT_HTTP_PORT, _T("<username>"), _T("<password>"),
            INTERNET_SERVICE_HTTP, 0, 0);
        if (hSession)
        {
            wcout << L"Opened session for WS and got valid handle" << endl;
            HINTERNET hRequest = HttpOpenRequest(hSession, _T("POST"), 
                _T("/Find-30/Common.asmx"), HTTP_VERSION, 
                NULL, (LPCTSTR*)AcceptTypes, dwFlags, 0);
            if (hRequest)
            {
                wcout << L"Opened Request for WS & got valid handle" << endl;
                TCHAR szSoapAction[256];
                _tcscpy( szSoapAction, _T("Content-Type: text/xml; 
                                                         charset=utf-8\n") );
                _tcscat( szSoapAction, _T("SOAPAction: \"") );
                _tcscat( szSoapAction, _T
                       ("http://s.mappoint.net/mappoint-30/GetVersionInfo") );
                _tcscat( szSoapAction , _T("\"\0") );
                
                TCHAR szSoap[512];
                BuildSoapRequest(szSoap);

                BOOL bSent = HttpSendRequest(hRequest, szSoapAction, 
                    _tcslen(szSoapAction), szSoap, _tcslen(szSoap) );
                if ( bSent )
                {
                    CHAR* lpBufferA = new CHAR[32000];
                    DWORD dwcBuffer;
                    if (!InternetReadFile (hRequest, 
                                      (LPVOID)lpBufferA, 32000, &dwcBuffer)) 
                    {
                        wcout << L"Failed to read response" << endl;
                    }
                    else{
                        CHAR* szResponse = new CHAR[dwcBuffer + 1];
                        strncpy(szResponse,lpBufferA, dwcBuffer);
                        wcout << L"Response" << endl << szResponse << endl;
                        delete [] szResponse;
                    }
                    delete [] lpBufferA;
                }
                else{
                    DWORD dwErr = GetLastError();
                    wcout << L"Send Request Failed. Error:" << dwErr << endl;
                }
                InternetCloseHandle(hRequest);
            }
            InternetCloseHandle(hSession);
        }
        InternetCloseHandle(hNet);
    }
    return 0;
}

February, 2005: Mapping, Web Services, & C++

Listing 10

void SaveMapBitmap(CHAR* szData)
{
    HBITMAP hBmp = NULL;
    CHAR* pszStart = strstr(szData, "<Bits>");
    if (pszStart)
    {
        pszStart+= strlen("<Bits>" );
        CHAR* pszEnd = strstr(szData, "</Bits>");
        int nLen = pszEnd-pszStart;
        CHAR* pBits = new CHAR[nLen];
        strncpy(pBits,pszStart, nLen );
        BYTE *pConvertedBuffer = NULL;      
        int nSize = 0;
        Base64Decode(pBits, nLen, NULL, &nSize);
        pConvertedBuffer = new BYTE[nSize+1];
        Base64Decode(pBits, nLen, pConvertedBuffer, &nSize);
        HANDLE hFile = CreateFile(_T("C:\\MapImage.bmp"), GENERIC_WRITE, 
            0, NULL, CREATE_ALWAYS, 0, 0);
        if (hFile != INVALID_HANDLE_VALUE) 
        { 
            DWORD dwBytes = 0;
            WriteFile(hFile, pConvertedBuffer, nSize, &dwBytes,NULL);
            CloseHandle(hFile);
        }
        delete [] pConvertedBuffer;
        delete [] pBits;
    }
}

February, 2005: Mapping, Web Services, & C++

Listing 2

void FindAddress(HINTERNET hSession, TCHAR* szAddress)
{
    LPTSTR AcceptTypes[2] = {TEXT("text/xml"), NULL}; 
    DWORD dwFlags = INTERNET_FLAG_RELOAD | 
        INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_KEEP_CONNECTION; 

    HINTERNET hRequest = HttpOpenRequest(hSession, _T("POST"), 
        _T("/Find-30/FindService.asmx"), HTTP_VERSION, 
        NULL, (LPCTSTR*)AcceptTypes, dwFlags, 0);
    if (hRequest)
    {
        wcout << _T("Opened Request for WS and got valid handle") << endl;
        TCHAR szSoapAction[256];
        _tcscpy( szSoapAction, _T("Content-Type: text/xml; charset=utf-8\n") );
        _tcscat( szSoapAction, _T("SOAPAction: \"") );
        _tcscat( szSoapAction, _T
               ("http://s.mappoint.net/mappoint-30/FindAddress") );
        _tcscat( szSoapAction , _T("\"\0") );
        TCHAR szSoap[1024];
        BuildFindAddressSoapRequest(szAddress, szSoap);
        DWORD dwSoapSize = _tcslen(szSoap);
        CHAR* szSoapRequestA = new CHAR[dwSoapSize + 1];
        DWORD dwSize = WideCharToMultiByte (CP_ACP, 0, szSoap, 
            -1, szSoapRequestA, 0, NULL, NULL);  
        WideCharToMultiByte (CP_ACP, 0, szSoap, -1, 
            szSoapRequestA, dwSize, NULL, NULL);
        DWORD dwError, dwStatus, dwErrorCode;
        DWORD dwStatusSize = sizeof(dwStatus);
        BOOL bSent = HttpSendRequest(hRequest, szSoapAction, 
            _tcslen(szSoapAction), szSoapRequestA, strlen(szSoapRequestA) );
        if ( bSent )
        {
            CHAR* lpBufferA = new CHAR[32000];
            DWORD dwcBuffer;
            if (!InternetReadFile (hRequest, (LPVOID)lpBufferA, 32000, &dwcBuffer)) 
            {
                wcout << _T("Failed to read response") << endl;
            }
            else{
                CHAR* szResponse = new CHAR[dwcBuffer + 1];
                strncpy(szResponse,lpBufferA, dwcBuffer);
                wcout << _T("Response") << endl << szResponse << endl;
                delete [] szResponse;
            }
            delete [] lpBufferA;
        }
        else
        {
            DWORD dwErr = GetLastError();
            wcout << _T("Send Request Failed. Error:") << dwErr << endl;
        }
        InternetCloseHandle(hRequest);
    }
}

February, 2005: Mapping, Web Services, & C++

Listing 3

void BuildFindAddressSoapRequest(TCHAR* szAddress, TCHAR* pszSoap)
{
    _tcscpy(pszSoap, _T("<?xml version=\"1.0\" encoding=\"") );
    _tcscat(pszSoap, _T("utf-8\" ?>") );
    _tcscat(pszSoap, 
        _T("<soap:Envelope 
                  xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" 
                  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" 
                  xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">") );
    _tcscat(pszSoap, _T("<soap:Header>") );
    _tcscat(pszSoap, _T("</soap:Header>") );
    _tcscat(pszSoap, _T("<soap:Body>") );
    _tcscat(pszSoap, _T("<FindAddress 
                  xmlns=\"http://s.mappoint.net/mappoint-30/\">") );
        _tcscat(pszSoap, _T("<specification>") );
            _tcscat(pszSoap, _T("<DataSourceName>") );
            _tcscat(pszSoap, _T("MapPoint.AP") );
            _tcscat(pszSoap, _T("</DataSourceName>") );
            _tcscat(pszSoap, _T("<InputAddress>") );
                _tcscat(pszSoap, _T("<CountryRegion>") );
                _tcscat(pszSoap, _T("Australia") );
                _tcscat(pszSoap, _T("</CountryRegion>") );
                _tcscat(pszSoap, _T("<FormattedAddress>") );
                _tcscat(pszSoap, szAddress );
                _tcscat(pszSoap, _T("</FormattedAddress>") );
            _tcscat(pszSoap, _T("</InputAddress>") );
        _tcscat(pszSoap, _T("</specification>") );
    _tcscat(pszSoap, _T("</FindAddress>") );
    _tcscat(pszSoap, _T("</soap:Body>") );
    _tcscat(pszSoap, _T("</soap:Envelope>") );
}

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.