RadarFX API Documentation
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
3 - The Client API

Table of Contents

This chapter explains how to integrate the RadarFX client API into your own application.

3.1 Initializing Connections

Each client application must create a connection to the RadarFX Server. This is done using the DtRadarFxConnector object. The client must create a Connector object. This is done by instantiating an instance of the class.

myConnector = new DtRadarFxConnector();

Once the connector is created, the client must register callbacks for when a connection with a server succeeds and is disconnected. These callbacks are encapsulated in a DtDelegate object.

&Client::handleConnection);
myConnector->addConnectedCallback(myConnectionDelegate);
myDisconnectionDelegate = new makRadarFx::DtRadarFxConnector::DtConnectionDelegate(this, &Client::handleDisconnection);
myConnector->addDisconnectedCallback(myDisconnectionDelegate);

3.2 Connecting to a Server

Once a connector is created and initialized, it is ready to connect to the server. Connecting to the server is done using the connect() function.

myConnector->connect(myServerAddress, myPort);

The server address is the IP address or hostname of the server and the port to connect on. This function will not block. The DtRadarFxConnector's connection callbacks will be run when a connection is established with the server. The DtRadarFxConnector manages its own internal sockets using a network thread.

3.3 Registering Message Delegates

When the Connector receives a message from the server, the server uses its message handler to dispatch the message to any registered delegates. Delegates are registered with the message handler using the message type and a delegate wrapping the client function to call.

mySarMessageDelegate = new makRadarFx::DtMessageDelegate(this, &Client::handleSarRadarFxMessage);
myConnector->messageHandler().addMessageCallback(DtSAR_RESPONSE_TYPE, mySarMessageDelegate);

The default message types are defined in radarFxShared/rfxMessageTypes.h:

// these are the IDs for the message types
const unsigned int DtHEARTBEAT_TYPE = 1;
const unsigned int DtSAR_REQUEST_TYPE = 100;
const unsigned int DtSAR_RESPONSE_TYPE = 101;
const unsigned int DtELEVATION_REQUEST_TYPE = 200;
const unsigned int DtELEVATION_RESPONSE_TYPE = 201;
const unsigned int DtOCCLUSION_REQUEST_TYPE = 300;
const unsigned int DtOCCLUSION_RESPONSE_TYPE = 301;
const unsigned int DtENTITY_LIST_REQUEST_TYPE = 400;
const unsigned int DtENTITY_LIST_RESPONSE_TYPE = 401;
const unsigned int DtSENSOR_LIST_REQUEST_TYPE = 500;
const unsigned int DtSENSOR_LIST_RESPONSE_TYPE = 501;
const unsigned int DtISAR_REQUEST_TYPE = 600;
const unsigned int DtISAR_RESPONSE_TYPE = 601;
const unsigned int DtENTITY_LOCATION_REQUEST_TYPE = 700;
const unsigned int DtENTITY_LOCATION_RESPONSE_TYPE = 701;

You can extend the server and client by adding your own message types.

3.4 Sending Messages

Messages are sent using the Connector's sendMessage() function.

myConnector->sendMessage(msg);

Since the Connector's socket is being processed in a separate thread, there is no guarantee when the message gets sent. To accommodate this, the Connector makes a copy of the message internally and the clients message can go out of scope as soon as the sendMessage() function finishes. After a message is sent, if the requestor name and request ID fields were not previously filled out, which is the typical use case, the Connector fills these fields out in the message.

3.5 Processing Messages

The client must be ticked periodically to dispatch any connection callbacks or received messages. This is done by calling the tick() function.

myConnector->tick();

In addition to calling the tick() function, the client can check the status of the connection to the server using the isOK() method.

myConnector->isOk()

3.6 Requesting a Sensor List

One of the first things that a client application needs to know is the sensors that the server has configured on it. This list of sensors is requested from the server using a DtSensorListRequest. There are no parameters to this message. The client just needs to create one and send it using the Connector's sendMessage() function.

