INDI Alignment Layer
0.0
|
00001 00002 #include "indidevapi.h" // For IDLog 00003 #include "AlignmentDatabase.h" 00004 #include <memory> 00005 #include <cmath> 00006 #include <sys/time.h> 00007 #include <limits> 00008 #include "Wm5DistSegment3Segment3.h" 00009 #include "Wm5IntrSegment3Triangle3.h" 00010 #include <fstream> 00011 00012 00013 CAlignmentDatabase::CAlignmentDatabase() 00014 { 00015 Wm5::Memory::Initialize(); 00016 LowerCoordinateSystemUnits = HOURS_DEGREES; 00017 00018 #ifdef LOAD_TEST_DATA 00019 sleep(30); // Give time to attach debugger 00020 AddAlignmentPoint(5.619669, 0.03447, 0.506809, 1.732239, 1.463808); 00021 AddAlignmentPoint(5.659376, 0.618501, 1.557218, 5.427625, 0.611563); 00022 RecalculateDatabase(); 00023 #endif 00024 } 00025 00026 CAlignmentDatabase::~CAlignmentDatabase() 00027 { 00028 Wm5::Memory::Terminate("WM5MemoryDebug.txt"); 00029 } 00030 00031 bool CAlignmentDatabase::AddAlignmentPoint(const double ReferenceTime, const double RightAscension, const double Declination, 00032 const double Axis1, const double Axis2, bool Recalculate) 00033 { 00034 // Save the new alignment point 00035 AlignmentPoints.push_back(AlignmentPoint(ReferenceTime, RightAscension, Declination, Axis1, Axis2)); 00036 00037 if (Recalculate) 00038 return RecalculateDatabase(); 00039 else 00040 return true; 00041 } 00042 00043 bool CAlignmentDatabase::RecalculateDatabase() 00044 { 00045 Wm5::Vector3d* UpperDirectionCosines; 00046 Wm5::Vector3d* LowerDirectionCosines; 00047 std::auto_ptr<Wm5::ConvexHull3d> UpperLayerConvexHull; 00048 00049 // TODO make this work with one and two alignment points 00050 if (AlignmentPoints.size() < 3) 00051 { 00052 IDLog("CAlignmentDatabase::RecalculateDatabase - Too few alignment points to generate matrices\n"); 00053 return true; 00054 } 00055 00056 // Allocate Vector3 arrays large enough to hold all the direction cosines 00057 UpperDirectionCosines = new Wm5::Vector3d[AlignmentPoints.size() + 1]; 00058 LowerDirectionCosines = new Wm5::Vector3d[AlignmentPoints.size() + 1]; 00059 00060 // Set centre points to complete convex hulls 00061 UpperDirectionCosines[0] = Wm5::Vector3d(0.0, 0.0, 0.0); 00062 LowerDirectionCosines[0] = Wm5::Vector3d(0.0, 0.0, 0.0); 00063 00064 // Iterate through all the alignment points generating new sets of rectangular coordinates (direction cosines) to give to the convex hull classes 00065 int iCount = 1; 00066 for (std::vector<AlignmentPoint>::iterator i = AlignmentPoints.begin() ; i != AlignmentPoints.end(); i++) 00067 { 00068 UpperDirectionCosines[iCount] = MakeUpperDirectionCosine((*i).RightAscension, (*i).Declination, (*i).ReferenceTime); 00069 00070 LowerDirectionCosines[iCount] = MakeLowerDirectionCosine((*i).Axis1, (*i).Axis2, (*i).ReferenceTime); 00071 00072 iCount++; 00073 } 00074 00075 00076 // Set up the hulls 00077 UpperLayerConvexHull.reset(new Wm5::ConvexHull3d(iCount, UpperDirectionCosines, Epsilon, false, Wm5::Query::QT_RATIONAL)); 00078 00079 // Sanity checks 00080 int UpperLayerDimension = UpperLayerConvexHull->GetDimension(); 00081 int UpperLayerFacets = UpperLayerConvexHull->GetNumSimplices(); 00082 const int *UpperIndices = UpperLayerConvexHull->GetIndices(); 00083 if (UpperLayerDimension < 3) 00084 { 00085 IDLog("CAlignmentDatabase::AddAlignmentPoint - UpperLayerConvexHull not 3d (%d)\n", UpperLayerDimension); 00086 delete[] UpperDirectionCosines; 00087 delete[] LowerDirectionCosines; 00088 return false; 00089 } 00090 00091 // Reset the database 00092 UpperLayerTinFacets.clear(); 00093 LowerLayerTinFacets.clear(); 00094 00095 // Iterate through the facets of the upper layer convex hull; 00096 for (int i = 0; i < UpperLayerFacets; i++) 00097 { 00098 TinFacet UpperLayerFacet; 00099 TinFacet LowerLayerFacet; 00100 00101 // Construct the triangular facets 00102 UpperLayerFacet.Facet = Wm5::Triangle3d(UpperDirectionCosines[UpperIndices[3 * i + 0]], 00103 UpperDirectionCosines[UpperIndices[3 * i + 1]], 00104 UpperDirectionCosines[UpperIndices[3 * i + 2]]); 00105 LowerLayerFacet.Facet = Wm5::Triangle3d(LowerDirectionCosines[UpperIndices[3 * i + 0]], 00106 LowerDirectionCosines[UpperIndices[3 * i + 1]], 00107 LowerDirectionCosines[UpperIndices[3 * i + 2]]); 00108 00109 // Compute the matrices 00110 00111 Wm5::Vector3d UpperVertex1 = UpperLayerFacet.Facet.V[0]; 00112 Wm5::Vector3d UpperVertex2 = UpperLayerFacet.Facet.V[1]; 00113 Wm5::Vector3d UpperVertex3 = UpperLayerFacet.Facet.V[2]; 00114 Wm5::Vector3d LowerVertex1 = LowerLayerFacet.Facet.V[0]; 00115 Wm5::Vector3d LowerVertex2 = LowerLayerFacet.Facet.V[1]; 00116 Wm5::Vector3d LowerVertex3 = LowerLayerFacet.Facet.V[2]; 00117 00118 if ((UpperVertex1 == Wm5::Vector3d::ZERO) || 00119 (UpperVertex2 == Wm5::Vector3d::ZERO) || 00120 (UpperVertex3 == Wm5::Vector3d::ZERO)) 00121 { 00122 // One of the vertices is the origin. So this facet does not lie on the 00123 // surface of the unit sphere. However the two vertices that are not the 00124 // origin will define a line segment of the border of the triangulated area (TIN) 00125 // Compute an artificially generated third point (vector cross product) to replace 00126 // the origin vertex 00127 Wm5::Segment3d BoundarySegment; 00128 if (UpperVertex1 == Wm5::Vector3d::ZERO) 00129 { 00130 UpperVertex1 = UpperVertex2.UnitCross(UpperVertex3); 00131 LowerVertex1 = LowerVertex2.UnitCross(LowerVertex3); 00132 } 00133 else if (UpperVertex2 == Wm5::Vector3d::ZERO) 00134 { 00135 UpperVertex2 = UpperVertex1.UnitCross(UpperVertex3); 00136 LowerVertex2 = LowerVertex1.UnitCross(LowerVertex3); 00137 } 00138 else 00139 { 00140 UpperVertex3 = UpperVertex1.UnitCross(UpperVertex2); 00141 LowerVertex3 = LowerVertex1.UnitCross(LowerVertex2); 00142 } 00143 } 00144 00145 Wm5::Matrix3d UpperMatrix(UpperVertex1.X(), UpperVertex2.X(), UpperVertex3.X(), 00146 UpperVertex1.Y(), UpperVertex2.Y(), UpperVertex3.Y(), 00147 UpperVertex1.Z(), UpperVertex2.Z(), UpperVertex3.Z()); 00148 Wm5::Matrix3d LowerMatrix(LowerVertex1.X(), LowerVertex2.X(), LowerVertex3.X(), 00149 LowerVertex1.Y(), LowerVertex2.Y(), LowerVertex3.Y(), 00150 LowerVertex1.Z(), LowerVertex2.Z(), LowerVertex3.Z()); 00151 UpperLayerFacet.TransformationMatrix = LowerMatrix * UpperMatrix.Inverse(); 00152 LowerLayerFacet.TransformationMatrix = UpperLayerFacet.TransformationMatrix.Inverse(); 00153 00154 // Update the database 00155 UpperLayerTinFacets.push_back(UpperLayerFacet); 00156 LowerLayerTinFacets.push_back(LowerLayerFacet); 00157 } 00158 delete[] UpperDirectionCosines; 00159 delete[] LowerDirectionCosines; 00160 return true; 00161 } 00162 00163 bool CAlignmentDatabase::TransformUpperToLower(const double RightAscension, const double Declination, double& Axis1, double& Axis2) 00164 { 00165 const double ApproximateGMST = UTCNow(); 00166 00167 Wm5::Vector3d UpperDirectionCosine = MakeUpperDirectionCosine(RightAscension, Declination, ApproximateGMST); 00168 00169 Wm5::Matrix3d TransformationMatrix = FindTransformationmatrix(UpperDirectionCosine, UPPER_TO_LOWER); 00170 00171 Wm5::Vector3d LowerDirectionCosine = TransformationMatrix * UpperDirectionCosine; 00172 00173 double Polar1 = atan2(LowerDirectionCosine[1], LowerDirectionCosine[0]); // Radians minus PI to plus PI 00174 double Polar2 = asin(LowerDirectionCosine[2]); // Radians minus PI/2 to plus PI/2 00175 00176 switch (LowerCoordinateSystemUnits) 00177 { 00178 case HOURS_DEGREES: 00179 { 00180 Axis1 = DummyHourAngleToRightAscension(Polar1, ApproximateGMST); 00181 Axis2 = Polar2 * 180.0 / M_PI; 00182 break; 00183 } 00184 case DEGREES_DEGREES: 00185 { 00186 Axis1 = Polar1 * 180.0 / M_PI; 00187 Axis2 = Polar2 * 180.0 / M_PI; 00188 break; 00189 } 00190 default: 00191 case RADIANS_RADIANS: 00192 { 00193 Axis1 = Polar1; 00194 Axis2 = Polar2; 00195 break; 00196 } 00197 } 00198 00199 IDLog("CAlignmentDatabase::TransformUpperToLower RightAscension %g Declination %g Axis1 %g Axis2 %g\n", RightAscension, 00200 Declination, 00201 Axis1, 00202 Axis2); 00203 00204 return true; 00205 } 00206 00207 bool CAlignmentDatabase::TransformLowerToUpper(const double Axis1, const double Axis2, double& RightAscension, double& Declination) 00208 { 00209 const double ApproximateGMST = UTCNow(); 00210 Wm5::Vector3d LowerDirectionCosine = MakeLowerDirectionCosine(Axis1, Axis2, ApproximateGMST); 00211 00212 Wm5::Matrix3d TransformationMatrix = FindTransformationmatrix(LowerDirectionCosine, LOWER_TO_UPPER); 00213 00214 Wm5::Vector3d UpperDirectionCosine = TransformationMatrix * LowerDirectionCosine; 00215 RightAscension = DummyHourAngleToRightAscension(atan2(UpperDirectionCosine[1], UpperDirectionCosine[0]), ApproximateGMST); 00216 Declination = asin(UpperDirectionCosine[2]) * 180.0 / M_PI; // minus PI/2 to plus PI/2 00217 00218 IDLog("CAlignmentDatabase::TransformLowerToUpper Axis1 %g Axis2 %g RightAscension %g Declination %g\n", Axis1, 00219 Axis2, 00220 RightAscension, 00221 Declination); 00222 00223 return true; 00224 } 00225 00226 double CAlignmentDatabase::RightAscensionToDummyHourAngle(const double RightAscension, const double ReferenceTime) 00227 { 00228 double DummyHourAngle = (RightAscension * M_PI / 12.0) - 1.002737908 * (ReferenceTime * M_PI / 12.0); 00229 IDLog("CAlignmentDatabase::RightAscensionToDummyHourAngle - RightAscension %g ReferenceTime %g DummyHourAngle %g\n", RightAscension, 00230 ReferenceTime, 00231 DummyHourAngle); 00232 return DummyHourAngle; 00233 } 00234 00235 double CAlignmentDatabase::DummyHourAngleToRightAscension(const double DummyHourAngle, const double ReferenceTime) 00236 { 00237 // Normalise the hour angle; 00238 double NormalisedHourAngle; 00239 if (DummyHourAngle < 0) 00240 NormalisedHourAngle = 2 * M_PI + DummyHourAngle; 00241 else 00242 NormalisedHourAngle = DummyHourAngle; 00243 double RightAscension = (NormalisedHourAngle * 12.0 / M_PI) + 1.002737908 * ReferenceTime; 00244 IDLog("CAlignmentDatabase::DummyHourAngleToRightAscension - DummyHourAngle %g ReferenceTime %g RightAscension %g\n", DummyHourAngle, 00245 ReferenceTime, 00246 RightAscension); 00247 return RightAscension; 00248 } 00249 00250 Wm5::Vector3d CAlignmentDatabase::MakeUpperDirectionCosine(const double RightAscension, const double Declination, const double ReferenceTime) 00251 { 00252 double DeclinationInRadians = Declination * M_PI / 180.0; 00253 return Wm5::Vector3d(cos(DeclinationInRadians) * cos(RightAscensionToDummyHourAngle(RightAscension, ReferenceTime)), 00254 cos(DeclinationInRadians) * sin(RightAscensionToDummyHourAngle(RightAscension, ReferenceTime)), 00255 sin(DeclinationInRadians)); 00256 } 00257 00258 Wm5::Vector3d CAlignmentDatabase::MakeLowerDirectionCosine(const double Axis1, const double Axis2, const double ReferenceTime) 00259 { 00260 switch (LowerCoordinateSystemUnits) 00261 { 00262 case HOURS_DEGREES: 00263 { 00264 double RA = RightAscensionToDummyHourAngle(Axis1, ReferenceTime); 00265 double Dec = Axis2 * M_PI / 180.0; 00266 return Wm5::Vector3d(cos(Dec) * cos(RA), cos(Dec) * sin(RA), sin(Dec)); 00267 } 00268 case DEGREES_DEGREES: 00269 { 00270 00271 double Azimuth = -Axis1 * M_PI / 180.0; 00272 double Altitude = Axis2 * M_PI / 180.0; 00273 return Wm5::Vector3d(cos(Altitude) * cos(Azimuth), cos(Altitude) * sin(Azimuth), sin(Altitude)); 00274 } 00275 default: 00276 case RADIANS_RADIANS: 00277 { 00278 double Azimuth = -Axis1; 00279 double Altitude = Axis2; 00280 return Wm5::Vector3d(cos(Altitude) * cos(Azimuth), cos(Altitude) * sin(Azimuth), sin(Altitude)); 00281 } 00282 } 00283 } 00284 00285 const double CAlignmentDatabase::UTCNow() 00286 { 00287 // I would really like this to be UT1Now but that would be harder 00288 // this will be within 0.9 seconds of UT1 00289 struct timeval now; 00290 gettimeofday(&now,NULL); 00291 struct tm breakdown = *gmtime(&now.tv_sec); 00292 return breakdown.tm_hour + breakdown.tm_min / 60.0 + breakdown.tm_sec / 3600.0 + now.tv_usec / 3600000000.0; 00293 } 00294 00295 const Wm5::Matrix3d CAlignmentDatabase::FindTransformationmatrix(const Wm5::Vector3d DirectionCosine, Direction Direction) 00296 { 00297 Wm5::Matrix3d TransformationMatrix; 00298 00299 TinFacets& Facets = Direction == UPPER_TO_LOWER ? UpperLayerTinFacets : LowerLayerTinFacets; 00300 00301 // Make the direction cosine (should be a unit vector) a bit longer just in case. 00302 Wm5::Segment3d DirectionSegment(Wm5::Vector3d::ZERO, DirectionCosine * 2.0); 00303 00304 // The facets should all approximate to a patch on the surface of a unit sphere centered on the origin. 00305 // The only exception being those facets with the origin as a vertex. 00306 // So iterate through the facets and for each facet that has the origin as vertex check the distance of closest approach 00307 // between my DirectionVector and the line segment formed by the other two facet vertices. If the facet does not have the 00308 // origin as vertex check whether the vector intersects the surface patch. If the vector intersects then return the 00309 // TrasnformationMatrix from that facet. If I reach the end without intersecting the return the TransformationMatrix from the 00310 // origin facet the vector passes nearest to (whew!). 00311 00312 TinFacets::iterator NearestOriginFacet; 00313 double DistanceToNearestOriginSegment = std::numeric_limits<double>::max(); 00314 00315 for (TinFacets::iterator i = Facets.begin(); i != Facets.end(); i++) 00316 { 00317 Wm5::Vector3d Vertex1 = (*i).Facet.V[0]; 00318 Wm5::Vector3d Vertex2 = (*i).Facet.V[1]; 00319 Wm5::Vector3d Vertex3 = (*i).Facet.V[2]; 00320 00321 if ((Vertex1 == Wm5::Vector3d::ZERO) || 00322 (Vertex2 == Wm5::Vector3d::ZERO) || 00323 (Vertex3 == Wm5::Vector3d::ZERO)) 00324 { 00325 Wm5::Segment3d BoundarySegment; 00326 if (Vertex1 == Wm5::Vector3d::ZERO) 00327 BoundarySegment = Wm5::Segment3d(Vertex2, Vertex3); 00328 else if (Vertex2 == Wm5::Vector3d::ZERO) 00329 BoundarySegment = Wm5::Segment3d(Vertex1, Vertex3); 00330 else 00331 BoundarySegment = Wm5::Segment3d(Vertex1, Vertex2); 00332 Wm5::DistSegment3Segment3d Distance(DirectionSegment, BoundarySegment); 00333 double DistanceToSegment = Distance.Get(); 00334 if (DistanceToSegment < DistanceToNearestOriginSegment) 00335 { 00336 DistanceToNearestOriginSegment = DistanceToSegment; 00337 NearestOriginFacet = i; 00338 } 00339 } 00340 else 00341 { 00342 Wm5::IntrSegment3Triangle3d Intersection(DirectionSegment, (*i).Facet); 00343 if (Intersection.Test()) 00344 return (*i).TransformationMatrix; 00345 } 00346 } 00347 00348 if (std::numeric_limits<double>::max() == DistanceToNearestOriginSegment) 00349 { 00350 IDLog("CAlignmentDatabase::FindTransformationmatrix - FATAL cannot find transformation matrix\n"); 00351 return Wm5::Matrix3d::IDENTITY; 00352 } 00353 return (*NearestOriginFacet).TransformationMatrix; 00354 } 00355 00356 bool CAlignmentDatabase::Save(const std::string& Filename) 00357 { 00358 std::fstream SavedAlignmentPoints(Filename.c_str(), std::ios_base::out); 00359 00360 for (std::vector<AlignmentPoint>::iterator i = AlignmentPoints.begin() ; i != AlignmentPoints.end() && !SavedAlignmentPoints.fail(); i++) 00361 { 00362 SavedAlignmentPoints << (*i).ReferenceTime << " " 00363 << (*i).RightAscension << " " 00364 << (*i).Declination << " " 00365 << (*i).Axis1 << " " 00366 << (*i).Axis2 << std::endl; 00367 } 00368 if (SavedAlignmentPoints.fail()) 00369 return false; 00370 SavedAlignmentPoints.close(); 00371 return true; 00372 } 00373 00374 bool CAlignmentDatabase::Load(const std::string& Filename) 00375 { 00376 std::vector<AlignmentPoint> NewAlignmentPoints; 00377 std::fstream SavedAlignmentPoints(Filename.c_str(), std::ios_base::in); 00378 if (!SavedAlignmentPoints) 00379 { 00380 IDLog("CAlignmentDatabase::Load - Failed to open saved points file (%s)\n", Filename.c_str()); 00381 return false; 00382 } 00383 double ReferenceTime; 00384 while (SavedAlignmentPoints >> ReferenceTime) 00385 { 00386 double RightAscension, Declination, Axis1, Axis2; 00387 00388 // Not sure if I should test for eof here - I never really understood iostreams 00389 00390 // Read rest of line 00391 SavedAlignmentPoints >> RightAscension >> Declination >> Axis1 >> Axis2; 00392 00393 if (!SavedAlignmentPoints.eof() && !SavedAlignmentPoints.fail()) 00394 NewAlignmentPoints.push_back(AlignmentPoint(ReferenceTime, RightAscension, Declination, Axis1, Axis2)); 00395 } 00396 if (!SavedAlignmentPoints.eof()) 00397 { 00398 IDLog("CAlignmentDatabase::Load - Failed to read saved points from file (%s)\n", Filename.c_str()); 00399 return false; 00400 } 00401 SavedAlignmentPoints.close(); 00402 AlignmentPoints = NewAlignmentPoints; 00403 RecalculateDatabase(); 00404 return true; 00405 } 00406 00407 bool CAlignmentDatabase::Append(const std::string& Filename) 00408 { 00409 std::vector<AlignmentPoint> NewAlignmentPoints; 00410 std::fstream SavedAlignmentPoints(Filename.c_str(), std::ios_base::in); 00411 if (!SavedAlignmentPoints) 00412 { 00413 IDLog("CAlignmentDatabase::Append - Failed to open saved points file (%s)\n", Filename.c_str()); 00414 return false; 00415 } 00416 double ReferenceTime; 00417 while (SavedAlignmentPoints >> ReferenceTime) 00418 { 00419 double RightAscension, Declination, Axis1, Axis2; 00420 00421 // Not sure if I should test for eof here - I never really understood iostreams 00422 00423 // Read rest of line 00424 SavedAlignmentPoints >> RightAscension >> Declination >> Axis1 >> Axis2; 00425 00426 if (!SavedAlignmentPoints.eof() && !SavedAlignmentPoints.fail()) 00427 NewAlignmentPoints.push_back(AlignmentPoint(ReferenceTime, RightAscension, Declination, Axis1, Axis2)); 00428 } 00429 if (!SavedAlignmentPoints.eof()) 00430 { 00431 IDLog("CAlignmentDatabase::Append - Failed to read saved points from file (%s)\n", Filename.c_str()); 00432 return false; 00433 } 00434 SavedAlignmentPoints.close(); 00435 AlignmentPoints.insert(AlignmentPoints.end(), NewAlignmentPoints.begin(), NewAlignmentPoints.end()); 00436 RecalculateDatabase(); 00437 return true; 00438 } 00439 00440 00441