Developing Virtual Earth Applications with C++

image My good friend Mark Riches from VizRT / Curious recently integrated Microsoft Virtual Earth into the Viz Curious Maps software for television production. They use Virtual Earth maps in broadcast scenarios all over the world. “Viz Curious Maps is the broadcast industry’s de facto standard for the creation of branded maps and geographic animations. By extending Viz Curious Maps to a server edition, it can provide real-time, branded map imagery to several clients embedded within Vizrt’s control applications. Templates created within Viz Curious Maps can be dynamically accessed through the Viz Trio character generator and Viz Content Pilot, as well as Viz Weather, Viz Traffic, and Viz Artist. Users can seamlessly create branded, animated maps and add them to graphic templates or insert them directly into a 3D scene.

Well, as a result of his work to get Virtual Earth integrated into Viz Curious Maps Mark switched from hitting our tile server directly to using the Virtual Earth Web Service (the supported way of getting tiles). Since Viz Curious Maps is built with C++ and Virtual Earth Web Service uses a SOAP protocol Mark used gSOAP to open communications between the two. Mark was nice enough to document his code and provide it to me which I will now provide to you….

Accessing Virtual Earth Web Services using gSoap

Vizrt supplies graphics systems to TV broadcasters across the world. Amongst its products is Viz Curious Maps which is used by hundreds of broadcasters – ranging from the local to the international – to create animated map graphics for TV news. Curious Maps has long had an integration with Virtual Earth, fetching tiles of map imagery to be used in map animations. This is being upgraded in the latest release to access the latest BirdsEye imagery using the Virtual Earth Web Services API.

Viz Curious Maps is a large, complicated C++ based desktop application that allows users to develop their own house map styles that suit the look of their broadcast graphics. You will probably have seen a TV map that starts off showing the whole world, which turns and zooms to highlight a region of news; regions, towns, streets and imagery fading in to help tell the story. This was almost certainly generated from Viz Curious Maps. Using the C# integration of the API was not really an option for us – because we need to build for both PC and Mac – instead we decided to use gSoap.

gSoap works by generating C++ Proxy Classes for the various web services. This is a two stage process. wsdl2h generates a header file from wsdl definitions, and soapcpp2 converts this header file to the C++ classes. The reason for the division is that it allows you to generate your own classes by hand coding header files – but we don’t need to do that here.

This is how to do it:

Build the Proxy Classes

1. Download gSoap from https://www.cs.fsu.edu/~engelen/soap.html and unpack it.