DtSensorListRequest msg;
myConnector->sendMessage(msg);

The response from the server contains a list of the names of the sensors that are available on the server.

Note
The message is only valid in the delegate while it is being called. The passed in message will go out of scope. It is advisable to make a copy of the data contained in the message if it is needed outside the function.

3.7 Request Entity List

The server connects to the simulation exercise using DIS or HLA and can send information to the client about the entities that exist in the simulation. This mechanism alleviates the need for the client to join the simulation, but does not prevent it from doing so.

When the client needs to know the platform that is hosting the sensor it is making requests from, it can request the list of known entities from the server. This is done by sending the server a DtEntityListRequest message without any parameters.

DtEntityListRequest msg;
myConnector->sendMessage(msg);

The server will send the client a response that contains a list of entity descriptions. The entity description is defined as:

{.cpp}
struct RFX_DLL_SHARED DtEntityDescription
{
std::string myId;
std::string myMarkingText;
std::string myEntityType;
}

The ID is the simulation-specific global ID of the entity. The entity type is the 7 digit DIS Enumeration describing the type of entity. The marking text is a human readable string associated with simulation entities, typically this is the common name, call sign, or the sortie name of the entity. While very useful for identifying entities, be aware that the marking text is not guaranteed to be unique within a given simulation exercise.

Note
Please see the SISO Enumerations for Simulation Interoperability document for DIS Enumeration definitions (SISO-REF-010-2011.1 is the latest version).

It is important to request periodic updates to the entity list, because entities can join the simulation late or leave early.

3.8 Hosted, Absolute, and Current Observer Requests

SAR, elevation, and occlusion requests can be a hosted request, an absolute request, or a current observer request. Absolute requests explicitly specify the location of the sensor that is making the request in world coordinates (Latitude, Longitude and Altitude). Hosted requests specify the ID of a simulation entity that the sensor is attached to and a relative offset and relative rotation. A current observer request uses the default observer in the server's current location and orientation as the position of the sensor. Whether a request is hosted, absolute, or using the current observer is implied by which data fields are set on the message.

3.8.1 Hosted Requests

If the SAR, elevation, or occlusion request specifies a simulation entity ID using the setOwnshipEntityId() function, then the server treats the message as a hosted message and uses the simulation entity's location to determine where the sensor is in the simulation for acquiring the target.

3.8.2 Absolute Requests

If the client uses the setOwnshipLocation() function to define where the sensor platform is located, the message is treated as an absolute request.

3.8.3 Current Observer Requests

In a current observer request, the client specifically calls the function useCurrentObserver().

3.8.4 Message Field Confliction

In any messages in which more than one method is called, for example useCurrentObserver() and setOwnshipLocation(), the last function called determines how the message is interpreted by the server. Warning messages are printed to the console if conflicting fields are set within the same message.

Note
There is an additional function for adjusting the orientation of DtSarRequest requests. The function setRadarOffsetOrientation will specify an offset orientation for the sensor but only if the request is not specified to use the observer location or orientation. This is used by the Strip SAR example for setting up the sensor to point in a specific direction in relation to the entity.

3.9 Requesting a SAR Image

The client can request a SAR image from the server by sending a DtSarRequest message. The message must specify:

A client can use one of the methods described in Hosted, Absolute, and Current Observer Requests to determine the sensor location and orientation in the simulation.

The client must specify which sensor model on the server to use to capture the image. This is done by specifying the name of the sensor model in the request message. The names of the available sensor models are available on the server and can be requested by clients by sending a DtSensorListRequest message (for details, please see Requesting a Sensor List).

The SAR request must specify the crossrange and downrange size of the area of interest. These values specify the size in meters of the area of interest centered on the target location.

3.9.1 Configuring How the Image is Returned

The client must specify how the server should return the image. This includes the size of the return image in pixels, the format, and the data type. The return data type is only used with data formats that allow for more than one data type, for example PNG and JPG cannot define the return data type.

