Resource Page DescriptionThis project is an example for accessing the Virtual Earth SOAP API from C/C++ via the gSOAP-toolkit. It shows how to get the "clientToken" via SOAP-authentication and then it downloads an image from a specific location (for example: brandenburg gate, germany) and displays it.
Here is a sample image, what you get, if you use this example code:
Overview
Virtual Earth is providing a
SOAP interface to access the whole functionality of this site. Also there is a AJAX-JavaScript-API (Google-Maps only has a JavaScript-Interface), but this is hard to use from native C/C++ code. So the best way is to use the
SOAP-based interface.
This sample shows how to use the SOAP-based interface to download an image for a specific location.
Steps to do
Creating an access token for the Virtual Earth staging environment
Virtual Earth has to different environments to access the services: Staging an Production. Both environments have the same interface. The staging environment can be use to test your application. The productiion environment has better performance and is a paid service. The staging environment can be freely used for development.
To access the staging environment, you must create a free account. to get your
Virtual Earth Token.
The following steps must be done:
- Get a Windows Live ID at https://login.live.com. If you already have a Windows Live ID, you can use it instead of creating a new one.
- Sign up for a Virtual Earth Platform developer account. Go to https://mappoint-css.live.com/mwssignup and sign up for a free evaluation developer account.
- Once you have received account creation confirmation email, you can manage your Virtual Earth Platform developer account on the Virtual Earth Platform Customer Services site. You use your Windows Live ID to log in to the Virtual Earth Platform Customer Services site.
- On the Manage Account page of the Virtual Earth Platform Customer Services site you will find your Account ID (this is later used for authentication). The Manage Password page allows you to set the password for this account ID (this must be done at least one time). It might take up to 2 hours to propagate this password change.
- I recommend that you test your account via the Verify Credentials page. Here is a screenshot, on how to test your credentials:

