Skip to content

C++

The Cpp connector can be found on GitHub. It ships with the connector class OkapiConnector and an associated test class, which shows how to use every method available.

Getting started

Prerequisite

  • a C++ compiler: e.g. gcc or clang
  • CMake: at least verison 3.2
  • a text editor or IDE of your choice (we like to use VS Code or Code::Blocks)
  • a terminal
  • library dependencies:
  • boost_system, boost_thread, boost_chrono, crypto, ssl and
  • cpprest1

Compile the code

  1. Clone the OkapiJavaConnector into your development folder:
      git clone https://github.com/OKAPIOrbits/OkapiCppConnector.git
    
  2. To be able to run the tests, insert your username and password in OkapiConnectorTest.cpp
  3. When working via terminal create a build folder in OkapiCppConnector and change into the directory.
      mkdir build
      cd build
    
  4. Execute
      cmake ../
    
    which prepares the make files
  5. Run
      make install
    
    which compiles the code and creates the executable and library.
  6. You can now find the okapi-connector-test in the bin folder. Additionally, you can now find the dynamic library libokapi-connector.so or libokapi-connector.dylib in the lib folder. You can use this library and link it to be able to use the Okapi interface within your own application. To run the connector, use
      LD_LIBRARY_PATH=../lib ./okapi-connector-test
    

Using the Connector

Authentication

The authentication is achieved when calling the OkapiConnector init function:

OkapiConnector::OkapiResult initResult
    = connector.init("https://api.okapiorbits.com/","username", "password");
The access token will be is stored in the connector and will automatically be used when communicating with OKAPI:Aether. Please note that the token is valid for 24 hours and must be refreshed after expiration in order to keep communicating with the platform. It the authentication fails a 401 http code will be returned. An new call to the init() function will refresh the token.

Interacting with the API

Once the conncetor is initialized it can be used to interact with the platform. To send a pass prediction request first the Json object needs to created:

  // Define the ground location
  web::json::value groundLocationContent;
  groundLocationContent[U("altitude")] = web::json::value::number( 0.048);
  groundLocationContent[U("longitude")] = web::json::value::number(10.645);
  groundLocationContent[U("latitude")] = web::json::value::number(52.328);

  // Define the time window in which the passes should be generated
  web::json::value timeWindowContent;
  timeWindowContent[U("start")] = web::json::value::string("2018-08-06T18:19:44.256Z");
  timeWindowContent[U("end")] = web::json::value::string("2018-08-07T00:00:00.000Z");

  // Define how to propagate and the step size of the output
  web::json::value predictPassesSettingsSimple;
  predictPassesSettingsSimple[U("output_step_size")] = web::json::value::number(60);
  predictPassesSettingsSimple[U("geopotential_degree_order")] = web::json::value::number(2);

  // Defining the satellite properties and orbit
  web::json::value simpleState;
  simpleState[U("area")] = web::json::value::number(0.01);
  simpleState[U("mass")] = web::json::value::number(1.3);
  simpleState[U("x")] = web::json::value::number(-2915.65441951);
  simpleState[U("y")] = web::json::value::number(-3078.17058851);
  simpleState[U("z")] = web::json::value::number(5284.39698421);
  simpleState[U("x_dot")] = web::json::value::number(4.94176934);
  simpleState[U("y_dot")] = web::json::value::number(-5.83109248);
  simpleState[U("z_dot")] = web::json::value::number(-0.66365683);
  simpleState[U("epoch")] = web::json::value::string("2018-08-06T18:19:43.256Z");

  // Bringing together the four parts for one request body
  web::json::value passPredictionNumericalRequestBody;
  web::json::value groundLocation;
  groundLocation[U("type")] = web::json::value::string("ground_loc.json");
  groundLocation[U("content")] = groundLocationContent;
  passPredictionNumericalRequestBody[U("ground_location")] = groundLocation;
  web::json::value timeWindow;
  timeWindow[U("type")] = web::json::value::string("tw.json");
  timeWindow[U("content")] = timeWindowContent;
  passPredictionNumericalRequestBody[U("time_window")] = timeWindow;
  web::json::value orbit;
  orbit[U("type")] = web::json::value::string("state.json");
  orbit[U("content")] = simpleState;
  passPredictionNumericalRequestBody[U("orbit")] = orbit;
  web::json::value settings;
  settings[U("type")] = web::json::value::string("shared_prop_settings.json");
  settings[U("content")] = predictPassesSettingsSimple;
  passPredictionNumericalRequestBody[U("settings")] = settings;
