Feed Sensor Data¶
Feed sensor data, such as position, acceleration, compass or temperature.
Setup¶
Prerequisites¶
Setting your API key token¶
To set your API key token, define it like this:
#define API_TOKEN "YOUR_API_KEY_TOKEN"
replacing YOUR_API_KEY_TOKEN
with your actual API key token text, within the quotes.
This can be done in the main()
function, before the following line:
#if defined(API_TOKEN)
or outside the main()
function, further up, or in a header file.
Although it is also possible to define your API key token as the text value of the GEM_TOKEN
environment variable, this is not recommended, as it is not secure.
Use case¶
Feed data obtained from external sensors to the SDK.
How to use the sample¶
This example does not have a map viewport as usual; instead, the sensor data is printed in the debug console.
How it works¶
Create an instance of
Environment
and set your API key token:
1int main( int argc, char** argv )
2{
3 std::string projectApiToken = "";
4#define API_TOKEN "YOUR_API_KEY_TOKEN"
5#if defined(API_TOKEN)
6 projectApiToken = std::string( API_TOKEN );
7#endif
8 // Sdk objects can be created & used below this line
9 Environment::SdkSession session(projectApiToken, { argc > 1 ? argv[1] : "" });
The SDK is initialized with your API key token string and the log file path, where to write the application logs. Note that the log file path is not initialized by default, the empty string above, after the API key token, which means that no logs are written. The log file path is initialized with the first command line argument, if any. Create a
gem::MapView
interactive map object, using an OpenGL context for 3D graphics, the SDK environment created above, and a touch event listener for interactive user touch and mouse input, such as pan and zoom.
1GEM_LOGE("FEED SENSOR DATA EXAMPLE ### ### ### ###");
2
3std::thread thread1(pushDataThread, "pushDataThread");
4while( thread1.joinable() )
5{
6 WAIT_TIME_OUT( 1000 );
7}
8return 0;
There are 2 threads - one thread
pushDataThread
feeds sensor data into the SDK (which in a real situation would be obtained from external sensors). ThepushDataThread()
first creates agem::sense::DataTypeList
containing the types of data to be pushed. Position data should always be included, because there can be no navigation without position. All other types are optional. In this example, 3 data types are pushed - Position, Acceleration and Gyroscope. In a real life situation, the sensor data is read from external sensors; in this example, each of the 3 sensor data types is in a separate header file containing sample data. ADataReceiver
listener is added for each type of data the SDK is interested in receiving. The data is pushed, waiting between records, just like real data, and note that the different sensors do not have to be pushed at the same rate.
1void pushDataThread(std::string pushDataThread) //gem::sense::DataSourcePtr externalDataSource
2{
3 int startTimeSec = (int)positionData[0][0];
4 int accelerationIndex = 0, gyroscopeIndex = 0, positionIndex = 0;
5
6 // Create a list of the sensor data types that will be input into the SDK
7 gem::sense::DataTypeList availableDataTypes;
8 availableDataTypes.push_back(gem::sense::EDataType::Position); // Position is mandatory
9 availableDataTypes.push_back(gem::sense::EDataType::Acceleration);
10 availableDataTypes.push_back(gem::sense::EDataType::Gyroscope);
11
12 // Create an external data source to input the above data types into the SDK
13 auto externalDataSourcePair = gem::sense::produceExternalDataSource(availableDataTypes);
14 auto externalDataSource = externalDataSourcePair.first;
15 if ( externalDataSource )
16 {
17 // Add a listener for the data type(s) desired to be received back from the SDK
18 auto listener = gem::StrongPointerFactory<DataReceiver>();
19 externalDataSource->addListener(listener, gem::sense::EDataType::Position);
20 externalDataSource->addListener(listener, gem::sense::EDataType::ImprovedPosition);
21
22 // Start the data source
23 externalDataSource->start();
24 while (true)
25 {
26 GEM_LOGE("PUSH ACCELERATION DATA (input)");
27 while (true)
28 {
29 if (accelerationIndex >= TOTAL_ACCELERATION_RECORDS || startTimeSec < (int)accelerationData[accelerationIndex][0])
30 {
31 break;
32 }
33 externalDataSource->pushData(
34 gem::sense::produceAccelerationData((gem::LargeInteger)(accelerationData[accelerationIndex][0] * 1000.0),
35 (double)accelerationData[accelerationIndex][1],
36 (double)accelerationData[accelerationIndex][2],
37 (double)accelerationData[accelerationIndex][3],
38 gem::sense::EUnitOfMeasurement::G
39 ).first); // ios data is in G, android data is in m/s^2
40 accelerationIndex++;
41 }
42 GEM_LOGE("PUSH GYROSCOPE DATA (input)");
43 while (true)
44 {
45 if (gyroscopeIndex >= TOTAL_GYROSCOPE_RECORDS || startTimeSec < (int)gyroscopeData[gyroscopeIndex][0])
46 {
47 break;
48 }
49 externalDataSource->pushData(
50 gem::sense::produceRotationRateData((gem::LargeInteger)(gyroscopeData[gyroscopeIndex][0] * 1000.0),
51 (double)gyroscopeData[gyroscopeIndex][1],
52 (double)gyroscopeData[gyroscopeIndex][2],
53 (double)gyroscopeData[gyroscopeIndex][3]
54 ).first);
55 gyroscopeIndex++;
56 }
57 while (true)
58 {
59 if (positionIndex >= TOTAL_POSITION_RECORDS || startTimeSec < (int)positionData[positionIndex][0])
60 {
61 break;
62 }
63 GEM_LOGE("PUSH POSITION DATA RECORD %d t = %d sec (input) lon,lat( %.8f, %.8f ); gyro rec %d; acceleration rec %d",
64 positionIndex, startTimeSec, (double)positionData[positionIndex][3], (double)positionData[positionIndex][2], gyroscopeIndex, accelerationIndex);
65 externalDataSource->pushData(
66 gem::sense::producePositionData((gem::LargeInteger)(positionData[positionIndex][0] * 1000.0),
67 (double)positionData[positionIndex][2], // Latitude
68 (double)positionData[positionIndex][3], // Longitude
69 (double)positionData[positionIndex][4], // Altitude (m)
70 (double)positionData[positionIndex][6], // Heading (deg east of north)
71 (double)positionData[positionIndex][5] // Speed (m/s)
72 ).first);
73 positionIndex++;
74 }
75 startTimeSec++;
76
77 // Push / feed / input one position / second to the SDK, along with all other sensor measurements during that second
78 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
79 }
80 GEM_LOGE("DATA INPUT COMPLETE");
81 }
82}
The other thread is the SDK receiving the sensor data and printing it to the debug console, also indicating the type of data received. A custom
DataSourceListener
class is defined to enable receiving processed data from the SDK. Although only 3 types of sensors are fed into the SDK, this class shows how to detect all possible processed sensor output from the SDK. The customDataSourceListener
must implement theonNewData()
function to enable receiving processed data from the SDK.
1class DataReceiver : public gem::sense::IDataSourceListener
2{
3public:
4 DataReceiver() {}
5 void onNewData(gem::sense::DataPtr data) override
6 {
7 auto dataType = data.get()->getType();
8 switch((int)dataType)
9 {
10 case (int)gem::sense::EDataType::Acceleration:
11 GEM_LOGE("RECEIVED NEW DATA - Acceleration"); break;
12 case (int)gem::sense::EDataType::Activity:
13 GEM_LOGE("RECEIVED NEW DATA - Activity"); break;
14 case (int)gem::sense::EDataType::Attitude:
15 GEM_LOGE("RECEIVED NEW DATA - Attitude"); break;
16 case (int)gem::sense::EDataType::Battery:
17 GEM_LOGE("RECEIVED NEW DATA - Battery"); break;
18 case (int)gem::sense::EDataType::Camera:
19 GEM_LOGE("RECEIVED NEW DATA - Camera"); break;
20 case (int)gem::sense::EDataType::Compass:
21 GEM_LOGE("RECEIVED NEW DATA - Compass"); break;
22 case (int)gem::sense::EDataType::MagneticField:
23 GEM_LOGE("RECEIVED NEW DATA - MagneticField"); break;
24 case (int)gem::sense::EDataType::Orientation:
25 GEM_LOGE("RECEIVED NEW DATA - Orientation"); break;
26 case (int)gem::sense::EDataType::Position:
27 {
28 auto dataPosition = data.get()->cast<gem::sense::IPosition>();
29 double alt = dataPosition.get()->getAltitude();
30 double lat = dataPosition.get()->getLatitude();
31 double lon = dataPosition.get()->getLongitude();
32 GEM_LOGE("RECEIVED NEW DATA - Position lon,lat( %.8f, %.8f ); alt %f (inout)", lon, lat, alt);
33 break;
34 }
35 case (int)gem::sense::EDataType::ImprovedPosition:
36 {
37 auto dataImprovedPosition = data.get()->cast<gem::sense::IImprovedPosition>();
38 unsigned long timeStamp = dataImprovedPosition.get()->getSatelliteTime();
39 double alt = dataImprovedPosition.get()->getAltitude();
40 double lat = dataImprovedPosition.get()->getLatitude();
41 double lon = dataImprovedPosition.get()->getLongitude();
42 double speed = dataImprovedPosition.get()->getSpeed();
43 double speedLimit = dataImprovedPosition.get()->getRoadSpeedLimit();
44 double heading = dataImprovedPosition.get()->getCourse();
45 double horizAccuracy = dataImprovedPosition.get()->getHorizontalAccuracy();
46 GEM_LOGE("RECEIVED NEW DATA - ImprovedPosition[map matched] timestamp( %lu ); "
47 "lon,lat( %.8f, %.8f ); alt %dm; heading %.1f(deg); horizAcc %.1fm; "
48 "speed %f(m/sec), %.1f(km/h); speed limit %.1f(m/s) (output)",
49 timeStamp, lon, lat, (int)alt, heading, horizAccuracy, speed, speed*3.6, speedLimit );
50 break;
51 }
52 case (int)gem::sense::EDataType::Gyroscope:
53 GEM_LOGE("RECEIVED NEW DATA - Gyroscope"); break;
54 case (int)gem::sense::EDataType::Temperature:
55 GEM_LOGE("RECEIVED NEW DATA - Temperature"); break;
56 case (int)gem::sense::EDataType::Notification:
57 GEM_LOGE("RECEIVED NEW DATA - Notification"); break;
58 case (int)gem::sense::EDataType::MountInformation:
59 GEM_LOGE("RECEIVED NEW DATA - MountInformation"); break;
60 default:
61 GEM_LOGE("RECEIVED NEW DATA - UNDEFINED"); break;
62 }
63 }
64};