The client can request that the server rotate the image so that north is up or keep the image with the sensor view up. The default is to not rotate the image and the top of the image is the direction the sensor is pointing. The client can also request that the server save the image to a file in addition to returning the image data to the client. By default the server does not save any images.

3.10 Requesting an ISAR Image

The client can request an ISAR image(s) from the server by sending a DtIsarRequest message. The message must specify:

A client can use one of the methods described in Hosted, Absolute, and Current Observer Requests to determine the sensor location and orientation in the simulation.

The client must specify which sensor model on the server to use to capture the image. This is done by specifying the name of the sensor model in the request message. The names of the available sensor models are available on the server and can be requested by clients by sending a DtSensorListRequest message (for details, please see Requesting a Sensor List).

The ISAR request must specify the view magnification to use for the images. This values specifies the amount of zoom that should be applied to the channel. VR-Vantage has a limitation that the paging is adjusted based on the zoom magnification instead of the field of view (FOV) of the camera. This is why ISAR uses a view magnification instead of absolute field of view values. It is recommended that you configure your channel to have a FOV around 45 degrees and then use the view magnification to get the intended FOV for the ISAR sensor.

3.10.1 Configuring How the Image is Returned

The client must specify how the server should return the image. This includes the size of the return image in pixels, the format, the data type and the number of images. The return data type is only used with data formats that allow for more than one data type, for example PNG and JPG cannot define the return data type.

The client can request that the server rotate the image so that north is up or keep the image with the sensor view up. The default is to not rotate the image and the top of the image is the direction the sensor is pointing. The client can also request that the server save the image to a file in addition to returning the image data to the client. By default the server does not save any images.

ISAR requests will return multiple images if the client specifies a number of images greater than 1. The server will generate multiple responses for the single request and each response will contain the index so the client can order the responses based on the order they were captured.

3.11 Strip SAR Example

RadarFX Simple Client includes an example of how to request multiple SAR images and combine them into a single image. are three important peices of the example: ) Strip SAR Request Setup ) Strip SAR Request Timer ) Strip SAR Response Handler

3.11.1 Strip SAR Request Setup

The strip SAR requests use the same requests as the regular SAR requests, DtSarRequest.

Here is the code from the example for creating a strip SAR request:

void DtRadarFxSimpleClientWindow::initializeStripSarRequestMessage()
{
//setup Host location
std::string id = myStripHostEntityList->itemData(
myStripHostEntityList->currentIndex()).toString().toStdString();
myStripSarRequest.setOwnshipEntityId(id);
//setup target location
myStripSarRequest.useObserverForTarget();
myStripSarRequest.setCrossrange(myStripWidth->value());
myStripSarRequest.setDownrange(myStripDownrange->value());
myStripSarRequest.setPower(myStripPower->value());
myStripSarRequest.setRadarSystemName("SAR");
DtImageSize imageSize;
imageSize.x = myImageSizeX->value() / myStripSarImageRequests;
imageSize.y = myImageSizeY->value();
myStripSarRequest.setImageSize(imageSize);
myStripSarRequest.setImageRotation(TRACK_UP);
switch (myImageFormat->currentIndex())
{
case 0:
myStripSarRequest.setImageFormat(PNG);
break;
case 1:
myStripSarRequest.setImageFormat(JPG);
break;
case 2:
myStripSarRequest.setImageFormat(BMP);
break;
case 3:
myStripSarRequest.setImageFormat(RAW);
break;
case 4:
myStripSarRequest.setImageFormat(NITF);
break;
}
switch (myImageDataType->currentIndex())
{
case 0:
myStripSarRequest.setImageDataType(makRadarFx::BYTE);
break;
case 1:
myStripSarRequest.setImageDataType(makRadarFx::SHORT);
break;
case 2:
myStripSarRequest.setImageDataType(makRadarFx::FLOAT);
break;
}
myStripSarRequest.setImageReturnMethod(STREAM);
if (mySaveOnServer->checkState() == Qt::Checked)
{
myStripSarRequest.setImageReturnMethod(STREAM | FILESYSTEM);
myStripSarRequest.setImageFilename(myFilename->text().toStdString());
myStripSarRequest.setImageSavePath(myServerPath->text().toStdString());
}
else
{
myStripSarRequest.setImageReturnMethod(STREAM);
}
double radarPitch = DtDeg2Rad(myStripOrientation->value()) - M_PI;
myStripSarRequest.setRadarOffsetOrientation(DtTaitBryan(M_PI_2, radarPitch, 0));
if (myStripOrientation->value() > 90)
{
myStripLeftToRight = false;
}
else
{
myStripLeftToRight = true;
}
myStripSarRequest.setRestoreState(false);
}