It contains CCSDS OPM compliant orbit data, the ground station location, time window and settings for the propagation fidelity and output step size.

The computation is handed of to the platform by invoking the sendRequest() method:

  // Send request for NEPTUNE pass prediction
  OkapiConnector::OkapiResult response = connector.sendRequest("/predict-passes/neptune/requests", passPredictionNumericalRequestBody);
  // Check response
  if (response.error.code != 200 && response.error.code != 202)
  {
    cout << "Neptune request failed with status: " << response.error.status << endl;
    cout << response.error.message << endl;
  }
  string requestId = connector.getRequestId(response);
  cout << "Request ID: " << requestId << endl;
The end point /predict-passes/neptune/requests is used which utilizes the open source Neptune numerical propagator for the extrapolation of the state vector. Alternatives are the orekit propagator or the SGP4 analytic orbit theory for TLE, utilizing the endpoints /predict-passes/orekit/requests and /predict-passes/sgp4/requests, respectively. Please refer to the API for more information on all available endpoints.

Errors from the backend are passed in an OkapiConnector::OkapiError struct. It contains the status code and a message.

The requestId is retrieved using the line connector.getRequestId(responseNeptune);. After the processing is finished it is used to retrieve the result via the waitForProcessingAndGetValues() function:

  OkapiConnector::OkapiResult result = connector.waitForProcessingAndGetValues("/predict-passes/neptune/results/" + requestId + "/summary");
  if (result.error.code != 200 && result.error.code != 202)
  {
    cout << "Response failed with status: " << result.error.status << endl;
    cout << result.error.message << endl;
  }
  else
  {
    cout << result.body.serialize() << endl;
  }
The waitForProcessingAndGetValues() is a convenience method, which blocks as long as the full result is not available. For more control over the process of requesting a computation and retrieving results the methods sendRequest() and getValues() are available.

The Cpp connector offers special methods, which reduce the effort for the developer to interact with the platform. For example, the getSatellites() acan be used to retrieve all satellites associated with a given account:

  OkapiConnector::OkapiResult result = okapi.getSatellites();

  // Retrieve the newly assigned satellite id
  if (result.error.code == 200) {
    cout << result.body.serialize() << endl;
  } else {
    cout << result.error.status << ": " << result.error.message << endl;
  }

A satellite can be added just as easily:

  string newSatelliteId = "";

  web::json::value newSatellite;
  newSatellite[U("name")] = web::json::value::string("Sputnik");
  std::vector<web::json::value> noradIds;
  noradIds.push_back(web::json::value::string("1"));
  newSatellite[U("norad_ids")] = web::json::value::array(noradIds);
  // This is a random ID, which will be changed by the backend but currently it is still required
  newSatellite[U("satellite_id")] = web::json::value::string("550e8400-e29b-11d4-a716-446655440000");
  newSatellite[U("space_track_status")] = web::json::value::string("sharing_agreement_signed");

  OkapiConnector::OkapiResult result = okapi.addSatellite(newSatellite);

  if (result.error.code == 200) {
    newSatellite = result.body;
    cout << newSatellite.serialize() << endl;
    newSatelliteId = newSatellite.as_object().at(U("satellite_id")).as_string();
  } else {
    cout << result.error.status << ": " << result.error.message << endl;
  }
  return newSatelliteId;

Currently the satellite_id must also be set by the developer. This id will however be overriden in the platform. The retrieved satellite newSatellite will have an updated satellite_id!

Other convenient methods are:

  OkapiResult getConjunctions()
  OkapiResult getCdms(string conjunctionId)
  OkapiResult getManeuverEvals(string conjunctionId)
  OkapiResult updateSatellite(web::json::value requestBody, string satelliteId)
  OkapiResult deleteSatellite(string satelliteId)


  1. https://github.com/microsoft/cpprestsdk