(
Click here for a larger image)
The sample project
The
sample project (14 MB) contains all files for using the
Virtual Earth SOAP-API. It already had generaded files from the WSDL-SOAP-definitions. You just need to change the "AccountID" and "Password" in the source-file "VirtualEarthBitmapDownload.cpp". And then you can start it. Here are the sample-codelines you need to change:
#define VIRTUAL_EARTH_ACCOUNT_ID "123456"
#define VIRTUAL_EARTH_PASSWORD "PassW.RD"
After you compiled and started this project, you will see the above image.
Step-by-Step description
You can build this sample by your own. Here is the description how I build this sample.
Prerequisite
Before you start, you need to download the following things:
- Visual Studio 2008 C++ Express Edition if you do not already have Visual Studio 2008
- The latest release from the gSOAP toolkit (I used version gsoap_2.7.13.tar.gz). You need to unpack the tar.gz file into a subfolder gsoap.
- For authentication, we need the OpenSSL package. A precompiled version for windows, can be found here. You need to download the full version, which also contains the LIB/H-files (you must not use the "light"-version, because it does not contain the development-files). I used version 0.9.8.k. You can eithe ruse the x86 or x64 download (or both, if you want to support both platforms). Then you need to install these files. I have then copied the files into a subdirectory of the project (OpenSSL / OpenSSLx64). You only need the LIB and Include directories.
That's all what we need. If you downloaded my sample-project, all files are already present.
Generating the h/cpp-files from the WSDL-descriptions
There are several WebServices involved if you want to use the Virtual Earth SOAP-API. The fundamental web-service is "common.wsdl". And then there are four different web-services especially for Virtual Earth. Here is a list of all web-services, we need to consume in order to use virtual erath:
For importing the web-services, so they can be used from C/C++ , we need to use the gSOAP-tool
wsdl2h and
soapcpp2 from the gsoap\bin\win32 directory. Because the first wsdl-file needs authentication (with your LiveID), and wsdl2h is not build with authentication-support, you need to download the file to your local hard-drive and then generate the h-files. Here is a small batch-file which can be used to generate the proxy-files for C/C++:
rem Store the files in a subdirectory
mkdir GeneratedFiles
cd GeneratedFiles
rem Be sure, that we deleted all previous files
del *.* /q
rem Specify the path to the gSOAP toolkit
set wsdl2h=..\gsoap\bin\win32\wsdl2h
set soapcpp2=..\gsoap\bin\win32\soapcpp2
rem First login and download common.wsdl from https://staging.common.virtualearth.net/find-30/common.asmx?wsdl
rem Generate the h-files
rem WHATCHOUT for ☇ symbols! Do NOT break this line!
%wsdl2h% -s -o VirtualEarth.h☇
..\common.wsdl☇
http://staging.dev.virtualearth.net/webservices/v1/searchservice/searchservice.svc?wsdl☇
http://staging.dev.virtualearth.net/webservices/v1/geocodeservice/geocodeservice.svc?wsdl☇
http://staging.dev.virtualearth.net/webservices/v1/imageryservice/imageryservice.svc?wsdl☇
http://staging.dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc?wsdl
rem Generate the proxy-files
%soapcpp2% VirtualEarth.h
cd..
It should succeed without any error.
Creating your C/C++ project
Then you must create a Win32 project (for example, a simple Console-Application). You should disable precompiled headers, because the toolkit-files are not using precompiled headers.
Adding files to your project
Then you need to add the following files to your project:
For authentication-plugin:
- .\gsoap\plugin\httpda.cpp (you need to rename the file from httpda.c to httpda.cpp, because there seems to be a bug, if you compiled it with c-extension)
- .\gsoap\plugin\httpda.h
- .\gsoap\plugin\md5evp.c
- .\gsoap\plugin\md5evp.h
- .\gsoap\plugin\threads.c
- .\gsoap\plugin\threads.h
Web-Service-files:
- .\gsoap\stdsoap2.cpp
- .\GeneratedFiles\soapC.cpp
- .\GeneratedFiles\soapClient.cpp
Adjusting project settings
Then you need to change your project configuration:
- Add the compiler macros:
- WITH_OPENSSL (must be set to use the OpenSSL-plugin for authentication
- SOAP_DEBUG (only if you want to get some log-files)
- _CRT_SECURE_NO_DEPRECATE (only if you want to suppress some warnings for some CRT functions (like sprintf))
- Add the include-path to your project
- GeneratedFiles
- gsoap
- OpenSSL\include
- Add the lib-path for the OpenSSL-LIBs:
- OpenSSL\lib\VC\static (I prefer the static version, then we do not need any OpenSSL-DLLs)
Referening the web-service in you project
You need to include the basic gSOAP file "soapH.h" and the proxy-files for the web-services. Also you need to include
one of the nsmap-files (it does not matter which one) which contains the namespace variable.
In our example the these are the following files:
// SOAP-basics:
#include "soapH.h"
// Generated namespace table (just include any one of the nsmap-file)
#include "BasicHttpBinding_USCOREIGeocodeService.nsmap"
// Generated proxy-classes
#include "soapCommonServiceSoapProxy.h"
#include "soapBasicHttpBinding_USCOREIGeocodeServiceProxy.h"
#include "soapBasicHttpBinding_USCOREIImageryServiceProxy.h"
#include "soapBasicHttpBinding_USCOREIRouteServiceProxy.h"
#include "soapBasicHttpBinding_USCOREISearchServiceProxy.h"
Implementing the functions
GetClientToken
This function is the basic function to get access to the Virtual Earth API. You need to generate this client-token your every session you create. This token expiries after a specified time. To get this token, your SOAP-client must authenticate on the server. For the authentication, we need to use the OpenSSL-plugin.
You need to add the following code to your project:
// OpenSSL-Support for authentication:
#include "plugin/httpda.h"
#ifdef _DEBUG
#ifdef _DLL
#pragma comment(lib, "libeay32MDd.lib")
#pragma comment(lib, "ssleay32MDd.lib")
#else
#pragma comment(lib, "libeay32MTd.lib")
#pragma comment(lib, "ssleay32MTd.lib")
#endif
#else
#ifdef _DLL
#pragma comment(lib, "libeay32MD.lib")
#pragma comment(lib, "ssleay32MD.lib")
#else
#pragma comment(lib, "libeay32MT.lib")
#pragma comment(lib, "ssleay32MT.lib")
#endif
#endif
// The GetToken function needs to be called once before any web service request is made. Once
// the token is retrieved, it can be cached for use in subsequent web service requests.
// (as long as the time period is not expired)
std::string GetToken(LPCSTR szAccountID, LPCSTR szPassword, LPCSTR clientIPAddress, int validityDurationMinutes)
{
printf("GetClientToken...");
std::string token;
// Use the CommonService to retrive a token
CommonServiceSoap commonService;
// register OpenSSL-Plugin for authentication
soap_register_plugin(commonService.soap, http_da);
// Set the token specification properties
_ns1__GetClientToken input;
ns1__TokenSpecification spec;
input.specification = &spec;
input.specification->ClientIPAddress = const_cast<char*>(clientIPAddress);
input.specification->TokenValidityDurationMinutes = validityDurationMinutes;
_ns1__GetClientTokenResponse response;
// first try without authentication, so that the required fields are filled, if we need authentication ;)
int soap_error = commonService.__ns1__GetClientToken(&input, &response);
if (soap_error == 401) // HTTP authentication is required
{
if (strcmp(commonService.soap->authrealm, "MapPoint") == 0) // check authentication realm
{
struct http_da_info info; // store userid and passwd
http_da_save(commonService.soap, &info, "MapPoint", szAccountID, szPassword);
// Try again with authentication
soap_error = commonService.__ns1__GetClientToken(&input, &response);
if (soap_error == SOAP_OK)
{
token = response.GetClientTokenResult;
}
}
}
else
{
if (soap_error == SOAP_OK)
{
token = response.GetClientTokenResult;
}
}
if (token.length() <= 0)
printf("\n### ERR: Could not retrive tolken: %d\n", soap_error);
else
printf(" ok\n");
return token;
}
GetMapUri
In my example I ust call the function
GetMapUri from the
ImageryService. We need to specify several parameters like token, latitude, longitude, zoomLevel, ...
Here is the code to retrive the URL to an image:
std::string GetMapUri(LPCSTR token, double latitude, double longitude, int zoomLevel, int imageHeight, int imageWidth)
{
printf("GetMapUri...");
// Use the ImageryService to retrive an image for a location
BasicHttpBinding_USCOREIImageryService imageryService;
_ns12__GetMapUri getMapUriInput;
_ns12__GetMapUriResponse mapUriResponse;
// Fill in the credentials
ns4__Credentials credentials;
credentials.Token = const_cast<char*>(token);
ns13__MapUriRequest request;
getMapUriInput.request = &request;
request.Credentials = &credentials;
ns4__Location loc;
request.Center = &loc;
loc.Latitude = &latitude;
loc.Longitude = &longitude;
ns13__MapUriOptions opt;
request.Options = &opt;
ns4__MapStyle ms = ns4__MapStyle__AerialWithLabels; //ns4__MapStyle__Aerial; //ns4__MapStyle__Road; //ns4__MapStyle__AerialWithLabels;
opt.Style = &ms;
opt.ZoomLevel = &zoomLevel;
ns4__SizeOfint si;
opt.ImageSize = &si;
si.Height = &imageHeight;
si.Width = &imageWidth;
int soap_error = imageryService.__ns13__GetMapUri(&getMapUriInput, &mapUriResponse);
if (soap_error != SOAP_OK)
{
if (mapUriResponse.soap->fault != 0)
printf("\n### ERR: %s: %s\n", mapUriResponse.soap->fault->faultcode, mapUriResponse.soap->fault->faultstring);
else
printf("\n### ERR: Calling SOAP function\n");
return "";
}
printf(" ok\n");
std::string mapUri = mapUriResponse.GetMapUriResult->Uri;
// replace the token-string with my token...
std::string::size_type pos = mapUri.find("{token}");
if (pos != std::string::npos)
mapUri.replace(pos, 7, token);
//printf("MAP-Uri: %s\n", mapUri.c_str());
return mapUri;
}
Now we have the URL to display the image.
DownloadFile
To be able to display the image, we need to download the image to a file. For this I used the WinINet-API:
// For WinINet-functions
#include <windows.h>
#include "Wininet.h"
#pragma comment(lib, "Wininet.lib")
int DownloadFile(LPCTSTR szUri, LPCTSTR szFileName)
{
printf("DownloadFile...");
HINTERNET hInt = InternetOpen(_T("MyVirtualEarthDownload/1.0"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hInt == NULL)
{
printf("\n### ERR: Error opening internet connection\n");
return 1;
}
HINTERNET hIntUrl = InternetOpenUrl(hInt, szUri, NULL, 0, 0, NULL);
if (hIntUrl == NULL)
{
InternetCloseHandle(hInt);
printf("\n### ERR: Error opening internet URL\n");
return 2;
}
DWORD dwBytesRead = 0;
BYTE buf[1024];
BOOL bRet = FALSE;
HANDLE hFile = INVALID_HANDLE_VALUE;
while((bRet = InternetReadFile(hIntUrl, buf, sizeof(buf), &dwBytesRead)) == TRUE)
{
if (dwBytesRead == 0)
break; // succeeded...
printf(".");
if (hFile == INVALID_HANDLE_VALUE)
hFile = CreateFile(szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("\n### ERR: Could not create file\n");
bRet = FALSE;
break;
}
DWORD dwBytesWritten;
bRet = WriteFile(hFile, buf, dwBytesRead, &dwBytesWritten, NULL);
if (bRet == FALSE)
{
printf("\n### ERR: Error during file-writing\n");
break;
}
}
InternetCloseHandle(hIntUrl);
InternetCloseHandle(hInt);
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
if (bRet == FALSE)
{
printf("\n### Error during file-download\n");
return -1;
}
printf(" ok\n");
return 0;
}
Now you have all functions implemented. You can now write the main-function:
main function
All functions are now implemented, we need to use it...
Here is the code to download and display an image:
// The IP-Adress must only be set, if YOU want to differentate between different clients...
#define IP_ADDRESS "127.0.0.1"
// Get your own Virtual-Earth-Test-Account:
// Virtual-Earth Customer Service:
// https://mappoint-css.live.com/CSCV3/
//
#define VIRTUAL_EARTH_ACCOUNT_ID "123456"
#define VIRTUAL_EARTH_PASSWORD "passW.RD"
// Helper function to convert std::string to std::wstring
std::wstring s2ws(const std::string &str)
{
std::wstring r;
LPWSTR szTextW = new WCHAR[str.size()+1];
MultiByteToWideChar(0, 0, str.c_str(), -1, szTextW, (int)str.size()+1);
r = szTextW;
delete [] szTextW;
return r;
}
int _tmain(int argc, _TCHAR* argv[])
{
// Get a Virtual Earth token before making a request
std::string token = GetToken(VIRTUAL_EARTH_ACCOUNT_ID, VIRTUAL_EARTH_PASSWORD, IP_ADDRESS, 60);
if (token.length() <= 0)
{
printf("\n### Could not retrive valid token...");
return 1;
}
// Brandenburg Gate, Berlin, Germany
std::string mapUri = GetMapUri(token.c_str(), 52.51628056168556, 13.377829939126963, 16, 600, 800);
std::wstring ws = s2ws(mapUri);
LPCTSTR szFileName = _T("map.jpg");
if (DownloadFile(ws.c_str(), szFileName) == 0)
{
// Display the downloaded image:
printf("Displaying image...");
ShellExecute(NULL, _T("open"), szFileName, NULL, NULL, SW_SHOW);
printf(" ok\n");
}
return 0;
}
Now you should be able to use all Virtual Earth SOAP-API functions.
Happy coding!