Notice how it is close to the same as the SAR request. The same request type is used but this time it is defined as a member variable:

class DtRadarFxSimpleClientWindow : public QMainWindow, public Ui::DtSimpleClient
{
...
protected:
makRadarFx::DtSarRequest myStripSarRequest;
...
};

This is so that the request can be reused for all the strips being requested. It will be used again in the strip SAR timer section.

The other important difference between this and the SAR request setup code is how the observer location and orientation are setup.

//setup Host location
std::string id = myStripHostEntityList->itemData(
myStripHostEntityList->currentIndex()).toString().toStdString();
myStripSarRequest.setOwnshipEntityId(id);
//setup target location
myStripSarRequest.useObserverForTarget();

The location is always an entity location and the orientation matches the current orientation of the entity but with a slight adjustment:

double radarPitch = DtDeg2Rad(myStripOrientation->value()) - M_PI;
myStripSarRequest.setRadarOffsetOrientation(DtTaitBryan(M_PI_2, radarPitch, 0));

The setRadarOffsetOrientation call sets up the observer to point to the left side of the entity if the orientation specified in the GUI is set to 0, down for an orientation of 90, right for an orienation of 180, and somewhere in between those for other values. For example if the orientation is set to 45 then the SAR request will be at an angle of 45 degrees down from the left side of the entity.

The last line of the function is also important for a multiple request SAR setup like strip SAR:

myStripSarRequest.setRestoreState(false);

This code sets the request so that it will not restore the observer, window, or channel state back to what it was before the request was processed. By not restoring the state the SAR requests are processed faster and the Server is much more likely to keep up with the multiple requests.

3.11.2 Strip SAR Request Timer

In addition to the SAR request, another important part of the Strip SAR example is the SAR Request Timer. This timer is used to send a SAR request at a rate that matches the width of the SAR strip divided by the speed of the entity.

Here is the code that sets up the timer:

{
...
myStripSarTimer = new QTimer(this);
myStripSarTimer->setSingleShot(false);
connect(myStripSarTimer, SIGNAL(timeout()), this, SLOT(onStripSarTick()));
...
}

The timer is off by default and will use the onStripSarTick function as the handler for the timeout of the timer.

The handler for the "Start Strip SAR Capture" button is used to intialize and send the first request and to start up the timer for sending the rest of the requests:

void DtRadarFxSimpleClientWindow::onStripSarStart()
{
//reset the graphics view so the next image coming is at the default size
myGraphicsView->scene()->clear();
myGraphicsView->resetMatrix();
myGraphicsView->resetTransform();
setImageCount(0);
myStripSarImageRequests = myStripCount->value();
myCurrentStripSarImage = 0;
myCurrentStripSarImageRequested = 1;
myStripSarRunning = true;
double entitySpeed = myStripEntitySpeed->value();
double stripWidth = myStripWidth->value();
int interval = (int)((stripWidth / entitySpeed) * 1000.0);
myStripSarTimer->setInterval(interval);
if (myCurrentStripSarImageRequested != myStripSarImageRequests)
{
myStripSarTimer->start();
}
initializeStripSarRequestMessage();
sendStripSarRequest();
myStripSarButton->setEnabled(false);
myImageInfo->setText("Waiting for SAR response");
myStatusBar->showMessage("Requested STRIP SAR images", 5000);
}

