Agrarsense
TransformSensor.cpp
Go to the documentation of this file.
1// Copyright (c) 2023 FrostBit Software Lab at the Lapland University of Applied Sciences
2//
3// This work is licensed under the terms of the MIT license.
4// For a copy, see <https://opensource.org/licenses/MIT>.
5
6#include "TransformSensor.h"
7
11
12#include "GeoReferencingSystem.h"
13#include "Kismet/GameplayStatics.h"
14#include "Engine/World.h"
15
16ATransformSensor::ATransformSensor(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
17{
18 // TickManager handles this Actor ticking
19 PrimaryActorTick.bCanEverTick = false;
20}
21
22void ATransformSensor::Init(FTransformSensorParameters Params, bool SimulateSensor)
23{
24 // Disable this actor's collisions.
25 SetActorEnableCollision(false);
26
27 SetSimulateSensor(SimulateSensor);
28 Parameters = Params;
29
30 ROSMessages::geometry_msgs::Transform transformMsg;
31 TransformMessage = MakeShared<ROSMessages::geometry_msgs::Transform>(transformMsg);
32
33 NavSatFixMessage = MakeShared<ROSMessages::sensor_msgs::NavSatFix>();
34 NavSatFixMessage->header.frame_id = "world";
35 NavSatFixMessage->position_covariance = {
36 1.0, 0, 0,
37 0, 1.0, 0,
38 0, 0, 1.0
39 };
40 NavSatFixMessage->position_covariance_type = NavSatFixMessage->CovarianceType::COVARIANCE_TYPE_DIAGONAL_KNOWN;
41 NavSatFixMessage->status.status = NavSatFixMessage->status.STATUS_FIX;
42 NavSatFixMessage->status.service = NavSatFixMessage->status.SERVICE_GPS;
43
46}
47
49{
50 Super::BeginPlay();
51
52 SetActorEnableCollision(false);
53
54 TickEntry = ATickManager::AddTick(this, BindTick(this, &ATransformSensor::TickParallel), ETickType::LateTickParallel);
55}
56
57void ATransformSensor::EndPlay(const EEndPlayReason::Type EndPlayReason)
58{
60
62
64 TransformMessage.Reset();
65 NavSatFixMessage.Reset();
66
67 Super::EndPlay(EndPlayReason);
68}
69
71{
72 Super::CreateROSTopic();
73
74 GeoReferencingSystem = AGeoReferencingSystem::GetGeoReferencingSystem(GetWorld());
75
76 // If map has been georeferenced, create gnss and navsatfix topics.
78 {
79 FString BaseTopicName = FString::Printf(TEXT("/agrarsense/out/sensors/%s"), *GetSensorIdentifier());
80 FString NavSatFixTopicName;
81
82 int32 LastSlashIndex = BaseTopicName.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
83 if (LastSlashIndex != INDEX_NONE)
84 {
85 NavSatFixTopicName = BaseTopicName.Left(LastSlashIndex) + TEXT("/navsatfix");
86 }
87 else
88 {
89 NavSatFixTopicName = BaseTopicName + TEXT("/navsatfix");
90 }
91
92 NavSatFixTopic = NewObject<UTopic>(UTopic::StaticClass());
94 {
95 NavSatFixTopic->Init(ROSInstance->ROSIntegrationCore, NavSatFixTopicName, TEXT("sensor_msgs/NavSatFix"));
96 NavSatFixTopic->Advertise();
97 }
98 }
99}
100
102{
103 if (NavSatFixTopic)
104 {
105 NavSatFixTopic->Unadvertise();
106 NavSatFixTopic->Unsubscribe();
107 NavSatFixTopic->MarkAsDisconnected();
108 NavSatFixTopic->ConditionalBeginDestroy();
109 NavSatFixTopic = nullptr;
110 }
111
112 if (ROSTopic)
113 {
114 ROSTopic->Unadvertise();
115 ROSTopic->Unsubscribe();
116 ROSTopic->MarkAsDisconnected();
117 ROSTopic->ConditionalBeginDestroy();
118 ROSTopic = nullptr;
119 }
120}
121
123{
124 if (!CanSimulateSensor())
125 {
126 return;
127 }
128
129 FTransform CurrentTransform;
130 const bool HasValidTransform =
131 (Parameters.UseOwningActorTransform && Parameters.OwningActor && (CurrentTransform = Parameters.OwningActor->GetTransform(), true)) ||
132 (Parameters.PrimitiveComponent && Parameters.UsePrimiteRelativeTransform && (CurrentTransform = Parameters.PrimitiveComponent->GetRelativeTransform(), true)) ||
133 (Parameters.PrimitiveComponent && !Parameters.UsePrimiteRelativeTransform && (CurrentTransform = Parameters.PrimitiveComponent->GetComponentTransform(), true));
134
135 if (!HasValidTransform)
136 {
137 return;
138 }
139
140 const FVector CurrentPosition = CurrentTransform.GetLocation();
141 const FQuat CurrentRotation = CurrentTransform.GetRotation();
142
143 const float PositionToleranceSq = 0.01f;
144 const float RotationTolerance = 0.001f;
145
146 const bool PositionChanged = FVector::DistSquared(CurrentPosition, PreviousPosition) > PositionToleranceSq;
147 const bool RotationChanged = !CurrentRotation.Equals(PreviousRotation, RotationTolerance);
148
149 TimeSinceLastPublish += DeltaTime;
150
151 if (PositionChanged || RotationChanged || TimeSinceLastPublish >= 1.0f)
152 {
154 PreviousPosition = CurrentPosition;
155 PreviousRotation = CurrentRotation;
156
157 if (IsROSConnected())
158 {
159 SendTransformDataToROS(CurrentPosition, CurrentRotation);
160 SendGpsDataToROS(CurrentPosition);
161 }
162
163 SaveTransformMetaDataToDisk(CurrentPosition, CurrentRotation);
164 }
165}
166
167void ATransformSensor::SendTransformDataToROS(const FVector& Translation, const FQuat& Rotation)
168{
169 UTopic* Topic = GetROSTopic();
170 if (Topic && TransformMessage.IsValid())
171 {
172 TransformMessage->translation = Translation;
173 TransformMessage->rotation = Rotation;
174
175 Topic->Publish(TransformMessage);
176 }
177}
178
179void ATransformSensor::SendGpsDataToROS(const FVector& ActorPosition)
180{
182 {
183 return;
184 }
185
186 // Convert Actor position to geographic coordinates
187 FGeographicCoordinates GeographicCoordinates = UCoordinateConversionUtilities::UnrealToGeographicCoordinates(GeoReferencingSystem, ActorPosition);
188 const double Latitude = GeographicCoordinates.Latitude;
189 const double Longitude = GeographicCoordinates.Longitude;
190 const double Altitude = GeographicCoordinates.Altitude;
191
192 // gps data as NavSatFix ROS message
193 if (NavSatFixTopic && NavSatFixMessage.IsValid())
194 {
195 NavSatFixMessage->latitude = Latitude;
196 NavSatFixMessage->longitude = Longitude;
197 NavSatFixMessage->altitude = Altitude;
198 NavSatFixMessage->header.time = FROSTime::Now();
199
201 }
202}
203
204void ATransformSensor::SaveTransformMetaDataToDisk(const FVector& ActorPosition, const FQuat& Rotation)
205{
207 {
208 return;
209 }
210
211 const FRotator ActorRotation = Rotation.Rotator();
212
213 FString MetaData;
214
215 FString TimeStamp = CreateTimeStampString();
216
218 {
219 const FGeographicCoordinates GeographicCoordinates = UCoordinateConversionUtilities::UnrealToGeographicCoordinates(GeoReferencingSystem, ActorPosition);
220 const double Latitude = GeographicCoordinates.Latitude;
221 const double Longitude = GeographicCoordinates.Longitude;
222 const double Altitude = GeographicCoordinates.Altitude;
223
224 MetaData = FString::Printf(TEXT("%s, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.8f, %.8f, %.8f"), *TimeStamp, ActorPosition.X, ActorPosition.Y, ActorPosition.Z,
225 ActorRotation.Pitch, ActorRotation.Yaw, ActorRotation.Roll,
226 GeographicCoordinates.Latitude, GeographicCoordinates.Longitude, GeographicCoordinates.Altitude);
227 }
228 else
229 {
230 MetaData = FString::Printf(TEXT("%s, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f"), *TimeStamp, ActorPosition.X, ActorPosition.Y, ActorPosition.Z,
231 ActorRotation.Pitch, ActorRotation.Yaw, ActorRotation.Roll);
232 }
233
234 // Write to the log file (this is written after sensor is destroyed)
235 WriteToLogFile(MetaData);
236}
237
239{
241 {
242 return;
243 }
244
246
247 FLogFileSettings Settings;
250 Settings.QueueLength = MAX_int32; // Only write the log after destroying the sensor
251 Settings.KeepFileOpen = false;
252 Settings.Timestamp = false;
253 Settings.OverrideFilePath = true;
254 Settings.FilePath = FileSavePath;
255
256 LogFile = NewObject<ULogFile>(ULogFile::StaticClass());
257 if (IsValid(LogFile))
258 {
259 FString Prefix;
261 {
262 Prefix = Vehicle->GetActorID_Implementation();
263 }
264 FString FileName = Prefix.IsEmpty() ? TEXT("transform") : Prefix + TEXT("_transform");
265
266 LogFile->Create(FileName, Settings);
267
268 AGeoReferencingSystem* GeoRef = AGeoReferencingSystem::GetGeoReferencingSystem(GetWorld());
269 if (GeoRef)
270 {
271 WriteToLogFile("timestamp, X location, Y location, Z location, yaw rotation, pitch rotation, roll rotation, GPS latitude, GPS longitude, GPS altitude");
272 }
273 else
274 {
275 WriteToLogFile("timestamp, X location, Y location, Z location, yaw rotation, pitch rotation, roll rotation");
276 }
277 }
278}
279
281{
282 if (!FileSavePath.IsEmpty())
283 {
284 return;
285 }
286
287 const FString DataLocation = UAgrarsensePaths::GetDataFolder();
288
289 // Default save path
290 FileSavePath = DataLocation + GetActorID_Implementation() + "/";
291
293 {
294 // If sensor is attached to vehicle, save data to vehicle folder
295 const FString VehicleName = Vehicle->GetActorID_Implementation();
296 FileSavePath = DataLocation + VehicleName;
297 }
298 else if (ParentActor)
299 {
300 if (ASensor* ParentSensor = Cast<ASensor>(ParentActor))
301 {
302 if (AVehicle* SensorVehicle = ParentSensor->IsAttachedToVehicle())
303 {
304 // If this TransformSensor is attached to a sensor that's attached to a vehicle,
305 // save data to Vehicle/Sensor folder
306 const FString VehicleName = SensorVehicle->GetActorID_Implementation();
307 const FString ParentSensorName = ParentSensor->GetActorID_Implementation();
308 FileSavePath = DataLocation + VehicleName + "/" + ParentSensorName;
309 }
310 }
311 }
312}
static auto BindTick(ObjectType *Object, FunctionType Function)
Definition: TickManager.h:162
Definition: Sensor.h:45
UROSIntegrationGameInstance * ROSInstance
Definition: Sensor.h:375
bool CanSimulateSensor() const
Definition: Sensor.h:170
FString GetSensorIdentifier() const
Definition: Sensor.h:75
UTopic * ROSTopic
Definition: Sensor.h:361
AActor * ParentActor
Definition: Sensor.h:370
void SetSimulateSensor(bool SimulateSensor)
Definition: Sensor.h:160
UTopic * GetROSTopic() const
Definition: Sensor.h:150
AVehicle * IsAttachedToVehicle() const
Definition: Sensor.cpp:159
ULogFile * LogFile
Definition: Sensor.h:367
virtual FString GetActorID_Implementation() const override
Definition: Sensor.h:216
FString CreateTimeStampString() const
Definition: Sensor.cpp:330
FString FileSavePath
Definition: Sensor.h:372
void WriteToLogFile(const FString &Message)
Definition: Sensor.cpp:302
FORCEINLINE bool IsROSConnected() const
Definition: Sensor.h:201
static void RemoveTick(FTickEntry TickEntry)
Definition: TickManager.cpp:80
static FTickEntry AddTick(UObject *Object, std::function< void(float)> Function, ETickType Type)
Definition: TickManager.cpp:51
void SendTransformDataToROS(const FVector &Translation, const FQuat &Rotation)
void CreateDataSavePath() override
virtual void BeginPlay() override
void SendGpsDataToROS(const FVector &ActorPosition)
UTopic * NavSatFixTopic
TSharedPtr< ROSMessages::geometry_msgs::Transform > TransformMessage
void SaveTransformMetaDataToDisk(const FVector &Translation, const FQuat &Rotation)
void CreateLogFile() override
TSharedPtr< ROSMessages::sensor_msgs::NavSatFix > NavSatFixMessage
virtual void CreateROSTopic() override
FVector PreviousPosition
FTransformSensorParameters Parameters
virtual void DestroyROSTopic() override
AGeoReferencingSystem * GeoReferencingSystem
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
FTickEntry TickEntry
void TickParallel(float DeltaTime)
void Init(FTransformSensorParameters Params, bool SimulateSensor=true)
ATransformSensor(const FObjectInitializer &ObjectInitializer)
static FString GetDataFolder()
static FGeographicCoordinates UnrealToGeographicCoordinates(AGeoReferencingSystem *GeoReferencingSystem, const FVector &Position)
void Create(const FString &FileNameWithoutExtension, FLogFileSettings Settings)
Definition: LogFile.cpp:40
bool KeepFileOpen
Definition: LogFile.h:42
bool Timestamp
Definition: LogFile.h:39
FString FilePath
Definition: LogFile.h:54
FFileWriteOptions FileWriteOptions
Definition: LogFile.h:45
int32 QueueLength
Definition: LogFile.h:48
bool OverrideFilePath
Definition: LogFile.h:51
FFileCreationOptions FileCreationOptions
Definition: LogFile.h:36
UPrimitiveComponent * PrimitiveComponent