2. Use the wsdl application to generate a header file. We want to include the TokenService and the ImageryService in our classes. There is a slight complication here that downloading the TokenService wsdl requires authentication. It is possible to build a version of wsdl that supports authentication – but the pre-built tool does not. So I downloaded the TokenService wsdl (from https://staging.common.virtualearth.net/find-30/common.asmx?wsdl) to a local file, then run wsdl on this and the ImageryService wsdl in one go. This is the command I used: wsdl2h -oVEWS.h common.wsdl https://staging.dev.virtualearth.net/webservices/v1/imageryservice/imageryservice.svc?wsdl

3. Use the soapcpp2 tool to create proxy classes using this command: soapcpp2 -i -C -I..\gsoap-2.7\gsoap\import VEWS.h
where: -i builds C++ classes, -C create client side classes only and -I denotes the path to the import folder from the gsoap installation

4. This will generate a .cpp, .h and .nsmap for each service, plus soapC.cpp, soapH.h and soapStub.h. Add all of these into your Visual Studio project.

5. Add stdsoap2.cpp and stdsoap2.h to your project

6. Add an include path for the gsoap folder

7. Add WITH_OPENSSL to the PreProcessor definitions of your project

Get ready for Authentication

The TokenService requires authentication – gSoap can handle this using a plugin which depends on an SSL implementation. It is simplest to use OpenSSL.

1. Get OpenSSL via https://www.openssl.org/related/binaries.html and install it.

2. Add an include path to the include folder.

3. Add libeay32.lib and ssleay32.lib to your project

4. Add the following files from gSoap/plugin to your Visual Studio project:

· httpda.cpp and .h (rename httpda.c to httpda.cpp so it uses the c++ compiler – this made a difference for me)

· md5evp.c and .h

· threads.c and .h

Write the application

#include one of the .nsmap files – it doesn’t matter which.

You need to create an instance of the CommonService proxy and fill in the token specification. Parameters are all specified by address – so that unused optional parameters are left with a NULL pointer. Although this is simple, it makes the code slightly verbose. You then call GetClientToken on the CommonService. This will fail with error 401, because it requires authentication. At this point you pass the username and password to the httpda plugin, and re-try the request. This should now succeed – returning a token.

Access to the ImageryService is the same. Fill in the appropriate parameters and call the method. The output class will contain the returned information – including the URI which you can then use to fetch imagery tiles.

Sample code below. There are options to the gSoap tools that allow you to get better naming. This example leaves everything at default.

Port to Mac

All the files generated from soapcpp2 are platform independent; as are the files from gsoap themselves, so just copy them across. OpenSSL is built-in to Mac OS X, so:

1. Add the same files to your project.

2. Add /usr/include to your Include path

3. Add libssl.dylib and libcrypto.dylib to the link

4. Add WITH_OPENSSL to the PreProcessor definitions

It couldn’t be much simpler.

Here’s a sample:

#include "Soap/CommonServiceSoap.nsmap"

#include "Soap/soapBasicHttpBinding_USCOREIImageryServiceProxy.h"

#include "Soap/soapCommonServiceSoapProxy.h"

#include "plugin/httpda.h"

#define IP_ADDRESS "192.168.0.1"

#define VEWS_USER "123456"

#define VEWS_PASSWORD "Password1234_"

void CVESoapTestDlg::TestVirtualEarth ()

{

// Initialise Soap

soap soap;

soap_init ( &soap );

soap_register_plugin(&soap, http_da);

// Construct a proxy for the CommonService

CommonServiceSoapProxy token_service ( soap );

ns1__TokenSpecification token_spec;

_ns1__GetClientToken gct_input;

// Set up input parameters

gct_input.specification = &token_spec;

std::string ip_address = IP_ADDRESS;

token_spec.ClientIPAddress = &ip_address;

token_spec.TokenValidityDurationMinutes = 60;

_ns1__GetClientTokenResponse gct_output;

// Try to get the client token

int soap_error = token_service.GetClientToken ( &gct_input, &gct_output ) ;

if ( soap_error == 401) // HTTP authentication is required

{

if ( strcmp(token_service.authrealm, "MapPoint" ) == 0 ) // check authentication realm

{

struct http_da_info info; // to store userid and passwd

http_da_save(&token_service, &info, "MapPoint", VEWS_USER, VEWS_PASSWORD);

// Try again to get client token

soap_error = token_service.GetClientToken ( &gct_input, &gct_output );

if ( soap_error == SOAP_OK )

{

// Construct proxy for the ImageryService

BasicHttpBinding_USCOREIImageryServiceProxy imagery_service ( soap );

_ns3__GetImageryMetadata gimd_input;

_ns3__GetImageryMetadataResponse gimd_output;

ns6__ImageryMetadataRequest request;

gimd_input.request = &request;

// Fill in the credentials

ns4__Credentials credentials;

request.Credentials = &credentials;

credentials.Token = gct_output.GetClientTokenResult;

// Specify the options

ns6__ImageryMetadataOptions options;

request.Options = &options;

ns4__Location location;

options.Location = &location;

double lat = 47.608;

double lng = -122.337;

location.Latitude = ⪫

location.Longitude = &lng;

int zoom_level = 19;

options.ZoomLevel = &zoom_level;

ns4__MapStyle style = ns4__MapStyle__Birdseye;

request.Style = &style;

// Try GetImageryMetaData request

soap_error = imagery_service.GetImageryMetadata ( &gimd_input, &gimd_output );

if ( soap_error == SOAP_OK )

AfxMessageBox ( _T ( "Success!" ) );

else

AfxMessageBox ( _T ( " Authentication\nGetImageryMetadata Failed!" ) );

}

else

AfxMessageBox ( _T ( "Authentication Failed" ) );

}

}

}

Thanks Mark!

CP