Notice how the interval is set based on the strip width and the speed of the entity.

The code for sending the strip SAR request is very simple because the SAR request stays the same between requests for the same strip SAR image:

void DtRadarFxSimpleClientWindow::sendStripSarRequest()
{
myConnector->sendMessage(myStripSarRequest);
}

Handing the timer tick is then just sending another request and checking to see if it is the last request:

void DtRadarFxSimpleClientWindow::onStripSarTick()
{
sendStripSarRequest();
myCurrentStripSarImageRequested++;
if (myCurrentStripSarImageRequested == myStripSarImageRequests)
{
myStripSarTimer->stop();
}
}

This method of sending requests based on the speed of the entity is not the most accurate way to implement Strip SAR. An alternative would be to use the real speed of the entity by listening to the simulation in the client and adjusting the time of the request based on the real speed. Another alternative would be to listen to the simulation and get the location of the entity and once the entity reaches the strip width distance from last request then trigger another request. Finally, the most accurate method would be to use absolute locations for both the sensor location and the orientation and send requests based on the actual sensor location and the location on the ground that is the exact distance of the strip width away from the previous request.

3.11.3 Strip SAR Response Handler

Lastly the SAR response code needs to handle the Strip SAR Request and combine the images into a single larger image. Since the same request is used as the regular SAR request, the response handler is reused for the strip SAR response:

void DtRadarFxSimpleClientWindow::handleSarResponse(makRadarFx::DtBaseMessage* msg)
{
...
myPixmap = new QGraphicsPixmapItem();
myPixmap->setPixmap(pixmap);
if (myStripLeftToRight)
{
myPixmap->setOffset(response->imageSize().x * (myCurrentStripSarImage), 0);
}
else
{
myPixmap->setOffset(response->imageSize().x * (myStripSarImageRequests - myCurrentStripSarImage - 1), 0);
}
myGraphicsView->scene()->addItem(myPixmap);
if (myStripLeftToRight)
{
myGraphicsView->setSceneRect(0, 0, response->imageSize().x * (myCurrentStripSarImage + 1), response->imageSize().y);
}
else
{
myGraphicsView->setSceneRect(0, 0, response->imageSize().x * myStripSarImageRequests, response->imageSize().y);
}
if (myStripSarRunning)
{
myCurrentStripSarImage++;
if (myCurrentStripSarImage == myStripSarImageRequests)
{
myStripSarRunning = false;
myStripSarButton->setEnabled(true);
myStatusBar->showMessage("STRIP SAR image complete", 5000);
myStripSarImageRequests = 1;
myCurrentStripSarImage = 0;
myCurrentStripSarImageRequested = 0;
myGetImageButton->setEnabled(myConnector->isOk());
}
else
{
std::stringstream stripSarMessage;
stripSarMessage << "Received STRIP SAR image " << myCurrentStripSarImage << "/" << myStripSarImageRequests;
myStatusBar->showMessage(QString::fromStdString(stripSarMessage.str()), 5000);
}
}
...
}

The code above sets the offset for the image based on which strip SAR image is currently being handled. It then adjusts the size of the graphics view based on the images and lastly checks to see if it is the last image so it can reenable the Strip SAR Button.

3.11.4 Entity Location/Orientation/Velocity Request

The server connects to the simulation exercise using DIS or HLA and can send information to the client about the location, orientation, and velocity of entities that exist in the simulation. This mechanism alleviates the need for the client to join the simulation, but does not prevent it from doing so.

In the case of Strip SAR it is useful to get the entity speed from the server so that it can request SAR images at the correct rate based on the speed of the ownship entity and the width of the Strip.

DtEntityLocationRequest msg;
std::string id = myStripHostEntityList->itemData(
myStripHostEntityList->currentIndex()).toString().toStdString();
msg.setEntityId(id);
myConnector->sendMessage(msg);

The entity id is the same entity id from the entity list response.

An entity location response contains the location (in the geocentric coordinate system), the topographic orientation, and the velocity. The speed of the entity can easily be calculated from the velocity like this:

double speed = sqrt(response->entityVelocity().magnitudeSquared());

3.12 Requesting Elevation Information

The client can request the elevation at a given target point, which simulates using a given sensor to determine the elevation. To request the elevation of a target point the client must send an elevation request message and have an elevation response message delegate registered with the connector's message handler. To send the client message, the client must create an elevation message, fill out the appropriate fields and send it using the connector's sendMessage() function.

DtElevationRequest msg;
msg.setOwnshipEntityId("1:3001:42");
msg.setTargetLocation(DtVector(42.39459, -71.145948, 0));
myConnector->sendMessage(msg);
Note
The attitude field of the target location is ignored by the server. Typically this value is set to 0, as in the example.

After the request is sent, the message will contain valid requestor names and request ID fields that were applied by the Connector. The client can keep track of these values if it needs to differentiate between different elevation requests. Typical use cases will not need to maintain this data.

myConnector->sendMessage(msg);
int elevationRequestId=msg.requestId();

The server processes the request and send a response. When the response arrives, it is sent to the Connector's message handler for dispatching to any client delegates registered for the message type. Within the function that will handle the response message from the server, the client must cast the message to the correct type, check for errors on the cast, then process the message.

void DtRadarFxSimpleClientWindow::handleElevationResponse(makRadarFx::DtBaseMessage* msg)
{
DtElevationResponse* response=dynamic_cast<DtElevationResponse*>(msg);
if(response != NULL)
{
float elevation=response->elevation();
//Do something with the elevation
}
else
{
std::cout << "Elevation response handler received non-elevation message. This should not happen" << std::endl;
}
}

3.12.1 Elevation Response Errors

The target location can be obscured by terrain or other obstacles from the requesting sensor platform. This can occur if the sensor platform is behind a hill or too far away. In this case, the response is -1 and the error field on the response is set to ELEVATION_ERROR_OCCLUDED.

3.13 Requesting Occlusion Information

The client can ask the server whether or not a target location is occluded from a sensor platform. To run an occlusion test the client must send an occlusion request message and receive an occlusion response from the server through the message delegate registered with the connector's message handler. To send the client message, the client must create an occlusion request message, fill out the appropriate fields and send it using the connector's sendMessage() function.

DtOcclusionRequest msg;
msg.setOwnshipEntityId("1:3001:42");
msg.setTargetLocation(DtVector(42.39459, -71.145948, 0));
myConnector->sendMessage(msg);

After the request is sent, the message will contain valid requestor names and request ID fields that were applied by the Connector. The client can keep track of these values if it needs to differentiate between different elevation requests. Typical use cases will not need to maintain this data.

myConnector->sendMessage(msg);
int elevationRequestId=msg.requestId();

The server processes the request and sends a response. When the response arrives, it is sent to the Connector's message handler for dispatching to any client delegates registered for the message type. In the function that handles the response message from the server, the client must cast the message to the correct type, check for errors on the cast, then process the message.

void DtRadarFxSimpleClientWindow::handleOcclusionResponse(makRadarFx::DtBaseMessage* msg)
{
DtOcclusionResponse* response=dynamic_cast<DtOcclusionResponse*>(msg);
if(response != NULL)
{
bool occluded=response->occluded();
//Do something with the response
}
else
{
std::cout << "Occlusion response handler received non-occlusion message. This should not happen" << std::endl;
}
}

3.14 Shutting Down

To disconnect from the server, call the disconnect method on the Connector. Calling disconnect shuts down the internal network thread and deletes any messages that have not been processed yet.

myConnector->disconnect();

[<< Design Concepts] [Home] [Top of Page] [RadarFX Developer Examples >>]


Document ID: 1.0
Copyright © 2017 VT MAK Inc. All Rights Reserved (www.mak.com)